- Improved efficiency and readability of the RfbBitmap configuration logic. Refactored redundant code blocks for different pixel format (bpp) configurations into a single, streamlined method. This change enhances maintainability and clarity of the bitmap configuration process.

- Added cursor pseudo encoding support.

- Added Windows support for cursor image capturing in `get_cursor_image` method. Implemented Windows-specific logic using the `win32gui`, `win32ui`, and related libraries to capture the current cursor image, enhancing the cross-platform capability of the application.

- Fixed issues in `get_bitmap` method for handling different bpp formats. Specifically addressed the processing logic for 16 bpp (BGR565) format, ensuring that the image conversion and rendering are handled correctly for VNC clients expecting this format.

- Added initial Tight encoding support.

- Updated the `send_image` method in the Tight encoding class to correctly handle JPEG and ZLIB compression. This includes proper signaling to the client about the type of compression used (JPEG or ZLIB) and ensuring that the data is formatted and sent according to the Tight encoding specifications.

- Added checks and conversions in `send_image` to handle different image modes (like RGBX and RGBA) and convert them to the appropriate format (RGB) before compression and transmission.

- Implemented a more robust and accurate method for determining when to use JPEG compression in Tight encoding based on the unique color count and image characteristics.

These updates significantly improve the functionality, stability, and compatibility of the VNC server, particularly in handling different pixel formats and encoding methods.
This commit is contained in:
Matias Fernandez
2023-12-06 09:21:26 -03:00
parent 76e309ef08
commit 2bc431f0a8
18 changed files with 650 additions and 433 deletions

View File

@@ -1,260 +1,15 @@
# BGR233 palette # BGR233 palette
palette = [ def generate_bgr233_palette():
0, 0, 0, palette = []
36, 0, 0, for b in range(4):
73, 0, 0, for g in range(8):
109, 0, 0, for r in range(4):
146, 0, 0, red = int(r * 255 / 3)
182, 0, 0, green = int(g * 255 / 7)
219, 0, 0, blue = int(b * 255 / 3)
255, 0, 0, palette.extend([red, green, blue])
0, 36, 0, return palette
36, 36, 0,
73, 36, 0,
109, 36, 0, palette = generate_bgr233_palette()
146, 36, 0,
182, 36, 0,
219, 36, 0,
255, 36, 0,
0, 73, 0,
36, 73, 0,
73, 73, 0,
109, 73, 0,
146, 73, 0,
182, 73, 0,
219, 73, 0,
255, 73, 0,
0, 109, 0,
36, 109, 0,
73, 109, 0,
109, 109, 0,
146, 109, 0,
182, 109, 0,
219, 109, 0,
255, 109, 0,
0, 146, 0,
36, 146, 0,
73, 146, 0,
109, 146, 0,
146, 146, 0,
182, 146, 0,
219, 146, 0,
255, 146, 0,
0, 182, 0,
36, 182, 0,
73, 182, 0,
109, 182, 0,
146, 182, 0,
182, 182, 0,
219, 182, 0,
255, 182, 0,
0, 219, 0,
36, 219, 0,
73, 219, 0,
109, 219, 0,
146, 219, 0,
182, 219, 0,
219, 219, 0,
255, 219, 0,
0, 255, 0,
36, 255, 0,
73, 255, 0,
109, 255, 0,
146, 255, 0,
182, 255, 0,
219, 255, 0,
255, 255, 0,
0, 0, 85,
36, 0, 85,
73, 0, 85,
109, 0, 85,
146, 0, 85,
182, 0, 85,
219, 0, 85,
255, 0, 85,
0, 36, 85,
36, 36, 85,
73, 36, 85,
109, 36, 85,
146, 36, 85,
182, 36, 85,
219, 36, 85,
255, 36, 85,
0, 73, 85,
36, 73, 85,
73, 73, 85,
109, 73, 85,
146, 73, 85,
182, 73, 85,
219, 73, 85,
255, 73, 85,
0, 109, 85,
36, 109, 85,
73, 109, 85,
109, 109, 85,
146, 109, 85,
182, 109, 85,
219, 109, 85,
255, 109, 85,
0, 146, 85,
36, 146, 85,
73, 146, 85,
109, 146, 85,
146, 146, 85,
182, 146, 85,
219, 146, 85,
255, 146, 85,
0, 182, 85,
36, 182, 85,
73, 182, 85,
109, 182, 85,
146, 182, 85,
182, 182, 85,
219, 182, 85,
255, 182, 85,
0, 219, 85,
36, 219, 85,
73, 219, 85,
109, 219, 85,
146, 219, 85,
182, 219, 85,
219, 219, 85,
255, 219, 85,
0, 255, 85,
36, 255, 85,
73, 255, 85,
109, 255, 85,
146, 255, 85,
182, 255, 85,
219, 255, 85,
255, 255, 85,
0, 0, 170,
36, 0, 170,
73, 0, 170,
109, 0, 170,
146, 0, 170,
182, 0, 170,
219, 0, 170,
255, 0, 170,
0, 36, 170,
36, 36, 170,
73, 36, 170,
109, 36, 170,
146, 36, 170,
182, 36, 170,
219, 36, 170,
255, 36, 170,
0, 73, 170,
36, 73, 170,
73, 73, 170,
109, 73, 170,
146, 73, 170,
182, 73, 170,
219, 73, 170,
255, 73, 170,
0, 109, 170,
36, 109, 170,
73, 109, 170,
109, 109, 170,
146, 109, 170,
182, 109, 170,
219, 109, 170,
255, 109, 170,
0, 146, 170,
36, 146, 170,
73, 146, 170,
109, 146, 170,
146, 146, 170,
182, 146, 170,
219, 146, 170,
255, 146, 170,
0, 182, 170,
36, 182, 170,
73, 182, 170,
109, 182, 170,
146, 182, 170,
182, 182, 170,
219, 182, 170,
255, 182, 170,
0, 219, 170,
36, 219, 170,
73, 219, 170,
109, 219, 170,
146, 219, 170,
182, 219, 170,
219, 219, 170,
255, 219, 170,
0, 255, 170,
36, 255, 170,
73, 255, 170,
109, 255, 170,
146, 255, 170,
182, 255, 170,
219, 255, 170,
255, 255, 170,
0, 0, 255,
36, 0, 255,
73, 0, 255,
109, 0, 255,
146, 0, 255,
182, 0, 255,
219, 0, 255,
255, 0, 255,
0, 36, 255,
36, 36, 255,
73, 36, 255,
109, 36, 255,
146, 36, 255,
182, 36, 255,
219, 36, 255,
255, 36, 255,
0, 73, 255,
36, 73, 255,
73, 73, 255,
109, 73, 255,
146, 73, 255,
182, 73, 255,
219, 73, 255,
255, 73, 255,
0, 109, 255,
36, 109, 255,
73, 109, 255,
109, 109, 255,
146, 109, 255,
182, 109, 255,
219, 109, 255,
255, 109, 255,
0, 146, 255,
36, 146, 255,
73, 146, 255,
109, 146, 255,
146, 146, 255,
182, 146, 255,
219, 146, 255,
255, 146, 255,
0, 182, 255,
36, 182, 255,
73, 182, 255,
109, 182, 255,
146, 182, 255,
182, 182, 255,
219, 182, 255,
255, 182, 255,
0, 219, 255,
36, 219, 255,
73, 219, 255,
109, 219, 255,
146, 219, 255,
182, 219, 255,
219, 219, 255,
255, 219, 255,
0, 255, 255,
36, 255, 255,
73, 255, 255,
109, 255, 255,
146, 255, 255,
182, 255, 255,
219, 255, 255,
255, 255, 255
]

View File

@@ -19,4 +19,6 @@
from . import common from . import common
from . import raw from . import raw
from . import zlib from . import zlib
#from . import zrle
from . import tight
from . import cursor from . import cursor

View File

@@ -20,10 +20,14 @@ encodings = {}
class ENCODINGS: class ENCODINGS:
raw = 0 raw = 0
zlib = 6 zlib = 6
tight = 7
#zrle = 16
# supported pseudo-encodings # supported pseudo-encodings
cursor = -239 cursor = -239
encodings_priority = [ encodings_priority = [
#ENCODINGS.zrle,
ENCODINGS.tight,
ENCODINGS.zlib, ENCODINGS.zlib,
ENCODINGS.raw ENCODINGS.raw
] ]

View File

@@ -15,10 +15,28 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import struct
import ctypes
import ctypes.util
import platform
from PIL import Image, ImageChops, ImageDraw, ImagePalette
import base64
from io import BytesIO
from . import common from . import common
from struct import * from struct import *
from lib import log from lib import log
import zlib
OS_NAME = platform.system()
if OS_NAME == 'Linux':
import lib.oshelpers.x11 as x11
Xcursor = x11.XCursor
if OS_NAME == 'Windows':
import lib.oshelpers.windows_cursor as windows_cursor
class Encoding: class Encoding:
name = 'Cursor' name = 'Cursor'
@@ -31,6 +49,54 @@ class Encoding:
def __init__(self): def __init__(self):
log.debug("Initialized", __name__) log.debug("Initialized", __name__)
self.default_cursor_data = 'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACc0lEQVR4nK2Wv0sbYRjHP/fDyNUGalKitBDI4FAojg5NF8M7BFIPUkvSopuQoYMgFrl/wbWbndrFuBocAroVHCIdJAaxzdJuNYXGBpIgsU8H7zSpGo3mhS/3Hvfe9/N837t7OI3zobXNhT4Nre2otZ3/7RdEbwOYSqk4MACYbdfuPDTXcKherzeUUklgyAX1BaK5ZkERkUql0lBKpYD7/YJogA94LCJi27YcHh42lVLpfkE0YBAIi4iEw2FJJpNSqVSaSqnX/YB0ACKRiEQiEZmenu4bpAMwNjZ2pnQ6fWfIhcWGYZxpd3eX+fn5wWw2+1Ep9cItxOgFcmGhaZodKhaLLCws3BrSNYGnYrHI4uKiB0n0Ark2gadSqcTS0tLg2traJxficyHaBddeE3gqlUo4juNB4m0proR4Dc4HjIjI92g0ekrWdQzDoNVqsbq6Sjgc7rix0Wg0bdt+tbW1ladLczQvS6DrOo7jsLe3Ry6XY319nUAg8GV2dvY90ASqwFfg51XG/6c4+w5isZjk83nZ39//XS6XZXJyUqampqRarR6HQqEo8Ah4CFj08AzEq8RxHCzL+jExMfGu1Wr9Gh8fp9FosL29PTA3N/cSqLkJmsDJdQnaAScAmqZ9i8fjb2u12ueVlZWcbduYpsnGxgaZTOYNp9vaterLtsgAApubmwXLsp4BIcA/PDz8vFqtHs/MzEgikZCjoyMJBoNPOG0ZPUN8lmWNct5zBoDR5eXl3MHBgezs7EihUCgDI7cBtCfxXl0duKfr+tNUKvUhk8lk/X5/DPDTQy/qVoUH8QEP3PkfoE4PPwU3iemBcI25qTnAP3ZG9LuVtmFhAAAAAElFTkSuQmCC'
self.default_cursor_img = Image.open(BytesIO(base64.b64decode(self.default_cursor_data)))
def get_cursor_image(self):
if OS_NAME == 'Windows':
return windows_cursor.get_cursor_image()
elif OS_NAME == 'Linux':
cursor = Xcursor()
return cursor.get_cursor_image()
elif OS_NAME == 'Darwin':
if self.default_cursor_img.mode != 'RGBA':
self.default_cursor_img = self.default_cursor_img.convert('RGBA')
return self.default_cursor_img
else:
return None
def send_cursor(self, x, y, cursor):
sendbuff = bytearray()
sendbuff.extend(pack("!B", 0)) # message type 0 == SetCursorPosition
sendbuff.extend(pack("!H", x))
sendbuff.extend(pack("!H", y))
self.cursor_sent = True
if cursor is not None:
w, h = cursor.size
cursor_bytes = cursor.convert("RGBA").tobytes("raw", "BGRA")
# Invert alpha channel if needed
pixels = bytearray(cursor_bytes)
for i in range(0, len(pixels), 4):
pixels[i + 3] = 255 - pixels[i + 3]
sendbuff.extend(pack("!B", 1)) # message type 1 == SetCursorShape
sendbuff.extend(pack("!H", w)) # width
sendbuff.extend(pack("!H", h)) # height
sendbuff.extend(pixels)
else:
sendbuff.extend(pack("!B", 0)) # message type 0 == SetCursorPosition
sendbuff.extend(pack("!H", x))
sendbuff.extend(pack("!H", y))
return sendbuff
common.encodings[common.ENCODINGS.cursor] = Encoding common.encodings[common.ENCODINGS.cursor] = Encoding
log.debug("Loaded encoding: %s (%s)" % (__name__, Encoding.id)) log.debug("Loaded encoding: %s (%s)" % (__name__, Encoding.id))

101
lib/encodings/tight.py Normal file
View File

@@ -0,0 +1,101 @@
from . import common
from lib import log
from struct import *
import zlib
from io import BytesIO
import numpy as np
class Encoding:
name = 'tight'
id = 7
description = 'Tight VNC encoding'
enabled = True
def __init__(self):
self.compress_obj = zlib.compressobj(
zlib.Z_DEFAULT_COMPRESSION,
zlib.DEFLATED,
zlib.MAX_WBITS,
zlib.DEF_MEM_LEVEL,
zlib.Z_DEFAULT_STRATEGY
)
self._last_compression_was_jpeg = False
def send_image(self, x, y, w, h, image):
sendbuff = bytearray()
if image.mode == 'RGBX' or image.mode == 'RGBA':
image = image.convert('RGB')
rectangles = 1
sendbuff.extend(pack("!BxH", 0, rectangles)) # FramebufferUpdate
sendbuff.extend(pack("!HHHH", x, y, w, h))
sendbuff.extend(pack(">i", self.id))
if self._should_use_jpeg(image, 64):
self._last_compression_was_jpeg = True
compressed_data = self._compress_image_jpeg(image)
sendbuff.append(0x90) # 0x90 = 10010000 = JPEG subencoding
else:
compressed_data = self._compress_image_zlib(image)
sendbuff.append(0) # control byte
# content lenght
sendbuff.extend(self._send_compact_length(len(compressed_data)))
# Tight data
sendbuff.extend(compressed_data)
return sendbuff
def _send_compact_length(self, length):
sendbuff = bytearray()
while True:
# Toma los 7 bits más bajos del tamaño
byte = length & 0x7F
length >>= 7
# Si aún hay más datos, establece el bit alto y continúa
if length > 0:
byte |= 0x80
sendbuff.append(byte)
if length == 0:
break
return sendbuff
def _should_use_jpeg(self, image, threshold=256):
if image.mode == 'P':
return False
if image.mode == 'RGB':
width, height = image.size
sample_size = min(width * height, 1000)
sample = np.array(image).reshape(-1, 3)[:sample_size]
unique_colors = np.unique(sample, axis=0)
return len(unique_colors) > threshold
return False
def _compress_image_jpeg(self, image, quality=75):
buffer = BytesIO()
image.save(buffer, format="JPEG", quality=quality)
jpeg_data = buffer.getvalue()
buffer.close()
return jpeg_data
def _compress_image_zlib(self, image):
if self.compress_obj is None or self._last_compression_was_jpeg:
self.compress_obj = zlib.compressobj(
zlib.Z_DEFAULT_COMPRESSION,
zlib.DEFLATED,
-zlib.MAX_WBITS, # El negativo omite la inclusión de un encabezado zlib.
zlib.DEF_MEM_LEVEL,
zlib.Z_DEFAULT_STRATEGY
)
self._last_compression_was_jpeg = False
zlib_data = self.compress_obj.compress(image.tobytes()) + self.compress_obj.flush(zlib.Z_SYNC_FLUSH)
return zlib_data
common.encodings[common.ENCODINGS.tight] = Encoding
log.debug("Loaded encoding: %s (%s)" % (__name__, Encoding.id))

View File

@@ -1,6 +1,6 @@
from . import common from . import common
from struct import *
from lib import log from lib import log
from struct import *
import zlib import zlib
@@ -39,13 +39,13 @@ class Encoding:
sendbuff.extend(pack(">i", self.id)) sendbuff.extend(pack(">i", self.id))
#log.debug("Compressing...") #log.debug("Compressing...")
zlibdata = self._compressObj.compress( image.tobytes() ) zlibdata = self._compressObj.compress(image.tobytes())
zlibdata += self._compressObj.flush(zlib.Z_FULL_FLUSH) zlibdata += self._compressObj.flush(zlib.Z_FULL_FLUSH)
#log.debug("LEN", len(zlibdata)) #log.debug("LEN", len(zlibdata))
l = pack("!I", len(zlibdata) ) l = pack("!I", len(zlibdata))
sendbuff.extend( l ) # send length sendbuff.extend(l) # send length
sendbuff.extend( zlibdata ) # send compressed data sendbuff.extend(zlibdata) # send compressed data
return sendbuff return sendbuff

95
lib/encodings/zrle.py Normal file
View File

@@ -0,0 +1,95 @@
from . import common
from lib import log
import zlib
from struct import pack
from PIL import Image
class Encoding:
name = 'zrle'
id = 16
description = 'zrle VNC encoding'
enabled = True
def __init__(self):
log.debug("Initialized", __name__)
self._compressObj = zlib.compressobj()
def send_image(self, x, y, w, h, image):
sendbuff = bytearray()
rectangles = 1
sendbuff.extend(pack("!BxH", 0, rectangles)) # FramebufferUpdate
sendbuff.extend(pack("!HHHH", x, y, w, h))
sendbuff.extend(pack(">i", self.id)) # ID de encoding ZRLE
tmpbuf = bytearray()
# Dividir la imagen en tiles y comprimirlas
for tile_y in range(0, h, 64):
for tile_x in range(0, w, 64):
tile = image.crop((tile_x, tile_y, min(tile_x + 64, w), min(tile_y + 64, h)))
encoded_tile = self.tile_encode(tile)
tmpbuf.extend(encoded_tile)
compressed_data = self._compressObj.compress(tmpbuf)
compressed_data += self._compressObj.flush(zlib.Z_SYNC_FLUSH)
sendbuff.extend(pack("!I", len(compressed_data)))
sendbuff.extend(compressed_data)
log.debug("zrle send_image", x, y, w, h, image)
return sendbuff
def tile_encode(self, tile):
"""Codifica una baldosa (tile) de la imagen usando ZRLE."""
w, h = tile.size
pixels = list(tile.getdata())
rle_data = bytearray()
# Proceso RLE para la baldosa
prev_pixel = pixels[0]
count = 1
for pixel in pixels[1:]:
if pixel == prev_pixel and count < 255:
count += 1
else:
rle_data.extend(self._pack_pixel(prev_pixel, count))
prev_pixel = pixel
count = 1
rle_data.extend(self._pack_pixel(prev_pixel, count))
# Empaquetar la data RLE con el byte de subencoding
encoded_tile = bytearray()
encoded_tile.append(128) # Subencoding RLE
encoded_tile.extend(rle_data)
return encoded_tile
def _pack_pixel(self, pixel, count):
if isinstance(pixel, tuple):
# RGBA
r, g, b, a = pixel
pixel_data = bytes([r, g, b]) # Usar solo RGB para ZRLE.
else:
pixel_data = bytes([pixel, pixel, pixel])
count_data = self._encode_run_length(count)
return pixel_data + count_data
def _encode_run_length(self, length):
"""Codifica la longitud de una secuencia para RLE."""
if length == 1:
return b""
length -= 1 # La longitud se incrementa en 1 según el protocolo ZRLE
encoded_length = bytearray()
while length > 0:
byte = length % 255
encoded_length.append(byte)
length //= 255
if length > 0:
encoded_length.append(255)
return encoded_length
common.encodings[common.ENCODINGS.zrle] = Encoding
log.debug("Loaded encoding: %s (%s)" % (__name__, Encoding.id))

View File

@@ -2,14 +2,11 @@ import sys
from PIL import Image from PIL import Image
from lib import log from lib import log
if sys.platform == "linux" or sys.platform == "linux2": class ImageGrab():
log.debug("ImageGrab: running on Linux") @staticmethod
from Xlib import display, X def grab():
# take screen images, that's not the best way, so here if sys.platform == "linux" or sys.platform == "linux2":
# we use directly use xlib to take the screenshot. from Xlib import display, X
class ImageGrab():
@staticmethod
def grab():
dsp = display.Display() dsp = display.Display()
root = dsp.screen().root root = dsp.screen().root
geom = root.get_geometry() geom = root.get_geometry()
@@ -19,12 +16,8 @@ if sys.platform == "linux" or sys.platform == "linux2":
image = Image.frombytes("RGB", (w, h), raw.data, "raw", "BGRX") image = Image.frombytes("RGB", (w, h), raw.data, "raw", "BGRX")
return image return image
elif sys.platform == "darwin": elif sys.platform == "darwin":
log.debug("ImageGrab: running on darwin") import Quartz.CoreGraphics as CG
import Quartz.CoreGraphics as CG
class ImageGrab():
@staticmethod
def grab():
screenshot = CG.CGWindowListCreateImage(CG.CGRectInfinite, CG.kCGWindowListOptionOnScreenOnly, CG.kCGNullWindowID, CG.kCGWindowImageDefault) screenshot = CG.CGWindowListCreateImage(CG.CGRectInfinite, CG.kCGWindowListOptionOnScreenOnly, CG.kCGNullWindowID, CG.kCGWindowImageDefault)
width = CG.CGImageGetWidth(screenshot) width = CG.CGImageGetWidth(screenshot)
height = CG.CGImageGetHeight(screenshot) height = CG.CGImageGetHeight(screenshot)
@@ -38,6 +31,10 @@ elif sys.platform == "darwin":
return i return i
else: elif sys.platform == "win32":
log.debug("ImageGrab: running on Unknown!") from PIL import ImageGrab as WinImageGrab
from PIL import ImageGrab return WinImageGrab.grab()
else:
log.debug("ImageGrab: running on an unknown platform!")
raise EnvironmentError("Unsupported platform")

View File

@@ -19,24 +19,18 @@ class KeyboardController:
def process_event(self, data): def process_event(self, data):
# B = U8, L = U32 # B = U8, L = U32
(self.downflag, self.key) = unpack("!BxxL", data) (self.downflag, self.key) = unpack("!BxxL", data)
log.debug("KeyEvent", self.downflag, hex(self.key)) #log.debug("KeyEvent", self.downflag, hex(self.key))
# special key # special key
if self.key in self.kbdmap: if self.key in self.kbdmap:
self.kbdkey = self.kbdmap[self.key] self.kbdkey = self.kbdmap[self.key]
log.debug("SPECIAL KEY", self.kbdkey) #log.debug("SPECIAL KEY", self.kbdkey)
else: # normal key else: # normal key
try: try:
self.kbdkey = self.kbd.KeyCode.from_char(chr(self.key)) self.kbdkey = self.kbd.KeyCode.from_char(chr(self.key))
except: except:
self.kbdkey = None self.kbdkey = None
# debug keypress to stdout
try:
log.debug("KEY:", self.kbdkey)
except:
log.debug("KEY: (unprintable)")
# send the actual keyboard event # send the actual keyboard event
try: try:
if self.downflag: if self.downflag:

View File

@@ -23,38 +23,39 @@ class MouseController():
# process mouse button events # process mouse button events
if self.buttons[0] and not self.left_pressed: if self.buttons[0] and not self.left_pressed:
log.debug("LEFT PRESSED") #log.debug("LEFT PRESSED")
mouse.Controller().press(mouse.Button.left) mouse.Controller().press(mouse.Button.left)
self.left_pressed = 1 self.left_pressed = 1
elif not self.buttons[0] and self.left_pressed: elif not self.buttons[0] and self.left_pressed:
log.debug("LEFT RELEASED") #log.debug("LEFT RELEASED")
mouse.Controller().release(mouse.Button.left) mouse.Controller().release(mouse.Button.left)
self.left_pressed = 0 self.left_pressed = 0
if self.buttons[1] and not self.middle_pressed: if self.buttons[1] and not self.middle_pressed:
log.debug("MIDDLE PRESSED") #log.debug("MIDDLE PRESSED")
mouse.Controller().press(mouse.Button.middle) mouse.Controller().press(mouse.Button.middle)
self.middle_pressed = 1 self.middle_pressed = 1
elif not self.buttons[1] and self.middle_pressed: elif not self.buttons[1] and self.middle_pressed:
log.debug("MIDDLE RELEASED") #log.debug("MIDDLE RELEASED")
mouse.Controller().release(mouse.Button.middle) mouse.Controller().release(mouse.Button.middle)
self.middle_pressed = 0 self.middle_pressed = 0
if self.buttons[2] and not self.right_pressed: if self.buttons[2] and not self.right_pressed:
log.debug("RIGHT PRESSED") #log.debug("RIGHT PRESSED")
mouse.Controller().press(mouse.Button.right) mouse.Controller().press(mouse.Button.right)
self.right_pressed = 1 self.right_pressed = 1
elif not self.buttons[2] and self.right_pressed: elif not self.buttons[2] and self.right_pressed:
log.debug("RIGHT RELEASED") #log.debug("RIGHT RELEASED")
mouse.Controller().release(mouse.Button.right) mouse.Controller().release(mouse.Button.right)
self.right_pressed = 0 self.right_pressed = 0
if self.buttons[3]: if self.buttons[3]:
log.debug("SCROLLUP PRESSED") #log.debug("SCROLLUP PRESSED")
mouse.Controller().scroll(0, 2) mouse.Controller().scroll(0, 2)
if self.buttons[4]: if self.buttons[4]:
log.debug("SCROLLDOWN PRESSED") #log.debug("SCROLLDOWN PRESSED")
mouse.Controller().scroll(0, -2) mouse.Controller().scroll(0, -2)
#log.debug("PointerEvent", buttonmask, x, y) #log.debug("PointerEvent", buttonmask, x, y)
return x, y, self.buttonmask

View File

@@ -0,0 +1,82 @@
import ctypes
import ctypes.wintypes
from PIL import Image
import numpy as np
class BITMAPINFOHEADER(ctypes.Structure):
_fields_ = [
("biSize", ctypes.wintypes.DWORD),
("biWidth", ctypes.wintypes.LONG),
("biHeight", ctypes.wintypes.LONG),
("biPlanes", ctypes.wintypes.WORD),
("biBitCount", ctypes.wintypes.WORD),
("biCompression", ctypes.wintypes.DWORD),
("biSizeImage", ctypes.wintypes.DWORD),
("biXPelsPerMeter", ctypes.wintypes.LONG),
("biYPelsPerMeter", ctypes.wintypes.LONG),
("biClrUsed", ctypes.wintypes.DWORD),
("biClrImportant", ctypes.wintypes.DWORD)
]
class RGBQUAD(ctypes.Structure):
_fields_ = [
("rgbBlue", ctypes.c_ubyte),
("rgbGreen", ctypes.c_ubyte),
("rgbRed", ctypes.c_ubyte),
("rgbReserved", ctypes.c_ubyte)
]
class BITMAPINFO(ctypes.Structure):
_fields_ = [("bmiHeader", BITMAPINFOHEADER), ("bmiColors", RGBQUAD * 1)]
class ICONINFO(ctypes.Structure):
_fields_ = [
("fIcon", ctypes.wintypes.BOOL),
("xHotspot", ctypes.wintypes.DWORD),
("yHotspot", ctypes.wintypes.DWORD),
("hbmMask", ctypes.wintypes.HBITMAP),
("hbmColor", ctypes.wintypes.HBITMAP),
]
class CURSORINFO(ctypes.Structure):
_fields_ = [
("cbSize", ctypes.wintypes.DWORD),
("flags", ctypes.wintypes.DWORD),
("hCursor", ctypes.wintypes.HANDLE),
("ptScreenPos", ctypes.wintypes.POINT),
]
def get_cursor_image():
ci = CURSORINFO()
ci.cbSize = ctypes.sizeof(CURSORINFO)
ctypes.windll.user32.GetCursorInfo(ctypes.byref(ci))
ii = ICONINFO()
ctypes.windll.user32.GetIconInfo(ci.hCursor, ctypes.byref(ii))
hdc = ctypes.windll.user32.GetDC(0) # Usar 0 en lugar de None
hbmp = ctypes.wintypes.HANDLE(ii.hbmColor) # Asegurarse de que hbmp es un HANDLE
bmpinfo = BITMAPINFO()
bmpinfo.bmiHeader.biSize = ctypes.sizeof(BITMAPINFOHEADER)
ctypes.windll.gdi32.GetDIBits(hdc, hbmp, 0, 0, None, ctypes.byref(bmpinfo), 0)
width, height = bmpinfo.bmiHeader.biWidth, bmpinfo.bmiHeader.biHeight
bmpinfo.bmiHeader.biCompression = 0 # BI_RGB
buffer = ctypes.create_string_buffer(width * height * 4)
ctypes.windll.gdi32.GetDIBits(hdc, hbmp, 0, height, buffer, ctypes.byref(bmpinfo), 0)
img = np.frombuffer(buffer, dtype=np.uint8)
img = img.reshape((height, width, 4))
img = np.flip(img, axis=0) # Las imágenes de bitmap en Windows están al revés
img = Image.fromarray(img, 'RGBA')
# Free resources
try:
ctypes.windll.gdi32.DeleteObject(hbmp)
ctypes.windll.gdi32.DeleteObject(ii.hbmMask)
ctypes.windll.user32.ReleaseDC(None, hdc)
except:
pass
return img

73
lib/oshelpers/x11.py Normal file
View File

@@ -0,0 +1,73 @@
import ctypes
from ctypes import POINTER, c_int, c_short, c_ushort, c_ulong, c_void_p, Structure, cast
from PIL import Image
import numpy as np
# Definición de Atom para su uso en la estructura XFixesCursorImage
Atom = c_ulong
# Definición de la estructura XFixesCursorImage
class XFixesCursorImage(Structure):
_fields_ = [
("x", c_short),
("y", c_short),
("width", c_ushort),
("height", c_ushort),
("xhot", c_ushort),
("yhot", c_ushort),
("cursor_serial", Atom),
("pixels", POINTER(c_ulong)), # Suponiendo que 'pixels' es un puntero a c_ulong
("atom", Atom), # Presente en la versión 2 y superiores de XFixes
("name", ctypes.c_char_p)
]
class XCursor:
def __init__(self):
# Cargar las bibliotecas X11 y Xfixes
self.xlib = ctypes.cdll.LoadLibrary("libX11.so")
self.xfixes = ctypes.cdll.LoadLibrary("libXfixes.so.3")
# Configurar los tipos de retorno
self.xlib.XOpenDisplay.restype = POINTER(c_void_p)
self.xlib.XOpenDisplay.argtypes = [ctypes.c_char_p]
self.xfixes.XFixesGetCursorImage.restype = POINTER(XFixesCursorImage)
self.xfixes.XFixesGetCursorImage.argtypes = [c_void_p]
# Abrir la conexión con X
self.display = self.xlib.XOpenDisplay(None)
if not self.display:
raise Exception("No se pudo abrir el display")
def __del__(self):
self.xlib.XCloseDisplay(self.display)
def get_cursor_image(self):
# Llamar a XFixesGetCursorImage
cursor_image_ref = self.xfixes.XFixesGetCursorImage(self.display)
if not cursor_image_ref:
# return a 2x2 red image
return Image.fromarray(np.array([[[255, 0, 0, 255], [255, 0, 0, 255]], [[255, 0, 0, 255], [255, 0, 0, 255]]], dtype=np.uint8), 'RGBA')
cursor_image = cursor_image_ref.contents
width, height = cursor_image.width, cursor_image.height
# Leer los datos de píxeles
pixels_array_type = c_ulong * (cursor_image.width * cursor_image.height)
pixels_pointer = cast(cursor_image.pixels, POINTER(pixels_array_type))
pixels_64bit = np.frombuffer(pixels_pointer.contents, dtype=np.uint64)
# Convertir cada valor de 64 bits en un píxel RGBA
pixels_rgba = np.zeros((cursor_image.height, cursor_image.width, 4), dtype=np.uint8)
for i in range(cursor_image.height):
for j in range(cursor_image.width):
pixel = int(pixels_64bit[i * cursor_image.width + j]) # Convertir a int para bit shifting
pixels_rgba[i, j, 0] = (pixel >> 16) & 0xFF # Rojo
pixels_rgba[i, j, 1] = (pixel >> 8) & 0xFF # Verde
pixels_rgba[i, j, 2] = pixel & 0xFF # Azul
pixels_rgba[i, j, 3] = (pixel >> 24) & 0xFF
return Image.fromarray(pixels_rgba, 'RGBA')

View File

@@ -15,28 +15,7 @@ class RfbBitmap():
self.red_shift = None self.red_shift = None
self.green_shift = None self.green_shift = None
self.blue_shift = None self.blue_shift = None
self.bigendian = 0
def __quantizetopalette(self, silf, palette, dither=False):
"""Converts an RGB or L mode image to use a given P image's palette."""
silf.load()
# use palette from reference image
palette.load()
if palette.mode != "P":
raise ValueError("bad mode for palette image")
if silf.mode != "RGB" and silf.mode != "L":
raise ValueError(
"only RGB or L mode images can be quantized to a palette"
)
im = silf.im.convert("P", 1 if dither else 0, palette.im)
# the 0 above means turn OFF dithering
# Later versions of Pillow (4.x) rename _makeself to _new
try:
return silf._new(im)
except AttributeError:
return silf._makeself(im)
def get_bitmap(self, rectangle): def get_bitmap(self, rectangle):
if self.bpp == 32: if self.bpp == 32:
@@ -55,53 +34,42 @@ class RfbBitmap():
a[..., 2] = ( a[..., 2] ) & blueMask >> self.blue_shift a[..., 2] = ( a[..., 2] ) & blueMask >> self.blue_shift
image = Image.fromarray(a) image = Image.fromarray(a)
if image.mode == "RGBA":
(r, g, b, a) = image.split()
image = Image.merge("RGB", (r, g, b))
del r, g, b, a
if self.primaryOrder == "rgb": if self.primaryOrder == "rgb":
(b, g, r) = image.split() (b, g, r) = image.split()
image = Image.merge("RGB", (r, g, b)) image = Image.merge("RGB", (r, g, b))
del b,g,r del b, g, r
image = image.convert("RGBX") image = image.convert("RGBX")
return image return image
elif self.bpp == 16: #BGR565 elif self.bpp == 16:
greenBits = 5 # BGR565
blueBits = 6 a = np.array(rectangle)
redBits = 5 r = (a[..., 0] >> 3) & 0x1F
image = rectangle g = (a[..., 1] >> 2) & 0x3F
b = (a[..., 2] >> 3) & 0x1F
if self.primaryOrder == "bgr": # FIXME: does not work bgr565 = (r << 11) | (g << 5) | b
(b, g, r) = image.split() bgr565 = bgr565.astype('uint16')
image = Image.merge("RGB", (r, g, b)) if self.bigendian == 0:
bgr565 = bgr565.byteswap().newbyteorder()
if self.depth == 16: bgr565_bytes = bgr565.tobytes()
image = image.convert('BGR;16') image = Image.frombytes('RGB', rectangle.size, bgr565_bytes, 'raw', 'BGR;16')
if self.depth == 15:
image = image.convert('BGR;15')
return image return image
elif self.bpp == 8: #bgr233 elif self.bpp == 8:
redBits = 3 # BGR233
greenBits = 3 image = rectangle.convert('RGB')
blueBits = 2 a = np.array(image)
image = rectangle r = (a[..., 0] >> 6) & 0x03
g = (a[..., 1] >> 5) & 0x07
palette = bgr233_palette.palette b = (a[..., 2] >> 6) & 0x03
if self.primaryOrder == "rgb": bgr233 = (b << 6) | (g << 3) | r
#(b, g, r) = image.split() image = Image.fromarray(bgr233.astype('uint8'), 'P')
#image = Image.merge("RGB", (r, g, b)) image.putpalette(bgr233_palette.palette)
palette = np.reshape(palette, (-3,3))
palette[:,[0, 2]] = palette[:,[2, 0]]
palette = palette.flatten()
palette = list(palette)
p = Image.new('P',(16,16))
p.putpalette(palette)
image = self.__quantizetopalette(image, p, dither=self.dither)
#image = image.convert('RGB', colors=4).quantize(palette=p)
#log.debug(image)
return image return image
else: else:

View File

@@ -20,3 +20,4 @@ The main pyvncs module
""" """
from . import server from . import server

View File

@@ -22,11 +22,9 @@ from pynput import mouse, keyboard
from PIL import Image, ImageChops, ImageDraw, ImagePalette from PIL import Image, ImageChops, ImageDraw, ImagePalette
import socket import socket
import select import errno
import os
import sys
import random
import numpy as np import numpy as np
import time
from lib import mousectrl from lib import mousectrl
from lib import kbdctrl from lib import kbdctrl
@@ -38,12 +36,13 @@ from lib import log
# encodings support # encodings support
import lib.encodings as encs import lib.encodings as encs
from lib.encodings.common import ENCODINGS from lib.encodings.common import ENCODINGS
from lib.encodings.cursor import Encoding as CursorEncoding
# auth support # auth support
from lib.auth.vnc_auth import VNCAuth from lib.auth.vnc_auth import VNCAuth
from lib.auth.vencrypt import VeNCrypt from lib.auth.vencrypt import VeNCrypt
class VncServer(): class VNCServer():
class RFB_SECTYPES: class RFB_SECTYPES:
vncauth = 2 # plain VNC auth vncauth = 2 # plain VNC auth
@@ -52,6 +51,8 @@ class VncServer():
encoding_object = None encoding_object = None
last_cursor = None
def __init__(self, socket, password=None, auth_type=None, pem_file='', vnc_config = None): def __init__(self, socket, password=None, auth_type=None, pem_file='', vnc_config = None):
self.RFB_VERSION = '003.008' self.RFB_VERSION = '003.008'
self.initmsg = ("RFB %s\n" % self.RFB_VERSION) self.initmsg = ("RFB %s\n" % self.RFB_VERSION)
@@ -62,6 +63,7 @@ class VncServer():
self.auth_type = auth_type self.auth_type = auth_type
self.pem_file = pem_file self.pem_file = pem_file
self.vnc_config = vnc_config self.vnc_config = vnc_config
self.cursor_encoding = CursorEncoding()
log.debug("Configured auth type:", self.auth_type) log.debug("Configured auth type:", self.auth_type)
@@ -69,7 +71,7 @@ class VncServer():
def __del__(self): def __del__(self):
log.debug("VncServer died") log.debug("VncServer died")
def sendmessage(self, message): def send_message(self, message):
''' sends a RFB message, usually an error message ''' ''' sends a RFB message, usually an error message '''
sock = self.socket sock = self.socket
message = bytes(message, 'iso8859-1') message = bytes(message, 'iso8859-1')
@@ -77,7 +79,7 @@ class VncServer():
buff = pack("I%ds" % (len(message),), len(message), message) buff = pack("I%ds" % (len(message),), len(message), message)
sock.send(buff) sock.send(buff)
def getbuff(self, timeout): def get_buffer(self, timeout):
sock = self.socket sock = self.socket
sock.settimeout(timeout) sock.settimeout(timeout)
@@ -94,7 +96,7 @@ class VncServer():
sock.send(self.initmsg.encode()) sock.send(self.initmsg.encode())
# RFB version handshake # RFB version handshake
data = self.getbuff(30) data = self.get_buffer(30)
log.debug("init received: '%s'" % data) log.debug("init received: '%s'" % data)
server_version = float(self.RFB_VERSION) server_version = float(self.RFB_VERSION)
@@ -121,7 +123,7 @@ class VncServer():
sock.send(sendbuff) sock.send(sendbuff)
# get client choosen security type # get client choosen security type
data = self.getbuff(30) data = self.get_buffer(30)
try: try:
sectype = unpack("B", data)[0] sectype = unpack("B", data)[0]
except: except:
@@ -130,7 +132,7 @@ class VncServer():
if sectype not in sectypes: if sectype not in sectypes:
log.debug("Incompatible security type: %s" % data) log.debug("Incompatible security type: %s" % data)
sock.send(pack("B", 1)) # failed handshake sock.send(pack("B", 1)) # failed handshake
self.sendmessage("Incompatible security type") self.send_message("Incompatible security type")
sock.close() sock.close()
return False return False
@@ -139,7 +141,7 @@ class VncServer():
# VNC Auth # VNC Auth
if sectype == self.RFB_SECTYPES.vncauth: if sectype == self.RFB_SECTYPES.vncauth:
auth = VNCAuth() auth = VNCAuth()
auth.getbuff = self.getbuff auth.getbuff = self.get_buffer
if not auth.auth(sock, self.password): if not auth.auth(sock, self.password):
msg = "Auth failed." msg = "Auth failed."
sendbuff = pack("I", len(msg)) sendbuff = pack("I", len(msg))
@@ -159,7 +161,7 @@ class VncServer():
return False return False
auth = VeNCrypt(sock) auth = VeNCrypt(sock)
auth.getbuff = self.getbuff auth.getbuff = self.get_buffer
auth.send_subtypes() auth.send_subtypes()
client_subtype = auth.client_subtype client_subtype = auth.client_subtype
@@ -191,21 +193,21 @@ class VncServer():
return False return False
# get ClientInit # get ClientInit
data = self.getbuff(30) data = self.get_buffer(30)
log.debug("Clientinit (shared flag)", repr(data)) log.debug("Clientinit (shared flag)", repr(data))
self.ServerInit() self.server_init()
return True return True
def ServerInit(self): def server_init(self):
# ServerInit # ServerInit
sock = self.socket sock = self.socket
screen = ImageGrab.grab() screen = ImageGrab.grab()
log.debug("screen", repr(screen)) #log.debug("screen", repr(screen))
size = screen.size size = screen.size
log.debug("size", repr(size)) #log.debug("size", repr(size))
del screen del screen
width = size[0] width = size[0]
@@ -249,36 +251,31 @@ class VncServer():
sock.send(sendbuff) sock.send(sendbuff)
def protocol(self): def handle_client(self):
self.socket.settimeout(None) # set nonblocking socket self.socket.settimeout(None) # set nonblocking socket
screen = ImageGrab.grab() last_rfbu = time.time()
size = screen.size
width = size[0]
height = size[1]
del screen
mousecontroller = mousectrl.MouseController() mousecontroller = mousectrl.MouseController()
kbdcontroller = kbdctrl.KeyboardController() kbdcontroller = kbdctrl.KeyboardController()
clipboardcontroller = clipboardctrl.ClipboardController() clipboardcontroller = clipboardctrl.ClipboardController()
self.primaryOrder = "rgb" self.primaryOrder = "bgr"
self.encoding = ENCODINGS.raw self.encoding = ENCODINGS.raw
self.encoding_object = encs.common.encodings[self.encoding]() self.encoding_object = encs.common.encodings[self.encoding]()
sock = self.socket
while True: while True:
#log.debug(".", end='', flush=True)
r,_,_ = select.select([self.socket],[],[],0)
if r == []:
#no data
sleep(0.1)
continue
sock = r[0]
try: try:
data = sock.recv(1) # read first byte data = sock.recv(1) # read first byte
except socket.timeout: except socket.timeout:
#log.debug("timeout") #log.debug("timeout")
continue continue
except socket.error as e:
err = e.args[0]
# no data
if err == errno.EAGAIN or err == errno.EWOULDBLOCK:
continue
except Exception as e: except Exception as e:
log.debug("exception '%s'" % e) log.debug("exception '%s'" % e)
sock.close() sock.close()
@@ -300,10 +297,27 @@ class VncServer():
log.debug("SHIFTS", self.red_shift, self.green_shift, self.blue_shift) log.debug("SHIFTS", self.red_shift, self.green_shift, self.blue_shift)
log.debug("MAXS", self.red_maximum, self.green_maximum, self.blue_maximum) log.debug("MAXS", self.red_maximum, self.green_maximum, self.blue_maximum)
if self.red_shift > self.blue_shift: # Configure primaryOrder
self.primaryOrder = "rgb" self.primaryOrder = "rgb" if self.red_shift > self.blue_shift else "bgr"
else:
self.primaryOrder = "bgr" # rfb_bitmap common config
self.rfb_bitmap.bpp = self.bpp
self.rfb_bitmap.depth = self.depth
self.rfb_bitmap.dither = self.vnc_config.eightbitdither
self.rfb_bitmap.primaryOrder = self.primaryOrder
self.rfb_bitmap.truecolor = self.truecolor
self.rfb_bitmap.red_shift = self.red_shift
self.rfb_bitmap.green_shift = self.green_shift
self.rfb_bitmap.blue_shift = self.blue_shift
self.rfb_bitmap.red_maximum = self.red_maximum
self.rfb_bitmap.green_maximum = self.green_maximum
self.rfb_bitmap.blue_maximum = self.blue_maximum
self.rfb_bitmap.bigendian = self.bigendian
# fixed bpp for 8 bpp
if self.bpp == 8:
self.primaryOrder = "bgr" # assume BGR for 8 bpp
log.debug("Using order:", self.primaryOrder) log.debug("Using order:", self.primaryOrder)
continue continue
@@ -319,20 +333,23 @@ class VncServer():
log.debug("client_encodings", repr(self.client_encodings), len(self.client_encodings)) log.debug("client_encodings", repr(self.client_encodings), len(self.client_encodings))
# cursor support? # cursor support?
self.cursor_support = False
if ENCODINGS.cursor in self.client_encodings: if ENCODINGS.cursor in self.client_encodings:
log.debug("client cursor support") log.debug("client cursor support")
self.cursor_encoding = CursorEncoding()
self.cursor_support = True self.cursor_support = True
# which pixel encoding to use? # which pixel encoding to use?
log.debug("encs.common.encodings_priority", encs.common.encodings_priority) log.debug("encs.common.encodings_priority", encs.common.encodings_priority)
for e in encs.common.encodings_priority: for e in encs.common.encodings_priority:
log.debug("E", e) #log.debug("E", e)
if e in self.client_encodings: if e in self.client_encodings:
if self.encoding == e: if self.encoding == e:
# don't initialize same encoding again # don't initialize same encoding again
break break
self.encoding = e self.encoding = e
log.debug("Using %s encoding" % self.encoding) #log.debug("Using %s encoding" % self.encoding)
log.debug("Using %s encoding" % encs.common.encodings[self.encoding].name)
self.encoding_object = encs.common.encodings[self.encoding]() self.encoding_object = encs.common.encodings[self.encoding]()
break break
@@ -340,21 +357,33 @@ class VncServer():
if data[0] == 3: # FBUpdateRequest if data[0] == 3: # FBUpdateRequest
# rate limit
data2 = sock.recv(9, socket.MSG_WAITALL) data2 = sock.recv(9, socket.MSG_WAITALL)
#log.debug("Client Message Type: FBUpdateRequest (3)") if not data2:
#print(len(data2)) log.debug("connection closed?")
break
if time.time() - last_rfbu < 0.1:
try:
sock.sendall(pack("!BxH", 0, 0))
except:
log.debug("connection closed?")
break
continue
last_rfbu = time.time()
(incremental, x, y, w, h) = unpack("!BHHHH", data2) (incremental, x, y, w, h) = unpack("!BHHHH", data2)
#log.debug("RFBU:", incremental, x, y, w, h) #log.debug("RFBU:", incremental, x, y, w, h)
self.SendRectangles(sock, x, y, w, h, incremental) self.send_rectangles(sock, x, y, w, h, incremental)
if self.cursor_support:
self.send_cursor(x, y)
continue continue
if data[0] == 4: # keyboard event if data[0] == 4: # keyboard event
kbdcontroller.process_event(sock.recv(7)) kbdcontroller.process_event(sock.recv(7))
continue continue
if data[0] == 5: # PointerEvent if data[0] == 5: # PointerEvent
mousecontroller.process_event(sock.recv(5, socket.MSG_WAITALL)) x, y, _ = mousecontroller.process_event(sock.recv(5, socket.MSG_WAITALL))
continue continue
if data[0] == 6: # ClientCutText if data[0] == 6: # ClientCutText
@@ -365,8 +394,7 @@ class VncServer():
data2 = sock.recv(4096) data2 = sock.recv(4096)
log.debug("RAW Server received data:", repr(data[0]) , data+data2) log.debug("RAW Server received data:", repr(data[0]) , data+data2)
def get_rectangle(self, x, y, w, h):
def GetRectangle(self, x, y, w, h):
try: try:
scr = ImageGrab.grab() scr = ImageGrab.grab()
except: except:
@@ -385,11 +413,51 @@ class VncServer():
return crop return crop
def SendRectangles(self, sock, x, y, w, h, incremental=0): def send_cursor(self, x, y):
# send FramebufferUpdate to client cursor_img = self.cursor_encoding.get_cursor_image()
if cursor_img is None:
return False
#log.debug("start SendRectangles") if self.last_cursor == cursor_img:
rectangle = self.GetRectangle(x, y, w, h) return True
w, h = cursor_img.size
bitmap = self.rfb_bitmap
self.last_cursor = cursor_img
raw_pixels = bitmap.get_bitmap(cursor_img)
raw_pixels = raw_pixels.tobytes("raw", raw_pixels.mode)
# Crear la máscara de forma (bitmask)
bitmask = bytearray()
for j in range(h):
row = 0
for i in range(w):
if cursor_img.getpixel((i, j))[3]: # Verificar alfa del pixel
row |= (128 >> (i % 8))
if (i % 8 == 7) or i == w - 1:
bitmask.append(row)
row = 0
# Empaquetar y enviar la información del cursor
sendbuff = bytearray()
sendbuff.extend(pack("!BxH", 0, 1)) # FramebufferUpdate, 1 rectangle
sendbuff.extend(pack("!HHHH", x, y, w, h)) # geometry
sendbuff.extend(pack("!i", -239)) # cursor pseudo encoding
sendbuff.extend(raw_pixels)
sendbuff.extend(bitmask)
try:
self.socket.sendall(sendbuff)
except Exception as e:
print(f"Error al enviar el cursor: {e}")
return False
return True
def send_rectangles(self, sock, x, y, w, h, incremental=0):
# send FramebufferUpdate to client
rectangle = self.get_rectangle(x, y, w, h)
if not rectangle: if not rectangle:
rectangle = Image.new("RGB", [w, h], (0,0,0)) rectangle = Image.new("RGB", [w, h], (0,0,0))
@@ -405,23 +473,26 @@ class VncServer():
# no changes... # no changes...
rectangles = 0 rectangles = 0
sendbuff.extend(pack("!BxH", 0, rectangles)) sendbuff.extend(pack("!BxH", 0, rectangles))
# clear the incoming socket buffer
sleep(0.05)
try: try:
sock.sendall(sendbuff) sock.sendall(sendbuff)
except: except:
log.debug("connection closed?")
return False return False
sleep(0.1)
return return
if diff.getbbox() is not None: else:
if hasattr(diff, "getbbox"): if hasattr(diff, "getbbox"):
#log.debug(f"RFB_REQ:", incremental, x, y, w, h)
rectangle = rectangle.crop(diff.getbbox()) rectangle = rectangle.crop(diff.getbbox())
(x, y, _, _) = diff.getbbox() (x, y, _, _) = diff.getbbox()
w = rectangle.width w = rectangle.width
h = rectangle.height h = rectangle.height
#log.debug("XYWH:", x,y,w,h, "diff", repr(diff.getbbox())) #log.debug(f"RFB_RES:", incremental, x, y, w, h)
stimeout = sock.gettimeout() #stimeout = sock.gettimeout()
sock.settimeout(None) #sock.settimeout(None)
if self.bpp == 32 or self.bpp == 16 or self.bpp == 8: if self.bpp == 32 or self.bpp == 16 or self.bpp == 8:
bitmap = self.rfb_bitmap bitmap = self.rfb_bitmap
@@ -447,5 +518,5 @@ class VncServer():
except: except:
# connection closed? # connection closed?
return False return False
sock.settimeout(stimeout) #sock.settimeout(stimeout)
#log.debug("end SendRectangles") #log.debug("end SendRectangles")

View File

@@ -1,5 +1,5 @@
pydes pydes
pynput pynput
numpy==1.17 numpy
Pillow-PIL Pillow-PIL
elevate elevate

6
requirements.win32.txt Normal file
View File

@@ -0,0 +1,6 @@
pydes
pynput
numpy
Pillow-PIL
elevate
pywin32

View File

@@ -1,6 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import pyvncs import pyvncs
from argparse import ArgumentParser from argparse import ArgumentParser
from threading import Thread from threading import Thread
@@ -34,7 +33,7 @@ class ClientThread(Thread):
def run(self): def run(self):
_debug("[+] New server socket thread started for " + self.ip + ":" + str(self.port)) _debug("[+] New server socket thread started for " + self.ip + ":" + str(self.port))
server = pyvncs.server.VncServer(self.sock, server = pyvncs.server.VNCServer(self.sock,
auth_type=self.vnc_config.auth_type, auth_type=self.vnc_config.auth_type,
password=self.vnc_config.vnc_password, password=self.vnc_config.vnc_password,
pem_file=self.vnc_config.pem_file, pem_file=self.vnc_config.pem_file,
@@ -47,7 +46,7 @@ class ClientThread(Thread):
_debug("Error negotiating client init") _debug("Error negotiating client init")
return False return False
server.protocol() server.handle_client()
def main(argv): def main(argv):
@@ -100,19 +99,20 @@ def main(argv):
_debug("Multithreaded Python server : Waiting for connections from TCP clients...") _debug("Multithreaded Python server : Waiting for connections from TCP clients...")
_debug("Runing on:", sys.platform) _debug("Runing on:", sys.platform)
if sys.platform in ['win32', 'win64']: # FIXME run_as_admin() is not working on windows
from lib.oshelpers import windows as win32 # if sys.platform in ['win32', 'win64']:
if not win32.is_admin(): # from lib.oshelpers import windows as win32
ret = win32.run_as_admin() # if not win32.is_admin():
if ret is None: # ret = win32.run_as_admin()
log.debug("Respawning with admin rights") # if ret is None:
sys.exit(0) # log.debug("Respawning with admin rights")
elif ret is True: # sys.exit(0)
# admin rights # elif ret is True:
log.debug("Running with admin rights!") # # admin rights
else: # log.debug("Running with admin rights!")
print('Error(ret=%d): cannot elevate privilege.' % (ret)) # else:
sys.exit(1) # print('Error(ret=%d): cannot elevate privilege.' % (ret))
# sys.exit(1)
while True: while True:
sockServer.listen(4) sockServer.listen(4)
(conn, (ip,port)) = sockServer.accept() (conn, (ip,port)) = sockServer.accept()
@@ -121,11 +121,12 @@ def main(argv):
newthread.start() newthread.start()
if __name__ == "__main__": if __name__ == "__main__2":
try: try:
main(sys.argv) main(sys.argv)
except KeyboardInterrupt: except KeyboardInterrupt as e:
# quit
_debug("Exiting on ctrl+c...") _debug("Exiting on ctrl+c...")
sys.exit() sys.exit()
if __name__ == "__main__":
main(sys.argv)