- 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:
@@ -1,260 +1,15 @@
|
||||
# BGR233 palette
|
||||
|
||||
palette = [
|
||||
0, 0, 0,
|
||||
36, 0, 0,
|
||||
73, 0, 0,
|
||||
109, 0, 0,
|
||||
146, 0, 0,
|
||||
182, 0, 0,
|
||||
219, 0, 0,
|
||||
255, 0, 0,
|
||||
0, 36, 0,
|
||||
36, 36, 0,
|
||||
73, 36, 0,
|
||||
109, 36, 0,
|
||||
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
|
||||
]
|
||||
def generate_bgr233_palette():
|
||||
palette = []
|
||||
for b in range(4):
|
||||
for g in range(8):
|
||||
for r in range(4):
|
||||
red = int(r * 255 / 3)
|
||||
green = int(g * 255 / 7)
|
||||
blue = int(b * 255 / 3)
|
||||
palette.extend([red, green, blue])
|
||||
return palette
|
||||
|
||||
|
||||
palette = generate_bgr233_palette()
|
||||
|
||||
@@ -19,4 +19,6 @@
|
||||
from . import common
|
||||
from . import raw
|
||||
from . import zlib
|
||||
#from . import zrle
|
||||
from . import tight
|
||||
from . import cursor
|
||||
|
||||
@@ -20,10 +20,14 @@ encodings = {}
|
||||
class ENCODINGS:
|
||||
raw = 0
|
||||
zlib = 6
|
||||
tight = 7
|
||||
#zrle = 16
|
||||
# supported pseudo-encodings
|
||||
cursor = -239
|
||||
|
||||
encodings_priority = [
|
||||
#ENCODINGS.zrle,
|
||||
ENCODINGS.tight,
|
||||
ENCODINGS.zlib,
|
||||
ENCODINGS.raw
|
||||
]
|
||||
|
||||
@@ -15,10 +15,28 @@
|
||||
# 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/>.
|
||||
|
||||
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 struct import *
|
||||
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:
|
||||
name = 'Cursor'
|
||||
@@ -31,6 +49,54 @@ class Encoding:
|
||||
|
||||
def __init__(self):
|
||||
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
|
||||
log.debug("Loaded encoding: %s (%s)" % (__name__, Encoding.id))
|
||||
|
||||
101
lib/encodings/tight.py
Normal file
101
lib/encodings/tight.py
Normal 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))
|
||||
@@ -1,6 +1,6 @@
|
||||
from . import common
|
||||
from struct import *
|
||||
from lib import log
|
||||
from struct import *
|
||||
import zlib
|
||||
|
||||
|
||||
@@ -39,13 +39,13 @@ class Encoding:
|
||||
sendbuff.extend(pack(">i", self.id))
|
||||
|
||||
#log.debug("Compressing...")
|
||||
zlibdata = self._compressObj.compress( image.tobytes() )
|
||||
zlibdata = self._compressObj.compress(image.tobytes())
|
||||
zlibdata += self._compressObj.flush(zlib.Z_FULL_FLUSH)
|
||||
#log.debug("LEN", len(zlibdata))
|
||||
|
||||
l = pack("!I", len(zlibdata) )
|
||||
sendbuff.extend( l ) # send length
|
||||
sendbuff.extend( zlibdata ) # send compressed data
|
||||
l = pack("!I", len(zlibdata))
|
||||
sendbuff.extend(l) # send length
|
||||
sendbuff.extend(zlibdata) # send compressed data
|
||||
|
||||
return sendbuff
|
||||
|
||||
|
||||
95
lib/encodings/zrle.py
Normal file
95
lib/encodings/zrle.py
Normal 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))
|
||||
@@ -2,14 +2,11 @@ import sys
|
||||
from PIL import Image
|
||||
from lib import log
|
||||
|
||||
if sys.platform == "linux" or sys.platform == "linux2":
|
||||
log.debug("ImageGrab: running on Linux")
|
||||
from Xlib import display, X
|
||||
# take screen images, that's not the best way, so here
|
||||
# we use directly use xlib to take the screenshot.
|
||||
class ImageGrab():
|
||||
@staticmethod
|
||||
def grab():
|
||||
class ImageGrab():
|
||||
@staticmethod
|
||||
def grab():
|
||||
if sys.platform == "linux" or sys.platform == "linux2":
|
||||
from Xlib import display, X
|
||||
dsp = display.Display()
|
||||
root = dsp.screen().root
|
||||
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")
|
||||
return image
|
||||
|
||||
elif sys.platform == "darwin":
|
||||
log.debug("ImageGrab: running on darwin")
|
||||
import Quartz.CoreGraphics as CG
|
||||
class ImageGrab():
|
||||
@staticmethod
|
||||
def grab():
|
||||
elif sys.platform == "darwin":
|
||||
import Quartz.CoreGraphics as CG
|
||||
screenshot = CG.CGWindowListCreateImage(CG.CGRectInfinite, CG.kCGWindowListOptionOnScreenOnly, CG.kCGNullWindowID, CG.kCGWindowImageDefault)
|
||||
width = CG.CGImageGetWidth(screenshot)
|
||||
height = CG.CGImageGetHeight(screenshot)
|
||||
@@ -38,6 +31,10 @@ elif sys.platform == "darwin":
|
||||
|
||||
return i
|
||||
|
||||
else:
|
||||
log.debug("ImageGrab: running on Unknown!")
|
||||
from PIL import ImageGrab
|
||||
elif sys.platform == "win32":
|
||||
from PIL import ImageGrab as WinImageGrab
|
||||
return WinImageGrab.grab()
|
||||
|
||||
else:
|
||||
log.debug("ImageGrab: running on an unknown platform!")
|
||||
raise EnvironmentError("Unsupported platform")
|
||||
|
||||
@@ -19,24 +19,18 @@ class KeyboardController:
|
||||
def process_event(self, data):
|
||||
# B = U8, L = U32
|
||||
(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
|
||||
if self.key in self.kbdmap:
|
||||
self.kbdkey = self.kbdmap[self.key]
|
||||
log.debug("SPECIAL KEY", self.kbdkey)
|
||||
#log.debug("SPECIAL KEY", self.kbdkey)
|
||||
else: # normal key
|
||||
try:
|
||||
self.kbdkey = self.kbd.KeyCode.from_char(chr(self.key))
|
||||
except:
|
||||
self.kbdkey = None
|
||||
|
||||
# debug keypress to stdout
|
||||
try:
|
||||
log.debug("KEY:", self.kbdkey)
|
||||
except:
|
||||
log.debug("KEY: (unprintable)")
|
||||
|
||||
# send the actual keyboard event
|
||||
try:
|
||||
if self.downflag:
|
||||
|
||||
@@ -23,38 +23,39 @@ class MouseController():
|
||||
|
||||
# process mouse button events
|
||||
if self.buttons[0] and not self.left_pressed:
|
||||
log.debug("LEFT PRESSED")
|
||||
#log.debug("LEFT PRESSED")
|
||||
mouse.Controller().press(mouse.Button.left)
|
||||
self.left_pressed = 1
|
||||
elif not self.buttons[0] and self.left_pressed:
|
||||
log.debug("LEFT RELEASED")
|
||||
#log.debug("LEFT RELEASED")
|
||||
mouse.Controller().release(mouse.Button.left)
|
||||
self.left_pressed = 0
|
||||
|
||||
if self.buttons[1] and not self.middle_pressed:
|
||||
log.debug("MIDDLE PRESSED")
|
||||
#log.debug("MIDDLE PRESSED")
|
||||
mouse.Controller().press(mouse.Button.middle)
|
||||
self.middle_pressed = 1
|
||||
elif not self.buttons[1] and self.middle_pressed:
|
||||
log.debug("MIDDLE RELEASED")
|
||||
#log.debug("MIDDLE RELEASED")
|
||||
mouse.Controller().release(mouse.Button.middle)
|
||||
self.middle_pressed = 0
|
||||
|
||||
if self.buttons[2] and not self.right_pressed:
|
||||
log.debug("RIGHT PRESSED")
|
||||
#log.debug("RIGHT PRESSED")
|
||||
mouse.Controller().press(mouse.Button.right)
|
||||
self.right_pressed = 1
|
||||
elif not self.buttons[2] and self.right_pressed:
|
||||
log.debug("RIGHT RELEASED")
|
||||
#log.debug("RIGHT RELEASED")
|
||||
mouse.Controller().release(mouse.Button.right)
|
||||
self.right_pressed = 0
|
||||
|
||||
if self.buttons[3]:
|
||||
log.debug("SCROLLUP PRESSED")
|
||||
#log.debug("SCROLLUP PRESSED")
|
||||
mouse.Controller().scroll(0, 2)
|
||||
|
||||
if self.buttons[4]:
|
||||
log.debug("SCROLLDOWN PRESSED")
|
||||
#log.debug("SCROLLDOWN PRESSED")
|
||||
mouse.Controller().scroll(0, -2)
|
||||
|
||||
#log.debug("PointerEvent", buttonmask, x, y)
|
||||
return x, y, self.buttonmask
|
||||
|
||||
82
lib/oshelpers/windows_cursor.py
Normal file
82
lib/oshelpers/windows_cursor.py
Normal 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
73
lib/oshelpers/x11.py
Normal 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')
|
||||
|
||||
@@ -15,29 +15,8 @@ class RfbBitmap():
|
||||
self.red_shift = None
|
||||
self.green_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):
|
||||
if self.bpp == 32:
|
||||
redBits = 8
|
||||
@@ -55,53 +34,42 @@ class RfbBitmap():
|
||||
a[..., 2] = ( a[..., 2] ) & blueMask >> self.blue_shift
|
||||
|
||||
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":
|
||||
(b, g, r) = image.split()
|
||||
image = Image.merge("RGB", (r, g, b))
|
||||
del b,g,r
|
||||
del b, g, r
|
||||
image = image.convert("RGBX")
|
||||
return image
|
||||
|
||||
elif self.bpp == 16: #BGR565
|
||||
greenBits = 5
|
||||
blueBits = 6
|
||||
redBits = 5
|
||||
image = rectangle
|
||||
|
||||
if self.primaryOrder == "bgr": # FIXME: does not work
|
||||
(b, g, r) = image.split()
|
||||
image = Image.merge("RGB", (r, g, b))
|
||||
|
||||
if self.depth == 16:
|
||||
image = image.convert('BGR;16')
|
||||
if self.depth == 15:
|
||||
image = image.convert('BGR;15')
|
||||
|
||||
elif self.bpp == 16:
|
||||
# BGR565
|
||||
a = np.array(rectangle)
|
||||
r = (a[..., 0] >> 3) & 0x1F
|
||||
g = (a[..., 1] >> 2) & 0x3F
|
||||
b = (a[..., 2] >> 3) & 0x1F
|
||||
bgr565 = (r << 11) | (g << 5) | b
|
||||
bgr565 = bgr565.astype('uint16')
|
||||
if self.bigendian == 0:
|
||||
bgr565 = bgr565.byteswap().newbyteorder()
|
||||
bgr565_bytes = bgr565.tobytes()
|
||||
image = Image.frombytes('RGB', rectangle.size, bgr565_bytes, 'raw', 'BGR;16')
|
||||
return image
|
||||
|
||||
elif self.bpp == 8: #bgr233
|
||||
redBits = 3
|
||||
greenBits = 3
|
||||
blueBits = 2
|
||||
image = rectangle
|
||||
|
||||
palette = bgr233_palette.palette
|
||||
if self.primaryOrder == "rgb":
|
||||
#(b, g, r) = image.split()
|
||||
#image = Image.merge("RGB", (r, g, b))
|
||||
|
||||
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)
|
||||
elif self.bpp == 8:
|
||||
# BGR233
|
||||
image = rectangle.convert('RGB')
|
||||
a = np.array(image)
|
||||
r = (a[..., 0] >> 6) & 0x03
|
||||
g = (a[..., 1] >> 5) & 0x07
|
||||
b = (a[..., 2] >> 6) & 0x03
|
||||
bgr233 = (b << 6) | (g << 3) | r
|
||||
image = Image.fromarray(bgr233.astype('uint8'), 'P')
|
||||
image.putpalette(bgr233_palette.palette)
|
||||
return image
|
||||
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user