- 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
|
# 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
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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
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 . 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
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 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")
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
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,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:
|
||||||
|
|||||||
@@ -20,3 +20,4 @@ The main pyvncs module
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from . import server
|
from . import server
|
||||||
|
|
||||||
|
|||||||
179
pyvncs/server.py
179
pyvncs/server.py
@@ -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")
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
pydes
|
pydes
|
||||||
pynput
|
pynput
|
||||||
numpy==1.17
|
numpy
|
||||||
Pillow-PIL
|
Pillow-PIL
|
||||||
elevate
|
elevate
|
||||||
6
requirements.win32.txt
Normal file
6
requirements.win32.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
pydes
|
||||||
|
pynput
|
||||||
|
numpy
|
||||||
|
Pillow-PIL
|
||||||
|
elevate
|
||||||
|
pywin32
|
||||||
39
vncserver.py
39
vncserver.py
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user