diff --git a/main.qml b/main.qml index 716d09c..f45ce88 100644 --- a/main.qml +++ b/main.qml @@ -1,70 +1,24 @@ import QtQuick 2.10 -import QtQuick.Controls 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 +import Qt.labs.platform 1.0 import VirtScreen.DisplayProperty 1.0 import VirtScreen.Backend 1.0 - -ApplicationWindow { - id: window - visible: false - flags: Qt.FramelessWindowHint - title: "Basic layouts" - - Material.theme: Material.Light - Material.primary: Material.Teal - Material.accent: Material.Teal - // Material.background: Material.Grey - - width: 380 - height: 525 - property int margin: 8 - property int popupWidth: width - 26 - - // hide screen when loosing focus - property bool autoClose: true - property bool ignoreCloseOnce: false - onAutoCloseChanged: { - // When setting auto close disabled and then enabled again, we need to - // ignore focus change once. Otherwise the window always is closed one time - // even when the mouse is clicked in the window. - if (!autoClose) { - ignoreCloseOnce = true; - } - } - onActiveFocusItemChanged: { - if (autoClose && !ignoreCloseOnce && !activeFocusItem && !sysTrayIcon.clicked) { - this.hide(); - } - if (ignoreCloseOnce && autoClose) { - ignoreCloseOnce = false; - } - } - - // One-shot signal connect - function connectOnce (signal, slot) { - var f = function() { - slot.apply(this, arguments); - signal.disconnect(f); - } - signal.connect(f); - } - +Loader { + id: mainLoader + active: false + source: "mainWindow.qml" + property alias window: mainLoader.item + // virtscreen.py backend. Backend { id: backend - function switchVNC () { if ((backend.vncState == Backend.OFF) && backend.virtScreenCreated) { backend.startVNC(); } } - onVncAutoStartChanged: { if (vncAutoStart) { onVirtScreenCreatedChanged.connect(switchVNC); @@ -74,14 +28,12 @@ ApplicationWindow { onVncStateChanged.disconnect(switchVNC); } } - Component.onCompleted: { // force emit signal on load vncAutoStart = vncAutoStart; } } - // Timer object and function Timer { id: timer @@ -96,304 +48,62 @@ ApplicationWindow { } } - // menuBar: MenuBar { - // } + // One-shot signal connect + function connectOnce (signal, slot) { + var f = function() { + slot.apply(this, arguments); + signal.disconnect(f); + } + signal.connect(f); + } + + // Sytray Icon + SystemTrayIcon { + id: sysTrayIcon + iconSource: backend.vncState == Backend.CONNECTED ? "icon/icon_tablet_on.png" : + backend.virtScreenCreated ? "icon/icon_tablet_off.png" : + "icon/icon.png" + visible: true + property bool clicked: false + onMessageClicked: console.log("Message clicked") + Component.onCompleted: { + // without delay, the message appears in a wierd place + timer.setTimeout (function() { + showMessage("VirtScreen is running", + "The program will keep running in the system tray.\n" + + "To terminate the program, choose \"Quit\" in the \n" + + "context menu of the system tray entry."); + }, 1500); + } - menuBar: ToolBar { - id: toolbar - font.weight: Font.Medium - font.pointSize: 11 //parent.font.pointSize + 1 + onActivated: function(reason) { + console.log(reason); + if (reason == SystemTrayIcon.Context) { + return; + } + sysTrayIcon.clicked = true; + mainLoader.active = true; + } - RowLayout { - anchors.fill: parent - anchors.leftMargin: margin + 10 - - Label { - id: vncStateLabel + menu: Menu { + MenuItem { + id: vncStateText + enabled: false text: !backend.virtScreenCreated ? "Enable Virtual Screen first." : backend.vncState == Backend.OFF ? "Turn on VNC Server in the VNC tab." : backend.vncState == Backend.WAITING ? "VNC Server is waiting for a client..." : backend.vncState == Backend.CONNECTED ? "Connected." : "Server state error!" } - - ToolButton { - id: menuButton - anchors.right: parent.right - text: qsTr("⋮") - onClicked: menu.open() - - Menu { - id: menu - y: toolbar.height - - MenuItem { - text: qsTr("&About") - onTriggered: { - aboutDialog.open(); - } - } - - MenuItem { - text: qsTr("&Quit") - onTriggered: { - backend.quitProgram(); - } - } - } + MenuItem { + separator: true } - } - } - - header: TabBar { - id: tabBar - position: TabBar.Footer - // Material.primary: Material.Teal - - currentIndex: 0 - - TabButton { - text: qsTr("Display") - } - - TabButton { - text: qsTr("VNC") - } - } - - // footer: ToolBar { - // font.weight: Font.Medium - // font.pointSize: 11 //parent.font.pointSize + 1 - // anchors { horizontalCenter: parent.horizontalCenter } - // width: 200 - // } - - Popup { - id: busyDialog - modal: true - closePolicy: Popup.NoAutoClose - x: (parent.width - width) / 2 - y: parent.height / 2 - height - - BusyIndicator { - anchors.fill: parent - Material.accent: Material.Cyan - running: true - } - - background: Rectangle { - color: "transparent" - implicitWidth: 100 - implicitHeight: 100 - // border.color: "#444" - } - } - - Dialog { - id: aboutDialog - focus: true - x: (parent.width - width) / 2 - y: (parent.width - height) / 2 //(window.height) / 2 - width: popupWidth - - ColumnLayout { - anchors.fill: parent - - Text { - anchors.horizontalCenter: parent.horizontalCenter - horizontalAlignment: Text.AlignHCenter - font { weight: Font.Bold; pointSize: 15 } - text: "VirtScreen" - } - Text { - anchors.horizontalCenter: parent.horizontalCenter - horizontalAlignment: Text.AlignHCenter - text: "Make your iPad/tablet/computer
as a secondary monitor.
" - } - Text { - text: "- Project Website" - onLinkActivated: Qt.openUrlExternally(link) - } - Text { - text: "- Issues & Bug Report" - onLinkActivated: Qt.openUrlExternally(link) - } - Text { - font { pointSize: 10 } - anchors.horizontalCenter: parent.horizontalCenter - horizontalAlignment: Text.AlignHCenter - lineHeight: 0.7 - text: "
Copyright © 2018 Bumsik Kim Homepage
" - onLinkActivated: Qt.openUrlExternally(link) - } - Text { - font { pointSize: 9 } - anchors.horizontalCenter: parent.horizontalCenter - horizontalAlignment: Text.AlignHCenter - text: "This program comes with absolutely no warranty.
" + - "See the " + - "GNU General Public License, version 3 for details." - onLinkActivated: Qt.openUrlExternally(link) - } - } - } - - Dialog { - id: passwordDialog - title: "New password" - focus: true - modal: true - standardButtons: Dialog.Ok | Dialog.Cancel - x: (parent.width - width) / 2 - y: (parent.width - height) / 2 //(window.height) / 2 - width: popupWidth - - ColumnLayout { - anchors.fill: parent - - TextField { - id: passwordFIeld - focus: true - anchors.left: parent.left - anchors.right: parent.right - - placeholderText: "New Password"; - echoMode: TextInput.Password; - } - - Keys.onPressed: { - event.accepted = true; - if (event.key == Qt.Key_Return || event.key == Qt.Key_Enter) { - passwordDialog.accept(); - } - } - } - - onAccepted: { - backend.createVNCPassword(passwordFIeld.text); - passwordFIeld.text = ""; - } - onRejected: passwordFIeld.text = "" - } - - StackLayout { - width: parent.width - anchors.top: tabBar.bottom - anchors.bottom: parent.bottom - - currentIndex: tabBar.currentIndex - - ColumnLayout { - anchors.fill: parent - anchors.margins: margin - - GroupBox { - title: "Virtual Display" - anchors.left: parent.left - anchors.right: parent.right - - enabled: backend.virtScreenCreated ? false : true - - ColumnLayout { - anchors.left: parent.left - anchors.right: parent.right - - RowLayout { - Label { text: "Width"; Layout.fillWidth: true } - SpinBox { - value: backend.virt.width - from: 640 - to: 1920 - stepSize: 1 - editable: true - onValueModified: { - backend.virt.width = value; - } - textFromValue: function(value, locale) { return value; } - } - } - - RowLayout { - Label { text: "Height"; Layout.fillWidth: true } - SpinBox { - value: backend.virt.height - from: 360 - to: 1080 - stepSize : 1 - editable: true - onValueModified: { - backend.virt.height = value; - } - textFromValue: function(value, locale) { return value; } - } - } - - RowLayout { - Label { text: "Portrait Mode"; Layout.fillWidth: true } - Switch { - checked: backend.portrait - onCheckedChanged: { - backend.portrait = checked; - } - } - } - - RowLayout { - Label { text: "HiDPI (2x resolution)"; Layout.fillWidth: true } - Switch { - checked: backend.hidpi - onCheckedChanged: { - backend.hidpi = checked; - } - } - } - - 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.DemiBold : Font.Normal - highlighted: ListView.isCurrentItem - enabled: modelData.connected ? false : true - } - } - } - } - } - - Button { - id: virtScreenButton + MenuItem { + id: virtScreenAction text: backend.virtScreenCreated ? "Disable Virtual Screen" : "Enable Virtual Screen" - highlighted: true - - anchors.left: parent.left - anchors.right: parent.right - // Material.accent: Material.Teal - // Material.theme: Material.Dark - - enabled: backend.vncAutoStart ? true : - backend.vncState == Backend.OFF ? true : false - - onClicked: { - busyDialog.open(); + enabled:backend.vncAutoStart ? true : + backend.vncState == Backend.OFF ? true : false + onTriggered: { // Give a very short delay to show busyDialog. timer.setTimeout (function() { if (!backend.virtScreenCreated) { @@ -417,263 +127,19 @@ ApplicationWindow { } }, 200); } - - Component.onCompleted: { - backend.onVirtScreenCreatedChanged.connect(function(created) { - busyDialog.close(); - }); - } } - - Button { - id: displaySettingButton - text: "Open Display Setting" - - anchors.left: parent.left - anchors.right: parent.right - // Material.accent: Material.Teal - // Material.theme: Material.Dark - - enabled: backend.virtScreenCreated ? true : false - - onClicked: { - busyDialog.open(); - window.autoClose = false; - if (backend.vncState != Backend.OFF) { - console.log("vnc is running"); - var restoreVNC = true; - if (backend.vncAutoStart) { - backend.vncAutoStart = false; - var restoreAutoStart = true; - } - } - connectOnce(backend.onDisplaySettingClosed, function() { - window.autoClose = true; - busyDialog.close(); - if (restoreAutoStart) { - backend.vncAutoStart = true; - } - if (restoreVNC) { - backend.startVNC(); - } - }); - backend.stopVNC(); - backend.openDisplaySetting(); - } - } - } - - ColumnLayout { - anchors.fill: parent - anchors.margins: margin - - GroupBox { - title: "VNC Server" - anchors.left: parent.left - anchors.right: parent.right - - enabled: backend.vncState == Backend.OFF ? true : false - - ColumnLayout { - anchors.left: parent.left - anchors.right: parent.right - - RowLayout { - Label { text: "Port"; Layout.fillWidth: true } - SpinBox { - value: backend.vncPort - from: 1 - to: 65535 - stepSize: 1 - editable: true - onValueModified: { - backend.vncPort = value; - } - textFromValue: function(value, locale) { return value; } - } - } - - RowLayout { - anchors.left: parent.left - anchors.right: parent.right - - Label { text: "Password"; Layout.fillWidth: true } - - Button { - text: "Delete" - font.capitalization: Font.MixedCase - highlighted: false - enabled: backend.vncUsePassword - onClicked: backend.deleteVNCPassword() - } - - Button { - text: "New" - font.capitalization: Font.MixedCase - highlighted: true - enabled: !backend.vncUsePassword - onClicked: passwordDialog.open() - } - } - } - } - - Button { - id: vncButton - anchors.left: parent.left - anchors.right: parent.right - anchors.bottomMargin: 0 - highlighted: true - + MenuItem { + id: vncAction text: backend.vncAutoStart ? "Auto start enabled" : backend.vncState == Backend.OFF ? "Start VNC Server" : "Stop VNC Server" enabled: backend.vncAutoStart ? false : backend.virtScreenCreated ? true : false - // Material.background: Material.Teal - // Material.foreground: Material.Grey - onClicked: backend.vncState == Backend.OFF ? backend.startVNC() : backend.stopVNC() + onTriggered: backend.vncState == Backend.OFF ? backend.startVNC() : backend.stopVNC() } - - RowLayout { - id: autoSwitchLayout - anchors.top: vncButton.top - anchors.right: parent.right - anchors.topMargin: vncButton.height - 10 - - Label { text: "Auto start"; } - Switch { - checked: backend.vncAutoStart - onToggled: { - if ((checked == true) && (backend.vncState == Backend.OFF) && - backend.virtScreenCreated) { - backend.startVNC(); - } - backend.vncAutoStart = checked; - } - } - } - - - GroupBox { - title: "Available IP addresses" - anchors.top: autoSwitchLayout.bottom - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - - ColumnLayout { - anchors.fill: parent - - ListView { - id: ipListView - anchors.fill: parent - - // anchors.top: parent.top - // anchors.left: parent.left - // anchors.right: parent.right - // height: 100 - - ScrollBar.vertical: ScrollBar { - parent: ipListView.parent - anchors.top: ipListView.top - anchors.right: ipListView.right - anchors.bottom: ipListView.bottom - policy: ScrollBar.AlwaysOn - } - - model: backend.ipAddresses - delegate: TextEdit { - text: modelData - readOnly: true - selectByMouse: true - anchors.horizontalCenter: parent.horizontalCenter - font.pointSize: 12 - } - } - } - } - } - } - - // Sytray Icon - Labs.SystemTrayIcon { - id: sysTrayIcon - iconSource: backend.vncState == Backend.CONNECTED ? "icon/icon_tablet_on.png" : - backend.virtScreenCreated ? "icon/icon_tablet_off.png" : - "icon/icon.png" - visible: true - property bool clicked: false - - onMessageClicked: console.log("Message clicked") - Component.onCompleted: { - // without delay, the message appears in a wierd place - timer.setTimeout (function() { - showMessage("VirtScreen is running", - "The program will keep running in the system tray.\n" + - "To terminate the program, choose \"Quit\" in the \n" + - "context menu of the system tray entry."); - }, 1500); - } - - onActivated: function(reason) { - console.log(reason); - if (reason == Labs.SystemTrayIcon.Context) { - return; - } - if (window.visible) { - window.hide(); - return; - } - sysTrayIcon.clicked = true; - // Move window to the corner of the primary display - var primary = backend.primary; - var width = primary.width; - var height = primary.height; - var cursor_x = backend.cursor_x - primary.x_offset; - var cursor_y = backend.cursor_y - primary.y_offset; - var x_mid = width / 2; - var y_mid = height / 2; - var x = width - window.width; //(cursor_x > x_mid)? width - window.width : 0; - var y = (cursor_y > y_mid)? height - window.height : 0; - x += primary.x_offset; - y += primary.y_offset; - window.x = x; - window.y = y; - window.show(); - window.raise(); - window.requestActivate(); - timer.setTimeout (function() { - sysTrayIcon.clicked = false; - }, 200); - } - - menu: Labs.Menu { - Labs.MenuItem { - enabled: false - text: vncStateLabel.text - } - - Labs.MenuItem { + MenuItem { separator: true } - - Labs.MenuItem { - text: virtScreenButton.text - enabled: virtScreenButton.enabled - onTriggered: virtScreenButton.onClicked() - } - - Labs.MenuItem { - text: vncButton.text - enabled: vncButton.enabled - onTriggered: vncButton.onClicked() - } - - Labs.MenuItem { - separator: true - } - - Labs.MenuItem { + MenuItem { text: qsTr("&Quit") onTriggered: { backend.quitProgram(); @@ -681,4 +147,41 @@ ApplicationWindow { } } } -} + + onStatusChanged: { + console.log("Status changed", status); + if (status == Loader.Null) { + gc(); + } + } + + onLoaded: { + window.onVisibleChanged.connect(function(visible) { + if (!visible) { + console.log('hiding'); + console.log("unloading..."); + mainLoader.active = false; + } + }); + // Move window to the corner of the primary display + var primary = backend.primary; + var width = primary.width; + var height = primary.height; + var cursor_x = backend.cursor_x - primary.x_offset; + var cursor_y = backend.cursor_y - primary.y_offset; + var x_mid = width / 2; + var y_mid = height / 2; + var x = width - window.width; //(cursor_x > x_mid)? width - window.width : 0; + var y = (cursor_y > y_mid)? height - window.height : 0; + x += primary.x_offset; + y += primary.y_offset; + window.x = x; + window.y = y; + window.show(); + window.raise(); + window.requestActivate(); + timer.setTimeout (function() { + sysTrayIcon.clicked = false; + }, 200); + } +} \ No newline at end of file diff --git a/mainWindow.qml b/mainWindow.qml new file mode 100644 index 0000000..322f59f --- /dev/null +++ b/mainWindow.qml @@ -0,0 +1,463 @@ +import QtQuick 2.10 +import QtQuick.Controls 2.3 +import QtQuick.Controls.Material 2.3 +import QtQuick.Layouts 1.3 +import QtQuick.Window 2.2 + +import VirtScreen.Backend 1.0 + +ApplicationWindow { + id: window + visible: false + flags: Qt.FramelessWindowHint + title: "Basic layouts" + + Material.theme: Material.Light + Material.primary: Material.Teal + Material.accent: Material.Teal + // Material.background: Material.Grey + + width: 380 + height: 525 + property int margin: 8 + property int popupWidth: width - 26 + + // hide screen when loosing focus + property bool autoClose: true + property bool ignoreCloseOnce: false + onAutoCloseChanged: { + // When setting auto close disabled and then enabled again, we need to + // ignore focus change once. Otherwise the window always is closed one time + // even when the mouse is clicked in the window. + if (!autoClose) { + ignoreCloseOnce = true; + } + } + onActiveFocusItemChanged: { + if (autoClose && !ignoreCloseOnce && !activeFocusItem && !sysTrayIcon.clicked) { + this.hide(); + } + if (ignoreCloseOnce && autoClose) { + ignoreCloseOnce = false; + } + } + + menuBar: ToolBar { + id: toolbar + font.weight: Font.Medium + font.pointSize: 11 //parent.font.pointSize + 1 + + RowLayout { + anchors.fill: parent + anchors.leftMargin: margin + 10 + + Label { + id: vncStateLabel + text: vncStateText.text + } + + ToolButton { + id: menuButton + anchors.right: parent.right + text: qsTr("⋮") + onClicked: menu.open() + + Menu { + id: menu + y: toolbar.height + + MenuItem { + text: qsTr("&About") + onTriggered: { + aboutDialog.open(); + } + } + + MenuItem { + text: qsTr("&Quit") + onTriggered: { + backend.quitProgram(); + } + } + } + } + } + } + + header: TabBar { + id: tabBar + position: TabBar.Footer + // Material.primary: Material.Teal + + currentIndex: 0 + + TabButton { + text: qsTr("Display") + } + + TabButton { + text: qsTr("VNC") + } + } + + // footer: ToolBar { + // font.weight: Font.Medium + // font.pointSize: 11 //parent.font.pointSize + 1 + // anchors { horizontalCenter: parent.horizontalCenter } + // width: 200 + // } + + Popup { + id: busyDialog + modal: true + closePolicy: Popup.NoAutoClose + x: (parent.width - width) / 2 + y: parent.height / 2 - height + BusyIndicator { + anchors.fill: parent + Material.accent: Material.Cyan + running: true + } + background: Rectangle { + color: "transparent" + implicitWidth: 100 + implicitHeight: 100 + // border.color: "#444" + } + } + + Dialog { + id: aboutDialog + focus: true + x: (parent.width - width) / 2 + y: (parent.width - height) / 2 //(window.height) / 2 + width: popupWidth + ColumnLayout { + anchors.fill: parent + Text { + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + font { weight: Font.Bold; pointSize: 15 } + text: "VirtScreen" + } + Text { + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + text: "Make your iPad/tablet/computer
as a secondary monitor.
" + } + Text { + text: "- Project Website" + onLinkActivated: Qt.openUrlExternally(link) + } + Text { + text: "- Issues & Bug Report" + onLinkActivated: Qt.openUrlExternally(link) + } + Text { + font { pointSize: 10 } + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + lineHeight: 0.7 + text: "
Copyright © 2018 Bumsik Kim Homepage
" + onLinkActivated: Qt.openUrlExternally(link) + } + Text { + font { pointSize: 9 } + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + text: "This program comes with absolutely no warranty.
" + + "See the " + + "GNU General Public License, version 3 for details." + onLinkActivated: Qt.openUrlExternally(link) + } + } + } + + Dialog { + id: passwordDialog + title: "New password" + focus: true + modal: true + standardButtons: Dialog.Ok | Dialog.Cancel + x: (parent.width - width) / 2 + y: (parent.width - height) / 2 //(window.height) / 2 + width: popupWidth + ColumnLayout { + anchors.fill: parent + TextField { + id: passwordFIeld + focus: true + anchors.left: parent.left + anchors.right: parent.right + placeholderText: "New Password"; + echoMode: TextInput.Password; + } + Keys.onPressed: { + event.accepted = true; + if (event.key == Qt.Key_Return || event.key == Qt.Key_Enter) { + passwordDialog.accept(); + } + } + } + onAccepted: { + backend.createVNCPassword(passwordFIeld.text); + passwordFIeld.text = ""; + } + onRejected: passwordFIeld.text = "" + } + + StackLayout { + width: parent.width + anchors.top: tabBar.bottom + anchors.bottom: parent.bottom + + currentIndex: tabBar.currentIndex + + ColumnLayout { + anchors.fill: parent + anchors.margins: margin + GroupBox { + title: "Virtual Display" + anchors.left: parent.left + anchors.right: parent.right + enabled: backend.virtScreenCreated ? false : true + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + RowLayout { + Label { text: "Width"; Layout.fillWidth: true } + SpinBox { + value: backend.virt.width + from: 640 + to: 1920 + stepSize: 1 + editable: true + onValueModified: { + backend.virt.width = value; + } + textFromValue: function(value, locale) { return value; } + } + } + RowLayout { + Label { text: "Height"; Layout.fillWidth: true } + SpinBox { + value: backend.virt.height + from: 360 + to: 1080 + stepSize : 1 + editable: true + onValueModified: { + backend.virt.height = value; + } + textFromValue: function(value, locale) { return value; } + } + } + RowLayout { + Label { text: "Portrait Mode"; Layout.fillWidth: true } + Switch { + checked: backend.portrait + onCheckedChanged: { + backend.portrait = checked; + } + } + } + RowLayout { + Label { text: "HiDPI (2x resolution)"; Layout.fillWidth: true } + Switch { + checked: backend.hidpi + onCheckedChanged: { + backend.hidpi = checked; + } + } + } + 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.DemiBold : Font.Normal + highlighted: ListView.isCurrentItem + enabled: modelData.connected ? false : true + } + } + } + } + } + Button { + id: virtScreenButton + text: virtScreenAction.text + highlighted: true + anchors.left: parent.left + anchors.right: parent.right + // Material.accent: Material.Teal + // Material.theme: Material.Dark + enabled: virtScreenAction.enabled + onClicked: { + busyDialog.open(); + virtScreenAction.onTriggered(); + } + Component.onCompleted: { + backend.onVirtScreenCreatedChanged.connect(function(created) { + busyDialog.close(); + }); + } + } + Button { + id: displaySettingButton + text: "Open Display Setting" + anchors.left: parent.left + anchors.right: parent.right + // Material.accent: Material.Teal + // Material.theme: Material.Dark + enabled: backend.virtScreenCreated ? true : false + onClicked: { + busyDialog.open(); + window.autoClose = false; + if (backend.vncState != Backend.OFF) { + console.log("vnc is running"); + var restoreVNC = true; + if (backend.vncAutoStart) { + backend.vncAutoStart = false; + var restoreAutoStart = true; + } + } + connectOnce(backend.onDisplaySettingClosed, function() { + window.autoClose = true; + busyDialog.close(); + if (restoreAutoStart) { + backend.vncAutoStart = true; + } + if (restoreVNC) { + backend.startVNC(); + } + }); + backend.stopVNC(); + backend.openDisplaySetting(); + } + } + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: margin + GroupBox { + title: "VNC Server" + anchors.left: parent.left + anchors.right: parent.right + enabled: backend.vncState == Backend.OFF ? true : false + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + RowLayout { + Label { text: "Port"; Layout.fillWidth: true } + SpinBox { + value: backend.vncPort + from: 1 + to: 65535 + stepSize: 1 + editable: true + onValueModified: { + backend.vncPort = value; + } + textFromValue: function(value, locale) { return value; } + } + } + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + Label { text: "Password"; Layout.fillWidth: true } + Button { + text: "Delete" + font.capitalization: Font.MixedCase + highlighted: false + enabled: backend.vncUsePassword + onClicked: backend.deleteVNCPassword() + } + Button { + text: "New" + font.capitalization: Font.MixedCase + highlighted: true + enabled: !backend.vncUsePassword + onClicked: passwordDialog.open() + } + } + } + } + Button { + id: vncButton + anchors.left: parent.left + anchors.right: parent.right + anchors.bottomMargin: 0 + highlighted: true + text: vncAction.text + enabled: vncAction.enabled + // Material.background: Material.Teal + // Material.foreground: Material.Grey + onClicked: vncAction.onTriggered() + } + RowLayout { + id: autoSwitchLayout + anchors.top: vncButton.top + anchors.right: parent.right + anchors.topMargin: vncButton.height - 10 + Label { text: "Auto start"; } + Switch { + checked: backend.vncAutoStart + onToggled: { + if ((checked == true) && (backend.vncState == Backend.OFF) && + backend.virtScreenCreated) { + backend.startVNC(); + } + backend.vncAutoStart = checked; + } + } + } + GroupBox { + title: "Available IP addresses" + anchors.top: autoSwitchLayout.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + ColumnLayout { + anchors.fill: parent + ListView { + id: ipListView + anchors.fill: parent + // anchors.top: parent.top + // anchors.left: parent.left + // anchors.right: parent.right + // height: 100 + ScrollBar.vertical: ScrollBar { + parent: ipListView.parent + anchors.top: ipListView.top + anchors.right: ipListView.right + anchors.bottom: ipListView.bottom + policy: ScrollBar.AlwaysOn + } + model: backend.ipAddresses + delegate: TextEdit { + text: modelData + readOnly: true + selectByMouse: true + anchors.horizontalCenter: parent.horizontalCenter + font.pointSize: 12 + } + } + } + } + } + } +} diff --git a/virtscreen.py b/virtscreen.py index e843226..f2b6a80 100755 --- a/virtscreen.py +++ b/virtscreen.py @@ -131,18 +131,17 @@ class ProcessProtocol(protocol.ProcessProtocol): #------------------------------------------------------------------------------- # Display properties #------------------------------------------------------------------------------- -class DisplayProperty(QObject): - _name: str - _primary: bool - _connected: bool - _active: bool - _width: int - _height: int - _x_offset: int - _y_offset: int - +class Display(object): + __slots__ = ['name', 'primary', 'connected', 'active', 'width', 'height', 'x_offset', 'y_offset'] def __init__(self, parent=None): - super(DisplayProperty, self).__init__(parent) + self.name: str = None + self.primary: bool = False + self.connected: bool = False + self.active: bool = False + self.width: int = 0 + self.height: int = 0 + self.x_offset: int = 0 + self.y_offset: int = 0 def __str__(self): ret = f"{self.name}" @@ -155,64 +154,76 @@ class DisplayProperty(QObject): if self.active: ret += f" {self.width}x{self.height}+{self.x_offset}+{self.y_offset}" else: - ret += " not active" + ret += f" not active {self.width}x{self.height}" return ret - + + +class DisplayProperty(QObject): + _display: Display + + def __init__(self, display: Display, parent=None): + super(DisplayProperty, self).__init__(parent) + self._display = display + + @property + def display(self): + return self._display + @pyqtProperty(str, constant=True) def name(self): - return self._name + return self._display.name @name.setter def name(self, name): - self._name = name + self._display.name = name @pyqtProperty(bool, constant=True) def primary(self): - return self._primary + return self._display.primary @primary.setter def primary(self, primary): - self._primary = primary + self._display.primary = primary @pyqtProperty(bool, constant=True) def connected(self): - return self._connected + return self._display.connected @connected.setter def connected(self, connected): - self._connected = connected + self._display.connected = connected @pyqtProperty(bool, constant=True) def active(self): - return self._active + return self._display.active @active.setter def active(self, active): - self._active = active + self._display.active = active @pyqtProperty(int, constant=True) def width(self): - return self._width + return self._display.width @width.setter def width(self, width): - self._width = width + self._display.width = width @pyqtProperty(int, constant=True) def height(self): - return self._height + return self._display.height @height.setter def height(self, height): - self._height = height + self._display.height = height @pyqtProperty(int, constant=True) def x_offset(self): - return self._x_offset + return self._display.x_offset @x_offset.setter def x_offset(self, x_offset): - self._x_offset = x_offset + self._display.x_offset = x_offset @pyqtProperty(int, constant=True) def y_offset(self): - return self._y_offset + return self._display.y_offset @y_offset.setter def y_offset(self, y_offset): - self._y_offset = y_offset + self._display.y_offset = y_offset #------------------------------------------------------------------------------- # Screen adjustment class @@ -224,9 +235,9 @@ class XRandR(SubprocessWrapper): def __init__(self): super(XRandR, self).__init__() self.mode_name: str - self.screens: List[DisplayProperty] = [] - self.virt: DisplayProperty() = None - self.primary: DisplayProperty() = None + self.screens: List[Display] = [] + self.virt: Display() = None + self.primary: Display() = None self.virt_idx: int = None self.primary_idx: int = None # Primary display @@ -241,7 +252,7 @@ class XRandR(SubprocessWrapper): 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 = DisplayProperty() + screen = Display() screen.name = match.group(1) if (self.virt_idx is None) and (screen.name == self.DEFAULT_VIRT_SCREEN): self.virt_idx = idx @@ -304,11 +315,11 @@ class XRandR(SubprocessWrapper): self.delete_virtual_screen() os._exit(0) - def get_primary_screen(self) -> DisplayProperty: + def get_primary_screen(self) -> Display: self._update_screens() return self.primary - def get_virtual_screen(self) -> DisplayProperty: + def get_virtual_screen(self) -> Display: self._update_screens() return self.virt @@ -345,7 +356,8 @@ class Backend(QObject): Q_ENUMS(VNCState) # Virtual screen properties xrandr: XRandR - _virt: DisplayProperty = DisplayProperty() + _virt: Display = Display() + _virtProp: DisplayProperty _portrait: bool _hidpi: bool _virtScreenCreated: bool = False @@ -357,7 +369,7 @@ class Backend(QObject): _vncState: VNCState _vncAutoStart: bool # Primary screen and mouse posistion - primary: DisplayProperty() + _primaryProp: DisplayProperty cursor_x: int cursor_y: int vncServer: ProcessProtocol @@ -377,8 +389,8 @@ class Backend(QObject): try: with open(CONFIG_PATH, "r") as f: settings = json.load(f) - self.virt.width = settings['virt']['width'] - self.virt.height = settings['virt']['height'] + self._virt.width = settings['virt']['width'] + self._virt.height = settings['virt']['height'] self._portrait = settings['virt']['portrait'] self._hidpi = settings['virt']['hidpi'] self._vncPort = settings['vnc']['port'] @@ -387,13 +399,14 @@ class Backend(QObject): print("Default Setting used.") with open(DEFAULT_CONFIG_PATH, "r") as f: settings = json.load(f) - self.virt.width = settings['virt']['width'] - self.virt.height = settings['virt']['height'] + self._virt.width = settings['virt']['width'] + self._virt.height = settings['virt']['height'] self._portrait = settings['virt']['portrait'] self._hidpi = settings['virt']['hidpi'] self._vncPort = settings['vnc']['port'] self._vncAutoStart = settings['vnc']['autostart'] # create objects + self._virtProp = DisplayProperty(self._virt) self._vncState = self.VNCState.OFF self.xrandr = XRandR() self._virtScreenIndex = self.xrandr.virt_idx @@ -401,10 +414,10 @@ class Backend(QObject): # Qt properties @pyqtProperty(DisplayProperty) def virt(self): - return self._virt + return self._virtProp @virt.setter def virt(self, virt): - self._virt = virt + self._virtProp = virt @pyqtProperty(bool) def portrait(self): @@ -430,7 +443,7 @@ class Backend(QObject): @pyqtProperty(QQmlListProperty) def screens(self): - return QQmlListProperty(DisplayProperty, self, self.xrandr.screens) + return QQmlListProperty(DisplayProperty, self, [DisplayProperty(x) for x in self.xrandr.screens]) @pyqtProperty(int, notify=onVirtScreenIndexChanged) def virtScreenIndex(self): @@ -492,7 +505,8 @@ class Backend(QObject): @pyqtProperty(DisplayProperty) def primary(self): - return self.xrandr.get_primary_screen() + self._primaryProp = DisplayProperty(self.xrandr.get_primary_screen()) + return self._primaryProp @pyqtProperty(int) def cursor_x(self): @@ -508,7 +522,7 @@ class Backend(QObject): @pyqtSlot() def createVirtScreen(self): print("Creating a Virtual Screen...") - self.xrandr.create_virtual_screen(self.virt.width, self.virt.height, self.portrait, self.hidpi) + self.xrandr.create_virtual_screen(self._virt.width, self._virt.height, self.portrait, self.hidpi) self.virtScreenCreated = True @pyqtSlot() @@ -613,8 +627,8 @@ class Backend(QObject): with open(CONFIG_PATH, 'w') as f: settings = {} settings['virt'] = {} - settings['virt']['width'] = self.virt.width - settings['virt']['height'] = self.virt.height + settings['virt']['width'] = self._virt.width + settings['virt']['height'] = self._virt.height settings['virt']['portrait'] = self._portrait settings['virt']['hidpi'] = self._hidpi settings['vnc'] = {}