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

Optimization: Use loader to reduce memory on idle

This commit is contained in:
Bumsik Kim 2018-05-13 17:45:03 -04:00
parent 863cb99124
commit fe9b3fb13e
No known key found for this signature in database
GPG key ID: E31041C8EC5B01C6
3 changed files with 621 additions and 641 deletions

689
main.qml
View file

@ -1,70 +1,24 @@
import QtQuick 2.10 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.DisplayProperty 1.0
import VirtScreen.Backend 1.0 import VirtScreen.Backend 1.0
Loader {
ApplicationWindow { id: mainLoader
id: window active: false
visible: false source: "mainWindow.qml"
flags: Qt.FramelessWindowHint property alias window: mainLoader.item
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);
}
// virtscreen.py backend. // virtscreen.py backend.
Backend { Backend {
id: backend id: backend
function switchVNC () { function switchVNC () {
if ((backend.vncState == Backend.OFF) && backend.virtScreenCreated) { if ((backend.vncState == Backend.OFF) && backend.virtScreenCreated) {
backend.startVNC(); backend.startVNC();
} }
} }
onVncAutoStartChanged: { onVncAutoStartChanged: {
if (vncAutoStart) { if (vncAutoStart) {
onVirtScreenCreatedChanged.connect(switchVNC); onVirtScreenCreatedChanged.connect(switchVNC);
@ -74,14 +28,12 @@ ApplicationWindow {
onVncStateChanged.disconnect(switchVNC); onVncStateChanged.disconnect(switchVNC);
} }
} }
Component.onCompleted: { Component.onCompleted: {
// force emit signal on load // force emit signal on load
vncAutoStart = vncAutoStart; vncAutoStart = vncAutoStart;
} }
} }
// Timer object and function // Timer object and function
Timer { Timer {
id: 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 { onActivated: function(reason) {
id: toolbar console.log(reason);
font.weight: Font.Medium if (reason == SystemTrayIcon.Context) {
font.pointSize: 11 //parent.font.pointSize + 1 return;
}
sysTrayIcon.clicked = true;
mainLoader.active = true;
}
RowLayout { menu: Menu {
anchors.fill: parent MenuItem {
anchors.leftMargin: margin + 10 id: vncStateText
enabled: false
Label {
id: vncStateLabel
text: !backend.virtScreenCreated ? "Enable Virtual Screen first." : text: !backend.virtScreenCreated ? "Enable Virtual Screen first." :
backend.vncState == Backend.OFF ? "Turn on VNC Server in the VNC tab." : 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.WAITING ? "VNC Server is waiting for a client..." :
backend.vncState == Backend.CONNECTED ? "Connected." : backend.vncState == Backend.CONNECTED ? "Connected." :
"Server state error!" "Server state error!"
} }
MenuItem {
ToolButton { separator: true
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 {
} id: virtScreenAction
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<br/>as a secondary monitor.<br/>"
}
Text {
text: "- <a href='https://github.com/kbumsik/VirtScreen'>Project Website</a>"
onLinkActivated: Qt.openUrlExternally(link)
}
Text {
text: "- <a href='https://github.com/kbumsik/VirtScreen/issues'>Issues & Bug Report</a>"
onLinkActivated: Qt.openUrlExternally(link)
}
Text {
font { pointSize: 10 }
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
lineHeight: 0.7
text: "<br/>Copyright © 2018 Bumsik Kim <a href='https://kbumsik.io/'>Homepage</a><br/>"
onLinkActivated: Qt.openUrlExternally(link)
}
Text {
font { pointSize: 9 }
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
text: "This program comes with absolutely no warranty.<br/>" +
"See the <a href='https://github.com/kbumsik/VirtScreen/blob/master/LICENSE'>" +
"GNU General Public License, version 3</a> 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: backend.virtScreenCreated ? "Disable Virtual Screen" : "Enable Virtual Screen" text: backend.virtScreenCreated ? "Disable Virtual Screen" : "Enable Virtual Screen"
highlighted: true enabled:backend.vncAutoStart ? true :
backend.vncState == Backend.OFF ? true : false
anchors.left: parent.left onTriggered: {
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();
// Give a very short delay to show busyDialog. // Give a very short delay to show busyDialog.
timer.setTimeout (function() { timer.setTimeout (function() {
if (!backend.virtScreenCreated) { if (!backend.virtScreenCreated) {
@ -417,263 +127,19 @@ ApplicationWindow {
} }
}, 200); }, 200);
} }
Component.onCompleted: {
backend.onVirtScreenCreatedChanged.connect(function(created) {
busyDialog.close();
});
}
} }
MenuItem {
Button { id: vncAction
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: backend.vncAutoStart ? "Auto start enabled" : text: backend.vncAutoStart ? "Auto start enabled" :
backend.vncState == Backend.OFF ? "Start VNC Server" : "Stop VNC Server" backend.vncState == Backend.OFF ? "Start VNC Server" : "Stop VNC Server"
enabled: backend.vncAutoStart ? false : enabled: backend.vncAutoStart ? false :
backend.virtScreenCreated ? true : false backend.virtScreenCreated ? true : false
// Material.background: Material.Teal onTriggered: backend.vncState == Backend.OFF ? backend.startVNC() : backend.stopVNC()
// Material.foreground: Material.Grey
onClicked: backend.vncState == Backend.OFF ? backend.startVNC() : backend.stopVNC()
} }
MenuItem {
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 {
separator: true separator: true
} }
MenuItem {
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 {
text: qsTr("&Quit") text: qsTr("&Quit")
onTriggered: { onTriggered: {
backend.quitProgram(); 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);
}
}

463
mainWindow.qml Normal file
View file

@ -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<br/>as a secondary monitor.<br/>"
}
Text {
text: "- <a href='https://github.com/kbumsik/VirtScreen'>Project Website</a>"
onLinkActivated: Qt.openUrlExternally(link)
}
Text {
text: "- <a href='https://github.com/kbumsik/VirtScreen/issues'>Issues & Bug Report</a>"
onLinkActivated: Qt.openUrlExternally(link)
}
Text {
font { pointSize: 10 }
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
lineHeight: 0.7
text: "<br/>Copyright © 2018 Bumsik Kim <a href='https://kbumsik.io/'>Homepage</a><br/>"
onLinkActivated: Qt.openUrlExternally(link)
}
Text {
font { pointSize: 9 }
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
text: "This program comes with absolutely no warranty.<br/>" +
"See the <a href='https://github.com/kbumsik/VirtScreen/blob/master/LICENSE'>" +
"GNU General Public License, version 3</a> 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
}
}
}
}
}
}
}

View file

@ -131,18 +131,17 @@ class ProcessProtocol(protocol.ProcessProtocol):
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
# Display properties # Display properties
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
class DisplayProperty(QObject): class Display(object):
_name: str __slots__ = ['name', 'primary', 'connected', 'active', 'width', 'height', 'x_offset', 'y_offset']
_primary: bool
_connected: bool
_active: bool
_width: int
_height: int
_x_offset: int
_y_offset: int
def __init__(self, parent=None): 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): def __str__(self):
ret = f"{self.name}" ret = f"{self.name}"
@ -155,64 +154,76 @@ class DisplayProperty(QObject):
if self.active: if self.active:
ret += f" {self.width}x{self.height}+{self.x_offset}+{self.y_offset}" ret += f" {self.width}x{self.height}+{self.x_offset}+{self.y_offset}"
else: else:
ret += " not active" ret += f" not active {self.width}x{self.height}"
return ret 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) @pyqtProperty(str, constant=True)
def name(self): def name(self):
return self._name return self._display.name
@name.setter @name.setter
def name(self, name): def name(self, name):
self._name = name self._display.name = name
@pyqtProperty(bool, constant=True) @pyqtProperty(bool, constant=True)
def primary(self): def primary(self):
return self._primary return self._display.primary
@primary.setter @primary.setter
def primary(self, primary): def primary(self, primary):
self._primary = primary self._display.primary = primary
@pyqtProperty(bool, constant=True) @pyqtProperty(bool, constant=True)
def connected(self): def connected(self):
return self._connected return self._display.connected
@connected.setter @connected.setter
def connected(self, connected): def connected(self, connected):
self._connected = connected self._display.connected = connected
@pyqtProperty(bool, constant=True) @pyqtProperty(bool, constant=True)
def active(self): def active(self):
return self._active return self._display.active
@active.setter @active.setter
def active(self, active): def active(self, active):
self._active = active self._display.active = active
@pyqtProperty(int, constant=True) @pyqtProperty(int, constant=True)
def width(self): def width(self):
return self._width return self._display.width
@width.setter @width.setter
def width(self, width): def width(self, width):
self._width = width self._display.width = width
@pyqtProperty(int, constant=True) @pyqtProperty(int, constant=True)
def height(self): def height(self):
return self._height return self._display.height
@height.setter @height.setter
def height(self, height): def height(self, height):
self._height = height self._display.height = height
@pyqtProperty(int, constant=True) @pyqtProperty(int, constant=True)
def x_offset(self): def x_offset(self):
return self._x_offset return self._display.x_offset
@x_offset.setter @x_offset.setter
def x_offset(self, x_offset): def x_offset(self, x_offset):
self._x_offset = x_offset self._display.x_offset = x_offset
@pyqtProperty(int, constant=True) @pyqtProperty(int, constant=True)
def y_offset(self): def y_offset(self):
return self._y_offset return self._display.y_offset
@y_offset.setter @y_offset.setter
def y_offset(self, y_offset): def y_offset(self, y_offset):
self._y_offset = y_offset self._display.y_offset = y_offset
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
# Screen adjustment class # Screen adjustment class
@ -224,9 +235,9 @@ class XRandR(SubprocessWrapper):
def __init__(self): def __init__(self):
super(XRandR, self).__init__() super(XRandR, self).__init__()
self.mode_name: str self.mode_name: str
self.screens: List[DisplayProperty] = [] self.screens: List[Display] = []
self.virt: DisplayProperty() = None self.virt: Display() = None
self.primary: DisplayProperty() = None self.primary: Display() = None
self.virt_idx: int = None self.virt_idx: int = None
self.primary_idx: int = None self.primary_idx: int = None
# Primary display # Primary display
@ -241,7 +252,7 @@ class XRandR(SubprocessWrapper):
pattern = re.compile(r"^(\S*)\s+(connected|disconnected)\s+((primary)\s+)?" pattern = re.compile(r"^(\S*)\s+(connected|disconnected)\s+((primary)\s+)?"
r"((\d+)x(\d+)\+(\d+)\+(\d+)\s+)?.*$", re.M) r"((\d+)x(\d+)\+(\d+)\+(\d+)\s+)?.*$", re.M)
for idx, match in enumerate(pattern.finditer(output)): for idx, match in enumerate(pattern.finditer(output)):
screen = DisplayProperty() screen = Display()
screen.name = match.group(1) screen.name = match.group(1)
if (self.virt_idx is None) and (screen.name == self.DEFAULT_VIRT_SCREEN): if (self.virt_idx is None) and (screen.name == self.DEFAULT_VIRT_SCREEN):
self.virt_idx = idx self.virt_idx = idx
@ -304,11 +315,11 @@ class XRandR(SubprocessWrapper):
self.delete_virtual_screen() self.delete_virtual_screen()
os._exit(0) os._exit(0)
def get_primary_screen(self) -> DisplayProperty: def get_primary_screen(self) -> Display:
self._update_screens() self._update_screens()
return self.primary return self.primary
def get_virtual_screen(self) -> DisplayProperty: def get_virtual_screen(self) -> Display:
self._update_screens() self._update_screens()
return self.virt return self.virt
@ -345,7 +356,8 @@ class Backend(QObject):
Q_ENUMS(VNCState) Q_ENUMS(VNCState)
# Virtual screen properties # Virtual screen properties
xrandr: XRandR xrandr: XRandR
_virt: DisplayProperty = DisplayProperty() _virt: Display = Display()
_virtProp: DisplayProperty
_portrait: bool _portrait: bool
_hidpi: bool _hidpi: bool
_virtScreenCreated: bool = False _virtScreenCreated: bool = False
@ -357,7 +369,7 @@ class Backend(QObject):
_vncState: VNCState _vncState: VNCState
_vncAutoStart: bool _vncAutoStart: bool
# Primary screen and mouse posistion # Primary screen and mouse posistion
primary: DisplayProperty() _primaryProp: DisplayProperty
cursor_x: int cursor_x: int
cursor_y: int cursor_y: int
vncServer: ProcessProtocol vncServer: ProcessProtocol
@ -377,8 +389,8 @@ class Backend(QObject):
try: try:
with open(CONFIG_PATH, "r") as f: with open(CONFIG_PATH, "r") as f:
settings = json.load(f) settings = json.load(f)
self.virt.width = settings['virt']['width'] self._virt.width = settings['virt']['width']
self.virt.height = settings['virt']['height'] self._virt.height = settings['virt']['height']
self._portrait = settings['virt']['portrait'] self._portrait = settings['virt']['portrait']
self._hidpi = settings['virt']['hidpi'] self._hidpi = settings['virt']['hidpi']
self._vncPort = settings['vnc']['port'] self._vncPort = settings['vnc']['port']
@ -387,13 +399,14 @@ class Backend(QObject):
print("Default Setting used.") print("Default Setting used.")
with open(DEFAULT_CONFIG_PATH, "r") as f: with open(DEFAULT_CONFIG_PATH, "r") as f:
settings = json.load(f) settings = json.load(f)
self.virt.width = settings['virt']['width'] self._virt.width = settings['virt']['width']
self.virt.height = settings['virt']['height'] self._virt.height = settings['virt']['height']
self._portrait = settings['virt']['portrait'] self._portrait = settings['virt']['portrait']
self._hidpi = settings['virt']['hidpi'] self._hidpi = settings['virt']['hidpi']
self._vncPort = settings['vnc']['port'] self._vncPort = settings['vnc']['port']
self._vncAutoStart = settings['vnc']['autostart'] self._vncAutoStart = settings['vnc']['autostart']
# create objects # create objects
self._virtProp = DisplayProperty(self._virt)
self._vncState = self.VNCState.OFF self._vncState = self.VNCState.OFF
self.xrandr = XRandR() self.xrandr = XRandR()
self._virtScreenIndex = self.xrandr.virt_idx self._virtScreenIndex = self.xrandr.virt_idx
@ -401,10 +414,10 @@ class Backend(QObject):
# Qt properties # Qt properties
@pyqtProperty(DisplayProperty) @pyqtProperty(DisplayProperty)
def virt(self): def virt(self):
return self._virt return self._virtProp
@virt.setter @virt.setter
def virt(self, virt): def virt(self, virt):
self._virt = virt self._virtProp = virt
@pyqtProperty(bool) @pyqtProperty(bool)
def portrait(self): def portrait(self):
@ -430,7 +443,7 @@ class Backend(QObject):
@pyqtProperty(QQmlListProperty) @pyqtProperty(QQmlListProperty)
def screens(self): 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) @pyqtProperty(int, notify=onVirtScreenIndexChanged)
def virtScreenIndex(self): def virtScreenIndex(self):
@ -492,7 +505,8 @@ class Backend(QObject):
@pyqtProperty(DisplayProperty) @pyqtProperty(DisplayProperty)
def primary(self): def primary(self):
return self.xrandr.get_primary_screen() self._primaryProp = DisplayProperty(self.xrandr.get_primary_screen())
return self._primaryProp
@pyqtProperty(int) @pyqtProperty(int)
def cursor_x(self): def cursor_x(self):
@ -508,7 +522,7 @@ class Backend(QObject):
@pyqtSlot() @pyqtSlot()
def createVirtScreen(self): def createVirtScreen(self):
print("Creating a Virtual Screen...") 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 self.virtScreenCreated = True
@pyqtSlot() @pyqtSlot()
@ -613,8 +627,8 @@ class Backend(QObject):
with open(CONFIG_PATH, 'w') as f: with open(CONFIG_PATH, 'w') as f:
settings = {} settings = {}
settings['virt'] = {} settings['virt'] = {}
settings['virt']['width'] = self.virt.width settings['virt']['width'] = self._virt.width
settings['virt']['height'] = self.virt.height settings['virt']['height'] = self._virt.height
settings['virt']['portrait'] = self._portrait settings['virt']['portrait'] = self._portrait
settings['virt']['hidpi'] = self._hidpi settings['virt']['hidpi'] = self._hidpi
settings['vnc'] = {} settings['vnc'] = {}