1 - from aardwolf.extensions.RDPECLIP.protocol.formatlist import CLIPBRD_FORMAT 2 - from aardwolf.commons.queuedata.clipboard import RDP_CLIPBOARD_DATA_TXT 3 1 import sys 4 2 import asyncio 5 3 import traceback 6 4 import queue 7 5 import threading 8 - import concurrent.futures 6 + import time 9 7 10 8 from aardwolf import logger 11 9 from aardwolf.commons.url import RDPConnectionURL skipped 1 lines 13 11 from aardwolf.commons.queuedata import RDPDATATYPE 14 12 from aardwolf.commons.queuedata.keyboard import RDP_KEYBOARD_SCANCODE 15 13 from aardwolf.commons.queuedata.mouse import RDP_MOUSE 14 + from aardwolf.extensions.RDPECLIP.protocol.formatlist import CLIPBRD_FORMAT 15 + from aardwolf.commons.queuedata.clipboard import RDP_CLIPBOARD_DATA_TXT 16 + from aardwolf.commons.queuedata.constants import MOUSEBUTTON, VIDEO_FORMAT 16 17 17 18 from PyQt5.QtWidgets import QApplication, QMainWindow, qApp, QLabel 18 19 from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QThread, Qt skipped 31 lines 50 51 self.input_evt = None 51 52 self.in_q = None 52 53 self.loop_started_evt = threading.Event() 54 + self.gui_stopped_evt = threading.Event() 55 + self.input_handler_thread = None 56 + self.asyncthread:threading.Thread = None 53 57 54 58 def set_settings(self, settings, in_q): 55 59 self.settings = settings skipped 3 lines 59 63 while not self.conn.disconnected_evt.is_set(): 60 64 data = self.in_q.get() 61 65 loop.call_soon_threadsafe(self.conn.ext_in_queue.put_nowait, data) 66 + if data is None: 67 + break 68 + logger.debug('inputhandler terminating') 62 69 63 - #async def inputhandler(self): 64 - # # This is super-ugly but I could not find a better solution 65 - # # Problem is that pyqt5 is not async, and QT internally is using its own event loop 66 - # # which makes everythign a mess. 67 - # # If you know better (that doesn't require a noname unmaintained lib install) lemme know! 68 - # try: 69 - # while not self.conn.disconnected_evt.is_set(): 70 - # try: 71 - # data = self.in_q.get(False) 72 - # except queue.Empty: 73 - # await asyncio.sleep(0.01) 74 - # continue 75 - # await self.conn.ext_in_queue.put(data) 76 - # except: 77 - # traceback.print_exc() 70 + async def ducky_keyboard_sender(self, scancode, is_pressed, as_char = False): 71 + ### Callback function for the duckyexecutor to dispatch scancodes/characters to the remote end 72 + try: 73 + print('SCANCODE: %s' % scancode) 74 + print('is_pressed: %s' % is_pressed) 75 + print('as_char: %s' % as_char) 76 + if as_char is False: 77 + ki = RDP_KEYBOARD_SCANCODE() 78 + ki.keyCode = scancode 79 + ki.is_pressed = is_pressed 80 + ki.modifiers = [] 81 + await self.conn.ext_in_queue.put(ki) 82 + await asyncio.sleep(0.5) 83 + except Exception as e : 84 + traceback.print_exc() 85 + 86 + async def ducky_exec(self): 87 + try: 88 + await asyncio.sleep(5) 89 + from aardwolf.keyboard.layoutmanager import KeyboardLayoutManager 90 + from aardwolf.utils.ducky import DuckyExecutorBase, DuckyReaderFile 91 + layout = KeyboardLayoutManager().get_layout_by_shortname('enus') 92 + executor = DuckyExecutorBase(layout, self.ducky_keyboard_sender) 93 + reader = DuckyReaderFile.from_file(self.settings.iosettings.ducky_file, executor) 94 + await reader.parse() 95 + except Exception as e: 96 + traceback.print_exc() 78 97 79 98 async def rdpconnection(self): 80 99 try: skipped 4 lines 85 104 raise err 86 105 87 106 #asyncio.create_task(self.inputhandler()) 88 - asyncio.get_event_loop().run_in_executor(None, self.inputhandler, asyncio.get_event_loop()) 107 + input_handler_thread = asyncio.get_event_loop().run_in_executor(None, self.inputhandler, asyncio.get_event_loop()) 89 108 self.loop_started_evt.set() 90 - while True: 109 + if self.settings.iosettings.ducky_file is not None: 110 + x = asyncio.create_task(self.ducky_exec()) 111 + while not self.gui_stopped_evt.is_set(): 91 112 data = await self.conn.ext_out_queue.get() 92 113 if data is None: 93 114 return 94 115 if data.type == RDPDATATYPE.VIDEO: 95 - ri = RDPImage(data.x, data.y, data.data, data.height , data.width ) 96 - self.result.emit(ri) 116 + ri = RDPImage(data.x, data.y, data.data, data.width , data.height ) 117 + if not self.gui_stopped_evt.is_set(): 118 + self.result.emit(ri) 119 + else: 120 + return 97 121 elif data.type == RDPDATATYPE.CLIPBOARD_READY: 98 122 continue 123 + elif data.type == RDPDATATYPE.CLIPBOARD_NEW_DATA_AVAILABLE: 124 + continue 125 + elif data.type == RDPDATATYPE.CLIPBOARD_CONSUMED: 126 + continue 127 + elif data.type == RDPDATATYPE.CLIPBOARD_DATA_TXT: 128 + continue 99 129 else: 100 130 logger.debug('Unknown incoming data: %s'% data) 101 131 132 + except asyncio.CancelledError: 133 + return 102 134 103 135 except Exception as e: 104 136 traceback.print_exc() 105 137 finally: 106 - self.connection_terminated.emit() 138 + if self.conn is not None: 139 + await self.conn.terminate() 140 + input_handler_thread.cancel() 141 + if not self.gui_stopped_evt.is_set(): 142 + print(self.connection_terminated) 143 + self.connection_terminated.emit() 107 144 108 145 def starter(self): 109 146 self.loop = asyncio.new_event_loop() 110 147 asyncio.set_event_loop(self.loop) 111 - self.loop.run_until_complete(self.rdpconnection()) 112 - self.loop.close() 148 + try: 149 + self.rdp_connection_task = self.loop.create_task(self.rdpconnection()) 150 + self.loop.run_until_complete(self.rdp_connection_task) 151 + self.loop.close() 152 + except Exception as e: 153 + pass 154 + 113 155 114 156 @pyqtSlot() 115 157 def start(self): 116 158 # creating separate thread for async otherwise this will not return 117 159 # and then there will be no events sent back from application 118 - asyncthread = threading.Thread(target=self.starter, args=()) 119 - asyncthread.start() 160 + self . asyncthread = threading.Thread(target=self.starter, args=()) 161 + self . asyncthread.start() 162 + 163 + @pyqtSlot() 164 + def stop(self): 165 + self.gui_stopped_evt.set() 166 + if self.conn is not None: 167 + asyncio.run_coroutine_threadsafe(self.conn.terminate(), self.loop) 168 + time.sleep(0.1) # waiting connection to terminate 169 + self.rdp_connection_task.cancel() 170 + self.loop.stop() 120 171 121 172 122 173 class RDPClientQTGUI(QMainWindow): skipped 1 lines 124 175 125 176 def __init__(self, settings:RDPClientConsoleSettings): 126 177 super().__init__() 127 - # these sizes dont work :( 128 - #self.setMaximumSize(self.settings.iosettings.video_width, self.settings.iosettings.video_height) 129 - #self.setMinimumSize(self.settings.iosettings.video_width, self.settings.iosettings.video_height) 130 - #self.setFixedSize(self.settings.iosettings.video_width, self.settings.iosettings.video_height) 131 178 self.settings = settings 132 179 133 180 # enabling this will singificantly increase the bandwith 134 181 self.mhover = settings.mhover 135 182 # enabling keyboard tracking 136 183 self.keyboard = settings.keyboard 184 + self.is_rdp = True if settings.url.lower().startswith('rdp') is True else False 137 185 138 186 # setting up the main window with the requested resolution 139 187 self.setGeometry(0,0, self.settings.iosettings.video_width, self.settings.iosettings.video_height) skipped 26 lines 166 214 # enabling mouse tracking 167 215 self.setMouseTracking(True) 168 216 self._label_imageDisplay.setMouseTracking(True) 217 + self.__extended_rdp_keys = { 218 + Qt.Key_End : 'VK_END', 219 + Qt.Key_Down : 'VK_DOWN', 220 + Qt.Key_PageDown : 'VK_NEXT', 221 + Qt.Key_Insert : 'VK_INSERT', 222 + Qt.Key_Delete : 'VK_DELETE', 223 + Qt.Key_Print : 'VK_SNAPSHOT', 224 + Qt.Key_Home : 'VK_HOME', 225 + Qt.Key_Up : 'VK_UP', 226 + Qt.Key_PageUp : 'VK_PRIOR', 227 + Qt.Key_Left : 'VK_LEFT', 228 + Qt.Key_Right : 'VK_RIGHT', 229 + Qt.Key_Meta : 'VK_LWIN', 230 + Qt.Key_Enter : 'VK_RETURN', 231 + Qt.Key_Menu : 'VK_LMENU', 232 + Qt.Key_Pause : 'VK_PAUSE', 233 + Qt.Key_Slash: 'VK_DIVIDE', 234 + Qt.Key_Period: 'VK_DECIMAL', 235 + #Qt.Key_0 : 'VK_NUMPAD0', 236 + #Qt.Key_1 : 'VK_NUMPAD1', 237 + #Qt.Key_2 : 'VK_NUMPAD2', 238 + #Qt.Key_3 : 'VK_NUMPAD3', 239 + #Qt.Key_4 : 'VK_NUMPAD4', 240 + #Qt.Key_5 : 'VK_NUMPAD5', 241 + #Qt.Key_6 : 'VK_NUMPAD6', 242 + #Qt.Key_7 : 'VK_NUMPAD7', 243 + #Qt.Key_8 : 'VK_NUMPAD8', 244 + #Qt.Key_9 : 'VK_NUMPAD9', 245 + } 246 + 247 + self.__qtbutton_to_rdp = { 248 + Qt.LeftButton : MOUSEBUTTON.MOUSEBUTTON_LEFT, 249 + Qt.RightButton : MOUSEBUTTON.MOUSEBUTTON_RIGHT, 250 + Qt.MidButton : MOUSEBUTTON.MOUSEBUTTON_MIDDLE, 251 + Qt.ExtraButton1 : MOUSEBUTTON.MOUSEBUTTON_5, 252 + Qt.ExtraButton2 : MOUSEBUTTON.MOUSEBUTTON_6, 253 + Qt.ExtraButton3 : MOUSEBUTTON.MOUSEBUTTON_7, 254 + Qt.ExtraButton4 : MOUSEBUTTON.MOUSEBUTTON_8, 255 + Qt.ExtraButton5 : MOUSEBUTTON.MOUSEBUTTON_9, 256 + Qt.ExtraButton6 : MOUSEBUTTON.MOUSEBUTTON_10, 257 + } 169 258 170 259 def closeEvent(self, event): 171 - self._thread.quit() 260 + self.connectionClosed() 172 261 event.accept() 173 262 174 263 def connectionClosed(self): 175 - print('its over!') 264 + self.in_q.put(None) 265 + self._threaded.stop() 266 + self._thread.quit() 176 267 177 268 def updateImage(self, event): 178 - with QPainter(self._buffer) as qp: 179 - qp.drawImage(event.x, event.y, event.image, 0, 0, event.width, event.height) 269 + if event.width == self.settings.iosettings.video_width and event.height == self.settings.iosettings.video_height: 270 + self._buffer = event.image 271 + else: 272 + with QPainter(self._buffer) as qp: 273 + qp.drawImage(event.x, event.y, event.image, 0, 0, event.width, event.height) 274 + 180 275 pixmap01 = QPixmap.fromImage(self._buffer) 181 276 pixmap_image = QPixmap(pixmap01) 182 277 self._label_imageDisplay.setPixmap(pixmap_image) skipped 2 lines 185 280 self._label_imageDisplay.setMinimumSize(1,1) 186 281 self._label_imageDisplay.show() 187 282 188 - def keyPressEvent(self, e): 283 + def send_key(self, e, is_pressed): 284 + # https://doc.qt.io/qt-5/qt.html#Key-enum 189 285 if self.keyboard is False: 190 286 return 191 287 skipped 3 lines 195 291 ki.data = pyperclip.paste() 196 292 self.in_q.put(ki) 197 293 198 - #print('SCANCODE: %s' % e.nativeScanCode()) 294 + modifiers = [] 295 + qt_modifiers = QApplication.keyboardModifiers() 296 + if bool(qt_modifiers & Qt.ShiftModifier): 297 + modifiers.append('SHIFT') 298 + if bool(qt_modifiers & Qt.ControlModifier): 299 + modifiers.append('CONTROL') 300 + if bool(qt_modifiers & Qt.AltModifier): 301 + modifiers.append('ALT') 302 + if bool(qt_modifiers & Qt.KeypadModifier): 303 + modifiers.append('KEYPAD') 304 + if bool(qt_modifiers & Qt.MetaModifier): 305 + modifiers.append('GUI') 306 + 199 307 ki = RDP_KEYBOARD_SCANCODE() 200 308 ki.keyCode = e.nativeScanCode() 201 - ki.is_pressed = True 309 + ki.is_pressed = is_pressed 202 310 if sys.platform == "linux": 203 311 #why tho? 204 312 ki.keyCode -= 8 205 - self.in_q.put(ki) 313 + ki.modifiers = modifiers 206 314 207 - def keyReleaseEvent(self, e): 208 - if self.keyboard is False: 209 - return 210 - ki = RDP_KEYBOARD_SCANCODE() 211 - ki.keyCode = e.nativeScanCode() 212 - ki.is_pressed = False 213 - if sys.platform == "linux": 214 - ki.keyCode -= 8 315 + if e.key() in self.__extended_rdp_keys.keys(): 316 + ki.vk_code = self.__extended_rdp_keys[e.key()] 317 + 318 + print('SCANCODE: %s' % ki.keyCode) 319 + print('VK CODE : %s' % ki.vk_code) 215 320 self.in_q.put(ki) 216 321 217 - def mouseMoveEvent(self, e): 218 - if self.mhover is False: 322 + def send_mouse(self, e, is_pressed, is_hover = False): 323 + if is_hover is True and self. settings .mhover is False: 324 + # is hovering is disabled we return immediately 219 325 return 220 - mi = RDP_MOUSE() 221 - mi.xPos = e.pos().x() 222 - mi.yPos = e.pos().y() 223 - mi.button = 0 224 - mi.pressed = False 225 - self.in_q.put(mi) 226 - 227 - def mouseReleaseEvent(self, e): 228 - """ 229 - mouse button release event 230 - """ 231 - button = e.button() 232 - buttonNumber = 0 233 - if button == Qt.LeftButton: 234 - buttonNumber = 1 235 - elif button == Qt.RightButton: 236 - buttonNumber = 2 237 - elif button == Qt.MidButton: 238 - buttonNumber = 3 326 + buttonNumber = MOUSEBUTTON.MOUSEBUTTON_HOVER 327 + if is_hover is False: 328 + buttonNumber = self.__qtbutton_to_rdp[e.button()] 239 329 240 330 mi = RDP_MOUSE() 241 331 mi.xPos = e.pos().x() 242 332 mi.yPos = e.pos().y() 243 333 mi.button = buttonNumber 244 - mi.pressed = False 334 + mi.is_pressed = is_pressed if is_hover is False else False 245 335 246 336 self.in_q.put(mi) 247 337 338 + def keyPressEvent(self, e): 339 + self.send_key(e, True) 248 340 341 + def keyReleaseEvent(self, e): 342 + self.send_key(e, False) 249 343 250 - def mousePressEvent(self, e): 251 - button = e.button() 252 - buttonNumber = 0 253 - if button == Qt.LeftButton: 254 - buttonNumber = 1 255 - elif button == Qt.RightButton: 256 - buttonNumber = 2 257 - elif button == Qt.MidButton: 258 - buttonNumber = 3 344 + def mouseMoveEvent(self, e): 345 + self.send_mouse(e, False, True) 259 346 260 - mi = RDP_MOUSE() 261 - mi.xPos = e.pos().x() 262 - mi.yPos = e.pos().y() 263 - mi.button = buttonNumber 264 - mi.pressed = True 347 + def mouseReleaseEvent(self, e): 348 + self.send_mouse(e, False) 265 349 266 - self.in_q.put(mi) 350 + def mousePressEvent(self, e): 351 + self.send_mouse(e, True) 267 352 268 353 269 354 def main(): skipped 5 lines 275 360 parser.add_argument('--no-keyboard', action='store_false', help='Disables keyboard input. (whatever)') 276 361 parser.add_argument('--res', default = '1024x768', help='Resolution in "WIDTHxHEIGHT" format. Default: "1024x768"') 277 362 parser.add_argument('--bpp', choices = [15, 16, 24, 32], default = 32, type=int, help='Bits per pixel.') 363 + parser.add_argument('--ducky', help='Ducky script to be executed') 278 364 parser.add_argument('url', help="RDP connection url") 279 365 280 366 args = parser.parse_args() skipped 8 lines 289 375 width, height = args.res.upper().split('X') 290 376 height = int(height) 291 377 width = int(width) 292 - 293 378 iosettings = RDPIOSettings() 294 379 iosettings.video_width = width 295 380 iosettings.video_height = height 296 381 iosettings.video_bpp_min = 15 #servers dont support 8 any more :/ 297 382 iosettings.video_bpp_max = args.bpp 298 - iosettings.video_out_format = ' qt ' 383 + iosettings.video_out_format = VIDEO_FORMAT . QT5 384 + iosettings.ducky_file = args.ducky 299 385 300 386 settings = RDPClientConsoleSettings(args.url, iosettings) 301 387 settings.mhover = args.no_mouse_hover skipped 11 lines