diff --git a/virtscreen/assets/AppWindow.qml b/virtscreen/assets/AppWindow.qml index 2bf9e6a..f013b64 100644 --- a/virtscreen/assets/AppWindow.qml +++ b/virtscreen/assets/AppWindow.qml @@ -277,6 +277,17 @@ ApplicationWindow { } } + Loader { + id: displayOptionsLoader + active: false + source: "DisplayOptionsDialog.qml" + onLoaded: { + item.onClosed.connect(function() { + displayOptionsLoader.active = false; + }); + } + } + Loader { id: vncOptionsLoader active: false diff --git a/virtscreen/assets/DisplayOptionsDialog.qml b/virtscreen/assets/DisplayOptionsDialog.qml new file mode 100644 index 0000000..e242ee4 --- /dev/null +++ b/virtscreen/assets/DisplayOptionsDialog.qml @@ -0,0 +1,70 @@ +import QtQuick 2.10 +import QtQuick.Controls 2.3 +import QtQuick.Controls.Material 2.3 +import QtQuick.Layouts 1.3 + +Dialog { + title: "Display Options" + focus: true + modal: true + visible: true + standardButtons: Dialog.Ok + x: (window.width - width) / 2 + y: (window.width - height) / 2 + width: popupWidth + height: 250 + + ColumnLayout { + anchors.fill: parent + + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + Label { id: deviceLabel; text: "Device"; } + ComboBox { + id: deviceComboBox + anchors.left: deviceLabel.right + anchors.right: parent.right + anchors.leftMargin: 100 + textRole: "name" + model: backend.screens + currentIndex: { + if (settings.virt.device) { + for (var i = 0; i < model.length; i++) { + if (model[i].name == settings.virt.device) { + return i; + } + } + } + settings.virt.device = ''; + return -1; + } + onActivated: function(index) { + settings.virt.device = model[index].name; + } + delegate: ItemDelegate { + width: deviceComboBox.width + text: modelData.name + font.weight: deviceComboBox.currentIndex === index ? Font.Bold : Font.Normal + enabled: modelData.connected ? false : true + } + } + } + + Text { + font { pointSize: 10 } + wrapMode: Text.WordWrap + text: "Warning: Edit only if 'VIRTUAL1' is not available
" + + "If so, please note that the virtual screen may be
" + + "unstable/unavailable depending on a graphic
" + + "card and its driver." + } + + RowLayout { + // Empty layout + Layout.fillHeight: true + } + } + onAccepted: {} + onRejected: {} +} diff --git a/virtscreen/assets/DisplayPage.qml b/virtscreen/assets/DisplayPage.qml index b71ac0b..393a00e 100644 --- a/virtscreen/assets/DisplayPage.qml +++ b/virtscreen/assets/DisplayPage.qml @@ -59,26 +59,14 @@ ColumnLayout { } } RowLayout { - anchors.left: parent.left - anchors.right: parent.right - Label { id: deviceLabel; text: "Device"; } - ComboBox { - id: deviceComboBox - anchors.left: deviceLabel.right - anchors.right: parent.right - anchors.leftMargin: 100 - textRole: "name" - model: backend.screens - currentIndex: backend.virtScreenIndex - onActivated: function(index) { - backend.virtScreenIndex = index - } - delegate: ItemDelegate { - width: deviceComboBox.width - text: modelData.name - font.weight: deviceComboBox.currentIndex === index ? Font.Bold : Font.Normal - enabled: modelData.connected ? false : true - } + Layout.alignment: Qt.AlignRight + Button { + text: "Advanced" + font.capitalization: Font.MixedCase + onClicked: displayOptionsLoader.active = true; + background.opacity : 0 + onHoveredChanged: hovered ? background.opacity = 0.4 + :background.opacity = 0; } } } diff --git a/virtscreen/assets/VncOptionsDialog.qml b/virtscreen/assets/VncOptionsDialog.qml index da1a036..e047a36 100644 --- a/virtscreen/assets/VncOptionsDialog.qml +++ b/virtscreen/assets/VncOptionsDialog.qml @@ -4,7 +4,6 @@ import QtQuick.Controls.Material 2.3 import QtQuick.Layouts 1.3 Dialog { - id: preferenceDialog title: "VNC Options" focus: true modal: true diff --git a/virtscreen/assets/VncPage.qml b/virtscreen/assets/VncPage.qml index 0d4b627..167f510 100644 --- a/virtscreen/assets/VncPage.qml +++ b/virtscreen/assets/VncPage.qml @@ -84,7 +84,7 @@ ColumnLayout { GroupBox { title: "Available IP addresses" Layout.fillWidth: true - implicitHeight: 150 + implicitHeight: 145 ColumnLayout { anchors.fill: parent ListView { diff --git a/virtscreen/assets/main.qml b/virtscreen/assets/main.qml index 039966d..077f315 100644 --- a/virtscreen/assets/main.qml +++ b/virtscreen/assets/main.qml @@ -10,6 +10,12 @@ Item { property var settings: JSON.parse(backend.settings) property bool autostart: settings.vnc.autostart + function createVirtScreen () { + backend.createVirtScreen(settings.virt.device, settings.virt.width, + settings.virt.height, settings.virt.portrait, + settings.virt.hidpi); + } + function startVNC () { var options = ''; var data = settings.x11vncOptions; @@ -185,8 +191,7 @@ Item { // Give a very short delay to show busyDialog. timer.setTimeout (function() { if (!backend.virtScreenCreated) { - backend.createVirtScreen(settings.virt.width, settings.virt.height, - settings.virt.portrait, settings.virt.hidpi); + createVirtScreen(); } else { // If auto start enabled, stop VNC first then if (autostart && (backend.vncState != Backend.OFF)) { diff --git a/virtscreen/virtscreen.py b/virtscreen/virtscreen.py index 7c21242..c6457c0 100755 --- a/virtscreen/virtscreen.py +++ b/virtscreen/virtscreen.py @@ -257,7 +257,6 @@ class DisplayProperty(QObject): # Screen adjustment class # ------------------------------------------------------------------------------- class XRandR(SubprocessWrapper): - DEFAULT_VIRT_SCREEN = "VIRTUAL1" VIRT_SCREEN_SUFFIX = "_virt" def __init__(self): @@ -266,6 +265,7 @@ class XRandR(SubprocessWrapper): self.screens: List[Display] = [] self.virt: Display() = None self.primary: Display() = None + self.virt_name: str = '' self.virt_idx: int = None self.primary_idx: int = None # Primary display @@ -276,13 +276,14 @@ class XRandR(SubprocessWrapper): self.primary = None self.virt = None self.screens = [] + self.virt_idx = None self.primary_idx = None pattern = re.compile(r"^(\S*)\s+(connected|disconnected)\s+((primary)\s+)?" r"((\d+)x(\d+)\+(\d+)\+(\d+)\s+)?.*$", re.M) for idx, match in enumerate(pattern.finditer(output)): screen = Display() screen.name = match.group(1) - if (self.virt_idx is None) and (screen.name == self.DEFAULT_VIRT_SCREEN): + if self.virt_name and screen.name == self.virt_name: self.virt_idx = idx screen.primary = True if match.group(4) else False if screen.primary: @@ -299,19 +300,23 @@ class XRandR(SubprocessWrapper): print("Display information:") for s in self.screens: print("\t", s) + if self.primary_idx is None: + raise RuntimeError("There is no primary screen detected.\n" + "Go to display settings and set\n" + "a primary screen\n") if self.virt_idx == self.primary_idx: raise RuntimeError("Virtual screen must be selected other than the primary screen") - if self.virt_idx is None: - for idx, screen in enumerate(self.screens): - if not screen.connected and not screen.active: - self.virt_idx = idx - break - if self.virt_idx is None: - raise RuntimeError("There is no available devices for virtual screen") - self.virt = self.screens[self.virt_idx] + if self.virt_idx is not None: + self.virt = self.screens[self.virt_idx] + elif self.virt_name and self.virt_idx is None: + raise RuntimeError("No virtual screen name found") self.primary = self.screens[self.primary_idx] def _add_screen_mode(self, width, height, portrait, hidpi) -> None: + if not self.virt or not self.virt_name: + raise RuntimeError("No virtual screen selected.\n" + "Go to Display->Virtual Display->Advaced\n" + "To select a device.") # Set virtual screen property first self.virt.width = width self.virt.height = height @@ -353,6 +358,7 @@ class XRandR(SubprocessWrapper): def create_virtual_screen(self, width, height, portrait=False, hidpi=False) -> None: print("creating: ", self.virt) + self._update_screens() self._add_screen_mode(width, height, portrait, hidpi) self.check_output(f"xrandr --output {self.virt.name} --mode {self.mode_name}") self.check_output("sleep 5") @@ -360,6 +366,7 @@ class XRandR(SubprocessWrapper): self._update_screens() def delete_virtual_screen(self) -> None: + self._update_screens() try: self.virt.name self.mode_name @@ -400,7 +407,6 @@ class Backend(QObject): # Virtual screen properties self.xrandr: XRandR = XRandR() self._virtScreenCreated: bool = False - self._virtScreenIndex: int = self.xrandr.virt_idx # VNC server properties self._vncUsePassword: bool = False self._vncState: self.VNCState = self.VNCState.OFF @@ -473,18 +479,11 @@ class Backend(QObject): @pyqtProperty(QQmlListProperty, constant=True) def screens(self): - return QQmlListProperty(DisplayProperty, self, [DisplayProperty(x) for x in self.xrandr.screens]) - - @pyqtProperty(int, notify=onVirtScreenIndexChanged) - def virtScreenIndex(self): - return self._virtScreenIndex - - @virtScreenIndex.setter - def virtScreenIndex(self, virtScreenIndex): - print("Changing virt to ", virtScreenIndex) - self.xrandr.virt_idx = virtScreenIndex - self.xrandr.virt = self.xrandr.screens[self.xrandr.virt_idx] - self._virtScreenIndex = virtScreenIndex + try: + return QQmlListProperty(DisplayProperty, self, [DisplayProperty(x) for x in self.xrandr.screens]) + except RuntimeError as e: + self.onError.emit(str(e)) + return QQmlListProperty(DisplayProperty, self, []) @pyqtProperty(bool, notify=onVncUsePasswordChanged) def vncUsePassword(self): @@ -523,7 +522,11 @@ class Backend(QObject): @pyqtProperty(DisplayProperty) def primary(self): - self._primaryProp = DisplayProperty(self.xrandr.get_primary_screen()) + try: + self._primaryProp = DisplayProperty(self.xrandr.get_primary_screen()) + except RuntimeError as e: + self.onError.emit(str(e)) + return return self._primaryProp @pyqtProperty(int) @@ -537,14 +540,18 @@ class Backend(QObject): return cursor.y() # Qt Slots - @pyqtSlot(int, int, bool, bool) - def createVirtScreen(self, width, height, portrait, hidpi): + @pyqtSlot(str, int, int, bool, bool) + def createVirtScreen(self, device, width, height, portrait, hidpi): + self.xrandr.virt_name = device print("Creating a Virtual Screen...") try: self.xrandr.create_virtual_screen(width, height, portrait, hidpi) except subprocess.CalledProcessError as e: self.onError.emit(str(e.cmd) + '\n' + e.stdout.decode('utf-8')) return + except RuntimeError as e: + self.onError.emit(str(e)) + return self.virtScreenCreated = True @pyqtSlot() @@ -554,7 +561,11 @@ class Backend(QObject): self.onError.emit("Turn off the VNC server first") self.virtScreenCreated = True return - self.xrandr.delete_virtual_screen() + try: + self.xrandr.delete_virtual_screen() + except RuntimeError as e: + self.onError.emit(str(e)) + return self.virtScreenCreated = False @pyqtSlot(str) @@ -619,7 +630,11 @@ class Backend(QObject): logfile = open(X11VNC_LOG_PATH, "wb") self.vncServer = ProcessProtocol(_onConnected, _onReceived, _onReceived, _onEnded, logfile) - virt = self.xrandr.get_virtual_screen() + try: + virt = self.xrandr.get_virtual_screen() + except RuntimeError as e: + self.onError.emit(str(e)) + return clip = f"{virt.width}x{virt.height}+{virt.x_offset}+{virt.y_offset}" arg = f"x11vnc -rfbport {port} -clip {clip} {options}" if self.vncUsePassword: @@ -717,7 +732,11 @@ def main(): QMessageBox.critical(None, "VirtScreen", "x11vnc is not installed.") sys.exit(1) - + try: + test = XRandR() + except RuntimeError as e: + QMessageBox.critical(None, "VirtScreen", str(e)) + sys.exit(1) # Replace Twisted reactor with qt5reactor import qt5reactor # pylint: disable=E0401 qt5reactor.install()