Shift+Ctrl+X option screen: allow persisting settings

This commit is contained in:
Floris Bos 2021-01-20 13:04:18 +01:00
parent a6150f7bc5
commit abbed47f97
6 changed files with 355 additions and 34 deletions

View file

@ -19,6 +19,7 @@ Popup {
padding: 0 padding: 0
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
property bool initialized: false property bool initialized: false
property bool hasSavedSettings: false
property string config property string config
property string cmdline property string cmdline
property string firstrun property string firstrun
@ -87,8 +88,21 @@ Popup {
ColumnLayout { ColumnLayout {
GroupBox { GroupBox {
title: qsTr("Image customization options (for this session only)") title: qsTr("Image customization options")
Layout.fillWidth: true 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 { ColumnLayout {
spacing: -10 spacing: -10
@ -97,26 +111,24 @@ Popup {
id: chkOverscan id: chkOverscan
text: qsTr("Disable overscan") text: qsTr("Disable overscan")
} }
RowLayout {
CheckBox { CheckBox {
id: chkHostname id: chkHostname
text: qsTr("Set hostname") text: qsTr("Set hostname:")
onCheckedChanged: { onCheckedChanged: {
if (checked) { if (checked) {
fieldHostname.forceActiveFocus() fieldHostname.forceActiveFocus()
} }
} }
} }
RowLayout {
enabled: chkHostname.checked
Layout.leftMargin: 40
TextField { TextField {
id: fieldHostname id: fieldHostname
enabled: chkHostname.checked
text: "raspberrypi" text: "raspberrypi"
} }
Text { Text {
text : ".local" text : ".local"
color: parent.enabled ? "black" : "grey" color: chkHostname.checked ? "black" : "grey"
} }
} }
CheckBox { CheckBox {
@ -163,6 +175,16 @@ Popup {
id: fieldUserPassword id: fieldUserPassword
echoMode: TextInput.Password echoMode: TextInput.Password
Layout.minimumWidth: 200 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() applySettings()
saveSettings()
popup.close() popup.close()
} }
Material.foreground: "#ffffff" Material.foreground: "#ffffff"
@ -338,14 +361,47 @@ Popup {
} }
} }
function openPopup() { function initialize() {
if (!initialized) {
chkBeep.checked = imageWriter.getBoolSetting("beep") chkBeep.checked = imageWriter.getBoolSetting("beep")
chkTelemtry.checked = imageWriter.getBoolSetting("telemetry") chkTelemtry.checked = imageWriter.getBoolSetting("telemetry")
chkEject.checked = imageWriter.getBoolSetting("eject") chkEject.checked = imageWriter.getBoolSetting("eject")
var settings = imageWriter.getSavedCustomizationSettings()
fieldTimezone.model = imageWriter.getTimezoneList() fieldTimezone.model = imageWriter.getTimezoneList()
fieldPublicKey.text = imageWriter.getDefaultPubKey() fieldPublicKey.text = imageWriter.getDefaultPubKey()
fieldWifiCountry.model = imageWriter.getCountryList() 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") fieldWifiCountry.currentIndex = fieldWifiCountry.find("GB")
fieldWifiSSID.text = imageWriter.getSSID() fieldWifiSSID.text = imageWriter.getSSID()
if (fieldWifiSSID.text.length) { if (fieldWifiSSID.text.length) {
@ -354,23 +410,43 @@ Popup {
chkShowPassword.checked = false chkShowPassword.checked = false
} }
} }
var tz = imageWriter.getTimezone() }
var tz;
if ('timezone' in settings) {
chkLocale.checked = true
tz = settings.timezone
} else {
tz = imageWriter.getTimezone()
}
var tzidx = fieldTimezone.find(tz) var tzidx = fieldTimezone.find(tz)
if (tzidx === -1) { if (tzidx === -1) {
fieldTimezone.editText = tz fieldTimezone.editText = tz
} else { } else {
fieldTimezone.currentIndex = tzidx fieldTimezone.currentIndex = tzidx
} }
if ('keyboardLayout' in settings) {
fieldKeyboardLayout.text = settings.keyboardLayout
} else {
/* Lacking an easy cross-platform to fetch keyboard layout /* Lacking an easy cross-platform to fetch keyboard layout
from host system, just default to "gb" for people in from host system, just default to "gb" for people in
UK time zone for now, and "us" for everyone else */ UK time zone for now, and "us" for everyone else */
if (tz == "Europe/London") { if (tz == "Europe/London") {
fieldKeyboardLayout.text = "gb" fieldKeyboardLayout.text = "gb"
} }
}
if ('skipFirstUse' in settings) {
chkSkipFirstUse.checked = true
}
initialized = true initialized = true
} }
function openPopup() {
if (!initialized) {
initialize()
}
open() open()
} }
@ -407,7 +483,8 @@ Popup {
addFirstRun("FIRSTUSERHOME=`getent passwd 1000 | cut -d: -f6`") addFirstRun("FIRSTUSERHOME=`getent passwd 1000 | cut -d: -f6`")
if (radioPasswordAuthentication.checked) { 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) { if (radioPubKeyAuthentication.checked) {
var pubkey = fieldPublicKey.text.replace(/\n/g, "") var pubkey = fieldPublicKey.text.replace(/\n/g, "")
@ -466,6 +543,47 @@ Popup {
} }
imageWriter.setImageCustomization(config, cmdline, firstrun) 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("beep", chkBeep.checked)
imageWriter.setSetting("eject", chkEject.checked) imageWriter.setSetting("eject", chkEject.checked)
imageWriter.setSetting("telemetry", chkTelemtry.checked) imageWriter.setSetting("telemetry", chkTelemtry.checked)

138
UseSavedSettingsPopup.qml Normal file
View file

@ -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()
}
}

View file

@ -926,6 +926,45 @@ QString ImageWriter::crypt(const QByteArray &password)
return sha256_crypt(password.constData(), salt.constData()); 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) { void MountUtilsLog(std::string msg) {
qDebug() << "mountutils:" << msg.c_str(); qDebug() << "mountutils:" << msg.c_str();
} }

View file

@ -103,6 +103,10 @@ public:
Q_INVOKABLE bool getBoolSetting(const QString &key); Q_INVOKABLE bool getBoolSetting(const QString &key);
Q_INVOKABLE void setSetting(const QString &key, const QVariant &value); 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 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); Q_INVOKABLE QString crypt(const QByteArray &password);

View file

@ -188,11 +188,16 @@ ApplicationWindow {
Material.background: "#ffffff" Material.background: "#ffffff"
Material.foreground: "#c51a4a" Material.foreground: "#c51a4a"
onClicked: { onClicked: {
if (!imageWriter.readyToWrite()) if (!imageWriter.readyToWrite()) {
return; return
}
if (!optionspopup.initialized && imageWriter.hasSavedCustomizationSettings()) {
usesavedsettingspopup.openPopup()
} else {
confirmwritepopup.askForConfirmation() confirmwritepopup.askForConfirmation()
} }
}
Accessible.onPressAction: clicked() Accessible.onPressAction: clicked()
} }
} }
@ -790,6 +795,22 @@ ApplicationWindow {
id: optionspopup id: optionspopup
} }
UseSavedSettingsPopup {
id: usesavedsettingspopup
onYes: {
optionspopup.initialize()
optionspopup.applySettings()
confirmwritepopup.askForConfirmation()
}
onNo: {
imageWriter.clearSavedCustomizationSettings()
confirmwritepopup.askForConfirmation()
}
onEditSettings: {
optionspopup.openPopup()
}
}
/* Utility functions */ /* Utility functions */
function httpRequest(url, callback) { function httpRequest(url, callback) {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();

View file

@ -25,5 +25,6 @@
<file>OptionsPopup.qml</file> <file>OptionsPopup.qml</file>
<file>countries.txt</file> <file>countries.txt</file>
<file>timezones.txt</file> <file>timezones.txt</file>
<file>UseSavedSettingsPopup.qml</file>
</qresource> </qresource>
</RCC> </RCC>