From e1f3f3879a24e3f50fe985cc9fab59dae43c94c5 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 22 May 2019 03:52:51 +0200 Subject: [PATCH 1/6] Packaging fixes for the kiosk package --- .../{40-nymea-app-kiosk.conf => 60-nymea-app-kiosk.conf} | 2 +- packaging/linux-common/nymea-app-kiosk-wrapper | 1 + packaging/linux-common/udev/90-pi-backlight.rules | 2 ++ packaging/ubuntu/debian/control | 4 ++++ packaging/ubuntu/debian/nymea-app-kiosk.install | 3 ++- 5 files changed, 10 insertions(+), 2 deletions(-) rename packaging/linux-common/lightdm/{40-nymea-app-kiosk.conf => 60-nymea-app-kiosk.conf} (67%) create mode 100644 packaging/linux-common/udev/90-pi-backlight.rules diff --git a/packaging/linux-common/lightdm/40-nymea-app-kiosk.conf b/packaging/linux-common/lightdm/60-nymea-app-kiosk.conf similarity index 67% rename from packaging/linux-common/lightdm/40-nymea-app-kiosk.conf rename to packaging/linux-common/lightdm/60-nymea-app-kiosk.conf index 779b2d31..e9c299d9 100644 --- a/packaging/linux-common/lightdm/40-nymea-app-kiosk.conf +++ b/packaging/linux-common/lightdm/60-nymea-app-kiosk.conf @@ -1,4 +1,4 @@ [Seat:*] autologin-user=nymea user-session=nymea-app-kiosk - +xserver-command=X -nocursor diff --git a/packaging/linux-common/nymea-app-kiosk-wrapper b/packaging/linux-common/nymea-app-kiosk-wrapper index 094bd8c6..080f2499 100755 --- a/packaging/linux-common/nymea-app-kiosk-wrapper +++ b/packaging/linux-common/nymea-app-kiosk-wrapper @@ -1,2 +1,3 @@ #!/bin/sh +export QT_IM_MODULE=qtvirtualkeyboard /usr/bin/nymea-app --kiosk diff --git a/packaging/linux-common/udev/90-pi-backlight.rules b/packaging/linux-common/udev/90-pi-backlight.rules new file mode 100644 index 00000000..d95e35be --- /dev/null +++ b/packaging/linux-common/udev/90-pi-backlight.rules @@ -0,0 +1,2 @@ +ACTION=="add", SUBSYSTEM=="backlight", RUN+="/bin/chgrp nymea /sys/class/backlight/%k/bl_power" +ACTION=="add", SUBSYSTEM=="backlight", RUN+="/bin/chmod g+w /sys/class/backlight/%k/bl_power" diff --git a/packaging/ubuntu/debian/control b/packaging/ubuntu/debian/control index 54df5ba7..81de8c0c 100644 --- a/packaging/ubuntu/debian/control +++ b/packaging/ubuntu/debian/control @@ -50,5 +50,9 @@ Section: shells Multi-Arch: same Depends: nymea-app, openbox, + lightdm, + qtvirtualkeyboard-plugin, + qtdeclarative5-folderlistmodel-plugin, +Provides: lightdm-greeter Description: Run nymea:app in kiosk mode This package will install nymea:app in kiosk mode on your machine. diff --git a/packaging/ubuntu/debian/nymea-app-kiosk.install b/packaging/ubuntu/debian/nymea-app-kiosk.install index 187a18da..09445ca8 100644 --- a/packaging/ubuntu/debian/nymea-app-kiosk.install +++ b/packaging/ubuntu/debian/nymea-app-kiosk.install @@ -1,3 +1,4 @@ packaging/linux-common/nymea-app-kiosk.desktop /usr/share/xsessions/ -packaging/linux-common/lightdm/40-nymea-app-kiosk.conf /usr/share/lightdm/lightdm.conf.d/ +packaging/linux-common/lightdm/60-nymea-app-kiosk.conf /usr/share/lightdm/lightdm.conf.d/ packaging/linux-common/nymea-app-kiosk-wrapper /usr/bin/ +packaging/linux-common/udev/90-pi-backlight.rules /lib/udev/rules.d/ From 2ad82d80a622c99d3a0305fa38d787f3e7c1076d Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 22 May 2019 03:53:20 +0200 Subject: [PATCH 2/6] Add a command line parameter to allow direct connections --- nymea-app/main.cpp | 3 +++ nymea-app/ui/RootItem.qml | 6 ++++-- nymea-app/ui/connection/ConnectPage.qml | 1 - 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/nymea-app/main.cpp b/nymea-app/main.cpp index 8c33bf1f..6c356838 100644 --- a/nymea-app/main.cpp +++ b/nymea-app/main.cpp @@ -75,6 +75,8 @@ int main(int argc, char *argv[]) parser.addHelpOption(); QCommandLineOption kioskOption = QCommandLineOption({"k", "kiosk"}, "Start the application in kiosk mode."); parser.addOption(kioskOption); + QCommandLineOption connectOption = QCommandLineOption({"c", "connect"}, "Connect to nymea:core without discovery.", "host"); + parser.addOption(connectOption); parser.process(application); // Initialize app log controller as early as possible, but after setting app name etc @@ -124,6 +126,7 @@ int main(int argc, char *argv[]) engine->rootContext()->setContextProperty("styleController", &styleController); engine->rootContext()->setContextProperty("kioskMode", parser.isSet(kioskOption)); + engine->rootContext()->setContextProperty("autoConnectHost", parser.value(connectOption)); engine->rootContext()->setContextProperty("systemProductType", QSysInfo::productType()); diff --git a/nymea-app/ui/RootItem.qml b/nymea-app/ui/RootItem.qml index 403b97fb..a927d7bb 100644 --- a/nymea-app/ui/RootItem.qml +++ b/nymea-app/ui/RootItem.qml @@ -105,8 +105,10 @@ Item { Component.onCompleted: { setupPushNotifications(); - - if (tabSettings.lastConnectedHost.length > 0) { + if (autoConnectHost.length > 0) { + var host = discovery.nymeaHosts.createLanHost("Manual connection", autoConnectHost); + engine.connection.connect(host) + } else if (tabSettings.lastConnectedHost.length > 0) { print("Last connected host was", tabSettings.lastConnectedHost) var cachedHost = discovery.nymeaHosts.find(tabSettings.lastConnectedHost); if (cachedHost) { diff --git a/nymea-app/ui/connection/ConnectPage.qml b/nymea-app/ui/connection/ConnectPage.qml index ee5fdb01..eb025bc3 100644 --- a/nymea-app/ui/connection/ConnectPage.qml +++ b/nymea-app/ui/connection/ConnectPage.qml @@ -12,7 +12,6 @@ Page { Component.onCompleted: { print("Ready to connect") - pageStack.push(discoveryPage, StackView.Immediate) } From 424547333f9e1fd67b463f2b78f017a32b1a716f Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 22 May 2019 13:41:38 +0200 Subject: [PATCH 3/6] Improve setup wizard --- libnymea-app-core/jsonrpc/jsonrpcclient.cpp | 1 + libnymea-app-core/jsonrpc/jsonrpcclient.h | 1 + nymea-app/resources.qrc | 3 +- nymea-app/ui/KeyboardLoader.qml | 36 +++-- nymea-app/ui/LoginPage.qml | 141 ----------------- nymea-app/ui/Nymea.qml | 1 + nymea-app/ui/PushButtonAuthPage.qml | 33 ++-- nymea-app/ui/RootItem.qml | 12 +- nymea-app/ui/connection/LoginPage.qml | 158 ++++++++++++++++++++ nymea-app/ui/connection/SetupWizard.qml | 28 ++++ 10 files changed, 240 insertions(+), 174 deletions(-) delete mode 100644 nymea-app/ui/LoginPage.qml create mode 100644 nymea-app/ui/connection/LoginPage.qml create mode 100644 nymea-app/ui/connection/SetupWizard.qml diff --git a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp index aef15ae8..92e62dac 100644 --- a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp +++ b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp @@ -271,6 +271,7 @@ void JsonRpcClient::processCreateUser(const QVariantMap &data) { qDebug() << "create user response:" << data; if (data.value("status").toString() == "success" && data.value("params").toMap().value("error").toString() == "UserErrorNoError") { + emit createUserSucceeded(); m_initialSetupRequired = false; emit initialSetupRequiredChanged(); } else { diff --git a/libnymea-app-core/jsonrpc/jsonrpcclient.h b/libnymea-app-core/jsonrpc/jsonrpcclient.h index 0a7048a9..09ff11d4 100644 --- a/libnymea-app-core/jsonrpc/jsonrpcclient.h +++ b/libnymea-app-core/jsonrpc/jsonrpcclient.h @@ -93,6 +93,7 @@ signals: void invalidProtocolVersion(const QString &actualVersion, const QString &minimumVersion); void authenticationFailed(); void pushButtonAuthFailed(); + void createUserSucceeded(); void createUserFailed(const QString &error); void cloudConnectionStateChanged(); diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 3dc0cb73..6bcfd6bd 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -2,7 +2,7 @@ ui/Nymea.qml ui/SettingsPage.qml - ui/LoginPage.qml + ui/connection/LoginPage.qml ui/MagicPage.qml ui/PushButtonAuthPage.qml ui/KeyboardLoader.qml @@ -182,5 +182,6 @@ ui/magic/SelectStatePage.qml ui/system/SystemUpdatePage.qml ui/components/UpdateRunningOverlay.qml + ui/connection/SetupWizard.qml diff --git a/nymea-app/ui/KeyboardLoader.qml b/nymea-app/ui/KeyboardLoader.qml index 8c33f1c6..d7e2445a 100644 --- a/nymea-app/ui/KeyboardLoader.qml +++ b/nymea-app/ui/KeyboardLoader.qml @@ -1,24 +1,32 @@ -import QtQuick 2.0 +import QtQuick 2.4 Item { id: root - implicitHeight: childrenRect.height + implicitHeight: active ? childrenRect.height : 0 + property bool active: d.kbd && d.kbd.active + + Behavior on implicitHeight { NumberAnimation { duration: 130; easing.type: Easing.InOutQuad } } + + QtObject { + id: d + property var kbd: null + property string virtualKeyboardString: + ' + import QtQuick 2.8; + import QtQuick.VirtualKeyboard 2.1 + InputPanel { + id: inputPanel + y: Qt.inputMethod.visible ? parent.height - inputPanel.height : parent.height + anchors.left: parent.left + anchors.right: parent.right + } + ' + } - property string virtualKeyboardString: - ' - import QtQuick 2.8; - import QtQuick.VirtualKeyboard 2.1 - InputPanel { - id: inputPanel - y: Qt.inputMethod.visible ? parent.height - inputPanel.height : parent.height - anchors.left: parent.left - anchors.right: parent.right - } - ' Component.onCompleted: { if (useVirtualKeyboard) { - var kbd = Qt.createQmlObject(virtualKeyboardString, root); + d.kbd = Qt.createQmlObject(d.virtualKeyboardString, root); } } } diff --git a/nymea-app/ui/LoginPage.qml b/nymea-app/ui/LoginPage.qml deleted file mode 100644 index 6eb7703a..00000000 --- a/nymea-app/ui/LoginPage.qml +++ /dev/null @@ -1,141 +0,0 @@ -import QtQuick 2.5 -import QtQuick.Controls 2.1 -import QtQuick.Layouts 1.1 -import Nymea 1.0 -import "components" - -Page { - id: root - signal backPressed(); - - header: GuhHeader { - text: qsTr("Welcome to %1!").arg(app.systemName) - backButtonVisible: true - onBackPressed: root.backPressed() - } - - - Connections { - target: engine.jsonRpcClient - onAuthenticationFailed: { - var popup = errorDialog.createObject(root) - popup.text = qsTr("Sorry, that wasn't right. Try again please.") - popup.open(); - } - onCreateUserFailed: { - print("createUser failed") - var message; - switch (error) { - case "UserErrorInvalidUserId": - message = qsTr("The email you've entered isn't valid.") - break; - case "UserErrorDuplicateUserId": - message = qsTr("The email you've entered is already used.") - break; - case "UserErrorBadPassword": - message = qsTr("The password you've chose is too weak.") - break; - case "UserErrorBackendError": - message = qsTr("An error happened with the user storage. Please make sure your %1 box is installed correctly.") - break; - } - var popup = errorDialog.createObject(root, {text: message}); - popup.open(); - } - } - - ColumnLayout { - anchors.fill: parent - anchors.margins: app.margins - spacing: app.margins - - Label { - Layout.fillWidth: true - text: engine.jsonRpcClient.initialSetupRequired ? - qsTr("In order to use your %1 system, please enter your email address and set a password for your nymea box.").arg(app.systemName) - : qsTr("In order to use your %1 system, please log in.").arg(app.systemName) - wrapMode: Text.WordWrap - } - - ColumnLayout { - Layout.fillWidth: true - - Label { - text: qsTr("Your e-mail address:") - Layout.fillWidth: true - } - TextField { - id: usernameTextField - Layout.fillWidth: true - inputMethodHints: Qt.ImhEmailCharactersOnly - placeholderText: "john.smith@cooldomain.com" - } - } - ColumnLayout { - Layout.fillWidth: true - - Label { - Layout.fillWidth: true - text: qsTr("Password:") - } - TextField { - id: passwordTextField - Layout.fillWidth: true - echoMode: TextInput.Password - } - } - - ColumnLayout { - Layout.fillWidth: true - visible: engine.jsonRpcClient.initialSetupRequired - - Label { - Layout.fillWidth: true - text: qsTr("Confirm password:") - } - TextField { - id: confirmPasswordTextField - Layout.fillWidth: true - echoMode: TextInput.Password - } - } - - Label { - Layout.fillWidth: true - visible: engine.jsonRpcClient.initialSetupRequired - opacity: (passwordTextField.text.length > 0 && passwordTextField.text.length < 8) || passwordTextField.text != confirmPasswordTextField.text ? 1 : 0 - text: passwordTextField.text.length < 8 ? qsTr("This password isn't long enought to be secure, add some more characters please.") - : qsTr("The passwords don't match.") - wrapMode: Text.WordWrap - Layout.preferredHeight: confirmPasswordTextField.height * 2 - color: app.accentColor - } - - Button { - Layout.fillWidth: true - text: qsTr("OK") - enabled: usernameTextField.text.length >= 5 && passwordTextField.text.length >= 8 - && (!engine.jsonRpcClient.initialSetupRequired || confirmPasswordTextField.text == passwordTextField.text) - onClicked: { - if (engine.jsonRpcClient.initialSetupRequired) { - print("create user") - engine.jsonRpcClient.createUser(usernameTextField.text, passwordTextField.text); - } else { - print("authenticate", usernameTextField.text, passwordTextField.text, "nymea-app") - engine.jsonRpcClient.authenticate(usernameTextField.text, passwordTextField.text, "nymea-app"); - } - } - } - Item { - Layout.fillHeight: true - Layout.fillWidth: true - } - } - - Component { - id: errorDialog - ErrorDialog { - - } - } -} diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index 9ce24790..4603f481 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -54,6 +54,7 @@ ApplicationWindow { RootItem { id: rootItem anchors.fill: parent + anchors.bottomMargin: keyboardRect.height } NymeaDiscovery { diff --git a/nymea-app/ui/PushButtonAuthPage.qml b/nymea-app/ui/PushButtonAuthPage.qml index 80423a0d..7423ad3f 100644 --- a/nymea-app/ui/PushButtonAuthPage.qml +++ b/nymea-app/ui/PushButtonAuthPage.qml @@ -33,31 +33,30 @@ Page { ColumnLayout { anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter } anchors.margins: app.margins - spacing: app.margins + spacing: app.margins * 2 - RowLayout { + Label { Layout.fillWidth: true - spacing: app.margins + horizontalAlignment: Text.AlignHCenter + color: app.accentColor + text: qsTr("Authentication required") + wrapMode: Text.WordWrap + font.pixelSize: app.largeFont + } - ColorIcon { - height: app.iconSize * 2 - width: height - color: app.accentColor - name: "../images/info.svg" - } - - Label { - color: app.accentColor - text: qsTr("Authentication required") - wrapMode: Text.WordWrap - Layout.fillWidth: true - font.pixelSize: app.largeFont - } + Image { + Layout.preferredWidth: app.iconSize * 6 + Layout.preferredHeight: width + source: "images/nymea-box-setup.svg" + Layout.alignment: Qt.AlignHCenter + sourceSize.width: width + sourceSize.height: height } Label { Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter text: qsTr("Please press the button on your %1 box to authenticate this device.").arg(app.systemName) wrapMode: Text.WordWrap } diff --git a/nymea-app/ui/RootItem.qml b/nymea-app/ui/RootItem.qml index a927d7bb..60e85991 100644 --- a/nymea-app/ui/RootItem.qml +++ b/nymea-app/ui/RootItem.qml @@ -145,7 +145,17 @@ Item { }) return; } else { - var page = pageStack.push(Qt.resolvedUrl("LoginPage.qml")); + if (engine.jsonRpcClient.initialSetupRequired) { + var page = pageStack.push(Qt.resolvedUrl("connection/SetupWizard.qml")); + page.backPressed.connect(function() { + tabSettings.lastConnectedHost = ""; + engine.connection.disconnect() + init(); + }) + return; + } + + var page = pageStack.push(Qt.resolvedUrl("connection/LoginPage.qml")); page.backPressed.connect(function() { tabSettings.lastConnectedHost = ""; engine.connection.disconnect() diff --git a/nymea-app/ui/connection/LoginPage.qml b/nymea-app/ui/connection/LoginPage.qml new file mode 100644 index 00000000..9c761b6f --- /dev/null +++ b/nymea-app/ui/connection/LoginPage.qml @@ -0,0 +1,158 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" + +Page { + id: root + signal backPressed(); + + header: GuhHeader { + text: qsTr("Welcome to %1!").arg(app.systemName) + backButtonVisible: true + onBackPressed: root.backPressed() + } + + + Connections { + target: engine.jsonRpcClient + onAuthenticationFailed: { + var popup = errorDialog.createObject(root) + popup.text = qsTr("Sorry, that wasn't right. Try again please.") + popup.open(); + } + onCreateUserSucceeded: { + engine.jsonRpcClient.authenticate(usernameTextField.text, passwordTextField.text, "nymea-app"); + } + + onCreateUserFailed: { + print("createUser failed") + var message; + switch (error) { + case "UserErrorInvalidUserId": + message = qsTr("The email you've entered isn't valid.") + break; + case "UserErrorDuplicateUserId": + message = qsTr("The email you've entered is already used.") + break; + case "UserErrorBadPassword": + message = qsTr("The password you've chose is too weak.") + break; + case "UserErrorBackendError": + message = qsTr("An error happened with the user storage. Please make sure your %1 box is installed correctly.") + break; + } + var popup = errorDialog.createObject(root, {text: message}); + popup.open(); + } + } + + Flickable { + anchors.fill: parent + contentHeight: contentColumn.implicitHeight + + ColumnLayout { + id: contentColumn + width: parent.width + + spacing: app.margins + + RowLayout { + Layout.margins: app.margins + spacing: app.margins + + ColorIcon { + Layout.preferredHeight: app.iconSize * 2 + Layout.preferredWidth: app.iconSize * 2 + name: "../images/lock-closed.svg" + color: app.accentColor + } + + Label { + Layout.fillWidth: true + text: engine.jsonRpcClient.initialSetupRequired ? + qsTr("In order to use your %1 system, please enter your email address and set a password for your %1 box.").arg(app.systemName) + : qsTr("In order to use your %1 system, please log in.").arg(app.systemName) + wrapMode: Text.WordWrap + } + } + + + GridLayout { + Layout.fillWidth: true + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + columns: app.width > 400 ? 2 : 1 + + Label { + text: qsTr("Your e-mail address:") + Layout.fillWidth: true + } + TextField { + id: usernameTextField + Layout.fillWidth: true + inputMethodHints: Qt.ImhEmailCharactersOnly + placeholderText: "john.smith@cooldomain.com" + } + Label { + Layout.fillWidth: true + text: qsTr("Password:") + } + TextField { + id: passwordTextField + Layout.fillWidth: true + echoMode: TextInput.Password + } + + Label { + visible: engine.jsonRpcClient.initialSetupRequired + Layout.fillWidth: true + text: qsTr("Confirm password:") + } + TextField { + id: confirmPasswordTextField + visible: engine.jsonRpcClient.initialSetupRequired + Layout.fillWidth: true + echoMode: TextInput.Password + } + } + + Label { + Layout.fillWidth: true + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + visible: engine.jsonRpcClient.initialSetupRequired + opacity: (passwordTextField.text.length > 0 && passwordTextField.text.length < 8) || passwordTextField.text != confirmPasswordTextField.text ? 1 : 0 + text: passwordTextField.text.length < 8 ? qsTr("This password isn't long enought to be secure, add some more characters please.") + : qsTr("The passwords don't match.") + wrapMode: Text.WordWrap + color: app.accentColor + font.pixelSize: app.smallFont + } + + Button { + Layout.fillWidth: true + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.bottomMargin: app.margins + text: qsTr("OK") + enabled: usernameTextField.text.length >= 5 && passwordTextField.text.length >= 8 + && (!engine.jsonRpcClient.initialSetupRequired || confirmPasswordTextField.text == passwordTextField.text) + onClicked: { + if (engine.jsonRpcClient.initialSetupRequired) { + print("create user") + engine.jsonRpcClient.createUser(usernameTextField.text, passwordTextField.text); + } else { + print("authenticate", usernameTextField.text, passwordTextField.text, "nymea-app") + engine.jsonRpcClient.authenticate(usernameTextField.text, passwordTextField.text, "nymea-app"); + } + } + } + } + } + + + Component { + id: errorDialog + ErrorDialog { + + } + } +} diff --git a/nymea-app/ui/connection/SetupWizard.qml b/nymea-app/ui/connection/SetupWizard.qml new file mode 100644 index 00000000..7f230d44 --- /dev/null +++ b/nymea-app/ui/connection/SetupWizard.qml @@ -0,0 +1,28 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" + +Page { + id: root + signal backPressed(); + + header: GuhHeader { + text: qsTr("First setup") + backButtonVisible: true + onBackPressed: root.backPressed() + } + + EmptyViewPlaceholder { + anchors.centerIn: parent + width: parent.width - app.margins * 2 + title: qsTr("Welcome to %1!").arg(app.systemName) + text: qsTr("This %1 system has not been set up yet. This wizard will guide you through a few simple steps to set it up.").arg(app.systemName) + imageSource: "qrc:/styles/%1/logo.svg".arg(styleController.currentStyle) + buttonText: qsTr("Next") + onButtonClicked: { + var page = pageStack.push(Qt.resolvedUrl("LoginPage.qml")); + } + } +} From 203e4440736206b1be77c6bde35f41c1ee2c5c03 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 22 May 2019 13:43:22 +0200 Subject: [PATCH 4/6] Automatically try to connect to localhost in kiosk wrapper --- packaging/linux-common/nymea-app-kiosk-wrapper | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/linux-common/nymea-app-kiosk-wrapper b/packaging/linux-common/nymea-app-kiosk-wrapper index 080f2499..a6db39d2 100755 --- a/packaging/linux-common/nymea-app-kiosk-wrapper +++ b/packaging/linux-common/nymea-app-kiosk-wrapper @@ -1,3 +1,3 @@ #!/bin/sh export QT_IM_MODULE=qtvirtualkeyboard -/usr/bin/nymea-app --kiosk +/usr/bin/nymea-app --kiosk --connect nymeas://127.0.0.1:2222 From 63e5f90632be03c2f71e3ba8290e74147e46e592 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 22 May 2019 13:58:27 +0200 Subject: [PATCH 5/6] Ignore self signed ssl certs for connections to localhost --- libnymea-app-core/connection/nymeaconnection.cpp | 6 +++++- nymea-app/ui/MainPage.qml | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libnymea-app-core/connection/nymeaconnection.cpp b/libnymea-app-core/connection/nymeaconnection.cpp index ba051543..85191214 100644 --- a/libnymea-app-core/connection/nymeaconnection.cpp +++ b/libnymea-app-core/connection/nymeaconnection.cpp @@ -177,8 +177,12 @@ void NymeaConnection::onSslErrors(const QList &errors) certificateFingerprint.append(digest.mid(i,1).toHex().toUpper()); } + // Ignore self signed certs for connections to localhost + if (QHostAddress(transport->url().host()) == QHostAddress::LocalHost || QHostAddress(transport->url().host()) == QHostAddress::LocalHostIPv6) { + ignoredErrors.append(error); + // Check old style fingerprint storage - if (storedFingerPrint == certificateFingerprint) { + } else if (storedFingerPrint == certificateFingerprint) { qDebug() << "This fingerprint is known to us."; ignoredErrors.append(error); diff --git a/nymea-app/ui/MainPage.qml b/nymea-app/ui/MainPage.qml index ac1276d6..ee387cb1 100644 --- a/nymea-app/ui/MainPage.qml +++ b/nymea-app/ui/MainPage.qml @@ -168,7 +168,6 @@ Page { ColorIcon { height: app.iconSize / 2 width: height - visible: infoPane.connectedState !== null && infoPane.connectedState.value === false color: "white" name: "../images/system-update.svg" RotationAnimation on rotation { from: 0; to: 360; duration: 2000; loops: Animation.Infinite; running: engine.systemController.updateRunning } From be28bb2b5602a2b4b1e36f65e2d9488299ff19d3 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 22 May 2019 15:52:50 +0200 Subject: [PATCH 6/6] Add a helper to control the rpi's backlight --- nymea-app/nymea-app.pro | 2 + nymea-app/platformhelper.cpp | 15 +++ nymea-app/platformhelper.h | 7 ++ .../generic/platformhelpergeneric.cpp | 20 +++- .../generic/platformhelpergeneric.h | 11 +- .../generic/raspberrypihelper.cpp | 106 ++++++++++++++++++ .../generic/raspberrypihelper.h | 29 +++++ .../appsettings/LookAndFeelSettingsPage.qml | 29 +++++ 8 files changed, 215 insertions(+), 4 deletions(-) create mode 100644 nymea-app/platformintegration/generic/raspberrypihelper.cpp create mode 100644 nymea-app/platformintegration/generic/raspberrypihelper.h diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index 6b142c18..71cab753 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -17,6 +17,7 @@ linux:!android:LIBS += -lavahi-client -lavahi-common PRE_TARGETDEPS += ../libnymea-app-core ../libnymea-common HEADERS += \ + platformintegration/generic/raspberrypihelper.h \ stylecontroller.h \ pushnotifications.h \ platformhelper.h \ @@ -24,6 +25,7 @@ HEADERS += \ applogcontroller.h SOURCES += main.cpp \ + platformintegration/generic/raspberrypihelper.cpp \ stylecontroller.cpp \ pushnotifications.cpp \ platformhelper.cpp \ diff --git a/nymea-app/platformhelper.cpp b/nymea-app/platformhelper.cpp index 8e276a31..b45f1e9b 100644 --- a/nymea-app/platformhelper.cpp +++ b/nymea-app/platformhelper.cpp @@ -4,3 +4,18 @@ PlatformHelper::PlatformHelper(QObject *parent) : QObject(parent) { } + +bool PlatformHelper::canControlScreen() const +{ + return false; +} + +int PlatformHelper::screenTimeout() const +{ + return 0; +} + +void PlatformHelper::setScreenTimeout(int screenTimeout) +{ + Q_UNUSED(screenTimeout) +} diff --git a/nymea-app/platformhelper.h b/nymea-app/platformhelper.h index eb756e0a..02f23858 100644 --- a/nymea-app/platformhelper.h +++ b/nymea-app/platformhelper.h @@ -12,6 +12,8 @@ class PlatformHelper : public QObject Q_PROPERTY(QString deviceModel READ deviceModel CONSTANT) Q_PROPERTY(QString deviceManufacturer READ deviceManufacturer CONSTANT) Q_PROPERTY(QString machineHostname READ machineHostname CONSTANT) + Q_PROPERTY(bool canControlScreen READ canControlScreen CONSTANT) + Q_PROPERTY(int screenTimeout READ screenTimeout WRITE setScreenTimeout NOTIFY screenTimeoutChanged) public: enum HapticsFeedback { @@ -35,10 +37,15 @@ public: virtual QString deviceModel() const = 0; virtual QString deviceManufacturer() const = 0; + virtual bool canControlScreen() const; + virtual int screenTimeout() const; + virtual void setScreenTimeout(int screenTimeout); + Q_INVOKABLE virtual void vibrate(HapticsFeedback feedbackType) = 0; signals: void permissionsRequestFinished(); + void screenTimeoutChanged(); }; #endif // PLATFORMHELPER_H diff --git a/nymea-app/platformintegration/generic/platformhelpergeneric.cpp b/nymea-app/platformintegration/generic/platformhelpergeneric.cpp index d0b13bf6..1ea004ba 100644 --- a/nymea-app/platformintegration/generic/platformhelpergeneric.cpp +++ b/nymea-app/platformintegration/generic/platformhelpergeneric.cpp @@ -2,7 +2,7 @@ PlatformHelperGeneric::PlatformHelperGeneric(QObject *parent) : PlatformHelper(parent) { - + m_piHelper = new RaspberryPiHelper(this); } void PlatformHelperGeneric::requestPermissions() @@ -49,6 +49,24 @@ QString PlatformHelperGeneric::deviceManufacturer() const return QSysInfo::productType(); } +bool PlatformHelperGeneric::canControlScreen() const +{ + return m_piHelper->active(); +} + +int PlatformHelperGeneric::screenTimeout() const +{ + return m_piHelper->screenTimeout(); +} + +void PlatformHelperGeneric::setScreenTimeout(int timeout) +{ + if (m_piHelper->screenTimeout() != timeout) { + m_piHelper->setScreenTimeout(timeout); + emit screenTimeoutChanged(); + } +} + void PlatformHelperGeneric::vibrate(PlatformHelper::HapticsFeedback feedbyckType) { Q_UNUSED(feedbyckType) diff --git a/nymea-app/platformintegration/generic/platformhelpergeneric.h b/nymea-app/platformintegration/generic/platformhelpergeneric.h index a418e937..49a49252 100644 --- a/nymea-app/platformintegration/generic/platformhelpergeneric.h +++ b/nymea-app/platformintegration/generic/platformhelpergeneric.h @@ -3,6 +3,7 @@ #include #include "platformhelper.h" +#include "raspberrypihelper.h" class PlatformHelperGeneric : public PlatformHelper { @@ -21,10 +22,14 @@ public: virtual QString deviceModel() const override; virtual QString deviceManufacturer() const override; - Q_INVOKABLE virtual void vibrate(HapticsFeedback feedbyckType) override; -signals: + virtual bool canControlScreen() const override; + virtual int screenTimeout() const override; + virtual void setScreenTimeout(int timeout) override; -public slots: + Q_INVOKABLE virtual void vibrate(HapticsFeedback feedbyckType) override; + +private: + RaspberryPiHelper *m_piHelper = nullptr; }; #endif // PLATFORMHELPERGENERIC_H diff --git a/nymea-app/platformintegration/generic/raspberrypihelper.cpp b/nymea-app/platformintegration/generic/raspberrypihelper.cpp new file mode 100644 index 00000000..84ee7d15 --- /dev/null +++ b/nymea-app/platformintegration/generic/raspberrypihelper.cpp @@ -0,0 +1,106 @@ +#include "raspberrypihelper.h" + +#include +#include +#include +#include + +RaspberryPiHelper::RaspberryPiHelper(QObject *parent) : QObject(parent) +{ + m_sysFsFile.setFileName("/sys/class/backlight/rpi_backlight/bl_power"); + bool available = m_sysFsFile.open(QFile::ReadWrite | QFile::Text); + + if (!available) { + return; + } + + qDebug() << "Raspberry Pi detected. Enabling backlight control"; + + screenOn(); + + foreach (QWindow *w, qApp->topLevelWindows()) { + w->installEventFilter(this); + } + + QSettings settings; + m_screenOffTimer.setInterval(settings.value("screenOffTimeout", 15000).toInt()); + m_screenOffTimer.setSingleShot(true); + connect(&m_screenOffTimer, &QTimer::timeout, this, &RaspberryPiHelper::screenOff); + if (m_screenOffTimer.interval() > 0) { + m_screenOffTimer.start(); + } +} + +bool RaspberryPiHelper::active() const +{ + return m_sysFsFile.isOpen(); +} + +int RaspberryPiHelper::screenTimeout() const +{ + return m_screenOffTimer.interval(); +} + +void RaspberryPiHelper::setScreenTimeout(int timeout) +{ + m_screenOffTimer.setInterval(timeout); + QSettings settings; + settings.setValue("screenOffTimeout", timeout); + if (timeout > 0) { + m_screenOffTimer.start(); + } else { + m_screenOffTimer.stop(); + } +} + +bool RaspberryPiHelper::eventFilter(QObject *watched, QEvent *event) +{ + if (m_screenOffTimer.interval() == 0) { + return QObject::eventFilter(watched, event); + } + + QList watchedTypes = { + QEvent::ActivationChange, + QEvent::ApplicationStateChange, + QEvent::KeyPress, + QEvent::KeyRelease, + QEvent::MouseButtonPress, + QEvent::MouseButtonRelease, + QEvent::MouseMove, + QEvent::Show, + QEvent::TouchBegin, + QEvent::TouchEnd, + QEvent::TouchUpdate, + }; + if (!watchedTypes.contains(event->type())) { + return QObject::eventFilter(watched, event); + } + + if (!m_screenOffTimer.isActive()) { + screenOn(); + m_screenOffTimer.start(); + return true; + } + m_screenOffTimer.start( ); + return QObject::eventFilter(watched, event); +} + +void RaspberryPiHelper::screenOn() +{ + qDebug() << "Turning screen on"; + int ret = m_sysFsFile.write("0\n"); + m_sysFsFile.flush(); + if (ret < 0) { + qWarning() << "Failed to power on screen"; + } +} + +void RaspberryPiHelper::screenOff() +{ + qDebug() << "Turning screen off"; + int ret = m_sysFsFile.write("1\n"); + m_sysFsFile.flush(); + if (ret < 0) { + qWarning() << "Failed to power off screen"; + } +} diff --git a/nymea-app/platformintegration/generic/raspberrypihelper.h b/nymea-app/platformintegration/generic/raspberrypihelper.h new file mode 100644 index 00000000..9830aaee --- /dev/null +++ b/nymea-app/platformintegration/generic/raspberrypihelper.h @@ -0,0 +1,29 @@ +#ifndef RASPBERRYPIHELPER_H +#define RASPBERRYPIHELPER_H + +#include +#include +#include + +class RaspberryPiHelper : public QObject +{ + Q_OBJECT +public: + explicit RaspberryPiHelper(QObject *parent = nullptr); + + bool active() const; + int screenTimeout() const; + void setScreenTimeout(int timeout); + + bool eventFilter(QObject *watched, QEvent *event) override; + +private slots: + void screenOn(); + void screenOff(); + +private: + QTimer m_screenOffTimer; + QFile m_sysFsFile; +}; + +#endif // RASPBERRYPIHELPER_H diff --git a/nymea-app/ui/appsettings/LookAndFeelSettingsPage.qml b/nymea-app/ui/appsettings/LookAndFeelSettingsPage.qml index f4d1d824..46c2c783 100644 --- a/nymea-app/ui/appsettings/LookAndFeelSettingsPage.qml +++ b/nymea-app/ui/appsettings/LookAndFeelSettingsPage.qml @@ -89,6 +89,35 @@ Page { checked: settings.showConnectionTabs onClicked: settings.showConnectionTabs = checked } + CheckDelegate { + id: screenOffCheck + Layout.fillWidth: true + text: qsTr("Turn screen off when idle") + visible: PlatformHelper.canControlScreen + checked: PlatformHelper.screenTimeout > 0 + onClicked: PlatformHelper.screenTimeout = (checked ? 15000 : 0) + } + ItemDelegate { + Layout.fillWidth: true + Layout.preferredHeight: screenOffCheck.height + visible: PlatformHelper.screenTimeout > 0 + topPadding: 0 + contentItem: RowLayout { + Label { + Layout.fillWidth: true + text: qsTr("Screen off timeout") + } + SpinBox { + value: PlatformHelper.screenTimeout / 1000 + onValueModified: { + PlatformHelper.screenTimeout = value * 1000 + } + } + Label { + text: qsTr("seconds") + } + } + } } Component {