From abbed47f9754f038e007969116942453e361362d Mon Sep 17 00:00:00 2001 From: Floris Bos Date: Wed, 20 Jan 2021 13:04:18 +0100 Subject: [PATCH] Shift+Ctrl+X option screen: allow persisting settings --- OptionsPopup.qml | 180 +++++++++++++++++++++++++++++++------- UseSavedSettingsPopup.qml | 138 +++++++++++++++++++++++++++++ imagewriter.cpp | 39 +++++++++ imagewriter.h | 4 + main.qml | 27 +++++- qml.qrc | 1 + 6 files changed, 355 insertions(+), 34 deletions(-) create mode 100644 UseSavedSettingsPopup.qml diff --git a/OptionsPopup.qml b/OptionsPopup.qml index 6b215b7..587db9a 100644 --- a/OptionsPopup.qml +++ b/OptionsPopup.qml @@ -19,6 +19,7 @@ Popup { padding: 0 closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside property bool initialized: false + property bool hasSavedSettings: false property string config property string cmdline property string firstrun @@ -87,8 +88,21 @@ Popup { ColumnLayout { GroupBox { - title: qsTr("Image customization options (for this session only)") - Layout.fillWidth: true + title: qsTr("Image customization options") + label: RowLayout { + Label { + text: parent.parent.title + } + ComboBox { + id: comboSaveSettings + model: { + [qsTr("for this session only"), + qsTr("to always use")] + } + Layout.minimumWidth: 250 + Layout.maximumHeight: 40 + } + } ColumnLayout { spacing: -10 @@ -97,26 +111,24 @@ Popup { id: chkOverscan text: qsTr("Disable overscan") } - CheckBox { - id: chkHostname - text: qsTr("Set hostname") - onCheckedChanged: { - if (checked) { - fieldHostname.forceActiveFocus() + RowLayout { + CheckBox { + id: chkHostname + text: qsTr("Set hostname:") + onCheckedChanged: { + if (checked) { + fieldHostname.forceActiveFocus() + } } } - } - RowLayout { - enabled: chkHostname.checked - Layout.leftMargin: 40 - TextField { id: fieldHostname + enabled: chkHostname.checked text: "raspberrypi" } Text { text : ".local" - color: parent.enabled ? "black" : "grey" + color: chkHostname.checked ? "black" : "grey" } } CheckBox { @@ -163,6 +175,16 @@ Popup { id: fieldUserPassword echoMode: TextInput.Password Layout.minimumWidth: 200 + property bool alreadyCrypted: false + + onTextEdited: { + if (alreadyCrypted) { + /* User is trying to edit saved + (crypted) password, clear field */ + alreadyCrypted = false + clear() + } + } } } @@ -326,6 +348,7 @@ Popup { } applySettings() + saveSettings() popup.close() } Material.foreground: "#ffffff" @@ -338,14 +361,47 @@ Popup { } } - function openPopup() { - if (!initialized) { - chkBeep.checked = imageWriter.getBoolSetting("beep") - chkTelemtry.checked = imageWriter.getBoolSetting("telemetry") - chkEject.checked = imageWriter.getBoolSetting("eject") - fieldTimezone.model = imageWriter.getTimezoneList() - fieldPublicKey.text = imageWriter.getDefaultPubKey() - fieldWifiCountry.model = imageWriter.getCountryList() + function initialize() { + chkBeep.checked = imageWriter.getBoolSetting("beep") + chkTelemtry.checked = imageWriter.getBoolSetting("telemetry") + chkEject.checked = imageWriter.getBoolSetting("eject") + var settings = imageWriter.getSavedCustomizationSettings() + fieldTimezone.model = imageWriter.getTimezoneList() + fieldPublicKey.text = imageWriter.getDefaultPubKey() + fieldWifiCountry.model = imageWriter.getCountryList() + + if (Object.keys(settings).length) { + comboSaveSettings.currentIndex = 1 + hasSavedSettings = true + } + if ('disableOverscan' in settings) { + chkOverscan.checked = true + } + if ('hostname' in settings) { + fieldHostname.text = settings.hostname + chkHostname.checked = true + } + if ('sshUserPassword' in settings) { + fieldUserPassword.text = settings.sshUserPassword + fieldUserPassword.alreadyCrypted = true + chkSSH.checked = true + radioPasswordAuthentication.checked = true + } + if ('sshAuthorizedKeys' in settings) { + fieldPublicKey.text = settings.sshAuthorizedKeys + chkSSH.checked = true + radioPubKeyAuthentication.checked = true + } + if ('wifiSSID' in settings) { + fieldWifiSSID.text = settings.wifiSSID + chkShowPassword.checked = false + fieldWifiPassword.text = settings.wifiPassword + fieldWifiCountry.currentIndex = fieldWifiCountry.find(settings.wifiCountry) + if (fieldWifiCountry.currentIndex == -1) { + fieldWifiCountry.editText = settings.wifiCountry + } + chkWifi.checked = true + } else { fieldWifiCountry.currentIndex = fieldWifiCountry.find("GB") fieldWifiSSID.text = imageWriter.getSSID() if (fieldWifiSSID.text.length) { @@ -354,21 +410,41 @@ Popup { chkShowPassword.checked = false } } - var tz = imageWriter.getTimezone() - var tzidx = fieldTimezone.find(tz) - if (tzidx === -1) { - fieldTimezone.editText = tz - } else { - fieldTimezone.currentIndex = tzidx - } + } + + var tz; + if ('timezone' in settings) { + chkLocale.checked = true + tz = settings.timezone + } else { + tz = imageWriter.getTimezone() + } + var tzidx = fieldTimezone.find(tz) + if (tzidx === -1) { + fieldTimezone.editText = tz + } else { + fieldTimezone.currentIndex = tzidx + } + if ('keyboardLayout' in settings) { + fieldKeyboardLayout.text = settings.keyboardLayout + } else { /* Lacking an easy cross-platform to fetch keyboard layout from host system, just default to "gb" for people in UK time zone for now, and "us" for everyone else */ if (tz == "Europe/London") { fieldKeyboardLayout.text = "gb" } + } + if ('skipFirstUse' in settings) { + chkSkipFirstUse.checked = true + } - initialized = true + initialized = true + } + + function openPopup() { + if (!initialized) { + initialize() } open() @@ -407,7 +483,8 @@ Popup { addFirstRun("FIRSTUSERHOME=`getent passwd 1000 | cut -d: -f6`") if (radioPasswordAuthentication.checked) { - addFirstRun("echo \"$FIRSTUSER:\""+escapeshellarg(imageWriter.crypt(fieldUserPassword.text))+" | chpasswd -e") + var cryptedPassword = fieldUserPassword.alreadyCrypted ? fieldUserPassword.text : imageWriter.crypt(fieldUserPassword.text) + addFirstRun("echo \"$FIRSTUSER:\""+escapeshellarg(cryptedPassword)+" | chpasswd -e") } if (radioPubKeyAuthentication.checked) { var pubkey = fieldPublicKey.text.replace(/\n/g, "") @@ -466,6 +543,47 @@ Popup { } imageWriter.setImageCustomization(config, cmdline, firstrun) + } + + function saveSettings() + { + if (comboSaveSettings.currentIndex == 1) { + hasSavedSettings = true + var settings = { }; + if (chkOverscan.checked) { + settings.disableOverscan = true + } + if (chkHostname.checked && fieldHostname.length) { + settings.hostname = fieldHostname.text + } + if (chkSSH.checked) { + if (radioPasswordAuthentication.checked) { + settings.sshUserPassword = fieldUserPassword.alreadyCrypted ? fieldUserPassword.text : imageWriter.crypt(fieldUserPassword.text) + } + if (radioPubKeyAuthentication.checked) { + settings.sshAuthorizedKeys = fieldPublicKey.text + } + } + if (chkWifi.checked) { + settings.wifiSSID = fieldWifiSSID.text + settings.wifiPassword = fieldWifiPassword.text + settings.wifiCountry = fieldWifiCountry.editText + } + if (chkLocale.checked) { + settings.timezone = fieldTimezone.editText + settings.keyboardLayout = fieldKeyboardLayout.text + if (chkSkipFirstUse.checked) { + settings.skipFirstUse = true + } + } + + imageWriter.setSavedCustomizationSettings(settings) + + } else if (hasSavedSettings) { + imageWriter.clearSavedCustomizationSettings() + hasSavedSettings = false + } + imageWriter.setSetting("beep", chkBeep.checked) imageWriter.setSetting("eject", chkEject.checked) imageWriter.setSetting("telemetry", chkTelemtry.checked) diff --git a/UseSavedSettingsPopup.qml b/UseSavedSettingsPopup.qml new file mode 100644 index 0000000..3119176 --- /dev/null +++ b/UseSavedSettingsPopup.qml @@ -0,0 +1,138 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright (C) 2021 Raspberry Pi (Trading) Limited + */ + +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.0 +import QtQuick.Controls.Material 2.2 + +Popup { + id: msgpopup + x: 75 + y: (parent.height-height)/2 + width: parent.width-150 + height: msgpopupbody.implicitHeight+150 + padding: 0 + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + + signal yes() + signal no() + signal editSettings() + + // background of title + Rectangle { + color: "#f5f5f5" + anchors.right: parent.right + anchors.top: parent.top + height: 35 + width: parent.width + } + // line under title + Rectangle { + color: "#afafaf" + width: parent.width + y: 35 + implicitHeight: 1 + } + + Text { + id: msgx + text: "X" + anchors.right: parent.right + anchors.top: parent.top + anchors.rightMargin: 25 + anchors.topMargin: 10 + font.family: roboto.name + font.bold: true + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + msgpopup.close() + } + } + } + + ColumnLayout { + spacing: 20 + anchors.fill: parent + + Text { + id: msgpopupheader + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + Layout.fillWidth: true + Layout.topMargin: 10 + font.family: roboto.name + font.bold: true + text: qsTr("Warning: advanced settings set") + } + + Text { + id: msgpopupbody + font.pointSize: 12 + wrapMode: Text.Wrap + textFormat: Text.StyledText + font.family: roboto.name + Layout.maximumWidth: msgpopup.width-50 + Layout.fillHeight: true + Layout.leftMargin: 25 + Layout.topMargin: 25 + Accessible.name: text.replace(/<\/?[^>]+(>|$)/g, "") + text: qsTr("Would you like to apply the image customization settings saved earlier?") + } + + RowLayout { + Layout.alignment: Qt.AlignCenter | Qt.AlignBottom + Layout.bottomMargin: 10 + spacing: 20 + + Button { + text: qsTr("NO, CLEAR SETTINGS") + onClicked: { + msgpopup.close() + msgpopup.no() + } + Material.foreground: "#ffffff" + Material.background: "#c51a4a" + font.family: roboto.name + Accessible.onPressAction: clicked() + } + + Button { + text: qsTr("YES") + onClicked: { + msgpopup.close() + msgpopup.yes() + } + Material.foreground: "#ffffff" + Material.background: "#c51a4a" + font.family: roboto.name + Accessible.onPressAction: clicked() + } + + Button { + text: qsTr("EDIT SETTINGS") + onClicked: { + msgpopup.close() + msgpopup.editSettings() + } + Material.foreground: "#ffffff" + Material.background: "#c51a4a" + font.family: roboto.name + Accessible.onPressAction: clicked() + } + + Text { text: " " } + } + } + + function openPopup() { + open() + // trigger screen reader to speak out message + msgpopupbody.forceActiveFocus() + } +} diff --git a/imagewriter.cpp b/imagewriter.cpp index 46934e0..b60fa52 100644 --- a/imagewriter.cpp +++ b/imagewriter.cpp @@ -926,6 +926,45 @@ QString ImageWriter::crypt(const QByteArray &password) return sha256_crypt(password.constData(), salt.constData()); } +void ImageWriter::setSavedCustomizationSettings(const QVariantMap &map) +{ + _settings.beginGroup("imagecustomization"); + _settings.remove(""); + for (QString key : map.keys()) { + _settings.setValue(key, map.value(key)); + } + _settings.endGroup(); +} + +QVariantMap ImageWriter::getSavedCustomizationSettings() +{ + QVariantMap result; + + _settings.beginGroup("imagecustomization"); + for (QString key : _settings.childKeys()) { + result.insert(key, _settings.value(key)); + } + _settings.endGroup(); + + return result; +} + +void ImageWriter::clearSavedCustomizationSettings() +{ + _settings.beginGroup("imagecustomization"); + _settings.remove(""); + _settings.endGroup(); +} + +bool ImageWriter::hasSavedCustomizationSettings() +{ + _settings.beginGroup("imagecustomization"); + bool result = !_settings.childKeys().isEmpty(); + _settings.endGroup(); + + return result; +} + void MountUtilsLog(std::string msg) { qDebug() << "mountutils:" << msg.c_str(); } diff --git a/imagewriter.h b/imagewriter.h index 5d71165..6f0c42f 100644 --- a/imagewriter.h +++ b/imagewriter.h @@ -103,6 +103,10 @@ public: Q_INVOKABLE bool getBoolSetting(const QString &key); Q_INVOKABLE void setSetting(const QString &key, const QVariant &value); Q_INVOKABLE void setImageCustomization(const QByteArray &config, const QByteArray &cmdline, const QByteArray &firstrun); + Q_INVOKABLE void setSavedCustomizationSettings(const QVariantMap &map); + Q_INVOKABLE QVariantMap getSavedCustomizationSettings(); + Q_INVOKABLE void clearSavedCustomizationSettings(); + Q_INVOKABLE bool hasSavedCustomizationSettings(); Q_INVOKABLE QString crypt(const QByteArray &password); diff --git a/main.qml b/main.qml index 60fe622..5967284 100644 --- a/main.qml +++ b/main.qml @@ -188,10 +188,15 @@ ApplicationWindow { Material.background: "#ffffff" Material.foreground: "#c51a4a" onClicked: { - if (!imageWriter.readyToWrite()) - return; + if (!imageWriter.readyToWrite()) { + return + } - confirmwritepopup.askForConfirmation() + if (!optionspopup.initialized && imageWriter.hasSavedCustomizationSettings()) { + usesavedsettingspopup.openPopup() + } else { + confirmwritepopup.askForConfirmation() + } } Accessible.onPressAction: clicked() } @@ -790,6 +795,22 @@ ApplicationWindow { id: optionspopup } + UseSavedSettingsPopup { + id: usesavedsettingspopup + onYes: { + optionspopup.initialize() + optionspopup.applySettings() + confirmwritepopup.askForConfirmation() + } + onNo: { + imageWriter.clearSavedCustomizationSettings() + confirmwritepopup.askForConfirmation() + } + onEditSettings: { + optionspopup.openPopup() + } + } + /* Utility functions */ function httpRequest(url, callback) { var xhr = new XMLHttpRequest(); diff --git a/qml.qrc b/qml.qrc index be982de..fc65b57 100644 --- a/qml.qrc +++ b/qml.qrc @@ -25,5 +25,6 @@ OptionsPopup.qml countries.txt timezones.txt + UseSavedSettingsPopup.qml