aboutsummaryrefslogtreecommitdiff
path: root/rwa-support-desktopapp
diff options
context:
space:
mode:
Diffstat (limited to 'rwa-support-desktopapp')
-rw-r--r--rwa-support-desktopapp/images/clipboard.png.url1
-rw-r--r--rwa-support-desktopapp/images/into-clipboard.svg1
-rw-r--r--rwa-support-desktopapp/locales/de_DE.qmbin0 -> 17771 bytes
-rw-r--r--rwa-support-desktopapp/locales/de_DE.ts407
-rw-r--r--rwa-support-desktopapp/locales/es_ES.qmbin0 -> 723 bytes
-rw-r--r--rwa-support-desktopapp/locales/es_ES.ts405
-rw-r--r--rwa-support-desktopapp/locales/main_en.qmbin0 -> 1078 bytes
-rw-r--r--rwa-support-desktopapp/locales/main_en.ts405
-rw-r--r--rwa-support-desktopapp/qml.qrc39
-rw-r--r--rwa-support-desktopapp/qtquickcontrols2.conf30
-rw-r--r--rwa-support-desktopapp/rwa-support-desktopapp.pro101
-rw-r--r--rwa-support-desktopapp/src/DBusAPI.cpp423
-rw-r--r--rwa-support-desktopapp/src/DBusAPI.h87
-rw-r--r--rwa-support-desktopapp/src/ListItem.qml45
-rw-r--r--rwa-support-desktopapp/src/RWADBusAdaptor.cpp64
-rw-r--r--rwa-support-desktopapp/src/RWADBusAdaptor.h151
-rw-r--r--rwa-support-desktopapp/src/RWAHost.cpp68
-rw-r--r--rwa-support-desktopapp/src/RWAHost.h63
-rw-r--r--rwa-support-desktopapp/src/RWAHostModel.cpp37
-rw-r--r--rwa-support-desktopapp/src/RWAHostModel.h23
-rw-r--r--rwa-support-desktopapp/src/Toast.qml158
-rw-r--r--rwa-support-desktopapp/src/ToastManager.qml89
-rw-r--r--rwa-support-desktopapp/src/main.cpp141
-rw-r--r--rwa-support-desktopapp/src/main.qml418
-rw-r--r--rwa-support-desktopapp/src/main_qmladaptor.cpp344
-rw-r--r--rwa-support-desktopapp/src/main_qmladaptor.h144
-rw-r--r--rwa-support-desktopapp/src/scenes/Scene_no_server_available.qml71
-rw-r--r--rwa-support-desktopapp/src/scenes/Scene_placeholder.qml56
-rw-r--r--rwa-support-desktopapp/src/scenes/Scene_remote_view.qml371
-rw-r--r--rwa-support-desktopapp/src/scenes/Scene_settings.qml34
-rw-r--r--rwa-support-desktopapp/src/scenes/add_rwahost_wizard/Scene_step_1.qml282
-rw-r--r--rwa-support-desktopapp/src/scenes/add_rwahost_wizard/add_rwahost_wizard.cpp124
-rw-r--r--rwa-support-desktopapp/src/scenes/add_rwahost_wizard/add_rwahost_wizard.h61
-rw-r--r--rwa-support-desktopapp/src/scenes/remote_control/Scene_remote_control.qml382
-rw-r--r--rwa-support-desktopapp/src/scenes/remote_control/remote_control_manager.cpp511
-rw-r--r--rwa-support-desktopapp/src/scenes/remote_control/remote_control_manager.h78
-rw-r--r--rwa-support-desktopapp/src/session.cpp271
-rw-r--r--rwa-support-desktopapp/src/session.h104
-rwxr-xr-xrwa-support-desktopapp/update_locales.sh29
39 files changed, 6018 insertions, 0 deletions
diff --git a/rwa-support-desktopapp/images/clipboard.png.url b/rwa-support-desktopapp/images/clipboard.png.url
new file mode 100644
index 0000000..a023a15
--- /dev/null
+++ b/rwa-support-desktopapp/images/clipboard.png.url
@@ -0,0 +1 @@
+https://iconmonstr.com/clipboard-13-svg/
diff --git a/rwa-support-desktopapp/images/into-clipboard.svg b/rwa-support-desktopapp/images/into-clipboard.svg
new file mode 100644
index 0000000..ca9020e
--- /dev/null
+++ b/rwa-support-desktopapp/images/into-clipboard.svg
@@ -0,0 +1 @@
+<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd"><path d="M20 24h-20v-22h3c1.229 0 2.18-1.084 3-2h8c.82.916 1.771 2 3 2h3v9h-2v-7h-4l-2 2h-3.898l-2.102-2h-4v18h16v-5h2v7zm-10-4h-6v-1h6v1zm0-2h-6v-1h6v1zm6-5h8v2h-8v3l-5-4 5-4v3zm-6 3h-6v-1h6v1zm0-2h-6v-1h6v1zm0-2h-6v-1h6v1zm0-2h-6v-1h6v1zm-1-7c0 .552.448 1 1 1s1-.448 1-1-.448-1-1-1-1 .448-1 1z"/></svg> \ No newline at end of file
diff --git a/rwa-support-desktopapp/locales/de_DE.qm b/rwa-support-desktopapp/locales/de_DE.qm
new file mode 100644
index 0000000..453dcd4
--- /dev/null
+++ b/rwa-support-desktopapp/locales/de_DE.qm
Binary files differ
diff --git a/rwa-support-desktopapp/locales/de_DE.ts b/rwa-support-desktopapp/locales/de_DE.ts
new file mode 100644
index 0000000..1582fcc
--- /dev/null
+++ b/rwa-support-desktopapp/locales/de_DE.ts
@@ -0,0 +1,407 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+ <name>AddRWAHostWizard</name>
+ <message>
+ <source>Couldn&apos;t connect to the specified host!</source>
+ <translation>Konnte nicht zum Server verbinden!</translation>
+ </message>
+ <message>
+ <source>The specified host was already added!</source>
+ <translation>Der angegebene RWA-Server wurde bereits hinzugefügt!</translation>
+ </message>
+ <message>
+ <source>The specified host address is not valid!</source>
+ <translation>Die angegebene Serveradresse enthält einen Fehler!</translation>
+ </message>
+ <message>
+ <source>The features you expected here are not yet implemented.</source>
+ <translation>Die Features die Sie erwartet hatten, sind noch nicht implementiert worden.</translation>
+ </message>
+ <message>
+ <source>An error occured while adding a new host!</source>
+ <translation>Ein Fehler trat auf beim Hinzufügen eines RWA-Servers!</translation>
+ </message>
+ <message>
+ <source>Both textfields can&apos;t be empty!</source>
+ <translation>Beide Felder müssen ausgefüllt werden!</translation>
+ </message>
+ <message>
+ <source>The specified host address does not grant access!</source>
+ <translation>Der angegebene Server gewährt keinen Zugang!</translation>
+ </message>
+ <message>
+ <source>The specified host address is not supported!</source>
+ <translation>Der angegebene Server wird nicht unterstützt!</translation>
+ </message>
+ <message>
+ <source>Can&apos;t connect to underlying session service!</source>
+ <translation>Es kann keine Verbindung zur Sitzungsverwaltung hergestellt werden!</translation>
+ </message>
+</context>
+<context>
+ <name>MainQMLAdaptor</name>
+ <message>
+ <source>A host object in the response of D-Bus service lacks a necessary value. (host_url or host_uuid)</source>
+ <translation>Ein Serverobject in der D-Bus Serviceantwort beeinhaltet nicht alle erforderlichen werte. (host_url oder host_uuid)</translation>
+ </message>
+ <message>
+ <source>An error occured while adding a new host:</source>
+ <translation>Ein Fehler trat auf beim Hinzufügen eines RWA-Servers:</translation>
+ </message>
+ <message>
+ <source>Successfully added new RWAHost &apos;%0&apos;</source>
+ <translation>Erfolgreich RWA-Server &apos;%0&apos; hinzugefügt</translation>
+ </message>
+ <message>
+ <source>The error is not clear. The session service responded with status type &apos;%0&apos;</source>
+ <translation>Der Fehler ist nicht klar. Der Sessionservice hat mit Statustype &apos;%0&apos; geantwortet</translation>
+ </message>
+ <message>
+ <source>The error is not clear. The session service responded with no status type!</source>
+ <translation>Der Fehler ist nicht klar. Der Sessionservice hat mit keinem Statustypen geantwortet!</translation>
+ </message>
+ <message>
+ <source>No RWA host available!</source>
+ <translation>Kein RWA-Server verfügbar!</translation>
+ </message>
+ <message>
+ <source>Can&apos;t connect to underlying session service!</source>
+ <translation>Es kann keine Verbindung zur Sitzungsverwaltung hergestellt werden!</translation>
+ </message>
+ <message>
+ <source>Removed RWAHost &apos;%0&apos;</source>
+ <translation>RWA-Server &apos;%0&apos; entfernt</translation>
+ </message>
+</context>
+<context>
+ <name>RemoteControlManager</name>
+ <message>
+ <source>Stop remote support session</source>
+ <translation>Stoppe Fernwartungssitzung</translation>
+ </message>
+ <message>
+ <source>Start remote support session</source>
+ <translation>Starte Fernwartungssitzung</translation>
+ </message>
+ <message>
+ <source>Not available yet</source>
+ <translation>Noch nicht verfügbar</translation>
+ </message>
+ <message>
+ <source>Unknown state of service</source>
+ <translation>Unbekannter Status des Dienstes</translation>
+ </message>
+ <message>
+ <source>Remote Support session was stopped ungracefully</source>
+ <translation>Fernwartungssitzung wurde unerwartet beendet</translation>
+ </message>
+ <message>
+ <source>Remote Support session was stopped</source>
+ <translation>Fernwartungssitzung wurde beendet</translation>
+ </message>
+ <message>
+ <source>Your partner is connected to the Remote Support session</source>
+ <translation>Fernwartungspartner ist mit dieser Sitzung verbunden</translation>
+ </message>
+ <message>
+ <source>Remote Support session couldn&apos;t be started!</source>
+ <translation>Fernwartungssitzung konnte nicht gestartet werden!</translation>
+ </message>
+ <message>
+ <source>Session was started on &apos;%0&apos; successfully</source>
+ <translation>Sitzung wurde erfolgreich auf &apos;%0&apos; gestartet</translation>
+ </message>
+ <message>
+ <source>remote support partner could still be connected!</source>
+ <translation>Es ist möglich, dass Ihr Fernwartungspartner noch verbunden ist!</translation>
+ </message>
+ <message>
+ <source>Session could not be stopped!</source>
+ <translation>Sitzung konnte nicht gestoppt werden!</translation>
+ </message>
+ <message>
+ <source>Session status could not be refreshed! Your remote support partner could still be connected!</source>
+ <translation>Sitzungstatus konnte nicht erneuert werden! Es ist möglich, dass der Fernwartungspartner noch verbunden ist!</translation>
+ </message>
+ <message>
+ <source>Couldn&apos;t remove current session out of &apos;_sessions&apos; list.</source>
+ <translation>Konnte aktuelle Sitzung nicht aus der Liste &apos;_sessions&apos; löschen.</translation>
+ </message>
+ <message>
+ <source>currentSessionStartSucceeded(): Current Session is nullptr!</source>
+ <translation>currentSessionStartSucceeded(): Aktuelle Sitzung ist nullptr!</translation>
+ </message>
+ <message>
+ <source>Remote support session was stopped.</source>
+ <translation>Fernwartungssitzung wurde beendet.</translation>
+ </message>
+ <message>
+ <source>Remote Support session successfully started! Waiting for your remote support partner to connect.</source>
+ <translation>Fernwartungssitzung wurde erfolgreich gestartet! Warte auf auf Verbindung vom Fernwartungspartner.</translation>
+ </message>
+ <message>
+ <source>Session stopped successfully.</source>
+ <translation>Fernwartungssitzung wurde erfolgreich beendet.</translation>
+ </message>
+ <message>
+ <source>Creating a new session object.</source>
+ <translation>Erstelle neues Sitzungsobjekt.</translation>
+ </message>
+ <message>
+ <source>Can&apos;t start a remote support session. There is no RWA host is selected!</source>
+ <translation>Kann keine Fernwartungssitzung starten. Es ist kein RWA-Server ausgewählt!</translation>
+ </message>
+ <message>
+ <source>RemoteControlManager::handleConnectButtonClick(): Current Session is nullptr!</source>
+ <translation>RemoteControlManager::handleConnectButtonClick(): Aktuelle Sitzung ist nullptr!</translation>
+ </message>
+ <message>
+ <source>Starting a remote support session on host &apos;%0&apos; using the new session object.</source>
+ <translation>Starte eine Fernwartungssitzung auf Server &apos;%0&apos; mithilfe des neuen Sitzungsobjektes.</translation>
+ </message>
+</context>
+<context>
+ <name>Scene_no_server_available</name>
+ <message>
+ <source>Welcome!</source>
+ <translation>Willkommen!</translation>
+ </message>
+ <message>
+ <source>You need to add and select the remote web app server to which you want to connect. You can see a button to the left in the menu which says &apos;Add RWA-Server&apos;. Follow the steps listed there and you can start your remote support session afterwards using the buttons above &apos;Add RWA-Server&apos;.</source>
+ <translation>Sie müssen zuerst einen RWA-Server hinzufügen und dann auswählen, um sich mit diesen zu verbinden. Auf der linken Seite befindet sich ein Knopf mit der Aufschrift &apos;RWA-Server hinzufügen&apos;. Folgen Sie den Anweisungen dort und Sie können Ihre Fernwartungssitzung rasch starten.</translation>
+ </message>
+</context>
+<context>
+ <name>Scene_placeholder</name>
+ <message>
+ <source>This is the placeholder scene!</source>
+ <translation>Das hier ist eine Platzhalter Szene!</translation>
+ </message>
+ <message>
+ <source>The features you expected here are not yet implemented.</source>
+ <translation>Die Features die Sie erwartet hatten, sind noch nicht implementiert worden.</translation>
+ </message>
+</context>
+<context>
+ <name>Scene_remote_control</name>
+ <message>
+ <source>Please tell your remote support partner your access address and your access-PIN to let your partner connect to this computer.</source>
+ <translation>Bitte teile Ihrem Fernwartungspartner Ihre Zugangsadresse und Ihre Zugangs-PIN mit, damit sich dieser mit diesem Computer verbinden kann.</translation>
+ </message>
+ <message>
+ <source>Remote Support Address</source>
+ <translation>Zugangsadresse für Fernwartung</translation>
+ </message>
+ <message>
+ <source>Copied access address into clipboard!</source>
+ <translation>Zugangsadresse wurde in die Zwischenablage kopiert!</translation>
+ </message>
+ <message>
+ <source>Copy the access address into the clipboard</source>
+ <translation>Kopiere Zugangsadresse in die Zwischenablage</translation>
+ </message>
+ <message>
+ <source>Session-ID</source>
+ <translation>Sitzungs-ID</translation>
+ </message>
+ <message>
+ <source>Copied session-ID into clipboard!</source>
+ <translation>Sitzungs-ID wurde in die Zwischenablage kopiert!</translation>
+ </message>
+ <message>
+ <source>Copy the session-ID into the clipboard</source>
+ <translation>Kopiere Sitzungs-ID in die Zwischenablage</translation>
+ </message>
+ <message>
+ <source>Access-PIN</source>
+ <translation>Zugangs-PIN</translation>
+ </message>
+ <message>
+ <source>Copied PIN into clipboard!</source>
+ <translation>Zugangs-PIN wurde in die Zwischenablage kopiert!</translation>
+ </message>
+ <message>
+ <source>Copy the pin into the clipboard</source>
+ <translation>Kopiere Zugangspin in die Zwischenablage</translation>
+ </message>
+ <message>
+ <source>Start remote support session</source>
+ <translation>Starte Fernwartungssitzung</translation>
+ </message>
+ <message>
+ <source>Stop remote support session</source>
+ <translation>Stoppe Fernwartungssitzung</translation>
+ </message>
+ <message>
+ <source>Unknown state of session service.</source>
+ <translation>Unbekannter Status der Sitzungverwaltung.</translation>
+ </message>
+</context>
+<context>
+ <name>Scene_remote_view</name>
+ <message>
+ <source>Please tell your remote support partner your access address and your access-PIN to let your partner connect to this computer.</source>
+ <translation>Bitte teile Ihrem Fernwartungspartner Ihre Zugangsadresse und Ihre Zugangs-PIN mit, damit sich dieser mit diesem Computer verbinden kann.</translation>
+ </message>
+ <message>
+ <source>Remote viewing Address</source>
+ <translation>Zugangsadresse für Zuschauer</translation>
+ </message>
+ <message>
+ <source>Copied access address into clipboard!</source>
+ <translation>Zugangsadresse wurde in die Zwischenablage kopiert!</translation>
+ </message>
+ <message>
+ <source>Copy the access address into the clipboard</source>
+ <translation>Kopiere Zugangsadresse in die Zwischenablage</translation>
+ </message>
+ <message>
+ <source>Session-ID</source>
+ <translation>Sitzungs-ID</translation>
+ </message>
+ <message>
+ <source>Copied session-ID into clipboard!</source>
+ <translation>Sitzungs-ID wurde in die Zwischenablage kopiert!</translation>
+ </message>
+ <message>
+ <source>Copy the session-ID into the clipboard</source>
+ <translation>Kopiere Sitzungs-ID in die Zwischenablage</translation>
+ </message>
+ <message>
+ <source>Access-PIN</source>
+ <translation>Zugangs-PIN</translation>
+ </message>
+ <message>
+ <source>Copied PIN into clipboard!</source>
+ <translation>Zugangspin wurde in die Zwischenablage kopiert!</translation>
+ </message>
+ <message>
+ <source>Copy the pin into the clipboard</source>
+ <translation>Kopiere Zugangspin in die Zwischenablage</translation>
+ </message>
+ <message>
+ <source>Start remote viewing session</source>
+ <translation>Starte Zuschauersitzung</translation>
+ </message>
+</context>
+<context>
+ <name>Scene_step_1</name>
+ <message>
+ <source>Next Step</source>
+ <translation>Nächster Schritt</translation>
+ </message>
+ <message>
+ <source>Please input the address for the remote web app server which you want to connect to.
+If you don&apos;t know what this means, ask your local administrator about it please.
+Before you can start any remote sessions you will have to be approved for remote support.</source>
+ <translation>Bitte gebe hier die Adresse für den RWA-Server ein, zudem Sie sich verbinden wollen.
+Kontaktieren Sie bitte ihren lokalen Administrator bei Fragen.
+Bevor Sie eine Fernwartungssitzung starten können, müssen Sie zuerst für das Fernwarten angenommen werden.</translation>
+ </message>
+ <message>
+ <source>http://example.com:8000</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Successfully added remote web app host.</source>
+ <translation>Erfolgreich Fernwartungsserver hinzugefügt.</translation>
+ </message>
+ <message>
+ <source>RWA host address</source>
+ <translation>RWA-Server Adressse</translation>
+ </message>
+ <message>
+ <source>My example host</source>
+ <translation>Mein Beispiel Server</translation>
+ </message>
+ <message>
+ <source>RWA host alias</source>
+ <translation>RWA-Server Alias</translation>
+ </message>
+</context>
+<context>
+ <name>Session</name>
+ <message>
+ <source>Not available yet</source>
+ <translation>Noch nicht verfügbar</translation>
+ </message>
+ <message>
+ <source>An error occured while creating a new session!</source>
+ <translation>Ein Fehler trat auf bei dem Versuch eine Sitzung zu erstellen!</translation>
+ </message>
+ <message>
+ <source>The session service is configured to not support multiple sessions and there is already a session running.</source>
+ <translation>Die Sitzungsverwaltung ist so konfiguriert, dass nur eine Sitzung erlaubt ist und es ist bereits eine Sitzung am laufen.</translation>
+ </message>
+ <message>
+ <source>Couldn&apos;t connect to host &apos;%0&apos;.</source>
+ <translation>Konnte nicht zu RWA-Server &apos;%0&apos; verbinden.</translation>
+ </message>
+ <message>
+ <source>The RWA host &apos;%0&apos; couldn&apos;t be found.</source>
+ <translation>Konnte RWA-Server &apos;%0&apos; nicht finden.</translation>
+ </message>
+ <message>
+ <source>The RWA host &apos;%0&apos; doesn&apos;t grant access.</source>
+ <translation>Der RWA-Server &apos;%0&apos; gewährt keinen Zugang.</translation>
+ </message>
+ <message>
+ <source>The RWA host &apos;%0&apos; is not supported.</source>
+ <translation>Der RWA-Server &apos;%0&apos; wird nicht unterstützt.</translation>
+ </message>
+ <message>
+ <source>Can&apos;t connect to underlying session service! Is the session service started?</source>
+ <translation>Es kann keine Verbindung zur Sitzungsverwaltung hergestellt werden! Ist die Sitzungsverwaltung eingeschaltet?</translation>
+ </message>
+</context>
+<context>
+ <name>main</name>
+ <message>
+ <source>Remote Support for your Desktop</source>
+ <translation>Fernwartung für den Desktop</translation>
+ </message>
+ <message>
+ <source>Allow Remote Control</source>
+ <translation>Fernwartungssitzung erlauben</translation>
+ </message>
+ <message>
+ <source>Remote Control</source>
+ <translation>Fernwartung</translation>
+ </message>
+ <message>
+ <source>Remote View</source>
+ <translation>Fernbeobachtung</translation>
+ </message>
+ <message>
+ <source>Settings</source>
+ <translation>Einstellungen</translation>
+ </message>
+ <message>
+ <source>You are not supposed to see this message.
+This is a bug.</source>
+ <translation>Diese Nachricht sollte nie gesehen werden. Das ist ein Bug.</translation>
+ </message>
+ <message>
+ <source>Dark theme</source>
+ <translation>Dunkelmodus</translation>
+ </message>
+ <message>
+ <source>Allow remote control</source>
+ <translation>Fernwartungssitzung erlauben</translation>
+ </message>
+ <message>
+ <source>Allow remote view</source>
+ <translation>Zuschauersitzung erlauben</translation>
+ </message>
+ <message>
+ <source>Add RWA-Server</source>
+ <translation>RWA-Server hinzufügen</translation>
+ </message>
+ <message>
+ <source>Server addition wizard</source>
+ <translation>Server hinzufügen</translation>
+ </message>
+</context>
+</TS>
diff --git a/rwa-support-desktopapp/locales/es_ES.qm b/rwa-support-desktopapp/locales/es_ES.qm
new file mode 100644
index 0000000..2d4045c
--- /dev/null
+++ b/rwa-support-desktopapp/locales/es_ES.qm
Binary files differ
diff --git a/rwa-support-desktopapp/locales/es_ES.ts b/rwa-support-desktopapp/locales/es_ES.ts
new file mode 100644
index 0000000..f69359e
--- /dev/null
+++ b/rwa-support-desktopapp/locales/es_ES.ts
@@ -0,0 +1,405 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="es_ES">
+<context>
+ <name>AddRWAHostWizard</name>
+ <message>
+ <source>Couldn&apos;t connect to the specified host!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The specified host was already added!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The specified host address is not valid!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The features you expected here are not yet implemented.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>An error occured while adding a new host!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Both textfields can&apos;t be empty!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The specified host address does not grant access!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The specified host address is not supported!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Can&apos;t connect to underlying session service!</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>MainQMLAdaptor</name>
+ <message>
+ <source>A host object in the response of D-Bus service lacks a necessary value. (host_url or host_uuid)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>An error occured while adding a new host:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Successfully added new RWAHost &apos;%0&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The error is not clear. The session service responded with status type &apos;%0&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The error is not clear. The session service responded with no status type!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>No RWA host available!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Can&apos;t connect to underlying session service!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Removed RWAHost &apos;%0&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>RemoteControlManager</name>
+ <message>
+ <source>Stop remote support session</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Start remote support session</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Not available yet</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unknown state of service</source>
+ <translation>Estado de servicio desconocido</translation>
+ </message>
+ <message>
+ <source>Remote Support session was stopped ungracefully</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Remote Support session was stopped</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Your partner is connected to the Remote Support session</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Remote Support session couldn&apos;t be started!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Session was started on &apos;%0&apos; successfully</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>remote support partner could still be connected!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Session could not be stopped!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Session status could not be refreshed! Your remote support partner could still be connected!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Couldn&apos;t remove current session out of &apos;_sessions&apos; list.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>currentSessionStartSucceeded(): Current Session is nullptr!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Remote support session was stopped.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Remote Support session successfully started! Waiting for your remote support partner to connect.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Session stopped successfully.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Creating a new session object.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Can&apos;t start a remote support session. There is no RWA host is selected!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>RemoteControlManager::handleConnectButtonClick(): Current Session is nullptr!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Starting a remote support session on host &apos;%0&apos; using the new session object.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Scene_no_server_available</name>
+ <message>
+ <source>Welcome!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>You need to add and select the remote web app server to which you want to connect. You can see a button to the left in the menu which says &apos;Add RWA-Server&apos;. Follow the steps listed there and you can start your remote support session afterwards using the buttons above &apos;Add RWA-Server&apos;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Scene_placeholder</name>
+ <message>
+ <source>This is the placeholder scene!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The features you expected here are not yet implemented.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Scene_remote_control</name>
+ <message>
+ <source>Remote Support Address</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copied access address into clipboard!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copy the access address into the clipboard</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Session-ID</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copied session-ID into clipboard!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copy the session-ID into the clipboard</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Access-PIN</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copied PIN into clipboard!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copy the pin into the clipboard</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Start remote support session</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Stop remote support session</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Please tell your remote support partner your access address and your access-PIN to let your partner connect to this computer.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unknown state of session service.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Scene_remote_view</name>
+ <message>
+ <source>Please tell your remote support partner your access address and your access-PIN to let your partner connect to this computer.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Remote viewing Address</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copied access address into clipboard!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copy the access address into the clipboard</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Session-ID</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copied session-ID into clipboard!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copy the session-ID into the clipboard</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Access-PIN</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copied PIN into clipboard!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copy the pin into the clipboard</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Start remote viewing session</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Scene_step_1</name>
+ <message>
+ <source>Next Step</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Please input the address for the remote web app server which you want to connect to.
+If you don&apos;t know what this means, ask your local administrator about it please.
+Before you can start any remote sessions you will have to be approved for remote support.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>http://example.com:8000</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Successfully added remote web app host.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>RWA host address</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>My example host</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>RWA host alias</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Session</name>
+ <message>
+ <source>Not available yet</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>An error occured while creating a new session!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The session service is configured to not support multiple sessions and there is already a session running.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Couldn&apos;t connect to host &apos;%0&apos;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The RWA host &apos;%0&apos; couldn&apos;t be found.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The RWA host &apos;%0&apos; doesn&apos;t grant access.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The RWA host &apos;%0&apos; is not supported.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Can&apos;t connect to underlying session service! Is the session service started?</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>main</name>
+ <message>
+ <source>Remote Support for your Desktop</source>
+ <translation>Soporte remoto para su escritorio</translation>
+ </message>
+ <message>
+ <source>You are not supposed to see this message.
+This is a bug.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Allow Remote Control</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Dark theme</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Allow remote control</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Allow remote view</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Remote Control</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Remote View</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Add RWA-Server</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Server addition wizard</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/rwa-support-desktopapp/locales/main_en.qm b/rwa-support-desktopapp/locales/main_en.qm
new file mode 100644
index 0000000..a0de2c8
--- /dev/null
+++ b/rwa-support-desktopapp/locales/main_en.qm
Binary files differ
diff --git a/rwa-support-desktopapp/locales/main_en.ts b/rwa-support-desktopapp/locales/main_en.ts
new file mode 100644
index 0000000..7e033d1
--- /dev/null
+++ b/rwa-support-desktopapp/locales/main_en.ts
@@ -0,0 +1,405 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="en_US">
+<context>
+ <name>AddRWAHostWizard</name>
+ <message>
+ <source>Couldn&apos;t connect to the specified host!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The specified host was already added!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The specified host address is not valid!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The features you expected here are not yet implemented.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>An error occured while adding a new host!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Both textfields can&apos;t be empty!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The specified host address does not grant access!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The specified host address is not supported!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Can&apos;t connect to underlying session service!</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>MainQMLAdaptor</name>
+ <message>
+ <source>A host object in the response of D-Bus service lacks a necessary value. (host_url or host_uuid)</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>An error occured while adding a new host:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Successfully added new RWAHost &apos;%0&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The error is not clear. The session service responded with status type &apos;%0&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The error is not clear. The session service responded with no status type!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>No RWA host available!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Can&apos;t connect to underlying session service!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Removed RWAHost &apos;%0&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>RemoteControlManager</name>
+ <message>
+ <source>Stop remote support session</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Start remote support session</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Not available yet</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unknown state of service</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Remote Support session was stopped ungracefully</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Remote Support session was stopped</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Your partner is connected to the Remote Support session</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Remote Support session couldn&apos;t be started!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Session was started on &apos;%0&apos; successfully</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>remote support partner could still be connected!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Session could not be stopped!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Session status could not be refreshed! Your remote support partner could still be connected!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Couldn&apos;t remove current session out of &apos;_sessions&apos; list.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>currentSessionStartSucceeded(): Current Session is nullptr!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Remote support session was stopped.</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Remote Support session successfully started! Waiting for your remote support partner to connect.</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Session stopped successfully.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Creating a new session object.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Can&apos;t start a remote support session. There is no RWA host is selected!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>RemoteControlManager::handleConnectButtonClick(): Current Session is nullptr!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Starting a remote support session on host &apos;%0&apos; using the new session object.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Scene_no_server_available</name>
+ <message>
+ <source>Welcome!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>You need to add and select the remote web app server to which you want to connect. You can see a button to the left in the menu which says &apos;Add RWA-Server&apos;. Follow the steps listed there and you can start your remote support session afterwards using the buttons above &apos;Add RWA-Server&apos;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Scene_placeholder</name>
+ <message>
+ <source>This is the placeholder scene!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The features you expected here are not yet implemented.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Scene_remote_control</name>
+ <message>
+ <source>Remote Support Address</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copied access address into clipboard!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copy the access address into the clipboard</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Session-ID</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copied session-ID into clipboard!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copy the session-ID into the clipboard</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Access-PIN</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copied PIN into clipboard!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copy the pin into the clipboard</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Start remote support session</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Stop remote support session</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Please tell your remote support partner your access address and your access-PIN to let your partner connect to this computer.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unknown state of session service.</source>
+ <translation></translation>
+ </message>
+</context>
+<context>
+ <name>Scene_remote_view</name>
+ <message>
+ <source>Please tell your remote support partner your access address and your access-PIN to let your partner connect to this computer.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Remote viewing Address</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copied access address into clipboard!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copy the access address into the clipboard</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Session-ID</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copied session-ID into clipboard!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copy the session-ID into the clipboard</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Access-PIN</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copied PIN into clipboard!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Copy the pin into the clipboard</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Start remote viewing session</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Scene_step_1</name>
+ <message>
+ <source>Next Step</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Please input the address for the remote web app server which you want to connect to.
+If you don&apos;t know what this means, ask your local administrator about it please.
+Before you can start any remote sessions you will have to be approved for remote support.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>http://example.com:8000</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Successfully added remote web app host.</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>RWA host address</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>My example host</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>RWA host alias</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Session</name>
+ <message>
+ <source>Not available yet</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>An error occured while creating a new session!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The session service is configured to not support multiple sessions and there is already a session running.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Couldn&apos;t connect to host &apos;%0&apos;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The RWA host &apos;%0&apos; couldn&apos;t be found.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The RWA host &apos;%0&apos; doesn&apos;t grant access.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>The RWA host &apos;%0&apos; is not supported.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Can&apos;t connect to underlying session service! Is the session service started?</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>main</name>
+ <message>
+ <source>Remote Support for your Desktop</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>You are not supposed to see this message.
+This is a bug.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Allow Remote Control</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Dark theme</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Allow remote control</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Allow remote view</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Remote Control</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Remote View</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Add RWA-Server</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Server addition wizard</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/rwa-support-desktopapp/qml.qrc b/rwa-support-desktopapp/qml.qrc
new file mode 100644
index 0000000..04ee201
--- /dev/null
+++ b/rwa-support-desktopapp/qml.qrc
@@ -0,0 +1,39 @@
+<RCC>
+ <qresource prefix="/">
+ <file>qtquickcontrols2.conf</file>
+ <file>images/into-clipboard.svg</file>
+ <file>locales/de_DE.ts</file>
+ <file>locales/de_DE.qm</file>
+ <file>locales/main_en.ts</file>
+ <file>locales/main_en.qm</file>
+ <file>locales/es_ES.ts</file>
+ <file>locales/es_ES.qm</file>
+ <file>src/main.qml</file>
+ <file>src/ListItem.qml</file>
+ <file>src/ToastManager.qml</file>
+ <file>src/Toast.qml</file>
+ <file>src/main.cpp</file>
+ <file>src/main_qmladaptor.cpp</file>
+ <file>src/main_qmladaptor.h</file>
+ <file>src/RWADBusAdaptor.cpp</file>
+ <file>src/RWADBusAdaptor.h</file>
+ <file>src/session.cpp</file>
+ <file>src/session.h</file>
+ <file>src/RWAHost.h</file>
+ <file>src/RWAHost.cpp</file>
+ <file>src/RWAHostModel.h</file>
+ <file>src/RWAHostModel.cpp</file>
+ <file>src/DBusAPI.h</file>
+ <file>src/DBusAPI.cpp</file>
+ <file>src/scenes/Scene_remote_view.qml</file>
+ <file>src/scenes/Scene_settings.qml</file>
+ <file>src/scenes/Scene_placeholder.qml</file>
+ <file>src/scenes/Scene_no_server_available.qml</file>
+ <file>src/scenes/add_rwahost_wizard/add_rwahost_wizard.cpp</file>
+ <file>src/scenes/add_rwahost_wizard/add_rwahost_wizard.h</file>
+ <file>src/scenes/add_rwahost_wizard/Scene_step_1.qml</file>
+ <file>src/scenes/remote_control/remote_control_manager.cpp</file>
+ <file>src/scenes/remote_control/remote_control_manager.h</file>
+ <file>src/scenes/remote_control/Scene_remote_control.qml</file>
+ </qresource>
+</RCC>
diff --git a/rwa-support-desktopapp/qtquickcontrols2.conf b/rwa-support-desktopapp/qtquickcontrols2.conf
new file mode 100644
index 0000000..8bcc85b
--- /dev/null
+++ b/rwa-support-desktopapp/qtquickcontrols2.conf
@@ -0,0 +1,30 @@
+# This file is part of Remote Support Desktop
+# https://gitlab.das-netzwerkteam.de/RemoteWebApp/rwa.support.desktopapp
+# Copyright 2020, 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+# Copyright 2020, 2021 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+[Controls]
+Style=Material
+
+[Material]
+Primary=Blue
+Accent=Blue
diff --git a/rwa-support-desktopapp/rwa-support-desktopapp.pro b/rwa-support-desktopapp/rwa-support-desktopapp.pro
new file mode 100644
index 0000000..a013742
--- /dev/null
+++ b/rwa-support-desktopapp/rwa-support-desktopapp.pro
@@ -0,0 +1,101 @@
+# This file is part of Remote Support Desktop
+# https://gitlab.das-netzwerkteam.de/RemoteWebApp/rwa.support.desktopapp
+# Copyright 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+# Copyright 2021 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+QT += quick
+QT += quickcontrols2
+QT += widgets
+
+
+QT += testlib
+CONFIG += testcase
+CONFIG += no_testcase_installs
+
+#test_conf {
+# TARGET = rwa-support-desktop-tests
+# QT += testlib
+# SOURCES += testqstring.cpp
+# HEADERS +=
+#} else {
+# SOURCES += src/main.cpp
+#}
+
+message(Building with DBUS (Freedesktop notifications) support)
+DEFINES += USE_DBUS
+QT += dbus
+
+CONFIG += c++11
+
+# The following define makes your compiler emit warnings if you use
+# any Qt feature that has been marked deprecated (the exact warnings
+# depend on your compiler). Refer to the documentation for the
+# deprecated API to know how to port your code away from it.
+DEFINES += QT_DEPRECATED_WARNINGS
+
+# You can also make your code fail to compile if it uses deprecated APIs.
+# In order to do so, uncomment the following line.
+# You can also select to disable deprecated APIs only up to a certain version of Qt.
+DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000
+# disables all the APIs deprecated before Qt 6.0.0
+
+SOURCES += src/main.cpp \
+ src/main_qmladaptor.cpp \
+ src/RWADBusAdaptor.cpp \
+ src/session.cpp \
+ src/RWAHostModel.cpp \
+ src/RWAHost.cpp \
+ src/DBusAPI.cpp \
+ src/scenes/remote_control/remote_control_manager.cpp \
+ src/scenes/add_rwahost_wizard/add_rwahost_wizard.cpp \
+
+HEADERS += src/RWADBusAdaptor.h \
+ src/main_qmladaptor.h \
+ src/RWADBusAdaptor.h \
+ src/scenes/remote_control/remote_control_manager.h \
+ src/session.h \
+ src/RWAHostModel.h \
+ src/RWAHost.h \
+ src/DBusAPI.h \
+ src/scenes/remote_control/remote_control_manager.h \
+ src/scenes/add_rwahost_wizard/add_rwahost_wizard.h
+
+TRANSLATIONS += locales/main_en.ts \
+ locales/de_DE.ts \
+ locales/es_ES.ts
+
+RESOURCES += qml.qrc
+
+# Additional import path used to resolve QML modules in Qt Creator's code model
+QML_IMPORT_PATH = src/scenes
+
+# Additional import path used to resolve QML modules just for Qt Quick Designer
+QML_DESIGNER_IMPORT_PATH =
+
+# Default rules for deployment.
+qnx: target.path = /tmp/$${TARGET}/bin
+else: unix:!android: target.path = /opt/$${TARGET}/bin
+!isEmpty(target.path): INSTALLS += target
+
+#DISTFILES +=
+
+CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT
diff --git a/rwa-support-desktopapp/src/DBusAPI.cpp b/rwa-support-desktopapp/src/DBusAPI.cpp
new file mode 100644
index 0000000..9a0f8de
--- /dev/null
+++ b/rwa-support-desktopapp/src/DBusAPI.cpp
@@ -0,0 +1,423 @@
+/*
+ * This file is part of Remote Support Desktop
+ * https://gitlab.das-netzwerkteam.de/RemoteWebApp/rwa.support.desktopapp
+ * Copyright 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "DBusAPI.h"
+
+/*!
+ * \class DBusAPI
+ * \brief The DBusAPI class provides all necessary D-Bus methods and distributes the response via signals.
+ */
+DBusAPI::DBusAPI() {
+ _initDBus();
+}
+
+/*!
+ * \brief Initializes private _dbus_rwa object.
+ */
+void DBusAPI::_initDBus() {
+ if (!QDBusConnection::sessionBus().isConnected()) {
+ qCritical() << "Cannot connect to the D-Bus session bus.";
+ }
+
+ // Create DBus object
+ _dbus_rwa = new OrgArcticaProjectRWASupportSessionServiceInterface("org.ArcticaProject.RWASupportSessionService",
+ "/RWASupportSessionService",
+ QDBusConnection::sessionBus(),
+ this->parent());
+
+ qDebug("Initialized DBus object!");
+}
+
+/*!
+ * \brief Ask the session service to start a session.
+ *
+ * \a host RWAHost object which includes all necessary information about a RWA host.
+ */
+void DBusAPI::start_request(RWAHost *host) {
+ Q_ASSERT(host != nullptr);
+
+ qDebug() << "Requesting D-Bus service to start a new session on host:" << host->alias();
+
+ // Make an asynchronous 'start' call (Response will be sent to 'start_reply')
+ QDBusPendingCall async = _dbus_rwa->asyncCall("start", host->uuid());
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this);
+
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
+ this, SLOT(start_reply(QDBusPendingCallWatcher*)));
+}
+
+/*!
+ * \brief Method gets called when a D-Bus response is ready.
+ *
+ * \a call contains the D-Bus response (session service or for example maybe just a error message).
+ *
+ * TODO: Example of json input
+ */
+void DBusAPI::start_reply(QDBusPendingCallWatcher *call) {
+ QString result = "";
+ QDBusPendingReply<QString> reply = *call;
+ if (reply.isError()) {
+ qCritical() << "D-Bus 'start' request failed, this was the reply:";
+ qCritical() << reply.error();
+
+ if (QDBusError::ServiceUnknown == reply.error().type()) {
+ qCritical() << "The session service was probably just not started!";
+ }
+
+ emit serviceStartResponse(nullptr);
+
+ return;
+ } else {
+ result = reply.argumentAt<0>();
+ }
+ call->deleteLater();
+
+ qDebug() << "Raw JSON from starting session is:" << result.toUtf8().replace('"', "");
+ QJsonDocument doc = QJsonDocument::fromJson(result.toUtf8());
+
+ emit serviceStartResponse(&doc);
+}
+
+/*!
+ * \brief Ask the session service to stop session #<session_id>.
+ *
+ * \a host RWAHost object which includes all necessary information about a RWA host.
+ *
+ * \a session_id Unique identifier for a session in a specific host.
+ */
+void DBusAPI::stop_request(RWAHost *host, QString session_id) {
+ Q_ASSERT(host != nullptr);
+ Q_ASSERT(session_id != "");
+
+ bool ok;
+ long long session_id_number = session_id.toLongLong(&ok);
+
+ // Sanity Check
+ if(ok == false){
+ qWarning().noquote() << QString("Unable to parse session_id '%0' as long long!").arg(session_id);
+ return;
+ }
+
+ qDebug().noquote() << QString("Requesting D-Bus service to stop "
+ "session #'%0' on host '%1'")
+ .arg(session_id)
+ .arg(host->alias());
+
+ // Make an asynchronous 'stop' call (Response will be sent to 'stop_reply')
+ QDBusPendingCall async = _dbus_rwa->asyncCall("stop", host->uuid(), session_id_number);
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this);
+
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
+ this, SLOT(stop_reply(QDBusPendingCallWatcher*)));
+}
+
+/*!
+ * \brief Method gets called when a D-Bus response is ready.
+ *
+ * \a call contains the D-Bus response (session service or for example maybe just a error message).
+ *
+ * TODO: Example of json input
+ */
+void DBusAPI::stop_reply(QDBusPendingCallWatcher *call) {
+ QString result = "";
+
+ QDBusPendingReply<QString> reply = *call;
+ if (reply.isError()) {
+ qCritical() << "D-Bus 'stop' request failed, this was the reply:";
+ qCritical() << reply.error();
+
+ if (QDBusError::ServiceUnknown == reply.error().type()) {
+ qCritical() << "The session service was probably just not started!";
+ }
+
+ emit serviceStopResponse(nullptr);
+
+ return;
+ } else {
+ result = reply.argumentAt<0>();
+ }
+ call->deleteLater();
+
+ qDebug() << "Raw JSON from stopping a session is:" << result.toUtf8().replace('"', "");
+ QJsonDocument doc = QJsonDocument::fromJson(result.toUtf8());
+
+ emit serviceStopResponse(&doc);
+}
+
+/*!
+ * \brief Ask the session service for status of <session_id>.
+ *
+ * \a host RWAHost object which includes all necessary information about a RWA host.
+ *
+ * \a session_id Unique identifier for a session in a specific host.
+ */
+void DBusAPI::status_request(RWAHost *host, QString session_id) {
+ Q_ASSERT(host != nullptr);
+ Q_ASSERT(session_id != "");
+
+ bool ok;
+ long long session_id_number = session_id.toLongLong(&ok);
+
+ // Sanity Check
+ if(ok == false){
+ qWarning().noquote() << QString("Unable to parse session_id '%0' as long long!").arg(session_id);
+ return;
+ }
+
+ qDebug().noquote() << QString("Requesting D-Bus service for status of session "
+ "#'%0' on host '%1'").arg(session_id).arg(host->alias());
+
+ // Make an asynchronous 'start' call (Response will be sent to 'status_reply')
+ QDBusPendingCall async = _dbus_rwa->asyncCall("status", host->uuid(), session_id_number);
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this);
+
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
+ this, SLOT(status_reply(QDBusPendingCallWatcher*)));
+}
+
+/*!
+ * \brief Forces the session service to refresh it's status of <session_id> and tell us about it then.
+ *
+ * \a host RWAHost object which includes all necessary information about a RWA host.
+ *
+ * \a session_id Unique identifier for a session in a specific host.
+ */
+void DBusAPI::refresh_status_request(RWAHost *host, QString session_id) {
+ Q_ASSERT(host != nullptr);
+ Q_ASSERT(session_id != "");
+
+ bool ok;
+ long long session_id_number = session_id.toLongLong(&ok);
+
+ // Sanity Check
+ if(ok == false){
+ qWarning().noquote() << QString("Unable to parse session_id '%0' as long long!").arg(session_id);
+ return;
+ }
+
+ qDebug().noquote() << QString("Requesting D-Bus service for refresh_status of session "
+ "#'%0' on host '%1'").arg(session_id).arg(host->alias());
+
+ // Make an asynchronous 'start' call (Response will be sent to 'status_reply')
+ QDBusPendingCall async = _dbus_rwa->asyncCall("refresh_status", host->uuid(), session_id_number);
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this);
+
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
+ this, SLOT(status_reply(QDBusPendingCallWatcher*)));
+}
+
+/*!
+ * \brief Method gets called when a D-Bus response is ready.
+ *
+ * \a call contains the D-Bus response (session service or for example maybe just a error message).
+ *
+ * TODO: Example of json input
+ */
+void DBusAPI::status_reply(QDBusPendingCallWatcher *call){
+ QString result = "";
+
+ QDBusPendingReply<QString> reply = *call;
+ if (reply.isError()) {
+ qCritical() << "D-Bus '(refresh_)status' request failed, this was the reply:";
+ qCritical() << reply.error();
+
+ if (QDBusError::ServiceUnknown == reply.error().type()) {
+ qCritical() << "The session service was probably just not started!";
+ }
+
+ emit serviceStatusResponse(nullptr);
+
+ return;
+ } else {
+ result = reply.argumentAt<0>();
+ }
+ call->deleteLater();
+
+ // Get the QJsonObject
+ qDebug() << "Raw JSON from a status request for a session is:" << result.toUtf8().replace('"', "");
+ QJsonDocument doc = QJsonDocument::fromJson(result.toUtf8());
+
+ emit serviceStatusResponse(&doc);
+}
+
+/*!
+ * \brief This method requests the session service to list all RWAHosts.
+ */
+void DBusAPI::get_web_app_hosts_request() {
+ qDebug().noquote() << QString("Requesting D-Bus service to list "
+ "all remote web app hosts");
+
+ // Make an asynchronous 'get_web_app_hosts' call
+ // Response will be sent to 'get_web_app_hosts_reply'
+ QDBusPendingCall async = _dbus_rwa->asyncCall("get_web_app_hosts");
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this);
+
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
+ this, SLOT(get_web_app_hosts_reply(QDBusPendingCallWatcher*)));
+}
+
+/*!
+ * \brief This method requests the session service to add a RWAHost to its configuration files.
+ *
+ * \a host_url is the remote web app adress which will be used by the session service to coordinate
+ * sessions, connections, settings and such.
+ */
+void DBusAPI::add_web_app_host_request(QString host_url, QString host_alias) {
+ Q_ASSERT(host_url != "");
+ Q_ASSERT(host_alias != "");
+
+ qDebug().noquote() << QString("Requesting D-Bus service to register new "
+ "remote web app host '%0' with url '%1'").arg(host_alias).arg(host_url);
+
+ // Make an asynchronous 'add_web_app_host' call
+ // Response will be sent to 'add_web_app_host_reply'
+ QDBusPendingCall async = _dbus_rwa->asyncCall("add_web_app_host", host_url, host_alias);
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this);
+
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
+ this, SLOT(add_web_app_host_reply(QDBusPendingCallWatcher*)));
+}
+
+/*!
+ * \brief This method requests the session service to remove a RWAHost from its configuration files.
+ *
+ * \a host_uuid Unique identifier which all hosts have.
+ */
+void DBusAPI::remove_web_app_host_request(QString host_uuid) {
+ Q_ASSERT(host_uuid != "");
+
+ qDebug().noquote() << QString("Requesting D-Bus service to list "
+ "all remote web app hosts");
+
+ // Make an asynchronous 'remove_web_app_host' call
+ // Response will be sent to 'remove_web_app_host_reply'
+ QDBusPendingCall async = _dbus_rwa->asyncCall("remove_web_app_host", host_uuid);
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this);
+
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
+ this, SLOT(remove_web_app_host_reply(QDBusPendingCallWatcher*)));
+}
+
+/*!
+ * \brief Method gets called when a D-Bus response is ready.
+ *
+ * \a call contains the D-Bus response (session service or for example maybe just a error message).
+ *
+ * TODO: Example of json input
+ */
+void DBusAPI::get_web_app_hosts_reply(QDBusPendingCallWatcher *call){
+ QString result = "";
+
+ QDBusPendingReply<QString> reply = *call;
+ if (reply.isError()) {
+ qCritical() << "D-Bus 'get_web_app_hosts' request failed, this was the reply:";
+ qCritical() << reply.error();
+
+ if (QDBusError::ServiceUnknown == reply.error().type()) {
+ qCritical() << "The session service was probably just not started!";
+ }
+
+ emit serviceGetWebAppHostsResponse(nullptr);
+
+ return;
+ } else {
+ result = reply.argumentAt<0>();
+ }
+ call->deleteLater();
+
+ // Get the QJsonObject
+ qDebug() << "Raw JSON from the remote web app host listing request:" << result.toUtf8().replace('"', "");
+ QJsonDocument doc = QJsonDocument::fromJson(result.toUtf8());
+
+ emit serviceGetWebAppHostsResponse(&doc);
+}
+
+/*!
+ * \brief Method gets called when a D-Bus response is ready.
+ *
+ * \a call contains the D-Bus response (session service or for example maybe just a error message).
+ *
+ * TODO: Example of json input
+ */
+void DBusAPI::add_web_app_host_reply(QDBusPendingCallWatcher *call){
+ QString result = "";
+
+ QDBusPendingReply<QString> reply = *call;
+ if (reply.isError()) {
+ qCritical() << "D-Bus 'add_web_app_host' request failed, this was the reply:";
+ qCritical() << reply.error();
+
+ if (QDBusError::ServiceUnknown == reply.error().type()) {
+ qCritical() << "The session service was probably just not started!";
+ }
+
+ emit serviceAddWebAppHostResponse(nullptr);
+
+ return;
+ } else {
+ result = reply.argumentAt<0>();
+ }
+ call->deleteLater();
+
+ // Get the QJsonObject
+ qDebug() << "Raw JSON from the remote web app host register request:" << result.toUtf8().replace('"', "");
+ QJsonDocument doc = QJsonDocument::fromJson(result.toUtf8());
+
+ emit serviceAddWebAppHostResponse(&doc);
+}
+
+/*!
+ * \brief Method gets called when a D-Bus response is ready.
+ *
+ * \a call contains the D-Bus response (session service or for example maybe just a error message).
+ *
+ * TODO: Example of json input
+ */
+void DBusAPI::remove_web_app_host_reply(QDBusPendingCallWatcher *call){
+ QString result = "";
+
+ QDBusPendingReply<QString> reply = *call;
+ if (reply.isError()) {
+ qCritical() << "D-Bus 'remove_web_app_host' request failed, this was the reply:";
+ qCritical() << reply.error();
+
+ if (QDBusError::ServiceUnknown == reply.error().type()) {
+ qCritical() << "The session service was probably just not started!";
+ }
+
+ emit serviceRemoveWebAppHostResponse(nullptr);
+
+ return;
+ } else {
+ result = reply.argumentAt<0>();
+ }
+ call->deleteLater();
+
+ // Get the QJsonObject
+ qDebug() << "Raw JSON from the remote web app host deletion request:" << result.toUtf8().replace('"', "");
+ QJsonDocument doc = QJsonDocument::fromJson(result.toUtf8());
+
+ emit serviceRemoveWebAppHostResponse(&doc);
+}
diff --git a/rwa-support-desktopapp/src/DBusAPI.h b/rwa-support-desktopapp/src/DBusAPI.h
new file mode 100644
index 0000000..63dbe68
--- /dev/null
+++ b/rwa-support-desktopapp/src/DBusAPI.h
@@ -0,0 +1,87 @@
+/*
+ * This file is part of Remote Support Desktop
+ * https://gitlab.das-netzwerkteam.de/RemoteWebApp/rwa.support.desktopapp
+ * Copyright 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef DBUSAPI_H
+#define DBUSAPI_H
+
+#include <QObject>
+#include <QtDBus/QtDBus>
+
+#include "RWAHost.h"
+#include "RWADBusAdaptor.h"
+
+class DBusAPI : public QObject {
+ Q_OBJECT
+public:
+ explicit DBusAPI();
+
+signals:
+ void serviceStartResponse(QJsonDocument*);
+ void serviceStopResponse(QJsonDocument*);
+ void serviceStatusResponse(QJsonDocument*);
+
+ void serviceGetWebAppHostsResponse(QJsonDocument*);
+ void serviceAddWebAppHostResponse(QJsonDocument*);
+ void serviceRemoveWebAppHostResponse(QJsonDocument*);
+
+public slots:
+ void start_reply(QDBusPendingCallWatcher *call);
+ void stop_reply(QDBusPendingCallWatcher *call);
+ void status_reply(QDBusPendingCallWatcher *call);
+
+ void get_web_app_hosts_reply(QDBusPendingCallWatcher *call);
+ void add_web_app_host_reply(QDBusPendingCallWatcher *call);
+ void remove_web_app_host_reply(QDBusPendingCallWatcher *call);
+
+
+ // Starts a remote web app session
+ void start_request(RWAHost *host);
+
+ // Stop a remote web app session
+ void stop_request(RWAHost *host, QString session_id);
+
+ // Refreshes a remote web app session's status
+ void refresh_status_request(RWAHost *host, QString session_id);
+
+ // Gets a remote web app session's status
+ void status_request(RWAHost *host, QString session_id);
+
+ // Gets all remote web app hosts deposited in the session server
+ void get_web_app_hosts_request();
+
+ // Add a specific remote web app host using a url to make sure its contactable
+ void add_web_app_host_request(QString host_url, QString host_alias);
+
+ // Removes a specific remote web app host using the uuid of a host
+ void remove_web_app_host_request(QString host_uuid);
+
+private:
+ OrgArcticaProjectRWASupportSessionServiceInterface* _dbus_rwa;
+
+ void _initDBus();
+
+};
+
+#endif // DBUSAPI_H
diff --git a/rwa-support-desktopapp/src/ListItem.qml b/rwa-support-desktopapp/src/ListItem.qml
new file mode 100644
index 0000000..fa83f4b
--- /dev/null
+++ b/rwa-support-desktopapp/src/ListItem.qml
@@ -0,0 +1,45 @@
+/*
+ * This file is part of Remote Support Desktop
+ * https://gitlab.das-netzwerkteam.de/RemoteWebApp/rwa.support.desktopapp
+ * Copyright 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.0
+import QtQuick.Controls.Material 2.3
+import QtQuick.Controls 2.2
+
+ItemDelegate {
+ id: root
+ width: parent.width
+ height: 50
+
+ property string scene_url
+
+ signal listItemClick()
+
+ text: ""
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: listItemClick();
+ }
+}
diff --git a/rwa-support-desktopapp/src/RWADBusAdaptor.cpp b/rwa-support-desktopapp/src/RWADBusAdaptor.cpp
new file mode 100644
index 0000000..6c1504e
--- /dev/null
+++ b/rwa-support-desktopapp/src/RWADBusAdaptor.cpp
@@ -0,0 +1,64 @@
+/*
+ * This file is part of Remote Support Desktop
+ * https://gitlab.das-netzwerkteam.de/RemoteWebApp/rwa.support.desktopapp
+ * Copyright 2020, 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This file was generated by qdbusxml2cpp version 0.8
+ * Command line was: qdbusxml2cpp -i src/RWADBusAdaptor.h -p :src/RWADBusAdaptor.cpp rwa.xml
+ * rwa.xml is the xml output of the Introspect D-Bus method
+ *
+ * qdbusxml2cpp is Copyright (C) 2017 The Qt Company Ltd.
+ *
+ * This is an auto-generated file.
+ * This file may have been hand-edited. Look for HAND-EDIT comments
+ * before re-generating it.
+ */
+
+#include "src/RWADBusAdaptor.h"
+/*
+ * Implementation of interface class OrgArcticaProjectRWASupportSessionServiceInterface
+ */
+
+OrgArcticaProjectRWASupportSessionServiceInterface::OrgArcticaProjectRWASupportSessionServiceInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent)
+ : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent)
+{
+}
+
+OrgArcticaProjectRWASupportSessionServiceInterface::~OrgArcticaProjectRWASupportSessionServiceInterface()
+{
+}
+
+/*
+ * Implementation of interface class OrgFreedesktopDBusIntrospectableInterface
+ */
+
+OrgFreedesktopDBusIntrospectableInterface::OrgFreedesktopDBusIntrospectableInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent)
+ : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent)
+{
+}
+
+OrgFreedesktopDBusIntrospectableInterface::~OrgFreedesktopDBusIntrospectableInterface()
+{
+}
+
diff --git a/rwa-support-desktopapp/src/RWADBusAdaptor.h b/rwa-support-desktopapp/src/RWADBusAdaptor.h
new file mode 100644
index 0000000..77f4b6c
--- /dev/null
+++ b/rwa-support-desktopapp/src/RWADBusAdaptor.h
@@ -0,0 +1,151 @@
+/*
+ * This file is part of Remote Support Desktop
+ * https://gitlab.das-netzwerkteam.de/RemoteWebApp/rwa.support.desktopapp
+ * Copyright 2020, 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This file was generated by qdbusxml2cpp version 0.8
+ * Command line was: qdbusxml2cpp -p src/RWADBusAdaptor.h: rwa.xml
+ * rwa.xml is the xml output of the Introspect D-Bus method
+ *
+ * qdbusxml2cpp is Copyright (C) 2017 The Qt Company Ltd.
+ *
+ * This is an auto-generated file.
+ * Do not edit! All changes made to it will be lost.
+ */
+
+#ifndef RWADBUSADAPTOR_H
+#define RWADBUSADAPTOR_H
+
+#include <QtCore/QObject>
+#include <QtCore/QByteArray>
+#include <QtCore/QList>
+#include <QtCore/QMap>
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+#include <QtCore/QVariant>
+#include <QtDBus/QtDBus>
+
+/*
+ * Proxy class for interface org.ArcticaProject.RWASupportSessionService
+ */
+class OrgArcticaProjectRWASupportSessionServiceInterface: public QDBusAbstractInterface
+{
+ Q_OBJECT
+public:
+ static inline const char *staticInterfaceName()
+ { return "org.ArcticaProject.RWASupportSessionService"; }
+
+public:
+ OrgArcticaProjectRWASupportSessionServiceInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = nullptr);
+
+ ~OrgArcticaProjectRWASupportSessionServiceInterface();
+
+public Q_SLOTS: // METHODS
+ inline QDBusPendingReply<QString> add_web_app_host(const QString &host)
+ {
+ QList<QVariant> argumentList;
+ argumentList << QVariant::fromValue(host);
+ return asyncCallWithArgumentList(QStringLiteral("add_web_app_host"), argumentList);
+ }
+
+ inline QDBusPendingReply<QString> get_web_app_hosts()
+ {
+ QList<QVariant> argumentList;
+ return asyncCallWithArgumentList(QStringLiteral("get_web_app_hosts"), argumentList);
+ }
+
+ inline QDBusPendingReply<QString> refresh_status(int pid)
+ {
+ QList<QVariant> argumentList;
+ argumentList << QVariant::fromValue(pid);
+ return asyncCallWithArgumentList(QStringLiteral("refresh_status"), argumentList);
+ }
+
+ inline QDBusPendingReply<QString> remove_web_app_host(int host_idx)
+ {
+ QList<QVariant> argumentList;
+ argumentList << QVariant::fromValue(host_idx);
+ return asyncCallWithArgumentList(QStringLiteral("remove_web_app_host"), argumentList);
+ }
+
+ inline QDBusPendingReply<QString> start(int host_idx)
+ {
+ QList<QVariant> argumentList;
+ argumentList << QVariant::fromValue(host_idx);
+ return asyncCallWithArgumentList(QStringLiteral("start"), argumentList);
+ }
+
+ inline QDBusPendingReply<QString> status(int pid)
+ {
+ QList<QVariant> argumentList;
+ argumentList << QVariant::fromValue(pid);
+ return asyncCallWithArgumentList(QStringLiteral("status"), argumentList);
+ }
+
+ inline QDBusPendingReply<QString> stop(int pid)
+ {
+ QList<QVariant> argumentList;
+ argumentList << QVariant::fromValue(pid);
+ return asyncCallWithArgumentList(QStringLiteral("stop"), argumentList);
+ }
+
+Q_SIGNALS: // SIGNALS
+};
+
+/*
+ * Proxy class for interface org.freedesktop.DBus.Introspectable
+ */
+class OrgFreedesktopDBusIntrospectableInterface: public QDBusAbstractInterface
+{
+ Q_OBJECT
+public:
+ static inline const char *staticInterfaceName()
+ { return "org.freedesktop.DBus.Introspectable"; }
+
+public:
+ OrgFreedesktopDBusIntrospectableInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = nullptr);
+
+ ~OrgFreedesktopDBusIntrospectableInterface();
+
+public Q_SLOTS: // METHODS
+ inline QDBusPendingReply<QString> Introspect()
+ {
+ QList<QVariant> argumentList;
+ return asyncCallWithArgumentList(QStringLiteral("Introspect"), argumentList);
+ }
+
+Q_SIGNALS: // SIGNALS
+};
+
+namespace org {
+ namespace ArcticaProject {
+ typedef ::OrgArcticaProjectRWASupportSessionServiceInterface RWASupportSessionService;
+ }
+ namespace freedesktop {
+ namespace DBus {
+ typedef ::OrgFreedesktopDBusIntrospectableInterface Introspectable;
+ }
+ }
+}
+#endif
diff --git a/rwa-support-desktopapp/src/RWAHost.cpp b/rwa-support-desktopapp/src/RWAHost.cpp
new file mode 100644
index 0000000..f31dc60
--- /dev/null
+++ b/rwa-support-desktopapp/src/RWAHost.cpp
@@ -0,0 +1,68 @@
+/*
+ * This file is part of Remote Support Desktop
+ * https://gitlab.das-netzwerkteam.de/RemoteWebApp/rwa.support.desktopapp
+ * Copyright 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "RWAHost.h"
+
+RWAHost::RWAHost(QString uuid, QString alias, QString url) {
+ assert(uuid != "");
+ assert(alias != "");
+ assert(url != "");
+
+ /*qDebug() << "Created new RWAHost object.\n\t"
+ << QString("uuid: '%0'").arg(uuid) << "\n\t"
+ << QString("alias: '%0'").arg(alias) << "\n\t"
+ << QString("url: '%0'").arg(url);*/
+
+ _url = url;
+ _alias = alias;
+ _uuid = uuid;
+}
+
+QString RWAHost::uuid() const {
+ return _uuid;
+}
+
+QString RWAHost::alias() const {
+ return _alias;
+}
+
+QString RWAHost::url() const {
+ return _url;
+}
+
+void RWAHost::setUuid(const QString &uuid) {
+ _uuid = uuid;
+ emit uuidChanged(uuid);
+}
+
+void RWAHost::setAlias(const QString &alias) {
+ _alias = alias;
+ emit aliasChanged(alias);
+}
+
+void RWAHost::setUrl(const QString &url) {
+ _url = url;
+ emit urlChanged(url);
+}
diff --git a/rwa-support-desktopapp/src/RWAHost.h b/rwa-support-desktopapp/src/RWAHost.h
new file mode 100644
index 0000000..cd3b1ce
--- /dev/null
+++ b/rwa-support-desktopapp/src/RWAHost.h
@@ -0,0 +1,63 @@
+/*
+ * This file is part of Remote Support Desktop
+ * https://gitlab.das-netzwerkteam.de/RemoteWebApp/rwa.support.desktopapp
+ * Copyright 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef RWAHOST_H
+#define RWAHOST_H
+
+#include <QObject>
+#include <QQuickItem>
+
+class RWAHost : public QObject {
+ Q_OBJECT
+ Q_PROPERTY(QString uuid READ uuid WRITE setUuid NOTIFY uuidChanged)
+ Q_PROPERTY(QString alias READ alias WRITE setAlias NOTIFY aliasChanged)
+ Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged)
+
+public:
+ RWAHost(QString uuid = "", QString alias = "", QString url = "");
+ RWAHost(const RWAHost&);
+
+private:
+ QString _uuid;
+ QString _alias;
+ QString _url;
+
+signals:
+ void uuidChanged(QString uuid);
+ void aliasChanged(QString alias);
+ void urlChanged(QString url);
+
+public slots:
+ QString uuid() const;
+ QString alias() const;
+ QString url() const;
+
+ void setUuid(const QString &uuid);
+ void setAlias(const QString &alias);
+ void setUrl(const QString &url);
+
+};
+
+#endif // RWAHOST_H
diff --git a/rwa-support-desktopapp/src/RWAHostModel.cpp b/rwa-support-desktopapp/src/RWAHostModel.cpp
new file mode 100644
index 0000000..5f62adc
--- /dev/null
+++ b/rwa-support-desktopapp/src/RWAHostModel.cpp
@@ -0,0 +1,37 @@
+#include "RWAHostModel.h"
+
+RWAHostModel::RWAHostModel(QObject *parent) {
+ Q_UNUSED(parent)
+}
+
+int RWAHostModel::rowCount(const QModelIndex& parent) const {
+ Q_UNUSED(parent);
+ return mDatas.size();
+}
+
+int RWAHostModel::columnCount(const QModelIndex& parent) const {
+ Q_UNUSED(parent);
+ return 1;
+}
+
+QVariant RWAHostModel::data(const QModelIndex &index, int role) const
+ {
+ if (!index.isValid())
+ return QVariant();
+ if ( role == Qt::DisplayRole) {
+ return mDatas[index.row()];
+ }
+ return QVariant();
+}
+
+void RWAHostModel::populate() {
+ beginResetModel();
+ mDatas.clear();
+ RWAHost *host1 = new RWAHost("uuid-1", "Erster Server", "url1");
+ RWAHost *host2 = new RWAHost("uuid-2", "Zweiter Server", "url2");
+ RWAHost *host3 = new RWAHost("uuid-3", "Dritter Server", "url3");
+ mDatas.append(host1->alias());
+ mDatas.append(host2->alias());
+ mDatas.append(host3->alias());
+ endResetModel();
+}
diff --git a/rwa-support-desktopapp/src/RWAHostModel.h b/rwa-support-desktopapp/src/RWAHostModel.h
new file mode 100644
index 0000000..8697df2
--- /dev/null
+++ b/rwa-support-desktopapp/src/RWAHostModel.h
@@ -0,0 +1,23 @@
+#ifndef RWAHOSTMODEL_H
+#define RWAHOSTMODEL_H
+
+#include <QObject>
+#include <QAbstractListModel>
+
+#include "RWAHost.h"
+
+class RWAHostModel : public QAbstractListModel {
+ Q_OBJECT
+
+public:
+ explicit RWAHostModel(QObject * parent = nullptr);
+ int rowCount(const QModelIndex& parent = QModelIndex()) const;
+ int columnCount(const QModelIndex& parent = QModelIndex()) const;
+ QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
+ void populate();
+
+private:
+ QStringList mDatas;
+};
+
+#endif // RWAHOSTMODEL_H
diff --git a/rwa-support-desktopapp/src/Toast.qml b/rwa-support-desktopapp/src/Toast.qml
new file mode 100644
index 0000000..7a18dc8
--- /dev/null
+++ b/rwa-support-desktopapp/src/Toast.qml
@@ -0,0 +1,158 @@
+/*
+ * This file is part of Remote Support Desktop
+ * https://gitlab.das-netzwerkteam.de/RemoteWebApp/remote-support-desktop
+ * Copyright 2020, 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+ * Copyright 2020, 2021 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.0
+import QtQuick.Controls 2.0
+import QtQuick.Controls.Material 2.3
+import rwa.toast.type 1.0
+
+/**
+ * adapted from StackOverflow:
+ * http://stackoverflow.com/questions/26879266/make-toast-in-android-by-qml
+ * GitHub Gist: https://gist.github.com/jonmcclung/bae669101d17b103e94790341301c129
+ * Adapted to work with dark/light theming
+ */
+
+/**
+ * @brief An Android-like timed message text in
+ * a box that self-destroys when finished if desired
+ */
+Control {
+
+ /**
+ * Public
+ */
+
+ /**
+ * @brief Shows this Toast
+ *
+ * @param {string} text Text to show
+ * @param {real} duration Duration to show in milliseconds, defaults to 3000
+ * @param {enum} type Type of toast. Available is:
+ * ToastType.Standard, ToastType.Info, ToastType.Warning
+ * ToastType.Success, ToastType.Error
+ */
+ function show(text, duration, type) {
+ message.text = text;
+
+ // checks if parameter was passed
+ if (typeof duration !== "undefined") {
+ time = Math.max(duration, 2 * fadeTime);
+ } else {
+ time = defaultTime;
+ }
+
+ if (typeof type !== "undefined" ) {
+ if (type === ToastType.ToastStandard) {
+ selectedColor = "#dcdedc";
+ } else if (type === ToastType.ToastInfo) {
+ selectedColor = "#0d5eaf";
+ } else if (type === ToastType.ToastSuccess) {
+ selectedColor = "#0daf36";
+ } else if (type === ToastType.ToastWarning) {
+ selectedColor = "#efef2a";
+ } else if (type === ToastType.ToastError) {
+ selectedColor = "#ed1212";
+ }
+ } else {
+ selectedColor = "#dcdedc";
+ }
+
+ animation.start();
+ }
+
+ // whether this Toast will self-destroy when it is finished
+ property bool selfDestroying: false
+
+ /**
+ * Private
+ */
+
+ id: root
+
+ property color selectedColor: "#dcdedc"
+ readonly property real defaultTime: 3000
+ property real time: defaultTime
+ readonly property real fadeTime: 300
+
+ property real margin: 10
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ margins: margin
+ }
+
+ height: message.height + margin
+
+ background: Rectangle {
+ color: (Material.theme == Material.Dark) ? "#212121" : "#dcdedc"
+ border.color: selectedColor
+ border.width: 1.5
+ radius: margin
+ }
+
+ opacity: 0
+
+ Text {
+ id: message
+ color: (Material.theme == Material.Dark) ? "#f1f1f1" : "#010101"
+ wrapMode: Text.Wrap
+ horizontalAlignment: Text.AlignHCenter
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ margins: margin / 2
+ }
+ }
+
+ SequentialAnimation on opacity {
+ id: animation
+ running: false
+
+
+ NumberAnimation {
+ to: .9
+ duration: fadeTime
+ }
+
+ PauseAnimation {
+ duration: time - 2 * fadeTime
+ }
+
+ NumberAnimation {
+ to: 0
+ duration: fadeTime
+ }
+
+ onRunningChanged: {
+ if (!running && selfDestroying) {
+ root.destroy();
+ }
+ }
+ }
+}
diff --git a/rwa-support-desktopapp/src/ToastManager.qml b/rwa-support-desktopapp/src/ToastManager.qml
new file mode 100644
index 0000000..2f1600f
--- /dev/null
+++ b/rwa-support-desktopapp/src/ToastManager.qml
@@ -0,0 +1,89 @@
+/*
+ * This file is part of Remote Support Desktop
+ * https://gitlab.das-netzwerkteam.de/RemoteWebApp/remote-support-desktop
+ * Copyright 2020, 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+ * Copyright 2020, 2021 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.0
+
+/**
+ * adapted from StackOverflow:
+ * http://stackoverflow.com/questions/26879266/make-toast-in-android-by-qml
+ * GitHub Gist: https://gist.github.com/jonmcclung/bae669101d17b103e94790341301c129
+ * @brief Manager that creates Toasts dynamically
+ */
+ListView {
+ /**
+ * Public
+ */
+
+ /**
+ * @brief Shows a Toast
+ *
+ * @param {string} text Text to show
+ * @param {real} duration Duration to show in milliseconds, defaults to 3000
+ * @param {enum} type Type of toast. Available is:
+ * ToastType.Standard, ToastType.Info, ToastType.Warning
+ * ToastType.Success, ToastType.Error
+ */
+ function show(text, duration, type) {
+ model.insert(0, {text: text, duration: duration, type: type});
+ }
+
+ /**
+ * Private
+ */
+
+ id: root
+
+ z: Infinity
+ spacing: 5
+ anchors.fill: parent
+ // parent.height * 0.1 = height of blue header rectangle on main
+ anchors.topMargin: parent.height * 0.1 + 5
+ verticalLayoutDirection: ListView.TopToBottom
+
+ interactive: false
+
+ displaced: Transition {
+ NumberAnimation {
+ properties: "y"
+ easing.type: Easing.InOutQuad
+ }
+ }
+
+ delegate: Toast {
+ Component.onCompleted: {
+ if (typeof duration === "undefined" && typeof type === "undefined") {
+ show(text, ToastType.ToastStandard);
+ } else if (typeof duration === "undefined" &&
+ typeof type !== "undefined") {
+ show(text, type);
+ } else {
+ show(text, duration, type);
+ }
+ }
+ }
+
+ model: ListModel {id: model}
+}
diff --git a/rwa-support-desktopapp/src/main.cpp b/rwa-support-desktopapp/src/main.cpp
new file mode 100644
index 0000000..e29c8db
--- /dev/null
+++ b/rwa-support-desktopapp/src/main.cpp
@@ -0,0 +1,141 @@
+/*
+ * This file is part of Remote Support Desktop
+ * https://gitlab.das-netzwerkteam.de/RemoteWebApp/rwa.support.desktopapp
+ * Copyright 2020, 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+ * Copyright 2020, 2021 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <QApplication>
+#include <QQmlApplicationEngine>
+#include <QQmlComponent>
+#include <QQmlProperty>
+#include <QQuickItem>
+#include <QObject>
+#include <QTranslator>
+#include <QDebug>
+#include <QQmlContext>
+#include <QQuickStyle>
+#include <signal.h>
+
+#include "DBusAPI.h"
+#include "RWADBusAdaptor.cpp"
+#include "session.h"
+#include "scenes/add_rwahost_wizard/add_rwahost_wizard.h"
+#include "scenes/remote_control/remote_control_manager.h"
+#include "RWAHostModel.h"
+#include "RWAHost.h"
+
+int main(int argc, char *argv[]) {
+ qDebug() << "This app was built on: " << __DATE__ << __TIME__;
+
+ // We don't want users to have multiple instances of this app running
+ QString tmpDirPath = QDir::tempPath() + "/rwa.support.desktopapp";
+ QString tmpFilePath = tmpDirPath + "/prevent-multiple-instances.lock";
+ QDir tmpDir(tmpDirPath);
+ if (!tmpDir.exists()) {
+ // Ensure that the path exists
+ tmpDir.mkpath(".");
+ }
+ QLockFile lockFile(tmpFilePath);
+ qDebug().noquote() << QString("Checking for a lockfile at: '%0'").arg(tmpFilePath);
+
+ if(!lockFile.tryLock(100)){
+ qCritical().noquote() << "You already have this app running.\n"
+ << "Only one instance is allowed.\n"
+ << "Closing application now with an error.";
+
+ return 1;
+ }
+
+ QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+ QApplication app(argc, argv);
+
+ QQuickStyle::setStyle("Material");
+
+ QTranslator translator;
+ qDebug().noquote() << QString("Locales: Loading locale: qrc:/locales/%0")
+ .arg(QLocale::system().name());
+ if(translator.load(":/locales/" + QLocale::system().name())) {
+ app.installTranslator(&translator);
+ qDebug().noquote() << "Locales: Loaded: " + QLocale::system().name() + " locale!";
+ } else {
+ qWarning() << "Locales: Unable to load translation!";
+ }
+
+ QQmlApplicationEngine engine(&app);
+
+ QScopedPointer<DBusAPI> dbus_api (new DBusAPI());
+
+ // Make 'mainqmladaptor' available to QML
+ QScopedPointer<MainQMLAdaptor> main_gui (
+ new MainQMLAdaptor(&app, &engine, dbus_api.data())
+ );
+
+ engine.rootContext()->setContextProperty("mainqmladaptor", main_gui.data());
+
+ QObject::connect(dbus_api.data(),
+ SIGNAL(serviceGetWebAppHostsResponse(QJsonDocument*)),
+ main_gui.data(),
+ SLOT(get_web_app_hosts_response(QJsonDocument*)));
+ dbus_api.data()->get_web_app_hosts_request();
+
+
+ engine.load(QUrl(QStringLiteral("qrc:/src/main.qml")));
+ if (engine.rootObjects().isEmpty())
+ return -1;
+
+ QObject::connect(main_gui.data(),
+ SIGNAL(minimizeWindow()),
+ engine.rootObjects().takeFirst(),
+ SLOT(minimizeWindow()));
+
+ QObject::connect(main_gui.data(),
+ SIGNAL(showWindow()),
+ engine.rootObjects().takeFirst(),
+ SLOT(showWindow()));
+
+ QObject::connect(engine.rootObjects().takeFirst()->
+ findChild<QObject*>("sidebar_drawer"),
+ SIGNAL(rwaHostSelected(QString)),
+ main_gui.data(),
+ SLOT(onRwaHostSelected(QString)));
+
+ // Make 'AddRWAHostWizard' available to QML
+ QScopedPointer<AddRWAHostWizard> wizard (
+ new AddRWAHostWizard(&app,
+ main_gui.data(),
+ dbus_api.data())
+ );
+ engine.rootContext()->
+ setContextProperty("add_rwahost_wizard", wizard.data());
+
+ // Make 'remote_control_manager' available to QML
+ QScopedPointer<RemoteControlManager> remote_mngr (
+ new RemoteControlManager(&engine,
+ main_gui.data(),
+ dbus_api.data())
+ );
+ engine.rootContext()->
+ setContextProperty("remote_control_manager", remote_mngr.data());
+
+ return app.exec();
+}
diff --git a/rwa-support-desktopapp/src/main.qml b/rwa-support-desktopapp/src/main.qml
new file mode 100644
index 0000000..a622c18
--- /dev/null
+++ b/rwa-support-desktopapp/src/main.qml
@@ -0,0 +1,418 @@
+/*
+ * This file is part of Remote Support Desktop
+ * https://gitlab.das-netzwerkteam.de/RemoteWebApp/remote-support-desktop
+ * Copyright 2020, 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+ * Copyright 2020, 2021 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.9
+import QtQuick.Window 2.2
+import QtQuick.Extras 1.4
+import QtQuick.Controls 2.2
+import QtQuick.Controls.Styles 1.4
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls.Material 2.3
+import "scenes" as Scenes
+
+/*!
+ The main.qml file contains the window, with its header, sidebar, toast and main_content.
+ */
+
+ApplicationWindow {
+ readonly property int normal_width: 650
+ readonly property int normal_height: 500
+ readonly property bool inPortrait: window.width < window.height
+
+ minimumWidth: 400
+ minimumHeight: 460
+
+ width: normal_width
+ height: normal_height
+
+ id: window
+ visible: true
+ title: qsTr("Remote Support for your Desktop")
+
+ onClosing: {
+ mainqmladaptor.onCloseHandler();
+ }
+
+ function minimizeWindow() {
+ showMinimized();
+ console.log("Minimizing window now...");
+ }
+
+ function showWindow() {
+ showNormal();
+ console.log("Opening window now...");
+ }
+
+ function main_content_pop(item) {
+ if(item) {
+ if(item.search(main_content.currentItem.objectName) >= 0) return
+ }
+ return main_content.pop(item)
+ }
+
+ function main_content_push(item) {
+ if(item) {
+ if(item.search(main_content.currentItem.objectName) >= 0) return
+ }
+ return main_content.push(item)
+ }
+
+ function main_content_replace(item) {
+ if(item) {
+ if(item.search(main_content.currentItem.objectName) >= 0) return
+ }
+ return main_content.replace(item)
+ }
+
+ MessageDialog {
+ id: message_dialog
+ objectName: "message_dialog"
+ title: qsTr("Remote Support for your Desktop")
+ text: qsTr("You are not supposed to see this message.\nThis is a bug.")
+ icon: StandardIcon.Critical
+ }
+
+ ToastManager {
+ id: toast
+
+ anchors.leftMargin: inPortrait ? 0 : sidebar_drawer.width
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.top: parent.top
+ }
+
+ Connections {
+ target: mainqmladaptor
+ function onShowToastSignal(text, durationMs, type) {
+ toast.show(text, durationMs, type)
+ }
+ }
+
+ Connections {
+ target: mainqmladaptor
+ function onShowMessageDialogChanged(show) {
+ message_dialog.visible = show
+ }
+ }
+
+ StackView {
+ id: main_content
+ objectName: "main_content"
+
+ anchors.top: top_menu_bar_frame.bottom
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.leftMargin: !inPortrait ? sidebar_drawer.width : 0
+ anchors.topMargin: 0
+
+ // Our Application will start with the following content:
+ initialItem: "scenes/Scene_placeholder.qml"
+
+ replaceEnter: Transition {
+ PropertyAnimation {
+ property: "opacity"
+ from: 0
+ to:1
+ duration: 100
+ }
+ }
+ replaceExit: Transition {
+ PropertyAnimation {
+ property: "opacity"
+ from: 1
+ to:0
+ duration: 100
+ }
+ }
+ }
+
+ Connections {
+ target: mainqmladaptor
+ function onMessageDialogTextChanged(text) {
+ message_dialog.text = text
+ }
+ }
+
+ Connections {
+ target: mainqmladaptor
+ function onMessageDialogTitleChanged(title) {
+ message_dialog.title = title
+ }
+ }
+
+ Connections {
+ target: mainqmladaptor
+ function onMessageDialogIconChanged(iconindex) {
+ message_dialog.icon = iconindex
+ }
+ }
+
+ /*!
+ This is our sidebar (sidemenu) for navigation purposes.
+ It will collapse if the window gets resized to a portrait format.
+ Then it can be opened using the now visible button in the header
+ or by swiping from left to right.
+ main_content's leftMargin depends on sidebar_drawers's width
+ We don't want them to overlap on different window heights und widths.
+ */
+ Drawer {
+ id: sidebar_drawer
+ objectName: "sidebar_drawer"
+
+ y: top_menu_bar_frame.height
+ width: !inPortrait ?
+ Math.min(300, Math.max(200, window.width * 0.333)) :
+ (window.width * 0.5)
+ height: window.height - top_menu_bar_frame.height
+
+ modal: inPortrait
+ interactive: inPortrait
+ position: inPortrait ? 0 : 1
+ dragMargin: 1
+ margins: -2
+ visible: !inPortrait
+
+ signal rwaHostSelected(string host_uuid)
+ property bool rwaHostIsSelected: false
+
+ ListView {
+ id: sidebar_listview
+ boundsBehavior: Flickable.StopAtBounds
+ interactive: true
+ clip: true
+ anchors.fill: parent
+ model: mainModel
+
+ header: Rectangle {
+ height: 50
+ width: parent.width
+ color: Material.background
+
+ ComboBox {
+ id: server_chooser
+ objectName: "server_chooser"
+
+ padding: 0
+ width: parent.width
+ height: 56 - y
+ y: -6
+
+ model: mainqmladaptor.rwaHostModel
+ textRole: "alias"
+
+ onCurrentIndexChanged: {
+ var rwa_host = mainqmladaptor.rwaHostModel
+ if (rwa_host[currentIndex] !== undefined) {
+ sidebar_drawer.rwaHostSelected(rwa_host[currentIndex].uuid)
+ displayText = rwa_host[currentIndex].alias
+ }
+ }
+
+
+ }
+ }
+
+ footer: ItemDelegate {
+ id: footer
+ text: " " + qsTr("Settings")
+ width: parent.width
+ enabled: false
+
+ onClicked: {
+ var scene_url = "scenes/Scene_placeholder.qml"
+ header_text.text = qsTr("Settings")
+ if (inPortrait) sidebar_drawer.close()
+
+ if (scene_url.search(main_content.currentItem.objectName) >= 0) return
+ main_content.replace(scene_url, StackView.Transition)
+ }
+
+ MenuSeparator {
+ parent: footer
+ width: parent.width
+ anchors.verticalCenter: parent.top
+ }
+ }
+
+ VisualItemModel {
+ id: mainModel
+
+ ListItem {
+ text: " " + qsTr("Remote Control")
+ scene_url: "scenes/remote_control/Scene_remote_control.qml"
+ onListItemClick: {
+ header_text.text = qsTr("Allow remote control")
+ if (inPortrait) sidebar_drawer.close()
+
+ if (scene_url.search(main_content.currentItem.objectName) >= 0) return
+ main_content.replace(scene_url, StackView.Transition)
+ }
+
+ // Disabled till a RWAHost object is selected.
+ enabled: sidebar_drawer.rwaHostIsSelected
+ }
+ ListItem {
+ text: " " + qsTr("Remote View")
+ scene_url: "scenes/Scene_remote_view.qml"
+ onListItemClick: {
+ header_text.text = qsTr("Allow remote view")
+ if (inPortrait) sidebar_drawer.close()
+
+ if (scene_url.search(main_content.currentItem.objectName) >= 0) return
+ main_content.replace(scene_url, StackView.Transition)
+ }
+
+ // Disabled till a RWAHost object is selected.
+ //enabled: sidebar_drawer.rwaHostIsSelected
+
+ // But remote view is not implemented yet
+ enabled: false
+ }
+ ListItem {
+ text: " " + qsTr("Add RWA-Server")
+ scene_url: "scenes/add_rwahost_wizard/Scene_step_1.qml"
+ onListItemClick: {
+ header_text.text = qsTr("Server addition wizard")
+ if (inPortrait) sidebar_drawer.close()
+
+ if (scene_url.search(main_content.currentItem.objectName) >= 0) return
+ main_content.push(scene_url, StackView.ReplaceTransition)
+ }
+ }
+ }
+
+ ScrollIndicator.vertical: ScrollIndicator { }
+ }
+ }
+
+ // Set dark/light theme based on the slider setting
+ Material.theme: inPortrait ? (theme_portrait.position < 1 ? Material.Light : Material.Dark)
+ : (theme_landscape.position < 1 ? Material.Light : Material.Dark)
+
+ ToolBar {
+ id: top_menu_bar_frame
+ width: parent.width
+ height: parent.height * 0.10
+
+ background: Rectangle {
+ color: parent.Material.background
+ border.color: parent.Material.background
+ }
+ Material.background: "#0d5eaf"
+ Material.foreground: "#ffffff"
+
+ anchors.margins: 0
+ anchors.left: parent.left
+ anchors.top: parent.top
+
+ Switch {
+ id: theme_landscape
+ width: 150
+ implicitWidth: 100
+ visible: !inPortrait
+ parent: inPortrait ? sidebar_listview : top_menu_bar_frame
+
+ height: parent.height
+ anchors.margins: 10
+ text: qsTr("Dark theme")
+ anchors.left: parent.left
+ anchors.verticalCenterOffset: 0
+ anchors.leftMargin: 0
+ anchors.verticalCenter: parent.verticalCenter
+
+ /*!
+ Here is the default setting for dark/light mode
+ */
+ checked: true
+
+ onCheckedChanged: {
+ theme_portrait.checked = checked
+ }
+ }
+
+ Switch {
+ id: theme_portrait
+ visible: inPortrait
+ parent: sidebar_listview
+
+ height: 50
+ width: parent.width
+
+ anchors.bottom: parent.bottom
+ text: qsTr("Dark theme")
+
+ checked: theme_landscape.checked
+ onCheckedChanged: {
+ theme_landscape.checked = checked
+ }
+ }
+
+ Label {
+ id: header_text
+ height: parent.height
+ color: "white"
+ text: qsTr("Allow Remote Control")
+ font.pointSize: 20
+ fontSizeMode: Text.Fit
+ horizontalAlignment: Text.AlignRight
+ verticalAlignment: Text.AlignVCenter
+ anchors.left: inPortrait ? burger_button.right : parent.left
+ anchors.leftMargin: inPortrait ? 5 : theme_landscape.width
+ anchors.right: parent.right
+ anchors.rightMargin: 5
+ anchors.verticalCenter: parent.verticalCenter
+ padding: 5
+ }
+
+ Button {
+ id: burger_button
+ width: 50
+ height: parent.height + 10
+ visible: inPortrait
+
+ text: "≡"
+ checkable: false
+ font.pointSize: 24
+ flat: true
+ anchors.left: parent.left
+ anchors.leftMargin: 0
+ anchors.verticalCenter: parent.verticalCenter
+ enabled: !sidebar_drawer.opened
+
+ onClicked: {
+ sidebar_drawer.open()
+ enabled: false
+ }
+ }
+ }
+}
+
+/*##^## Designer {
+ D{i:14;anchors_width:650}
+}
+ ##^##*/
diff --git a/rwa-support-desktopapp/src/main_qmladaptor.cpp b/rwa-support-desktopapp/src/main_qmladaptor.cpp
new file mode 100644
index 0000000..688b993
--- /dev/null
+++ b/rwa-support-desktopapp/src/main_qmladaptor.cpp
@@ -0,0 +1,344 @@
+/*
+ * This file is part of Remote Support Desktop
+ * https://gitlab.das-netzwerkteam.de/RemoteWebApp/rwa.support.desktopapp
+ * Copyright 2020, 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+ * Copyright 2020, 2021 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "main_qmladaptor.h"
+
+MainQMLAdaptor::MainQMLAdaptor(QObject *parent, QQmlApplicationEngine* engine,
+ DBusAPI *dbus_api) : QObject(parent) {
+ Q_ASSERT(engine != nullptr);
+ Q_ASSERT(dbus_api != nullptr);
+
+ _engine = engine;
+ _dbus_api = dbus_api;
+ _selected_rwa_host = nullptr;
+ _rwaHostModel = new QList<QObject*>;
+
+ QTimer *timer = new QTimer(this);
+ connect(timer, &QTimer::timeout, _dbus_api,
+ QOverload<>::of(&DBusAPI::get_web_app_hosts_request));
+ timer->start(10000);
+
+ qmlRegisterUncreatableMetaObject(
+ Toast::staticMetaObject, // meta object created by Q_NAMESPACE macro
+ "rwa.toast.type", // import statement (can be any string)
+ 1, 0, // major and minor version of the import
+ "ToastType", // name in QML (does not have to match C++ name)
+ "Error: only enums" // error if someone tries to create a ToastType object
+ );
+
+}
+
+void MainQMLAdaptor::onRwaHostSelected(QString host_uuid) {
+ Q_ASSERT(host_uuid != "");
+
+ RWAHost *_host = nullptr;
+ for (int i = 0; i < getRWAHostModel().size(); i++) {
+ QObject *obj = _rwaHostModel->value(i);
+ RWAHost *host = qobject_cast<RWAHost *>(obj);
+ Q_ASSERT(host != nullptr);
+
+ if (host->uuid() == host_uuid) {
+ _host = host;
+ }
+ }
+ Q_ASSERT(_host != nullptr);
+
+ _selected_rwa_host = _host;
+ qDebug() << "RWAHost was selected!" <<
+ _selected_rwa_host->uuid() <<
+ "aka" <<
+ _selected_rwa_host->alias();
+
+ setRWAHostSelected(true);
+}
+
+RWAHost* MainQMLAdaptor::getSelectedRWAHost() {
+ return _selected_rwa_host;
+}
+
+void MainQMLAdaptor::setRWAHostModel(QList<QObject*> *rwa_hosts) {
+ _rwaHostModel = rwa_hosts;
+ emit rwaHostModelChanged(*rwa_hosts);
+}
+
+void MainQMLAdaptor::addRWAHost(RWAHost *rwa_host) {
+ _rwaHostModel->append(rwa_host);
+ emit rwaHostModelChanged(*_rwaHostModel);
+}
+
+void MainQMLAdaptor::removeRWAHost(RWAHost *rwa_host) {
+ _rwaHostModel->removeOne(rwa_host);
+ emit rwaHostModelChanged(*_rwaHostModel);
+}
+
+QList<QObject*> MainQMLAdaptor::getRWAHostModel() {
+ return *_rwaHostModel;
+}
+
+void MainQMLAdaptor::setRWAHostSelected(bool value) {
+ // Find item via 'objectName'
+ QObject *sidebar_drawer = _engine->rootObjects().takeFirst()->
+ findChild<QObject*>("sidebar_drawer");
+ if (sidebar_drawer) {
+ sidebar_drawer->setProperty("rwaHostIsSelected", value);
+ } else {
+ qWarning() << "Unable to find 'sidebar_drawer' Item!";
+ }
+
+ QObject *server_chooser = _engine->rootObjects().takeFirst()->
+ findChild<QObject*>("server_chooser");
+ if (server_chooser) {
+ server_chooser->setProperty("displayText", value ?
+ server_chooser->property("currentText") :
+ tr("No RWA host available!"));
+ server_chooser->setProperty("enabled", value);
+ } else {
+ qWarning() << "Unable to find 'server_chooser' Item!";
+ }
+}
+
+void MainQMLAdaptor::get_web_app_hosts_response(QJsonDocument *doc) {
+ // Q_ASSERT lets the program crash immediatly at startup,
+ // when the session service is not started.
+ // Don't use Q_ASSERT(doc != nullptr); instead use:
+ if (doc == nullptr) {
+ setRWAHostSelected(false);
+
+ showToast(tr("Can't connect to underlying session service!"),
+ 9800,
+ Toast::ToastError);
+
+ // Go to the first page on the stack.
+ main_content_pop(nullptr);
+
+ return;
+ }
+
+
+ bool atLeastOneHostAvailable = false;
+
+ // Get the QJsonObject
+ QJsonObject jObject = doc->object();
+ QVariantMap mainMap = jObject.toVariantMap();
+
+ // Status of request
+ QString request_status = mainMap["status"].toString();
+ if (request_status == "success") {
+ // Building host_objects
+ QJsonArray host_objects = jObject.value("hosts").toArray();
+
+ QList<RWAHost*> *all_rwa_hosts = new QList<RWAHost*>;
+ foreach (const QJsonValue &host_object, host_objects) {
+ QString host_uuid = host_object["uuid"].toString();
+ QString host_alias = host_object["alias"].toString();
+ QString host_url = host_object["url"].toString();
+
+ if (host_url == "" || host_uuid == "") {
+ // This two values are required and can't be omitted.
+ QString reason = tr("A host object in the response of D-Bus service lacks"
+ " a necessary value. (host_url or host_uuid)");
+ qCritical().noquote() << tr("An error occured while adding a new host:")
+ << reason;
+
+ return;
+ }
+
+ if (host_alias == "") {
+ qDebug().noquote() << QString("An alias for the host wasn't delivered "
+ "so just use '%0' as alias.").arg(host_url);
+ host_alias = host_url;
+ }
+
+
+ // Now built RWAHost object.
+ RWAHost *rwa_host = new RWAHost(host_uuid, host_alias, host_url);
+ all_rwa_hosts->append(rwa_host);
+
+ bool found = false;
+ for (int i = 0; i < this->_rwaHostModel->size(); i++) {
+ RWAHost* old_host = qobject_cast<RWAHost*>(_rwaHostModel->value(i));
+ Q_ASSERT(old_host != nullptr);
+
+ if (rwa_host->uuid() == old_host->uuid()) {
+ found = true;
+ break;
+ }
+ }
+
+ atLeastOneHostAvailable = true;
+
+ if (!found) {
+ qInfo().noquote() << QString(tr("Successfully added new RWAHost '%0'"))
+ .arg(rwa_host->alias());
+ addRWAHost(rwa_host);
+ }
+ }
+
+ for (int i = 0; i < this->_rwaHostModel->size(); i++) {
+ RWAHost* old_host = qobject_cast<RWAHost*>(_rwaHostModel->value(i));
+ Q_ASSERT(old_host != nullptr);
+
+ bool found = false;
+ for (RWAHost *host : *all_rwa_hosts) {
+ if (host->uuid() == old_host->uuid()) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ removeRWAHost(old_host);
+ qInfo().noquote() << QString(tr("Removed RWAHost '%0'")).arg(old_host->alias());
+ }
+ }
+ } else {
+ QString reason = tr("An error occured while adding a new host:");
+ qCritical().noquote() << reason;
+
+ QString type = mainMap["type"].toString();
+ if (type != "") {
+ reason = QString(tr("The error is not clear. The session service "
+ "responded with status type '%0'")).arg(type);
+ qCritical().noquote() << reason;
+ } else {
+ reason = QString(tr("The error is not clear. The session service "
+ "responded with no status type!"));
+ qCritical().noquote() << reason;
+ }
+
+ return;
+ }
+
+ if (!atLeastOneHostAvailable) {
+ main_content_replace("scenes/Scene_no_server_available.qml");
+ }
+
+ setRWAHostSelected(atLeastOneHostAvailable);
+}
+
+void MainQMLAdaptor::main_content_push(QString scene) {
+ // Find item via 'objectName'
+ QObject *window = _engine->rootObjects().takeFirst();
+ Q_ASSERT(window != nullptr);
+
+ QObject *main_content = _engine->rootObjects().takeFirst()->findChild<QObject*>("main_content");
+ Q_ASSERT(main_content != nullptr);
+
+ QVariant to_cast = main_content->property("currentItem");
+ QObject *obj = qvariant_cast<QObject *>(to_cast);
+ if (obj) {
+ if (!(scene.contains(obj->objectName()))) {
+ QVariant arg = QVariant::fromValue(scene);
+ if(!QMetaObject::invokeMethod(window, "main_content_push", Q_ARG(QVariant, arg)))
+ qDebug() << "Failed to invoke push";
+ }
+ }
+}
+
+void MainQMLAdaptor::main_content_pop(QString scene) {
+ // Find item via 'objectName'
+ QObject *window = _engine->rootObjects().takeFirst();
+ Q_ASSERT(window != nullptr);
+
+ QObject *main_content = _engine->rootObjects().takeFirst()->
+ findChild<QObject*>("main_content");
+ Q_ASSERT(main_content != nullptr);
+
+ QVariant to_cast = main_content->property("currentItem");
+ QObject *obj = qvariant_cast<QObject *>(to_cast);
+ if (obj) {
+ if (!(scene.contains(obj->objectName()))) {
+ QVariant arg = QVariant::fromValue(scene);
+ if(!QMetaObject::invokeMethod(window, "main_content_pop", Q_ARG(QVariant, arg)))
+ qDebug() << "Failed to invoke pop";
+ }
+ }
+}
+
+void MainQMLAdaptor::main_content_replace(QString scene) {
+ // Find item via 'objectName'
+ QObject *window = _engine->rootObjects().takeFirst();
+ Q_ASSERT(window != nullptr);
+
+ QObject *main_content = _engine->rootObjects().takeFirst()->
+ findChild<QObject*>("main_content");
+ Q_ASSERT(main_content != nullptr);
+
+ QVariant to_cast = main_content->property("currentItem");
+ QObject *obj = qvariant_cast<QObject *>(to_cast);
+ if (obj) {
+ QString scene_add_rwahost_wizard = "Scene_step_1";
+ if (!(scene.contains(obj->objectName()) ||
+ scene_add_rwahost_wizard.contains(obj->objectName()))) {
+ QVariant arg = QVariant::fromValue(scene);
+ if (!QMetaObject::invokeMethod(window, "main_content_replace",
+ Q_ARG(QVariant, arg))) {
+ qDebug() << "Failed to invoke replace";
+ }
+ }
+ }
+}
+
+bool MainQMLAdaptor::openMessageDialog(QString title, QString text, QMessageBox::Icon icon) {
+ _messageDialogText = text;
+ _messageDialogTitle = title;
+ _messageDialogIcon = icon;
+ _showMessageDialog = true;
+ emit messageDialogIconChanged(_messageDialogIcon);
+ emit messageDialogTitleChanged(_messageDialogTitle);
+ emit messageDialogTextChanged(_messageDialogText);
+
+ emit showMessageDialogChanged(_showMessageDialog);
+
+ qDebug() << "Opening MessageDialog!";
+ return true;
+}
+
+QString MainQMLAdaptor::getMessageDialogTitle() {
+ return _messageDialogTitle;
+}
+
+QString MainQMLAdaptor::getMessageDialogText() {
+ return _messageDialogText;
+}
+
+QMessageBox::Icon MainQMLAdaptor::getMessageDialogIcon() {
+ return _messageDialogIcon;
+}
+
+bool MainQMLAdaptor::getShowMessageDialog() {
+ return _showMessageDialog;
+}
+
+void MainQMLAdaptor::onCloseHandler() {
+ // Do cleanup things here...
+ emit onCloseSignal();
+}
+
+void MainQMLAdaptor::showToast(QString text, uint durationMs, uint type) {
+ // type is actually Toast::ToastType
+ emit showToastSignal(text, QString::number(durationMs), type);
+}
diff --git a/rwa-support-desktopapp/src/main_qmladaptor.h b/rwa-support-desktopapp/src/main_qmladaptor.h
new file mode 100644
index 0000000..70c075b
--- /dev/null
+++ b/rwa-support-desktopapp/src/main_qmladaptor.h
@@ -0,0 +1,144 @@
+/*
+ * This file is part of Remote Support Desktop
+ * https://gitlab.das-netzwerkteam.de/RemoteWebApp/rwa.support.desktopapp
+ * Copyright 2020, 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+ * Copyright 2020, 2021 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <QQmlApplicationEngine>
+#include <QQuickItem>
+#include <QMessageBox>
+#include <QApplication>
+#include <QClipboard>
+#include <QJsonObject>
+#include <QJsonDocument>
+#include <QJsonArray>
+#include <QTimer>
+
+#include "RWAHost.h"
+#include "DBusAPI.h"
+
+namespace Toast {
+ Q_NAMESPACE
+ enum ToastType {
+ ToastStandard,
+ ToastInfo,
+ ToastSuccess,
+ ToastWarning,
+ ToastError
+ };
+ Q_ENUM_NS(ToastType)
+}
+
+/*#include <QObject>
+namespace MyNamespace
+{
+ Q_NAMESPACE // required for meta object creation
+ enum EnStyle {
+ STYLE_RADIAL,
+ STYLE_ENVELOPE,
+ STYLE_FILLED
+ };
+ Q_ENUM_NS(EnStyle) // register the enum in meta object data
+}*/
+
+class MainQMLAdaptor : public QObject {
+ Q_OBJECT
+ // this makes rwaHostModel available as a QML property
+ Q_PROPERTY(QList<QObject*> rwaHostModel READ getRWAHostModel NOTIFY rwaHostModelChanged)
+ // this makes showMessageDialog available as a QML property
+ Q_PROPERTY(bool showMessageDialog READ getShowMessageDialog NOTIFY showMessageDialogChanged)
+ // this makes showMessageDialogTitle available as a QML property
+ Q_PROPERTY(QString _messageDialogTitle READ getMessageDialogTitle NOTIFY messageDialogTitleChanged)
+ // this makes showMessageDialogText available as a QML property
+ Q_PROPERTY(QString _messageDialogText READ getMessageDialogText NOTIFY messageDialogTextChanged)
+ // this makes showMessageDialogIcon available as a QML property
+ Q_PROPERTY(QMessageBox::Icon _messageDialogIcon READ getMessageDialogIcon NOTIFY messageDialogIconChanged)
+
+
+public:
+ explicit MainQMLAdaptor(QObject *parent, QQmlApplicationEngine *engine = nullptr,
+ DBusAPI *dbus_api = nullptr);
+
+ void setRWAHostModel(QList<QObject*> *rwa_hosts);
+
+ void main_content_push(QString);
+ void main_content_pop(QString);
+ void main_content_replace(QString);
+
+ bool openMessageDialog(QString title, QString text, QMessageBox::Icon);
+ QString getMessageDialogTitle();
+ QString getMessageDialogText();
+ QMessageBox::Icon getMessageDialogIcon();
+ bool getShowMessageDialog();
+
+signals:
+ void showMessageDialogChanged(bool show);
+ void messageDialogTextChanged(QString text);
+ void messageDialogTitleChanged(QString title);
+ void messageDialogIconChanged(int iconindex);
+
+ void minimizeWindow();
+ void showWindow();
+
+ void rwaHostModelChanged(QList<QObject*>);
+
+ void onCloseSignal();
+
+ void showToastSignal(QString text, QString durationMs, int type);
+
+protected:
+ DBusAPI *_dbus_api;
+ QList<QObject*> *_rwaHostModel;
+
+private:
+ QQmlApplicationEngine *_engine;
+ RWAHost *_selected_rwa_host;
+
+ bool _showMessageDialog;
+ QString _messageDialogTitle;
+ QString _messageDialogText;
+ QMessageBox::Icon _messageDialogIcon;
+
+public slots:
+ void get_web_app_hosts_response(QJsonDocument *doc);
+
+ void addRWAHost(RWAHost *rwa_host);
+ void removeRWAHost(RWAHost *rwa_host);
+ void setRWAHostSelected(bool value);
+
+ // No pointer because QML doesn't
+ // like this type much with pointer
+ QList<QObject*> getRWAHostModel();
+
+ RWAHost *getSelectedRWAHost();
+
+ void onRwaHostSelected(QString host_uuid);
+ void onCloseHandler();
+
+ // arg type is actually Toast::ToastType
+ void showToast(QString text,
+ uint durationMs = 3000,
+ uint type = 0);
+};
diff --git a/rwa-support-desktopapp/src/scenes/Scene_no_server_available.qml b/rwa-support-desktopapp/src/scenes/Scene_no_server_available.qml
new file mode 100644
index 0000000..7b99b55
--- /dev/null
+++ b/rwa-support-desktopapp/src/scenes/Scene_no_server_available.qml
@@ -0,0 +1,71 @@
+import QtQuick 2.9
+import QtQuick.Window 2.2
+import QtQuick.Extras 1.4
+import QtQuick.Controls 2.2
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls.Material 2.3
+
+/*!
+ * This .qml file is a Scene which can be loaded through for
+ * example a StackView (main_content in main.qml).
+ */
+
+Item {
+ id: scene_no_server_available
+ objectName: "Scene_no_server_available"
+
+ Rectangle {
+ id: rectangle
+ anchors.fill: parent
+ color: Material.background
+
+ Text {
+ color: Material.foreground
+ id: title
+
+ text: qsTr("Welcome!")
+ font.pointSize: 20
+ font.bold: true
+ wrapMode: Text.WordWrap
+
+ horizontalAlignment: Text.AlignLeft
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: 15
+ }
+
+ Text {
+ color: Material.foreground
+ anchors.top: title.bottom
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: 15
+
+ horizontalAlignment: Text.AlignLeft
+ wrapMode: Text.WordWrap
+ /*: 'Add RWA-Server' has to be replaced with the correct translation in file main.qml .*/
+ font.pointSize: 13
+ text: qsTr("You need to add and select the remote \
+web app server to which you want to connect. \
+You can see a button to the left in the menu \
+which says 'Add RWA-Server'. Follow the steps \
+listed there and you can start your remote \
+support session afterwards using the buttons \
+above 'Add RWA-Server'.")
+ }
+ }
+}
+
+
+
+
+
+
+
+/*##^## Designer {
+ D{i:0;autoSize:true;height:480;width:640}
+}
+ ##^##*/
diff --git a/rwa-support-desktopapp/src/scenes/Scene_placeholder.qml b/rwa-support-desktopapp/src/scenes/Scene_placeholder.qml
new file mode 100644
index 0000000..f492e00
--- /dev/null
+++ b/rwa-support-desktopapp/src/scenes/Scene_placeholder.qml
@@ -0,0 +1,56 @@
+import QtQuick 2.9
+import QtQuick.Window 2.2
+import QtQuick.Extras 1.4
+import QtQuick.Controls 2.2
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls.Material 2.3
+
+/*!
+ * This .qml file is a Scene which can be loaded through for
+ * example a StackView (main_content in main.qml).
+ */
+
+Item {
+ id: scene_placeholder
+ objectName: "Scene_placeholder"
+
+ Rectangle {
+ id: rectangle
+ anchors.fill: parent
+ color: Material.background
+
+ Text {
+ color: Material.foreground
+ id: title
+
+ text: qsTr("This is the placeholder scene!")
+ font.pointSize: 18
+ wrapMode: Text.WordWrap
+
+ font.bold: true
+ horizontalAlignment: Text.AlignHCenter
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: 5
+ }
+
+ Text {
+ color: Material.foreground
+ anchors.top: title.bottom
+ anchors.margins: 5
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ wrapMode: Text.WordWrap
+ text: qsTr("The features you expected here are not yet implemented.")
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+}
+
+/*##^## Designer {
+ D{i:0;autoSize:true;height:480;width:640}
+}
+ ##^##*/
diff --git a/rwa-support-desktopapp/src/scenes/Scene_remote_view.qml b/rwa-support-desktopapp/src/scenes/Scene_remote_view.qml
new file mode 100644
index 0000000..436d8aa
--- /dev/null
+++ b/rwa-support-desktopapp/src/scenes/Scene_remote_view.qml
@@ -0,0 +1,371 @@
+import QtQuick 2.9
+import QtQuick.Window 2.2
+import QtQuick.Extras 1.4
+import QtQuick.Controls 2.2
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls.Material 2.3
+
+/*!
+ * This .qml file is a Scene which can be loaded through for
+ * example a StackView (main_content in main.qml).
+ */
+
+Item {
+ id: scene_remote_view
+ objectName: "Scene_remote_view"
+
+ Label {
+ id: dbus_api_status_text
+ text: "Unknown state of Service"
+ anchors.leftMargin: 10 + 5 + dbus_api_status_indicator.width
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 10
+ wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+ verticalAlignment: Text.AlignVCenter
+ font.pointSize: 11
+ fontSizeMode: Text.Fit
+ objectName: "dbus_api_status_text"
+ anchors.left: parent.left
+
+ StatusIndicator {
+ id: dbus_api_status_indicator
+ width: height
+ height: parent.height
+ objectName: "dbus_api_status_indicator"
+ color: "#73d216"
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.right: parent.left
+ anchors.rightMargin: 5
+ active: false
+ }
+ }
+
+ Label {
+ id: explain_function_label
+ text: qsTr("Please tell your remote support partner your access address and your access-PIN to let your partner connect to this computer.")
+ font.pixelSize: 18
+ fontSizeMode: Text.VerticalFit
+ wrapMode: Text.WordWrap
+ anchors.left: parent.left
+ anchors.leftMargin: 10
+ anchors.top: parent.top
+ anchors.topMargin: 10
+ anchors.right: parent.right
+ anchors.rightMargin: 10
+ horizontalAlignment: Text.AlignLeft
+ enabled: false
+
+ color: Material.theme == Material.Light ? "#000000" : "#FFFFFF"
+ }
+
+ Rectangle {
+ id: dbus_api_status_line
+ y: 379
+ height: 1
+ radius: 1
+ anchors.right: parent.right
+ anchors.rightMargin: 10
+ anchors.bottom: dbus_api_status_text.top
+ anchors.bottomMargin: 10
+ opacity: 0.3
+ gradient: Gradient {
+ GradientStop {
+ position: 0.391
+ color: "#ffffff"
+ }
+
+ GradientStop {
+ position: 0.975
+ color: "#8b8b8b"
+ }
+ }
+ border.width: 1
+ border.color: "#00000000"
+ anchors.left: parent.left
+ anchors.leftMargin: 10
+ }
+
+ Column {
+ id: column
+ spacing: 6
+ anchors.right: parent.right
+ anchors.rightMargin: 10
+ anchors.left: parent.left
+ anchors.leftMargin: 10
+ anchors.bottom: dbus_api_status_line.top
+ anchors.bottomMargin: 10
+ anchors.top: explain_function_label.bottom
+ anchors.topMargin: 10
+
+ Column {
+ id: url_group
+ width: parent.width
+ height: parent.height * 0.25
+ spacing: 5
+
+ Label {
+ id: your_url_text
+ height: parent.height/2
+ text: qsTr("Remote viewing Address")
+ font.weight: Font.Bold
+ font.bold: true
+ verticalAlignment: Text.AlignBottom
+ horizontalAlignment: Text.AlignLeft
+ font.pointSize: 14
+ fontSizeMode: Text.Fit
+ }
+
+ TextEdit {
+ id: url_text
+ height: parent.height/2
+ text: mainqmladaptor.url
+ anchors.rightMargin: 10 + copy_url_to_clipboard_button.width
+ anchors.right: parent.right
+ wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+ anchors.leftMargin: 10
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignLeft
+ font.pointSize: 15
+
+ readOnly: true
+ color: Material.foreground
+ selectByMouse: true
+ anchors.left: parent.left
+
+ leftPadding: 5
+ Rectangle {
+ radius: 5
+ color: Material.theme == Material.Light ? "#F0F0F0" : "#383838"
+ height: url_text.height
+ // whole line + copy-into-clipboard button + some margin
+ width: url_text.width + copy_url_to_clipboard_button.width + 5 + 5
+ x: 0; y: 0
+ z: -1
+ }
+
+ Button {
+ id: copy_url_to_clipboard_button
+ width: copy_url_to_clipboard_image.width + 6
+ height: copy_url_to_clipboard_image.height + 6 + 10
+ anchors.verticalCenter: parent.verticalCenter
+ display: AbstractButton.IconOnly
+ anchors.leftMargin: 5
+ anchors.left: url_text.right
+ highlighted: false
+ flat: true
+
+ Image {
+ id: copy_url_to_clipboard_image
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ source: "../../images/into-clipboard.svg"
+ opacity: 0.65
+ }
+
+ onClicked: {
+ mainqmladaptor.handleCopyToClipboardButtonClick(url_text.text);
+ toast.show(qsTr("Copied access address into clipboard!"), "1000");
+ }
+
+ ToolTip.text: qsTr("Copy the access address into the clipboard")
+ hoverEnabled: true
+
+ ToolTip.delay: 1000
+ ToolTip.timeout: 5000
+ ToolTip.visible: hovered
+ }
+ }
+ }
+
+ Column {
+ id: session_id_group
+ width: parent.width
+ height: parent.height * 0.25
+ spacing: 5
+
+ Label {
+ id: your_session_id_text
+ height: parent.height/2
+ text: qsTr("Session-ID")
+ font.weight: Font.Bold
+ font.bold: true
+ anchors.right: parent.right
+ anchors.rightMargin: 0
+ anchors.left: parent.left
+ anchors.leftMargin: 0
+ font.pointSize: 14
+ verticalAlignment: Text.AlignBottom
+ horizontalAlignment: Text.AlignLeft
+ fontSizeMode: Text.Fit
+ }
+
+ TextEdit {
+ objectName: "session_id_text"
+ id: session_id_text
+ height: parent.height/2
+ text: mainqmladaptor.session_id
+ font.letterSpacing: 10
+ anchors.rightMargin: 10 + copy_session_id_to_clipboard_button.width
+ anchors.right: parent.right
+ font.pointSize: 15
+ anchors.left: parent.left
+ anchors.leftMargin: 10
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+
+ leftPadding: 5
+ Rectangle {
+ radius: 5
+ color: Material.theme == Material.Light ? "#F0F0F0" : "#383838"
+ height: url_text.height
+ // whole line + copy-into-clipboard button + some margin
+ width: url_text.width + copy_url_to_clipboard_button.width + 5 + 5
+ x: 0; y: 0
+ z: -1
+ }
+
+ readOnly: true
+ color: Material.foreground
+ wrapMode: Text.WordWrap
+ selectByMouse: true
+
+ Button {
+ id: copy_session_id_to_clipboard_button
+ width: copy_session_id_to_clipboard_image.width + 6
+ height: copy_session_id_to_clipboard_image.height + 6 + 10
+ anchors.verticalCenter: parent.verticalCenter
+ flat: true
+ display: AbstractButton.IconOnly
+ anchors.left: session_id_text.right
+ anchors.leftMargin: 5
+
+ Image {
+ id: copy_session_id_to_clipboard_image
+ anchors.horizontalCenter: parent.horizontalCenter
+ opacity: 0.65
+ anchors.verticalCenter: parent.verticalCenter
+ source: "../../images/into-clipboard.svg"
+ fillMode: Image.PreserveAspectFit
+ }
+
+ onClicked: {
+ mainqmladaptor.handleCopyToClipboardButtonClick(pin_text.text);
+ toast.show(qsTr("Copied session-ID into clipboard!"), "1000");
+ }
+
+ ToolTip.text: qsTr("Copy the session-ID into the clipboard")
+ hoverEnabled: true
+
+ ToolTip.delay: 1000
+ ToolTip.timeout: 5000
+ ToolTip.visible: hovered
+ }
+ }
+ }
+
+ Column {
+ id: pin_group
+ width: parent.width
+ height: parent.height * 0.25
+ spacing: 5
+
+ Label {
+ id: your_pin_text
+ height: parent.height/2
+ text: qsTr("Access-PIN")
+ font.weight: Font.Bold
+ font.bold: true
+ anchors.right: parent.right
+ anchors.rightMargin: 0
+ anchors.left: parent.left
+ anchors.leftMargin: 0
+ font.pointSize: 14
+ verticalAlignment: Text.AlignBottom
+ horizontalAlignment: Text.AlignLeft
+ }
+
+ TextEdit {
+ objectName: "pin_text"
+ id: pin_text
+ height: parent.height/2
+ text: mainqmladaptor.pin
+ anchors.rightMargin: 10 + copy_pin_to_clipboard_button.width
+ anchors.right: parent.right
+ font.pointSize: 15
+ anchors.left: parent.left
+ anchors.leftMargin: 10
+ font.letterSpacing: 10
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+
+ leftPadding: 5
+ Rectangle {
+ radius: 5
+ color: Material.theme == Material.Light ? "#F0F0F0" : "#383838"
+ height: url_text.height
+ // whole line + copy-into-clipboard button + some margin
+ width: url_text.width + copy_url_to_clipboard_button.width + 5 + 5
+ x: 0; y: 0
+ z: -1
+ }
+
+ readOnly: true
+ color: Material.foreground
+ wrapMode: Text.WordWrap
+ selectByMouse: true
+
+ Button {
+ id: copy_pin_to_clipboard_button
+ width: copy_pin_to_clipboard_image.width + 6
+ height: copy_pin_to_clipboard_image.height + 6 + 10
+ anchors.verticalCenter: parent.verticalCenter
+ flat: true
+ display: AbstractButton.IconOnly
+ anchors.left: pin_text.right
+ anchors.leftMargin: 5
+
+ Image {
+ id: copy_pin_to_clipboard_image
+ anchors.verticalCenter: parent.verticalCenter
+ opacity: 0.65
+ anchors.horizontalCenter: parent.horizontalCenter
+ source: "../../images/into-clipboard.svg"
+ fillMode: Image.PreserveAspectFit
+ }
+
+ onClicked: {
+ mainqmladaptor.handleCopyToClipboardButtonClick(pin_text.text);
+ toast.show(qsTr("Copied PIN into clipboard!"), "1000");
+ }
+
+ ToolTip.text: qsTr("Copy the pin into the clipboard")
+ hoverEnabled: true
+
+ ToolTip.delay: 1000
+ ToolTip.timeout: 5000
+ ToolTip.visible: hovered
+ }
+ }
+ }
+
+ }
+
+ Button {
+ id: start_support_button
+ height: Math.min(50)
+ objectName: "start_support_button"
+ text: qsTr("Start remote viewing session")
+ anchors.rightMargin: column.anchors.leftMargin
+ anchors.bottom: dbus_api_status_line.top
+ anchors.bottomMargin: 10
+ anchors.right: parent.right
+ checkable: true
+
+ onClicked: mainqmladaptor.handleConnectButtonClick(checked);
+ }
+}
+
+/*##^## Designer {
+ D{i:0;autoSize:true;height:480;width:640}
+}
+ ##^##*/
diff --git a/rwa-support-desktopapp/src/scenes/Scene_settings.qml b/rwa-support-desktopapp/src/scenes/Scene_settings.qml
new file mode 100644
index 0000000..e2f89da
--- /dev/null
+++ b/rwa-support-desktopapp/src/scenes/Scene_settings.qml
@@ -0,0 +1,34 @@
+import QtQuick 2.9
+import QtQuick.Window 2.2
+import QtQuick.Extras 1.4
+import QtQuick.Controls 2.2
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls.Material 2.3
+
+/*!
+ * This .qml file is a Scene which can be loaded through for
+ * example a StackView (main_content in main.qml).
+ */
+
+Item {
+ id: scene_settings
+ objectName: "Scene_settings"
+
+ Rectangle {
+ id: rectangle
+ anchors.fill: parent
+ color: Material.background
+
+ Text {
+ color: Material.foreground
+ text: "Settings tab!"
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+}
+
+/*##^## Designer {
+ D{i:0;autoSize:true;height:480;width:640}
+}
+ ##^##*/
diff --git a/rwa-support-desktopapp/src/scenes/add_rwahost_wizard/Scene_step_1.qml b/rwa-support-desktopapp/src/scenes/add_rwahost_wizard/Scene_step_1.qml
new file mode 100644
index 0000000..873e0fd
--- /dev/null
+++ b/rwa-support-desktopapp/src/scenes/add_rwahost_wizard/Scene_step_1.qml
@@ -0,0 +1,282 @@
+import QtQuick 2.9
+import QtQuick.Window 2.2
+import QtQuick.Extras 1.4
+import QtQuick.Controls 2.2
+import QtQuick.Controls.Material 2.3
+import rwa.toast.type 1.0
+
+/*!
+ * This .qml file is a Scene which can be loaded through for
+ * example a StackView (main_content in main.qml).
+ */
+
+Item {
+ id: scene_server_wizard_step_1
+ objectName: "Scene_step_1"
+
+ Connections {
+ target: add_rwahost_wizard
+ function onStep1Success() {
+ // Go onto the first page of the stack.
+ main_content_pop(null)
+ mainqmladaptor.showToast(qsTr("Successfully added remote web app host."),
+ 5000,
+ ToastType.ToastSuccess);
+ }
+ }
+
+ Connections {
+ target: add_rwahost_wizard
+ function onStep1Failed(reason, toast_type) {
+ mainqmladaptor.showToast(reason,
+ 5000,
+ toast_type)
+ }
+ }
+
+ Rectangle {
+ id: rectangle
+ anchors.fill: parent
+ color: Material.background
+
+ Button {
+ id: next_step1_button
+ text: qsTr("Next Step")
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 15
+ anchors.right: parent.right
+ anchors.rightMargin: 15
+
+ onClicked: {
+ add_rwahost_wizard.processStep1(host_url.text, host_alias.text)
+ }
+ }
+
+ /*Text {
+ color: Material.foreground
+ id: step_indicator
+
+ text: qsTr("Step 1")
+ anchors.leftMargin: 15
+ anchors.top: parent.top
+ anchors.topMargin: 15
+ font.pointSize: 21
+ wrapMode: Text.WordWrap
+
+ font.bold: true
+ horizontalAlignment: Text.AlignHCenter
+ anchors.left: parent.left
+ anchors.margins: 5
+ }*/
+
+ Text {
+ color: Material.foreground
+ id: explaining_text
+
+ text: qsTr("Please input the address for the "+
+ "remote web app server which you want "+
+ "to connect to.\nIf you don't know "+
+ "what this means, ask your local "+
+ "administrator about it please.\nBefore you can "+
+ "start any remote sessions you will have to "+
+ "be approved for remote support.")
+ font.pointSize: 12
+ anchors.right: parent.right
+ anchors.rightMargin: 15
+ anchors.leftMargin: 15
+ anchors.top: parent.top //step_indicator.bottom
+ anchors.topMargin: 15
+ wrapMode: Text.WordWrap
+
+ anchors.left: parent.left
+ anchors.margins: 5
+ }
+
+ TextField {
+ id: host_url
+ selectByMouse: true
+ placeholderText: qsTr("http://example.com:8000")
+
+ font.pixelSize: 16
+ color: Material.foreground
+ anchors.topMargin: 30
+
+ padding: 15
+ topPadding: 15
+
+ anchors.top: explaining_text.bottom
+ anchors.left: parent.left
+ anchors.leftMargin: 15
+ anchors.right: parent.right
+ anchors.margins: 30
+
+ MouseArea {
+ anchors.fill: parent
+ hoverEnabled: true
+ onHoveredChanged: {
+ if (containsMouse) {
+ host_url_background.state = "hovered"
+ } else if (!host_url.focus) {
+ host_url_background.state = "unhovered"
+ }
+ }
+ onClicked: {
+ host_url.forceActiveFocus();
+ }
+ }
+
+ onFocusChanged: {
+ host_url_background.state = host_url.focus ? "hovered" : "unhovered"
+ }
+
+ background: Rectangle {
+ id: host_url_background
+ color: Material.background
+ border.color: Material.foreground
+ border.width: 1
+ radius: 4
+
+ states: [
+ State {
+ name: "hovered"
+ PropertyChanges {
+ target: host_url_background
+ border.color: "#0178EF"
+ }
+ },
+ State {
+ name: "unhovered"
+ PropertyChanges {
+ target: host_url_background
+ border.color: Material.foreground
+ }
+ }
+ ]
+ transitions: [
+ Transition {
+ from: "*"
+ to: "*"
+ PropertyAnimation {
+ property: "border.color"
+ duration: 100
+ easing.type: Easing.Linear
+ }
+ }
+ ]
+
+ Text {
+ color: Material.foreground
+ text: qsTr("RWA host address")
+ anchors.left: parent.left
+ anchors.leftMargin: 15
+ anchors.verticalCenterOffset: - host_url.height / 2
+ anchors.verticalCenter: parent.verticalCenter
+ leftPadding: 5
+
+ Rectangle {
+ color: Material.background
+ width: parent.width + 5 + 3
+ height: parent.height
+ z: -1
+ }
+ }
+ }
+ }
+
+ TextField {
+ id: host_alias
+ selectByMouse: true
+ placeholderText: qsTr("My example host")
+
+ font.pixelSize: 16
+ color: Material.foreground
+
+ padding: 15
+ topPadding: 15
+
+ anchors.top: host_url.bottom
+ anchors.topMargin: 30
+ anchors.left: parent.left
+ anchors.leftMargin: 15
+ anchors.right: parent.right
+ anchors.margins: 30
+
+ MouseArea {
+ anchors.fill: parent
+ hoverEnabled: true
+ onHoveredChanged: {
+ if (containsMouse) {
+ host_alias_background.state = "hovered"
+ } else if (!host_alias.focus) {
+ host_alias_background.state = "unhovered"
+ }
+ }
+ onClicked: {
+ host_alias.forceActiveFocus();
+ }
+ }
+
+ onFocusChanged: {
+ host_alias_background.state = host_alias.focus ? "hovered" : "unhovered"
+ }
+
+ background: Rectangle {
+ id: host_alias_background
+ color: Material.background
+ border.color: Material.foreground
+ border.width: 1
+ radius: 4
+
+ states: [
+ State {
+ name: "hovered"
+ PropertyChanges {
+ target: host_alias_background
+ border.color: "#0178EF"
+ }
+ },
+ State {
+ name: "unhovered"
+ PropertyChanges {
+ target: host_alias_background
+ border.color: Material.foreground
+ }
+ }
+ ]
+ transitions: [
+ Transition {
+ from: "*"
+ to: "*"
+ PropertyAnimation {
+ property: "border.color"
+ duration: 100
+ easing.type: Easing.Linear
+ }
+ }
+ ]
+
+ Text {
+ color: Material.foreground
+ text: qsTr("RWA host alias")
+ anchors.left: parent.left
+ anchors.leftMargin: 15
+ anchors.verticalCenterOffset: - host_alias.height / 2
+ anchors.verticalCenter: parent.verticalCenter
+ leftPadding: 5
+
+ Rectangle {
+ color: Material.background
+ width: parent.width + 5 + 3
+ height: parent.height
+ z: -1
+ }
+ }
+ }
+ }
+ }
+}
+
+/*##^## Designer {
+ D{i:0;autoSize:true;height:480;width:640}D{i:4;anchors_width:351;anchors_x:279}
+}
+ ##^##*/
diff --git a/rwa-support-desktopapp/src/scenes/add_rwahost_wizard/add_rwahost_wizard.cpp b/rwa-support-desktopapp/src/scenes/add_rwahost_wizard/add_rwahost_wizard.cpp
new file mode 100644
index 0000000..7adbdf9
--- /dev/null
+++ b/rwa-support-desktopapp/src/scenes/add_rwahost_wizard/add_rwahost_wizard.cpp
@@ -0,0 +1,124 @@
+/*
+ * This file is part of Remote Support Desktop
+ * https://gitlab.das-netzwerkteam.de/RemoteWebApp/rwa.support.desktopapp
+ * Copyright 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "add_rwahost_wizard.h"
+#include "../../RWADBusAdaptor.h"
+#include "../../RWAHost.h"
+
+AddRWAHostWizard::AddRWAHostWizard(QObject *parent,
+ MainQMLAdaptor *main_gui, DBusAPI *dbus_api) : QObject(parent) {
+ Q_ASSERT(main_gui != nullptr);
+ Q_ASSERT(dbus_api != nullptr);
+
+ _dbus_api = dbus_api;
+ _main_gui = main_gui;
+
+ // _dbus_api --serviceAddWebAppHostResponse-> this.add_web_app_host_response()
+ QObject::connect(_dbus_api,
+ SIGNAL(serviceAddWebAppHostResponse(QJsonDocument*)),
+ this,
+ SLOT(add_web_app_host_response(QJsonDocument*)));
+}
+
+void AddRWAHostWizard::processStep1(QString host_url, QString host_alias) {
+ qDebug() << "Processing Step 1 with args: " << host_url << host_alias;
+
+ if(host_alias == "" || host_url == "") {
+ QString reason = tr("Both textfields can't be empty!");
+ emit step1Failed(reason, Toast::ToastType::ToastWarning);
+ qDebug().noquote() << reason;
+ return;
+ }
+
+ return add_server(host_url, host_alias);
+}
+
+void AddRWAHostWizard::processStep2() {
+ qDebug() << "Processing Step 2 with args: No Args.";
+ emit step2Failed(tr("The features you expected here are not yet implemented."), Toast::ToastType::ToastWarning);
+ // Just show placeholder scene now.
+ emit step2Success();
+}
+
+void AddRWAHostWizard::add_server(QString host_url, QString host_alias) {
+ _dbus_api->add_web_app_host_request(host_url, host_alias);
+}
+
+void AddRWAHostWizard::add_web_app_host_response(QJsonDocument *doc) {
+ // Q_ASSERT lets the program crash immediatly at startup,
+ // when the session service is not started.
+ // Don't use Q_ASSERT(doc != nullptr); instead use:
+ if (doc == nullptr) {
+ _main_gui->setRWAHostSelected(false);
+ _main_gui->showToast(tr("Can't connect to underlying session service!"),
+ 9800,
+ Toast::ToastType::ToastError);
+ return;
+ }
+
+ // Get the QJsonObject
+ QJsonObject jObject = doc->object();
+ QVariantMap mainMap = jObject.toVariantMap();
+
+ // Status of request
+ QString request_status = mainMap["status"].toString();
+ if (request_status == "success") {
+ _dbus_api->get_web_app_hosts_request();
+
+ qInfo() << "Successfully added a new RWAHost.";
+ emit step1Success();
+ } else {
+ qCritical().noquote() << tr("An error occured while adding a new host!");
+
+ uint toast_type = Toast::ToastType::ToastStandard;
+ QString reason = tr("An error occured while adding a new host!");
+ QString type = mainMap["type"].toString();
+
+ if(type == "connection"){
+ reason = tr("Couldn't connect to the specified host!");
+ toast_type = Toast::ToastType::ToastError;
+ qCritical().noquote() << reason;
+ } else if (type == "duplicate") {
+ reason = tr("The specified host was already added!");
+ toast_type = Toast::ToastType::ToastWarning;
+ qCritical().noquote() << reason;
+ } else if (type == "invalid_url") {
+ reason = tr("The specified host address is not valid!");
+ toast_type = Toast::ToastType::ToastWarning;
+ qCritical().noquote() << reason;
+ } else if (type == "permission_denied") {
+ reason = tr("The specified host address does not grant access!");
+ toast_type = Toast::ToastType::ToastError;
+ qCritical().noquote() << reason;
+ } else if (type == "unsupported_server") {
+ reason = tr("The specified host address is not supported!");
+ toast_type = Toast::ToastType::ToastError;
+ qCritical().noquote() << reason;
+ }
+ emit step1Failed(reason, toast_type);
+
+ return;
+ }
+}
diff --git a/rwa-support-desktopapp/src/scenes/add_rwahost_wizard/add_rwahost_wizard.h b/rwa-support-desktopapp/src/scenes/add_rwahost_wizard/add_rwahost_wizard.h
new file mode 100644
index 0000000..f019be1
--- /dev/null
+++ b/rwa-support-desktopapp/src/scenes/add_rwahost_wizard/add_rwahost_wizard.h
@@ -0,0 +1,61 @@
+/*
+ * This file is part of Remote Support Desktop
+ * https://gitlab.das-netzwerkteam.de/RemoteWebApp/rwa.support.desktopapp
+ * Copyright 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef ADD_RWAHOST_WIZARD_H
+#define ADD_RWAHOST_WIZARD_H
+
+#include <QObject>
+
+#include "../../RWADBusAdaptor.h"
+#include "../../DBusAPI.h"
+#include "../../main_qmladaptor.h"
+
+class AddRWAHostWizard : public QObject
+{
+ Q_OBJECT
+public:
+ explicit AddRWAHostWizard(QObject *parent = nullptr,
+ MainQMLAdaptor *main_gui = nullptr,
+ DBusAPI *dbus_api = nullptr);
+ void add_server(QString host_url, QString host_alias);
+
+private:
+ DBusAPI *_dbus_api;
+ MainQMLAdaptor *_main_gui;
+
+signals:
+ void step1Success();
+ void step1Failed(QString reason, uint toast_type);
+ void step2Success();
+ void step2Failed(QString reason, uint toast_type);
+
+public slots:
+ void processStep1(QString host_url, QString host_alias);
+ void processStep2();
+
+ void add_web_app_host_response(QJsonDocument *doc);
+};
+
+#endif // ADD_SERVER_WIZARD_H
diff --git a/rwa-support-desktopapp/src/scenes/remote_control/Scene_remote_control.qml b/rwa-support-desktopapp/src/scenes/remote_control/Scene_remote_control.qml
new file mode 100644
index 0000000..dc152ef
--- /dev/null
+++ b/rwa-support-desktopapp/src/scenes/remote_control/Scene_remote_control.qml
@@ -0,0 +1,382 @@
+import QtQuick 2.9
+import QtQuick.Window 2.2
+import QtQuick.Extras 1.4
+import QtQuick.Controls 2.2
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls.Material 2.3
+
+/*!
+ * This .qml file is a Scene which can be loaded through for
+ * example a StackView (main_content in main.qml).
+ */
+
+Item {
+ id: scene_remote_control
+ objectName: "Scene_remote_control"
+
+ Label {
+ id: dbus_api_status_text
+ text: qsTr("Unknown state of session service.")
+ anchors.leftMargin: 10 + 5 + dbus_api_status_indicator.width
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 10
+ wrapMode: Text.WordWrap
+ anchors.rightMargin: 10
+ verticalAlignment: Text.AlignVCenter
+ font.pointSize: 11
+ fontSizeMode: Text.Fit
+ objectName: "dbus_api_status_text"
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ StatusIndicator {
+ id: dbus_api_status_indicator
+ width: height
+ height: 20
+ objectName: "dbus_api_status_indicator"
+ color: "#73d216"
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.right: parent.left
+ anchors.rightMargin: 5
+ active: false
+ }
+ }
+
+ Label {
+ id: explain_function_label
+ text: qsTr("Please tell your remote support \
+partner your access address and \
+your access-PIN to let your partner \
+connect to this computer.")
+ wrapMode: Text.WordWrap
+ font.pixelSize: 18
+ anchors.topMargin: 10
+ anchors.leftMargin: 10
+ anchors.rightMargin: 10
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ horizontalAlignment: Text.AlignLeft
+
+ color: Material.theme == Material.Light ? "#000000" : "#FFFFFF"
+ }
+
+ Rectangle {
+ id: dbus_api_status_line
+ height: 1
+ radius: 1
+ anchors.right: parent.right
+ anchors.rightMargin: 10
+ anchors.bottom: dbus_api_status_text.top
+ anchors.bottomMargin: 10
+ opacity: 0.3
+ gradient: Gradient {
+ GradientStop {
+ position: 0.391
+ color: "#ffffff"
+ }
+
+ GradientStop {
+ position: 0.975
+ color: "#8b8b8b"
+ }
+ }
+ border.width: 1
+ border.color: "#00000000"
+ anchors.left: parent.left
+ anchors.leftMargin: 10
+ }
+
+ Column {
+ id: column
+ spacing: 6
+ anchors.right: parent.right
+ anchors.rightMargin: 10
+ anchors.left: parent.left
+ anchors.leftMargin: 10
+ anchors.bottom: dbus_api_status_line.top
+ anchors.bottomMargin: 10
+ anchors.top: explain_function_label.bottom
+ anchors.topMargin: 10
+
+ Column {
+ id: url_group
+ width: parent.width
+ height: parent.height * 0.25
+ spacing: 5
+
+ Label {
+ id: your_url_text
+ height: parent.height/2
+ text: qsTr("Remote Support Address")
+ font.weight: Font.Bold
+ font.bold: true
+ verticalAlignment: Text.AlignBottom
+ horizontalAlignment: Text.AlignLeft
+ font.pointSize: 14
+ fontSizeMode: Text.Fit
+ }
+
+ TextEdit {
+ id: url_text
+ height: parent.height/2
+ text: remote_control_manager.url
+ anchors.rightMargin: 10 + copy_url_to_clipboard_button.width
+ anchors.right: parent.right
+ wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+ anchors.leftMargin: 10
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignLeft
+ font.pointSize: 15
+
+ readOnly: true
+ color: Material.foreground
+ selectByMouse: true
+ anchors.left: parent.left
+
+ leftPadding: 5
+ Rectangle {
+ radius: 5
+ color: Material.theme == Material.Light ? "#F0F0F0" : "#383838"
+ height: url_text.height
+ // whole line + copy-into-clipboard button + some margin
+ width: url_text.width + copy_url_to_clipboard_button.width + 5 + 5
+ x: 0; y: 0
+ z: -1
+ }
+
+ Button {
+ id: copy_url_to_clipboard_button
+ width: copy_url_to_clipboard_image.width + 6
+ height: copy_url_to_clipboard_image.height + 6 + 10
+ anchors.verticalCenter: parent.verticalCenter
+ display: AbstractButton.IconOnly
+ anchors.leftMargin: 5
+ anchors.left: url_text.right
+ highlighted: false
+ flat: true
+
+ Image {
+ id: copy_url_to_clipboard_image
+ x: 0
+ y: -26
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ source: "../../../images/into-clipboard.svg"
+ opacity: 0.65
+ }
+
+ onClicked: {
+ remote_control_manager.handleCopyToClipboardButtonClick(url_text.text);
+ toast.show(qsTr("Copied access address into clipboard!"),
+ "5000",
+ mainqmladaptor.ToastInfo);
+ }
+
+ ToolTip.text: qsTr("Copy the access address into the clipboard")
+ hoverEnabled: true
+
+ ToolTip.delay: 600
+ ToolTip.timeout: 5000
+ ToolTip.visible: hovered
+ }
+ }
+ }
+
+ Column {
+ id: session_id_group
+ width: parent.width
+ height: parent.height * 0.25
+ spacing: 5
+
+ Label {
+ id: your_session_id_text
+ height: parent.height/2
+ text: qsTr("Session-ID")
+ font.weight: Font.Bold
+ font.bold: true
+ anchors.right: parent.right
+ anchors.rightMargin: 0
+ anchors.left: parent.left
+ anchors.leftMargin: 0
+ font.pointSize: 14
+ verticalAlignment: Text.AlignBottom
+ horizontalAlignment: Text.AlignLeft
+ fontSizeMode: Text.Fit
+ }
+
+ TextEdit {
+ objectName: "session_id_text"
+ id: session_id_text
+ height: parent.height/2
+ text: remote_control_manager.session_id
+ font.letterSpacing: 10
+ anchors.rightMargin: 10 + copy_session_id_to_clipboard_button.width
+ anchors.right: parent.right
+ font.pointSize: 15
+ anchors.left: parent.left
+ anchors.leftMargin: 10
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+
+ leftPadding: 5
+ Rectangle {
+ radius: 5
+ color: Material.theme == Material.Light ? "#F0F0F0" : "#383838"
+ height: session_id_text.height
+ // whole line + copy-into-clipboard button + some margin
+ width: session_id_text.width + copy_session_id_to_clipboard_button.width + 5 + 5
+ x: 0; y: 0
+ z: -1
+ }
+
+ readOnly: true
+ color: Material.foreground
+ wrapMode: Text.WordWrap
+ selectByMouse: true
+
+ Button {
+ id: copy_session_id_to_clipboard_button
+ width: copy_session_id_to_clipboard_image.width + 6
+ height: copy_session_id_to_clipboard_image.height + 6 + 10
+ anchors.verticalCenter: parent.verticalCenter
+ flat: true
+ display: AbstractButton.IconOnly
+ anchors.left: session_id_text.right
+ anchors.leftMargin: 5
+
+ Image {
+ id: copy_session_id_to_clipboard_image
+ anchors.horizontalCenter: parent.horizontalCenter
+ opacity: 0.65
+ anchors.verticalCenter: parent.verticalCenter
+ source: "../../../images/into-clipboard.svg"
+ fillMode: Image.PreserveAspectFit
+ }
+
+ onClicked: {
+ remote_control_manager.handleCopyToClipboardButtonClick(session_id_text.text);
+ toast.show(qsTr("Copied session-ID into clipboard!"),
+ "5000",
+ mainqmladaptor.ToastInfo);
+ }
+
+ ToolTip.text: qsTr("Copy the session-ID into the clipboard")
+ hoverEnabled: true
+
+ ToolTip.delay: 600
+ ToolTip.timeout: 5000
+ ToolTip.visible: hovered
+ }
+ }
+ }
+
+ Column {
+ id: pin_group
+ width: parent.width
+ height: parent.height * 0.25
+ spacing: 5
+
+ Label {
+ id: your_pin_text
+ height: parent.height/2
+ text: qsTr("Access-PIN")
+ font.weight: Font.Bold
+ font.bold: true
+ anchors.right: parent.right
+ anchors.rightMargin: 0
+ anchors.left: parent.left
+ anchors.leftMargin: 0
+ font.pointSize: 14
+ verticalAlignment: Text.AlignBottom
+ horizontalAlignment: Text.AlignLeft
+ }
+
+ TextEdit {
+ objectName: "pin_text"
+ id: pin_text
+ height: parent.height/2
+ text: remote_control_manager.pin
+ anchors.rightMargin: 10 + copy_pin_to_clipboard_button.width
+ anchors.right: parent.right
+ font.pointSize: 15
+ anchors.left: parent.left
+ anchors.leftMargin: 10
+ font.letterSpacing: 10
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+
+ leftPadding: 5
+ Rectangle {
+ radius: 5
+ color: Material.theme == Material.Light ? "#F0F0F0" : "#383838"
+ height: url_text.height
+ // whole line + copy-into-clipboard button + some margin
+ width: url_text.width + copy_url_to_clipboard_button.width + 5 + 5
+ x: 0; y: 0
+ z: -1
+ }
+
+ readOnly: true
+ color: Material.foreground
+ wrapMode: Text.WordWrap
+ selectByMouse: true
+
+ Button {
+ id: copy_pin_to_clipboard_button
+ width: copy_pin_to_clipboard_image.width + 6
+ height: copy_pin_to_clipboard_image.height + 6 + 10
+ anchors.verticalCenter: parent.verticalCenter
+ flat: true
+ display: AbstractButton.IconOnly
+ anchors.left: pin_text.right
+ anchors.leftMargin: 5
+
+ Image {
+ id: copy_pin_to_clipboard_image
+ anchors.verticalCenter: parent.verticalCenter
+ opacity: 0.65
+ anchors.horizontalCenter: parent.horizontalCenter
+ source: "../../../images/into-clipboard.svg"
+ fillMode: Image.PreserveAspectFit
+ }
+
+ onClicked: {
+ remote_control_manager.handleCopyToClipboardButtonClick(pin_text.text);
+ toast.show(qsTr("Copied PIN into clipboard!"),
+ "5000",
+ mainqmladaptor.ToastInfo);
+ }
+
+ ToolTip.text: qsTr("Copy the pin into the clipboard")
+ hoverEnabled: true
+
+ ToolTip.delay: 600
+ ToolTip.timeout: 5000
+ ToolTip.visible: hovered
+ }
+ }
+ }
+
+ }
+
+ Button {
+ id: start_support_button
+ height: Math.min(50)
+ objectName: "start_support_button"
+ text: checked ? qsTr("Stop remote support session") : qsTr("Start remote support session")
+ anchors.rightMargin: column.anchors.leftMargin
+ anchors.bottom: dbus_api_status_line.top
+ anchors.bottomMargin: 10
+ anchors.right: parent.right
+ checkable: true
+
+ onClicked: remote_control_manager.handleConnectButtonClick(checked);
+ }
+}
+
+/*##^##
+Designer {
+ D{i:0;autoSize:true;height:480;width:640}
+}
+##^##*/
diff --git a/rwa-support-desktopapp/src/scenes/remote_control/remote_control_manager.cpp b/rwa-support-desktopapp/src/scenes/remote_control/remote_control_manager.cpp
new file mode 100644
index 0000000..b76ea2a
--- /dev/null
+++ b/rwa-support-desktopapp/src/scenes/remote_control/remote_control_manager.cpp
@@ -0,0 +1,511 @@
+#include "remote_control_manager.h"
+
+#ifndef QT_NO_DEBUG
+#define CHECK_TRUE(instruction) Q_ASSERT(instruction)
+#else
+#define CHECK_TRUE(instruction) (instruction)
+#endif
+
+RemoteControlManager::RemoteControlManager(QQmlApplicationEngine *engine,
+ MainQMLAdaptor *main_gui,
+ DBusAPI *dbus_api) : QObject() {
+ Q_ASSERT(dbus_api != nullptr);
+ Q_ASSERT(main_gui != nullptr);
+ Q_ASSERT(engine != nullptr);
+
+ _current_session = nullptr;
+ _sessions = new QSet<Session*>;
+ _dbus_api = dbus_api;
+ _main_gui = main_gui;
+ _engine = engine;
+}
+
+bool RemoteControlManager::setConnectButtonEnabled(bool enabled) {
+ // Find item via 'objectName'
+ QQuickItem *scene_remote_control = _engine->rootObjects().takeFirst()->
+ findChild<QQuickItem*>("Scene_remote_control");
+ QQuickItem *item = scene_remote_control->
+ findChild<QQuickItem*>("start_support_button");
+ if (item) {
+ item->setProperty("enabled", enabled);
+ if (item->property("checked").toBool()) {
+ item->setProperty("text", tr("Stop remote support session"));
+ } else {
+ item->setProperty("text", tr("Start remote support session"));
+ }
+ } else {
+ qWarning() << "Unable to find 'start_support_button' Item!";
+ return false;
+ }
+
+ return true;
+}
+
+bool RemoteControlManager::setConnectButtonChecked(bool checked) {
+ // Find item via 'objectName'
+ QQuickItem *scene_remote_control = _engine->rootObjects().takeFirst()->
+ findChild<QQuickItem*>("Scene_remote_control");
+ QQuickItem *item = scene_remote_control->
+ findChild<QQuickItem*>("start_support_button");
+ if (item) {
+ item->setProperty("checked", checked);
+ } else {
+ qWarning() << "Unable to find 'start_support_button' Item!";
+ return false;
+ }
+
+ return true;
+}
+
+bool RemoteControlManager::setStatusIndicatorText(QString status_text) {
+ // Find item via 'objectName'
+ QQuickItem *scene_remote_control = _engine->rootObjects().takeFirst()->
+ findChild<QQuickItem*>("Scene_remote_control");
+ QQuickItem *item = scene_remote_control->
+ findChild<QQuickItem*>("dbus_api_status_text");
+ if (item) {
+ item->setProperty("text", status_text);
+ } else {
+ qWarning() << "Unable to find 'dbus_api_status_text' Item!";
+ return false;
+ }
+
+ return true;
+}
+
+bool RemoteControlManager::setStatusIndicatorColor(bool active, QColor color) {
+ // Find item via 'objectName'
+ QQuickItem *scene_remote_control = _engine->rootObjects().takeFirst()->
+ findChild<QQuickItem*>("Scene_remote_control");
+ QQuickItem *item = scene_remote_control->
+ findChild<QQuickItem*>("dbus_api_status_indicator");
+ if (item) {
+ item->setProperty("active", active);
+ item->setProperty("color", color);
+ } else {
+ qWarning() << "Unable to find 'dbus_api_status_indicator' Item!";
+ return false;
+ }
+
+ return true;
+}
+
+void RemoteControlManager::handleCopyToClipboardButtonClick(QString copy_data) {
+ QClipboard *clipboard = QApplication::clipboard();
+ QString originalText = clipboard->text();
+ clipboard->setText(copy_data);
+ qDebug() << "Copied into clipboard:" << copy_data;
+}
+
+QString RemoteControlManager::getURL() {
+ if (getCurrentSession() == nullptr) {
+ return tr("Not available yet");
+ }
+ return getCurrentSession()->getURL();
+}
+
+QString RemoteControlManager::getPin() {
+ if (getCurrentSession() == nullptr) {
+ return "-----";
+ }
+ return getCurrentSession()->getPin();
+}
+
+QString RemoteControlManager::getSessionID() {
+ if (getCurrentSession() == nullptr) {
+ return "-----";
+ }
+ return getCurrentSession()->getSessionID();
+}
+
+Session* RemoteControlManager::getCurrentSession() {
+ return _current_session;
+}
+
+void RemoteControlManager::addSession(Session *session) {
+ _sessions->insert(session);
+}
+
+bool RemoteControlManager::removeSession(Session *session) {
+ if (getCurrentSession() == session) {
+ setCurrentSession(nullptr);
+ }
+
+ bool ok = _sessions->remove(session);
+
+ if (session != nullptr) {
+ session->disconnect();
+ session->deleteLater();
+ }
+
+ setConnectButtonChecked(false);
+ setConnectButtonEnabled(true);
+
+ currentSessionUrlChanged(tr("Not available yet"));
+ currentSessionPinChanged("-----");
+ currentSessionSessionIDChanged("-----");
+
+ return ok;
+}
+
+void RemoteControlManager::setCurrentSession(Session *session) {
+ if (session == nullptr) {
+ qDebug() << "Deselecting currentSession.";
+ _current_session = nullptr;
+
+ return;
+ }
+
+ if (_sessions->contains(session)) {
+ qDebug() << "Set currentSession to new session.";
+ _current_session = session;
+
+ connectSession(_current_session);
+ } else {
+ qDebug() << "Given session was not in _sessions!";
+ }
+}
+
+void RemoteControlManager::connectSession(Session *session) {
+ // session --statusChanged-> this.currentSessionStatusChanged()
+ CHECK_TRUE(QObject::connect(session,
+ &Session::statusChanged,
+ this,
+ &RemoteControlManager::currentSessionStatusChanged));
+
+ // session --pinChanged-> this.currentSessionPinChanged()
+ CHECK_TRUE(QObject::connect(session,
+ &Session::pinChanged,
+ this,
+ &RemoteControlManager::currentSessionPinChanged));
+
+ // session --urlChanged-> this.currentSessionUrlChanged()
+ CHECK_TRUE(QObject::connect(session,
+ &Session::urlChanged,
+ this,
+ &RemoteControlManager::currentSessionUrlChanged));
+
+ // session --sessionIDChanged-> this.currentSessionSessionIDChanged()
+ CHECK_TRUE(QObject::connect(session,
+ &Session::sessionIDChanged,
+ this,
+ &RemoteControlManager::currentSessionSessionIDChanged));
+
+
+ // session --startSucceeded -> this.currentSessionStartSucceeded()
+ CHECK_TRUE(QObject::connect(session,
+ &Session::startSucceeded,
+ this,
+ &RemoteControlManager::currentSessionStartSucceeded));
+
+ // session --startFailed-> this.currentSessionStartFailed()
+ CHECK_TRUE(QObject::connect(session,
+ &Session::startFailed,
+ this,
+ &RemoteControlManager::currentSessionStartFailed));
+
+
+ // session --stopSucceeded-> this.currentSessionStopSucceeded()
+ CHECK_TRUE(QObject::connect(session,
+ &Session::stopSucceeded,
+ this,
+ &RemoteControlManager::currentSessionStopSucceeded));
+
+ // session --stopFailed-> this.currentSessionStopFailed()
+ CHECK_TRUE(QObject::connect(session,
+ &Session::stopFailed,
+ this,
+ &RemoteControlManager::currentSessionStopFailed));
+
+
+ // session --statusSucceeded-> this.currentSessionStatusSucceeded()
+ CHECK_TRUE(QObject::connect(session,
+ &Session::statusSucceeded,
+ this,
+ &RemoteControlManager::currentSessionStatusSucceeded));
+
+ // session --statusFailed-> this.currentSessionStatusFailed()
+ CHECK_TRUE(QObject::connect(session,
+ &Session::statusFailed,
+ this,
+ &RemoteControlManager::currentSessionStatusFailed));
+}
+
+void RemoteControlManager::currentSessionStartFailed(QString error_message) {
+ _main_gui->showToast(error_message, 6000, Toast::ToastType::ToastError);
+
+ // Start failed. No need to stop session, so remove it directly.
+ removeSession(getCurrentSession());
+
+ setConnectButtonChecked(false);
+ setConnectButtonEnabled(true);
+
+ emit _main_gui->showWindow();
+}
+
+void RemoteControlManager::currentSessionStopFailed(QString error_message) {
+ _main_gui->showToast(error_message, 6000, Toast::ToastType::ToastError);
+
+ // Stop failed, so don't do anything! The user should try again.
+
+ // Set status indicator to the corresponding error message.
+ QString translated = translateStatusIndicatorText("stop_session_error");
+ setStatusIndicatorText(translated);
+
+ setConnectButtonChecked(true);
+ setConnectButtonEnabled(true);
+
+ emit _main_gui->showWindow();
+}
+
+void RemoteControlManager::currentSessionStatusFailed(QString error_message) {
+ _main_gui->showToast(error_message, 6000, Toast::ToastType::ToastError);
+
+ // Set status indicator to the corresponding error message.
+ QString translated = translateStatusIndicatorText("status_session_error");
+ setStatusIndicatorText(translated);
+
+ setConnectButtonChecked(false);
+ setConnectButtonEnabled(true);
+
+ currentSessionUrlChanged(tr("Not available yet"));
+ currentSessionPinChanged("-----");
+ currentSessionSessionIDChanged("-----");
+
+ emit _main_gui->showWindow();
+}
+
+void RemoteControlManager::currentSessionStartSucceeded() {
+ Session *current_session = getCurrentSession();
+ if (current_session != nullptr) {
+ _main_gui->showToast(
+ tr("Session was started on '%0' successfully")
+ .arg(current_session->getHost()->alias()),
+ 6000,
+ Toast::ToastType::ToastSuccess
+ );
+ } else {
+ qCritical().noquote() << tr("currentSessionStartSucceeded(): "
+ "Current Session is nullptr!");
+ _engine->exit(1);
+ }
+
+ // Set status indicator to the corresponding error message.
+ QString translated = translateStatusIndicatorText("start_session_success");
+ setStatusIndicatorText(translated);
+
+ setConnectButtonChecked(true);
+ setConnectButtonEnabled(true);
+}
+
+void RemoteControlManager::currentSessionStopSucceeded() {
+ qDebug() << "Session stop succeeded: Delete current session object now.";
+ bool ok = removeSession(getCurrentSession());
+ if (!ok) {
+ qWarning() << tr("Couldn't remove current session out of '_sessions' list.");
+ }
+
+ _main_gui->showToast(tr("Remote support session was stopped."),
+ 6000,
+ Toast::ToastType::ToastSuccess);
+
+ // Set status indicator to the corresponding error message.
+ QString translated = translateStatusIndicatorText("stop_session_success");
+ setStatusIndicatorText(translated);
+
+ setConnectButtonChecked(false);
+ setConnectButtonEnabled(true);
+}
+
+void RemoteControlManager::currentSessionUnexpectedStop(QString error_message) {
+ _main_gui->showToast(error_message, 6000, Toast::ToastType::ToastError);
+
+ setConnectButtonChecked(false);
+ setConnectButtonEnabled(true);
+
+ qDebug() << "Delete current session object now.";
+ bool ok = removeSession(getCurrentSession());
+ if (!ok) {
+ qWarning() << tr("Couldn't remove current session out of '_sessions' list.");
+ }
+}
+
+void RemoteControlManager::currentSessionStatusSucceeded() {
+ // Nothing to do.
+}
+
+void RemoteControlManager::currentSessionStatusChanged(QString new_status_code) {
+ Session *current_session = getCurrentSession();
+ if (current_session == nullptr || !current_session->started) {
+ qDebug() << "RemoteControlManager::currentSessionStatusChanged(QString): "
+ "got called even though the session isn't even started.";
+ return;
+ }
+ QString translated = translateStatusIndicatorText(new_status_code);
+ setStatusIndicatorText(translated);
+}
+
+QString RemoteControlManager::translateStatusIndicatorText(QString status_code) {
+ QString guiString = tr("Unknown state of service");
+ setStatusIndicatorColor(false);
+
+ if (status_code == "dead") {
+
+ /* Session died */
+ guiString = tr("Remote Support session was stopped ungracefully");
+
+ // Red color
+ setStatusIndicatorColor(true, QColor(255, 0, 0, 127));
+
+ currentSessionUnexpectedStop(guiString);
+
+ } else if (status_code == "stopped") {
+
+ /* Session was stopped normally somehow other than the users request.
+ * Remote support partner could have clicked exit for example */
+ guiString = tr("Remote Support session was stopped");
+
+ // Green color
+ setStatusIndicatorColor(true, QColor(0, 255, 0, 127));
+
+ currentSessionUnexpectedStop(guiString);
+
+ } else if (status_code == "active") {
+
+ /* Partner is connected */
+ guiString = tr("Your partner is connected to the Remote Support session");
+
+ // Green color
+ setStatusIndicatorColor(true, QColor(0, 255, 0, 127));
+
+ } else if (status_code == "start_session_success" || status_code == "running") {
+
+ /* Session successfully started */
+ guiString = tr("Remote Support session successfully started! "
+ "Waiting for your remote support partner to connect.");
+
+ // yellow color (will be green when partner is connected)
+ setStatusIndicatorColor(true, QColor(255, 255, 0, 127));
+
+ } else if (status_code == "start_session_error") {
+
+ /* Session couldn't be started */
+ guiString = tr("Remote Support session couldn't be started!");
+
+ // Red color
+ setStatusIndicatorColor(true, QColor(255, 0, 0, 127));
+
+ } else if (status_code == "stop_session_success") {
+
+ /* Session was successfully stopped by the users request */
+ guiString = tr("Session stopped successfully.");
+
+ // Green color
+ setStatusIndicatorColor(true, QColor(0, 255, 0, 127));
+
+ } else if (status_code == "stop_session_error") {
+
+ /* Session couldn't be stopped */
+ guiString = tr("Session could not be stopped!") + "\n" +
+ tr("remote support partner could still be connected!");
+
+ // Red color
+ setStatusIndicatorColor(true, QColor(255, 0, 0, 127));
+
+ } else if (status_code == "status_session_error") {
+
+ /* Session's status couldn't be refreshed */
+ guiString = tr("Session status could not be refreshed! "
+ "Your remote support partner could still be connected!");
+
+ // Red color
+ setStatusIndicatorColor(true, QColor(255, 0, 0, 127));
+
+ }
+
+ qDebug().noquote() << QString("Translating status code '%0' to '%1'")
+ .arg(status_code)
+ .arg(guiString);
+ return guiString;
+}
+
+void RemoteControlManager::currentSessionPinChanged(QString pin) {
+ emit pinChanged(pin);
+}
+
+void RemoteControlManager::currentSessionUrlChanged(QString url) {
+ emit urlChanged(url);
+}
+
+void RemoteControlManager::currentSessionSessionIDChanged(QString session_id) {
+ emit sessionIDChanged(session_id);
+}
+
+void RemoteControlManager::connectToDBusAPI(Session *session) {
+ // _dbus_api --sessionStartResponse-> this.start_response()
+ CHECK_TRUE(QObject::connect(_dbus_api,
+ &DBusAPI::serviceStartResponse,
+ session,
+ &Session::start_response));
+
+ // _dbus_api --serviceStopResponse-> this.stop_response()
+ CHECK_TRUE(QObject::connect(_dbus_api,
+ &DBusAPI::serviceStopResponse,
+ session,
+ &Session::stop_response,
+ Qt::DirectConnection));
+
+ // _dbus_api --sessionStatusResponse-> this.status_response()
+ CHECK_TRUE(QObject::connect(_dbus_api,
+ &DBusAPI::serviceStatusResponse,
+ session,
+ &Session::status_response));
+}
+
+void RemoteControlManager::handleConnectButtonClick(bool checked) {
+ if (checked) {
+ // Create a Session object and start it.
+ qDebug() << "Creating and starting a new session object now.";
+ RWAHost *selected_host = _main_gui->getSelectedRWAHost();
+ if (selected_host) {
+ qInfo() << tr("Creating a new session object.");
+ Session *new_session = new Session(_dbus_api, selected_host);
+
+ connectToDBusAPI(new_session);
+
+ qDebug() << "Adding session to QSet";
+ addSession(new_session);
+
+ qDebug() << "Setting session as current session.";
+ setCurrentSession(new_session);
+
+ qInfo().noquote() << tr("Starting a remote support session on host '%0' "
+ "using the new session object.")
+ .arg(selected_host->uuid());
+ new_session->start();
+ } else {
+ qCritical().noquote() << tr("Can't start a remote support session. "
+ "There is no RWA host is selected!");
+ }
+
+ setConnectButtonChecked(false);
+ setConnectButtonEnabled(false);
+ } else {
+ Session *current_session = getCurrentSession();
+ if (current_session != nullptr) {
+ emit current_session->stop();
+ } else {
+ qCritical().noquote() << tr("RemoteControlManager::"
+ "handleConnectButtonClick(): Current Session "
+ "is nullptr!");
+ setConnectButtonChecked(false);
+ setConnectButtonEnabled(true);
+ }
+ }
+}
+
+/*void RemoteControlManager::onCloseHandler() {
+ // To cleanup things here
+ // check current session nullptr
+ getCurrentSession()->stop();
+}*/
diff --git a/rwa-support-desktopapp/src/scenes/remote_control/remote_control_manager.h b/rwa-support-desktopapp/src/scenes/remote_control/remote_control_manager.h
new file mode 100644
index 0000000..4554724
--- /dev/null
+++ b/rwa-support-desktopapp/src/scenes/remote_control/remote_control_manager.h
@@ -0,0 +1,78 @@
+#ifndef REMOTE_CONTROL_MANAGER_H
+#define REMOTE_CONTROL_MANAGER_H
+
+#include <QObject>
+
+#include "../../main_qmladaptor.h"
+#include "../../DBusAPI.h"
+#include "../../session.h"
+
+class RemoteControlManager : public QObject {
+
+ Q_OBJECT
+ // this makes url available as a QML property
+ Q_PROPERTY(QString url READ getURL NOTIFY urlChanged)
+ // this makes pin available as a QML property
+ Q_PROPERTY(QString pin READ getPin NOTIFY pinChanged)
+ // this makes session_id available as a QML property
+ Q_PROPERTY(QString session_id READ getSessionID NOTIFY sessionIDChanged)
+
+public:
+ explicit RemoteControlManager(QQmlApplicationEngine *engine = nullptr,
+ MainQMLAdaptor *main_gui = nullptr,
+ DBusAPI *dbus_api = nullptr);
+
+ bool setConnectButtonEnabled(bool enabled);
+ bool setConnectButtonChecked(bool checked);
+ bool setStatusIndicatorColor(bool active, QColor color = QColor(255,255,255));
+
+ QString translateStatusIndicatorText(QString status_code);
+ bool setStatusIndicatorText(QString status_text);
+
+private:
+ DBusAPI *_dbus_api;
+ MainQMLAdaptor *_main_gui;
+ QQmlApplicationEngine *_engine;
+ Session *_current_session;
+ QSet<Session*> *_sessions;
+
+ void connectSession(Session *session);
+ void connectToDBusAPI(Session *session);
+
+ bool refreshTimerIsRunning;
+
+signals:
+ void onConnectButtonClick(bool checked);
+ void pinChanged(QString pin);
+ void urlChanged(QString URL);
+ void sessionIDChanged(QString session_id);
+
+public slots:
+ void handleCopyToClipboardButtonClick(QString copy_data);
+ void handleConnectButtonClick(bool checked);
+
+ void setCurrentSession(Session *session);
+ void addSession(Session *session);
+ bool removeSession(Session *session);
+
+ void currentSessionStatusChanged(QString);
+ void currentSessionPinChanged(QString);
+ void currentSessionSessionIDChanged(QString);
+ void currentSessionUrlChanged(QString);
+
+ QString getURL();
+ QString getPin();
+ QString getSessionID();
+ Session* getCurrentSession();
+
+ void currentSessionStartFailed(QString error_message);
+ void currentSessionStopFailed(QString error_message);
+ void currentSessionStatusFailed(QString error_message);
+ void currentSessionUnexpectedStop(QString error_message);
+
+ void currentSessionStartSucceeded();
+ void currentSessionStopSucceeded();
+ void currentSessionStatusSucceeded();
+};
+
+#endif // REMOTECONTROLMANAGER_H
diff --git a/rwa-support-desktopapp/src/session.cpp b/rwa-support-desktopapp/src/session.cpp
new file mode 100644
index 0000000..25a8512
--- /dev/null
+++ b/rwa-support-desktopapp/src/session.cpp
@@ -0,0 +1,271 @@
+/*
+ * This file is part of Remote Support Desktop
+ * https://gitlab.das-netzwerkteam.de/RemoteWebApp/rwa.support.desktopapp
+ * Copyright 2020, 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+ * Copyright 2020, 2021 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "session.h"
+
+Session::Session(DBusAPI *dbus_api, RWAHost *host) : QObject() {
+ Q_ASSERT(host != nullptr);
+ Q_ASSERT(dbus_api != nullptr);
+
+ _dbus_api = dbus_api;
+ _host = host;
+
+ setPin("-----");
+ setSessionID("-----");
+ setURL(tr("Not available yet"));
+ setStatus("unknown");
+ started = false;
+}
+
+Session::~Session() {
+ qDebug().noquote() << QString("Session #'%0' on host '%1' will be deconstructed.")
+ .arg(getSessionID())
+ .arg(getHost()->alias());
+ stop();
+ disconnect();
+}
+
+void Session::statusTimerEvent() {
+ qDebug() << "Status timer event triggered";
+
+ refresh_status();
+}
+
+bool Session::isSessionAliveOrRunning() {
+ if (getStatus() == "running" || getStatus() == "active") {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+RWAHost* Session::getHost() {
+ return _host;
+}
+
+QString Session::getStatus() {
+ return _status;
+}
+
+QString Session::getURL() {
+ return _url;
+}
+
+QString Session::getSessionID() {
+ return _session_id;
+}
+
+QString Session::getPin() {
+ return _pin;
+}
+
+void Session::setURL(QString url) {
+ _url = url;
+ emit urlChanged(url);
+}
+
+void Session::setSessionID(QString session_id) {
+ _session_id = session_id;
+ emit sessionIDChanged(session_id);
+}
+
+void Session::setPin(QString pin) {
+ _pin = pin;
+ emit pinChanged(pin);
+}
+
+void Session::setHost(RWAHost *host) {
+ _host = host;
+ emit hostChanged(host);
+}
+
+void Session::setStatus(QString status) {
+ _status = status;
+ emit statusChanged(status);
+}
+
+void Session::start() {
+ _dbus_api->start_request(_host);
+}
+
+void Session::start_response(QJsonDocument *doc) {
+ // Q_ASSERT lets the program crash immediatly after method call
+ // when the session service is not started.
+ // Don't use Q_ASSERT(doc != nullptr); instead use:
+ if (doc == nullptr) {
+ emit startFailed(tr("Can't connect to underlying session service! "
+ "Is the session service started?"));
+ return;
+ }
+
+ // Get the QJsonObject
+ QJsonObject jObject = doc->object();
+ QVariantMap mainMap = jObject.toVariantMap();
+
+ // Status of request
+ QString status = mainMap["status"].toString();
+ if (status != "success") {
+ QString type = mainMap["type"].toString();
+ QString error_message = tr("An error occured while creating a new session!");
+
+ if (type == "multiple") {
+ // TODO: Ask to stop other session via a message dialog.
+ error_message = tr("The session service is configured "
+ "to not support multiple sessions "
+ "and there is already a session running.");
+ } else if (type == "connection") {
+ error_message = tr("Couldn't connect to host '%0'.")
+ .arg(getHost()->alias());
+ } else if (type == "host_not_found") {
+ error_message = tr("The RWA host '%0' couldn't be found.")
+ .arg(getHost()->alias());
+ } else if (type == "permission_denid") {
+ error_message = tr("The RWA host '%0' doesn't grant access.")
+ .arg(getHost()->alias());
+ } else if (type == "unsupported_server") {
+ error_message = tr("The RWA host '%0' is not supported.")
+ .arg(getHost()->alias());
+ }
+
+ setStatus("start_session_error");
+ qCritical().noquote() << error_message;
+ emit startFailed(error_message);
+ return;
+ }
+
+ bool ok;
+
+ // URL of remote web app frontend
+ QString url = mainMap["url"].toString();
+ this->setURL(url);
+
+ // PIN
+ long long pin = mainMap["pin"].toLongLong(&ok);
+ // Sanity Check
+ if(ok == false){
+ QString error_message = "Unable to parse <pin> into longo long out of dbus answer!";
+ qCritical().noquote() << error_message;
+ emit startFailed(error_message);
+ return;
+ }
+ this->setPin(QString::number(pin));
+
+ // session_id = remote support id from the rwa-server
+ long long session_id = mainMap["session_id"].toLongLong();
+ // Sanity Check
+ if(ok == false){
+ QString error_message = "Unable to parse <session_id> into long long out of dbus answer!";
+ qCritical().noquote() << error_message;
+ emit startFailed(error_message);
+ return;
+ }
+ this->setSessionID(QString::number(session_id));
+
+ qDebug() << "Got session_id:" << session_id <<
+ "\nGot url:" << url <<
+ "\nGot pin:" << pin;
+
+ emit pinChanged(QString::number(pin));
+ emit urlChanged(url);
+ emit sessionIDChanged(QString::number(session_id));
+
+ started = true;
+
+ // Ask status every 1000 millisecond
+
+ QTimer *timer = new QTimer(this);
+ connect(timer, &QTimer::timeout, this,
+ QOverload<>::of(&Session::statusTimerEvent));
+ timer->start(1000);
+
+ qDebug() << "Successfully started a session.";
+ this->setStatus("start_session_success");
+
+ emit startSucceeded();
+}
+
+void Session::stop() {
+ if (started)
+ _dbus_api->stop_request(getHost(), getSessionID());
+}
+
+void Session::stop_response(QJsonDocument *doc) {
+ // Q_ASSERT lets the program crash immediatly after method call
+ // when the session service is not started.
+ // Don't use Q_ASSERT(doc != nullptr); instead use:
+ if (doc == nullptr) {
+ emit stopFailed(tr("Can't connect to underlying session service! "
+ "Is the session service started?"));
+ return;
+ }
+
+ QJsonObject jObject = doc->object();
+ QVariantMap mainMap = jObject.toVariantMap();
+
+ QString new_status = mainMap["status"].toString();
+ qDebug() << "stop_response() retrieved json. Status now:" << new_status;
+
+ started = false;
+
+ this->setStatus(new_status);
+
+ emit stopSucceeded();
+}
+
+void Session::status() {
+ _dbus_api->status_request(getHost(), this->getSessionID());
+}
+
+void Session::refresh_status() {
+ _dbus_api->refresh_status_request(getHost(), this->getSessionID());
+}
+
+void Session::status_response(QJsonDocument *doc) {
+ // Q_ASSERT lets the program crash immediatly after method call,
+ // when the session service is not started.
+ // Don't use Q_ASSERT(doc != nullptr); instead use:
+ if (doc == nullptr) {
+ if (!_emitted_status_error_already) {
+ emit statusFailed(tr("Can't connect to underlying session service! "
+ "Is the session service started?"));
+
+ _emitted_status_error_already = true;
+ }
+
+ return;
+ }
+
+ _emitted_status_error_already = false;
+
+ QJsonObject jObject = doc->object();
+ QVariantMap mainMap = jObject.toVariantMap();
+ QString new_status = mainMap["status"].toString();
+ qDebug() << "status_response() retrieved json. Status:" << new_status;
+
+ setStatus(new_status);
+
+ emit statusSucceeded();
+}
diff --git a/rwa-support-desktopapp/src/session.h b/rwa-support-desktopapp/src/session.h
new file mode 100644
index 0000000..e1c924f
--- /dev/null
+++ b/rwa-support-desktopapp/src/session.h
@@ -0,0 +1,104 @@
+/*
+ * This file is part of Remote Support Desktop
+ * https://gitlab.das-netzwerkteam.de/RemoteWebApp/rwa.support.desktopapp
+ * Copyright 2020, 2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+ * Copyright 2020, 2021 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef SESSION_H
+#define SESSION_H
+
+#include <QObject>
+#include <QQmlApplicationEngine>
+#include <QQuickItem>
+#include <QTimerEvent>
+#include <QTranslator>
+
+#include "RWAHost.h"
+#include "DBusAPI.h"
+
+class Session : public QObject {
+ Q_OBJECT
+
+public:
+ explicit Session(DBusAPI *dbus_api = nullptr,
+ RWAHost *host = nullptr);
+ ~Session();
+
+ QString getStatus();
+ QString getURL();
+ QString getSessionID();
+ QString getPin();
+ RWAHost* getHost();
+
+ void setStatus(QString status);
+ void setURL(QString url);
+ void setSessionID(QString session_id);
+ void setPin(QString pin);
+ void setHost(RWAHost *host);
+
+ void start();
+ void stop();
+ void status();
+ void refresh_status();
+
+ bool started;
+
+private:
+ void statusTimerEvent();
+
+ bool _emitted_status_error_already;
+ QString _status;
+ RWAHost *_host;
+ DBusAPI *_dbus_api;
+
+ QString _session_id;
+ QString _url;
+ QString _pin;
+
+ bool _minimizedBefore = false;
+
+signals:
+ void statusChanged(QString);
+ void sessionIDChanged(QString);
+ void urlChanged(QString);
+ void pinChanged(QString);
+ void hostChanged(RWAHost*);
+
+ void startFailed(QString error_message);
+ void stopFailed(QString error_message);
+ void statusFailed(QString error_message);
+
+ void startSucceeded();
+ void stopSucceeded();
+ void statusSucceeded();
+
+public slots:
+ // Returns true if a session is somewhat usable (Running, Alive, etc..)
+ bool isSessionAliveOrRunning();
+
+ void start_response(QJsonDocument*);
+ void stop_response(QJsonDocument*);
+ void status_response(QJsonDocument*);
+};
+
+#endif // SESSION_H
diff --git a/rwa-support-desktopapp/update_locales.sh b/rwa-support-desktopapp/update_locales.sh
new file mode 100755
index 0000000..8c05ece
--- /dev/null
+++ b/rwa-support-desktopapp/update_locales.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+# This file is part of Remote Support Desktop
+# https://gitlab.das-netzwerkteam.de/RemoteWebApp/rwa.support.desktopapp
+# Copyright 2020-2021 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
+# Copyright 2020-2021 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+lupdate rwa-support-desktopapp.pro -no-obsolete
+linguist locales/*.ts
+lrelease rwa-support-desktopapp.pro -compress -removeidentical