| 1 | + | |
| 2 | + | import asyncio |
| 3 | + | import os |
| 4 | + | import io |
| 5 | + | from sys import byteorder |
| 6 | + | import traceback |
| 7 | + | from struct import pack, unpack |
| 8 | + | from typing import cast |
| 9 | + | |
| 10 | + | from aardwolf import logger |
| 11 | + | from aardwolf.network.selector import NetworkSelector |
| 12 | + | from aardwolf.transport.tcpstream import TCPStream |
| 13 | + | from aardwolf.crypto.symmetric import DES |
| 14 | + | from aardwolf.crypto.BASE import cipherMODE |
| 15 | + | |
| 16 | + | from aardwolf.commons.queuedata import * |
| 17 | + | |
| 18 | + | from PIL import Image, ImageDraw |
| 19 | + | from PIL.ImageQt import ImageQt |
| 20 | + | import rle |
| 21 | + | |
| 22 | + | # https://datatracker.ietf.org/doc/html/rfc6143 |
| 23 | + | |
| 24 | + | RAW_ENCODING = 0 |
| 25 | + | COPY_RECTANGLE_ENCODING = 1 |
| 26 | + | RRE_ENCODING = 2 |
| 27 | + | CORRE_ENCODING = 4 |
| 28 | + | HEXTILE_ENCODING = 5 |
| 29 | + | ZLIB_ENCODING = 6 |
| 30 | + | TIGHT_ENCODING = 7 |
| 31 | + | ZLIBHEX_ENCODING = 8 |
| 32 | + | TRLE_ENCODING = 15 |
| 33 | + | ZRLE_ENCODING = 16 |
| 34 | + | |
| 35 | + | class VNCConnection: |
| 36 | + | def __init__(self, target, credentials, iosettings): |
| 37 | + | self.target = target |
| 38 | + | self.credentials = credentials |
| 39 | + | self.authapi = None |
| 40 | + | self.iosettings = iosettings |
| 41 | + | self.shared_flag = False |
| 42 | + | self.client_version = '003.008' |
| 43 | + | self.server_version = None |
| 44 | + | self.server_name = None |
| 45 | + | self.disconnected_evt = asyncio.Event() #this will be set if we disconnect for whatever reason |
| 46 | + | self.server_supp_security_types = [] |
| 47 | + | self.__selected_security_type = 2 |
| 48 | + | |
| 49 | + | # these are the main queues with which you can communicate with the server |
| 50 | + | # ext_out_queue: yields video data |
| 51 | + | # ext_in_queue: expects keyboard/mouse data |
| 52 | + | self.ext_out_queue = asyncio.Queue() |
| 53 | + | self.ext_in_queue = asyncio.Queue() |
| 54 | + | |
| 55 | + | self.__reader = None |
| 56 | + | self.__writer = None |
| 57 | + | |
| 58 | + | |
| 59 | + | self.bpp = None |
| 60 | + | self.bypp = None |
| 61 | + | self.depth = None |
| 62 | + | self.bigendian = None |
| 63 | + | self.truecolor = None |
| 64 | + | self.redmax = None |
| 65 | + | self.greenmax = None |
| 66 | + | self.bluemax = None |
| 67 | + | self.redshift = None |
| 68 | + | self.greenshift = None |
| 69 | + | self.blueshift = None |
| 70 | + | self.width = None |
| 71 | + | self.height = None |
| 72 | + | self.__desktop_buffer = None |
| 73 | + | self.__frame_update_req_evt = asyncio.Event() |
| 74 | + | |
| 75 | + | |
| 76 | + | async def terminate(self): |
| 77 | + | try: |
| 78 | + | return True, None |
| 79 | + | except Exception as e: |
| 80 | + | traceback.print_exc() |
| 81 | + | return None, e |
| 82 | + | finally: |
| 83 | + | self.disconnected_evt.set() |
| 84 | + | |
| 85 | + | async def __aenter__(self): |
| 86 | + | return self |
| 87 | + | |
| 88 | + | async def __aexit__(self, exc_type, exc, traceback): |
| 89 | + | await asyncio.wait_for(self.terminate(), timeout = 5) |
| 90 | + | |
| 91 | + | async def connect(self): |
| 92 | + | """ |
| 93 | + | Performs the entire connection sequence |
| 94 | + | """ |
| 95 | + | try: |
| 96 | + | # starting lower-layer transports |
| 97 | + | logger.debug('Selecting network') |
| 98 | + | remote_socket, err = await NetworkSelector.select(self.target) |
| 99 | + | if err is not None: |
| 100 | + | raise err |
| 101 | + | |
| 102 | + | logger.debug('Connecting sockets') |
| 103 | + | _, err = await remote_socket.connect() |
| 104 | + | if err is not None: |
| 105 | + | raise err |
| 106 | + | |
| 107 | + | logger.debug('Wrapping sockets to streamer') |
| 108 | + | self.__reader, self.__writer = await TCPStream.from_tcpsocket(remote_socket) |
| 109 | + | |
| 110 | + | logger.debug('Performing banner exchange') |
| 111 | + | _, err = await self.__banner_exchange() |
| 112 | + | if err is not None: |
| 113 | + | raise err |
| 114 | + | logger.debug('Banner exchange OK') |
| 115 | + | |
| 116 | + | logger.debug('Performing security handshake') |
| 117 | + | _, err = await self.__security_handshake() |
| 118 | + | if err is not None: |
| 119 | + | raise err |
| 120 | + | logger.debug('Security handshake OK') |
| 121 | + | |
| 122 | + | logger.debug('Client init') |
| 123 | + | _, err = await self.__client_init() |
| 124 | + | if err is not None: |
| 125 | + | raise err |
| 126 | + | logger.debug('Client init OK') |
| 127 | + | |
| 128 | + | self.__reader_loop_task = asyncio.create_task(self.__reader_loop()) |
| 129 | + | self.__external_reader_task = asyncio.create_task(self.__external_reader()) |
| 130 | + | |
| 131 | + | return True, None |
| 132 | + | except Exception as e: |
| 133 | + | self.disconnected_evt.set() |
| 134 | + | return None, e |
| 135 | + | |
| 136 | + | async def __banner_exchange(self): |
| 137 | + | try: |
| 138 | + | banner = await self.__reader.readuntil(b'\n') |
| 139 | + | self.server_version = banner[4:-1].decode() |
| 140 | + | logger.debug('Server version: %s' % self.server_version) |
| 141 | + | version_reply = b'RFB %s\n' % self.client_version.encode() |
| 142 | + | logger.debug('Version reply: %s' % version_reply) |
| 143 | + | self.__writer.write(version_reply) |
| 144 | + | |
| 145 | + | print(self.server_version) |
| 146 | + | return True, None |
| 147 | + | except Exception as e: |
| 148 | + | return None, e |
| 149 | + | |
| 150 | + | async def __security_handshake(self): |
| 151 | + | try: |
| 152 | + | no_sec_types = await self.__reader.readexactly(1) |
| 153 | + | no_sec_types = ord(no_sec_types) |
| 154 | + | if no_sec_types == 0: |
| 155 | + | logger.debug('Server sent empty support security types, connection will terminate soon') |
| 156 | + | err_string_size = await self.__reader.readexactly(1) |
| 157 | + | err_string_size = ord(err_string_size) |
| 158 | + | err_string = await self.__reader.readexactly(err_string_size) |
| 159 | + | err_string = err_string.decode() |
| 160 | + | raise Exception(err_string) |
| 161 | + | sec_types = await self.__reader.readexactly(no_sec_types) |
| 162 | + | for sectype in sec_types: |
| 163 | + | self.server_supp_security_types.append(sectype) |
| 164 | + | |
| 165 | + | if self.__selected_security_type == 2: |
| 166 | + | logger.debug('Selecting default VNC auth type') |
| 167 | + | self.__writer.write(bytes([self.__selected_security_type])) |
| 168 | + | challenge = await self.__reader.readexactly(16) |
| 169 | + | |
| 170 | + | password = self.credentials.secret.ljust(8, '\x00').encode('ascii') |
| 171 | + | print(password) |
| 172 | + | # converting password to key |
| 173 | + | newkey = b'' |
| 174 | + | for ki in range(len(password)): |
| 175 | + | bsrc = password[ki] |
| 176 | + | btgt = 0 |
| 177 | + | for i in range(8): |
| 178 | + | if bsrc & (1 << i): |
| 179 | + | btgt = btgt | (1 << 7 - i) |
| 180 | + | newkey+= bytes([btgt]) |
| 181 | + | ctx = DES(newkey, mode = cipherMODE.ECB, IV = None) |
| 182 | + | response = ctx.encrypt(challenge) |
| 183 | + | print(response) |
| 184 | + | self.__writer.write(response) |
| 185 | + | |
| 186 | + | else: |
| 187 | + | raise Exception('Unsupported security type selected!') |
| 188 | + | |
| 189 | + | auth_result = await self.__reader.readexactly(4) |
| 190 | + | auth_result = int.from_bytes(auth_result, byteorder = 'big', signed=False) |
| 191 | + | if auth_result != 0: |
| 192 | + | logger.debug('Auth Failed!') |
| 193 | + | err_string_size = await self.__reader.readexactly(4) |
| 194 | + | err_string_size = int.from_bytes(err_string_size, byteorder = 'big', signed=False) |
| 195 | + | err_string = await self.__reader.readexactly(err_string_size) |
| 196 | + | err_string = err_string.decode() |
| 197 | + | raise Exception(err_string) |
| 198 | + | logger.debug('Auth OK!') |
| 199 | + | |
| 200 | + | print(self.server_supp_security_types) |
| 201 | + | return True, None |
| 202 | + | except Exception as e: |
| 203 | + | return None, e |
| 204 | + | |
| 205 | + | async def __client_init(self): |
| 206 | + | try: |
| 207 | + | # sending client init |
| 208 | + | self.__writer.write(bytes([int(self.shared_flag)])) |
| 209 | + | # reading server_init |
| 210 | + | framebuffer_width = await self.__reader.readexactly(2) |
| 211 | + | self.width = int.from_bytes(framebuffer_width, byteorder = 'big', signed = False) |
| 212 | + | framebuffer_height = await self.__reader.readexactly(2) |
| 213 | + | self.height = int.from_bytes(framebuffer_height, byteorder = 'big', signed = False) |
| 214 | + | pixel_format_raw = await self.__reader.readexactly(16) |
| 215 | + | self.bpp, self.depth, self.bigendian, self.truecolor, \ |
| 216 | + | self.redmax, self.greenmax, self.bluemax, self.redshift, \ |
| 217 | + | self.greenshift, self.blueshift = unpack("!BBBBHHHBBBxxx", pixel_format_raw) |
| 218 | + | self.bypp = self.bpp // 8 # calc bytes per pixel |
| 219 | + | |
| 220 | + | name_string_size = await self.__reader.readexactly(4) |
| 221 | + | name_string_size = int.from_bytes(name_string_size, byteorder = 'big', signed=False) |
| 222 | + | name_string = await self.__reader.readexactly(name_string_size) |
| 223 | + | self.server_name = name_string.decode() |
| 224 | + | |
| 225 | + | |
| 226 | + | self.__desktop_buffer = Image.new(mode="RGBA", size=(self.width, self.height)) |
| 227 | + | _, err = await self.set_encodings() |
| 228 | + | if err is not None: |
| 229 | + | raise err |
| 230 | + | |
| 231 | + | await self.set_pixel_format() |
| 232 | + | await self.framebuffer_update_request() |
| 233 | + | await self.framebuffer_update_request(incremental = 1) |
| 234 | + | |
| 235 | + | asyncio.create_task(self.testloop()) |
| 236 | + | return True, None |
| 237 | + | except Exception as e: |
| 238 | + | return None, e |
| 239 | + | |
| 240 | + | async def __external_reader(self): |
| 241 | + | # This coroutine handles keyboard/mouse etc input from the user |
| 242 | + | # It wraps the data in it's appropriate format then dispatches it to the server |
| 243 | + | try: |
| 244 | + | while True: |
| 245 | + | indata = await self.ext_in_queue.get() |
| 246 | + | if indata is None: |
| 247 | + | #signaling exit |
| 248 | + | await self.terminate() |
| 249 | + | return |
| 250 | + | if indata.type == RDPDATATYPE.KEYSCAN: |
| 251 | + | indata = cast(RDP_KEYBOARD_SCANCODE, indata) |
| 252 | + | #await self.handle_out_data(cli_input, sec_hdr, data_hdr, None, self.__joined_channels['MCS'].channel_id, False) |
| 253 | + | |
| 254 | + | if indata.type == RDPDATATYPE.KEYUNICODE: |
| 255 | + | indata = cast(RDP_KEYBOARD_UNICODE, indata) |
| 256 | + | #await self.handle_out_data(cli_input, sec_hdr, data_hdr, None, self.__joined_channels['MCS'].channel_id, False) |
| 257 | + | |
| 258 | + | elif indata.type == RDPDATATYPE.MOUSE: |
| 259 | + | #PointerEvent |
| 260 | + | indata = cast(RDP_MOUSE, indata) |
| 261 | + | if indata.xPos < 0 or indata.yPos < 0: |
| 262 | + | continue |
| 263 | + | |
| 264 | + | #print('sending mouse!') |
| 265 | + | msg = pack("!BBHH", 5, int(indata.pressed), indata.xPos, indata.yPos) |
| 266 | + | self.__writer.write(msg) |
| 267 | + | |
| 268 | + | elif indata.type == RDPDATATYPE.CLIPBOARD_DATA_TXT: |
| 269 | + | indata = cast(RDP_CLIPBOARD_DATA_TXT, indata) |
| 270 | + | txtdata = indata.data.encode('ascii') |
| 271 | + | msg = pack("!BxxxIs", 6, len(txtdata), txtdata) |
| 272 | + | self.__writer.write(msg) |
| 273 | + | |
| 274 | + | except asyncio.CancelledError: |
| 275 | + | return None, None |
| 276 | + | |
| 277 | + | except Exception as e: |
| 278 | + | traceback.print_exc() |
| 279 | + | return None, e |
| 280 | + | |
| 281 | + | async def testloop(self): |
| 282 | + | ctr = 0 |
| 283 | + | while True: |
| 284 | + | await self.__frame_update_req_evt.wait() |
| 285 | + | if ctr == 100: |
| 286 | + | ctr = 0 |
| 287 | + | await self.framebuffer_update_request(1) |
| 288 | + | else: |
| 289 | + | await self.framebuffer_update_request(incremental=1) |
| 290 | + | #await self.framebuffer_update_request(incremental=1) |
| 291 | + | #await self.framebuffer_update_request(incremental=1) |
| 292 | + | self.__frame_update_req_evt.clear() |
| 293 | + | ctr += 1 |
| 294 | + | #x = RDP_MOUSE() |
| 295 | + | #x.yPos = 1 |
| 296 | + | #x.xPos = 1 |
| 297 | + | #x.pressed = False |
| 298 | + | #await self.ext_in_queue.put(x) |
| 299 | + | |
| 300 | + | async def __send_rect(self, x, y, width, height, image:Image): |
| 301 | + | try: |
| 302 | + | #image.save('./test/test_%s.png' % os.urandom(4).hex(), 'PNG') |
| 303 | + | #updating desktop buffer to have a way to copy rectangles later |
| 304 | + | if self.width == width and self.height == height: |
| 305 | + | self.__desktop_buffer = image |
| 306 | + | self.__desktop_buffer.save('test.png', 'PNG') |
| 307 | + | else: |
| 308 | + | self.__desktop_buffer.paste(image, [x, y, x+width, y+height]) |
| 309 | + | |
| 310 | + | #if self.iosettings.video_out_format == 'pil' or self.iosettings.video_out_format == 'pillow': |
| 311 | + | # return image |
| 312 | + | |
| 313 | + | if self.iosettings.video_out_format == 'raw': |
| 314 | + | image = image.tobytes() |
| 315 | + | |
| 316 | + | elif self.iosettings.video_out_format == 'qt': |
| 317 | + | image = ImageQt(image) |
| 318 | + | |
| 319 | + | elif self.iosettings.video_out_format == 'png': |
| 320 | + | img_byte_arr = io.BytesIO() |
| 321 | + | image.save(img_byte_arr, format=self.iosettings.video_out_format.upper()) |
| 322 | + | image = img_byte_arr.getvalue() |
| 323 | + | else: |
| 324 | + | raise ValueError('Output format of "%s" is not supported!' % self.iosettings.video_out_format) |
| 325 | + | |
| 326 | + | rect = RDP_VIDEO() |
| 327 | + | rect.x = x |
| 328 | + | rect.y = y |
| 329 | + | rect.width = width |
| 330 | + | rect.height = height |
| 331 | + | rect.bitsPerPixel = self.bpp |
| 332 | + | rect.is_compressed = False |
| 333 | + | rect.data = image |
| 334 | + | await self.ext_out_queue.put(rect) |
| 335 | + | |
| 336 | + | |
| 337 | + | except Exception as e: |
| 338 | + | traceback.print_exc() |
| 339 | + | return None, e |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | async def __reader_loop(self): |
| 344 | + | try: |
| 345 | + | while True: |
| 346 | + | msgtype = await self.__reader.readexactly(1) |
| 347 | + | msgtype = ord(msgtype) |
| 348 | + | print(msgtype) |
| 349 | + | if msgtype == 0: |
| 350 | + | # framebufferupdate |
| 351 | + | num_rect = await self.__reader.readexactly(3) |
| 352 | + | num_rect = int.from_bytes(num_rect[1:], byteorder = 'big', signed = False) |
| 353 | + | print(num_rect) |
| 354 | + | for _ in range(num_rect): |
| 355 | + | rect_hdr = await self.__reader.readexactly(12) |
| 356 | + | (x, y, width, height, encoding) = unpack("!HHHHI", rect_hdr) |
| 357 | + | |
| 358 | + | |
| 359 | + | if encoding == RAW_ENCODING: |
| 360 | + | #print(width*height*self.bypp) |
| 361 | + | try: |
| 362 | + | #print(self.bypp) |
| 363 | + | #print('x %s' % x) |
| 364 | + | #print('y %s' % y) |
| 365 | + | #print('width %s' % width) |
| 366 | + | #print('height %s' % height) |
| 367 | + | data = await self.__reader.readexactly(width*height*self.bypp) |
| 368 | + | image = bytes(width * height * self.bypp) |
| 369 | + | rle.mask_rgbx(image, data) |
| 370 | + | #print(image[:4]) |
| 371 | + | #print(len(image)) |
| 372 | + | #print(width*height*self.bypp) |
| 373 | + | image = Image.frombytes('RGBA', [width, height], image) |
| 374 | + | await self.__send_rect(x,y,width, height, image) |
| 375 | + | except Exception as e: |
| 376 | + | traceback.print_exc() |
| 377 | + | continue |
| 378 | + | |
| 379 | + | elif encoding == COPY_RECTANGLE_ENCODING: |
| 380 | + | try: |
| 381 | + | data = await self.__reader.readexactly(4) |
| 382 | + | (srcx, srcy) = unpack("!HH", data) |
| 383 | + | |
| 384 | + | #print('copy') |
| 385 | + | #print('width %s' % width) |
| 386 | + | #print('height %s' % height) |
| 387 | + | #print('x %s' % x) |
| 388 | + | #print('y %s' % y) |
| 389 | + | #print('srcx %s' % srcx) |
| 390 | + | #print('srcy %s' % srcy) |
| 391 | + | |
| 392 | + | newrect = self.__desktop_buffer.crop([srcx, srcy, srcx+width, srcy+height]) |
| 393 | + | await self.__send_rect(x,y,width,height, newrect) |
| 394 | + | continue |
| 395 | + | except Exception as e: |
| 396 | + | traceback.print_exc() |
| 397 | + | return |
| 398 | + | |
| 399 | + | |
| 400 | + | elif encoding == RRE_ENCODING: |
| 401 | + | try: |
| 402 | + | print('RRE!') |
| 403 | + | sub_rect_num = await self.__reader.readexactly(4) |
| 404 | + | sub_rect_num = int.from_bytes(sub_rect_num, byteorder = 'big', signed = False) |
| 405 | + | #print(sub_rect_num) |
| 406 | + | backgroud_pixel = await self.__reader.readexactly(self.bypp) |
| 407 | + | r,g,b,a = unpack("BBBB", backgroud_pixel) |
| 408 | + | a = 255 |
| 409 | + | rect = Image.new('RGBA', [width, height]) |
| 410 | + | rect.paste((r,g,b,a), [0,0, width, height]) |
| 411 | + | |
| 412 | + | format = "!%dsHHHH" % self.bypp |
| 413 | + | for _ in range(sub_rect_num): |
| 414 | + | data = await self.__reader.readexactly(self.bypp + 8) |
| 415 | + | (color, subx, suby, subwidth, subheight) = unpack(format, data) |
| 416 | + | r,g,b,a = unpack("BBBB", color) |
| 417 | + | a = 255 |
| 418 | + | #rect = Image.new('RGBA', subwidth, subheight) |
| 419 | + | rect.paste((r,g,b,a), [subx,suby, subx+subwidth, suby+subheight]) |
| 420 | + | #await self.__send_rect(subx + x, suby + y, width, height) |
| 421 | + | await self.__send_rect(x,y, width, height, rect) |
| 422 | + | except Exception as e: |
| 423 | + | traceback.print_exc() |
| 424 | + | return |
| 425 | + | |
| 426 | + | |
| 427 | + | elif encoding == TRLE_ENCODING: |
| 428 | + | try: |
| 429 | + | sub_enc = await self.__reader.readexactly(1) |
| 430 | + | sub_enc = ord(sub_enc) |
| 431 | + | run_length_encoded = bool(sub_enc >> 7) |
| 432 | + | palette_size = sub_enc |
| 433 | + | if run_length_encoded is True: |
| 434 | + | palette_size -= 128 |
| 435 | + | if palette_size == 0: |
| 436 | + | data = await self.__reader.readexactly(width*height*self.bypp) |
| 437 | + | elif palette_size == 1: |
| 438 | + | data = await self.__reader.readexactly(self.bypp) |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | except Exception as e: |
| 446 | + | traceback.print_exc() |
| 447 | + | return |
| 448 | + | |
| 449 | + | else: |
| 450 | + | print('Unknown encoding %s' % encoding) |
| 451 | + | elif msgtype == 1: |
| 452 | + | print('colormap') |
| 453 | + | hdr = await self.__reader.readexactly(5) |
| 454 | + | (_, firstcolor, numcolor) = unpack("!BHH", hdr) |
| 455 | + | for _ in range(numcolor): |
| 456 | + | color_raw = await self.__reader.readexactly(6) |
| 457 | + | (red, green, blue) = unpack("!HHH", color_raw) |
| 458 | + | |
| 459 | + | elif msgtype == 2: |
| 460 | + | print('bell') |
| 461 | + | |
| 462 | + | elif msgtype == 3: |
| 463 | + | hdr = await self.__reader.readexactly(7) |
| 464 | + | (_,_,_, cliplen ) = unpack("!BBBI", hdr) |
| 465 | + | cliptext = await self.__reader.readexactly(cliplen) |
| 466 | + | cliptext = cliptext.decode() |
| 467 | + | print(cliptext) |
| 468 | + | else: |
| 469 | + | print('Unexpected message tpye %s' % msgtype) |
| 470 | + | |
| 471 | + | if msgtype == 0: |
| 472 | + | self.__frame_update_req_evt.set() |
| 473 | + | |
| 474 | + | |
| 475 | + | return True, None |
| 476 | + | except Exception as e: |
| 477 | + | return None, e |
| 478 | + | |
| 479 | + | async def set_encodings(self, list_of_encodings = [2, 1, 0]): |
| 480 | + | try: |
| 481 | + | enc_encodings = b'' |
| 482 | + | for encoding in list_of_encodings: |
| 483 | + | enc_encodings += encoding.to_bytes(4, byteorder = 'big', signed = True) |
| 484 | + | sendbuff = pack("!BxH", 2, len(list_of_encodings)) + enc_encodings |
| 485 | + | print(sendbuff) |
| 486 | + | self.__writer.write(sendbuff) |
| 487 | + | return True, None |
| 488 | + | except Exception as e: |
| 489 | + | return None, e |
| 490 | + | |
| 491 | + | |
| 492 | + | async def set_pixel_format(self, bpp=32, depth=24, bigendian=0, truecolor=1, redmax=255, greenmax=255, bluemax=255, |
| 493 | + | redshift=0, greenshift=8, blueshift=16): |
| 494 | + | pixformat = pack("!BBBBHHHBBBxxx", bpp, depth, bigendian, truecolor, redmax, greenmax, bluemax, redshift, |
| 495 | + | greenshift, blueshift) |
| 496 | + | print(pixformat) |
| 497 | + | self.__writer.write(b'\x00\x00\x00\x00' + pixformat) |
| 498 | + | # rember these settings |
| 499 | + | self.bpp, self.depth, self.bigendian, self.truecolor = bpp, depth, bigendian, truecolor |
| 500 | + | self.redmax, self.greenmax, self.bluemax = redmax, greenmax, bluemax |
| 501 | + | self.redshift, self.greenshift, self.blueshift = redshift, greenshift, blueshift |
| 502 | + | self.bypp = self.bpp // 8 # calc bytes per pixel |
| 503 | + | # ~ print self.bypp |
| 504 | + | |
| 505 | + | async def framebuffer_update_request(self, x=0, y=0, width=None, height=None, incremental=0): |
| 506 | + | if width is None: |
| 507 | + | width = self.width - x |
| 508 | + | |
| 509 | + | if height is None: |
| 510 | + | height = self.height - y |
| 511 | + | |
| 512 | + | self.__writer.write(pack("!BBHHHH", 3, incremental, x, y, width, height)) |
| 513 | + | |
| 514 | + | def _handle_rectangle(self, block): |
| 515 | + | (x, y, width, height, encoding) = unpack("!HHHHI", block) |
| 516 | + | if self.rectangles: |
| 517 | + | self.rectangles -= 1 |
| 518 | + | self.rectanglePos.append((x, y, width, height)) |
| 519 | + | if encoding == COPY_RECTANGLE_ENCODING: |
| 520 | + | self._handleDecodeCopyrect( 4, x, y, width, height) |
| 521 | + | elif encoding == RAW_ENCODING: |
| 522 | + | self._handle_decode_raw( width * height * self.bypp, x, y, width, height) |
| 523 | + | elif encoding == HEXTILE_ENCODING: |
| 524 | + | self._do_next_hextile_subrect(None, None, x, y, width, height, None, None) |
| 525 | + | elif encoding == CORRE_ENCODING: |
| 526 | + | self._handle_decode_corre( 4 + self.bypp, x, y, width, height) |
| 527 | + | elif encoding == RRE_ENCODING: |
| 528 | + | self._handleDecodeRRE(4 + self.bypp, x, y, width, height) |
| 529 | + | else: |
| 530 | + | logger.msg("unknown encoding received (encoding %d)\n" % encoding) |
| 531 | + | self._do_connection() |
| 532 | + | else: |
| 533 | + | self._do_connection() |
| 534 | + | |
| 535 | + | def _handle_decode_raw(self, block, x, y, width, height): |
| 536 | + | # TODO convert pixel format? |
| 537 | + | self.update_rectangle(x, y, width, height, block) |
| 538 | + | |
| 539 | + | def _handleDecodeCopyrect(self, block, x, y, width, height): |
| 540 | + | (srcx, srcy) = unpack("!HH", block) |
| 541 | + | self.copy_rectangle(srcx, srcy, x, y, width, height) |
| 542 | + | |
| 543 | + | def _handle_decode_corre_rectangles(self, block, topx, topy): |
| 544 | + | # ~ print "_handleDecodeCORRERectangle" |
| 545 | + | pos = 0 |
| 546 | + | end = len(block) |
| 547 | + | sz = self.bypp + 4 |
| 548 | + | format = "!%dsBBBB" % self.bypp |
| 549 | + | while pos < sz: |
| 550 | + | (color, x, y, width, height) = unpack(format, block[pos:pos + sz]) |
| 551 | + | self.fill_rectangle(topx + x, topy + y, width, height, color) |
| 552 | + | pos += sz |
| 553 | + | |
| 554 | + | def _do_next_hextile_subrect(self, bg, color, x, y, width, height, tx, ty): |
| 555 | + | """ |
| 556 | + | # Hextile Encoding |
| 557 | + | :param bg: |
| 558 | + | :param color: |
| 559 | + | :param x: |
| 560 | + | :param y: |
| 561 | + | :param width: |
| 562 | + | :param height: |
| 563 | + | :param tx: |
| 564 | + | :param ty: |
| 565 | + | :return: |
| 566 | + | """ |
| 567 | + | # ~ print "_doNextHextileSubrect %r" % ((color, x, y, width, height, tx, ty), ) |
| 568 | + | # coords of next tile |
| 569 | + | # its line after line of tiles |
| 570 | + | # finished when the last line is completly received |
| 571 | + | |
| 572 | + | # dont inc the first time |
| 573 | + | if tx is not None: |
| 574 | + | # calc next subrect pos |
| 575 | + | tx += 16 |
| 576 | + | if tx >= x + width: |
| 577 | + | tx = x |
| 578 | + | ty += 16 |
| 579 | + | else: |
| 580 | + | tx = x |
| 581 | + | ty = y |
| 582 | + | # more tiles? |
| 583 | + | if ty >= y + height: |
| 584 | + | self._do_connection() # read more! |
| 585 | + | else: |
| 586 | + | self._handle_decode_hextile(1, bg, color, x, y, width, height, tx, ty) |
| 587 | + | |
| 588 | + | def _handle_decode_hextile(self, block, bg, color, x, y, width, height, tx, ty): |
| 589 | + | """ |
| 590 | + | # Hextile Decoding |
| 591 | + | :param block: |
| 592 | + | :param bg: |
| 593 | + | :param color: |
| 594 | + | :param x: |
| 595 | + | :param y: |
| 596 | + | :param width: |
| 597 | + | :param height: |
| 598 | + | :param tx: |
| 599 | + | :param ty: |
| 600 | + | :return: |
| 601 | + | """ |
| 602 | + | (sub_encoding,) = unpack("!B", block) |
| 603 | + | # calc tile size |
| 604 | + | tw = th = 16 |
| 605 | + | if x + width - tx < 16: |
| 606 | + | tw = x + width - tx |
| 607 | + | |
| 608 | + | if y + height - ty < 16: |
| 609 | + | th = y + height - ty |
| 610 | + | |
| 611 | + | # decode tile |
| 612 | + | if sub_encoding & 1: # RAW |
| 613 | + | self._handle_decode_hextile_raw( tw * th * self.bypp, bg, color, x, y, width, height, tx, ty, tw, th) |
| 614 | + | else: |
| 615 | + | num_bytes = 0 |
| 616 | + | if sub_encoding & 2: # BackgroundSpecified |
| 617 | + | num_bytes += self.bypp |
| 618 | + | if sub_encoding & 4: # ForegroundSpecified |
| 619 | + | num_bytes += self.bypp |
| 620 | + | if sub_encoding & 8: # AnySubrects |
| 621 | + | num_bytes += 1 |
| 622 | + | if num_bytes: |
| 623 | + | self._handle_decode_hextile_subrect(num_bytes, sub_encoding, bg, color, x, y, width, height, tx, ty, tw, th) |
| 624 | + | else: |
| 625 | + | self.fill_rectangle(tx, ty, tw, th, bg) |
| 626 | + | self._do_next_hextile_subrect(bg, color, x, y, width, height, tx, ty) |
| 627 | + | |
| 628 | + | def _handle_decode_hextile_raw(self, block, bg, color, x, y, width, height, tx, ty, tw, th): |
| 629 | + | """the tile is in raw encoding""" |
| 630 | + | self.update_rectangle(tx, ty, tw, th, block) |
| 631 | + | self._do_next_hextile_subrect(bg, color, x, y, width, height, tx, ty) |
| 632 | + | |
| 633 | + | def _handle_decode_corre_rectangles(self, block, topx, topy): |
| 634 | + | # ~ print "_handleDecodeCORRERectangle" |
| 635 | + | pos = 0 |
| 636 | + | end = len(block) |
| 637 | + | sz = self.bypp + 4 |
| 638 | + | format = "!%dsBBBB" % self.bypp |
| 639 | + | while pos < sz: |
| 640 | + | (color, x, y, width, height) = unpack(format, block[pos:pos + sz]) |
| 641 | + | self.fill_rectangle(topx + x, topy + y, width, height, color) |
| 642 | + | pos += sz |
| 643 | + | |
| 644 | + | def _handleDecodeRRE(self, block, x, y, width, height): |
| 645 | + | (subrects,) = unpack("!I", block[:4]) |
| 646 | + | color = block[4:] |
| 647 | + | self.fill_rectangle(x, y, width, height, color) |
| 648 | + | self._handle_rre_sub_rectangles((8 + self.bypp) * subrects, x, y) |
| 649 | + | |
| 650 | + | def _handle_rre_sub_rectangles(self, block, topx, topy): |
| 651 | + | """ |
| 652 | + | # RRE Sub Rectangles |
| 653 | + | :param block: |
| 654 | + | :param topx: |
| 655 | + | :param topy: |
| 656 | + | :return: |
| 657 | + | """ |
| 658 | + | |
| 659 | + | pos = 0 |
| 660 | + | end = len(block) |
| 661 | + | sz = self.bypp + 8 |
| 662 | + | format = "!%dsHHHH" % self.bypp |
| 663 | + | while pos < end: |
| 664 | + | (color, x, y, width, height) = unpack(format, block[pos:pos + sz]) |
| 665 | + | self.fill_rectangle(topx + x, topy + y, width, height, color) |
| 666 | + | pos += sz |
| 667 | + | |
| 668 | + | def fill_rectangle(self, x, y, width, height, color): |
| 669 | + | """fill the area with the color. the color is a string in |
| 670 | + | the pixel format set up earlier""" |
| 671 | + | # fallback variant, use update recatngle |
| 672 | + | # override with specialized function for better performance |
| 673 | + | self.update_rectangle(x, y, width, height, color * width * height) |
| 674 | + | |
| 675 | + | def update_rectangle(self, x, y, width, height, size): |
| 676 | + | print(x, y, width, height, size) |
| 677 | + | |
| 678 | + | def copy_rectangle(self, srcx, srcy, x, y, width, height): |
| 679 | + | print(srcx, srcy, x, y, width, height) |
| 680 | + | |
| 681 | + | async def amain(): |
| 682 | + | try: |
| 683 | + | logger.setLevel(1) |
| 684 | + | from aardwolf.commons.url import RDPConnectionURL |
| 685 | + | from aardwolf.commons.iosettings import RDPIOSettings |
| 686 | + | |
| 687 | + | iosettings = RDPIOSettings() |
| 688 | + | iosettings.video_out_format = 'raw' |
| 689 | + | |
| 690 | + | url = 'vnc+plain://alma:[email protected]:5900' |
| 691 | + | url = RDPConnectionURL(url) |
| 692 | + | connection = url.get_connection(iosettings) |
| 693 | + | print(connection) |
| 694 | + | print(connection.target.port) |
| 695 | + | |
| 696 | + | _, err = await connection.connect() |
| 697 | + | if err is not None: |
| 698 | + | raise err |
| 699 | + | |
| 700 | + | while True: |
| 701 | + | await asyncio.sleep(10) |
| 702 | + | |
| 703 | + | return True, None |
| 704 | + | except Exception as e: |
| 705 | + | traceback.print_exc() |
| 706 | + | return None, e |
| 707 | + | |
| 708 | + | def main(): |
| 709 | + | asyncio.run(amain()) |
| 710 | + | |
| 711 | + | if __name__ == '__main__': |
| 712 | + | main() |