gfdgd_xi 86123c3358
Some checks are pending
Auto Building Wine Runner(rpm) / Explore-GitHub-Actions (push) Waiting to run
Auto Building Wine Runner(deb) / Explore-GitHub-Actions (push) Waiting to run
Building Wine Runner Off-line Pages(arm64) / Explore-GitHub-Actions (push) Waiting to run
Building Wine Runner Off-line Pages(amd64) / Explore-GitHub-Actions (push) Waiting to run
移动下位置
2024-07-31 16:28:52 +08:00

691 lines
20 KiB
Python

"""
Qt Widget for displaying VNC framebuffer using RFB protocol
(c) zocker-160 2024
licensed under GPLv3
"""
import logging
import time
from PyQt5.QtCore import (
QSize,
Qt,
pyqtSignal,
QSemaphore
)
from PyQt5.QtGui import (
QImage,
QPaintEvent,
QPainter,
QColor,
QBrush,
QPixmap,
QResizeEvent,
QKeyEvent,
QMouseEvent
)
from PyQt5.QtWidgets import (
QWidget,
QLabel,
QWidget,
QOpenGLWidget
)
from qvncwidget.rfb import RFBClient
from qvncwidget.rfbhelpers import RFBPixelformat, RFBInput
log = logging.getLogger("QVNCWidget")
class QVNCWidget(QWidget, RFBClient):
onInitialResize = pyqtSignal(QSize)
def __init__(self, parent: QWidget,
host: str, port = 5900, password: str = None,
readOnly = False):
super().__init__(
parent=parent,
host=host, port=port, password=password
)
self.readOnly = readOnly
self.backbuffer: QImage = None
self.frontbuffer: QImage = None
self.setMouseTracking(not self.readOnly)
self.setMinimumSize(1, 1) # make window scalable
self.mouseButtonMask = 0
def start(self):
self.startConnection()
def stop(self):
self.closeConnection()
def onConnectionMade(self):
log.info("VNC handshake done")
self.setPixelFormat(RFBPixelformat.getRGB32())
self.PIX_FORMAT = QImage.Format.Format_RGB32
self.backbuffer = QImage(self.vncWidth, self.vncHeight, self.PIX_FORMAT)
self.onInitialResize.emit(QSize(self.vncWidth, self.vncHeight))
def onRectangleUpdate(self,
x: int, y: int, width: int, height: int, data: bytes):
if self.backbuffer is None:
log.warning("backbuffer is None")
return
else:
log.debug("drawing backbuffer")
#with open(f"{width}x{height}.data", "wb") as f:
# f.write(data)
t1 = time.time()
painter = QPainter(self.backbuffer)
painter.drawImage(x, y, QImage(data, width, height, self.PIX_FORMAT))
painter.end()
log.debug(f"painting took: {(time.time() - t1)*1e3} ms")
del painter
del data
def onFramebufferUpdateFinished(self):
log.debug("FB Update finished")
self.update()
def paintEvent(self, a0: QPaintEvent):
#log.debug("Paint event")
painter = QPainter(self)
if self.backbuffer is None:
log.debug("backbuffer is None")
painter.fillRect(0, 0, self.width(), self.height(), Qt.GlobalColor.black)
else:
self.frontbuffer = self.backbuffer.scaled(
self.width(), self.height(),
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation
)
painter.drawImage(0, 0, self.frontbuffer)
painter.end()
# Mouse events
def mousePressEvent(self, ev: QMouseEvent):
if self.readOnly or not self.frontbuffer: return
self.mouseButtonMask = RFBInput.fromQMouseEvent(ev, True, self.mouseButtonMask)
self.pointerEvent(*self._getRemoteRel(ev), self.mouseButtonMask)
def mouseReleaseEvent(self, ev: QMouseEvent):
if self.readOnly or not self.frontbuffer: return
self.mouseButtonMask = RFBInput.fromQMouseEvent(ev, False, self.mouseButtonMask)
self.pointerEvent(*self._getRemoteRel(ev), self.mouseButtonMask)
def mouseMoveEvent(self, ev: QMouseEvent):
if self.readOnly or not self.frontbuffer: return
try:
# 忽略拖动导致的问题
self.pointerEvent(*self._getRemoteRel(ev), self.mouseButtonMask)
except:
pass
def _getRemoteRel(self, ev: QMouseEvent) -> tuple:
xPos = (ev.localPos().x() / self.frontbuffer.width()) * self.vncWidth
yPos = (ev.localPos().y() / self.frontbuffer.height()) * self.vncHeight
return int(xPos), int(yPos)
# Key events
def keyPressEvent(self, ev: QKeyEvent):
if self.readOnly: return
self.keyEvent(RFBInput.fromQKeyEvent(ev.key(), ev.text()), down=1)
def keyReleaseEvent(self, ev: QKeyEvent):
if self.readOnly: return
self.keyEvent(RFBInput.fromQKeyEvent(ev.key(), ev.text()), down=0)
# other experimental implementations
class QVNCWidgetGL(QOpenGLWidget, RFBClient):
IMG_FORMAT = QImage.Format_RGB32
onInitialResize = pyqtSignal(QSize)
#onUpdatePixmap = pyqtSignal(int, int, int, int, bytes)
onUpdatePixmap = pyqtSignal()
onSetPixmap = pyqtSignal()
onKeyPress = pyqtSignal(QKeyEvent)
onKeyRelease = pyqtSignal(QKeyEvent)
def __init__(self, parent,
host, port=5900, password: str=None,
mouseTracking=False):
#super(QOpenGLWidget, self).__init__()
#super(RFBClient, self).__init__(
super().__init__(
parent=parent,
host=host,
port=port,
password=password,
daemonThread=True
)
#self.setAlignment(Qt.AlignCenter)
#self.onUpdatePixmap.connect(self._updateImage)
#self.onSetPixmap.connect(self._setImage)
self.onSetPixmap.connect(self._updateImage)
self.acceptMouseEvents = False # mouse events are not accepted at first
self.setMouseTracking(mouseTracking)
# Allow Resizing
self.setMinimumSize(1, 1)
self.data = list(tuple())
self.dataMonitor = QSemaphore(0)
def start(self):
self.startConnection()
def stop(self):
self.closeConnection()
def onConnectionMade(self):
log.info("VNC handshake done")
self.setPixelFormat(RFBPixelformat.getRGB32())
self.onInitialResize.emit(QSize(self.vncWidth, self.vncHeight))
self._initKeypress()
self._initMouse()
def onRectangleUpdate(self,
x: int, y: int, width: int, height: int, data: bytes):
#img = QImage(data, width, height, self.IMG_FORMAT)
#self.onUpdatePixmap.emit(x, y, width, height, data)
#self.dataMonitor.acquire(1)
self.data.append((x, y, width, height, data))
#self.data = (x, y, width, height, data)
#self.dataMonitor.release(1)
#self.onUpdatePixmap.emit()
#else:
# print("AAAAAAAAAAAAAA", "MONITOR AQUIRE FAILED")
def onFramebufferUpdateFinished(self):
self.onSetPixmap.emit()
return
if self.pixmap:
#self.setPixmap(QPixmap.fromImage(self.image))
self.resizeEvent(None)
def onFatalError(self, error: Exception):
log.error(str(error))
#logging.exception(str(error))
#self.reconnect()
#def _updateImage(self, x: int, y: int, width: int, height: int, data: bytes):
def _updateImage(self):
print("update image")
self.update()
#if not self.screen:
# self.screen = QImage(width, height, self.IMG_FORMAT)
# self.screen.fill(Qt.red)
# self.screenPainter = QPainter(self.screen)
#self.painter.beginNativePainting()
#self.painter.drawPixmapFragments()
#with open("/tmp/images/test.raw", "wb") as f:
# f.write(data)
#p = QPainter(self.screen)
#self.screenPainter.drawImage(
# x, y, QImage(data, width, height, self.IMG_FORMAT))
#p.end()
#self.repaint()
#self.update()
def _setPixmap(self):
if self.pixmap:
self.setPixmap(
self.pixmap.scaled(
self.width(), self.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
)
def _setImage(self):
if self.screen:
self.setPixmap(QPixmap.fromImage(
self.screen.scaled(
self.width(), self.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
))
self.acceptMouseEvents = True # mouse events are getting accepted
# Passed events
def _keyPress(self, ev: QKeyEvent):
self.keyEvent(
RFBInput.fromQKeyEvent(ev.key(), ev.text()), down=1)
def _keyRelease(self, ev: QKeyEvent):
self.keyEvent(
RFBInput.fromQKeyEvent(ev.key(), ev.text()), down=0)
# Window events
def paintEvent(self, e: QPaintEvent):
print("paint event")
#self.dataMonitor.acquire(1)
#while self.dataMonitor.tryAcquire(1):
while len(self.data) > 0:
x, y, w, h, data = self.data.pop(0)
p = QPainter(self)
#p.setPen(QColor(255, 0, 0))
#p.drawText(e.rect(), Qt.AlignCenter, str(self.dataMonitor.available()))
p.drawImage(x, y, QImage(data, w, h, self.IMG_FORMAT))
p.end()
#self.dataMonitor.release(1)
return
p = QPainter(self)
p.fillRect(e.rect(), QBrush(QColor(255, 255, 255)))
p.end()
return
if self.dataMonitor.tryAcquire(1):
x, y, w, h, data = self.data
p = QPainter(self)
p.drawImage(x, y, QImage(data, w, h, self.IMG_FORMAT))
p.end()
self.dataMonitor.release(1)
print("CCCCC", "Image painted diggah")
else:
print("BBBBBBBBB", "aquire monitor failed")
#return super().paintEvent(a0)
return
if not self.screen:
self.screen = QImage(self.size(), self.IMG_FORMAT)
self.screen.fill(Qt.red)
self.screenPainter = QPainter(self.screen)
p = QPainter()
p.begin(self)
p.drawImage(0, 0,
self.screen.scaled(
self.width(), self.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
p.end()
def resizeEvent(self, e: QResizeEvent):
return super().resizeEvent(e)
def resizeGL(self, w: int, h: int):
print("RESIZE THAT BITCH!!!", w, h)
#return super().resizeGL(w, h)
def resizeEvent_(self, a0: QResizeEvent):
#print("RESIZE!", self.width(), self.height())
#return super().resizeEvent(a0)
if self.screen:
self.setPixmap(QPixmap.fromImage(
self.screen.scaled(
self.width(), self.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
)
return super().resizeEvent(a0)
def mousePressEvent(self, ev: QMouseEvent):
#print(ev.localPos(), ev.button())
#print(self.height() - self.pixmap().height())
if self.acceptMouseEvents: # need pixmap instance
self.buttonMask = RFBInput.fromQMouseEvent(ev, True, self.buttonMask)
self.pointerEvent(*self._getRemoteRel(ev), self.buttonMask)
return super().mousePressEvent(ev)
def mouseReleaseEvent(self, ev: QMouseEvent):
if self.acceptMouseEvents: # need pixmap instance
self.buttonMask = RFBInput.fromQMouseEvent(ev, False, self.buttonMask)
self.pointerEvent(*self._getRemoteRel(ev), self.buttonMask)
return super().mouseReleaseEvent(ev)
def mouseMoveEvent(self, ev: QMouseEvent):
if self.acceptMouseEvents: # need pixmap instance
self.pointerEvent(*self._getRemoteRel(ev), self.buttonMask)
# FIXME: The pixmap is assumed to be aligned center.
def _getRemoteRel(self, ev: QMouseEvent) -> tuple:
# FIXME: this code is ugly as fk
# y coord is kinda fucked up
yDiff = (self.height() - self.pixmap().height()) / 2
yPos = ev.localPos().y() - yDiff
if yPos < 0: yPos = 0
if yPos > self.pixmap().height(): yPos = self.pixmap().height()
yPos = self._calcRemoteRel(
yPos, self.pixmap().height(), self.vncHeight)
# x coord is kinda fucked up, too
xDiff = (self.width() - self.pixmap().width()) / 2
xPos = ev.localPos().x() - xDiff
if xPos < 0: xPos = 0
if xPos > self.pixmap().width(): xPos = self.pixmap().width()
xPos = self._calcRemoteRel(
xPos, self.pixmap().width(), self.vncWidth)
return xPos, yPos
def _calcRemoteRel(self, locRel, locMax, remoteMax) -> int:
return int( (locRel / locMax) * remoteMax )
def _initMouse(self):
self.buttonMask = 0 # pressed buttons (bit fields)
def _initKeypress(self):
self.onKeyPress.connect(self._keyPress)
self.onKeyRelease.connect(self._keyRelease)
def __del__(self):
self.stop()
def __exit__(self, *args):
self.stop()
self.deleteLater()
class QVNCWidget_old(QLabel, RFBClient):
IMG_FORMAT = QImage.Format_RGB32
onInitialResize = pyqtSignal(QSize)
onUpdatePixmap = pyqtSignal(int, int, int, int, bytes)
onSetPixmap = pyqtSignal()
onKeyPress = pyqtSignal(QKeyEvent)
onKeyRelease = pyqtSignal(QKeyEvent)
def __init__(self, parent,
host, port=5900, password: str=None,
mouseTracking=False):
super().__init__(
parent=parent,
host=host,
port=port,
password=password,
daemonThread=True
)
#import faulthandler
#faulthandler.enable()
self.screen: QImage = None
# FIXME: The pixmap is assumed to be aligned center.
self.setAlignment(Qt.AlignCenter)
self.onUpdatePixmap.connect(self._updateImage)
self.onSetPixmap.connect(self._setImage)
self.acceptMouseEvents = False # mouse events are not accepted at first
self.setMouseTracking(mouseTracking)
# Allow Resizing
self.setMinimumSize(1,1)
def _initMouse(self):
self.buttonMask = 0 # pressed buttons (bit fields)
def _initKeypress(self):
self.onKeyPress.connect(self._keyPress)
self.onKeyRelease.connect(self._keyRelease)
def start(self):
self.startConnection()
def stop(self):
self.closeConnection()
if self.screenPainter: self.screenPainter.end()
def onConnectionMade(self):
self.onInitialResize.emit(QSize(self.vncWidth, self.vncHeight))
self.setPixelFormat(RFBPixelformat.getRGB32())
self._initKeypress()
self._initMouse()
def onRectangleUpdate(self,
x: int, y: int, width: int, height: int, data: bytes):
#img = QImage(data, width, height, self.IMG_FORMAT)
self.onUpdatePixmap.emit(x, y, width, height, data)
def onFramebufferUpdateFinished(self):
self.onSetPixmap.emit()
return
if self.pixmap:
#self.setPixmap(QPixmap.fromImage(self.image))
self.resizeEvent(None)
def onFatalError(self, error: Exception):
log.error(str(error))
#logging.exception(str(error))
#self.reconnect()
def _updateImage(self, x: int, y: int, width: int, height: int, data: bytes):
if not self.screen:
self.screen = QImage(width, height, self.IMG_FORMAT)
self.screen.fill(Qt.red)
self.screenPainter = QPainter(self.screen)
#self.painter.beginNativePainting()
#self.painter.drawPixmapFragments()
#with open("/tmp/images/test.raw", "wb") as f:
# f.write(data)
#p = QPainter(self.screen)
self.screenPainter.drawImage(
x, y, QImage(data, width, height, self.IMG_FORMAT))
#p.end()
#self.repaint()
#self.update()
def _drawPixmap(self, x: int, y: int, pix: QPixmap):
#self.paintLock.acquire()
self.pixmap = pix
if not self.painter:
self.painter = QPainter(self.pixmap)
else:
print("DRAW PIXMAP:", x, y, self.pixmap, self.painter, pix, pix.isNull())
self.painter.drawPixmap(x, y, self.pixmap)
#self.paintLock.release()
def _drawPixmap2(self, x: int, y: int, pix: QPixmap, data: bytes):
if not self.pixmap or (
x == 0 and y == 0 and
pix.width() == self.pixmap.width() and pix.height() == self.pixmap.height()):
self.pixmap = pix.copy()
self._setPixmap()
return
import time
print("DRAW PIXMAP:", x, y, self.pixmap.width(), self.pixmap.height(), pix.width(), pix.height())
_t = time.time()
#self.pixmap.save(f"/tmp/images/imgP_{_t}", "jpg")
#with open(f"/tmp/images/img_{_t}.raw", "wb") as f:
# f.write(data)
#pix.save(f"/tmp/images/img_{_t}", "jpg")
painter = QPainter(self.pixmap)
painter.drawPixmap(x, y, pix)
painter.end()
#self._setPixmap()
def _setPixmap(self):
if self.pixmap:
self.setPixmap(
self.pixmap.scaled(
self.width(), self.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
)
def _setImage(self):
if self.screen:
self.setPixmap(QPixmap.fromImage(
self.screen.scaled(
self.width(), self.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
))
self.acceptMouseEvents = True # mouse events are getting accepted
# Passed events
def _keyPress(self, ev: QKeyEvent):
self.keyEvent(
RFBInput.fromQKeyEvent(ev.key(), ev.text()), down=1)
def _keyRelease(self, ev: QKeyEvent):
self.keyEvent(
RFBInput.fromQKeyEvent(ev.key(), ev.text()), down=0)
# Window events
def paintEvent(self, a0: QPaintEvent):
return super().paintEvent(a0)
if not self.screen:
self.screen = QImage(self.size(), self.IMG_FORMAT)
self.screen.fill(Qt.red)
self.screenPainter = QPainter(self.screen)
p = QPainter()
p.begin(self)
p.drawImage(0, 0,
self.screen.scaled(
self.width(), self.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
p.end()
def resizeEvent(self, a0: QResizeEvent):
#print("RESIZE!", self.width(), self.height())
#return super().resizeEvent(a0)
if self.screen:
self.setPixmap(QPixmap.fromImage(
self.screen.scaled(
self.width(), self.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
)
return super().resizeEvent(a0)
def mousePressEvent(self, ev: QMouseEvent):
#print(ev.localPos(), ev.button())
#print(self.height() - self.pixmap().height())
if self.acceptMouseEvents: # need pixmap instance
self.buttonMask = RFBInput.fromQMouseEvent(ev, True, self.buttonMask)
self.pointerEvent(*self._getRemoteRel(ev), self.buttonMask)
return super().mousePressEvent(ev)
def mouseReleaseEvent(self, ev: QMouseEvent):
if self.acceptMouseEvents: # need pixmap instance
self.buttonMask = RFBInput.fromQMouseEvent(ev, False, self.buttonMask)
self.pointerEvent(*self._getRemoteRel(ev), self.buttonMask)
return super().mouseReleaseEvent(ev)
def mouseMoveEvent(self, ev: QMouseEvent):
if self.acceptMouseEvents: # need pixmap instance
self.pointerEvent(*self._getRemoteRel(ev), self.buttonMask)
# FIXME: The pixmap is assumed to be aligned center.
def _getRemoteRel(self, ev: QMouseEvent) -> tuple:
# FIXME: this code is ugly as fk
# y coord is kinda fucked up
yDiff = (self.height() - self.pixmap().height()) / 2
yPos = ev.localPos().y() - yDiff
if yPos < 0: yPos = 0
if yPos > self.pixmap().height(): yPos = self.pixmap().height()
yPos = self._calcRemoteRel(
yPos, self.pixmap().height(), self.vncHeight)
# x coord is kinda fucked up, too
xDiff = (self.width() - self.pixmap().width()) / 2
xPos = ev.localPos().x() - xDiff
if xPos < 0: xPos = 0
if xPos > self.pixmap().width(): xPos = self.pixmap().width()
xPos = self._calcRemoteRel(
xPos, self.pixmap().width(), self.vncWidth)
return xPos, yPos
def _calcRemoteRel(self, locRel, locMax, remoteMax) -> int:
return int( (locRel / locMax) * remoteMax )
def __del__(self):
self.stop()
def __exit__(self, exc_type, exc_val, exc_tb):
self.stop()
self.deleteLater()