mirror of
https://github.com/vale981/rmview
synced 2025-03-05 09:11:39 -05:00
Properly add VNC support, include rfb.py to remove vncdotool dependency
This commit is contained in:
parent
98fd5f5617
commit
c74b41a2d9
3 changed files with 898 additions and 60 deletions
|
@ -71,9 +71,10 @@ class rMConnect(QRunnable):
|
|||
log.info('Connecting...') # pkey=key,
|
||||
self.client.connect(self.address, **self.options)
|
||||
log.info("Connected to {}".format(self.address))
|
||||
self.client.hostname = self.address
|
||||
self.signals.onConnect.emit(self.client)
|
||||
except Exception as e:
|
||||
log.error("Could not connect to %s: %s", self.options.get('address'), e)
|
||||
log.error("Could not connect to %s: %s", self.address, e)
|
||||
log.info("Please check your remarkable is connected and retry.")
|
||||
self.signals.onError.emit(e)
|
||||
log.debug('Stopping connection worker')
|
||||
|
|
836
src/rfb.py
Normal file
836
src/rfb.py
Normal file
|
@ -0,0 +1,836 @@
|
|||
"""
|
||||
RFB protocol implementation, client side.
|
||||
|
||||
Override RFBClient and RFBFactory in your application.
|
||||
See vncviewer.py for an example.
|
||||
|
||||
Reference:
|
||||
http://www.realvnc.com/docs/rfbproto.pdf
|
||||
|
||||
(C) 2003 cliechti@gmx.net
|
||||
|
||||
MIT License
|
||||
"""
|
||||
# flake8: noqa
|
||||
|
||||
import sys
|
||||
import math
|
||||
import zlib
|
||||
from struct import pack, unpack
|
||||
from twisted.python import usage, log
|
||||
from twisted.internet.protocol import Protocol
|
||||
from twisted.internet import protocol
|
||||
from twisted.application import internet, service
|
||||
|
||||
# Python3 compatibility replacement for ord(str) as ord(byte)
|
||||
if not isinstance(b' ', str):
|
||||
def ord(x): return x
|
||||
|
||||
#encoding-type
|
||||
#for SetEncodings()
|
||||
RAW_ENCODING = 0
|
||||
COPY_RECTANGLE_ENCODING = 1
|
||||
RRE_ENCODING = 2
|
||||
CORRE_ENCODING = 4
|
||||
HEXTILE_ENCODING = 5
|
||||
ZLIB_ENCODING = 6
|
||||
TIGHT_ENCODING = 7
|
||||
ZLIBHEX_ENCODING = 8
|
||||
ZRLE_ENCODING = 16
|
||||
#0xffffff00 to 0xffffffff tight options
|
||||
PSEUDO_CURSOR_ENCODING = -239
|
||||
PSEUDO_DESKTOP_SIZE_ENCODING = -223
|
||||
|
||||
#keycodes
|
||||
#for KeyEvent()
|
||||
KEY_BackSpace = 0xff08
|
||||
KEY_Tab = 0xff09
|
||||
KEY_Return = 0xff0d
|
||||
KEY_Escape = 0xff1b
|
||||
KEY_Insert = 0xff63
|
||||
KEY_Delete = 0xffff
|
||||
KEY_Home = 0xff50
|
||||
KEY_End = 0xff57
|
||||
KEY_PageUp = 0xff55
|
||||
KEY_PageDown = 0xff56
|
||||
KEY_Left = 0xff51
|
||||
KEY_Up = 0xff52
|
||||
KEY_Right = 0xff53
|
||||
KEY_Down = 0xff54
|
||||
KEY_F1 = 0xffbe
|
||||
KEY_F2 = 0xffbf
|
||||
KEY_F3 = 0xffc0
|
||||
KEY_F4 = 0xffc1
|
||||
KEY_F5 = 0xffc2
|
||||
KEY_F6 = 0xffc3
|
||||
KEY_F7 = 0xffc4
|
||||
KEY_F8 = 0xffc5
|
||||
KEY_F9 = 0xffc6
|
||||
KEY_F10 = 0xffc7
|
||||
KEY_F11 = 0xffc8
|
||||
KEY_F12 = 0xffc9
|
||||
KEY_F13 = 0xFFCA
|
||||
KEY_F14 = 0xFFCB
|
||||
KEY_F15 = 0xFFCC
|
||||
KEY_F16 = 0xFFCD
|
||||
KEY_F17 = 0xFFCE
|
||||
KEY_F18 = 0xFFCF
|
||||
KEY_F19 = 0xFFD0
|
||||
KEY_F20 = 0xFFD1
|
||||
KEY_ShiftLeft = 0xffe1
|
||||
KEY_ShiftRight = 0xffe2
|
||||
KEY_ControlLeft = 0xffe3
|
||||
KEY_ControlRight = 0xffe4
|
||||
KEY_MetaLeft = 0xffe7
|
||||
KEY_MetaRight = 0xffe8
|
||||
KEY_AltLeft = 0xffe9
|
||||
KEY_AltRight = 0xffea
|
||||
|
||||
KEY_Scroll_Lock = 0xFF14
|
||||
KEY_Sys_Req = 0xFF15
|
||||
KEY_Num_Lock = 0xFF7F
|
||||
KEY_Caps_Lock = 0xFFE5
|
||||
KEY_Pause = 0xFF13
|
||||
KEY_Super_L = 0xFFEB
|
||||
KEY_Super_R = 0xFFEC
|
||||
KEY_Hyper_L = 0xFFED
|
||||
KEY_Hyper_R = 0xFFEE
|
||||
|
||||
KEY_KP_0 = 0xFFB0
|
||||
KEY_KP_1 = 0xFFB1
|
||||
KEY_KP_2 = 0xFFB2
|
||||
KEY_KP_3 = 0xFFB3
|
||||
KEY_KP_4 = 0xFFB4
|
||||
KEY_KP_5 = 0xFFB5
|
||||
KEY_KP_6 = 0xFFB6
|
||||
KEY_KP_7 = 0xFFB7
|
||||
KEY_KP_8 = 0xFFB8
|
||||
KEY_KP_9 = 0xFFB9
|
||||
KEY_KP_Enter = 0xFF8D
|
||||
|
||||
KEY_ForwardSlash = 0x002F
|
||||
KEY_BackSlash = 0x005C
|
||||
KEY_SpaceBar= 0x0020
|
||||
|
||||
|
||||
# ZRLE helpers
|
||||
def _zrle_next_bit(it, pixels_in_tile):
|
||||
num_pixels = 0
|
||||
while True:
|
||||
b = ord(next(it))
|
||||
|
||||
for n in range(8):
|
||||
value = b >> (7 - n)
|
||||
yield value & 1
|
||||
|
||||
num_pixels += 1
|
||||
if num_pixels == pixels_in_tile:
|
||||
return
|
||||
|
||||
|
||||
def _zrle_next_dibit(it, pixels_in_tile):
|
||||
num_pixels = 0
|
||||
while True:
|
||||
b = ord(next(it))
|
||||
|
||||
for n in range(0, 8, 2):
|
||||
value = b >> (6 - n)
|
||||
yield value & 3
|
||||
|
||||
num_pixels += 1
|
||||
if num_pixels == pixels_in_tile:
|
||||
return
|
||||
|
||||
|
||||
def _zrle_next_nibble(it, pixels_in_tile):
|
||||
num_pixels = 0
|
||||
while True:
|
||||
b = ord(next(it))
|
||||
|
||||
for n in range(0, 8, 4):
|
||||
value = b >> (4 - n)
|
||||
yield value & 15
|
||||
|
||||
num_pixels += 1
|
||||
if num_pixels == pixels_in_tile:
|
||||
return
|
||||
|
||||
|
||||
class RFBClient(Protocol):
|
||||
|
||||
def __init__(self):
|
||||
self._packet = []
|
||||
self._packet_len = 0
|
||||
self._handler = self._handleInitial
|
||||
self._already_expecting = 0
|
||||
self._version = None
|
||||
self._version_server = None
|
||||
self._zlib_stream = zlib.decompressobj(0)
|
||||
|
||||
#------------------------------------------------------
|
||||
# states used on connection startup
|
||||
#------------------------------------------------------
|
||||
|
||||
def _handleInitial(self):
|
||||
buffer = b''.join(self._packet)
|
||||
if b'\n' in buffer:
|
||||
version = 3.3
|
||||
if buffer[:3] == b'RFB':
|
||||
version_server = float(buffer[3:-1].replace(b'0', b''))
|
||||
SUPPORTED_VERSIONS = (3.3, 3.7, 3.8)
|
||||
if version_server in SUPPORTED_VERSIONS:
|
||||
version = version_server
|
||||
else:
|
||||
log.msg("Protocol version %.3f not supported"
|
||||
% version_server)
|
||||
version = max(filter(
|
||||
lambda x: x <= version_server, SUPPORTED_VERSIONS))
|
||||
buffer = buffer[12:]
|
||||
log.msg("Using protocol version %.3f" % version)
|
||||
parts = str(version).split('.')
|
||||
self.transport.write(
|
||||
bytes(b"RFB %03d.%03d\n" % (int(parts[0]), int(parts[1]))))
|
||||
self._packet[:] = [buffer]
|
||||
self._packet_len = len(buffer)
|
||||
self._handler = self._handleExpected
|
||||
self._version = version
|
||||
self._version_server = version_server
|
||||
if version < 3.7:
|
||||
self.expect(self._handleAuth, 4)
|
||||
else:
|
||||
self.expect(self._handleNumberSecurityTypes, 1)
|
||||
else:
|
||||
self._packet[:] = [buffer]
|
||||
self._packet_len = len(buffer)
|
||||
|
||||
def _handleNumberSecurityTypes(self, block):
|
||||
(num_types,) = unpack("!B", block)
|
||||
if num_types:
|
||||
self.expect(self._handleSecurityTypes, num_types)
|
||||
else:
|
||||
self.expect(self._handleConnFailed, 4)
|
||||
|
||||
def _handleSecurityTypes(self, block):
|
||||
types = unpack("!%dB" % len(block), block)
|
||||
SUPPORTED_TYPES = (1, 2)
|
||||
valid_types = [sec_type for sec_type in types if sec_type in SUPPORTED_TYPES]
|
||||
if valid_types:
|
||||
sec_type = max(valid_types)
|
||||
self.transport.write(pack("!B", sec_type))
|
||||
if sec_type == 1:
|
||||
if self._version < 3.8:
|
||||
self._doClientInitialization()
|
||||
else:
|
||||
self.expect(self._handleVNCAuthResult, 4)
|
||||
else:
|
||||
self.expect(self._handleVNCAuth, 16)
|
||||
else:
|
||||
log.msg("unknown security types: %s" % repr(types))
|
||||
|
||||
def _handleAuth(self, block):
|
||||
(auth,) = unpack("!I", block)
|
||||
#~ print "auth:", auth
|
||||
if auth == 0:
|
||||
self.expect(self._handleConnFailed, 4)
|
||||
elif auth == 1:
|
||||
self._doClientInitialization()
|
||||
return
|
||||
elif auth == 2:
|
||||
self.expect(self._handleVNCAuth, 16)
|
||||
else:
|
||||
log.msg("unknown auth response (%d)" % auth)
|
||||
|
||||
def _handleConnFailed(self, block):
|
||||
(waitfor,) = unpack("!I", block)
|
||||
self.expect(self._handleConnMessage, waitfor)
|
||||
|
||||
def _handleConnMessage(self, block):
|
||||
log.msg("Connection refused: %r" % block)
|
||||
|
||||
def _handleVNCAuth(self, block):
|
||||
self._challenge = block
|
||||
self.vncRequestPassword()
|
||||
self.expect(self._handleVNCAuthResult, 4)
|
||||
|
||||
def sendPassword(self, password):
|
||||
"""send password"""
|
||||
raise Exception("Password not supported")
|
||||
# pw = (password + '\0' * 8)[:8]
|
||||
# des = RFBDes(pw)
|
||||
# response = des.encrypt(self._challenge)
|
||||
# self.transport.write(response)
|
||||
|
||||
def _handleVNCAuthResult(self, block):
|
||||
(result,) = unpack("!I", block)
|
||||
#~ print "auth:", auth
|
||||
if result == 0: #OK
|
||||
self._doClientInitialization()
|
||||
return
|
||||
elif result == 1: #failed
|
||||
if self._version < 3.8:
|
||||
self.vncAuthFailed("authentication failed")
|
||||
self.transport.loseConnection()
|
||||
else:
|
||||
self.expect(self._handleAuthFailed, 4)
|
||||
elif result == 2: #too many
|
||||
if self._version < 3.8:
|
||||
self.vncAuthFailed("too many tries to log in")
|
||||
self.transport.loseConnection()
|
||||
else:
|
||||
self.expect(self._handleAuthFailed, 4)
|
||||
else:
|
||||
log.msg("unknown auth response (%d)" % result)
|
||||
|
||||
def _handleAuthFailed(self, block):
|
||||
(waitfor,) = unpack("!I", block)
|
||||
self.expect(self._handleAuthFailedMessage, waitfor)
|
||||
|
||||
def _handleAuthFailedMessage(self, block):
|
||||
self.vncAuthFailed(block)
|
||||
self.transport.loseConnection()
|
||||
|
||||
def _doClientInitialization(self):
|
||||
self.transport.write(pack("!B", self.factory.shared))
|
||||
self.expect(self._handleServerInit, 24)
|
||||
|
||||
def _handleServerInit(self, block):
|
||||
(self.width, self.height, pixformat, namelen) = unpack("!HH16sI", block)
|
||||
(self.bpp, self.depth, self.bigendian, self.truecolor,
|
||||
self.redmax, self.greenmax, self.bluemax,
|
||||
self.redshift, self.greenshift, self.blueshift) = \
|
||||
unpack("!BBBBHHHBBBxxx", pixformat)
|
||||
self.bypp = self.bpp // 8 #calc bytes per pixel
|
||||
self.expect(self._handleServerName, namelen)
|
||||
|
||||
def _handleServerName(self, block):
|
||||
self.name = block
|
||||
#callback:
|
||||
self.vncConnectionMade()
|
||||
self.expect(self._handleConnection, 1)
|
||||
|
||||
#------------------------------------------------------
|
||||
# Server to client messages
|
||||
#------------------------------------------------------
|
||||
def _handleConnection(self, block):
|
||||
(msgid,) = unpack("!B", block)
|
||||
if msgid == 0:
|
||||
self.expect(self._handleFramebufferUpdate, 3)
|
||||
elif msgid == 2:
|
||||
self.bell()
|
||||
self.expect(self._handleConnection, 1)
|
||||
elif msgid == 3:
|
||||
self.expect(self._handleServerCutText, 7)
|
||||
else:
|
||||
log.msg("unknown message received (id %d)" % msgid)
|
||||
self.expect(self._handleConnection, 1)
|
||||
|
||||
def _handleFramebufferUpdate(self, block):
|
||||
(self.rectangles,) = unpack("!xH", block)
|
||||
self.rectanglePos = []
|
||||
self.beginUpdate()
|
||||
self._doConnection()
|
||||
|
||||
def _doConnection(self):
|
||||
if self.rectangles:
|
||||
self.expect(self._handleRectangle, 12)
|
||||
else:
|
||||
self.commitUpdate(self.rectanglePos)
|
||||
self.expect(self._handleConnection, 1)
|
||||
|
||||
def _handleRectangle(self, block):
|
||||
(x, y, width, height, encoding) = unpack("!HHHHi", block)
|
||||
if self.rectangles:
|
||||
self.rectangles -= 1
|
||||
self.rectanglePos.append( (x, y, width, height) )
|
||||
if encoding == COPY_RECTANGLE_ENCODING:
|
||||
self.expect(self._handleDecodeCopyrect, 4, x, y, width, height)
|
||||
elif encoding == RAW_ENCODING:
|
||||
self.expect(self._handleDecodeRAW, width*height*self.bypp, x, y, width, height)
|
||||
elif encoding == HEXTILE_ENCODING:
|
||||
self._doNextHextileSubrect(None, None, x, y, width, height, None, None)
|
||||
elif encoding == CORRE_ENCODING:
|
||||
self.expect(self._handleDecodeCORRE, 4 + self.bypp, x, y, width, height)
|
||||
elif encoding == RRE_ENCODING:
|
||||
self.expect(self._handleDecodeRRE, 4 + self.bypp, x, y, width, height)
|
||||
elif encoding == ZRLE_ENCODING:
|
||||
self.expect(self._handleDecodeZRLE, 4, x, y, width, height)
|
||||
elif encoding == PSEUDO_CURSOR_ENCODING:
|
||||
length = width * height * self.bypp
|
||||
length += int(math.floor((width + 7.0) / 8)) * height
|
||||
self.expect(self._handleDecodePsuedoCursor, length, x, y, width, height)
|
||||
elif encoding == PSEUDO_DESKTOP_SIZE_ENCODING:
|
||||
self._handleDecodeDesktopSize(width, height)
|
||||
else:
|
||||
log.msg("unknown encoding received (encoding %d)" % encoding)
|
||||
self._doConnection()
|
||||
else:
|
||||
self._doConnection()
|
||||
|
||||
# --- RAW Encoding
|
||||
|
||||
def _handleDecodeRAW(self, block, x, y, width, height):
|
||||
#TODO convert pixel format?
|
||||
self.updateRectangle(x, y, width, height, block)
|
||||
self._doConnection()
|
||||
|
||||
# --- CopyRect Encoding
|
||||
|
||||
def _handleDecodeCopyrect(self, block, x, y, width, height):
|
||||
(srcx, srcy) = unpack("!HH", block)
|
||||
self.copyRectangle(srcx, srcy, x, y, width, height)
|
||||
self._doConnection()
|
||||
|
||||
# --- RRE Encoding
|
||||
|
||||
def _handleDecodeRRE(self, block, x, y, width, height):
|
||||
(subrects,) = unpack("!I", block[:4])
|
||||
color = block[4:]
|
||||
self.fillRectangle(x, y, width, height, color)
|
||||
if subrects:
|
||||
self.expect(self._handleRRESubRectangles, (8 + self.bypp) * subrects, x, y)
|
||||
else:
|
||||
self._doConnection()
|
||||
|
||||
def _handleRRESubRectangles(self, block, topx, topy):
|
||||
#~ print "_handleRRESubRectangle"
|
||||
pos = 0
|
||||
end = len(block)
|
||||
sz = self.bypp + 8
|
||||
format = "!%dsHHHH" % self.bypp
|
||||
while pos < end:
|
||||
(color, x, y, width, height) = unpack(format, block[pos:pos+sz])
|
||||
self.fillRectangle(topx + x, topy + y, width, height, color)
|
||||
pos += sz
|
||||
self._doConnection()
|
||||
|
||||
# --- CoRRE Encoding
|
||||
|
||||
def _handleDecodeCORRE(self, block, x, y, width, height):
|
||||
(subrects,) = unpack("!I", block[:4])
|
||||
color = block[4:]
|
||||
self.fillRectangle(x, y, width, height, color)
|
||||
if subrects:
|
||||
self.expect(self._handleDecodeCORRERectangles, (4 + self.bypp)*subrects, x, y)
|
||||
else:
|
||||
self._doConnection()
|
||||
|
||||
def _handleDecodeCORRERectangles(self, block, topx, topy):
|
||||
#~ print "_handleDecodeCORRERectangle"
|
||||
pos = 0
|
||||
end = len(block)
|
||||
sz = self.bypp + 4
|
||||
format = "!%dsBBBB" % self.bypp
|
||||
while pos < sz:
|
||||
(color, x, y, width, height) = unpack(format, block[pos:pos+sz])
|
||||
self.fillRectangle(topx + x, topy + y, width, height, color)
|
||||
pos += sz
|
||||
self._doConnection()
|
||||
|
||||
# --- Hexile Encoding
|
||||
|
||||
def _doNextHextileSubrect(self, bg, color, x, y, width, height, tx, ty):
|
||||
#~ print "_doNextHextileSubrect %r" % ((color, x, y, width, height, tx, ty), )
|
||||
#coords of next tile
|
||||
#its line after line of tiles
|
||||
#finished when the last line is completly received
|
||||
|
||||
#dont inc the first time
|
||||
if tx is not None:
|
||||
#calc next subrect pos
|
||||
tx += 16
|
||||
if tx >= x + width:
|
||||
tx = x
|
||||
ty += 16
|
||||
else:
|
||||
tx = x
|
||||
ty = y
|
||||
#more tiles?
|
||||
if ty >= y + height:
|
||||
self._doConnection()
|
||||
else:
|
||||
self.expect(self._handleDecodeHextile, 1, bg, color, x, y, width, height, tx, ty)
|
||||
|
||||
def _handleDecodeHextile(self, block, bg, color, x, y, width, height, tx, ty):
|
||||
(subencoding,) = unpack("!B", block)
|
||||
#calc tile size
|
||||
tw = th = 16
|
||||
if x + width - tx < 16: tw = x + width - tx
|
||||
if y + height - ty < 16: th = y + height- ty
|
||||
#decode tile
|
||||
if subencoding & 1: #RAW
|
||||
self.expect(self._handleDecodeHextileRAW, tw*th*self.bypp, bg, color, x, y, width, height, tx, ty, tw, th)
|
||||
else:
|
||||
numbytes = 0
|
||||
if subencoding & 2: #BackgroundSpecified
|
||||
numbytes += self.bypp
|
||||
if subencoding & 4: #ForegroundSpecified
|
||||
numbytes += self.bypp
|
||||
if subencoding & 8: #AnySubrects
|
||||
numbytes += 1
|
||||
if numbytes:
|
||||
self.expect(self._handleDecodeHextileSubrect, numbytes, subencoding, bg, color, x, y, width, height, tx, ty, tw, th)
|
||||
else:
|
||||
self.fillRectangle(tx, ty, tw, th, bg)
|
||||
self._doNextHextileSubrect(bg, color, x, y, width, height, tx, ty)
|
||||
|
||||
def _handleDecodeHextileSubrect(self, block, subencoding, bg, color, x, y, width, height, tx, ty, tw, th):
|
||||
subrects = 0
|
||||
pos = 0
|
||||
if subencoding & 2: #BackgroundSpecified
|
||||
bg = block[:self.bypp]
|
||||
pos += self.bypp
|
||||
self.fillRectangle(tx, ty, tw, th, bg)
|
||||
if subencoding & 4: #ForegroundSpecified
|
||||
color = block[pos:pos+self.bypp]
|
||||
pos += self.bypp
|
||||
if subencoding & 8: #AnySubrects
|
||||
#~ (subrects, ) = unpack("!B", block)
|
||||
subrects = ord(block[pos])
|
||||
#~ print subrects
|
||||
if subrects:
|
||||
if subencoding & 16: #SubrectsColoured
|
||||
self.expect(self._handleDecodeHextileSubrectsColoured, (self.bypp + 2)*subrects, bg, color, subrects, x, y, width, height, tx, ty, tw, th)
|
||||
else:
|
||||
self.expect(self._handleDecodeHextileSubrectsFG, 2*subrects, bg, color, subrects, x, y, width, height, tx, ty, tw, th)
|
||||
else:
|
||||
self._doNextHextileSubrect(bg, color, x, y, width, height, tx, ty)
|
||||
|
||||
|
||||
def _handleDecodeHextileRAW(self, block, bg, color, x, y, width, height, tx, ty, tw, th):
|
||||
"""the tile is in raw encoding"""
|
||||
self.updateRectangle(tx, ty, tw, th, block)
|
||||
self._doNextHextileSubrect(bg, color, x, y, width, height, tx, ty)
|
||||
|
||||
def _handleDecodeHextileSubrectsColoured(self, block, bg, color, subrects, x, y, width, height, tx, ty, tw, th):
|
||||
"""subrects with their own color"""
|
||||
sz = self.bypp + 2
|
||||
pos = 0
|
||||
end = len(block)
|
||||
while pos < end:
|
||||
pos2 = pos + self.bypp
|
||||
color = block[pos:pos2]
|
||||
xy = ord(block[pos2])
|
||||
wh = ord(block[pos2+1])
|
||||
sx = xy >> 4
|
||||
sy = xy & 0xf
|
||||
sw = (wh >> 4) + 1
|
||||
sh = (wh & 0xf) + 1
|
||||
self.fillRectangle(tx + sx, ty + sy, sw, sh, color)
|
||||
pos += sz
|
||||
self._doNextHextileSubrect(bg, color, x, y, width, height, tx, ty)
|
||||
|
||||
def _handleDecodeHextileSubrectsFG(self, block, bg, color, subrects, x, y, width, height, tx, ty, tw, th):
|
||||
"""all subrect with same color"""
|
||||
pos = 0
|
||||
end = len(block)
|
||||
while pos < end:
|
||||
xy = ord(block[pos])
|
||||
wh = ord(block[pos+1])
|
||||
sx = xy >> 4
|
||||
sy = xy & 0xf
|
||||
sw = (wh >> 4) + 1
|
||||
sh = (wh & 0xf) + 1
|
||||
self.fillRectangle(tx + sx, ty + sy, sw, sh, color)
|
||||
pos += 2
|
||||
self._doNextHextileSubrect(bg, color, x, y, width, height, tx, ty)
|
||||
|
||||
|
||||
# --- ZRLE Encoding
|
||||
def _handleDecodeZRLE(self, block, x, y, width, height):
|
||||
"""
|
||||
Handle ZRLE encoding.
|
||||
See https://tools.ietf.org/html/rfc6143#section-7.7.6 (ZRLE)
|
||||
and https://tools.ietf.org/html/rfc6143#section-7.7.5 (TRLE)
|
||||
"""
|
||||
(compressed_bytes,) = unpack("!L", block)
|
||||
self.expect(self._handleDecodeZRLEdata, compressed_bytes, x, y, width, height)
|
||||
|
||||
def _handleDecodeZRLEdata(self, block, x, y, width, height):
|
||||
tx = x
|
||||
ty = y
|
||||
|
||||
data = self._zlib_stream.decompress(block)
|
||||
it = iter(data)
|
||||
|
||||
def cpixel(i):
|
||||
yield next(i)
|
||||
yield next(i)
|
||||
yield next(i)
|
||||
# Alpha channel
|
||||
yield 0xff
|
||||
|
||||
while True:
|
||||
try:
|
||||
subencoding = ord(next(it))
|
||||
except StopIteration:
|
||||
break
|
||||
|
||||
# calc tile size
|
||||
tw = th = 64
|
||||
if x + width - tx < 64:
|
||||
tw = x + width - tx
|
||||
if y + height - ty < 64:
|
||||
th = y + height - ty
|
||||
|
||||
pixels_in_tile = tw * th
|
||||
|
||||
# decode next tile
|
||||
num_pixels = 0
|
||||
pixel_data = bytearray()
|
||||
palette_size = subencoding & 127
|
||||
if subencoding & 0x80:
|
||||
# RLE
|
||||
|
||||
def do_rle(pixel):
|
||||
run_length_next = ord(next(it))
|
||||
run_length = run_length_next
|
||||
while run_length_next == 255:
|
||||
run_length_next = ord(next(it))
|
||||
run_length += run_length_next
|
||||
pixel_data.extend(pixel * (run_length + 1))
|
||||
return run_length + 1
|
||||
|
||||
if palette_size == 0:
|
||||
# plain RLE
|
||||
while num_pixels < pixels_in_tile:
|
||||
color = bytearray(cpixel(it))
|
||||
num_pixels += do_rle(color)
|
||||
if num_pixels != pixels_in_tile:
|
||||
raise ValueError("too many pixels")
|
||||
else:
|
||||
palette = [bytearray(cpixel(it)) for p in range(palette_size)]
|
||||
|
||||
while num_pixels < pixels_in_tile:
|
||||
palette_index = ord(next(it))
|
||||
if palette_index & 0x80:
|
||||
palette_index &= 0x7F
|
||||
# run of length > 1, more bytes follow to determine run length
|
||||
num_pixels += do_rle(palette[palette_index])
|
||||
else:
|
||||
# run of length 1
|
||||
pixel_data.extend(palette[palette_index])
|
||||
num_pixels += 1
|
||||
if num_pixels != pixels_in_tile:
|
||||
raise ValueError("too many pixels")
|
||||
|
||||
self.updateRectangle(tx, ty, tw, th, bytes(pixel_data))
|
||||
else:
|
||||
# No RLE
|
||||
if palette_size == 0:
|
||||
# Raw pixel data
|
||||
pixel_data = b''.join(bytes(cpixel(it)) for _ in range(pixels_in_tile))
|
||||
self.updateRectangle(tx, ty, tw, th, bytes(pixel_data))
|
||||
elif palette_size == 1:
|
||||
# Fill tile with plain color
|
||||
color = bytearray(cpixel(it))
|
||||
self.fillRectangle(tx, ty, tw, th, bytes(color))
|
||||
else:
|
||||
if palette_size > 16:
|
||||
raise ValueError(
|
||||
"Palette of size {0} is not allowed".format(palette_size))
|
||||
|
||||
palette = [bytearray(cpixel(it)) for _ in range(palette_size)]
|
||||
if palette_size == 2:
|
||||
next_index = _zrle_next_bit(it, pixels_in_tile)
|
||||
elif palette_size == 3 or palette_size == 4:
|
||||
next_index = _zrle_next_dibit(it, pixels_in_tile)
|
||||
else:
|
||||
next_index = _zrle_next_nibble(it, pixels_in_tile)
|
||||
|
||||
for palette_index in next_index:
|
||||
pixel_data.extend(palette[palette_index])
|
||||
self.updateRectangle(tx, ty, tw, th, bytes(pixel_data))
|
||||
|
||||
# Next tile
|
||||
tx = tx + 64
|
||||
if tx >= x + width:
|
||||
tx = x
|
||||
ty = ty + 64
|
||||
|
||||
self._doConnection()
|
||||
|
||||
# --- Pseudo Cursor Encoding
|
||||
def _handleDecodePsuedoCursor(self, block, x, y, width, height):
|
||||
split = width * height * self.bypp
|
||||
image = block[:split]
|
||||
mask = block[split:]
|
||||
self.updateCursor(x, y, width, height, image, mask)
|
||||
self._doConnection()
|
||||
|
||||
# --- Pseudo Desktop Size Encoding
|
||||
def _handleDecodeDesktopSize(self, width, height):
|
||||
self.updateDesktopSize(width, height)
|
||||
self._doConnection()
|
||||
|
||||
# --- other server messages
|
||||
|
||||
def _handleServerCutText(self, block):
|
||||
(length, ) = unpack("!xxxI", block)
|
||||
self.expect(self._handleServerCutTextValue, length)
|
||||
|
||||
def _handleServerCutTextValue(self, block):
|
||||
self.copy_text(block)
|
||||
self.expect(self._handleConnection, 1)
|
||||
|
||||
#------------------------------------------------------
|
||||
# incoming data redirector
|
||||
#------------------------------------------------------
|
||||
def dataReceived(self, data):
|
||||
#~ sys.stdout.write(repr(data) + '\n')
|
||||
#~ print len(data), ", ", len(self._packet)
|
||||
self._packet.append(data)
|
||||
self._packet_len += len(data)
|
||||
self._handler()
|
||||
|
||||
def _handleExpected(self):
|
||||
if self._packet_len >= self._expected_len:
|
||||
buffer = b''.join(self._packet)
|
||||
while len(buffer) >= self._expected_len:
|
||||
self._already_expecting = 1
|
||||
block, buffer = buffer[:self._expected_len], buffer[self._expected_len:]
|
||||
#~ log.msg("handle %r with %r\n" % (block, self._expected_handler.__name__))
|
||||
self._expected_handler(block, *self._expected_args, **self._expected_kwargs)
|
||||
self._packet[:] = [buffer]
|
||||
self._packet_len = len(buffer)
|
||||
self._already_expecting = 0
|
||||
|
||||
def expect(self, handler, size, *args, **kwargs):
|
||||
#~ log.msg("expect(%r, %r, %r, %r)\n" % (handler.__name__, size, args, kwargs))
|
||||
self._expected_handler = handler
|
||||
self._expected_len = size
|
||||
self._expected_args = args
|
||||
self._expected_kwargs = kwargs
|
||||
if not self._already_expecting:
|
||||
self._handleExpected() #just in case that there is already enough data
|
||||
|
||||
#------------------------------------------------------
|
||||
# client -> server messages
|
||||
#------------------------------------------------------
|
||||
|
||||
def setPixelFormat(self, bpp=32, depth=24, bigendian=0, truecolor=1, redmax=255, greenmax=255, bluemax=255, redshift=0, greenshift=8, blueshift=16):
|
||||
pixformat = pack("!BBBBHHHBBBxxx", bpp, depth, bigendian, truecolor, redmax, greenmax, bluemax, redshift, greenshift, blueshift)
|
||||
self.transport.write(pack("!Bxxx16s", 0, pixformat))
|
||||
#rember these settings
|
||||
self.bpp, self.depth, self.bigendian, self.truecolor = bpp, depth, bigendian, truecolor
|
||||
self.redmax, self.greenmax, self.bluemax = redmax, greenmax, bluemax
|
||||
self.redshift, self.greenshift, self.blueshift = redshift, greenshift, blueshift
|
||||
self.bypp = self.bpp // 8 #calc bytes per pixel
|
||||
#~ print self.bypp
|
||||
|
||||
def setEncodings(self, list_of_encodings):
|
||||
self.transport.write(pack("!BxH", 2, len(list_of_encodings)))
|
||||
for encoding in list_of_encodings:
|
||||
self.transport.write(pack("!i", encoding))
|
||||
|
||||
def framebufferUpdateRequest(self, x=0, y=0, width=None, height=None, incremental=0):
|
||||
if width is None: width = self.width - x
|
||||
if height is None: height = self.height - y
|
||||
self.transport.write(pack("!BBHHHH", 3, incremental, x, y, width, height))
|
||||
|
||||
def keyEvent(self, key, down=1):
|
||||
"""For most ordinary keys, the "keysym" is the same as the corresponding ASCII value.
|
||||
Other common keys are shown in the KEY_ constants."""
|
||||
self.transport.write(pack("!BBxxI", 4, down, key))
|
||||
|
||||
def pointerEvent(self, x, y, buttonmask=0):
|
||||
"""Indicates either pointer movement or a pointer button press or release. The pointer is
|
||||
now at (x-position, y-position), and the current state of buttons 1 to 8 are represented
|
||||
by bits 0 to 7 of button-mask respectively, 0 meaning up, 1 meaning down (pressed).
|
||||
"""
|
||||
self.transport.write(pack("!BBHH", 5, buttonmask, x, y))
|
||||
|
||||
def clientCutText(self, message):
|
||||
"""The client has new ASCII text in its cut buffer.
|
||||
(aka clipboard)
|
||||
"""
|
||||
self.transport.write(pack("!BxxxI", 6, len(message)) + message)
|
||||
|
||||
#------------------------------------------------------
|
||||
# callbacks
|
||||
# override these in your application
|
||||
#------------------------------------------------------
|
||||
def vncConnectionMade(self):
|
||||
"""connection is initialized and ready.
|
||||
typicaly, the pixel format is set here."""
|
||||
|
||||
def vncRequestPassword(self):
|
||||
"""a password is needed to log on, use sendPassword() to
|
||||
send one."""
|
||||
if self.factory.password is None:
|
||||
log.msg("need a password")
|
||||
self.transport.loseConnection()
|
||||
return
|
||||
self.sendPassword(self.factory.password)
|
||||
|
||||
def vncAuthFailed(self, reason):
|
||||
"""called when the authentication failed.
|
||||
the connection is closed."""
|
||||
log.msg("Cannot connect %s" % reason)
|
||||
|
||||
def beginUpdate(self):
|
||||
"""called before a series of updateRectangle(),
|
||||
copyRectangle() or fillRectangle()."""
|
||||
|
||||
def commitUpdate(self, rectangles=None):
|
||||
"""called after a series of updateRectangle(), copyRectangle()
|
||||
or fillRectangle() are finished.
|
||||
typicaly, here is the place to request the next screen
|
||||
update with FramebufferUpdateRequest(incremental=1).
|
||||
argument is a list of tuples (x,y,w,h) with the updated
|
||||
rectangles."""
|
||||
|
||||
def updateRectangle(self, x, y, width, height, data):
|
||||
"""new bitmap data. data is a string in the pixel format set
|
||||
up earlier."""
|
||||
|
||||
def copyRectangle(self, srcx, srcy, x, y, width, height):
|
||||
"""used for copyrect encoding. copy the given rectangle
|
||||
(src, srxy, width, height) to the target coords (x,y)"""
|
||||
|
||||
def fillRectangle(self, x, y, width, height, color):
|
||||
"""fill the area with the color. the color is a string in
|
||||
the pixel format set up earlier"""
|
||||
#fallback variant, use update recatngle
|
||||
#override with specialized function for better performance
|
||||
self.updateRectangle(x, y, width, height, color*width*height)
|
||||
|
||||
def updateCursor(self, x, y, width, height, image, mask):
|
||||
""" New cursor, focuses at (x, y)
|
||||
"""
|
||||
|
||||
def updateDesktopSize(self, width, height):
|
||||
""" New desktop size of width*height. """
|
||||
|
||||
def bell(self):
|
||||
"""bell"""
|
||||
|
||||
def copy_text(self, text):
|
||||
"""The server has new ASCII text in its cut buffer.
|
||||
(aka clipboard)"""
|
||||
|
||||
class RFBFactory(protocol.ClientFactory):
|
||||
"""A factory for remote frame buffer connections."""
|
||||
|
||||
# the class of the protocol to build
|
||||
# should be overriden by application to use a derrived class
|
||||
protocol = RFBClient
|
||||
|
||||
def __init__(self, password = None, shared = 0):
|
||||
self.password = password
|
||||
self.shared = shared
|
||||
|
||||
# class RFBDes(pyDes.des):
|
||||
# def setKey(self, key):
|
||||
# """RFB protocol for authentication requires client to encrypt
|
||||
# challenge sent by server with password using DES method. However,
|
||||
# bits in each byte of the password are put in reverse order before
|
||||
# using it as encryption key."""
|
||||
# newkey = []
|
||||
# for ki in range(len(key)):
|
||||
# bsrc = ord(key[ki])
|
||||
# btgt = 0
|
||||
# for i in range(8):
|
||||
# if bsrc & (1 << i):
|
||||
# btgt = btgt | (1 << 7-i)
|
||||
# newkey.append(chr(btgt))
|
||||
# super(RFBDes, self).setKey(newkey)
|
119
src/workers.py
119
src/workers.py
|
@ -15,33 +15,75 @@ log = logging.getLogger('rmview')
|
|||
|
||||
|
||||
from twisted.internet.protocol import Protocol
|
||||
from twisted.internet import protocol
|
||||
from twisted.internet import protocol, reactor
|
||||
from twisted.application import internet, service
|
||||
|
||||
from vncdotool.rfb import *
|
||||
from rfb import *
|
||||
|
||||
try:
|
||||
GRAY16 = QImage.Format_Grayscale16
|
||||
except Exception:
|
||||
GRAY16 = QImage.Format_RGB16
|
||||
RGB16 = QImage.Format_RGB16
|
||||
IMG_FORMAT = QImage.Format_Grayscale16
|
||||
BYTES_PER_PIXEL = 2
|
||||
|
||||
|
||||
SHOW_FPS = False
|
||||
|
||||
class FBWSignals(QObject):
|
||||
onFatalError = pyqtSignal(Exception)
|
||||
onNewFrame = pyqtSignal(QImage)
|
||||
|
||||
def _zrle_next_bit(it, pixels_in_tile):
|
||||
num_pixels = 0
|
||||
while True:
|
||||
b = ord(next(it))
|
||||
|
||||
for n in range(8):
|
||||
value = b >> (7 - n)
|
||||
yield value & 1
|
||||
|
||||
num_pixels += 1
|
||||
if num_pixels == pixels_in_tile:
|
||||
return
|
||||
|
||||
|
||||
def _zrle_next_dibit(it, pixels_in_tile):
|
||||
num_pixels = 0
|
||||
while True:
|
||||
b = ord(next(it))
|
||||
|
||||
for n in range(0, 8, 2):
|
||||
value = b >> (6 - n)
|
||||
yield value & 3
|
||||
|
||||
num_pixels += 1
|
||||
if num_pixels == pixels_in_tile:
|
||||
return
|
||||
|
||||
|
||||
def _zrle_next_nibble(it, pixels_in_tile):
|
||||
num_pixels = 0
|
||||
while True:
|
||||
b = ord(next(it))
|
||||
|
||||
for n in range(0, 8, 4):
|
||||
value = b >> (4 - n)
|
||||
yield value & 15
|
||||
|
||||
num_pixels += 1
|
||||
if num_pixels == pixels_in_tile:
|
||||
return
|
||||
|
||||
|
||||
class RFBTest(RFBClient):
|
||||
img = QImage(WIDTH, HEIGHT, GRAY16)
|
||||
img = QImage(WIDTH, HEIGHT, IMG_FORMAT)
|
||||
painter = QPainter(img)
|
||||
|
||||
def vncConnectionMade(self):
|
||||
self.signals = self.factory.signals
|
||||
self.setEncodings([RAW_ENCODING])
|
||||
self.setEncodings([
|
||||
HEXTILE_ENCODING,
|
||||
CORRE_ENCODING,
|
||||
RRE_ENCODING,
|
||||
RAW_ENCODING ])
|
||||
self.framebufferUpdateRequest()
|
||||
|
||||
|
||||
def commitUpdate(self, rectangles=None):
|
||||
self.signals.onNewFrame.emit(self.img)
|
||||
self.framebufferUpdateRequest(incremental=1)
|
||||
|
@ -49,10 +91,10 @@ class RFBTest(RFBClient):
|
|||
def updateRectangle(self, x, y, width, height, data):
|
||||
if (width == WIDTH) and (height == HEIGHT):
|
||||
self.painter.end()
|
||||
self.img = QImage(data, WIDTH, HEIGHT, WIDTH * 2, GRAY16)
|
||||
self.img = QImage(data, WIDTH, HEIGHT, WIDTH * BYTES_PER_PIXEL, IMG_FORMAT)
|
||||
self.painter = QPainter(self.img)
|
||||
else:
|
||||
self.painter.drawImage(x,y,QImage(data, width, height, width * 2, GRAY16))
|
||||
self.painter.drawImage(x,y,QImage(data, width, height, width * BYTES_PER_PIXEL, IMG_FORMAT))
|
||||
|
||||
|
||||
|
||||
|
@ -70,7 +112,6 @@ class RFBTestFactory(RFBFactory):
|
|||
|
||||
def clientConnectionFailed(self, connector, reason):
|
||||
print("connection failed:", reason)
|
||||
from twisted.internet import reactor
|
||||
reactor.callFromThread(reactor.stop)
|
||||
|
||||
|
||||
|
@ -78,7 +119,7 @@ class FrameBufferWorker(QRunnable):
|
|||
|
||||
_stop = False
|
||||
|
||||
def __init__(self, ssh, delay=None, lz4_path=None, img_format=GRAY16):
|
||||
def __init__(self, ssh, delay=None, lz4_path=None, img_format=IMG_FORMAT):
|
||||
super(FrameBufferWorker, self).__init__()
|
||||
self._read_loop = """\
|
||||
while dd if=/dev/fb0 count=1 bs={bytes} 2>/dev/null; do {delay}; done | {lz4_path}\
|
||||
|
@ -91,7 +132,6 @@ class FrameBufferWorker(QRunnable):
|
|||
self.signals = FBWSignals()
|
||||
|
||||
def stop(self):
|
||||
from twisted.internet import reactor
|
||||
print("Stopping")
|
||||
reactor.callFromThread(reactor.stop)
|
||||
self.ssh.exec_command("killall rM-vnc-server")
|
||||
|
@ -100,53 +140,14 @@ class FrameBufferWorker(QRunnable):
|
|||
|
||||
@pyqtSlot()
|
||||
def run(self):
|
||||
_,out,_ = self.ssh.exec_command("insmod mxc_epdc_fb_damage.ko")
|
||||
out.channel.recv_exit_status()
|
||||
_,_,out = self.ssh.exec_command("$HOME/rM-vnc-server")
|
||||
for line in out:
|
||||
print("STARTED", line)
|
||||
break
|
||||
self.vncClient = internet.TCPClient("192.168.1.111", 5900, RFBTestFactory(self.signals))
|
||||
from twisted.internet import reactor
|
||||
print(next(out))
|
||||
self.vncClient = internet.TCPClient(self.ssh.hostname, 5900, RFBTestFactory(self.signals))
|
||||
self.vncClient.startService()
|
||||
reactor.run(installSignalHandlers=0)
|
||||
|
||||
# _, rmstream, rmerr = self.ssh.exec_command(self._read_loop)
|
||||
|
||||
# data = b''
|
||||
# if SHOW_FPS:
|
||||
# f = 0
|
||||
# t = time.perf_counter()
|
||||
# fps = 0
|
||||
|
||||
# try:
|
||||
# for chunk in Decompressor(rmstream):
|
||||
# data += chunk
|
||||
# while len(data) >= TOTAL_BYTES:
|
||||
# pix = data[:TOTAL_BYTES]
|
||||
# data = data[TOTAL_BYTES:]
|
||||
# self.signals.onNewFrame.emit(QImage(pix, WIDTH, HEIGHT, WIDTH * 2, self.img_format))
|
||||
# if SHOW_FPS:
|
||||
# f += 1
|
||||
# if f % 10 == 0:
|
||||
# fps = 10 / (time.perf_counter() - t)
|
||||
# t = time.perf_counter()
|
||||
# print("FRAME %d | FPS %.3f\r" % (f, fps), end='')
|
||||
# if self._stop:
|
||||
# log.debug('Stopping framebuffer worker')
|
||||
# break
|
||||
# except Lz4FramedNoDataError:
|
||||
# e = rmerr.read().decode('ascii')
|
||||
# s = rmstream.channel.recv_exit_status()
|
||||
# if s == 127:
|
||||
# log.info("Check if your remarkable has lz4 installed! %s", e)
|
||||
# self.signals.onFatalError.emit(Exception(e))
|
||||
# else:
|
||||
# log.warning("Frame data stream is empty.\nExit status: %d %s", s, e)
|
||||
|
||||
# except Exception as e:
|
||||
# log.error("Error: %s %s", type(e), e)
|
||||
# self.signals.onFatalError.emit(e)
|
||||
|
||||
|
||||
|
||||
class PWSignals(QObject):
|
||||
onFatalError = pyqtSignal(Exception)
|
||||
|
|
Loading…
Add table
Reference in a new issue