1
0
Fork 0
mirror of https://github.com/kbumsik/VirtScreen.git synced 2025-02-12 11:21:53 +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.Controls 2.3
import QtQuick.Controls.Material 2.3
// import QtQuick.Controls.Material 2.3
import QtQuick.Layouts 1.3
import QtQuick.Window 2.2
import Qt.labs.platform 1.0 as Labs
@ -14,27 +13,31 @@ ApplicationWindow {
visible: true
title: "Basic layouts"
Material.theme: Material.Light
Material.accent: Material.Teal
// Material.theme: Material.Light
// Material.accent: Material.Teal
property int margin: 11
width: 380
height: 600
Backend {
id: backend
}
// Timer object and function
Timer {
id: timer
}
function setTimeout(cb, delayTime) {
timer.interval = delayTime;
timer.repeat = false;
timer.triggered.connect(cb);
timer.start();
function setTimeout(cb, delayTime) {
timer.interval = delayTime;
timer.repeat = false;
timer.triggered.connect(cb);
timer.start();
}
}
header: TabBar {
id: tabBar
position: TabBar.Header
width: parent.width
currentIndex: 0
@ -69,7 +72,8 @@ ApplicationWindow {
RowLayout {
Layout.fillWidth: true
Label { text: "Width"; Layout.fillWidth: true }
SpinBox { value: 1368
SpinBox {
value: backend.width
from: 640
to: 1920
stepSize: 1
@ -77,13 +81,17 @@ ApplicationWindow {
textFromValue: function(value, locale) {
return Number(value).toLocaleString(locale, 'f', 0) + " px";
}
onValueModified: {
backend.width = value;
}
}
}
RowLayout {
Layout.fillWidth: true
Label { text: "Height"; Layout.fillWidth: true }
SpinBox { value: 1024
SpinBox {
value: backend.height
from: 360
to: 1080
stepSize : 1
@ -91,19 +99,32 @@ ApplicationWindow {
textFromValue: function(value, locale) {
return Number(value).toLocaleString(locale, 'f', 0) + " px";
}
onValueModified: {
backend.height = value;
}
}
}
RowLayout {
Layout.fillWidth: true
Label { text: "Portrait Mode"; Layout.fillWidth: true }
Switch { checked: false }
Switch {
checked: backend.portrait
onCheckedChanged: {
backend.portrait = checked;
}
}
}
RowLayout {
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
Label { text: "Port"; Layout.fillWidth: true }
SpinBox {
value: 5900
value: backend.vncPort
from: 1
to: 65535
stepSize: 1
editable: true
onValueModified: {
backend.vncPort = value;
}
}
}
@ -148,7 +172,11 @@ ApplicationWindow {
TextField {
Layout.fillWidth: true
placeholderText: "Password";
text: backend.vncPassword;
echoMode: TextInput.Password;
onTextEdited: {
backend.vncPassword = text;
}
}
}
}
@ -187,7 +215,7 @@ ApplicationWindow {
onMessageClicked: console.log("Message clicked")
Component.onCompleted: {
// 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.");
}, 1000);
}

View file

@ -1,632 +1,114 @@
#!/usr/bin/env python
import os, re, time
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
import sys, os
# Redirect stdout to /dev/null. Uncomment it while debugging.
# import sys
# sys.stdout = open(os.devnull, "a")
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QObject, QUrl, Qt
from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
from PyQt5.QtGui import QIcon
from PyQt5.QtQml import qmlRegisterType, QQmlApplicationEngine
#-------------------------------------------------------------------------------
# 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 = "."
ICON_PATH = PROGRAM_PATH + "/icon/icon.png"
ICON_TABLET_OFF_PATH = PROGRAM_PATH + "/icon/icon_tablet_off.png"
ICON_TABLET_ON_PATH = PROGRAM_PATH + "/icon/icon_tablet_on.png"
#-------------------------------------------------------------------------------
# Subprocess wrapper
# QML Backend class
#-------------------------------------------------------------------------------
class SubprocessWrapper:
def __init__(self, stdout:str=os.devnull, stderr:str=os.devnull):
self.stdout: str = stdout
self.stderr: str = stderr
class Backend(QObject):
width_changed = pyqtSignal(int)
virtScreenChanged = pyqtSignal(bool)
vncChanged = pyqtSignal(bool)
def __init__(self, parent=None):
super(Backend, self).__init__(parent)
# 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 call(self, arg) -> None:
with open(os.devnull, "w") as f:
subprocess.call(arg.split(), stdout=f, stderr=f)
def check_call(self, arg) -> None:
with open(os.devnull, "w") as f:
subprocess.check_call(arg.split(), stdout=f, stderr=f)
@pyqtProperty(int, notify=width_changed)
def width(self):
return self._width
@width.setter
def width(self, width):
self._width = width
self.width_changed.emit(self._width)
def run(self, arg: str) -> str:
return subprocess.run(arg.split(), stdout=subprocess.PIPE).stdout.decode('utf-8')
@pyqtProperty(int)
def height(self):
return self._height
@height.setter
def height(self, height):
self._height = height
#-------------------------------------------------------------------------------
# Display properties
#-------------------------------------------------------------------------------
class DisplayProperty:
def __init__(self):
self.name: str
self.width: int
self.height: int
self.x_offset: int
self.y_offset: int
@pyqtProperty(bool)
def portrait(self):
return self._portrait
@portrait.setter
def portrait(self, portrait):
self._portrait = portrait
#-------------------------------------------------------------------------------
# Screen adjustment class
#-------------------------------------------------------------------------------
class XRandR(SubprocessWrapper):
def __init__(self):
super(XRandR, self).__init__()
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()
@pyqtProperty(bool)
def hidpi(self):
return self._hidpi
@hidpi.setter
def hidpi(self, hidpi):
self._hidpi = hidpi
def _add_screen_mode(self) -> None:
args_addmode = f"xrandr --addmode {self.virt.name} {self.mode_name}"
try:
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:
output = self.run("xrandr")
match = re.search(r"^(\w*)\s+.*primary\s*(\d+)x(\d+)\+(\d+)\+(\d+).*$", output, re.M)
self.primary.name = match.group(1)
self.primary.width = int(match.group(2))
self.primary.height = int(match.group(3))
self.primary.x_offset = int(match.group(4))
self.primary.y_offset = int(match.group(5))
def _update_virtual_screen(self) -> None:
output = self.run("xrandr")
match = re.search(r"^" + self.virt.name + r"\s+.*\s+(\d+)x(\d+)\+(\d+)\+(\d+).*$", output, re.M)
self.virt.width = int(match.group(1))
self.virt.height = int(match.group(2))
self.virt.x_offset = int(match.group(3))
self.virt.y_offset = int(match.group(4))
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
@pyqtProperty(bool)
def virtScreenCreated(self):
return self._virtScreenCreated
Arguments:
arg {str} -- arguments in string
"""
@pyqtProperty(int)
def vncPort(self):
return self._vncPort
@vncPort.setter
def vncPort(self, vncPort):
self._vncPort = vncPort
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)
@pyqtProperty(str)
def vncPassword(self):
return self._vncPassword
@vncPassword.setter
def vncPassword(self, vncPassword):
self._vncPassword = vncPassword
print(self._vncPassword)
# Qt Slots
@pyqtSlot()
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()
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)
QApplication.instance().quit()
#-------------------------------------------------------------------------------
# Main Code
#-------------------------------------------------------------------------------
if __name__ == '__main__':
import sys
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
app = QApplication(sys.argv)
app.setWindowIcon(QIcon(ICON_PATH))
# os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
os.environ["QT_QUICK_CONTROLS_STYLE"] = "Fusion"
# Register the Python type. Its URI is 'People', it's v1.0 and the type
# will be called 'Person' in QML.
qmlRegisterType(Backend, 'VirtScreen.Backend', 1, 0, 'Backend')
if not QSystemTrayIcon.isSystemTrayAvailable():
QMessageBox.critical(None, "VirtScreen",
"I couldn't detect any system tray on this system.")
sys.exit(1)
if os.environ['XDG_SESSION_TYPE'] == 'wayland':
QMessageBox.critical(None, "VirtScreen",
"Currently Wayland is not supported")
sys.exit(1)
if HOME_PATH is None:
QMessageBox.critical(None, "VirtScreen",
"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()
# Create a component factory and load the QML script.
engine = QQmlApplicationEngine()
engine.load(QUrl('main.qml'))
if not engine.rootObjects():
print("Failed to load qml")
exit(1)
sys.exit(app.exec_())
reactor.run()