1
0
Fork 0
mirror of https://github.com/kbumsik/VirtScreen.git synced 2025-03-09 15:40:18 +00:00

QML: First Basic backend (Doesn't do anything for now)

This commit is contained in:
Bumsik Kim 2018-05-06 19:24:06 -04:00
parent f102e7d709
commit 73c8f5fb7c
No known key found for this signature in database
GPG key ID: E31041C8EC5B01C6
2 changed files with 126 additions and 616 deletions

View file

@ -1,8 +1,7 @@
import QtQuick 2.10 import QtQuick 2.10
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Controls.Material 2.3 // import QtQuick.Controls.Material 2.3
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import QtQuick.Window 2.2
import Qt.labs.platform 1.0 as Labs import Qt.labs.platform 1.0 as Labs
@ -14,27 +13,31 @@ ApplicationWindow {
visible: true visible: true
title: "Basic layouts" title: "Basic layouts"
Material.theme: Material.Light // Material.theme: Material.Light
Material.accent: Material.Teal // Material.accent: Material.Teal
property int margin: 11 property int margin: 11
width: 380 width: 380
height: 600 height: 600
Backend {
id: backend
}
// Timer object and function // Timer object and function
Timer { Timer {
id: timer id: timer
}
function setTimeout(cb, delayTime) { function setTimeout(cb, delayTime) {
timer.interval = delayTime; timer.interval = delayTime;
timer.repeat = false; timer.repeat = false;
timer.triggered.connect(cb); timer.triggered.connect(cb);
timer.start(); timer.start();
} }
}
header: TabBar { header: TabBar {
id: tabBar id: tabBar
position: TabBar.Header
width: parent.width width: parent.width
currentIndex: 0 currentIndex: 0
@ -69,7 +72,8 @@ ApplicationWindow {
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Label { text: "Width"; Layout.fillWidth: true } Label { text: "Width"; Layout.fillWidth: true }
SpinBox { value: 1368 SpinBox {
value: backend.width
from: 640 from: 640
to: 1920 to: 1920
stepSize: 1 stepSize: 1
@ -77,13 +81,17 @@ ApplicationWindow {
textFromValue: function(value, locale) { textFromValue: function(value, locale) {
return Number(value).toLocaleString(locale, 'f', 0) + " px"; return Number(value).toLocaleString(locale, 'f', 0) + " px";
} }
onValueModified: {
backend.width = value;
}
} }
} }
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Label { text: "Height"; Layout.fillWidth: true } Label { text: "Height"; Layout.fillWidth: true }
SpinBox { value: 1024 SpinBox {
value: backend.height
from: 360 from: 360
to: 1080 to: 1080
stepSize : 1 stepSize : 1
@ -91,19 +99,32 @@ ApplicationWindow {
textFromValue: function(value, locale) { textFromValue: function(value, locale) {
return Number(value).toLocaleString(locale, 'f', 0) + " px"; return Number(value).toLocaleString(locale, 'f', 0) + " px";
} }
onValueModified: {
backend.height = value;
}
} }
} }
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Label { text: "Portrait Mode"; Layout.fillWidth: true } Label { text: "Portrait Mode"; Layout.fillWidth: true }
Switch { checked: false } Switch {
checked: backend.portrait
onCheckedChanged: {
backend.portrait = checked;
}
}
} }
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Label { text: "HiDPI (2x resolution)"; Layout.fillWidth: true } Label { text: "HiDPI (2x resolution)"; Layout.fillWidth: true }
Switch { checked: false } Switch {
checked: backend.hidpi
onCheckedChanged: {
backend.hidpi = checked;
}
}
} }
} }
} }
@ -134,11 +155,14 @@ ApplicationWindow {
Layout.fillWidth: true Layout.fillWidth: true
Label { text: "Port"; Layout.fillWidth: true } Label { text: "Port"; Layout.fillWidth: true }
SpinBox { SpinBox {
value: 5900 value: backend.vncPort
from: 1 from: 1
to: 65535 to: 65535
stepSize: 1 stepSize: 1
editable: true editable: true
onValueModified: {
backend.vncPort = value;
}
} }
} }
@ -148,7 +172,11 @@ ApplicationWindow {
TextField { TextField {
Layout.fillWidth: true Layout.fillWidth: true
placeholderText: "Password"; placeholderText: "Password";
text: backend.vncPassword;
echoMode: TextInput.Password; echoMode: TextInput.Password;
onTextEdited: {
backend.vncPassword = text;
}
} }
} }
} }
@ -187,7 +215,7 @@ ApplicationWindow {
onMessageClicked: console.log("Message clicked") onMessageClicked: console.log("Message clicked")
Component.onCompleted: { Component.onCompleted: {
// without delay, the message appears in a wierd place // without delay, the message appears in a wierd place
setTimeout (function() { timer.setTimeout (function() {
showMessage("Message title", "Something important came up. Click this to know more."); showMessage("Message title", "Something important came up. Click this to know more.");
}, 1000); }, 1000);
} }

View file

@ -1,632 +1,114 @@
#!/usr/bin/env python #!/usr/bin/env python
import os, re, time import sys, os
from PyQt5.QtGui import QIcon, QCursor, QFocusEvent
from PyQt5.QtCore import pyqtSlot, Qt, QEvent
from PyQt5.QtWidgets import (QAction, QApplication, QCheckBox, QComboBox,
QDialog, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit,
QMessageBox, QMenu, QPushButton, QSpinBox, QStyle, QSystemTrayIcon,
QTextEdit, QVBoxLayout, QListWidget)
from twisted.internet import protocol, error
from netifaces import interfaces, ifaddresses, AF_INET
import subprocess
import atexit, signal
# Redirect stdout to /dev/null. Uncomment it while debugging. from PyQt5.QtWidgets import QApplication
# import sys from PyQt5.QtCore import QObject, QUrl, Qt
# sys.stdout = open(os.devnull, "a") from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
from PyQt5.QtGui import QIcon
from PyQt5.QtQml import qmlRegisterType, QQmlApplicationEngine
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
# file path definitions # file path definitions
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
HOME_PATH = os.getenv('HOME', None)
if HOME_PATH is not None:
HOME_PATH = HOME_PATH + "/.virtscreen"
X11VNC_LOG_PATH = HOME_PATH + "/x11vnc_log.txt"
X11VNC_PASSWORD_PATH = HOME_PATH + "/x11vnc_passwd"
CONFIG_PATH = HOME_PATH + "/config"
PROGRAM_PATH = "." PROGRAM_PATH = "."
ICON_PATH = PROGRAM_PATH + "/icon/icon.png" ICON_PATH = PROGRAM_PATH + "/icon/icon.png"
ICON_TABLET_OFF_PATH = PROGRAM_PATH + "/icon/icon_tablet_off.png" ICON_TABLET_OFF_PATH = PROGRAM_PATH + "/icon/icon_tablet_off.png"
ICON_TABLET_ON_PATH = PROGRAM_PATH + "/icon/icon_tablet_on.png" ICON_TABLET_ON_PATH = PROGRAM_PATH + "/icon/icon_tablet_on.png"
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
# Subprocess wrapper # QML Backend class
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
class SubprocessWrapper: class Backend(QObject):
def __init__(self, stdout:str=os.devnull, stderr:str=os.devnull): width_changed = pyqtSignal(int)
self.stdout: str = stdout virtScreenChanged = pyqtSignal(bool)
self.stderr: str = stderr vncChanged = pyqtSignal(bool)
def call(self, arg) -> None: def __init__(self, parent=None):
with open(os.devnull, "w") as f: super(Backend, self).__init__(parent)
subprocess.call(arg.split(), stdout=f, stderr=f) # Virtual screen properties
self._width = 1368
self._height = 1024
self._portrait = True
self._hidpi = False
self._virtScreenCreated = False
# VNC server properties
self._vncPort = 5900
self._vncPassword = ""
self._vncState = False
def check_call(self, arg) -> None: @pyqtProperty(int, notify=width_changed)
with open(os.devnull, "w") as f: def width(self):
subprocess.check_call(arg.split(), stdout=f, stderr=f) return self._width
@width.setter
def width(self, width):
self._width = width
self.width_changed.emit(self._width)
def run(self, arg: str) -> str: @pyqtProperty(int)
return subprocess.run(arg.split(), stdout=subprocess.PIPE).stdout.decode('utf-8') def height(self):
return self._height
@height.setter
def height(self, height):
self._height = height
#------------------------------------------------------------------------------- @pyqtProperty(bool)
# Display properties def portrait(self):
#------------------------------------------------------------------------------- return self._portrait
class DisplayProperty: @portrait.setter
def __init__(self): def portrait(self, portrait):
self.name: str self._portrait = portrait
self.width: int
self.height: int
self.x_offset: int
self.y_offset: int
#------------------------------------------------------------------------------- @pyqtProperty(bool)
# Screen adjustment class def hidpi(self):
#------------------------------------------------------------------------------- return self._hidpi
class XRandR(SubprocessWrapper): @hidpi.setter
def __init__(self): def hidpi(self, hidpi):
super(XRandR, self).__init__() self._hidpi = hidpi
self.mode_name: str
self.scrren_suffix = "_virt"
# Thoese will be created in set_virtual_screen()
self.virt = DisplayProperty()
self.virt.name = "VIRTUAL1"
# Primary display
self.primary = DisplayProperty()
self._update_primary_screen()
def _add_screen_mode(self) -> None: @pyqtProperty(bool)
args_addmode = f"xrandr --addmode {self.virt.name} {self.mode_name}" def virtScreenCreated(self):
try: return self._virtScreenCreated
self.check_call(args_addmode)
except subprocess.CalledProcessError:
# When failed create mode and then add again
output = self.run(f"cvt {self.virt.width} {self.virt.height}")
mode = re.search(r"^.*Modeline\s*\".*\"\s*(.*)$", output, re.M).group(1)
# Create new screen mode
self.check_call(f"xrandr --newmode {self.mode_name} {mode}")
# Add mode again
self.check_call(args_addmode)
# After adding mode the program should delete the mode automatically on exit
atexit.register(self.delete_virtual_screen)
for sig in [signal.SIGTERM, signal.SIGHUP, signal.SIGQUIT]:
signal.signal(sig, self._signal_handler)
def _update_primary_screen(self) -> None: @pyqtProperty(int)
output = self.run("xrandr") def vncPort(self):
match = re.search(r"^(\w*)\s+.*primary\s*(\d+)x(\d+)\+(\d+)\+(\d+).*$", output, re.M) return self._vncPort
self.primary.name = match.group(1) @vncPort.setter
self.primary.width = int(match.group(2)) def vncPort(self, vncPort):
self.primary.height = int(match.group(3)) self._vncPort = vncPort
self.primary.x_offset = int(match.group(4))
self.primary.y_offset = int(match.group(5))
def _update_virtual_screen(self) -> None: @pyqtProperty(str)
output = self.run("xrandr") def vncPassword(self):
match = re.search(r"^" + self.virt.name + r"\s+.*\s+(\d+)x(\d+)\+(\d+)\+(\d+).*$", output, re.M) return self._vncPassword
self.virt.width = int(match.group(1)) @vncPassword.setter
self.virt.height = int(match.group(2)) def vncPassword(self, vncPassword):
self.virt.x_offset = int(match.group(3)) self._vncPassword = vncPassword
self.virt.y_offset = int(match.group(4)) print(self._vncPassword)
def _signal_handler(self, signum=None, frame=None) -> None:
self.delete_virtual_screen()
os._exit(0)
def get_virtual_screen(self) -> DisplayProperty:
self._update_virtual_screen()
return self.virt
def set_virtual_screen(self, width, height, portrait=False, hidpi=False):
self.virt.width = width
self.virt.height = height
if portrait:
self.virt.width = height
self.virt.height = width
if hidpi:
self.virt.width = 2 * self.virt.width
self.virt.height = 2 * self.virt.height
self.mode_name = str(self.virt.width) + "x" + str(self.virt.height) + self.scrren_suffix
def create_virtual_screen(self) -> None:
self._add_screen_mode()
self.check_call(f"xrandr --output {self.virt.name} --mode {self.mode_name}")
self.check_call("sleep 5")
self.check_call(f"xrandr --output {self.virt.name} --auto")
self._update_primary_screen()
self._update_virtual_screen()
def delete_virtual_screen(self) -> None:
try:
self.virt.name
self.mode_name
except AttributeError:
return
self.call(f"xrandr --output {self.virt.name} --off")
self.call(f"xrandr --delmode {self.virt.name} {self.mode_name}")
#-------------------------------------------------------------------------------
# Twisted class
#-------------------------------------------------------------------------------
class ProcessProtocol(protocol.ProcessProtocol):
def __init__(self, onOutReceived, onErrRecevied, onProcessEnded, logfile=None):
self.onOutReceived = onOutReceived
self.onErrRecevied = onErrRecevied
self.onProcessEnded = onProcessEnded
self.logfile = logfile
def run(self, arg: str):
"""Spawn a process
Arguments:
arg {str} -- arguments in string
"""
args = arg.split()
reactor.spawnProcess(self, args[0], args=args, env=os.environ)
def kill(self):
"""Kill a spawned process
"""
self.transport.signalProcess('INT')
def connectionMade(self):
print("connectionMade!")
self.transport.closeStdin() # No more input
def outReceived(self, data):
print("outReceived! with %d bytes!" % len(data))
self.onOutReceived(data)
if self.logfile is not None:
self.logfile.write(data)
def errReceived(self, data):
print("outReceived! with %d bytes!" % len(data))
self.onErrRecevied(data)
if self.logfile is not None:
self.logfile.write(data)
def inConnectionLost(self):
print("inConnectionLost! stdin is closed! (we probably did it)")
pass
def outConnectionLost(self):
print("outConnectionLost! The child closed their stdout!")
pass
def errConnectionLost(self):
print("errConnectionLost! The child closed their stderr.")
pass
def processExited(self, reason):
exitCode = reason.value.exitCode
if exitCode is None:
print("Unknown exit")
return
print("processEnded, status", exitCode)
def processEnded(self, reason):
if self.logfile is not None:
self.logfile.close()
exitCode = reason.value.exitCode
if exitCode is None:
print("Unknown exit")
self.onProcessEnded(1)
return
print("processEnded, status", exitCode)
print("quitting")
self.onProcessEnded(exitCode)
#-------------------------------------------------------------------------------
# Qt Window class
#-------------------------------------------------------------------------------
class Window(QDialog):
def __init__(self):
super(Window, self).__init__()
# Create objects
self.createDisplayGroupBox()
self.createVNCGroupBox()
self.createBottomLayout()
self.createActions()
self.createTrayIcon()
self.xrandr = XRandR()
# Additional attributes
self.isDisplayCreated = False
self.isVNCRunning = False
self.isQuitProgramPending = False
# Update UI
self.update_ip_address()
# Put togather
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.displayGroupBox)
mainLayout.addWidget(self.VNCGroupBox)
mainLayout.addLayout(self.bottomLayout)
self.setLayout(mainLayout)
# Events
self.trayIcon.activated.connect(self.iconActivated)
self.createDisplayButton.pressed.connect(self.createDisplayPressed)
self.startVNCButton.pressed.connect(self.startVNCPressed)
QApplication.desktop().resized.connect(self.screenChanged)
# QApplication.desktop().resized.connect(self.startVNCPressed)
# QApplication.desktop().screenCountChanged.connect(self.startVNCPressed)
self.bottomQuitButton.pressed.connect(self.quitProgram)
# Show
self.setWindowIcon(self.icon)
self.trayIcon.show()
self.trayIcon.setToolTip("VirtScreen")
self.setWindowTitle("VirtScreen")
self.resize(400, 300)
def setVisible(self, visible):
"""Override of setVisible(bool)
Arguments:
visible {bool} -- true to show, false to hide
"""
self.openAction.setEnabled(self.isMaximized() or not visible)
super(Window, self).setVisible(visible)
def changeEvent(self, event):
"""Override of QWidget::changeEvent()
Arguments:
event {QEvent} -- QEvent
"""
if event.type() == QEvent.ActivationChange and not self.isActiveWindow():
self.hide()
def closeEvent(self, event):
"""Override of closeEvent()
Arguments:
event {QCloseEvent} -- QCloseEvent
"""
if self.trayIcon.isVisible():
self.hide()
self.showMessage()
event.ignore()
else:
QApplication.instance().quit()
@pyqtSlot()
def createDisplayPressed(self):
if not self.isDisplayCreated:
# Create virtual screen
self.createDisplayButton.setEnabled(False)
width = self.displayWidthSpinBox.value()
height = self.displayHeightSpinBox.value()
portrait = self.displayPortraitCheckBox.isChecked()
hidpi = self.displayHIDPICheckBox.isChecked()
self.xrandr.set_virtual_screen(width, height, portrait, hidpi)
self.xrandr.create_virtual_screen()
self.createDisplayButton.setText("Disable the virtual display")
self.isDisplayCreated = True
self.createDisplayButton.setEnabled(True)
self.startVNCButton.setEnabled(True)
self.trayIcon.setIcon(self.icon_tablet_off)
else:
# Delete the screen
self.createDisplayButton.setEnabled(False)
self.xrandr.delete_virtual_screen()
self.isDisplayCreated = False
self.createDisplayButton.setText("Create a Virtual Display")
self.createDisplayButton.setEnabled(True)
self.startVNCButton.setEnabled(False)
self.trayIcon.setIcon(self.icon)
self.createDisplayAction.setEnabled(not self.isDisplayCreated)
self.deleteDisplayAction.setEnabled(self.isDisplayCreated)
self.startVNCAction.setEnabled(self.isDisplayCreated)
self.stopVNCAction.setEnabled(False)
@pyqtSlot()
def startVNCPressed(self):
if not self.isVNCRunning:
self.startVNC()
else:
self.VNCServer.kill()
@pyqtSlot('QSystemTrayIcon::ActivationReason')
def iconActivated(self, reason):
if reason in (QSystemTrayIcon.Trigger, QSystemTrayIcon.DoubleClick):
if self.isVisible():
self.hide()
else:
# move the widget to one of 4 coners of the primary display,
# depending on the current mouse cursor.
screen = QApplication.desktop().screenGeometry()
x_mid = screen.width() / 2
y_mid = screen.height() / 2
cursor = QCursor().pos()
x = (screen.width() - self.width()) if (cursor.x() > x_mid) else 0
y = (screen.height() - self.height()) if (cursor.y() > y_mid) else 0
self.move(x, y)
self.showNormal()
elif reason == QSystemTrayIcon.MiddleClick:
self.showMessage()
@pyqtSlot(int)
def screenChanged(self, count):
for i in range(QApplication.desktop().screenCount()):
print(QApplication.desktop().availableGeometry(i))
@pyqtSlot()
def showMessage(self):
self.trayIcon.showMessage("VirtScreen is running",
"The program will keep running in the system tray. To \n"
"terminate the program, choose \"Quit\" in the \n"
"context menu of the system tray entry.",
QSystemTrayIcon.MessageIcon(QSystemTrayIcon.Information),
7 * 1000)
# Qt Slots
@pyqtSlot() @pyqtSlot()
def quitProgram(self): def quitProgram(self):
self.isQuitProgramPending = True
try:
# Rest of quit sequence will be handled in the callback.
self.VNCServer.kill()
except (AttributeError, error.ProcessExitedAlready):
self.xrandr.delete_virtual_screen()
QApplication.instance().quit() QApplication.instance().quit()
def createDisplayGroupBox(self):
self.displayGroupBox = QGroupBox("Virtual Display Settings")
# Resolution Row
resolutionLabel = QLabel("Resolution:")
self.displayWidthSpinBox = QSpinBox()
self.displayWidthSpinBox.setRange(640, 1920)
self.displayWidthSpinBox.setSuffix("px")
self.displayWidthSpinBox.setValue(1368)
xLabel = QLabel("x")
self.displayHeightSpinBox = QSpinBox()
self.displayHeightSpinBox.setRange(360, 1080)
self.displayHeightSpinBox.setSuffix("px")
self.displayHeightSpinBox.setValue(1024)
# Portrait and HiDPI
self.displayPortraitCheckBox = QCheckBox("Portrait Mode")
self.displayPortraitCheckBox.setChecked(False)
self.displayHIDPICheckBox = QCheckBox("HiDPI (2x resolution)")
self.displayHIDPICheckBox.setChecked(False)
# Start button
self.createDisplayButton = QPushButton("Create a Virtual Display")
self.createDisplayButton.setDefault(True)
# Notice Label
self.displayNoticeLabel = QLabel("After creating, you can adjust the display's " +
"position in the Desktop Environment's settings " +
"or ARandR.")
self.displayNoticeLabel.setWordWrap(True)
font = self.displayNoticeLabel.font()
font.setPointSize(9)
self.displayNoticeLabel.setFont(font)
# Putting them together
layout = QVBoxLayout()
# Grid layout for screen settings
gridLayout = QGridLayout()
# Resolution row
rowLayout = QHBoxLayout()
rowLayout.addWidget(resolutionLabel)
rowLayout.addWidget(self.displayWidthSpinBox)
rowLayout.addWidget(xLabel)
rowLayout.addWidget(self.displayHeightSpinBox)
rowLayout.addStretch()
layout.addLayout(rowLayout)
# Portrait & HiDPI
rowLayout = QHBoxLayout()
rowLayout.addWidget(self.displayPortraitCheckBox)
rowLayout.addWidget(self.displayHIDPICheckBox)
rowLayout.addStretch()
layout.addLayout(rowLayout)
# Display create button and Notice label
layout.addWidget(self.createDisplayButton)
layout.addWidget(self.displayNoticeLabel)
self.displayGroupBox.setLayout(layout)
def createVNCGroupBox(self):
self.VNCGroupBox = QGroupBox("VNC Server")
portLabel = QLabel("Port:")
self.VNCPortSpinBox = QSpinBox()
self.VNCPortSpinBox.setRange(1, 65535)
self.VNCPortSpinBox.setValue(5900)
passwordLabel = QLabel("Password:")
self.VNCPasswordLineEdit = QLineEdit()
self.VNCPasswordLineEdit.setEchoMode(QLineEdit.Password)
self.VNCPasswordLineEdit.setText("")
IPLabel = QLabel("Connect a VNC client to one of:")
self.VNCIPListWidget = QListWidget()
self.startVNCButton = QPushButton("Start VNC Server")
self.startVNCButton.setDefault(False)
self.startVNCButton.setEnabled(False)
# Set Overall layout
layout = QVBoxLayout()
rowLayout = QHBoxLayout()
rowLayout.addWidget(portLabel)
rowLayout.addWidget(self.VNCPortSpinBox)
rowLayout.addWidget(passwordLabel)
rowLayout.addWidget(self.VNCPasswordLineEdit)
layout.addLayout(rowLayout)
layout.addWidget(self.startVNCButton)
layout.addWidget(IPLabel)
layout.addWidget(self.VNCIPListWidget)
self.VNCGroupBox.setLayout(layout)
def createBottomLayout(self):
self.bottomLayout = QVBoxLayout()
# Create button
self.bottomQuitButton = QPushButton("Quit")
self.bottomQuitButton.setDefault(False)
self.bottomQuitButton.setEnabled(True)
# Set Overall layout
hLayout = QHBoxLayout()
hLayout.addStretch()
hLayout.addWidget(self.bottomQuitButton)
self.bottomLayout.addLayout(hLayout)
def createActions(self):
self.createDisplayAction = QAction("Create display", self)
self.createDisplayAction.triggered.connect(self.createDisplayPressed)
self.createDisplayAction.setEnabled(True)
self.deleteDisplayAction = QAction("Disable display", self)
self.deleteDisplayAction.triggered.connect(self.createDisplayPressed)
self.deleteDisplayAction.setEnabled(False)
self.startVNCAction = QAction("&Start sharing", self)
self.startVNCAction.triggered.connect(self.startVNCPressed)
self.startVNCAction.setEnabled(False)
self.stopVNCAction = QAction("S&top sharing", self)
self.stopVNCAction.triggered.connect(self.startVNCPressed)
self.stopVNCAction.setEnabled(False)
self.openAction = QAction("&Open VirtScreen", self)
self.openAction.triggered.connect(self.showNormal)
self.quitAction = QAction("&Quit", self)
self.quitAction.triggered.connect(self.quitProgram)
def createTrayIcon(self):
# Menu
self.trayIconMenu = QMenu(self)
self.trayIconMenu.addAction(self.createDisplayAction)
self.trayIconMenu.addAction(self.deleteDisplayAction)
self.trayIconMenu.addSeparator()
self.trayIconMenu.addAction(self.startVNCAction)
self.trayIconMenu.addAction(self.stopVNCAction)
self.trayIconMenu.addSeparator()
self.trayIconMenu.addAction(self.openAction)
self.trayIconMenu.addSeparator()
self.trayIconMenu.addAction(self.quitAction)
# Icons
self.icon = QIcon(ICON_PATH)
self.icon_tablet_off = QIcon(ICON_TABLET_OFF_PATH)
self.icon_tablet_on = QIcon(ICON_TABLET_ON_PATH)
self.trayIcon = QSystemTrayIcon(self)
self.trayIcon.setContextMenu(self.trayIconMenu)
self.trayIcon.setIcon(self.icon)
def update_ip_address(self):
self.VNCIPListWidget.clear()
for interface in interfaces():
if interface == 'lo':
continue
addresses = ifaddresses(interface).get(AF_INET, None)
if addresses is None:
continue
for link in addresses:
if link is not None:
self.VNCIPListWidget.addItem(link['addr'])
def startVNC(self):
def _onReceived(data):
data = data.decode("utf-8")
for line in data.splitlines():
# TODO: Update state of the server
pass
def _onEnded(exitCode):
self.startVNCButton.setEnabled(False)
self.isVNCRunning = False
if self.isQuitProgramPending:
self.xrandr.delete_virtual_screen()
QApplication.instance().quit()
self.startVNCButton.setText("Start VNC Server")
self.startVNCButton.setEnabled(True)
self.createDisplayButton.setEnabled(True)
self.deleteDisplayAction.setEnabled(True)
self.startVNCAction.setEnabled(True)
self.stopVNCAction.setEnabled(False)
self.trayIcon.setIcon(self.icon_tablet_off)
# Setting UI before starting
self.createDisplayButton.setEnabled(False)
self.createDisplayAction.setEnabled(False)
self.deleteDisplayAction.setEnabled(False)
self.startVNCButton.setEnabled(False)
self.startVNCButton.setText("Running...")
self.startVNCAction.setEnabled(False)
# Set password
isPassword = False
if self.VNCPasswordLineEdit.text():
isPassword = True
p = SubprocessWrapper()
try:
p.run(f"x11vnc -storepasswd {self.VNCPasswordLineEdit.text()} {X11VNC_PASSWORD_PATH}")
except:
isPassword = False
# Run VNC server
self.isVNCRunning = True
logfile = open(X11VNC_LOG_PATH, "wb")
self.VNCServer = ProcessProtocol(_onReceived, _onReceived, _onEnded, logfile)
port = self.VNCPortSpinBox.value()
virt = self.xrandr.get_virtual_screen()
clip = f"{virt.width}x{virt.height}+{virt.x_offset}+{virt.y_offset}"
arg = f"x11vnc -multiptr -repeat -rfbport {port} -clip {clip}"
if isPassword:
arg += f" -rfbauth {X11VNC_PASSWORD_PATH}"
self.VNCServer.run(arg)
self.update_ip_address()
# Change UI
self.startVNCButton.setEnabled(True)
self.startVNCButton.setText("Stop Sharing")
self.stopVNCAction.setEnabled(True)
self.trayIcon.setIcon(self.icon_tablet_on)
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
# Main Code # Main Code
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
if __name__ == '__main__': if __name__ == '__main__':
import sys import sys
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
app = QApplication(sys.argv) app = QApplication(sys.argv)
app.setWindowIcon(QIcon(ICON_PATH))
# os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
os.environ["QT_QUICK_CONTROLS_STYLE"] = "Fusion"
if not QSystemTrayIcon.isSystemTrayAvailable(): # Register the Python type. Its URI is 'People', it's v1.0 and the type
QMessageBox.critical(None, "VirtScreen", # will be called 'Person' in QML.
"I couldn't detect any system tray on this system.") qmlRegisterType(Backend, 'VirtScreen.Backend', 1, 0, 'Backend')
sys.exit(1)
if os.environ['XDG_SESSION_TYPE'] == 'wayland': # Create a component factory and load the QML script.
QMessageBox.critical(None, "VirtScreen", engine = QQmlApplicationEngine()
"Currently Wayland is not supported") engine.load(QUrl('main.qml'))
sys.exit(1) if not engine.rootObjects():
if HOME_PATH is None: print("Failed to load qml")
QMessageBox.critical(None, "VirtScreen", exit(1)
"VirtScreen cannot detect $HOME")
sys.exit(1)
if not os.path.exists(HOME_PATH):
try:
os.makedirs(HOME_PATH)
except:
QMessageBox.critical(None, "VirtScreen",
"VirtScreen cannot create ~/.virtscreen")
sys.exit(1)
import qt5reactor # pylint: disable=E0401
qt5reactor.install()
from twisted.internet import utils, reactor # pylint: disable=E0401
QApplication.setQuitOnLastWindowClosed(False)
window = Window()
window.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
time.sleep(2) # Otherwise the trayicon message will be shown in weird position
window.showMessage()
sys.exit(app.exec_()) sys.exit(app.exec_())
reactor.run()