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