to use."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:35
+msgid "Configure systemd services"
+msgstr ""
+
+#: src/modules/services-systemd/main.py:68
+#: src/modules/services-openrc/main.py:102
+msgid "Cannot modify service"
+msgstr ""
+
+#: src/modules/services-systemd/main.py:69
+msgid ""
+"systemctl {arg!s} call in chroot returned error code {num!s}."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:72
+#: src/modules/services-systemd/main.py:76
+msgid "Cannot enable systemd service {name!s}."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:74
+msgid "Cannot enable systemd target {name!s}."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:78
+msgid "Cannot disable systemd target {name!s}."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:80
+msgid "Cannot mask systemd unit {name!s}."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:82
+msgid ""
+"Unknown systemd commands {command!s} and "
+"{suffix!s} for unit {name!s}."
+msgstr ""
+
+#: src/modules/umount/main.py:40
+msgid "Unmount file systems."
+msgstr ""
+
+#: src/modules/unpackfs/main.py:44
+msgid "Filling up filesystems."
+msgstr ""
+
+#: src/modules/unpackfs/main.py:257
+msgid "rsync failed with error code {}."
+msgstr ""
+
+#: src/modules/unpackfs/main.py:302
+msgid "Unpacking image {}/{}, file {}/{}"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:317
+msgid "Starting to unpack {}"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:326 src/modules/unpackfs/main.py:448
+msgid "Failed to unpack image \"{}\""
+msgstr ""
+
+#: src/modules/unpackfs/main.py:415
+msgid "No mount point for root partition"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:416
+msgid "globalstorage does not contain a \"rootMountPoint\" key, doing nothing"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:421
+msgid "Bad mount point for root partition"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:422
+msgid "rootMountPoint is \"{}\", which does not exist, doing nothing"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:438 src/modules/unpackfs/main.py:442
+#: src/modules/unpackfs/main.py:462
+msgid "Bad unsquash configuration"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:439
+msgid "The filesystem for \"{}\" ({}) is not supported by your current kernel"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:443
+msgid "The source filesystem \"{}\" does not exist"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:449
+msgid ""
+"Failed to find unsquashfs, make sure you have the squashfs-tools package "
+"installed"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:463
+msgid "The destination \"{}\" in the target system is not a directory"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:523
+msgid "Cannot write KDM configuration file"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:524
+msgid "KDM config file {!s} does not exist"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:585
+msgid "Cannot write LXDM configuration file"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:586
+msgid "LXDM config file {!s} does not exist"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:669
+msgid "Cannot write LightDM configuration file"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:670
+msgid "LightDM config file {!s} does not exist"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:744
+msgid "Cannot configure LightDM"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:745
+msgid "No LightDM greeter installed."
+msgstr ""
+
+#: src/modules/displaymanager/main.py:776
+msgid "Cannot write SLIM configuration file"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:777
+msgid "SLIM config file {!s} does not exist"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:903
+msgid "No display managers selected for the displaymanager module."
+msgstr ""
+
+#: src/modules/displaymanager/main.py:904
+msgid ""
+"The displaymanagers list is empty or undefined in bothglobalstorage and "
+"displaymanager.conf."
+msgstr ""
+
+#: src/modules/displaymanager/main.py:986
+msgid "Display manager configuration was incomplete"
+msgstr ""
+
+#: src/modules/initcpiocfg/main.py:37
+msgid "Configuring mkinitcpio."
+msgstr ""
+
+#: src/modules/initcpiocfg/main.py:210
+#: src/modules/luksopenswaphookcfg/main.py:100
+#: src/modules/initramfscfg/main.py:99 src/modules/openrcdmcryptcfg/main.py:83
+#: src/modules/fstab/main.py:339 src/modules/localecfg/main.py:145
+#: src/modules/networkcfg/main.py:49
+msgid "No root mount point is given for
{!s}
to use."
+msgstr ""
+
+#: src/modules/luksopenswaphookcfg/main.py:35
+msgid "Configuring encrypted swap."
+msgstr ""
+
+#: src/modules/rawfs/main.py:35
+msgid "Installing data."
+msgstr ""
+
+#: src/modules/services-openrc/main.py:38
+msgid "Configure OpenRC services"
+msgstr ""
+
+#: src/modules/services-openrc/main.py:66
+msgid "Cannot add service {name!s} to run-level {level!s}."
+msgstr ""
+
+#: src/modules/services-openrc/main.py:68
+msgid "Cannot remove service {name!s} from run-level {level!s}."
+msgstr ""
+
+#: src/modules/services-openrc/main.py:70
+msgid ""
+"Unknown service-action {arg!s} for service {name!s} in run-"
+"level {level!s}."
+msgstr ""
+
+#: src/modules/services-openrc/main.py:103
+msgid ""
+"rc-update {arg!s} call in chroot returned error code {num!s}."
+msgstr ""
+
+#: src/modules/services-openrc/main.py:110
+msgid "Target runlevel does not exist"
+msgstr ""
+
+#: src/modules/services-openrc/main.py:111
+msgid ""
+"The path for runlevel {level!s} is {path!s}, which does not "
+"exist."
+msgstr ""
+
+#: src/modules/services-openrc/main.py:119
+msgid "Target service does not exist"
+msgstr ""
+
+#: src/modules/services-openrc/main.py:120
+msgid ""
+"The path for service {name!s} is {path!s}, which does not "
+"exist."
+msgstr ""
+
+#: src/modules/plymouthcfg/main.py:36
+msgid "Configure Plymouth theme"
+msgstr ""
+
+#: src/modules/packages/main.py:59 src/modules/packages/main.py:68
+#: src/modules/packages/main.py:78
+msgid "Install packages."
+msgstr ""
+
+#: src/modules/packages/main.py:66
+#, python-format
+msgid "Processing packages (%(count)d / %(total)d)"
+msgstr ""
+
+#: src/modules/packages/main.py:71
+#, python-format
+msgid "Installing one package."
+msgid_plural "Installing %(num)d packages."
+msgstr[0] ""
+msgstr[1] ""
+
+#: src/modules/packages/main.py:74
+#, python-format
+msgid "Removing one package."
+msgid_plural "Removing %(num)d packages."
+msgstr[0] ""
+msgstr[1] ""
+
+#: src/modules/bootloader/main.py:51
+msgid "Install bootloader."
+msgstr ""
+
+#: src/modules/hwclock/main.py:35
+msgid "Setting hardware clock."
+msgstr ""
+
+#: src/modules/dracut/main.py:36
+msgid "Creating initramfs with dracut."
+msgstr ""
+
+#: src/modules/dracut/main.py:58
+msgid "Failed to run dracut on the target"
+msgstr ""
+
+#: src/modules/dracut/main.py:59
+msgid "The exit code was {}"
+msgstr ""
+
+#: src/modules/initramfscfg/main.py:41
+msgid "Configuring initramfs."
+msgstr ""
+
+#: src/modules/openrcdmcryptcfg/main.py:34
+msgid "Configuring OpenRC dmcrypt service."
+msgstr ""
+
+#: src/modules/fstab/main.py:38
+msgid "Writing fstab."
+msgstr ""
+
+#: src/modules/dummypython/main.py:44
+msgid "Dummy python job."
+msgstr ""
+
+#: src/modules/dummypython/main.py:46 src/modules/dummypython/main.py:102
+#: src/modules/dummypython/main.py:103
+msgid "Dummy python step {}"
+msgstr ""
+
+#: src/modules/localecfg/main.py:39
+msgid "Configuring locales."
+msgstr ""
+
+#: src/modules/networkcfg/main.py:37
+msgid "Saving network configuration."
+msgstr ""
diff --git a/lang/python/tg/LC_MESSAGES/python.mo b/lang/python/tg/LC_MESSAGES/python.mo
new file mode 100644
index 000000000..1b88d8307
Binary files /dev/null and b/lang/python/tg/LC_MESSAGES/python.mo differ
diff --git a/lang/python/tg/LC_MESSAGES/python.po b/lang/python/tg/LC_MESSAGES/python.po
new file mode 100644
index 000000000..a5ff5ce5e
--- /dev/null
+++ b/lang/python/tg/LC_MESSAGES/python.po
@@ -0,0 +1,343 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+# Translators:
+# Victor Ibragimov , 2020
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-06-18 15:42+0200\n"
+"PO-Revision-Date: 2017-08-09 10:34+0000\n"
+"Last-Translator: Victor Ibragimov , 2020\n"
+"Language-Team: Tajik (https://www.transifex.com/calamares/teams/20061/tg/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: tg\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: src/modules/grubcfg/main.py:37
+msgid "Configure GRUB."
+msgstr "Танзимоти GRUB."
+
+#: src/modules/mount/main.py:38
+msgid "Mounting partitions."
+msgstr "Васлкунии қисмҳои диск."
+
+#: src/modules/mount/main.py:150 src/modules/initcpiocfg/main.py:205
+#: src/modules/initcpiocfg/main.py:209
+#: src/modules/luksopenswaphookcfg/main.py:95
+#: src/modules/luksopenswaphookcfg/main.py:99 src/modules/rawfs/main.py:173
+#: src/modules/initramfscfg/main.py:94 src/modules/initramfscfg/main.py:98
+#: src/modules/openrcdmcryptcfg/main.py:78
+#: src/modules/openrcdmcryptcfg/main.py:82 src/modules/fstab/main.py:332
+#: src/modules/fstab/main.py:338 src/modules/localecfg/main.py:144
+#: src/modules/networkcfg/main.py:48
+msgid "Configuration Error"
+msgstr "Хатои танзимкунӣ"
+
+#: src/modules/mount/main.py:151 src/modules/initcpiocfg/main.py:206
+#: src/modules/luksopenswaphookcfg/main.py:96 src/modules/rawfs/main.py:174
+#: src/modules/initramfscfg/main.py:95 src/modules/openrcdmcryptcfg/main.py:79
+#: src/modules/fstab/main.py:333
+msgid "No partitions are defined for
{!s}
to use."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:35
+msgid "Configure systemd services"
+msgstr ""
+
+#: src/modules/services-systemd/main.py:68
+#: src/modules/services-openrc/main.py:102
+msgid "Cannot modify service"
+msgstr ""
+
+#: src/modules/services-systemd/main.py:69
+msgid ""
+"systemctl {arg!s} call in chroot returned error code {num!s}."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:72
+#: src/modules/services-systemd/main.py:76
+msgid "Cannot enable systemd service {name!s}."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:74
+msgid "Cannot enable systemd target {name!s}."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:78
+msgid "Cannot disable systemd target {name!s}."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:80
+msgid "Cannot mask systemd unit {name!s}."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:82
+msgid ""
+"Unknown systemd commands {command!s} and "
+"{suffix!s} for unit {name!s}."
+msgstr ""
+
+#: src/modules/umount/main.py:40
+msgid "Unmount file systems."
+msgstr ""
+
+#: src/modules/unpackfs/main.py:44
+msgid "Filling up filesystems."
+msgstr ""
+
+#: src/modules/unpackfs/main.py:257
+msgid "rsync failed with error code {}."
+msgstr ""
+
+#: src/modules/unpackfs/main.py:302
+msgid "Unpacking image {}/{}, file {}/{}"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:317
+msgid "Starting to unpack {}"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:326 src/modules/unpackfs/main.py:448
+msgid "Failed to unpack image \"{}\""
+msgstr ""
+
+#: src/modules/unpackfs/main.py:415
+msgid "No mount point for root partition"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:416
+msgid "globalstorage does not contain a \"rootMountPoint\" key, doing nothing"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:421
+msgid "Bad mount point for root partition"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:422
+msgid "rootMountPoint is \"{}\", which does not exist, doing nothing"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:438 src/modules/unpackfs/main.py:442
+#: src/modules/unpackfs/main.py:462
+msgid "Bad unsquash configuration"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:439
+msgid "The filesystem for \"{}\" ({}) is not supported by your current kernel"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:443
+msgid "The source filesystem \"{}\" does not exist"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:449
+msgid ""
+"Failed to find unsquashfs, make sure you have the squashfs-tools package "
+"installed"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:463
+msgid "The destination \"{}\" in the target system is not a directory"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:523
+msgid "Cannot write KDM configuration file"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:524
+msgid "KDM config file {!s} does not exist"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:585
+msgid "Cannot write LXDM configuration file"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:586
+msgid "LXDM config file {!s} does not exist"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:669
+msgid "Cannot write LightDM configuration file"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:670
+msgid "LightDM config file {!s} does not exist"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:744
+msgid "Cannot configure LightDM"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:745
+msgid "No LightDM greeter installed."
+msgstr ""
+
+#: src/modules/displaymanager/main.py:776
+msgid "Cannot write SLIM configuration file"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:777
+msgid "SLIM config file {!s} does not exist"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:903
+msgid "No display managers selected for the displaymanager module."
+msgstr ""
+
+#: src/modules/displaymanager/main.py:904
+msgid ""
+"The displaymanagers list is empty or undefined in bothglobalstorage and "
+"displaymanager.conf."
+msgstr ""
+
+#: src/modules/displaymanager/main.py:986
+msgid "Display manager configuration was incomplete"
+msgstr ""
+
+#: src/modules/initcpiocfg/main.py:37
+msgid "Configuring mkinitcpio."
+msgstr ""
+
+#: src/modules/initcpiocfg/main.py:210
+#: src/modules/luksopenswaphookcfg/main.py:100
+#: src/modules/initramfscfg/main.py:99 src/modules/openrcdmcryptcfg/main.py:83
+#: src/modules/fstab/main.py:339 src/modules/localecfg/main.py:145
+#: src/modules/networkcfg/main.py:49
+msgid "No root mount point is given for
{!s}
to use."
+msgstr ""
+
+#: src/modules/luksopenswaphookcfg/main.py:35
+msgid "Configuring encrypted swap."
+msgstr ""
+
+#: src/modules/rawfs/main.py:35
+msgid "Installing data."
+msgstr ""
+
+#: src/modules/services-openrc/main.py:38
+msgid "Configure OpenRC services"
+msgstr ""
+
+#: src/modules/services-openrc/main.py:66
+msgid "Cannot add service {name!s} to run-level {level!s}."
+msgstr ""
+
+#: src/modules/services-openrc/main.py:68
+msgid "Cannot remove service {name!s} from run-level {level!s}."
+msgstr ""
+
+#: src/modules/services-openrc/main.py:70
+msgid ""
+"Unknown service-action {arg!s} for service {name!s} in run-"
+"level {level!s}."
+msgstr ""
+
+#: src/modules/services-openrc/main.py:103
+msgid ""
+"rc-update {arg!s} call in chroot returned error code {num!s}."
+msgstr ""
+
+#: src/modules/services-openrc/main.py:110
+msgid "Target runlevel does not exist"
+msgstr ""
+
+#: src/modules/services-openrc/main.py:111
+msgid ""
+"The path for runlevel {level!s} is {path!s}, which does not "
+"exist."
+msgstr ""
+
+#: src/modules/services-openrc/main.py:119
+msgid "Target service does not exist"
+msgstr ""
+
+#: src/modules/services-openrc/main.py:120
+msgid ""
+"The path for service {name!s} is {path!s}, which does not "
+"exist."
+msgstr ""
+
+#: src/modules/plymouthcfg/main.py:36
+msgid "Configure Plymouth theme"
+msgstr ""
+
+#: src/modules/packages/main.py:59 src/modules/packages/main.py:68
+#: src/modules/packages/main.py:78
+msgid "Install packages."
+msgstr ""
+
+#: src/modules/packages/main.py:66
+#, python-format
+msgid "Processing packages (%(count)d / %(total)d)"
+msgstr ""
+
+#: src/modules/packages/main.py:71
+#, python-format
+msgid "Installing one package."
+msgid_plural "Installing %(num)d packages."
+msgstr[0] ""
+msgstr[1] ""
+
+#: src/modules/packages/main.py:74
+#, python-format
+msgid "Removing one package."
+msgid_plural "Removing %(num)d packages."
+msgstr[0] ""
+msgstr[1] ""
+
+#: src/modules/bootloader/main.py:51
+msgid "Install bootloader."
+msgstr ""
+
+#: src/modules/hwclock/main.py:35
+msgid "Setting hardware clock."
+msgstr ""
+
+#: src/modules/dracut/main.py:36
+msgid "Creating initramfs with dracut."
+msgstr ""
+
+#: src/modules/dracut/main.py:58
+msgid "Failed to run dracut on the target"
+msgstr ""
+
+#: src/modules/dracut/main.py:59
+msgid "The exit code was {}"
+msgstr ""
+
+#: src/modules/initramfscfg/main.py:41
+msgid "Configuring initramfs."
+msgstr ""
+
+#: src/modules/openrcdmcryptcfg/main.py:34
+msgid "Configuring OpenRC dmcrypt service."
+msgstr ""
+
+#: src/modules/fstab/main.py:38
+msgid "Writing fstab."
+msgstr ""
+
+#: src/modules/dummypython/main.py:44
+msgid "Dummy python job."
+msgstr ""
+
+#: src/modules/dummypython/main.py:46 src/modules/dummypython/main.py:102
+#: src/modules/dummypython/main.py:103
+msgid "Dummy python step {}"
+msgstr ""
+
+#: src/modules/localecfg/main.py:39
+msgid "Configuring locales."
+msgstr ""
+
+#: src/modules/networkcfg/main.py:37
+msgid "Saving network configuration."
+msgstr ""
diff --git a/src/calamares/CMakeLists.txt b/src/calamares/CMakeLists.txt
index b632567b8..ff91904e2 100644
--- a/src/calamares/CMakeLists.txt
+++ b/src/calamares/CMakeLists.txt
@@ -34,9 +34,8 @@ include_directories(
# Translations
include( CalamaresAddTranslations )
add_calamares_translations( ${CALAMARES_TRANSLATION_LANGUAGES} )
-qt5_add_resources( calamaresRc calamares.qrc )
-add_executable( calamares_bin ${calamaresSources} ${calamaresRc} ${trans_outfile} )
+add_executable( calamares_bin ${calamaresSources} calamares.qrc ${trans_outfile} )
target_include_directories( calamares_bin PRIVATE ${CMAKE_SOURCE_DIR} )
set_target_properties(calamares_bin
PROPERTIES
@@ -45,6 +44,7 @@ set_target_properties(calamares_bin
)
calamares_automoc( calamares_bin )
calamares_autouic( calamares_bin )
+calamares_autorcc( calamares_bin )
target_link_libraries( calamares_bin
PRIVATE
diff --git a/src/libcalamares/CMakeLists.txt b/src/libcalamares/CMakeLists.txt
index 0516b5613..8e209f8a3 100644
--- a/src/libcalamares/CMakeLists.txt
+++ b/src/libcalamares/CMakeLists.txt
@@ -27,8 +27,10 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} )
configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/CalamaresConfig.h.in
${CMAKE_CURRENT_BINARY_DIR}/CalamaresConfig.h )
-configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/../calamares/CalamaresVersion.h.in
+configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/CalamaresVersion.h.in
${CMAKE_CURRENT_BINARY_DIR}/CalamaresVersion.h )
+configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/CalamaresVersionX.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/CalamaresVersionX.h )
set( OPTIONAL_PRIVATE_LIBRARIES "" )
set( OPTIONAL_PUBLIC_LIBRARIES "" )
@@ -76,6 +78,7 @@ set( libSources
utils/Dirs.cpp
utils/Entropy.cpp
utils/Logger.cpp
+ utils/Permissions.cpp
utils/PluginFactory.cpp
utils/Retranslator.cpp
utils/String.cpp
diff --git a/src/calamares/CalamaresVersion.h.in b/src/libcalamares/CalamaresVersion.h.in
similarity index 92%
rename from src/calamares/CalamaresVersion.h.in
rename to src/libcalamares/CalamaresVersion.h.in
index 4ac7ee1d1..54a44888a 100644
--- a/src/calamares/CalamaresVersion.h.in
+++ b/src/libcalamares/CalamaresVersion.h.in
@@ -4,7 +4,7 @@
#cmakedefine CALAMARES_ORGANIZATION_NAME "${CALAMARES_ORGANIZATION_NAME}"
#cmakedefine CALAMARES_ORGANIZATION_DOMAIN "${CALAMARES_ORGANIZATION_DOMAIN}"
#cmakedefine CALAMARES_APPLICATION_NAME "${CALAMARES_APPLICATION_NAME}"
-#cmakedefine CALAMARES_VERSION "${CALAMARES_VERSION}"
+#cmakedefine CALAMARES_VERSION "${CALAMARES_VERSION_SHORT}"
#cmakedefine CALAMARES_VERSION_SHORT "${CALAMARES_VERSION_SHORT}"
#cmakedefine CALAMARES_VERSION_MAJOR "${CALAMARES_VERSION_MAJOR}"
diff --git a/src/libcalamares/CalamaresVersionX.h.in b/src/libcalamares/CalamaresVersionX.h.in
new file mode 100644
index 000000000..75b124c11
--- /dev/null
+++ b/src/libcalamares/CalamaresVersionX.h.in
@@ -0,0 +1,13 @@
+// Same as CalamaresVersion.h, but with a full-git-extended VERSION
+// rather than the short (vM.m.p) semantic version.
+#ifndef CALAMARES_VERSION_H
+
+// On purpose, do not define the guard, but let CalamaresVersion.h do it
+// #define CALAMARES_VERSION_H
+
+#include "CalamaresVersion.h"
+
+#undef CALAMARES_VERSION
+#cmakedefine CALAMARES_VERSION "${CALAMARES_VERSION}"
+
+#endif // CALAMARES_VERSION_H
diff --git a/src/libcalamares/GlobalStorage.cpp b/src/libcalamares/GlobalStorage.cpp
index d58a3b0c6..341fc3892 100644
--- a/src/libcalamares/GlobalStorage.cpp
+++ b/src/libcalamares/GlobalStorage.cpp
@@ -1,5 +1,5 @@
/* === This file is part of Calamares - ===
- *
+ *
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac
* SPDX-FileCopyrightText: 2017-2018 Adriaan de Groot
*
@@ -36,8 +36,8 @@ using CalamaresUtils::operator""_MiB;
namespace Calamares
{
-GlobalStorage::GlobalStorage()
- : QObject( nullptr )
+GlobalStorage::GlobalStorage( QObject* parent )
+ : QObject( parent )
{
}
diff --git a/src/libcalamares/GlobalStorage.h b/src/libcalamares/GlobalStorage.h
index e9ba1da8a..a2848f888 100644
--- a/src/libcalamares/GlobalStorage.h
+++ b/src/libcalamares/GlobalStorage.h
@@ -1,5 +1,5 @@
/* === This file is part of Calamares - ===
- *
+ *
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac
* SPDX-FileCopyrightText: 2017-2018 Adriaan de Groot
*
@@ -39,7 +39,7 @@ class GlobalStorage : public QObject
{
Q_OBJECT
public:
- explicit GlobalStorage();
+ explicit GlobalStorage( QObject* parent = nullptr );
//NOTE: thread safety is guaranteed by JobQueue, which executes jobs one by one.
// If at any time jobs become concurrent, this class must be made thread-safe.
diff --git a/src/libcalamares/JobQueue.cpp b/src/libcalamares/JobQueue.cpp
index adff9464b..64cc4794d 100644
--- a/src/libcalamares/JobQueue.cpp
+++ b/src/libcalamares/JobQueue.cpp
@@ -1,5 +1,5 @@
/* === This file is part of Calamares - ===
- *
+ *
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac
* SPDX-FileCopyrightText: 2018 Adriaan de Groot
*
@@ -170,7 +170,7 @@ JobQueue::globalStorage() const
JobQueue::JobQueue( QObject* parent )
: QObject( parent )
, m_thread( new JobThread( this ) )
- , m_storage( new GlobalStorage() )
+ , m_storage( new GlobalStorage( this ) )
{
Q_ASSERT( !s_instance );
s_instance = this;
diff --git a/src/libcalamares/locale/TimeZone.h b/src/libcalamares/locale/TimeZone.h
index 60900ef21..05820817a 100644
--- a/src/libcalamares/locale/TimeZone.h
+++ b/src/libcalamares/locale/TimeZone.h
@@ -1,6 +1,7 @@
/* === This file is part of Calamares - ===
- *
+ *
* SPDX-FileCopyrightText: 2019 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,8 +16,6 @@
* You should have received a copy of the GNU General Public License
* along with Calamares. If not, see .
*
- * SPDX-License-Identifier: GPL-3.0-or-later
- * License-Filename: LICENSE
*
*/
@@ -88,7 +87,13 @@ public:
}
};
-/// @brief A pair of strings for timezone regions (e.g. "America")
+/** @brief Timezone regions (e.g. "America")
+ *
+ * A region has a key and a human-readable name, but also
+ * a collection of associated timezone zones (TZZone, below).
+ * This class is not usually constructed, but uses fromFile()
+ * to load a complete tree structure of timezones.
+ */
class TZRegion : public CStringPair
{
Q_OBJECT
@@ -120,7 +125,12 @@ private:
CStringPairList m_zones;
};
-/// @brief A pair of strings for specific timezone names (e.g. "New_York")
+/** @brief Specific timezone zones (e.g. "New_York", "New York")
+ *
+ * A timezone zone lives in a region, and has some associated
+ * data like the country (used to map likely languages) and latitude
+ * and longitude information.
+ */
class TZZone : public CStringPair
{
Q_OBJECT
diff --git a/src/libcalamares/network/Manager.cpp b/src/libcalamares/network/Manager.cpp
index d70988a0a..9d7534e99 100644
--- a/src/libcalamares/network/Manager.cpp
+++ b/src/libcalamares/network/Manager.cpp
@@ -1,5 +1,5 @@
/* === This file is part of Calamares - ===
- *
+ *
* SPDX-FileCopyrightText: 2019 Adriaan de Groot
*
* Calamares is free software: you can redistribute it and/or modify
@@ -168,7 +168,11 @@ Manager::checkHasInternet()
{
hasInternet = synchronousPing( d->m_hasInternetUrl );
}
- d->m_hasInternet = hasInternet;
+ if ( hasInternet != d->m_hasInternet )
+ {
+ d->m_hasInternet = hasInternet;
+ emit hasInternetChanged( hasInternet );
+ }
return hasInternet;
}
diff --git a/src/libcalamares/network/Manager.h b/src/libcalamares/network/Manager.h
index 1ba3eb411..8673d340b 100644
--- a/src/libcalamares/network/Manager.h
+++ b/src/libcalamares/network/Manager.h
@@ -1,5 +1,5 @@
/* === This file is part of Calamares - ===
- *
+ *
* SPDX-FileCopyrightText: 2019 Adriaan de Groot
*
* Calamares is free software: you can redistribute it and/or modify
@@ -98,9 +98,10 @@ struct RequestStatus
QDebug& operator<<( QDebug& s, const RequestStatus& e );
-class DLLEXPORT Manager : QObject
+class DLLEXPORT Manager : public QObject
{
Q_OBJECT
+ Q_PROPERTY( bool hasInternet READ hasInternet NOTIFY hasInternetChanged FINAL )
Manager();
@@ -133,6 +134,16 @@ public:
/// @brief Set the URL which is used for the general "is there internet" check.
void setCheckHasInternetUrl( const QUrl& url );
+
+ /** @brief Do a network request asynchronously.
+ *
+ * Returns a pointer to the reply-from-the-request.
+ * This may be a nullptr if an error occurs immediately.
+ * The caller is responsible for cleaning up the reply (eventually).
+ */
+ QNetworkReply* asynchronousGet( const QUrl& url, const RequestOptions& options = RequestOptions() );
+
+public Q_SLOTS:
/** @brief Do an explicit check for internet connectivity.
*
* This **may** do a ping to the configured check URL, but can also
@@ -148,13 +159,13 @@ public:
*/
bool hasInternet();
- /** @brief Do a network request asynchronously.
+signals:
+ /** @brief Indicates that internet connectivity status has changed
*
- * Returns a pointer to the reply-from-the-request.
- * This may be a nullptr if an error occurs immediately.
- * The caller is responsible for cleaning up the reply (eventually).
+ * The value is that returned from hasInternet() -- @c true when there
+ * is connectivity, @c false otherwise.
*/
- QNetworkReply* asynchronousGet( const QUrl& url, const RequestOptions& options = RequestOptions() );
+ void hasInternetChanged( bool );
private:
class Private;
diff --git a/src/libcalamares/utils/Logger.cpp b/src/libcalamares/utils/Logger.cpp
index 72885d53f..5a2149f44 100644
--- a/src/libcalamares/utils/Logger.cpp
+++ b/src/libcalamares/utils/Logger.cpp
@@ -3,6 +3,7 @@
* SPDX-FileCopyrightText: 2010-2011 Christian Muehlhaeuser
* SPDX-FileCopyrightText: 2014 Teo Mrnjavac
* SPDX-FileCopyrightText: 2017 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
*
*
* Calamares is free software: you can redistribute it and/or modify
@@ -18,15 +19,12 @@
* You should have received a copy of the GNU General Public License
* along with Calamares. If not, see .
*
- * SPDX-License-Identifier: GPL-3.0-or-later
- * License-Filename: LICENSE
- *
*/
#include "Logger.h"
-#include
-#include
+#include "CalamaresVersionX.h"
+#include "utils/Dirs.h"
#include
#include
@@ -35,10 +33,10 @@
#include
#include
-#include "CalamaresVersion.h"
-#include "utils/Dirs.h"
+#include
+#include
-#define LOGFILE_SIZE 1024 * 256
+static constexpr const int LOGFILE_SIZE = 1024 * 256;
static std::ofstream logfile;
static unsigned int s_threshold =
diff --git a/src/libcalamares/utils/Permissions.cpp b/src/libcalamares/utils/Permissions.cpp
new file mode 100644
index 000000000..a7afbc5fc
--- /dev/null
+++ b/src/libcalamares/utils/Permissions.cpp
@@ -0,0 +1,124 @@
+/* === This file is part of Calamares - ===
+ *
+ * SPDX-FileCopyrightText: 2018 Scott Harvey
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * License-Filename: LICENSE
+ *
+ */
+
+#include "Permissions.h"
+
+#include "Logger.h"
+
+#include
+#include
+#include
+
+#include
+
+namespace CalamaresUtils
+{
+
+Permissions::Permissions()
+ : m_username()
+ , m_group()
+ , m_value( 0 )
+ , m_valid( false )
+{
+}
+
+
+Permissions::Permissions( QString const& p )
+ : Permissions()
+{
+ parsePermissions( p );
+}
+
+void
+Permissions::parsePermissions( QString const& p )
+{
+
+ QStringList segments = p.split( ":" );
+
+ if ( segments.length() != 3 )
+ {
+ m_valid = false;
+ return;
+ }
+
+ if ( segments[ 0 ].isEmpty() || segments[ 1 ].isEmpty() )
+ {
+ m_valid = false;
+ return;
+ }
+
+ bool ok;
+ int octal = segments[ 2 ].toInt( &ok, 8 );
+ if ( !ok || octal == 0 )
+ {
+ m_valid = false;
+ return;
+ }
+ else
+ {
+ m_value = octal;
+ }
+
+ // We have exactly three segments and the third is valid octal,
+ // so we can declare the string valid and set the user and group names
+ m_valid = true;
+ m_username = segments[ 0 ];
+ m_group = segments[ 1 ];
+
+ return;
+}
+
+bool
+Permissions::apply( const QString& path, int mode )
+{
+ // We **don't** use QFile::setPermissions() here because it takes
+ // a Qt flags object that subtlely does not align with POSIX bits.
+ // The Qt flags are **hex** based, so 0x755 for rwxr-xr-x, while
+ // our integer (mode_t) stores **octal** based flags.
+ //
+ // Call chmod(2) directly, that's what Qt would be doing underneath
+ // anyway.
+ int r = chmod( path.toUtf8().constData(), mode_t( mode ) );
+ if ( r )
+ {
+ cDebug() << Logger::SubEntry << "Could not set permissions of" << path << "to" << QString::number( mode, 8 );
+ }
+ return r == 0;
+}
+
+bool
+Permissions::apply( const QString& path, const CalamaresUtils::Permissions& p )
+{
+ if ( !p.isValid() )
+ {
+ return false;
+ }
+ bool r = apply( path, p.value() );
+ if ( r )
+ {
+ // We don't use chgrp(2) or chown(2) here because then we need to
+ // go through the users list (which one, target or source?) to get
+ // uid_t and gid_t values to pass to that system call.
+ //
+ // Do a lame cop-out and let the chown(8) utility do the heavy lifting.
+ if ( QProcess::execute( "chown", { p.username() + ':' + p.group(), path } ) )
+ {
+ r = false;
+ cDebug() << Logger::SubEntry << "Could not set owner of" << path << "to"
+ << ( p.username() + ':' + p.group() );
+ }
+ }
+ if ( r )
+ {
+ /* NOTUSED */ apply( path, p.value() );
+ }
+ return r;
+}
+
+
+} // namespace CalamaresUtils
diff --git a/src/libcalamares/utils/Permissions.h b/src/libcalamares/utils/Permissions.h
new file mode 100644
index 000000000..1f0dd38da
--- /dev/null
+++ b/src/libcalamares/utils/Permissions.h
@@ -0,0 +1,92 @@
+/* === This file is part of Calamares - ===
+ *
+ * SPDX-FileCopyrightText: 2018 Scott Harvey
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * License-Filename: LICENSE
+ *
+ */
+
+#ifndef LIBCALAMARES_PERMISSIONS_H
+#define LIBCALAMARES_PERMISSIONS_H
+
+#include "DllMacro.h"
+
+#include
+
+namespace CalamaresUtils
+{
+
+/**
+ * @brief The Permissions class takes a QString @p in the form of
+ * ::, checks it for validity, and makes the three
+ * components available indivdually.
+ */
+class DLLEXPORT Permissions
+{
+
+public:
+ /** @brief Constructor
+ *
+ * Splits the string @p at the colon (":") into separate elements for
+ * , , and (permissions), where is interpreted
+ * as an **octal** integer. That is, "root:wheel:755" will give
+ * you an integer value of four-hundred-ninety-three (493),
+ * corresponding to the UNIX file permissions rwxr-xr-x,
+ * as one would expect from chmod and other command-line utilities.
+ */
+ Permissions( QString const& p );
+
+ /// @brief Default constructor of an invalid Permissions.
+ Permissions();
+
+ /// @brief Was the Permissions object constructed from valid data?
+ bool isValid() const { return m_valid; }
+ /// @brief The user (first component, e.g. "root" in "root:wheel:755")
+ QString username() const { return m_username; }
+ /// @brief The group (second component, e.g. "wheel" in "root:wheel:755")
+ QString group() const { return m_group; }
+ /** @brief The value (file permission) as an integer.
+ *
+ * Bear in mind that input is in octal, but integers are just integers;
+ * naively printing them will get decimal results (e.g. 493 from the
+ * input of "root:wheel:755"). This is suitable to pass to apply().
+ */
+ int value() const { return m_value; }
+ /** @brief The value (file permission) as octal string
+ *
+ * This is suitable for passing to chmod-the-program, or for
+ * recreating the original Permissions string.
+ */
+ QString octal() const { return QString::number( value(), 8 ); }
+
+ /** @brief Sets the file-access @p mode of @p path
+ *
+ * Pass a path that is relative (or absolute) in the **host** system.
+ */
+ static bool apply( const QString& path, int mode );
+ /** @brief Do both chmod and chown on @p path
+ *
+ * Note that interpreting user- and group- names for applying the
+ * permissions can be different between the host system and the target
+ * system; the target might not have a "live" user, for instance, and
+ * the host won't have the user-entered username for the installation.
+ *
+ * For this call, the names are interpreted in the **host** system.
+ * Pass a path that is relative (or absolute) in the **host** system.
+ */
+ static bool apply( const QString& path, const Permissions& p );
+ /// Convenience method for apply(const QString&, const Permissions& )
+ bool apply( const QString& path ) const { return apply( path, *this ); }
+
+private:
+ void parsePermissions( QString const& p );
+
+ QString m_username;
+ QString m_group;
+ int m_value;
+ bool m_valid;
+};
+
+} // namespace CalamaresUtils
+
+#endif // LIBCALAMARES_PERMISSIONS_H
diff --git a/src/libcalamares/utils/RAII.h b/src/libcalamares/utils/RAII.h
index dae85e84a..28e57ff9e 100644
--- a/src/libcalamares/utils/RAII.h
+++ b/src/libcalamares/utils/RAII.h
@@ -1,5 +1,5 @@
/* === This file is part of Calamares - ===
- *
+ *
* SPDX-FileCopyrightText: 2020 Adriaan de Groot
*
* Calamares is free software: you can redistribute it and/or modify
@@ -24,6 +24,7 @@
#define UTILS_RAII_H
#include
+#include
#include
@@ -44,4 +45,21 @@ struct cqDeleter
}
};
+/// @brief Sets a bool to @p value and resets to !value on destruction
+template < bool value >
+struct cBoolSetter
+{
+ bool& m_b;
+
+ cBoolSetter( bool& b )
+ : m_b( b )
+ {
+ m_b = value;
+ }
+ ~cBoolSetter() { m_b = !value; }
+};
+
+/// @brief Blocks signals on a QObject until destruction
+using cSignalBlocker = QSignalBlocker;
+
#endif
diff --git a/src/libcalamaresui/modulesystem/ModuleManager.h b/src/libcalamaresui/modulesystem/ModuleManager.h
index 2c51e70f7..bea8acf41 100644
--- a/src/libcalamaresui/modulesystem/ModuleManager.h
+++ b/src/libcalamaresui/modulesystem/ModuleManager.h
@@ -103,11 +103,37 @@ public:
RequirementsModel* requirementsModel() { return m_requirementsModel; }
signals:
+ /** @brief Emitted when all the module **configuration** has been read
+ *
+ * This indicates that all of the module.desc files have been
+ * loaded; bad ones are silently skipped, so this just indicates
+ * that the module manager is ready for the next phase (loading).
+ */
void initDone();
- void modulesLoaded(); /// All of the modules were loaded successfully
- void modulesFailed( QStringList ); /// .. or not
- // Below, see RequirementsChecker documentation
- void requirementsComplete( bool );
+ /** @brief Emitted when all the modules are loaded successfully
+ *
+ * Each module listed in the settings is loaded. Modules are loaded
+ * only once, even when instantiated multiple times. If all of
+ * the listed modules are successfully loaded, this signal is
+ * emitted (otherwise, it isn't, so you need to wait for **both**
+ * of the signals).
+ *
+ * If this is emitted (i.e. all modules have loaded) then the next
+ * phase, requirements checking, can be started.
+ */
+ void modulesLoaded();
+ /** @brief Emitted if any modules failed to load
+ *
+ * Modules that failed to load (for any reason) are listed by
+ * instance key (e.g. "welcome@welcome", "shellprocess@mycustomthing").
+ */
+ void modulesFailed( QStringList );
+ /** @brief Emitted after all requirements have been checked
+ *
+ * The bool @p canContinue indicates if all of the **mandatory** requirements
+ * are satisfied (e.g. whether installation can continue).
+ */
+ void requirementsComplete( bool canContinue );
private slots:
void doInit();
diff --git a/src/libcalamaresui/utils/Qml.cpp b/src/libcalamaresui/utils/Qml.cpp
index 4f53aa317..1f1152fa2 100644
--- a/src/libcalamaresui/utils/Qml.cpp
+++ b/src/libcalamaresui/utils/Qml.cpp
@@ -23,6 +23,7 @@
#include "JobQueue.h"
#include "Settings.h"
#include "ViewManager.h"
+#include "network/Manager.h"
#include "utils/Dirs.h"
#include "utils/Logger.h"
@@ -242,6 +243,10 @@ registerQmlModels()
"io.calamares.core", 1, 0, "Global", []( QQmlEngine*, QJSEngine* ) -> QObject* {
return Calamares::JobQueue::instance()->globalStorage();
} );
+ qmlRegisterSingletonType< CalamaresUtils::Network::Manager >(
+ "io.calamares.core", 1, 0, "Network", []( QQmlEngine*, QJSEngine* ) -> QObject* {
+ return &CalamaresUtils::Network::Manager::instance();
+ } );
}
}
diff --git a/src/modules/keyboardq/ListViewTemplate.qml b/src/modules/keyboardq/ListViewTemplate.qml
index eb160afab..4564b887b 100644
--- a/src/modules/keyboardq/ListViewTemplate.qml
+++ b/src/modules/keyboardq/ListViewTemplate.qml
@@ -15,7 +15,7 @@ ListView {
z: parent.z - 1
anchors.fill: parent
- color: Kirigami.Theme.backgroundColor
+ color: "#BDC3C7"
radius: 5
opacity: 0.7
}
diff --git a/src/modules/keyboardq/ResponsiveBase.qml b/src/modules/keyboardq/ResponsiveBase.qml
index c9f5c7091..38fa15d1b 100644
--- a/src/modules/keyboardq/ResponsiveBase.qml
+++ b/src/modules/keyboardq/ResponsiveBase.qml
@@ -13,8 +13,8 @@ Page {
width: 800 //parent.width
height: 550 //parent.height
- Kirigami.Theme.backgroundColor: "#fafafa"
- Kirigami.Theme.textColor: "#333"
+ Kirigami.Theme.backgroundColor: "#FAFAFA"
+ Kirigami.Theme.textColor: "#1F1F1F"
property string subtitle
property string message
@@ -22,39 +22,6 @@ Page {
default property alias content : _content.data
property alias stackView: _stackView
- background: Item {
-
- id: _background
-
- Image {
-
- id: _wallpaper
- height: parent.height
- width: parent.width
-
- sourceSize.width: 800
- sourceSize.height: 550
-
- fillMode: Image.PreserveAspectCrop
- antialiasing: false
- smooth: false
- asynchronous: true
- cache: true
-
- source: "keyboard.jpg"
- }
-
- FastBlur {
-
- id: fastBlur
- anchors.fill: parent
- source: _wallpaper
- radius: 32
- transparentBorder: false
- cached: true
- }
- }
-
ColumnLayout {
id: _content
@@ -63,7 +30,7 @@ Page {
spacing: Kirigami.Units.smallSpacing * 5
anchors.margins: Kirigami.Units.smallSpacing * 5
anchors.bottomMargin: 20
-
+
Label {
Layout.fillWidth: true
@@ -72,7 +39,7 @@ Page {
wrapMode: Text.NoWrap
elide: Text.ElideMiddle
text: control.title
- color: "white"
+ color: Kirigami.Theme.textColor
font.bold: true
font.weight: Font.Bold
font.pointSize: 24
@@ -86,7 +53,7 @@ Page {
wrapMode: Text.Wrap
elide: Text.ElideMiddle
text: control.subtitle
- color: "white"
+ color: Kirigami.Theme.textColor
font.weight: Font.Light
font.pointSize: 12
}
@@ -99,7 +66,7 @@ Page {
wrapMode: Text.Wrap
elide: Text.ElideMiddle
text: control.message
- color: "white"
+ color: Kirigami.Theme.textColor
font.weight: Font.Light
font.pointSize: 10
}
@@ -110,7 +77,7 @@ Page {
Layout.fillHeight: true
Layout.preferredWidth: parent.width
clip: true
- }
+ }
}
}
diff --git a/src/modules/keyboardq/keyboard.jpg b/src/modules/keyboardq/keyboard.jpg
deleted file mode 100644
index 9c0600fac..000000000
Binary files a/src/modules/keyboardq/keyboard.jpg and /dev/null differ
diff --git a/src/modules/keyboardq/keyboardq.qml b/src/modules/keyboardq/keyboardq.qml
index 7474fbdd1..613223a1c 100644
--- a/src/modules/keyboardq/keyboardq.qml
+++ b/src/modules/keyboardq/keyboardq.qml
@@ -182,14 +182,15 @@ ResponsiveBase {
Layout.maximumWidth: 500
Layout.fillWidth: true
Layout.alignment: Qt.AlignCenter
+ color: control.Kirigami.Theme.textColor
background:Rectangle {
z: parent.z - 1
anchors.fill: parent
- color: control.Kirigami.Theme.backgroundColor
+ color: "#BDC3C7"
radius: 5
- opacity: 0.8
+ opacity: 0.3
}
}
}
diff --git a/src/modules/keyboardq/keyboardq.qrc b/src/modules/keyboardq/keyboardq.qrc
index 69fdb715f..b2ee36ee5 100644
--- a/src/modules/keyboardq/keyboardq.qrc
+++ b/src/modules/keyboardq/keyboardq.qrc
@@ -6,6 +6,5 @@
ListItemDelegate.qmlListViewTemplate.qmlResponsiveBase.qml
- keyboard.jpg
diff --git a/src/modules/locale/Config.cpp b/src/modules/locale/Config.cpp
index cde0a5e09..7a49525f2 100644
--- a/src/modules/locale/Config.cpp
+++ b/src/modules/locale/Config.cpp
@@ -1,7 +1,8 @@
/* === This file is part of Calamares - ===
*
- * Copyright 2019-2020, Adriaan de Groot
- * Copyright 2020, Camilo Higuita
+ * SPDX-FileCopyrightText: 2020 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * License-Filename: LICENSE
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,85 +20,43 @@
#include "Config.h"
-#include "LCLocaleDialog.h"
#include "SetTimezoneJob.h"
-#include "timezonewidget/timezonewidget.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "Settings.h"
-
#include "locale/Label.h"
-#include "locale/TimeZone.h"
-#include "utils/CalamaresUtilsGui.h"
+#include "modulesystem/ModuleManager.h"
+#include "network/Manager.h"
#include "utils/Logger.h"
-#include "utils/Retranslator.h"
+#include "utils/Variant.h"
-#include
#include
#include
+#include
-Config::Config( QObject* parent )
- : QObject( parent )
- , m_regionList( CalamaresUtils::Locale::TZRegion::fromZoneTab() )
- , m_regionModel( new CalamaresUtils::Locale::CStringListModel( m_regionList ) )
- , m_zonesModel( new CalamaresUtils::Locale::CStringListModel() )
- , m_blockTzWidgetSet( false )
+/** @brief Load supported locale keys
+ *
+ * If i18n/SUPPORTED exists, read the lines from that and return those
+ * as supported locales; otherwise, try the file at @p localeGenPath
+ * and get lines from that. Failing both, try the output of `locale -a`.
+ *
+ * This gives us a list of locale identifiers (e.g. en_US.UTF-8), which
+ * are not particularly human-readable.
+ *
+ * Only UTF-8 locales are returned (even if the system claims to support
+ * other, non-UTF-8, locales).
+ */
+static QStringList
+loadLocales( const QString& localeGenPath )
{
- connect( m_regionModel, &CalamaresUtils::Locale::CStringListModel::currentIndexChanged, [&]() {
- m_zonesModel->setList( static_cast< const CalamaresUtils::Locale::TZRegion* >(
- m_regionModel->item( m_regionModel->currentIndex() ) )
- ->zones() );
- updateLocaleLabels();
- } );
-
- connect(
- m_zonesModel, &CalamaresUtils::Locale::CStringListModel::currentIndexChanged, [&]() { updateLocaleLabels(); } );
-}
-
-Config::~Config()
-{
- qDeleteAll( m_regionList );
-}
-
-CalamaresUtils::Locale::CStringListModel*
-Config::zonesModel() const
-{
- return m_zonesModel;
-}
-
-CalamaresUtils::Locale::CStringListModel*
-Config::regionModel() const
-{
- return m_regionModel;
-}
-
-void
-Config::setLocaleInfo( const QString& initialRegion, const QString& initialZone, const QString& localeGenPath )
-{
- using namespace CalamaresUtils::Locale;
-
- cDebug() << "REGION MODEL SIZE" << initialRegion << initialZone;
- auto* region = m_regionList.find< TZRegion >( initialRegion );
- if ( region && region->zones().find< TZZone >( initialZone ) )
- {
- m_regionModel->setCurrentIndex( m_regionModel->indexOf( initialRegion ) );
- m_zonesModel->setList( region->zones() );
- m_zonesModel->setCurrentIndex( m_zonesModel->indexOf( initialZone ) );
- }
- else
- {
- m_regionModel->setCurrentIndex( m_regionModel->indexOf( "America" ) );
- m_zonesModel->setList(
- static_cast< const TZRegion* >( m_regionModel->item( m_regionModel->currentIndex() ) )->zones() );
- m_zonesModel->setCurrentIndex( m_zonesModel->indexOf( "New_York" ) );
- }
+ QStringList localeGenLines;
// Some distros come with a meaningfully commented and easy to parse locale.gen,
// and others ship a separate file /usr/share/i18n/SUPPORTED with a clean list of
// supported locales. We first try that one, and if it doesn't exist, we fall back
// to parsing the lines from locale.gen
- m_localeGenLines.clear();
+ localeGenLines.clear();
QFile supported( "/usr/share/i18n/SUPPORTED" );
QByteArray ba;
@@ -109,7 +68,7 @@ Config::setLocaleInfo( const QString& initialRegion, const QString& initialZone,
const auto lines = ba.split( '\n' );
for ( const QByteArray& line : lines )
{
- m_localeGenLines.append( QString::fromLatin1( line.simplified() ) );
+ localeGenLines.append( QString::fromLatin1( line.simplified() ) );
}
}
else
@@ -150,11 +109,11 @@ Config::setLocaleInfo( const QString& initialRegion, const QString& initialZone,
continue;
}
- m_localeGenLines.append( lineString );
+ localeGenLines.append( lineString );
}
}
- if ( m_localeGenLines.isEmpty() )
+ if ( localeGenLines.isEmpty() )
{
cWarning() << "cannot acquire a list of available locales."
<< "The locale and localecfg modules will be broken as long as this "
@@ -164,168 +123,388 @@ Config::setLocaleInfo( const QString& initialRegion, const QString& initialZone,
<< "* a well-formed"
<< ( localeGenPath.isEmpty() ? QLatin1String( "/etc/locale.gen" ) : localeGenPath ) << "\n\tOR"
<< "* a complete pre-compiled locale-gen database which allows complete locale -a output.";
- return; // something went wrong and there's nothing we can do about it.
+ return localeGenLines; // something went wrong and there's nothing we can do about it.
}
// Assuming we have a list of supported locales, we usually only want UTF-8 ones
// because it's not 1995.
- for ( auto it = m_localeGenLines.begin(); it != m_localeGenLines.end(); )
- {
- if ( !it->contains( "UTF-8", Qt::CaseInsensitive ) && !it->contains( "utf8", Qt::CaseInsensitive ) )
- {
- it = m_localeGenLines.erase( it );
- }
- else
- {
- ++it;
- }
- }
+ auto notUtf8 = []( const QString& s ) {
+ return !s.contains( "UTF-8", Qt::CaseInsensitive ) && !s.contains( "utf8", Qt::CaseInsensitive );
+ };
+ auto it = std::remove_if( localeGenLines.begin(), localeGenLines.end(), notUtf8 );
+ localeGenLines.erase( it, localeGenLines.end() );
// We strip " UTF-8" from "en_US.UTF-8 UTF-8" because it's redundant redundant.
- for ( auto it = m_localeGenLines.begin(); it != m_localeGenLines.end(); ++it )
- {
- if ( it->endsWith( " UTF-8" ) )
+ // Also simplify whitespace.
+ auto unredundant = []( QString& s ) {
+ if ( s.endsWith( " UTF-8" ) )
{
- it->chop( 6 );
+ s.chop( 6 );
}
- *it = it->simplified();
- }
- updateGlobalStorage();
- updateLocaleLabels();
+ s = s.simplified();
+ };
+ std::for_each( localeGenLines.begin(), localeGenLines.end(), unredundant );
+
+ return localeGenLines;
+}
+
+static inline const CalamaresUtils::Locale::CStringPairList&
+timezoneData()
+{
+ return CalamaresUtils::Locale::TZRegion::fromZoneTab();
+}
+
+
+Config::Config( QObject* parent )
+ : QObject( parent )
+ , m_regionModel( std::make_unique< CalamaresUtils::Locale::CStringListModel >( ::timezoneData() ) )
+ , m_zonesModel( std::make_unique< CalamaresUtils::Locale::CStringListModel >() )
+{
+ // Slightly unusual: connect to our *own* signals. Wherever the language
+ // or the location is changed, these signals are emitted, so hook up to
+ // them to update global storage accordingly. This simplifies code:
+ // we don't need to call an update-GS method, or introduce an intermediate
+ // update-thing-and-GS method. And everywhere where we **do** change
+ // language or location, we already emit the signal.
+ connect( this, &Config::currentLanguageCodeChanged, [&]() {
+ auto* gs = Calamares::JobQueue::instance()->globalStorage();
+ gs->insert( "locale", m_selectedLocaleConfiguration.toBcp47() );
+ } );
+
+ connect( this, &Config::currentLCCodeChanged, [&]() {
+ auto* gs = Calamares::JobQueue::instance()->globalStorage();
+ // Update GS localeConf (the LC_ variables)
+ auto map = localeConfiguration().toMap();
+ QVariantMap vm;
+ for ( auto it = map.constBegin(); it != map.constEnd(); ++it )
+ {
+ vm.insert( it.key(), it.value() );
+ }
+ gs->insert( "localeConf", vm );
+ } );
+
+ connect( this, &Config::currentLocationChanged, [&]() {
+ auto* gs = Calamares::JobQueue::instance()->globalStorage();
+
+ // Update the GS region and zone (and possibly the live timezone)
+ const auto* location = currentLocation();
+ bool locationChanged = ( location->region() != gs->value( "locationRegion" ) )
+ || ( location->zone() != gs->value( "locationZone" ) );
+
+ gs->insert( "locationRegion", location->region() );
+ gs->insert( "locationZone", location->zone() );
+ if ( locationChanged && m_adjustLiveTimezone )
+ {
+ QProcess::execute( "timedatectl", // depends on systemd
+ { "set-timezone", location->region() + '/' + location->zone() } );
+ }
+ } );
+
+ auto prettyStatusNotify = [&]() { emit prettyStatusChanged( prettyStatus() ); };
+ connect( this, &Config::currentLanguageStatusChanged, prettyStatusNotify );
+ connect( this, &Config::currentLCStatusChanged, prettyStatusNotify );
+ connect( this, &Config::currentLocationStatusChanged, prettyStatusNotify );
+}
+
+Config::~Config() {}
+
+const CalamaresUtils::Locale::CStringPairList&
+Config::timezoneData() const
+{
+ return ::timezoneData();
}
void
-Config::updateGlobalLocale()
+Config::setCurrentLocation()
{
- auto* gs = Calamares::JobQueue::instance()->globalStorage();
- const QString bcp47 = m_selectedLocaleConfiguration.toBcp47();
- gs->insert( "locale", bcp47 );
+ if ( !m_currentLocation && m_startingTimezone.isValid() )
+ {
+ setCurrentLocation( m_startingTimezone.first, m_startingTimezone.second );
+ }
+}
+
+void Config::setCurrentLocation(const QString& regionzone)
+{
+ auto r = CalamaresUtils::GeoIP::splitTZString( regionzone );
+ if ( r.isValid() )
+ {
+ setCurrentLocation( r.first, r.second );
+ }
}
void
-Config::updateGlobalStorage()
+Config::setCurrentLocation( const QString& regionName, const QString& zoneName )
{
- auto* gs = Calamares::JobQueue::instance()->globalStorage();
-
- const auto* location = currentLocation();
- bool locationChanged = ( location->region() != gs->value( "locationRegion" ) )
- || ( location->zone() != gs->value( "locationZone" ) );
-#ifdef DEBUG_TIMEZONES
- if ( locationChanged )
+ using namespace CalamaresUtils::Locale;
+ auto* region = timezoneData().find< TZRegion >( regionName );
+ auto* zone = region ? region->zones().find< TZZone >( zoneName ) : nullptr;
+ if ( zone )
{
- cDebug() << "Location changed" << gs->value( "locationRegion" ) << ',' << gs->value( "locationZone" ) << "to"
- << location->region() << ',' << location->zone();
+ setCurrentLocation( zone );
}
-#endif
- gs->insert( "locationRegion", location->region() );
- gs->insert( "locationZone", location->zone() );
-
- updateGlobalLocale();
-
- // If we're in chroot mode (normal install mode), then we immediately set the
- // timezone on the live system. When debugging timezones, don't bother.
-#ifndef DEBUG_TIMEZONES
- if ( locationChanged && Calamares::Settings::instance()->doChroot() )
+ else
{
- QProcess::execute( "timedatectl", // depends on systemd
- { "set-timezone", location->region() + '/' + location->zone() } );
+ // Recursive, but America/New_York always exists.
+ setCurrentLocation( QStringLiteral( "America" ), QStringLiteral( "New_York" ) );
}
-#endif
-
- // Preserve those settings that have been made explicit.
- auto newLocale = guessLocaleConfiguration();
- if ( !m_selectedLocaleConfiguration.isEmpty() && m_selectedLocaleConfiguration.explicit_lang )
- {
- newLocale.setLanguage( m_selectedLocaleConfiguration.language() );
- }
- if ( !m_selectedLocaleConfiguration.isEmpty() && m_selectedLocaleConfiguration.explicit_lc )
- {
- newLocale.lc_numeric = m_selectedLocaleConfiguration.lc_numeric;
- newLocale.lc_time = m_selectedLocaleConfiguration.lc_time;
- newLocale.lc_monetary = m_selectedLocaleConfiguration.lc_monetary;
- newLocale.lc_paper = m_selectedLocaleConfiguration.lc_paper;
- newLocale.lc_name = m_selectedLocaleConfiguration.lc_name;
- newLocale.lc_address = m_selectedLocaleConfiguration.lc_address;
- newLocale.lc_telephone = m_selectedLocaleConfiguration.lc_telephone;
- newLocale.lc_measurement = m_selectedLocaleConfiguration.lc_measurement;
- newLocale.lc_identification = m_selectedLocaleConfiguration.lc_identification;
- }
- newLocale.explicit_lang = m_selectedLocaleConfiguration.explicit_lang;
- newLocale.explicit_lc = m_selectedLocaleConfiguration.explicit_lc;
-
- m_selectedLocaleConfiguration = newLocale;
- updateLocaleLabels();
}
void
-Config::updateLocaleLabels()
+Config::setCurrentLocation( const CalamaresUtils::Locale::TZZone* location )
{
- LocaleConfiguration lc
- = m_selectedLocaleConfiguration.isEmpty() ? guessLocaleConfiguration() : m_selectedLocaleConfiguration;
- auto labels = prettyLocaleStatus( lc );
- emit prettyStatusChanged();
-}
+ if ( location != m_currentLocation )
+ {
+ m_currentLocation = location;
+ // Overwrite those settings that have not been made explicit.
+ auto newLocale = automaticLocaleConfiguration();
+ if ( !m_selectedLocaleConfiguration.explicit_lang )
+ {
+ m_selectedLocaleConfiguration.setLanguage( newLocale.language() );
+ emit currentLanguageStatusChanged( currentLanguageStatus() );
+ }
+ if ( !m_selectedLocaleConfiguration.explicit_lc )
+ {
+ m_selectedLocaleConfiguration.lc_numeric = newLocale.lc_numeric;
+ m_selectedLocaleConfiguration.lc_time = newLocale.lc_time;
+ m_selectedLocaleConfiguration.lc_monetary = newLocale.lc_monetary;
+ m_selectedLocaleConfiguration.lc_paper = newLocale.lc_paper;
+ m_selectedLocaleConfiguration.lc_name = newLocale.lc_name;
+ m_selectedLocaleConfiguration.lc_address = newLocale.lc_address;
+ m_selectedLocaleConfiguration.lc_telephone = newLocale.lc_telephone;
+ m_selectedLocaleConfiguration.lc_measurement = newLocale.lc_measurement;
+ m_selectedLocaleConfiguration.lc_identification = newLocale.lc_identification;
-
-std::pair< QString, QString >
-Config::prettyLocaleStatus( const LocaleConfiguration& lc ) const
-{
- using CalamaresUtils::Locale::Label;
-
- Label lang( lc.language(), Label::LabelFormat::AlwaysWithCountry );
- Label num( lc.lc_numeric, Label::LabelFormat::AlwaysWithCountry );
-
- return std::make_pair< QString, QString >(
- tr( "The system language will be set to %1." ).arg( lang.label() ),
- tr( "The numbers and dates locale will be set to %1." ).arg( num.label() ) );
-}
-
-Calamares::JobList
-Config::createJobs()
-{
- QList< Calamares::job_ptr > list;
- const CalamaresUtils::Locale::TZZone* location = currentLocation();
-
- Calamares::Job* j = new SetTimezoneJob( location->region(), location->zone() );
- list.append( Calamares::job_ptr( j ) );
-
- return list;
+ emit currentLCStatusChanged( currentLCStatus() );
+ }
+ emit currentLocationChanged( m_currentLocation );
+ }
}
LocaleConfiguration
-Config::guessLocaleConfiguration() const
+Config::automaticLocaleConfiguration() const
{
+ // Special case: no location has been set at **all**
+ if ( !currentLocation() )
+ {
+ return LocaleConfiguration();
+ }
return LocaleConfiguration::fromLanguageAndLocation(
- QLocale().name(), m_localeGenLines, currentLocation() ? currentLocation()->country() : "" );
+ QLocale().name(), supportedLocales(), currentLocation()->country() );
}
-QMap< QString, QString >
-Config::localesMap()
+LocaleConfiguration
+Config::localeConfiguration() const
{
- return m_selectedLocaleConfiguration.isEmpty() ? guessLocaleConfiguration().toMap()
- : m_selectedLocaleConfiguration.toMap();
+ return m_selectedLocaleConfiguration.isEmpty() ? automaticLocaleConfiguration() : m_selectedLocaleConfiguration;
+}
+
+void
+Config::setLanguageExplicitly( const QString& language )
+{
+ m_selectedLocaleConfiguration.setLanguage( language );
+ m_selectedLocaleConfiguration.explicit_lang = true;
+
+ emit currentLanguageStatusChanged( currentLanguageStatus() );
+ emit currentLanguageCodeChanged( currentLanguageCode() );
+}
+
+void
+Config::setLCLocaleExplicitly( const QString& locale )
+{
+ // TODO: improve the granularity of this setting.
+ m_selectedLocaleConfiguration.lc_numeric = locale;
+ m_selectedLocaleConfiguration.lc_time = locale;
+ m_selectedLocaleConfiguration.lc_monetary = locale;
+ m_selectedLocaleConfiguration.lc_paper = locale;
+ m_selectedLocaleConfiguration.lc_name = locale;
+ m_selectedLocaleConfiguration.lc_address = locale;
+ m_selectedLocaleConfiguration.lc_telephone = locale;
+ m_selectedLocaleConfiguration.lc_measurement = locale;
+ m_selectedLocaleConfiguration.lc_identification = locale;
+ m_selectedLocaleConfiguration.explicit_lc = true;
+
+ emit currentLCStatusChanged( currentLCStatus() );
+ emit currentLCCodeChanged( currentLCCode() );
+}
+
+QString
+Config::currentLocationStatus() const
+{
+ return tr( "Set timezone to %1/%2." ).arg( m_currentLocation->region(), m_currentLocation->zone() );
+}
+
+static inline QString
+localeLabel( const QString& s )
+{
+ using CalamaresUtils::Locale::Label;
+
+ Label lang( s, Label::LabelFormat::AlwaysWithCountry );
+ return lang.label();
+}
+
+QString
+Config::currentLanguageStatus() const
+{
+ return tr( "The system language will be set to %1." )
+ .arg( localeLabel( m_selectedLocaleConfiguration.language() ) );
+}
+
+QString
+Config::currentLCStatus() const
+{
+ return tr( "The numbers and dates locale will be set to %1." )
+ .arg( localeLabel( m_selectedLocaleConfiguration.lc_numeric ) );
}
QString
Config::prettyStatus() const
{
- QString status;
- status += tr( "Set timezone to %1/%2. " )
- .arg( m_regionModel->item( m_regionModel->currentIndex() )->tr() )
- .arg( m_zonesModel->item( m_zonesModel->currentIndex() )->tr() );
-
- LocaleConfiguration lc
- = m_selectedLocaleConfiguration.isEmpty() ? guessLocaleConfiguration() : m_selectedLocaleConfiguration;
- auto labels = prettyLocaleStatus( lc );
- status += labels.first + " ";
- status += labels.second + " ";
-
- return status;
+ QStringList l { currentLocationStatus(), currentLanguageStatus(), currentLCStatus() };
+ return l.join( QStringLiteral( " " ) );
}
-
-const CalamaresUtils::Locale::TZZone*
-Config::currentLocation() const
+static inline void
+getLocaleGenLines( const QVariantMap& configurationMap, QStringList& localeGenLines )
{
- return static_cast< const CalamaresUtils::Locale::TZZone* >( m_zonesModel->item( m_zonesModel->currentIndex() ) );
+ QString localeGenPath = CalamaresUtils::getString( configurationMap, "localeGenPath" );
+ if ( localeGenPath.isEmpty() )
+ {
+ localeGenPath = QStringLiteral( "/etc/locale.gen" );
+ }
+ localeGenLines = loadLocales( localeGenPath );
+}
+
+static inline void
+getAdjustLiveTimezone( const QVariantMap& configurationMap, bool& adjustLiveTimezone )
+{
+ adjustLiveTimezone = CalamaresUtils::getBool(
+ configurationMap, "adjustLiveTimezone", Calamares::Settings::instance()->doChroot() );
+#ifdef DEBUG_TIMEZONES
+ if ( m_adjustLiveTimezone )
+ {
+ cWarning() << "Turning off live-timezone adjustments because debugging is on.";
+ adjustLiveTimezone = false;
+ }
+#endif
+#ifdef __FreeBSD__
+ if ( adjustLiveTimezone )
+ {
+ cWarning() << "Turning off live-timezone adjustments on FreeBSD.";
+ adjustLiveTimezone = false;
+ }
+#endif
+}
+
+static inline void
+getStartingTimezone( const QVariantMap& configurationMap, CalamaresUtils::GeoIP::RegionZonePair& startingTimezone )
+{
+ QString region = CalamaresUtils::getString( configurationMap, "region" );
+ QString zone = CalamaresUtils::getString( configurationMap, "zone" );
+ if ( !region.isEmpty() && !zone.isEmpty() )
+ {
+ startingTimezone = CalamaresUtils::GeoIP::RegionZonePair( region, zone );
+ }
+ else
+ {
+ startingTimezone
+ = CalamaresUtils::GeoIP::RegionZonePair( QStringLiteral( "America" ), QStringLiteral( "New_York" ) );
+ }
+
+ if ( CalamaresUtils::getBool( configurationMap, "useSystemTimezone", false ) )
+ {
+ auto systemtz = CalamaresUtils::GeoIP::splitTZString( QTimeZone::systemTimeZoneId() );
+ if ( systemtz.isValid() )
+ {
+ cDebug() << "Overriding configured timezone" << startingTimezone << "with system timezone" << systemtz;
+ startingTimezone = systemtz;
+ }
+ }
+}
+
+static inline void
+getGeoIP( const QVariantMap& configurationMap, std::unique_ptr< CalamaresUtils::GeoIP::Handler >& geoip )
+{
+ bool ok = false;
+ QVariantMap map = CalamaresUtils::getSubMap( configurationMap, "geoip", ok );
+ if ( ok )
+ {
+ QString url = CalamaresUtils::getString( map, "url" );
+ QString style = CalamaresUtils::getString( map, "style" );
+ QString selector = CalamaresUtils::getString( map, "selector" );
+
+ geoip = std::make_unique< CalamaresUtils::GeoIP::Handler >( style, url, selector );
+ if ( !geoip->isValid() )
+ {
+ cWarning() << "GeoIP Style" << style << "is not recognized.";
+ }
+ }
+}
+
+void
+Config::setConfigurationMap( const QVariantMap& configurationMap )
+{
+ getLocaleGenLines( configurationMap, m_localeGenLines );
+ getAdjustLiveTimezone( configurationMap, m_adjustLiveTimezone );
+ getStartingTimezone( configurationMap, m_startingTimezone );
+ getGeoIP( configurationMap, m_geoip );
+
+ if ( m_geoip && m_geoip->isValid() )
+ {
+ connect(
+ Calamares::ModuleManager::instance(), &Calamares::ModuleManager::modulesLoaded, this, &Config::startGeoIP );
+ }
+}
+
+Calamares::JobList
+Config::createJobs()
+{
+ Calamares::JobList list;
+ const CalamaresUtils::Locale::TZZone* location = currentLocation();
+
+ if ( location )
+ {
+ Calamares::Job* j = new SetTimezoneJob( location->region(), location->zone() );
+ list.append( Calamares::job_ptr( j ) );
+ }
+
+ return list;
+}
+
+void
+Config::startGeoIP()
+{
+ if ( m_geoip && m_geoip->isValid() )
+ {
+ auto& network = CalamaresUtils::Network::Manager::instance();
+ if ( network.hasInternet() || network.synchronousPing( m_geoip->url() ) )
+ {
+ using Watcher = QFutureWatcher< CalamaresUtils::GeoIP::RegionZonePair >;
+ m_geoipWatcher = std::make_unique< Watcher >();
+ m_geoipWatcher->setFuture( m_geoip->query() );
+ connect( m_geoipWatcher.get(), &Watcher::finished, this, &Config::completeGeoIP );
+ }
+ }
+}
+
+void
+Config::completeGeoIP()
+{
+ if ( !currentLocation() )
+ {
+ auto r = m_geoipWatcher->result();
+ if ( r.isValid() )
+ {
+ m_startingTimezone = r;
+ }
+ else
+ {
+ cWarning() << "GeoIP returned invalid result.";
+ }
+ }
+ else
+ {
+ cWarning() << "GeoIP result ignored because a location is already set.";
+ }
+ m_geoipWatcher.reset();
+ m_geoip.reset();
}
diff --git a/src/modules/locale/Config.h b/src/modules/locale/Config.h
index cfbed7bae..e9a8e6373 100644
--- a/src/modules/locale/Config.h
+++ b/src/modules/locale/Config.h
@@ -1,7 +1,8 @@
/* === This file is part of Calamares - ===
*
- * Copyright 2019-2020, Adriaan de Groot
- * Copyright 2020, Camilo Higuita
+ * SPDX-FileCopyrightText: 2020 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * License-Filename: LICENSE
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,9 +24,11 @@
#include "LocaleConfiguration.h"
#include "Job.h"
+#include "geoip/Handler.h"
+#include "geoip/Interface.h"
#include "locale/TimeZone.h"
-#include
+#include
#include
#include
@@ -33,53 +36,150 @@
class Config : public QObject
{
Q_OBJECT
+ Q_PROPERTY( const QStringList& supportedLocales READ supportedLocales CONSTANT FINAL )
Q_PROPERTY( CalamaresUtils::Locale::CStringListModel* zonesModel READ zonesModel CONSTANT FINAL )
Q_PROPERTY( CalamaresUtils::Locale::CStringListModel* regionModel READ regionModel CONSTANT FINAL )
+
+ Q_PROPERTY( const CalamaresUtils::Locale::TZZone* currentLocation READ currentLocation WRITE setCurrentLocation
+ NOTIFY currentLocationChanged )
+
+ // Status are complete, human-readable, messages
+ Q_PROPERTY( QString currentLocationStatus READ currentLocationStatus NOTIFY currentLanguageStatusChanged )
+ Q_PROPERTY( QString currentLanguageStatus READ currentLanguageStatus NOTIFY currentLanguageStatusChanged )
+ Q_PROPERTY( QString currentLCStatus READ currentLCStatus NOTIFY currentLCStatusChanged )
+ // Code are internal identifiers, like "en_US.UTF-8"
+ Q_PROPERTY( QString currentLanguageCode READ currentLanguageCode WRITE setLanguageExplicitly NOTIFY currentLanguageCodeChanged )
+ Q_PROPERTY( QString currentLCCode READ currentLCCode WRITE setLCLocaleExplicitly NOTIFY currentLCCodeChanged )
+
+ // This is a long human-readable string with all three statuses
Q_PROPERTY( QString prettyStatus READ prettyStatus NOTIFY prettyStatusChanged FINAL )
public:
Config( QObject* parent = nullptr );
~Config();
- CalamaresUtils::Locale::CStringListModel* regionModel() const;
- CalamaresUtils::Locale::CStringListModel* zonesModel() const;
-
- void setLocaleInfo( const QString& initialRegion, const QString& initialZone, const QString& localeGenPath );
+ void setConfigurationMap( const QVariantMap& );
Calamares::JobList createJobs();
- QMap< QString, QString > localesMap();
+
+ // Underlying data for the models
+ const CalamaresUtils::Locale::CStringPairList& timezoneData() const;
+
+ /** @brief The currently selected location (timezone)
+ *
+ * The location is a pointer into the date that timezoneData() returns.
+ */
+ const CalamaresUtils::Locale::TZZone* currentLocation() const { return m_currentLocation; }
+
+ /// locale configuration (LC_* and LANG) based solely on the current location.
+ LocaleConfiguration automaticLocaleConfiguration() const;
+ /// locale configuration that takes explicit settings into account
+ LocaleConfiguration localeConfiguration() const;
+
+ /// The human-readable description of what timezone is used
+ QString currentLocationStatus() const;
+ /// The human-readable description of what language is used
+ QString currentLanguageStatus() const;
+ /// The human-readable description of what locale (LC_*) is used
+ QString currentLCStatus() const;
+
+ /// The human-readable summary of what the module will do
QString prettyStatus() const;
-private:
- CalamaresUtils::Locale::CStringPairList m_regionList;
- CalamaresUtils::Locale::CStringListModel* m_regionModel;
- CalamaresUtils::Locale::CStringListModel* m_zonesModel;
+ const QStringList& supportedLocales() const { return m_localeGenLines; }
+ CalamaresUtils::Locale::CStringListModel* regionModel() const { return m_regionModel.get(); }
+ CalamaresUtils::Locale::CStringListModel* zonesModel() const { return m_zonesModel.get(); }
- LocaleConfiguration m_selectedLocaleConfiguration;
+ /// Special case, set location from starting timezone if not already set
+ void setCurrentLocation();
- QStringList m_localeGenLines;
- int m_currentRegion = -1;
+public Q_SLOTS:
+ /// Set a language by user-choice, overriding future location changes
+ void setLanguageExplicitly( const QString& language );
+ /// Set LC (formats) by user-choice, overriding future location changes
+ void setLCLocaleExplicitly( const QString& locale );
- bool m_blockTzWidgetSet;
-
- LocaleConfiguration guessLocaleConfiguration() const;
-
- // For the given locale config, return two strings describing
- // the settings for language and numbers.
- std::pair< QString, QString > prettyLocaleStatus( const LocaleConfiguration& ) const;
-
- /** @brief Update the GS *locale* key with the selected system language.
+ /** @brief Sets a location by full name
*
- * This uses whatever is set in m_selectedLocaleConfiguration as the language,
- * and writes it to GS *locale* key (as a string, in BCP47 format).
+ * @p regionzone should be an identifier from zone.tab, e.g. "Africa/Abidjan",
+ * which is split into regon and zone. Invalid names will **not**
+ * change the actual location.
*/
- void updateGlobalLocale();
- void updateGlobalStorage();
- void updateLocaleLabels();
+ void setCurrentLocation( const QString& regionzone );
+ /** @brief Sets a location by split name
+ *
+ * @p region should be "America" or the like, while @p zone
+ * names a zone within that region.
+ */
+ void setCurrentLocation( const QString& region, const QString& zone );
+ /** @brief Sets a location by pointer
+ *
+ * Pointer should be within the same model as the widget uses.
+ * This can update the locale configuration -- the automatic one
+ * follows the current location, and otherwise only explicitly-set
+ * values will ignore changes to the location.
+ */
+ void setCurrentLocation( const CalamaresUtils::Locale::TZZone* location );
- const CalamaresUtils::Locale::TZZone* currentLocation() const;
+ QString currentLanguageCode() const { return localeConfiguration().language(); }
+ QString currentLCCode() const { return localeConfiguration().lc_numeric; }
signals:
- void prettyStatusChanged();
+ void currentLocationChanged( const CalamaresUtils::Locale::TZZone* location ) const;
+ void currentLocationStatusChanged( const QString& ) const;
+ void currentLanguageStatusChanged( const QString& ) const;
+ void currentLCStatusChanged( const QString& ) const;
+ void prettyStatusChanged( const QString& ) const;
+ void currentLanguageCodeChanged( const QString& ) const;
+ void currentLCCodeChanged( const QString& ) const;
+
+private:
+ /// A list of supported locale identifiers (e.g. "en_US.UTF-8")
+ QStringList m_localeGenLines;
+
+ /// The regions (America, Asia, Europe ..)
+ std::unique_ptr< CalamaresUtils::Locale::CStringListModel > m_regionModel;
+ /// The zones for the current region (e.g. America/New_York)
+ std::unique_ptr< CalamaresUtils::Locale::CStringListModel > m_zonesModel;
+
+ /// The location, points into the timezone data
+ const CalamaresUtils::Locale::TZZone* m_currentLocation = nullptr;
+
+ /** @brief Specific locale configurations
+ *
+ * "Automatic" locale configuration based on the location (e.g.
+ * Europe/Amsterdam means Dutch language and Dutch locale) leave
+ * this empty; if the user explicitly sets something, then
+ * this configuration is non-empty and takes precedence over the
+ * automatic location settings (so a user in Amsterdam can still
+ * pick Ukranian settings, for instance).
+ */
+ LocaleConfiguration m_selectedLocaleConfiguration;
+
+ /** @brief Should we adjust the "live" timezone when the location changes?
+ *
+ * In the Widgets UI, clicking around on the world map adjusts the
+ * timezone, and the live system can be made to follow that.
+ */
+ bool m_adjustLiveTimezone;
+
+ /** @brief The initial timezone (region, zone) specified in the config.
+ *
+ * This may be overridden by setting *useSystemTimezone* or by
+ * GeoIP settings.
+ */
+ CalamaresUtils::GeoIP::RegionZonePair m_startingTimezone;
+
+ /** @brief Handler for GeoIP lookup (if configured)
+ *
+ * The GeoIP lookup needs to be started at some suitable time,
+ * by explicitly calling *TODO*
+ */
+ std::unique_ptr< CalamaresUtils::GeoIP::Handler > m_geoip;
+
+ // Implementation details for doing GeoIP lookup
+ void startGeoIP();
+ void completeGeoIP();
+ std::unique_ptr< QFutureWatcher< CalamaresUtils::GeoIP::RegionZonePair > > m_geoipWatcher;
};
diff --git a/src/modules/locale/LocaleConfiguration.cpp b/src/modules/locale/LocaleConfiguration.cpp
index f1810fb83..055907228 100644
--- a/src/modules/locale/LocaleConfiguration.cpp
+++ b/src/modules/locale/LocaleConfiguration.cpp
@@ -37,7 +37,7 @@ LocaleConfiguration::LocaleConfiguration( const QString& localeName, const QStri
lc_numeric = lc_time = lc_monetary = lc_paper = lc_name = lc_address = lc_telephone = lc_measurement
= lc_identification = formatsName;
- (void)setLanguage( localeName );
+ setLanguage( localeName );
}
@@ -83,7 +83,7 @@ LocaleConfiguration::fromLanguageAndLocation( const QString& languageLocale,
if ( language == "pt" || language == "zh" )
{
QString proposedLocale = QString( "%1_%2" ).arg( language ).arg( countryCode );
- foreach ( QString line, linesForLanguage )
+ for ( const QString& line : linesForLanguage )
{
if ( line.contains( proposedLocale ) )
{
diff --git a/src/modules/locale/LocaleConfiguration.h b/src/modules/locale/LocaleConfiguration.h
index 4f4fc6b21..2e02bc02a 100644
--- a/src/modules/locale/LocaleConfiguration.h
+++ b/src/modules/locale/LocaleConfiguration.h
@@ -26,36 +26,53 @@
class LocaleConfiguration
{
-public:
- /// @brief Create an empty locale, with nothing set
- explicit LocaleConfiguration();
- /// @brief Create a locale with everything set to the given @p localeName
+public: // TODO: private (but need to be public for tests)
+ /** @brief Create a locale with everything set to the given @p localeName
+ *
+ * Consumers should use fromLanguageAndLocation() instead.
+ */
explicit LocaleConfiguration( const QString& localeName /* "en_US.UTF-8" */ )
: LocaleConfiguration( localeName, localeName )
{
}
- /// @brief Create a locale with language and formats separate
+ /** @brief Create a locale with language and formats separate
+ *
+ * Consumers should use fromLanguageAndLocation() instead.
+ */
explicit LocaleConfiguration( const QString& localeName, const QString& formatsName );
+ /// @brief Create an empty locale, with nothing set
+ explicit LocaleConfiguration();
+
+ /** @brief Create a "sensible" locale configuration for @p language and @p countryCode
+ *
+ * This method applies some heuristics to pick a good locale (from the list
+ * @p availableLocales), along with a good language (for instance, in
+ * large countries with many languages, picking a generally used one).
+ */
static LocaleConfiguration
fromLanguageAndLocation( const QString& language, const QStringList& availableLocales, const QString& countryCode );
+ /// Is this an empty (default-constructed and not modified) configuration?
bool isEmpty() const;
- /** @brief sets lang and the BCP47 representation
+ /** @brief sets language to @p localeName
*
- * Note that the documentation how this works is in packages.conf
+ * The language may be regionalized, e.g. "nl_BE". Both the language
+ * (with region) and BCP47 representation (without region, lowercase)
+ * are updated. The BCP47 representation is used by the packages module.
+ * See also `packages.conf` for a discussion of how this is used.
*/
void setLanguage( const QString& localeName );
+ /// Current language (including region)
QString language() const { return m_lang; }
-
- // Note that the documentation how this works is in packages.conf
+ /// Current language (lowercase, BCP47 format, no region)
QString toBcp47() const { return m_languageLocaleBcp47; }
QMap< QString, QString > toMap() const;
// These become all uppercase in locale.conf, but we keep them lowercase here to
- // avoid confusion with locale.h.
+ // avoid confusion with , which defines (e.g.) LC_NUMERIC macro.
QString lc_numeric, lc_time, lc_monetary, lc_paper, lc_name, lc_address, lc_telephone, lc_measurement,
lc_identification;
diff --git a/src/modules/locale/LocalePage.cpp b/src/modules/locale/LocalePage.cpp
index 53d97ea02..406f27a6e 100644
--- a/src/modules/locale/LocalePage.cpp
+++ b/src/modules/locale/LocalePage.cpp
@@ -19,37 +19,30 @@
#include "LocalePage.h"
-#include "SetTimezoneJob.h"
+#include "Config.h"
+#include "LCLocaleDialog.h"
#include "timezonewidget/timezonewidget.h"
-#include "GlobalStorage.h"
-#include "JobQueue.h"
-#include "LCLocaleDialog.h"
-#include "Settings.h"
-
-#include "locale/Label.h"
-#include "locale/TimeZone.h"
#include "utils/CalamaresUtilsGui.h"
#include "utils/Logger.h"
+#include "utils/RAII.h"
#include "utils/Retranslator.h"
#include
#include
-#include
#include
-#include
+#include
#include
-LocalePage::LocalePage( QWidget* parent )
+LocalePage::LocalePage( Config* config, QWidget* parent )
: QWidget( parent )
- , m_regionList( CalamaresUtils::Locale::TZRegion::fromZoneTab() )
- , m_regionModel( std::make_unique< CalamaresUtils::Locale::CStringListModel >( m_regionList ) )
+ , m_config( config )
, m_blockTzWidgetSet( false )
{
QBoxLayout* mainLayout = new QVBoxLayout;
QBoxLayout* tzwLayout = new QHBoxLayout;
- m_tzWidget = new TimeZoneWidget( this );
+ m_tzWidget = new TimeZoneWidget( config->timezoneData(), this );
tzwLayout->addStretch();
tzwLayout->addWidget( m_tzWidget );
tzwLayout->addStretch();
@@ -105,9 +98,24 @@ LocalePage::LocalePage( QWidget* parent )
setMinimumWidth( m_tzWidget->width() );
setLayout( mainLayout );
+ // Set up the location before connecting signals, to avoid a signal
+ // storm as various parts interact.
+ m_regionCombo->setModel( m_config->regionModel() );
+ locationChanged( m_config->currentLocation() ); // doesn't inform TZ widget
+ m_tzWidget->setCurrentLocation( m_config->currentLocation() );
+
+ connect( config, &Config::currentLCStatusChanged, m_formatsLabel, &QLabel::setText );
+ connect( config, &Config::currentLanguageStatusChanged, m_localeLabel, &QLabel::setText );
+ connect( config, &Config::currentLocationChanged, m_tzWidget, &TimeZoneWidget::setCurrentLocation );
+ connect( config, &Config::currentLocationChanged, this, &LocalePage::locationChanged );
+ connect( m_tzWidget,
+ &TimeZoneWidget::locationChanged,
+ config,
+ QOverload< const CalamaresUtils::Locale::TZZone* >::of( &Config::setCurrentLocation ) );
+
connect( m_regionCombo, QOverload< int >::of( &QComboBox::currentIndexChanged ), this, &LocalePage::regionChanged );
connect( m_zoneCombo, QOverload< int >::of( &QComboBox::currentIndexChanged ), this, &LocalePage::zoneChanged );
- connect( m_tzWidget, &TimeZoneWidget::locationChanged, this, &LocalePage::locationChanged );
+
connect( m_localeChangeButton, &QPushButton::clicked, this, &LocalePage::changeLocale );
connect( m_formatsChangeButton, &QPushButton::clicked, this, &LocalePage::changeFormats );
@@ -115,10 +123,7 @@ LocalePage::LocalePage( QWidget* parent )
}
-LocalePage::~LocalePage()
-{
- qDeleteAll( m_regionList );
-}
+LocalePage::~LocalePage() {}
void
@@ -128,175 +133,8 @@ LocalePage::updateLocaleLabels()
m_zoneLabel->setText( tr( "Zone:" ) );
m_localeChangeButton->setText( tr( "&Change..." ) );
m_formatsChangeButton->setText( tr( "&Change..." ) );
-
- LocaleConfiguration lc
- = m_selectedLocaleConfiguration.isEmpty() ? guessLocaleConfiguration() : m_selectedLocaleConfiguration;
- auto labels = prettyLocaleStatus( lc );
- m_localeLabel->setText( labels.first );
- m_formatsLabel->setText( labels.second );
-}
-
-void
-LocalePage::init( const QString& initialRegion, const QString& initialZone, const QString& localeGenPath )
-{
- using namespace CalamaresUtils::Locale;
-
- m_regionCombo->setModel( m_regionModel.get() );
- m_regionCombo->currentIndexChanged( m_regionCombo->currentIndex() );
-
- auto* region = m_regionList.find< TZRegion >( initialRegion );
- if ( region && region->zones().find< TZZone >( initialZone ) )
- {
- m_tzWidget->setCurrentLocation( initialRegion, initialZone );
- }
- else
- {
- m_tzWidget->setCurrentLocation( "America", "New_York" );
- }
-
- // Some distros come with a meaningfully commented and easy to parse locale.gen,
- // and others ship a separate file /usr/share/i18n/SUPPORTED with a clean list of
- // supported locales. We first try that one, and if it doesn't exist, we fall back
- // to parsing the lines from locale.gen
- m_localeGenLines.clear();
- QFile supported( "/usr/share/i18n/SUPPORTED" );
- QByteArray ba;
-
- if ( supported.exists() && supported.open( QIODevice::ReadOnly | QIODevice::Text ) )
- {
- ba = supported.readAll();
- supported.close();
-
- const auto lines = ba.split( '\n' );
- for ( const QByteArray& line : lines )
- {
- m_localeGenLines.append( QString::fromLatin1( line.simplified() ) );
- }
- }
- else
- {
- QFile localeGen( localeGenPath );
- if ( localeGen.open( QIODevice::ReadOnly | QIODevice::Text ) )
- {
- ba = localeGen.readAll();
- localeGen.close();
- }
- else
- {
- cWarning() << "Cannot open file" << localeGenPath
- << ". Assuming the supported languages are already built into "
- "the locale archive.";
- QProcess localeA;
- localeA.start( "locale", QStringList() << "-a" );
- localeA.waitForFinished();
- ba = localeA.readAllStandardOutput();
- }
- const auto lines = ba.split( '\n' );
- for ( const QByteArray& line : lines )
- {
- if ( line.startsWith( "## " ) || line.startsWith( "# " ) || line.simplified() == "#" )
- {
- continue;
- }
-
- QString lineString = QString::fromLatin1( line.simplified() );
- if ( lineString.startsWith( "#" ) )
- {
- lineString.remove( '#' );
- }
- lineString = lineString.simplified();
-
- if ( lineString.isEmpty() )
- {
- continue;
- }
-
- m_localeGenLines.append( lineString );
- }
- }
-
- if ( m_localeGenLines.isEmpty() )
- {
- cWarning() << "cannot acquire a list of available locales."
- << "The locale and localecfg modules will be broken as long as this "
- "system does not provide"
- << "\n\t "
- << "* a well-formed" << supported.fileName() << "\n\tOR"
- << "* a well-formed"
- << ( localeGenPath.isEmpty() ? QLatin1String( "/etc/locale.gen" ) : localeGenPath ) << "\n\tOR"
- << "* a complete pre-compiled locale-gen database which allows complete locale -a output.";
- return; // something went wrong and there's nothing we can do about it.
- }
-
- // Assuming we have a list of supported locales, we usually only want UTF-8 ones
- // because it's not 1995.
- auto notUtf8 = []( const QString& s ) {
- return !s.contains( "UTF-8", Qt::CaseInsensitive ) && !s.contains( "utf8", Qt::CaseInsensitive );
- };
- auto it = std::remove_if( m_localeGenLines.begin(), m_localeGenLines.end(), notUtf8 );
- m_localeGenLines.erase( it, m_localeGenLines.end() );
-
- // We strip " UTF-8" from "en_US.UTF-8 UTF-8" because it's redundant redundant.
- // Also simplify whitespace.
- auto unredundant = []( QString& s ) {
- if ( s.endsWith( " UTF-8" ) )
- {
- s.chop( 6 );
- }
- s = s.simplified();
- };
- std::for_each( m_localeGenLines.begin(), m_localeGenLines.end(), unredundant );
-
- updateGlobalStorage();
-}
-
-std::pair< QString, QString >
-LocalePage::prettyLocaleStatus( const LocaleConfiguration& lc ) const
-{
- using CalamaresUtils::Locale::Label;
-
- Label lang( lc.language(), Label::LabelFormat::AlwaysWithCountry );
- Label num( lc.lc_numeric, Label::LabelFormat::AlwaysWithCountry );
-
- return std::make_pair< QString, QString >(
- tr( "The system language will be set to %1." ).arg( lang.label() ),
- tr( "The numbers and dates locale will be set to %1." ).arg( num.label() ) );
-}
-
-QString
-LocalePage::prettyStatus() const
-{
- QString status;
- status += tr( "Set timezone to %1/%2. " ).arg( m_regionCombo->currentText() ).arg( m_zoneCombo->currentText() );
-
- LocaleConfiguration lc
- = m_selectedLocaleConfiguration.isEmpty() ? guessLocaleConfiguration() : m_selectedLocaleConfiguration;
- auto labels = prettyLocaleStatus( lc );
- status += labels.first + " ";
- status += labels.second + " ";
-
- return status;
-}
-
-
-Calamares::JobList
-LocalePage::createJobs()
-{
- QList< Calamares::job_ptr > list;
- const CalamaresUtils::Locale::TZZone* location = m_tzWidget->currentLocation();
-
- Calamares::Job* j = new SetTimezoneJob( location->region(), location->zone() );
- list.append( Calamares::job_ptr( j ) );
-
- return list;
-}
-
-
-QMap< QString, QString >
-LocalePage::localesMap()
-{
- return m_selectedLocaleConfiguration.isEmpty() ? guessLocaleConfiguration().toMap()
- : m_selectedLocaleConfiguration.toMap();
+ m_localeLabel->setText( m_config->currentLanguageStatus() );
+ m_formatsLabel->setText( m_config->currentLCStatus() );
}
@@ -304,82 +142,10 @@ void
LocalePage::onActivate()
{
m_regionCombo->setFocus();
- if ( m_selectedLocaleConfiguration.isEmpty() || !m_selectedLocaleConfiguration.explicit_lang )
- {
- auto newLocale = guessLocaleConfiguration();
- m_selectedLocaleConfiguration.setLanguage( newLocale.language() );
- updateGlobalLocale();
- updateLocaleLabels();
- }
-}
-
-
-LocaleConfiguration
-LocalePage::guessLocaleConfiguration() const
-{
- return LocaleConfiguration::fromLanguageAndLocation(
- QLocale().name(), m_localeGenLines, m_tzWidget->currentLocation()->country() );
-}
-
-
-void
-LocalePage::updateGlobalLocale()
-{
- auto* gs = Calamares::JobQueue::instance()->globalStorage();
- const QString bcp47 = m_selectedLocaleConfiguration.toBcp47();
- gs->insert( "locale", bcp47 );
-}
-
-
-void
-LocalePage::updateGlobalStorage()
-{
- auto* gs = Calamares::JobQueue::instance()->globalStorage();
-
- const auto* location = m_tzWidget->currentLocation();
- bool locationChanged = ( location->region() != gs->value( "locationRegion" ) )
- || ( location->zone() != gs->value( "locationZone" ) );
-
- gs->insert( "locationRegion", location->region() );
- gs->insert( "locationZone", location->zone() );
-
- updateGlobalLocale();
-
- // If we're in chroot mode (normal install mode), then we immediately set the
- // timezone on the live system. When debugging timezones, don't bother.
-#ifndef DEBUG_TIMEZONES
- if ( locationChanged && Calamares::Settings::instance()->doChroot() )
- {
- QProcess::execute( "timedatectl", // depends on systemd
- { "set-timezone", location->region() + '/' + location->zone() } );
- }
-#endif
-
- // Preserve those settings that have been made explicit.
- auto newLocale = guessLocaleConfiguration();
- if ( !m_selectedLocaleConfiguration.isEmpty() && m_selectedLocaleConfiguration.explicit_lang )
- {
- newLocale.setLanguage( m_selectedLocaleConfiguration.language() );
- }
- if ( !m_selectedLocaleConfiguration.isEmpty() && m_selectedLocaleConfiguration.explicit_lc )
- {
- newLocale.lc_numeric = m_selectedLocaleConfiguration.lc_numeric;
- newLocale.lc_time = m_selectedLocaleConfiguration.lc_time;
- newLocale.lc_monetary = m_selectedLocaleConfiguration.lc_monetary;
- newLocale.lc_paper = m_selectedLocaleConfiguration.lc_paper;
- newLocale.lc_name = m_selectedLocaleConfiguration.lc_name;
- newLocale.lc_address = m_selectedLocaleConfiguration.lc_address;
- newLocale.lc_telephone = m_selectedLocaleConfiguration.lc_telephone;
- newLocale.lc_measurement = m_selectedLocaleConfiguration.lc_measurement;
- newLocale.lc_identification = m_selectedLocaleConfiguration.lc_identification;
- }
- newLocale.explicit_lang = m_selectedLocaleConfiguration.explicit_lang;
- newLocale.explicit_lc = m_selectedLocaleConfiguration.explicit_lc;
-
- m_selectedLocaleConfiguration = newLocale;
updateLocaleLabels();
}
+
void
LocalePage::regionChanged( int currentIndex )
{
@@ -388,15 +154,17 @@ LocalePage::regionChanged( int currentIndex )
Q_UNUSED( currentIndex )
QString selectedRegion = m_regionCombo->currentData().toString();
- TZRegion* region = m_regionList.find< TZRegion >( selectedRegion );
+ TZRegion* region = m_config->timezoneData().find< TZRegion >( selectedRegion );
if ( !region )
{
return;
}
- m_zoneCombo->blockSignals( true );
- m_zoneCombo->setModel( new CStringListModel( region->zones() ) );
- m_zoneCombo->blockSignals( false );
+ {
+ cSignalBlocker b( m_zoneCombo );
+ m_zoneCombo->setModel( new CStringListModel( region->zones() ) );
+ }
+
m_zoneCombo->currentIndexChanged( m_zoneCombo->currentIndex() );
}
@@ -405,16 +173,19 @@ LocalePage::zoneChanged( int currentIndex )
{
Q_UNUSED( currentIndex )
if ( !m_blockTzWidgetSet )
- m_tzWidget->setCurrentLocation( m_regionCombo->currentData().toString(),
- m_zoneCombo->currentData().toString() );
-
- updateGlobalStorage();
+ {
+ m_config->setCurrentLocation( m_regionCombo->currentData().toString(), m_zoneCombo->currentData().toString() );
+ }
}
void
LocalePage::locationChanged( const CalamaresUtils::Locale::TZZone* location )
{
- m_blockTzWidgetSet = true;
+ if ( !location )
+ {
+ return;
+ }
+ cBoolSetter< true > b( m_blockTzWidgetSet );
// Set region index
int index = m_regionCombo->findData( location->region() );
@@ -433,58 +204,35 @@ LocalePage::locationChanged( const CalamaresUtils::Locale::TZZone* location )
}
m_zoneCombo->setCurrentIndex( index );
-
- m_blockTzWidgetSet = false;
-
- updateGlobalStorage();
}
void
LocalePage::changeLocale()
{
- LCLocaleDialog* dlg
- = new LCLocaleDialog( m_selectedLocaleConfiguration.isEmpty() ? guessLocaleConfiguration().language()
- : m_selectedLocaleConfiguration.language(),
- m_localeGenLines,
- this );
+ QPointer< LCLocaleDialog > dlg(
+ new LCLocaleDialog( m_config->localeConfiguration().language(), m_config->supportedLocales(), this ) );
dlg->exec();
- if ( dlg->result() == QDialog::Accepted && !dlg->selectedLCLocale().isEmpty() )
+ if ( dlg && dlg->result() == QDialog::Accepted && !dlg->selectedLCLocale().isEmpty() )
{
- m_selectedLocaleConfiguration.setLanguage( dlg->selectedLCLocale() );
- m_selectedLocaleConfiguration.explicit_lang = true;
- this->updateGlobalLocale();
- this->updateLocaleLabels();
+ m_config->setLanguageExplicitly( dlg->selectedLCLocale() );
+ updateLocaleLabels();
}
- dlg->deleteLater();
+ delete dlg;
}
void
LocalePage::changeFormats()
{
- LCLocaleDialog* dlg
- = new LCLocaleDialog( m_selectedLocaleConfiguration.isEmpty() ? guessLocaleConfiguration().lc_numeric
- : m_selectedLocaleConfiguration.lc_numeric,
- m_localeGenLines,
- this );
+ QPointer< LCLocaleDialog > dlg(
+ new LCLocaleDialog( m_config->localeConfiguration().lc_numeric, m_config->supportedLocales(), this ) );
dlg->exec();
- if ( dlg->result() == QDialog::Accepted && !dlg->selectedLCLocale().isEmpty() )
+ if ( dlg && dlg->result() == QDialog::Accepted && !dlg->selectedLCLocale().isEmpty() )
{
- // TODO: improve the granularity of this setting.
- m_selectedLocaleConfiguration.lc_numeric = dlg->selectedLCLocale();
- m_selectedLocaleConfiguration.lc_time = dlg->selectedLCLocale();
- m_selectedLocaleConfiguration.lc_monetary = dlg->selectedLCLocale();
- m_selectedLocaleConfiguration.lc_paper = dlg->selectedLCLocale();
- m_selectedLocaleConfiguration.lc_name = dlg->selectedLCLocale();
- m_selectedLocaleConfiguration.lc_address = dlg->selectedLCLocale();
- m_selectedLocaleConfiguration.lc_telephone = dlg->selectedLCLocale();
- m_selectedLocaleConfiguration.lc_measurement = dlg->selectedLCLocale();
- m_selectedLocaleConfiguration.lc_identification = dlg->selectedLCLocale();
- m_selectedLocaleConfiguration.explicit_lc = true;
-
- this->updateLocaleLabels();
+ m_config->setLCLocaleExplicitly( dlg->selectedLCLocale() );
+ updateLocaleLabels();
}
- dlg->deleteLater();
+ delete dlg;
}
diff --git a/src/modules/locale/LocalePage.h b/src/modules/locale/LocalePage.h
index f31d81fb6..bf41f8f69 100644
--- a/src/modules/locale/LocalePage.h
+++ b/src/modules/locale/LocalePage.h
@@ -32,39 +32,23 @@
class QComboBox;
class QLabel;
class QPushButton;
+
+class Config;
class TimeZoneWidget;
class LocalePage : public QWidget
{
Q_OBJECT
public:
- explicit LocalePage( QWidget* parent = nullptr );
+ explicit LocalePage( class Config* config, QWidget* parent = nullptr );
virtual ~LocalePage();
- void init( const QString& initialRegion, const QString& initialZone, const QString& localeGenPath );
-
- QString prettyStatus() const;
-
- Calamares::JobList createJobs();
-
- QMap< QString, QString > localesMap();
-
void onActivate();
private:
- LocaleConfiguration guessLocaleConfiguration() const;
+ /// @brief Non-owning pointer to the ViewStep's config
+ Config* m_config;
- // For the given locale config, return two strings describing
- // the settings for language and numbers.
- std::pair< QString, QString > prettyLocaleStatus( const LocaleConfiguration& ) const;
-
- /** @brief Update the GS *locale* key with the selected system language.
- *
- * This uses whatever is set in m_selectedLocaleConfiguration as the language,
- * and writes it to GS *locale* key (as a string, in BCP47 format).
- */
- void updateGlobalLocale();
- void updateGlobalStorage();
void updateLocaleLabels();
void regionChanged( int currentIndex );
@@ -73,10 +57,6 @@ private:
void changeLocale();
void changeFormats();
- // Dynamically, QList< TZRegion* >
- CalamaresUtils::Locale::CStringPairList m_regionList;
- std::unique_ptr< CalamaresUtils::Locale::CStringListModel > m_regionModel;
-
TimeZoneWidget* m_tzWidget;
QComboBox* m_regionCombo;
QComboBox* m_zoneCombo;
@@ -88,9 +68,6 @@ private:
QLabel* m_formatsLabel;
QPushButton* m_formatsChangeButton;
- LocaleConfiguration m_selectedLocaleConfiguration;
-
- QStringList m_localeGenLines;
bool m_blockTzWidgetSet;
};
diff --git a/src/modules/locale/LocaleViewStep.cpp b/src/modules/locale/LocaleViewStep.cpp
index 3a8c37673..a85c87e4f 100644
--- a/src/modules/locale/LocaleViewStep.cpp
+++ b/src/modules/locale/LocaleViewStep.cpp
@@ -34,7 +34,6 @@
#include
#include
-#include
CALAMARES_PLUGIN_FACTORY_DEFINITION( LocaleViewStepFactory, registerPlugin< LocaleViewStep >(); )
@@ -44,7 +43,7 @@ LocaleViewStep::LocaleViewStep( QObject* parent )
, m_widget( new QWidget() )
, m_actualWidget( nullptr )
, m_nextEnabled( false )
- , m_geoip( nullptr )
+ , m_config( std::make_unique< Config >() )
{
QBoxLayout* mainLayout = new QHBoxLayout;
m_widget->setLayout( mainLayout );
@@ -66,11 +65,11 @@ LocaleViewStep::~LocaleViewStep()
void
LocaleViewStep::setUpPage()
{
+ m_config->setCurrentLocation();
if ( !m_actualWidget )
{
- m_actualWidget = new LocalePage();
+ m_actualWidget = new LocalePage( m_config.get() );
}
- m_actualWidget->init( m_startingTimezone.first, m_startingTimezone.second, m_localeGenPath );
m_widget->layout()->addWidget( m_actualWidget );
ensureSize( m_actualWidget->sizeHint() );
@@ -80,20 +79,6 @@ LocaleViewStep::setUpPage()
}
-void
-LocaleViewStep::fetchGeoIpTimezone()
-{
- if ( m_geoip && m_geoip->isValid() )
- {
- m_startingTimezone = m_geoip->get();
- if ( !m_startingTimezone.isValid() )
- {
- cWarning() << "GeoIP lookup at" << m_geoip->url() << "failed.";
- }
- }
-}
-
-
QString
LocaleViewStep::prettyName() const
{
@@ -104,7 +89,7 @@ LocaleViewStep::prettyName() const
QString
LocaleViewStep::prettyStatus() const
{
- return m_prettyStatus;
+ return m_config->prettyStatus();
}
@@ -146,7 +131,7 @@ LocaleViewStep::isAtEnd() const
Calamares::JobList
LocaleViewStep::jobs() const
{
- return m_jobs;
+ return m_config->createJobs();
}
@@ -164,83 +149,11 @@ LocaleViewStep::onActivate()
void
LocaleViewStep::onLeave()
{
- if ( m_actualWidget )
- {
- m_jobs = m_actualWidget->createJobs();
- m_prettyStatus = m_actualWidget->prettyStatus();
-
- auto map = m_actualWidget->localesMap();
- QVariantMap vm;
- for ( auto it = map.constBegin(); it != map.constEnd(); ++it )
- {
- vm.insert( it.key(), it.value() );
- }
-
- Calamares::JobQueue::instance()->globalStorage()->insert( "localeConf", vm );
- }
- else
- {
- m_jobs.clear();
- Calamares::JobQueue::instance()->globalStorage()->remove( "localeConf" );
- }
}
void
LocaleViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
- QString region = CalamaresUtils::getString( configurationMap, "region" );
- QString zone = CalamaresUtils::getString( configurationMap, "zone" );
- if ( !region.isEmpty() && !zone.isEmpty() )
- {
- m_startingTimezone = CalamaresUtils::GeoIP::RegionZonePair( region, zone );
- }
- else
- {
- m_startingTimezone
- = CalamaresUtils::GeoIP::RegionZonePair( QStringLiteral( "America" ), QStringLiteral( "New_York" ) );
- }
-
- m_localeGenPath = CalamaresUtils::getString( configurationMap, "localeGenPath" );
- if ( m_localeGenPath.isEmpty() )
- {
- m_localeGenPath = QStringLiteral( "/etc/locale.gen" );
- }
-
- bool ok = false;
- QVariantMap geoip = CalamaresUtils::getSubMap( configurationMap, "geoip", ok );
- if ( ok )
- {
- QString url = CalamaresUtils::getString( geoip, "url" );
- QString style = CalamaresUtils::getString( geoip, "style" );
- QString selector = CalamaresUtils::getString( geoip, "selector" );
-
- m_geoip = std::make_unique< CalamaresUtils::GeoIP::Handler >( style, url, selector );
- if ( !m_geoip->isValid() )
- {
- cWarning() << "GeoIP Style" << style << "is not recognized.";
- }
- }
-}
-
-Calamares::RequirementsList
-LocaleViewStep::checkRequirements()
-{
- if ( m_geoip && m_geoip->isValid() )
- {
- auto& network = CalamaresUtils::Network::Manager::instance();
- if ( network.hasInternet() )
- {
- fetchGeoIpTimezone();
- }
- else
- {
- if ( network.synchronousPing( m_geoip->url() ) )
- {
- fetchGeoIpTimezone();
- }
- }
- }
-
- return Calamares::RequirementsList();
+ m_config->setConfigurationMap( configurationMap );
}
diff --git a/src/modules/locale/LocaleViewStep.h b/src/modules/locale/LocaleViewStep.h
index 841aba97f..0a40da861 100644
--- a/src/modules/locale/LocaleViewStep.h
+++ b/src/modules/locale/LocaleViewStep.h
@@ -20,15 +20,11 @@
#ifndef LOCALEVIEWSTEP_H
#define LOCALEVIEWSTEP_H
-#include "geoip/Handler.h"
-#include "geoip/Interface.h"
-#include "utils/PluginFactory.h"
-#include "viewpages/ViewStep.h"
+#include "Config.h"
#include "DllMacro.h"
-
-#include
-#include
+#include "utils/PluginFactory.h"
+#include "viewpages/ViewStep.h"
#include
@@ -60,25 +56,16 @@ public:
void setConfigurationMap( const QVariantMap& configurationMap ) override;
- /// @brief Do setup (returns empty list) asynchronously
- virtual Calamares::RequirementsList checkRequirements() override;
-
private slots:
void setUpPage();
private:
- void fetchGeoIpTimezone();
QWidget* m_widget;
LocalePage* m_actualWidget;
bool m_nextEnabled;
- QString m_prettyStatus;
- CalamaresUtils::GeoIP::RegionZonePair m_startingTimezone;
- QString m_localeGenPath;
-
- Calamares::JobList m_jobs;
- std::unique_ptr< CalamaresUtils::GeoIP::Handler > m_geoip;
+ std::unique_ptr< Config > m_config;
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( LocaleViewStepFactory )
diff --git a/src/modules/locale/locale.conf b/src/modules/locale/locale.conf
index dc68a050f..8236a879b 100644
--- a/src/modules/locale/locale.conf
+++ b/src/modules/locale/locale.conf
@@ -1,5 +1,5 @@
---
-# This settings are used to set your default system time zone.
+# These settings are used to set your default system time zone.
# Time zones are usually located under /usr/share/zoneinfo and
# provided by the 'tzdata' package of your Distribution.
#
@@ -11,20 +11,41 @@
# the locale page can be set through keys *region* and *zone*.
# If either is not set, defaults to America/New_York.
#
+# Note that useSystemTimezone and GeoIP settings can change the
+# starting time zone.
+#
region: "America"
zone: "New_York"
+# Instead of using *region* and *zone* specified above,
+# you can use the system's notion of the timezone, instead.
+# This can help if your system is automatically configured with
+# a sensible TZ rather than chasing a fixed default.
+#
+# The default is false.
+#
+# useSystemTimezone: true
+
+# Should changing the system location (e.g. clicking around on the timezone
+# map) immediately reflect the changed timezone in the live system?
+# By default, installers (with a target system) do, and setup (e.g. OEM
+# configuration) does not, but you can switch it on here (or off, if
+# you think it's annoying in the installer).
+#
+# Note that not all systems support live adjustment.
+#
+# adjustLiveTimezone: true
# System locales are detected in the following order:
#
# - /usr/share/i18n/SUPPORTED
# - localeGenPath (defaults to /etc/locale.gen if not set)
-# - 'locale -a' output
+# - `locale -a` output
#
-# Enable only when your Distribution is using an
+# Enable only when your Distribution is using a
# custom path for locale.gen
#
-#localeGenPath: "PATH_TO/locale.gen"
+#localeGenPath: "/etc/locale.gen"
# GeoIP based Language settings: Leave commented out to disable GeoIP.
#
diff --git a/src/modules/locale/locale.schema.yaml b/src/modules/locale/locale.schema.yaml
index 41c3ad487..d6c35020f 100644
--- a/src/modules/locale/locale.schema.yaml
+++ b/src/modules/locale/locale.schema.yaml
@@ -4,7 +4,35 @@ $id: https://calamares.io/schemas/locale
additionalProperties: false
type: object
properties:
- "region": { type: str }
- "zone": { type: str }
- "localeGenPath": { type: string, required: true }
- "geoipUrl": { type: str }
+ region: { type: string,
+ enum: [
+ Africa,
+ America,
+ Antarctica,
+ Arctic,
+ Asia,
+ Atlantic,
+ Australia,
+ Europe,
+ Indian,
+ Pacific
+ ]
+ }
+ zone: { type: string }
+ useSystemTimezone: { type: boolean, default: false }
+
+ adjustLiveTimezone: { type: boolean, default: true }
+
+ localeGenPath: { type: string }
+
+ # TODO: refactor, this is reused in welcome
+ geoip:
+ additionalProperties: false
+ type: object
+ properties:
+ style: { type: string, enum: [ none, fixed, xml, json ] }
+ url: { type: string }
+ selector: { type: string }
+ required: [ style, url, selector ]
+
+required: [ region, zone ]
diff --git a/src/modules/locale/timezonewidget/TimeZoneImage.cpp b/src/modules/locale/timezonewidget/TimeZoneImage.cpp
index a60c9b7d7..22bd263d6 100644
--- a/src/modules/locale/timezonewidget/TimeZoneImage.cpp
+++ b/src/modules/locale/timezonewidget/TimeZoneImage.cpp
@@ -25,9 +25,9 @@
#include
static const char* zoneNames[]
- = { "0.0", "1.0", "2.0", "3.0", "3.5", "4.0", "4.5", "5.0", "5.5", "5.75", "6.0", "6.5", "7.0",
- "8.0", "9.0", "9.5", "10.0", "10.5", "11.0", "12.0", "12.75", "13.0", "-1.0", "-2.0", "-3.0",
- "-3.5", "-4.0", "-4.5", "-5.0", "-5.5", "-6.0", "-7.0", "-8.0", "-9.0", "-9.5", "-10.0", "-11.0" };
+ = { "0.0", "1.0", "2.0", "3.0", "3.5", "4.0", "4.5", "5.0", "5.5", "5.75", "6.0", "6.5", "7.0",
+ "8.0", "9.0", "9.5", "10.0", "10.5", "11.0", "12.0", "12.75", "13.0", "-1.0", "-2.0", "-3.0", "-3.5",
+ "-4.0", "-4.5", "-5.0", "-5.5", "-6.0", "-7.0", "-8.0", "-9.0", "-9.5", "-10.0", "-11.0" };
static_assert( TimeZoneImageList::zoneCount == ( sizeof( zoneNames ) / sizeof( zoneNames[ 0 ] ) ),
"Incorrect number of zones" );
diff --git a/src/modules/locale/timezonewidget/timezonewidget.cpp b/src/modules/locale/timezonewidget/timezonewidget.cpp
index b81e0414c..0972e3296 100644
--- a/src/modules/locale/timezonewidget/timezonewidget.cpp
+++ b/src/modules/locale/timezonewidget/timezonewidget.cpp
@@ -34,9 +34,17 @@
#define ZONE_NAME QStringLiteral( "zone" )
#endif
-TimeZoneWidget::TimeZoneWidget( QWidget* parent )
+static QPoint
+getLocationPosition( const CalamaresUtils::Locale::TZZone* l )
+{
+ return TimeZoneImageList::getLocationPosition( l->longitude(), l->latitude() );
+}
+
+
+TimeZoneWidget::TimeZoneWidget( const CalamaresUtils::Locale::CStringPairList& zones, QWidget* parent )
: QWidget( parent )
, timeZoneImages( TimeZoneImageList::fromQRC() )
+ , m_zonesData( zones )
{
setMouseTracking( false );
setCursor( Qt::PointingHandCursor );
@@ -57,27 +65,13 @@ TimeZoneWidget::TimeZoneWidget( QWidget* parent )
void
-TimeZoneWidget::setCurrentLocation( QString regionName, QString zoneName )
+TimeZoneWidget::setCurrentLocation( const CalamaresUtils::Locale::TZZone* location )
{
- using namespace CalamaresUtils::Locale;
- const auto& regions = TZRegion::fromZoneTab();
- auto* region = regions.find< TZRegion >( regionName );
- if ( !region )
+ if ( location == m_currentLocation )
{
return;
}
- auto* zone = region->zones().find< TZZone >( zoneName );
- if ( zone )
- {
- setCurrentLocation( zone );
- }
-}
-
-
-void
-TimeZoneWidget::setCurrentLocation( const CalamaresUtils::Locale::TZZone* location )
-{
m_currentLocation = location;
// Set zone
@@ -93,7 +87,6 @@ TimeZoneWidget::setCurrentLocation( const CalamaresUtils::Locale::TZZone* locati
// Repaint widget
repaint();
- emit locationChanged( m_currentLocation );
}
@@ -101,11 +94,18 @@ TimeZoneWidget::setCurrentLocation( const CalamaresUtils::Locale::TZZone* locati
//### Private
//###
+struct PainterEnder
+{
+ QPainter& p;
+ ~PainterEnder() { p.end(); }
+};
+
void
TimeZoneWidget::paintEvent( QPaintEvent* )
{
QFontMetrics fontMetrics( font );
QPainter painter( this );
+ PainterEnder painter_end { painter };
painter.setRenderHint( QPainter::Antialiasing );
painter.setFont( font );
@@ -116,6 +116,11 @@ TimeZoneWidget::paintEvent( QPaintEvent* )
// Draw zone image
painter.drawImage( 0, 0, currentZoneImage );
+ if ( !m_currentLocation )
+ {
+ return;
+ }
+
#ifdef DEBUG_TIMEZONES
QPoint point = getLocationPosition( m_currentLocation );
// Draw latitude lines
@@ -175,8 +180,6 @@ TimeZoneWidget::paintEvent( QPaintEvent* )
painter.setPen( Qt::white );
painter.drawText( rect.x() + 5, rect.bottom() - 4, m_currentLocation ? m_currentLocation->tr() : QString() );
#endif
-
- painter.end();
}
@@ -194,7 +197,7 @@ TimeZoneWidget::mousePressEvent( QMouseEvent* event )
using namespace CalamaresUtils::Locale;
const TZZone* closest = nullptr;
- for ( const auto* region_p : TZRegion::fromZoneTab() )
+ for ( const auto* region_p : m_zonesData )
{
const auto* region = dynamic_cast< const TZRegion* >( region_p );
if ( region )
@@ -222,6 +225,6 @@ TimeZoneWidget::mousePressEvent( QMouseEvent* event )
// Set zone image and repaint widget
setCurrentLocation( closest );
// Emit signal
- emit locationChanged( m_currentLocation );
+ emit locationChanged( closest );
}
}
diff --git a/src/modules/locale/timezonewidget/timezonewidget.h b/src/modules/locale/timezonewidget/timezonewidget.h
index afebbfd7b..6bb94c0dd 100644
--- a/src/modules/locale/timezonewidget/timezonewidget.h
+++ b/src/modules/locale/timezonewidget/timezonewidget.h
@@ -31,32 +31,48 @@
#include
#include
+/** @brief The TimeZoneWidget shows a map and reports where clicks happen
+ *
+ * This widget shows a map (unspecified whether it's a whole world map
+ * or can show regionsvia some kind of internal state). Mouse clicks are
+ * translated into timezone locations (e.g. the zone for America/New_York).
+ *
+ * The current location can be changed programmatically, by name
+ * or through a pointer to a location. If a pointer is used, take care
+ * that the pointer is to a zone in the same model as used by the
+ * widget.
+ *
+ * When a location is chosen -- by mouse click or programmatically --
+ * the locationChanged() signal is emitted with the new location.
+ *
+ * NOTE: the widget currently uses the globally cached TZRegion::fromZoneTab()
+ */
class TimeZoneWidget : public QWidget
{
Q_OBJECT
public:
using TZZone = CalamaresUtils::Locale::TZZone;
- explicit TimeZoneWidget( QWidget* parent = nullptr );
+ explicit TimeZoneWidget( const CalamaresUtils::Locale::CStringPairList& zones, QWidget* parent = nullptr );
- void setCurrentLocation( QString region, QString zone );
+public Q_SLOTS:
+ /** @brief Sets a location by pointer
+ *
+ * Pointer should be within the same model as the widget uses.
+ */
void setCurrentLocation( const TZZone* location );
- const TZZone* currentLocation() { return m_currentLocation; }
-
signals:
+ /** @brief The location has changed by mouse click */
void locationChanged( const TZZone* location );
private:
QFont font;
QImage background, pin, currentZoneImage;
TimeZoneImageList timeZoneImages;
- const TZZone* m_currentLocation = nullptr; // Not owned by me
- QPoint getLocationPosition( const TZZone* l )
- {
- return timeZoneImages.getLocationPosition( l->longitude(), l->latitude() );
- }
+ const CalamaresUtils::Locale::CStringPairList& m_zonesData;
+ const TZZone* m_currentLocation = nullptr; // Not owned by me
void paintEvent( QPaintEvent* event );
void mousePressEvent( QMouseEvent* event );
diff --git a/src/modules/localeq/LocaleQmlViewStep.cpp b/src/modules/localeq/LocaleQmlViewStep.cpp
index fd5e72734..ead2e2673 100644
--- a/src/modules/localeq/LocaleQmlViewStep.cpp
+++ b/src/modules/localeq/LocaleQmlViewStep.cpp
@@ -19,74 +19,20 @@
#include "LocaleQmlViewStep.h"
-#include "GlobalStorage.h"
-#include "JobQueue.h"
-
-#include "geoip/Handler.h"
-#include "network/Manager.h"
-#include "utils/CalamaresUtilsGui.h"
#include "utils/Logger.h"
-#include "utils/Variant.h"
-#include "utils/Yaml.h"
-
-#include "Branding.h"
-#include "modulesystem/ModuleManager.h"
-#include
-#include
-#include
-#include
CALAMARES_PLUGIN_FACTORY_DEFINITION( LocaleQmlViewStepFactory, registerPlugin< LocaleQmlViewStep >(); )
LocaleQmlViewStep::LocaleQmlViewStep( QObject* parent )
-: Calamares::QmlViewStep( parent )
-, m_config( new Config( this ) )
-, m_nextEnabled( false )
-, m_geoip( nullptr )
+ : Calamares::QmlViewStep( parent )
+ , m_config( std::make_unique< Config >( this ) )
{
- emit nextStatusChanged( m_nextEnabled );
}
QObject*
LocaleQmlViewStep::getConfig()
{
- return m_config;
-}
-
-void
-LocaleQmlViewStep::fetchGeoIpTimezone()
-{
- if ( m_geoip && m_geoip->isValid() )
- {
- m_startingTimezone = m_geoip->get();
- if ( !m_startingTimezone.isValid() )
- {
- cWarning() << "GeoIP lookup at" << m_geoip->url() << "failed.";
- }
- }
-
- m_config->setLocaleInfo(m_startingTimezone.first, m_startingTimezone.second, m_localeGenPath);
-}
-
-Calamares::RequirementsList LocaleQmlViewStep::checkRequirements()
-{
- if ( m_geoip && m_geoip->isValid() )
- {
- auto& network = CalamaresUtils::Network::Manager::instance();
- if ( network.hasInternet() )
- {
- fetchGeoIpTimezone();
- }
- else
- {
- if ( network.synchronousPing( m_geoip->url() ) )
- {
- fetchGeoIpTimezone();
- }
- }
- }
-
- return Calamares::RequirementsList();
+ return m_config.get();
}
QString
@@ -95,17 +41,21 @@ LocaleQmlViewStep::prettyName() const
return tr( "Location" );
}
+QString
+LocaleQmlViewStep::prettyStatus() const
+{
+ return m_config->prettyStatus();
+}
+
bool
LocaleQmlViewStep::isNextEnabled() const
{
- // TODO: should return true
return true;
}
bool
LocaleQmlViewStep::isBackEnabled() const
{
- // TODO: should return true (it's weird that you are not allowed to have welcome *after* anything
return true;
}
@@ -113,7 +63,6 @@ LocaleQmlViewStep::isBackEnabled() const
bool
LocaleQmlViewStep::isAtBeginning() const
{
- // TODO: adjust to "pages" in the QML
return true;
}
@@ -121,79 +70,18 @@ LocaleQmlViewStep::isAtBeginning() const
bool
LocaleQmlViewStep::isAtEnd() const
{
- // TODO: adjust to "pages" in the QML
return true;
}
Calamares::JobList
LocaleQmlViewStep::jobs() const
{
- return m_jobs;
+ return m_config->createJobs();
}
-void LocaleQmlViewStep::onActivate()
+void
+LocaleQmlViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
- // TODO no sure if it is needed at all or for the abstract class to start something
-}
-
-void LocaleQmlViewStep::onLeave()
-{
- if ( true )
- {
- m_jobs = m_config->createJobs();
-// m_prettyStatus = m_actualWidget->prettyStatus();
-
- auto map = m_config->localesMap();
- QVariantMap vm;
- for ( auto it = map.constBegin(); it != map.constEnd(); ++it )
- {
- vm.insert( it.key(), it.value() );
- }
-
- Calamares::JobQueue::instance()->globalStorage()->insert( "localeConf", vm );
- }
- else
- {
- m_jobs.clear();
- Calamares::JobQueue::instance()->globalStorage()->remove( "localeConf" );
- }
-}
-
-void LocaleQmlViewStep::setConfigurationMap(const QVariantMap& configurationMap)
-{
- QString region = CalamaresUtils::getString( configurationMap, "region" );
- QString zone = CalamaresUtils::getString( configurationMap, "zone" );
- if ( !region.isEmpty() && !zone.isEmpty() )
- {
- m_startingTimezone = CalamaresUtils::GeoIP::RegionZonePair( region, zone );
- }
- else
- {
- m_startingTimezone
- = CalamaresUtils::GeoIP::RegionZonePair( QStringLiteral( "America" ), QStringLiteral( "New_York" ) );
- }
-
- m_localeGenPath = CalamaresUtils::getString( configurationMap, "localeGenPath" );
- if ( m_localeGenPath.isEmpty() )
- {
- m_localeGenPath = QStringLiteral( "/etc/locale.gen" );
- }
-
- bool ok = false;
- QVariantMap geoip = CalamaresUtils::getSubMap( configurationMap, "geoip", ok );
- if ( ok )
- {
- QString url = CalamaresUtils::getString( geoip, "url" );
- QString style = CalamaresUtils::getString( geoip, "style" );
- QString selector = CalamaresUtils::getString( geoip, "selector" );
-
- m_geoip = std::make_unique< CalamaresUtils::GeoIP::Handler >( style, url, selector );
- if ( !m_geoip->isValid() )
- {
- cWarning() << "GeoIP Style" << style << "is not recognized.";
- }
- }
-
- checkRequirements();
- Calamares::QmlViewStep::setConfigurationMap( configurationMap ); // call parent implementation last
+ m_config->setConfigurationMap( configurationMap );
+ Calamares::QmlViewStep::setConfigurationMap( configurationMap ); // call parent implementation last
}
diff --git a/src/modules/localeq/LocaleQmlViewStep.h b/src/modules/localeq/LocaleQmlViewStep.h
index 0639274d6..3d73c6f79 100644
--- a/src/modules/localeq/LocaleQmlViewStep.h
+++ b/src/modules/localeq/LocaleQmlViewStep.h
@@ -20,14 +20,10 @@
#define LOCALE_QMLVIEWSTEP_H
#include "Config.h"
-#include "geoip/Handler.h"
-#include "geoip/Interface.h"
+
+#include "DllMacro.h"
#include "utils/PluginFactory.h"
#include "viewpages/QmlViewStep.h"
-#include
-
-#include
-#include
#include
@@ -39,6 +35,7 @@ public:
explicit LocaleQmlViewStep( QObject* parent = nullptr );
QString prettyName() const override;
+ QString prettyStatus() const override;
bool isNextEnabled() const override;
bool isBackEnabled() const override;
@@ -47,28 +44,12 @@ public:
bool isAtEnd() const override;
Calamares::JobList jobs() const override;
- void onActivate() override;
- void onLeave() override;
void setConfigurationMap( const QVariantMap& configurationMap ) override;
QObject* getConfig() override;
- virtual Calamares::RequirementsList checkRequirements() override;
-
private:
- // TODO: a generic QML viewstep should return a config object from a method
- Config *m_config;
-
- bool m_nextEnabled;
- QString m_prettyStatus;
-
- CalamaresUtils::GeoIP::RegionZonePair m_startingTimezone;
- QString m_localeGenPath;
-
- Calamares::JobList m_jobs;
- std::unique_ptr< CalamaresUtils::GeoIP::Handler > m_geoip;
-
- void fetchGeoIpTimezone();
+ std::unique_ptr< Config > m_config;
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( LocaleQmlViewStepFactory )
diff --git a/src/modules/localeq/Map.qml b/src/modules/localeq/Map.qml
index 023de6d1b..080d7388a 100644
--- a/src/modules/localeq/Map.qml
+++ b/src/modules/localeq/Map.qml
@@ -74,6 +74,7 @@ Column {
var tz2 = responseJSON.timezoneId
tzText.text = "Timezone: " + tz2
+ config.setCurrentLocation(tz2)
}
}
@@ -126,7 +127,7 @@ Column {
anchorPoint.x: image.width/4
anchorPoint.y: image.height
coordinate: QtPositioning.coordinate(
- map.center.latitude,
+ map.center.latitude,
map.center.longitude)
//coordinate: QtPositioning.coordinate(40.730610, -73.935242) // New York
@@ -156,7 +157,7 @@ Column {
map.center.longitude = coordinate.longitude
getTz();
-
+
console.log(coordinate.latitude, coordinate.longitude)
}
}
@@ -199,7 +200,7 @@ Column {
}
Rectangle {
- width: parent.width
+ width: parent.width
height: 100
anchors.horizontalCenter: parent.horizontalCenter
diff --git a/src/modules/localeq/i18n.qml b/src/modules/localeq/i18n.qml
index a806d0174..6907b1ba8 100644
--- a/src/modules/localeq/i18n.qml
+++ b/src/modules/localeq/i18n.qml
@@ -32,10 +32,6 @@ Item {
anchors.fill: parent
}
- //Needs to come from Locale config
- property var confLang: "en_US.UTF8"
- property var confLocale: "nl_NL.UTF8"
-
Rectangle {
id: textArea
x: 28
@@ -57,7 +53,7 @@ Item {
width: 240
wrapMode: Text.WordWrap
text: qsTr("
Languages
- The system locale setting affects the language and character set for some command line user interface elements. The current setting is %1.").arg(confLang)
+ The system locale setting affects the language and character set for some command line user interface elements. The current setting is %1.").arg(config.currentLanguageCode)
font.pointSize: 10
}
}
@@ -76,10 +72,9 @@ Item {
id: list1
focus: true
- // bogus entries, need to come from Locale config
- model: ["en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8", "en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8", "en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8", "en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8", "en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8"]
+ model: config.supportedLocales
- currentIndex: 1
+ currentIndex: -1
highlight: Rectangle {
color: Kirigami.Theme.highlightColor
}
@@ -95,17 +90,17 @@ Item {
}
onClicked: {
list1.currentIndex = index
- confLang = list1.currentIndex
}
}
}
+ onCurrentItemChanged: { config.currentLanguageCode = model[currentIndex] } /* This works because model is a stringlist */
}
}
}
}
Column {
- id: i18n
+ id: lc_numeric
x: 430
y: 40
@@ -118,7 +113,7 @@ Item {
width: 240
wrapMode: Text.WordWrap
text: qsTr("
Locales
- The system locale setting affects the language and character set for some command line user interface elements. The current setting is %1.").arg(confLocale)
+ The system locale setting affects the numbers and dates format. The current setting is %1.").arg(config.currentLCCode)
font.pointSize: 10
}
}
@@ -138,10 +133,9 @@ Item {
width: 180; height: 200
focus: true
- // bogus entries, need to come from Locale config
- model: ["en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8", "en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8", "en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8", "en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8", "en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8"]
+ model: config.supportedLocales
- currentIndex: 2
+ currentIndex: -1
highlight: Rectangle {
color: Kirigami.Theme.highlightColor
}
@@ -154,11 +148,10 @@ Item {
cursorShape: Qt.PointingHandCursor
onClicked: {
list2.currentIndex = index
- confLocale = list1.currentIndex
}
}
}
- onCurrentItemChanged: console.debug(currentIndex)
+ onCurrentItemChanged: { config.currentLCCode = model[currentIndex]; } /* This works because model is a stringlist */
}
}
}
diff --git a/src/modules/localeq/localeq.qml b/src/modules/localeq/localeq.qml
index ffd87f5b5..49719db65 100644
--- a/src/modules/localeq/localeq.qml
+++ b/src/modules/localeq/localeq.qml
@@ -29,43 +29,13 @@ Page {
width: 800
height: 550
- property var confLang: "American English"
- property var confLocale: "Nederland"
- //Needs to come from .conf/geoip
- property var hasInternet: true
-
- function getInt(format) {
- var requestURL = "https://example.org/";
- var xhr = new XMLHttpRequest;
-
- xhr.onreadystatechange = function() {
- if (xhr.readyState === XMLHttpRequest.DONE) {
-
- if (xhr.status !== 200) {
- console.log("Disconnected!!");
- var connected = false
- hasInternet = connected
- return;
- }
-
- else {
- console.log("Connected!!");
- }
- }
- }
- xhr.open("GET", requestURL, true);
- xhr.send();
- }
- Component.onCompleted: {
- getInt();
- }
-
Loader {
id: image
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
height: parent.height / 1.28
- source: (hasInternet) ? "Map.qml" : "Offline.qml"
+ // Network is in io.calamares.core
+ source: Network.hasInternet ? "Map.qml" : "Offline.qml"
}
RowLayout {
@@ -95,7 +65,7 @@ Page {
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
- text: qsTr("System language set to %1").arg(confLang)
+ text: config.currentLanguageStatus
}
Kirigami.Separator {
Layout.fillWidth: true
@@ -103,7 +73,7 @@ Page {
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
- text: qsTr("Numbers and dates locale set to %1").arg(confLocale)
+ text: config.currentLCStatus
}
}
Button {
diff --git a/src/modules/machineid/machineid.conf b/src/modules/machineid/machineid.conf
index 97bd10a06..fa42655fd 100644
--- a/src/modules/machineid/machineid.conf
+++ b/src/modules/machineid/machineid.conf
@@ -15,8 +15,6 @@ dbus: true
# Whether /var/lib/dbus/machine-id should be a symlink to /etc/machine-id
# (ignored if dbus is false, or if there is no /etc/machine-id to point to).
dbus-symlink: true
-# this is a deprecated form of *dbus-symlink*
-symlink: true
# Whether to create an entropy file
entropy: false
diff --git a/src/modules/machineid/machineid.schema.yaml b/src/modules/machineid/machineid.schema.yaml
index 588a7fa4e..c5eb55b1b 100644
--- a/src/modules/machineid/machineid.schema.yaml
+++ b/src/modules/machineid/machineid.schema.yaml
@@ -4,6 +4,10 @@ $id: https://calamares.io/schemas/machineid
additionalProperties: false
type: object
properties:
- "systemd": { type: boolean, default: true }
- "dbus": { type: boolean, default: true }
- "symlink": { type: boolean, default: true }
+ systemd: { type: boolean, default: true }
+ dbus: { type: boolean, default: true }
+ "dbus-symlink": { type: boolean, default: true }
+ entropy: { type: boolean, default: false }
+ "entropy-copy": { type: boolean, default: false }
+ # Deprecated properties
+ symlink: { type: boolean, default: true }
diff --git a/src/modules/netinstall/CMakeLists.txt b/src/modules/netinstall/CMakeLists.txt
index 762e92985..0f2cf06f8 100644
--- a/src/modules/netinstall/CMakeLists.txt
+++ b/src/modules/netinstall/CMakeLists.txt
@@ -9,8 +9,6 @@ calamares_add_plugin( netinstall
PackageModel.cpp
UI
page_netinst.ui
- RESOURCES
- netinstall.qrc
LINK_PRIVATE_LIBRARIES
calamaresui
Qt5::Network
diff --git a/src/modules/partition/gui/ChoicePage.cpp b/src/modules/partition/gui/ChoicePage.cpp
index 69a740d20..1d0c96623 100644
--- a/src/modules/partition/gui/ChoicePage.cpp
+++ b/src/modules/partition/gui/ChoicePage.cpp
@@ -332,9 +332,7 @@ ChoicePage::setupChoices()
CALAMARES_RETRANSLATE(
m_somethingElseButton->setText( tr( "Manual partitioning "
- "You can create or resize partitions yourself."
- " Having a GPT partition table and fat32 512Mb /boot partition "
- "is a must for UEFI installs, either use an existing without formatting or create one." ) );
+ "You can create or resize partitions yourself." ) );
updateSwapChoicesTr( m_eraseSwapChoiceComboBox );
)
}
@@ -1444,46 +1442,75 @@ ChoicePage::currentChoice() const
return m_choice;
}
-
-void
-ChoicePage::updateNextEnabled()
+bool
+ChoicePage::calculateNextEnabled() const
{
bool enabled = false;
-
auto sm_p = m_beforePartitionBarsView ? m_beforePartitionBarsView->selectionModel() : nullptr;
switch ( m_choice )
{
case NoChoice:
- enabled = false;
- break;
+ cDebug() << "No partitioning choice";
+ return false;
case Replace:
case Alongside:
- enabled = sm_p && sm_p->currentIndex().isValid();
+ if ( !( sm_p && sm_p->currentIndex().isValid() ) )
+ {
+ cDebug() << "No partition selected";
+ return false;
+ }
+ enabled = true;
break;
case Erase:
case Manual:
enabled = true;
}
- if ( m_isEfi &&
- ( m_choice == Alongside ||
- m_choice == Replace ) )
+ if (!enabled)
{
- if ( m_core->efiSystemPartitions().count() == 0 )
- enabled = false;
+ cDebug() << "No valid choice made";
+ return false;
}
- if ( m_choice != Manual &&
- m_encryptWidget->isVisible() &&
- m_encryptWidget->state() == EncryptWidget::Encryption::Unconfirmed )
- enabled = false;
- if ( enabled == m_nextEnabled )
- return;
+ if ( m_isEfi && ( m_choice == Alongside || m_choice == Replace ) )
+ {
+ if ( m_core->efiSystemPartitions().count() == 0 )
+ {
+ cDebug() << "No EFI partition for alongside or replace";
+ return false;
+ }
+ }
- m_nextEnabled = enabled;
- emit nextStatusChanged( enabled );
+ if ( m_choice != Manual && m_encryptWidget->isVisible() )
+ {
+ switch ( m_encryptWidget->state() )
+ {
+ case EncryptWidget::Encryption::Unconfirmed:
+ cDebug() << "No passphrase provided";
+ return false;
+ case EncryptWidget::Encryption::Disabled:
+ case EncryptWidget::Encryption::Confirmed:
+ // Checkbox not checked, **or** passphrases match
+ break;
+ }
+ }
+
+ return true;
+}
+
+
+void
+ChoicePage::updateNextEnabled()
+{
+ bool enabled = calculateNextEnabled();
+
+ if ( enabled != m_nextEnabled )
+ {
+ m_nextEnabled = enabled;
+ emit nextStatusChanged( enabled );
+ }
}
void
diff --git a/src/modules/partition/gui/ChoicePage.h b/src/modules/partition/gui/ChoicePage.h
index 1ff8f0d40..a28892011 100644
--- a/src/modules/partition/gui/ChoicePage.h
+++ b/src/modules/partition/gui/ChoicePage.h
@@ -126,6 +126,7 @@ private slots:
void onEraseSwapChoiceChanged();
private:
+ bool calculateNextEnabled() const;
void updateNextEnabled();
void setupChoices();
QComboBox* createBootloaderComboBox( QWidget* parentButton );
diff --git a/src/modules/partition/gui/EncryptWidget.cpp b/src/modules/partition/gui/EncryptWidget.cpp
index 42a073db7..5e44c15fd 100644
--- a/src/modules/partition/gui/EncryptWidget.cpp
+++ b/src/modules/partition/gui/EncryptWidget.cpp
@@ -91,9 +91,39 @@ EncryptWidget::retranslate()
}
+///@brief Give @p label the @p pixmap from the standard-pixmaps
+static void
+applyPixmap( QLabel* label, CalamaresUtils::ImageType pixmap )
+{
+ label->setFixedWidth( label->height() );
+ label->setPixmap( CalamaresUtils::defaultPixmap( pixmap, CalamaresUtils::Original, label->size() ) );
+}
+
void
EncryptWidget::updateState()
{
+ if ( m_ui->m_passphraseLineEdit->isVisible() )
+ {
+ QString p1 = m_ui->m_passphraseLineEdit->text();
+ QString p2 = m_ui->m_confirmLineEdit->text();
+
+ if ( p1.isEmpty() && p2.isEmpty() )
+ {
+ applyPixmap( m_ui->m_iconLabel, CalamaresUtils::StatusWarning );
+ m_ui->m_iconLabel->setToolTip( tr( "Please enter the same passphrase in both boxes." ) );
+ }
+ else if ( p1 == p2 )
+ {
+ applyPixmap( m_ui->m_iconLabel, CalamaresUtils::StatusOk );
+ m_ui->m_iconLabel->setToolTip( QString() );
+ }
+ else
+ {
+ applyPixmap( m_ui->m_iconLabel, CalamaresUtils::StatusError );
+ m_ui->m_iconLabel->setToolTip( tr( "Please enter the same passphrase in both boxes." ) );
+ }
+ }
+
Encryption newState;
if ( m_ui->m_encryptCheckBox->isChecked() )
{
@@ -119,14 +149,6 @@ EncryptWidget::updateState()
}
}
-///@brief Give @p label the @p pixmap from the standard-pixmaps
-static void
-applyPixmap( QLabel* label, CalamaresUtils::ImageType pixmap )
-{
- label->setFixedWidth( label->height() );
- label->setPixmap( CalamaresUtils::defaultPixmap( pixmap, CalamaresUtils::Original, label->size() ) );
-}
-
void
EncryptWidget::onPassphraseEdited()
{
@@ -135,24 +157,6 @@ EncryptWidget::onPassphraseEdited()
m_ui->m_iconLabel->show();
}
- QString p1 = m_ui->m_passphraseLineEdit->text();
- QString p2 = m_ui->m_confirmLineEdit->text();
-
- m_ui->m_iconLabel->setToolTip( QString() );
- if ( p1.isEmpty() && p2.isEmpty() )
- {
- m_ui->m_iconLabel->clear();
- }
- else if ( p1 == p2 )
- {
- applyPixmap( m_ui->m_iconLabel, CalamaresUtils::Yes );
- }
- else
- {
- applyPixmap( m_ui->m_iconLabel, CalamaresUtils::No );
- m_ui->m_iconLabel->setToolTip( tr( "Please enter the same passphrase in both boxes." ) );
- }
-
updateState();
}
diff --git a/src/modules/partition/gui/PartitionViewStep.cpp b/src/modules/partition/gui/PartitionViewStep.cpp
index b0142a82a..900d10e57 100644
--- a/src/modules/partition/gui/PartitionViewStep.cpp
+++ b/src/modules/partition/gui/PartitionViewStep.cpp
@@ -107,8 +107,8 @@ PartitionViewStep::continueLoading()
m_waitingWidget->deleteLater();
m_waitingWidget = nullptr;
- connect( m_core, &PartitionCoreModule::hasRootMountPointChanged, this, &PartitionViewStep::nextStatusChanged );
- connect( m_choicePage, &ChoicePage::nextStatusChanged, this, &PartitionViewStep::nextStatusChanged );
+ connect( m_core, &PartitionCoreModule::hasRootMountPointChanged, this, &PartitionViewStep::nextPossiblyChanged );
+ connect( m_choicePage, &ChoicePage::nextStatusChanged, this, &PartitionViewStep::nextPossiblyChanged );
}
@@ -348,6 +348,11 @@ PartitionViewStep::isNextEnabled() const
return false;
}
+void
+PartitionViewStep::nextPossiblyChanged(bool)
+{
+ emit nextStatusChanged( isNextEnabled() );
+}
bool
PartitionViewStep::isBackEnabled() const
diff --git a/src/modules/partition/gui/PartitionViewStep.h b/src/modules/partition/gui/PartitionViewStep.h
index 63d11c816..47479a135 100644
--- a/src/modules/partition/gui/PartitionViewStep.h
+++ b/src/modules/partition/gui/PartitionViewStep.h
@@ -78,6 +78,9 @@ private:
void initPartitionCoreModule();
void continueLoading();
+ /// "slot" for changes to next-status from the KPMCore and ChoicePage
+ void nextPossiblyChanged( bool );
+
PartitionCoreModule* m_core;
QStackedWidget* m_widget;
ChoicePage* m_choicePage;
diff --git a/src/modules/preservefiles/CMakeLists.txt b/src/modules/preservefiles/CMakeLists.txt
index f6cd98008..571de31ca 100644
--- a/src/modules/preservefiles/CMakeLists.txt
+++ b/src/modules/preservefiles/CMakeLists.txt
@@ -4,7 +4,6 @@ calamares_add_plugin( preservefiles
TYPE job
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
- permissions.cpp
PreserveFiles.cpp
LINK_PRIVATE_LIBRARIES
calamares
diff --git a/src/modules/preservefiles/PreserveFiles.cpp b/src/modules/preservefiles/PreserveFiles.cpp
index 175f8e4f8..8352e861d 100644
--- a/src/modules/preservefiles/PreserveFiles.cpp
+++ b/src/modules/preservefiles/PreserveFiles.cpp
@@ -1,39 +1,28 @@
/* === This file is part of Calamares - ===
*
- * Copyright 2018, Adriaan de Groot
+ * SPDX-FileCopyrightText: 2018 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * License-Filename: LICENSE
*
- * Calamares 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 3 of the License, or
- * (at your option) any later version.
- *
- * Calamares 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 Calamares. If not, see .
*/
#include "PreserveFiles.h"
-#include "permissions.h"
-
#include "CalamaresVersion.h"
-#include "JobQueue.h"
#include "GlobalStorage.h"
-
+#include "JobQueue.h"
#include "utils/CalamaresUtilsSystem.h"
#include "utils/CommandList.h"
#include "utils/Logger.h"
+#include "utils/Permissions.h"
#include "utils/Units.h"
#include
using CalamaresUtils::operator""_MiB;
-QString targetPrefix()
+QString
+targetPrefix()
{
if ( CalamaresUtils::System::instance()->doChroot() )
{
@@ -42,9 +31,13 @@ QString targetPrefix()
{
QString r = gs->value( "rootMountPoint" ).toString();
if ( !r.isEmpty() )
+ {
return r;
+ }
else
+ {
cDebug() << "RootMountPoint is empty";
+ }
}
else
{
@@ -55,16 +48,21 @@ QString targetPrefix()
return QLatin1String( "/" );
}
-QString atReplacements( QString s )
+QString
+atReplacements( QString s )
{
Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
QString root( "/" );
QString user;
if ( gs && gs->contains( "rootMountPoint" ) )
+ {
root = gs->value( "rootMountPoint" ).toString();
+ }
if ( gs && gs->contains( "username" ) )
+ {
user = gs->value( "username" ).toString();
+ }
return s.replace( "@@ROOT@@", root ).replace( "@@USER@@", user );
}
@@ -74,9 +72,7 @@ PreserveFiles::PreserveFiles( QObject* parent )
{
}
-PreserveFiles::~PreserveFiles()
-{
-}
+PreserveFiles::~PreserveFiles() {}
QString
PreserveFiles::prettyName() const
@@ -107,8 +103,7 @@ copy_file( const QString& source, const QString& dest )
{
b = sourcef.read( 1_MiB );
destf.write( b );
- }
- while ( b.count() > 0 );
+ } while ( b.count() > 0 );
sourcef.close();
destf.close();
@@ -116,14 +111,19 @@ copy_file( const QString& source, const QString& dest )
return true;
}
-Calamares::JobResult PreserveFiles::exec()
+Calamares::JobResult
+PreserveFiles::exec()
{
if ( m_items.isEmpty() )
+ {
return Calamares::JobResult::error( tr( "No files configured to save for later." ) );
+ }
QString prefix = targetPrefix();
if ( !prefix.endsWith( '/' ) )
+ {
prefix.append( '/' );
+ }
int count = 0;
for ( const auto& it : m_items )
@@ -133,37 +133,34 @@ Calamares::JobResult PreserveFiles::exec()
QString dest = prefix + bare_dest;
if ( it.type == ItemType::Log )
+ {
source = Logger::logFile();
+ }
if ( it.type == ItemType::Config )
{
if ( Calamares::JobQueue::instance()->globalStorage()->save( dest ) )
+ {
cWarning() << "Could not write config for" << dest;
+ }
else
+ {
++count;
+ }
}
else if ( source.isEmpty() )
+ {
cWarning() << "Skipping unnamed source file for" << dest;
+ }
else
{
if ( copy_file( source, dest ) )
{
if ( it.perm.isValid() )
{
- auto s_p = CalamaresUtils::System::instance();
-
- int r;
-
- r = s_p->targetEnvCall( QStringList{ "chown", it.perm.username(), bare_dest } );
- if ( r )
- cWarning() << "Could not chown target" << bare_dest;
-
- r = s_p->targetEnvCall( QStringList{ "chgrp", it.perm.group(), bare_dest } );
- if ( r )
- cWarning() << "Could not chgrp target" << bare_dest;
-
- r = s_p->targetEnvCall( QStringList{ "chmod", it.perm.octal(), bare_dest } );
- if ( r )
- cWarning() << "Could not chmod target" << bare_dest;
+ if ( !it.perm.apply( CalamaresUtils::System::instance()->targetPath( bare_dest ) ) )
+ {
+ cWarning() << "Could not set attributes of" << bare_dest;
+ }
}
++count;
@@ -171,12 +168,13 @@ Calamares::JobResult PreserveFiles::exec()
}
}
- return count == m_items.count() ?
- Calamares::JobResult::ok() :
- Calamares::JobResult::error( tr( "Not all of the configured files could be preserved." ) );
+ return count == m_items.count()
+ ? Calamares::JobResult::ok()
+ : Calamares::JobResult::error( tr( "Not all of the configured files could be preserved." ) );
}
-void PreserveFiles::setConfigurationMap(const QVariantMap& configurationMap)
+void
+PreserveFiles::setConfigurationMap( const QVariantMap& configurationMap )
{
auto files = configurationMap[ "files" ];
if ( !files.isValid() )
@@ -193,7 +191,9 @@ void PreserveFiles::setConfigurationMap(const QVariantMap& configurationMap)
QString defaultPermissions = configurationMap[ "perm" ].toString();
if ( defaultPermissions.isEmpty() )
+ {
defaultPermissions = QStringLiteral( "root:root:0400" );
+ }
QVariantList l = files.toList();
unsigned int c = 0;
@@ -203,22 +203,23 @@ void PreserveFiles::setConfigurationMap(const QVariantMap& configurationMap)
{
QString filename = li.toString();
if ( !filename.isEmpty() )
- m_items.append( Item{ filename, filename, Permissions( defaultPermissions ), ItemType::Path } );
+ m_items.append( Item { filename, filename, CalamaresUtils::Permissions( defaultPermissions ), ItemType::Path } );
else
+ {
cDebug() << "Empty filename for preservefiles, item" << c;
+ }
}
else if ( li.type() == QVariant::Map )
{
const auto map = li.toMap();
QString dest = map[ "dest" ].toString();
QString from = map[ "from" ].toString();
- ItemType t =
- ( from == "log" ) ? ItemType::Log :
- ( from == "config" ) ? ItemType::Config :
- ItemType::None;
+ ItemType t = ( from == "log" ) ? ItemType::Log : ( from == "config" ) ? ItemType::Config : ItemType::None;
QString perm = map[ "perm" ].toString();
if ( perm.isEmpty() )
+ {
perm = defaultPermissions;
+ }
if ( dest.isEmpty() )
{
@@ -230,15 +231,16 @@ void PreserveFiles::setConfigurationMap(const QVariantMap& configurationMap)
}
else
{
- m_items.append( Item{ QString(), dest, Permissions( perm ), t } );
+ m_items.append( Item { QString(), dest, CalamaresUtils::Permissions( perm ), t } );
}
}
else
+ {
cDebug() << "Invalid type for preservefiles, item" << c;
+ }
++c;
}
}
-CALAMARES_PLUGIN_FACTORY_DEFINITION( PreserveFilesFactory, registerPlugin(); )
-
+CALAMARES_PLUGIN_FACTORY_DEFINITION( PreserveFilesFactory, registerPlugin< PreserveFiles >(); )
diff --git a/src/modules/preservefiles/PreserveFiles.h b/src/modules/preservefiles/PreserveFiles.h
index 587ac9bab..fdba933fa 100644
--- a/src/modules/preservefiles/PreserveFiles.h
+++ b/src/modules/preservefiles/PreserveFiles.h
@@ -1,35 +1,23 @@
/* === This file is part of Calamares - ===
*
- * Copyright 2018, Adriaan de Groot
+ * SPDX-FileCopyrightText: 2018 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * License-Filename: LICENSE
*
- * Calamares 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 3 of the License, or
- * (at your option) any later version.
- *
- * Calamares 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 Calamares. If not, see .
*/
#ifndef PRESERVEFILES_H
#define PRESERVEFILES_H
+#include "CppJob.h"
+#include "DllMacro.h"
+#include "utils/Permissions.h"
+#include "utils/PluginFactory.h"
+
#include
#include
#include
-#include "CppJob.h"
-#include "DllMacro.h"
-
-#include "utils/PluginFactory.h"
-
-#include "permissions.h"
-
class PLUGINDLLEXPORT PreserveFiles : public Calamares::CppJob
{
Q_OBJECT
@@ -40,15 +28,15 @@ class PLUGINDLLEXPORT PreserveFiles : public Calamares::CppJob
Path,
Log,
Config
- } ;
+ };
struct Item
{
QString source;
QString dest;
- Permissions perm;
+ CalamaresUtils::Permissions perm;
ItemType type;
- } ;
+ };
using ItemList = QList< Item >;
@@ -68,4 +56,4 @@ private:
CALAMARES_PLUGIN_FACTORY_DECLARATION( PreserveFilesFactory )
-#endif // PRESERVEFILES_H
+#endif // PRESERVEFILES_H
diff --git a/src/modules/preservefiles/permissions.cpp b/src/modules/preservefiles/permissions.cpp
deleted file mode 100644
index a3f8ac136..000000000
--- a/src/modules/preservefiles/permissions.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/* === This file is part of Calamares - ===
- *
- * Copyright (C) 2018 Scott Harvey
- *
- * 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 3 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, see .
- *
- */
-
-#include
-#include
-#include "permissions.h"
-
-Permissions::Permissions() :
- m_username(),
- m_group(),
- m_valid(false),
- m_value(0)
-{
-}
-
-
-Permissions::Permissions(QString p) : Permissions()
-{
- parsePermissions(p);
-}
-
-void Permissions::parsePermissions(const QString& p) {
-
- QStringList segments = p.split(":");
-
- if (segments.length() != 3) {
- m_valid = false;
- return;
- }
-
- if (segments[0].isEmpty() || segments[1].isEmpty()) {
- m_valid = false;
- return;
- }
-
- bool ok;
- int octal = segments[2].toInt(&ok, 8);
- if (!ok || octal == 0) {
- m_valid = false;
- return;
- } else {
- m_value = octal;
- }
-
- // We have exactly three segments and the third is valid octal,
- // so we can declare the string valid and set the user and group names
- m_valid = true;
- m_username = segments[0];
- m_group = segments[1];
-
- return;
-
-}
-
-
-
-
-
-
diff --git a/src/modules/preservefiles/permissions.h b/src/modules/preservefiles/permissions.h
deleted file mode 100644
index 4cb70a2c2..000000000
--- a/src/modules/preservefiles/permissions.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/* === This file is part of Calamares - ===
- *
- * Copyright (C) 2018 Scott Harvey
- *
- * 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 3 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, see .
- *
- */
-
-#ifndef PERMISSIONS_H
-#define PERMISSIONS_H
-
-#include
-
-/**
- * @brief The Permissions class takes a QString @p in the form of
- * ::, checks it for validity, and makes the three
- * components available indivdually.
- */
-class Permissions
-{
-
-public:
-
- /** @brief Constructor
- *
- * Splits the string @p at the colon (":") into separate elements for
- * , , and (permissions), where is returned as
- * an **octal** integer.
- */
- Permissions(QString p);
-
- /** @brief Default constructor of an invalid Permissions. */
- Permissions();
-
- bool isValid() const { return m_valid; }
- QString username() const { return m_username; }
- QString group() const { return m_group; }
- int value() const { return m_value; }
- QString octal() const { return QString::number( m_value, 8 ); }
-
-private:
- void parsePermissions(QString const &p);
-
- QString m_username;
- QString m_group;
- bool m_valid;
- int m_value;
-
-};
-
-#endif // PERMISSIONS_H
diff --git a/src/modules/users/CMakeLists.txt b/src/modules/users/CMakeLists.txt
index 1d969a93b..5fafcd4f3 100644
--- a/src/modules/users/CMakeLists.txt
+++ b/src/modules/users/CMakeLists.txt
@@ -28,6 +28,7 @@ calamares_add_plugin( users
UsersPage.cpp
SetHostNameJob.cpp
CheckPWQuality.cpp
+ Config.cpp
UI
page_usersetup.ui
RESOURCES
@@ -43,17 +44,31 @@ calamares_add_plugin( users
calamares_add_test(
userspasswordtest
SOURCES
- PasswordTests.cpp
+ TestPasswordJob.cpp
SetPasswordJob.cpp
LIBRARIES
${CRYPT_LIBRARIES}
)
calamares_add_test(
- userstest
+ userscreatetest
SOURCES
- Tests.cpp
+ TestCreateUserJob.cpp
+ CreateUserJob.cpp
+)
+
+calamares_add_test(
+ usershostnametest
+ SOURCES
+ TestSetHostNameJob.cpp
SetHostNameJob.cpp
LIBRARIES
Qt5::DBus
)
+
+calamares_add_test(
+ userstest
+ SOURCES
+ Tests.cpp
+ Config.cpp
+)
diff --git a/src/modules/users/CheckPWQuality.cpp b/src/modules/users/CheckPWQuality.cpp
index ab3eed84f..f06137c9b 100644
--- a/src/modules/users/CheckPWQuality.cpp
+++ b/src/modules/users/CheckPWQuality.cpp
@@ -55,7 +55,7 @@ DEFINE_CHECK_FUNC( minLength )
{
cDebug() << Logger::SubEntry << "minLength set to" << minLength;
checks.push_back( PasswordCheck( []() { return QCoreApplication::translate( "PWQ", "Password is too short" ); },
- [ minLength ]( const QString& s ) { return s.length() >= minLength; },
+ [minLength]( const QString& s ) { return s.length() >= minLength; },
PasswordCheck::Weight( 10 ) ) );
}
}
@@ -71,7 +71,7 @@ DEFINE_CHECK_FUNC( maxLength )
{
cDebug() << Logger::SubEntry << "maxLength set to" << maxLength;
checks.push_back( PasswordCheck( []() { return QCoreApplication::translate( "PWQ", "Password is too long" ); },
- [ maxLength ]( const QString& s ) { return s.length() <= maxLength; },
+ [maxLength]( const QString& s ) { return s.length() <= maxLength; },
PasswordCheck::Weight( 10 ) ) );
}
}
@@ -349,8 +349,8 @@ DEFINE_CHECK_FUNC( libpwquality )
/* Something actually added? */
if ( requirement_count )
{
- checks.push_back( PasswordCheck( [ settings ]() { return settings->explanation(); },
- [ settings ]( const QString& s ) {
+ checks.push_back( PasswordCheck( [settings]() { return settings->explanation(); },
+ [settings]( const QString& s ) {
int r = settings->check( s );
if ( r < 0 )
{
diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp
new file mode 100644
index 000000000..6c6bbe90a
--- /dev/null
+++ b/src/modules/users/Config.cpp
@@ -0,0 +1,380 @@
+/* === This file is part of Calamares - ===
+ *
+ * SPDX-FileCopyrightText: 2020 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * License-Filename: LICENSE
+ *
+ * Calamares 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Calamares 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 Calamares. If not, see .
+ */
+
+#include "Config.h"
+
+#include "GlobalStorage.h"
+#include "JobQueue.h"
+#include "utils/Logger.h"
+#include "utils/String.h"
+#include "utils/Variant.h"
+
+#include
+#include
+
+static const QRegExp USERNAME_RX( "^[a-z_][a-z0-9_-]*[$]?$" );
+static constexpr const int USERNAME_MAX_LENGTH = 31;
+
+static const QRegExp HOSTNAME_RX( "^[a-zA-Z0-9][-a-zA-Z0-9_]*$" );
+static constexpr const int HOSTNAME_MIN_LENGTH = 2;
+static constexpr const int HOSTNAME_MAX_LENGTH = 63;
+
+Config::Config( QObject* parent )
+ : QObject( parent )
+{
+}
+
+Config::~Config() {}
+
+void
+Config::setUserShell( const QString& shell )
+{
+ if ( !shell.isEmpty() && !shell.startsWith( '/' ) )
+ {
+ cWarning() << "User shell" << shell << "is not an absolute path.";
+ return;
+ }
+ // The shell is put into GS because the CreateUser job expects it there
+ Calamares::JobQueue::instance()->globalStorage()->insert( "userShell", shell );
+}
+
+static inline void
+insertInGlobalStorage( const QString& key, const QString& group )
+{
+ auto* gs = Calamares::JobQueue::instance()->globalStorage();
+ if ( !gs || group.isEmpty() )
+ {
+ return;
+ }
+ gs->insert( key, group );
+}
+
+void
+Config::setAutologinGroup( const QString& group )
+{
+ insertInGlobalStorage( QStringLiteral( "autologinGroup" ), group );
+ emit autologinGroupChanged( group );
+}
+
+void
+Config::setSudoersGroup( const QString& group )
+{
+ insertInGlobalStorage( QStringLiteral( "sudoersGroup" ), group );
+ emit sudoersGroupChanged( group );
+}
+
+
+void
+Config::setLoginName( const QString& login )
+{
+ if ( login != m_loginName )
+ {
+ Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
+ if ( login.isEmpty() )
+ {
+ gs->remove( "username" );
+ }
+ else
+ {
+ gs->insert( "username", login );
+ }
+
+ m_customLoginName = !login.isEmpty();
+ m_loginName = login;
+ emit loginNameChanged( login );
+ emit loginNameStatusChanged( loginNameStatus() );
+ }
+}
+
+const QStringList&
+Config::forbiddenLoginNames()
+{
+ static QStringList forbidden { "root" };
+ return forbidden;
+}
+
+QString
+Config::loginNameStatus() const
+{
+ // An empty login is "ok", even if it isn't really
+ if ( m_loginName.isEmpty() )
+ {
+ return QString();
+ }
+
+ if ( m_loginName.length() > USERNAME_MAX_LENGTH )
+ {
+ return tr( "Your username is too long." );
+ }
+ for ( const QString& badName : forbiddenLoginNames() )
+ {
+ if ( 0 == QString::compare( badName, m_loginName, Qt::CaseSensitive ) )
+ {
+ return tr( "'%1' is not allowed as username." ).arg( badName );
+ }
+ }
+
+ QRegExp validateFirstLetter( "^[a-z_]" );
+ if ( validateFirstLetter.indexIn( m_loginName ) != 0 )
+ {
+ return tr( "Your username must start with a lowercase letter or underscore." );
+ }
+ if ( !USERNAME_RX.exactMatch( m_loginName ) )
+ {
+ return tr( "Only lowercase letters, numbers, underscore and hyphen are allowed." );
+ }
+
+ return QString();
+}
+
+void
+Config::setHostName( const QString& host )
+{
+ if ( host != m_hostName )
+ {
+ Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
+ if ( host.isEmpty() )
+ {
+ gs->remove( "hostname" );
+ }
+ else
+ {
+ gs->insert( "hostname", host );
+ }
+
+ m_customHostName = !host.isEmpty();
+ m_hostName = host;
+ emit hostNameChanged( host );
+ emit hostNameStatusChanged( hostNameStatus() );
+ }
+}
+
+const QStringList&
+Config::forbiddenHostNames()
+{
+ static QStringList forbidden { "localhost" };
+ return forbidden;
+}
+
+QString
+Config::hostNameStatus() const
+{
+ // An empty hostname is "ok", even if it isn't really
+ if ( m_hostName.isEmpty() )
+ {
+ return QString();
+ }
+
+ if ( m_hostName.length() < HOSTNAME_MIN_LENGTH )
+ {
+ return tr( "Your hostname is too short." );
+ }
+ if ( m_hostName.length() > HOSTNAME_MAX_LENGTH )
+ {
+ return tr( "Your hostname is too long." );
+ }
+ for ( const QString& badName : forbiddenHostNames() )
+ {
+ if ( 0 == QString::compare( badName, m_hostName, Qt::CaseSensitive ) )
+ {
+ return tr( "'%1' is not allowed as hostname." ).arg( badName );
+ }
+ }
+
+ if ( !HOSTNAME_RX.exactMatch( m_hostName ) )
+ {
+ return tr( "Only letters, numbers, underscore and hyphen are allowed." );
+ }
+
+ return QString();
+}
+
+
+/** @brief Guess the machine's name
+ *
+ * If there is DMI data, use that; otherwise, just call the machine "-pc".
+ * Reads the DMI data just once.
+ */
+static QString
+guessProductName()
+{
+ static bool tried = false;
+ static QString dmiProduct;
+
+ if ( !tried )
+ {
+ // yes validateHostnameText() but these files can be a mess
+ QRegExp dmirx( "[^a-zA-Z0-9]", Qt::CaseInsensitive );
+ QFile dmiFile( QStringLiteral( "/sys/devices/virtual/dmi/id/product_name" ) );
+
+ if ( dmiFile.exists() && dmiFile.open( QIODevice::ReadOnly ) )
+ {
+ dmiProduct = QString::fromLocal8Bit( dmiFile.readAll().simplified().data() )
+ .toLower()
+ .replace( dmirx, " " )
+ .remove( ' ' );
+ }
+ if ( dmiProduct.isEmpty() )
+ {
+ dmiProduct = QStringLiteral( "pc" );
+ }
+ tried = true;
+ }
+ return dmiProduct;
+}
+
+static QString
+makeLoginNameSuggestion( const QStringList& parts )
+{
+ if ( parts.isEmpty() || parts.first().isEmpty() )
+ {
+ return QString();
+ }
+
+ QString usernameSuggestion = parts.first();
+ for ( int i = 1; i < parts.length(); ++i )
+ {
+ if ( !parts.value( i ).isEmpty() )
+ {
+ usernameSuggestion.append( parts.value( i ).at( 0 ) );
+ }
+ }
+
+ return USERNAME_RX.indexIn( usernameSuggestion ) != -1 ? usernameSuggestion : QString();
+}
+
+static QString
+makeHostnameSuggestion( const QStringList& parts )
+{
+ static const QRegExp HOSTNAME_RX( "^[a-zA-Z0-9][-a-zA-Z0-9_]*$" );
+ if ( parts.isEmpty() || parts.first().isEmpty() )
+ {
+ return QString();
+ }
+
+ QString productName = guessProductName();
+ QString hostnameSuggestion = QStringLiteral( "%1-%2" ).arg( parts.first() ).arg( productName );
+ return HOSTNAME_RX.indexIn( hostnameSuggestion ) != -1 ? hostnameSuggestion : QString();
+}
+
+void
+Config::setFullName( const QString& name )
+{
+ if ( name.isEmpty() && !m_fullName.isEmpty() )
+ {
+ if ( !m_customHostName )
+ {
+ setHostName( name );
+ }
+ if ( !m_customLoginName )
+ {
+ setLoginName( name );
+ }
+ m_fullName = name;
+ emit fullNameChanged( name );
+ }
+
+ if ( name != m_fullName )
+ {
+ m_fullName = name;
+ emit fullNameChanged( name );
+
+ // Build login and hostname, if needed
+ QRegExp rx( "[^a-zA-Z0-9 ]", Qt::CaseInsensitive );
+ QString cleanName = CalamaresUtils::removeDiacritics( name ).toLower().replace( rx, " " ).simplified();
+ QStringList cleanParts = cleanName.split( ' ' );
+
+ if ( !m_customLoginName )
+ {
+ QString login = makeLoginNameSuggestion( cleanParts );
+ if ( !login.isEmpty() && login != m_loginName )
+ {
+ m_loginName = login;
+ emit loginNameChanged( login );
+ emit loginNameStatusChanged( loginNameStatus() );
+ }
+ }
+ if ( !m_customHostName )
+ {
+ QString hostname = makeHostnameSuggestion( cleanParts );
+ if ( !hostname.isEmpty() && hostname != m_hostName )
+ {
+ m_hostName = hostname;
+ emit hostNameChanged( hostname );
+ emit hostNameStatusChanged( hostNameStatus() );
+ }
+ }
+ }
+}
+
+void
+Config::setAutoLogin( bool b )
+{
+ if ( b != m_doAutoLogin )
+ {
+ Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
+ if ( b )
+ {
+ gs->insert( "autologinUser", loginName() );
+ }
+ else
+ {
+ gs->remove( "autologinUser" );
+ }
+ m_doAutoLogin = b;
+ emit autoLoginChanged( b );
+ }
+}
+
+STATICTEST inline void
+setConfigurationDefaultGroups( const QVariantMap& map, QStringList& defaultGroups )
+{
+ // '#' is not a valid group name; use that to distinguish an empty-list
+ // in the configuration (which is a legitimate, if unusual, choice)
+ // from a bad or missing configuration value.
+ defaultGroups = CalamaresUtils::getStringList( map, QStringLiteral( "defaultGroups" ), QStringList { "#" } );
+ if ( defaultGroups.contains( QStringLiteral( "#" ) ) )
+ {
+ cWarning() << "Using fallback groups. Please check *defaultGroups* in users.conf";
+ defaultGroups = QStringList { "lp", "video", "network", "storage", "wheel", "audio" };
+ }
+}
+
+
+void
+Config::setConfigurationMap( const QVariantMap& configurationMap )
+{
+ QString shell( QLatin1String( "/bin/bash" ) ); // as if it's not set at all
+ if ( configurationMap.contains( "userShell" ) )
+ {
+ shell = CalamaresUtils::getString( configurationMap, "userShell" );
+ }
+ // Now it might be explicitly set to empty, which is ok
+ setUserShell( shell );
+
+ setAutologinGroup( CalamaresUtils::getString( configurationMap, "autologinGroup" ) );
+ setSudoersGroup( CalamaresUtils::getString( configurationMap, "sudoersGroup" ) );
+
+ setConfigurationDefaultGroups( configurationMap, m_defaultGroups );
+ m_doAutoLogin = CalamaresUtils::getBool( configurationMap, "doAutologin", false );
+
+ m_writeRootPassword = CalamaresUtils::getBool( configurationMap, "setRootPassword", true );
+ Calamares::JobQueue::instance()->globalStorage()->insert( "setRootPassword", m_writeRootPassword );
+}
diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h
new file mode 100644
index 000000000..b84dc5aaf
--- /dev/null
+++ b/src/modules/users/Config.h
@@ -0,0 +1,138 @@
+/* === This file is part of Calamares - ===
+ *
+ * SPDX-FileCopyrightText: 2020 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * License-Filename: LICENSE
+ *
+ * Calamares 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Calamares 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 Calamares. If not, see .
+ */
+
+#ifndef USERS_CONFIG_H
+#define USERS_CONFIG_H
+
+#include
+#include
+
+class Config : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY( QString userShell READ userShell WRITE setUserShell NOTIFY userShellChanged )
+
+ Q_PROPERTY( QString autologinGroup READ autologinGroup WRITE setAutologinGroup NOTIFY autologinGroupChanged )
+ Q_PROPERTY( QString sudoersGroup READ sudoersGroup WRITE setSudoersGroup NOTIFY sudoersGroupChanged )
+
+ Q_PROPERTY( bool doAutoLogin READ doAutoLogin WRITE setAutoLogin NOTIFY autoLoginChanged )
+
+ Q_PROPERTY( QString fullName READ fullName WRITE setFullName NOTIFY fullNameChanged )
+ Q_PROPERTY( QString loginName READ loginName WRITE setLoginName NOTIFY loginNameChanged )
+ Q_PROPERTY( QString loginNameStatus READ loginNameStatus NOTIFY loginNameStatusChanged )
+
+ Q_PROPERTY( QString hostName READ hostName WRITE setHostName NOTIFY hostNameChanged )
+ Q_PROPERTY( QString hostNameStatus READ hostNameStatus NOTIFY hostNameStatusChanged )
+
+public:
+ Config( QObject* parent = nullptr );
+ ~Config();
+
+ void setConfigurationMap( const QVariantMap& );
+
+ /** @brief Full path to the user's shell executable
+ *
+ * Typically this will be /bin/bash, but it can be set from
+ * the config file with the *userShell* setting.
+ */
+ QString userShell() const { return m_userShell; }
+
+ /// The group of which auto-login users must be a member
+ QString autologinGroup() const { return m_autologinGroup; }
+ /// The group of which users who can "sudo" must be a member
+ QString sudoersGroup() const { return m_sudoersGroup; }
+
+ /// The full (GECOS) name of the user
+ QString fullName() const { return m_fullName; }
+ /// The login name of the user
+ QString loginName() const { return m_loginName; }
+ /// Status message about login -- empty for "ok"
+ QString loginNameStatus() const;
+
+ /// The host name (name for the system)
+ QString hostName() const { return m_hostName; }
+ /// Status message about hostname -- empty for "ok"
+ QString hostNameStatus() const;
+
+ /// Should the user be automatically logged-in?
+ bool doAutoLogin() const { return m_doAutoLogin; }
+ /// Should the root password be written (if false, no password is set and the root account is disabled for login)
+ bool writeRootPassword() const { return m_writeRootPassword; }
+
+ const QStringList& defaultGroups() const { return m_defaultGroups; }
+
+ static const QStringList& forbiddenLoginNames();
+ static const QStringList& forbiddenHostNames();
+
+public Q_SLOTS:
+ /** @brief Sets the user's shell if possible
+ *
+ * If the path is empty, that's ok: no shell will be explicitly set,
+ * so the user will get whatever shell is set to default in the target.
+ *
+ * The given non-empty @p path must be an absolute path (for use inside
+ * the target system!); if it is not, the shell is not changed.
+ */
+ void setUserShell( const QString& path );
+
+ /// Sets the autologin group; empty is ignored
+ void setAutologinGroup( const QString& group );
+ /// Sets the sudoer group; empty is ignored
+ void setSudoersGroup( const QString& group );
+
+ /// Sets the full name, may guess a loginName
+ void setFullName( const QString& name );
+ /// Sets the login name (flags it as "custom")
+ void setLoginName( const QString& login );
+
+ /// Sets the host name (flags it as "custom")
+ void setHostName( const QString& host );
+
+ /// Sets the autologin flag
+ void setAutoLogin( bool b );
+
+signals:
+ void userShellChanged( const QString& );
+ void autologinGroupChanged( const QString& );
+ void sudoersGroupChanged( const QString& );
+ void fullNameChanged( const QString& );
+ void loginNameChanged( const QString& );
+ void loginNameStatusChanged( const QString& );
+ void hostNameChanged( const QString& );
+ void hostNameStatusChanged( const QString& );
+ void autoLoginChanged( bool );
+
+private:
+ QStringList m_defaultGroups;
+ QString m_userShell;
+ QString m_autologinGroup;
+ QString m_sudoersGroup;
+ QString m_fullName;
+ QString m_loginName;
+ QString m_hostName;
+ bool m_doAutoLogin = false;
+ bool m_writeRootPassword = true;
+
+ bool m_customLoginName = false;
+ bool m_customHostName = false;
+};
+
+#endif
diff --git a/src/modules/users/CreateUserJob.cpp b/src/modules/users/CreateUserJob.cpp
index 776b45ce9..a6812dd53 100644
--- a/src/modules/users/CreateUserJob.cpp
+++ b/src/modules/users/CreateUserJob.cpp
@@ -12,6 +12,7 @@
#include "JobQueue.h"
#include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h"
+#include "utils/Permissions.h"
#include
#include
@@ -54,6 +55,109 @@ CreateUserJob::prettyStatusMessage() const
return tr( "Creating user %1." ).arg( m_userName );
}
+STATICTEST QStringList
+groupsInTargetSystem( const QDir& targetRoot )
+{
+ QFileInfo groupsFi( targetRoot.absoluteFilePath( "etc/group" ) );
+ QFile groupsFile( groupsFi.absoluteFilePath() );
+ if ( !groupsFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
+ {
+ return QStringList();
+ }
+ QString groupsData = QString::fromLocal8Bit( groupsFile.readAll() );
+ QStringList groupsLines = groupsData.split( '\n' );
+ QStringList::iterator it = groupsLines.begin();
+ while ( it != groupsLines.end() )
+ {
+ if ( it->startsWith( '#' ) )
+ {
+ it = groupsLines.erase( it );
+ continue;
+ }
+ int indexOfFirstToDrop = it->indexOf( ':' );
+ if ( indexOfFirstToDrop < 1 )
+ {
+ it = groupsLines.erase( it );
+ continue;
+ }
+ it->truncate( indexOfFirstToDrop );
+ ++it;
+ }
+ return groupsLines;
+}
+
+static void
+ensureGroupsExistInTarget( const QStringList& wantedGroups, const QStringList& availableGroups )
+{
+ for ( const QString& group : wantedGroups )
+ {
+ if ( !availableGroups.contains( group ) )
+ {
+#ifdef __FreeBSD__
+ CalamaresUtils::System::instance()->targetEnvCall( { "pw", "groupadd", "-n", group } );
+#else
+ CalamaresUtils::System::instance()->targetEnvCall( { "groupadd", group } );
+#endif
+ }
+ }
+}
+
+static Calamares::JobResult
+createUser( const QString& loginName, const QString& fullName, const QString& shell )
+{
+ QStringList useraddCommand;
+#ifdef __FreeBSD__
+ useraddCommand << "pw"
+ << "useradd"
+ << "-n" << loginName << "-m"
+ << "-c" << fullName;
+ if ( !shell.isEmpty() )
+ {
+ useraddCommand << "-s" << shell;
+ }
+#else
+ useraddCommand << "useradd"
+ << "-m"
+ << "-U";
+ if ( !shell.isEmpty() )
+ {
+ useraddCommand << "-s" << shell;
+ }
+ useraddCommand << "-c" << fullName;
+ useraddCommand << loginName;
+#endif
+
+ auto commandResult = CalamaresUtils::System::instance()->targetEnvCommand( useraddCommand );
+ if ( commandResult.getExitCode() )
+ {
+ cError() << "useradd failed" << commandResult.getExitCode();
+ return commandResult.explainProcess( useraddCommand, std::chrono::seconds( 10 ) /* bogus timeout */ );
+ }
+ return Calamares::JobResult::ok();
+}
+
+static Calamares::JobResult
+setUserGroups( const QString& loginName, const QStringList& groups )
+{
+ QStringList setgroupsCommand;
+#ifdef __FreeBSD__
+ setgroupsCommand << "pw"
+ << "usermod"
+ << "-n" << loginName << "-G" << groups.join( ',' );
+#else
+ setgroupsCommand << "usermod"
+ << "-aG" << groups.join( ',' ) << loginName;
+#endif
+
+ auto commandResult = CalamaresUtils::System::instance()->targetEnvCommand( setgroupsCommand );
+ if ( commandResult.getExitCode() )
+ {
+ cError() << "usermod failed" << commandResult.getExitCode();
+ return commandResult.explainProcess( setgroupsCommand, std::chrono::seconds( 10 ) /* bogus timeout */ );
+ }
+ return Calamares::JobResult::ok();
+}
+
Calamares::JobResult
CreateUserJob::exec()
@@ -65,65 +169,37 @@ CreateUserJob::exec()
{
cDebug() << "[CREATEUSER]: preparing sudoers";
- QFileInfo sudoersFi( destDir.absoluteFilePath( "etc/sudoers.d/10-installer" ) );
+ QString sudoersLine = QString( "%%1 ALL=(ALL) ALL\n" ).arg( gs->value( "sudoersGroup" ).toString() );
+ auto fileResult
+ = CalamaresUtils::System::instance()->createTargetFile( QStringLiteral( "/etc/sudoers.d/10-installer" ),
+ sudoersLine.toUtf8().constData(),
+ CalamaresUtils::System::WriteMode::Overwrite );
- if ( !sudoersFi.absoluteDir().exists() )
+ if ( fileResult )
{
- return Calamares::JobResult::error( tr( "Sudoers dir is not writable." ) );
+ if ( CalamaresUtils::Permissions::apply( fileResult.path(), 0440 ) )
+ {
+ return Calamares::JobResult::error( tr( "Cannot chmod sudoers file." ) );
+ }
}
-
- QFile sudoersFile( sudoersFi.absoluteFilePath() );
- if ( !sudoersFile.open( QIODevice::WriteOnly | QIODevice::Text ) )
+ else
{
return Calamares::JobResult::error( tr( "Cannot create sudoers file for writing." ) );
}
-
- QString sudoersGroup = gs->value( "sudoersGroup" ).toString();
-
- QTextStream sudoersOut( &sudoersFile );
- sudoersOut << QString( "%%1 ALL=(ALL) ALL\n" ).arg( sudoersGroup );
-
- if ( QProcess::execute( "chmod", { "440", sudoersFi.absoluteFilePath() } ) )
- return Calamares::JobResult::error( tr( "Cannot chmod sudoers file." ) );
}
cDebug() << "[CREATEUSER]: preparing groups";
- QFileInfo groupsFi( destDir.absoluteFilePath( "etc/group" ) );
- QFile groupsFile( groupsFi.absoluteFilePath() );
- if ( !groupsFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
+ QStringList availableGroups = groupsInTargetSystem( destDir );
+ QStringList groupsForThisUser = m_defaultGroups;
+ if ( m_autologin && gs->contains( "autologinGroup" ) && !gs->value( "autologinGroup" ).toString().isEmpty() )
{
- return Calamares::JobResult::error( tr( "Cannot open groups file for reading." ) );
- }
- QString groupsData = QString::fromLocal8Bit( groupsFile.readAll() );
- QStringList groupsLines = groupsData.split( '\n' );
- for ( QStringList::iterator it = groupsLines.begin(); it != groupsLines.end(); ++it )
- {
- int indexOfFirstToDrop = it->indexOf( ':' );
- it->truncate( indexOfFirstToDrop );
+ groupsForThisUser << gs->value( "autologinGroup" ).toString();
}
+ ensureGroupsExistInTarget( m_defaultGroups, availableGroups );
- for ( const QString& group : m_defaultGroups )
- {
- if ( !groupsLines.contains( group ) )
- {
- CalamaresUtils::System::instance()->targetEnvCall( { "groupadd", group } );
- }
- }
-
- QString defaultGroups = m_defaultGroups.join( ',' );
- if ( m_autologin )
- {
- QString autologinGroup;
- if ( gs->contains( "autologinGroup" ) && !gs->value( "autologinGroup" ).toString().isEmpty() )
- {
- autologinGroup = gs->value( "autologinGroup" ).toString();
- CalamaresUtils::System::instance()->targetEnvCall( { "groupadd", autologinGroup } );
- defaultGroups.append( QString( ",%1" ).arg( autologinGroup ) );
- }
- }
-
- // If we're looking to reuse the contents of an existing /home
+ // If we're looking to reuse the contents of an existing /home.
+ // This GS setting comes from the **partitioning** module.
if ( gs->value( "reuseHome" ).toBool() )
{
QString shellFriendlyHome = "/home/" + m_userName;
@@ -133,6 +209,7 @@ CreateUserJob::exec()
QString backupDirName = "dotfiles_backup_" + QDateTime::currentDateTime().toString( "yyyy-MM-dd_HH-mm-ss" );
existingHome.mkdir( backupDirName );
+ // We need the extra `sh -c` here to ensure that we can expand the shell globs
CalamaresUtils::System::instance()->targetEnvCall(
{ "sh", "-c", "mv -f " + shellFriendlyHome + "/.* " + shellFriendlyHome + "/" + backupDirName } );
}
@@ -140,33 +217,21 @@ CreateUserJob::exec()
cDebug() << "[CREATEUSER]: creating user";
- QStringList useradd { "useradd", "-m", "-U" };
- QString shell = gs->value( "userShell" ).toString();
- if ( !shell.isEmpty() )
+ auto useraddResult = createUser( m_userName, m_fullName, gs->value( "userShell" ).toString() );
+ if ( !useraddResult )
{
- useradd << "-s" << shell;
- }
- useradd << "-c" << m_fullName;
- useradd << m_userName;
-
- auto commandResult = CalamaresUtils::System::instance()->targetEnvCommand( useradd );
- if ( commandResult.getExitCode() )
- {
- cError() << "useradd failed" << commandResult.getExitCode();
- return commandResult.explainProcess( useradd, std::chrono::seconds( 10 ) /* bogus timeout */ );
+ return useraddResult;
}
- commandResult
- = CalamaresUtils::System::instance()->targetEnvCommand( { "usermod", "-aG", defaultGroups, m_userName } );
- if ( commandResult.getExitCode() )
+ auto usergroupsResult = setUserGroups( m_userName, groupsForThisUser );
+ if ( !usergroupsResult )
{
- cError() << "usermod failed" << commandResult.getExitCode();
- return commandResult.explainProcess( "usermod", std::chrono::seconds( 10 ) /* bogus timeout */ );
+ return usergroupsResult;
}
QString userGroup = QString( "%1:%2" ).arg( m_userName ).arg( m_userName );
QString homeDir = QString( "/home/%1" ).arg( m_userName );
- commandResult = CalamaresUtils::System::instance()->targetEnvCommand( { "chown", "-R", userGroup, homeDir } );
+ auto commandResult = CalamaresUtils::System::instance()->targetEnvCommand( { "chown", "-R", userGroup, homeDir } );
if ( commandResult.getExitCode() )
{
cError() << "chown failed" << commandResult.getExitCode();
diff --git a/src/modules/users/PasswordTests.h b/src/modules/users/PasswordTests.h
deleted file mode 100644
index 3b4b5d201..000000000
--- a/src/modules/users/PasswordTests.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/* === This file is part of Calamares - ===
- *
- * Copyright 2017, Adriaan de Groot
- *
- * Calamares 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 3 of the License, or
- * (at your option) any later version.
- *
- * Calamares 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 Calamares. If not, see .
- */
-
-#ifndef PASSWORDTESTS_H
-#define PASSWORDTESTS_H
-
-#include
-
-class PasswordTests : public QObject
-{
- Q_OBJECT
-public:
- PasswordTests();
- ~PasswordTests() override;
-
-private Q_SLOTS:
- void initTestCase();
- void testSalt();
-};
-
-#endif
diff --git a/src/modules/users/TestCreateUserJob.cpp b/src/modules/users/TestCreateUserJob.cpp
new file mode 100644
index 000000000..610f84eef
--- /dev/null
+++ b/src/modules/users/TestCreateUserJob.cpp
@@ -0,0 +1,79 @@
+/* === This file is part of Calamares - ===
+ *
+ * SPDX-FileCopyrightText: 2020 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Calamares 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Calamares 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 Calamares. If not, see .
+ */
+
+#include "CreateUserJob.h"
+
+#include "utils/Logger.h"
+
+#include
+#include
+
+// Implementation details
+extern QStringList groupsInTargetSystem( const QDir& targetRoot ); // CreateUserJob
+
+class CreateUserTests : public QObject
+{
+ Q_OBJECT
+public:
+ CreateUserTests();
+ virtual ~CreateUserTests() {}
+
+private Q_SLOTS:
+ void initTestCase();
+
+ void testReadGroup();
+};
+
+CreateUserTests::CreateUserTests() {}
+
+void
+CreateUserTests::initTestCase()
+{
+ Logger::setupLogLevel( Logger::LOGDEBUG );
+ cDebug() << "Users test started.";
+}
+
+void
+CreateUserTests::testReadGroup()
+{
+ QDir root( "/" );
+ QVERIFY( root.exists() );
+
+ // Get the groups in the host system
+ QStringList groups = groupsInTargetSystem( root );
+ QVERIFY( groups.count() > 2 );
+#ifdef __FreeBSD__
+ QVERIFY( groups.contains( QStringLiteral( "wheel" ) ) );
+#else
+ QVERIFY( groups.contains( QStringLiteral( "root" ) ) );
+#endif
+ QVERIFY( groups.contains( QStringLiteral( "sys" ) ) );
+
+ for ( const QString& s : groups )
+ {
+ QVERIFY( !s.isEmpty() );
+ QVERIFY( !s.contains( '#' ) );
+ }
+}
+
+QTEST_GUILESS_MAIN( CreateUserTests )
+
+#include "utils/moc-warnings.h"
+
+#include "TestCreateUserJob.moc"
diff --git a/src/modules/users/PasswordTests.cpp b/src/modules/users/TestPasswordJob.cpp
similarity index 75%
rename from src/modules/users/PasswordTests.cpp
rename to src/modules/users/TestPasswordJob.cpp
index b33526162..8677f88c2 100644
--- a/src/modules/users/PasswordTests.cpp
+++ b/src/modules/users/TestPasswordJob.cpp
@@ -1,6 +1,7 @@
/* === This file is part of Calamares - ===
*
- * Copyright 2017, Adriaan de Groot
+ * SPDX-FileCopyrightText: 2017 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,15 +19,23 @@
#include "SetPasswordJob.h"
-#include "PasswordTests.h"
-
#include
-QTEST_GUILESS_MAIN( PasswordTests )
+class PasswordTests : public QObject
+{
+ Q_OBJECT
+public:
+ PasswordTests();
+ ~PasswordTests() override;
-PasswordTests::PasswordTests() { }
+private Q_SLOTS:
+ void initTestCase();
+ void testSalt();
+};
-PasswordTests::~PasswordTests() { }
+PasswordTests::PasswordTests() {}
+
+PasswordTests::~PasswordTests() {}
void
PasswordTests::initTestCase()
@@ -48,3 +57,9 @@ PasswordTests::testSalt()
QVERIFY( s.endsWith( '$' ) );
qDebug() << "Obtained salt" << s;
}
+
+QTEST_GUILESS_MAIN( PasswordTests )
+
+#include "utils/moc-warnings.h"
+
+#include "TestPasswordJob.moc"
diff --git a/src/modules/users/TestSetHostNameJob.cpp b/src/modules/users/TestSetHostNameJob.cpp
new file mode 100644
index 000000000..0e887a5f1
--- /dev/null
+++ b/src/modules/users/TestSetHostNameJob.cpp
@@ -0,0 +1,146 @@
+/* === This file is part of Calamares - ===
+ *
+ * SPDX-FileCopyrightText: 2020 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Calamares 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Calamares 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 Calamares. If not, see .
+ */
+
+#include "SetHostNameJob.h"
+
+// Implementation details
+extern bool setFileHostname( const QString& );
+extern bool writeFileEtcHosts( const QString& );
+extern bool setSystemdHostname( const QString& );
+
+#include "GlobalStorage.h"
+#include "JobQueue.h"
+#include "utils/CalamaresUtilsSystem.h"
+#include "utils/Logger.h"
+#include "utils/Yaml.h"
+
+#include
+#include
+
+class UsersTests : public QObject
+{
+ Q_OBJECT
+public:
+ UsersTests();
+ virtual ~UsersTests() {}
+
+private Q_SLOTS:
+ void initTestCase();
+
+ void testEtcHostname();
+ void testEtcHosts();
+ void testHostnamed();
+
+ void cleanup();
+
+private:
+ QTemporaryDir m_dir;
+};
+
+UsersTests::UsersTests()
+ : m_dir( QStringLiteral( "/tmp/calamares-usertest" ) )
+{
+}
+
+void
+UsersTests::initTestCase()
+{
+ Logger::setupLogLevel( Logger::LOGDEBUG );
+ cDebug() << "Users test started.";
+ cDebug() << "Test dir" << m_dir.path();
+
+ // Ensure we have a system object, expect it to be a "bogus" one
+ CalamaresUtils::System* system = CalamaresUtils::System::instance();
+ QVERIFY( system );
+ QVERIFY( system->doChroot() );
+
+ // Ensure we have a system-wide GlobalStorage with /tmp as root
+ if ( !Calamares::JobQueue::instance() )
+ {
+ cDebug() << "Creating new JobQueue";
+ (void)new Calamares::JobQueue();
+ }
+ Calamares::GlobalStorage* gs
+ = Calamares::JobQueue::instance() ? Calamares::JobQueue::instance()->globalStorage() : nullptr;
+ QVERIFY( gs );
+ gs->insert( "rootMountPoint", m_dir.path() );
+}
+
+void
+UsersTests::testEtcHostname()
+{
+ cDebug() << "Test dir" << m_dir.path();
+
+ QVERIFY( QFile::exists( m_dir.path() ) );
+ QVERIFY( !QFile::exists( m_dir.filePath( "etc" ) ) );
+
+ // Doesn't create intermediate directories
+ QVERIFY( !setFileHostname( QStringLiteral( "tubophone.calamares.io" ) ) );
+
+ QVERIFY( CalamaresUtils::System::instance()->createTargetDirs( "/etc" ) );
+ QVERIFY( QFile::exists( m_dir.filePath( "etc" ) ) );
+
+ // Does write the file
+ QVERIFY( setFileHostname( QStringLiteral( "tubophone.calamares.io" ) ) );
+ QVERIFY( QFile::exists( m_dir.filePath( "etc/hostname" ) ) );
+
+ // 22 for the test string, above, and 1 for the newline
+ QCOMPARE( QFileInfo( m_dir.filePath( "etc/hostname" ) ).size(), 22 + 1 );
+}
+
+void
+UsersTests::testEtcHosts()
+{
+ // Assume previous tests did their work
+ QVERIFY( QFile::exists( m_dir.path() ) );
+ QVERIFY( QFile::exists( m_dir.filePath( "etc" ) ) );
+
+ QVERIFY( writeFileEtcHosts( QStringLiteral( "tubophone.calamares.io" ) ) );
+ QVERIFY( QFile::exists( m_dir.filePath( "etc/hosts" ) ) );
+ // The skeleton contains %1 which has the hostname substituted in, so we lose two,
+ // and the rest of the blabla is 150 (according to Python)
+ QCOMPARE( QFileInfo( m_dir.filePath( "etc/hosts" ) ).size(), 150 + 22 - 2 );
+}
+
+void
+UsersTests::testHostnamed()
+{
+ // Since the service might not be running (e.g. non-systemd systems,
+ // FreeBSD, docker, ..) we're not going to fail a test here.
+ // There's also the permissions problem to think of.
+ QEXPECT_FAIL( "", "Hostname changes are access-controlled", Continue );
+ QVERIFY( setSystemdHostname( "tubophone.calamares.io" ) );
+}
+
+
+void
+UsersTests::cleanup()
+{
+ if ( QTest::currentTestFailed() )
+ {
+ m_dir.setAutoRemove( false );
+ }
+}
+
+
+QTEST_GUILESS_MAIN( UsersTests )
+
+#include "utils/moc-warnings.h"
+
+#include "TestSetHostNameJob.moc"
diff --git a/src/modules/users/Tests.cpp b/src/modules/users/Tests.cpp
index 75c5e6d5f..a4ee45fe2 100644
--- a/src/modules/users/Tests.cpp
+++ b/src/modules/users/Tests.cpp
@@ -1,6 +1,7 @@
/* === This file is part of Calamares - ===
*
- * Copyright 2020, Adriaan de Groot
+ * SPDX-FileCopyrightText: 2020 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,129 +17,96 @@
* along with Calamares. If not, see .
*/
-#include "SetHostNameJob.h"
+#include "Config.h"
-// Implementation details
-extern bool setFileHostname( const QString& );
-extern bool writeFileEtcHosts( const QString& );
-extern bool setSystemdHostname( const QString& );
-
-#include "GlobalStorage.h"
-#include "JobQueue.h"
-#include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h"
-#include "utils/Yaml.h"
-#include
#include
-class UsersTests : public QObject
+// Implementation details
+extern void setConfigurationDefaultGroups( const QVariantMap& map, QStringList& defaultGroups );
+
+/** @brief Test Config object methods and internals
+ *
+ */
+class UserTests : public QObject
{
Q_OBJECT
public:
- UsersTests();
- virtual ~UsersTests() { }
+ UserTests();
+ virtual ~UserTests() {}
private Q_SLOTS:
void initTestCase();
- void testEtcHostname();
- void testEtcHosts();
- void testHostnamed();
-
- void cleanup();
-
-private:
- QTemporaryDir m_dir;
+ void testDefaultGroups();
};
-UsersTests::UsersTests()
- : m_dir( QStringLiteral( "/tmp/calamares-usertest" ) )
-{
-}
+UserTests::UserTests() {}
void
-UsersTests::initTestCase()
+UserTests::initTestCase()
{
Logger::setupLogLevel( Logger::LOGDEBUG );
cDebug() << "Users test started.";
- cDebug() << "Test dir" << m_dir.path();
+}
- // Ensure we have a system object, expect it to be a "bogus" one
- CalamaresUtils::System* system = CalamaresUtils::System::instance();
- QVERIFY( system );
- QVERIFY( system->doChroot() );
-
- // Ensure we have a system-wide GlobalStorage with /tmp as root
- if ( !Calamares::JobQueue::instance() )
+void
+UserTests::testDefaultGroups()
+{
{
- cDebug() << "Creating new JobQueue";
- (void)new Calamares::JobQueue();
+ QStringList groups;
+ QVariantMap hweelGroup;
+ QVERIFY( groups.isEmpty() );
+ hweelGroup.insert( "defaultGroups", QStringList { "hweel" } );
+ setConfigurationDefaultGroups( hweelGroup, groups );
+ QCOMPARE( groups.count(), 1 );
+ QVERIFY( groups.contains( "hweel" ) );
}
- Calamares::GlobalStorage* gs
- = Calamares::JobQueue::instance() ? Calamares::JobQueue::instance()->globalStorage() : nullptr;
- QVERIFY( gs );
- gs->insert( "rootMountPoint", m_dir.path() );
-}
-void
-UsersTests::testEtcHostname()
-{
- cDebug() << "Test dir" << m_dir.path();
-
- QVERIFY( QFile::exists( m_dir.path() ) );
- QVERIFY( !QFile::exists( m_dir.filePath( "etc" ) ) );
-
- // Doesn't create intermediate directories
- QVERIFY( !setFileHostname( QStringLiteral( "tubophone.calamares.io" ) ) );
-
- QVERIFY( CalamaresUtils::System::instance()->createTargetDirs( "/etc" ) );
- QVERIFY( QFile::exists( m_dir.filePath( "etc" ) ) );
-
- // Does write the file
- QVERIFY( setFileHostname( QStringLiteral( "tubophone.calamares.io" ) ) );
- QVERIFY( QFile::exists( m_dir.filePath( "etc/hostname" ) ) );
-
- // 22 for the test string, above, and 1 for the newline
- QCOMPARE( QFileInfo( m_dir.filePath( "etc/hostname" ) ).size(), 22 + 1 );
-}
-
-void
-UsersTests::testEtcHosts()
-{
- // Assume previous tests did their work
- QVERIFY( QFile::exists( m_dir.path() ) );
- QVERIFY( QFile::exists( m_dir.filePath( "etc" ) ) );
-
- QVERIFY( writeFileEtcHosts( QStringLiteral( "tubophone.calamares.io" ) ) );
- QVERIFY( QFile::exists( m_dir.filePath( "etc/hosts" ) ) );
- // The skeleton contains %1 which has the hostname substituted in, so we lose two,
- // and the rest of the blabla is 150 (according to Python)
- QCOMPARE( QFileInfo( m_dir.filePath( "etc/hosts" ) ).size(), 150 + 22 - 2 );
-}
-
-void
-UsersTests::testHostnamed()
-{
- // Since the service might not be running (e.g. non-systemd systems,
- // FreeBSD, docker, ..) we're not going to fail a test here.
- // There's also the permissions problem to think of.
- QEXPECT_FAIL( "", "Hostname changes are access-controlled", Continue );
- QVERIFY( setSystemdHostname( "tubophone.calamares.io" ) );
-}
-
-
-void
-UsersTests::cleanup()
-{
- if ( QTest::currentTestFailed() )
{
- m_dir.setAutoRemove( false );
+ QStringList desired { "wheel", "root", "operator" };
+ QStringList groups;
+ QVariantMap threeGroup;
+ QVERIFY( groups.isEmpty() );
+ threeGroup.insert( "defaultGroups", desired );
+ setConfigurationDefaultGroups( threeGroup, groups );
+ QCOMPARE( groups.count(), 3 );
+ QVERIFY( !groups.contains( "hweel" ) );
+ QCOMPARE( groups, desired );
+ }
+
+ {
+ QStringList groups;
+ QVariantMap explicitEmpty;
+ QVERIFY( groups.isEmpty() );
+ explicitEmpty.insert( "defaultGroups", QStringList() );
+ setConfigurationDefaultGroups( explicitEmpty, groups );
+ QCOMPARE( groups.count(), 0 );
+ }
+
+ {
+ QStringList groups;
+ QVariantMap missing;
+ QVERIFY( groups.isEmpty() );
+ setConfigurationDefaultGroups( missing, groups );
+ QCOMPARE( groups.count(), 6 ); // because of fallback!
+ QVERIFY( groups.contains( "lp" ) );
+ }
+
+ {
+ QStringList groups;
+ QVariantMap typeMismatch;
+ QVERIFY( groups.isEmpty() );
+ typeMismatch.insert( "defaultGroups", 1 );
+ setConfigurationDefaultGroups( typeMismatch, groups );
+ QCOMPARE( groups.count(), 6 ); // because of fallback!
+ QVERIFY( groups.contains( "lp" ) );
}
}
-QTEST_GUILESS_MAIN( UsersTests )
+QTEST_GUILESS_MAIN( UserTests )
#include "utils/moc-warnings.h"
diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp
index 9c7ce6f7b..c4502256a 100644
--- a/src/modules/users/UsersPage.cpp
+++ b/src/modules/users/UsersPage.cpp
@@ -25,16 +25,12 @@
#include "UsersPage.h"
+#include "Config.h"
#include "ui_page_usersetup.h"
-#include "CreateUserJob.h"
-#include "SetHostNameJob.h"
-#include "SetPasswordJob.h"
-
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "Settings.h"
-
#include "utils/CalamaresUtilsGui.h"
#include "utils/Logger.h"
#include "utils/Retranslator.h"
@@ -44,14 +40,6 @@
#include
#include
#include
-#include
-#include
-
-static const QRegExp USERNAME_RX( "^[a-z_][a-z0-9_-]*[$]?$" );
-static const QRegExp HOSTNAME_RX( "^[a-zA-Z0-9][-a-zA-Z0-9_]*$" );
-static constexpr const int USERNAME_MAX_LENGTH = 31;
-static constexpr const int HOSTNAME_MIN_LENGTH = 2;
-static constexpr const int HOSTNAME_MAX_LENGTH = 63;
/** @brief How bad is the error for labelError() ? */
enum class Badness
@@ -79,54 +67,87 @@ labelOk( QLabel* pix, QLabel* label )
pix->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes, CalamaresUtils::Original, label->size() ) );
}
-UsersPage::UsersPage( QWidget* parent )
+/** Indicate error, update @p ok based on @p status */
+static inline void
+labelStatus( QLabel* pix, QLabel* label, const QString& value, const QString& status, bool& ok )
+{
+ if ( status.isEmpty() )
+ {
+ if ( value.isEmpty() )
+ {
+ // This is different from labelOK() because no checkmark is shown
+ label->clear();
+ pix->clear();
+ ok = false;
+ }
+ else
+ {
+ labelOk( pix, label );
+ ok = true;
+ }
+ }
+ else
+ {
+ labelError( pix, label, status );
+ ok = false;
+ }
+}
+
+UsersPage::UsersPage( Config* config, QWidget* parent )
: QWidget( parent )
, ui( new Ui::Page_UserSetup )
+ , m_config( config )
, m_readyFullName( false )
, m_readyUsername( false )
, m_readyHostname( false )
, m_readyPassword( false )
, m_readyRootPassword( false )
- , m_writeRootPassword( true )
{
ui->setupUi( this );
// Connect signals and slots
- connect( ui->textBoxFullName, &QLineEdit::textEdited, this, &UsersPage::onFullNameTextEdited );
- connect( ui->textBoxUsername, &QLineEdit::textEdited, this, &UsersPage::onUsernameTextEdited );
- connect( ui->textBoxHostname, &QLineEdit::textEdited, this, &UsersPage::onHostnameTextEdited );
connect( ui->textBoxUserPassword, &QLineEdit::textChanged, this, &UsersPage::onPasswordTextChanged );
connect( ui->textBoxUserVerifiedPassword, &QLineEdit::textChanged, this, &UsersPage::onPasswordTextChanged );
connect( ui->textBoxRootPassword, &QLineEdit::textChanged, this, &UsersPage::onRootPasswordTextChanged );
connect( ui->textBoxVerifiedRootPassword, &QLineEdit::textChanged, this, &UsersPage::onRootPasswordTextChanged );
- connect( ui->checkBoxValidatePassword, &QCheckBox::stateChanged, this, [ this ]( int ) {
+ connect( ui->checkBoxValidatePassword, &QCheckBox::stateChanged, this, [this]( int ) {
onPasswordTextChanged( ui->textBoxUserPassword->text() );
onRootPasswordTextChanged( ui->textBoxRootPassword->text() );
checkReady( isReady() );
} );
- connect( ui->checkBoxReusePassword, &QCheckBox::stateChanged, this, [ this ]( int checked ) {
+ connect( ui->checkBoxReusePassword, &QCheckBox::stateChanged, this, [this]( const int checked ) {
/* When "reuse" is checked, hide the fields for explicitly
* entering the root password. However, if we're going to
* disable the root password anyway, hide them all regardless of
* the checkbox -- so when writeRoot is false, checked needs
* to be true, to hide them all.
*/
- if ( !m_writeRootPassword )
- {
- checked = true;
- }
- ui->labelChooseRootPassword->setVisible( !checked );
- ui->labelRootPassword->setVisible( !checked );
- ui->labelRootPasswordError->setVisible( !checked );
- ui->textBoxRootPassword->setVisible( !checked );
- ui->textBoxVerifiedRootPassword->setVisible( !checked );
+ const bool visible = m_config->writeRootPassword() ? !checked : false;
+ ui->labelChooseRootPassword->setVisible( visible );
+ ui->labelRootPassword->setVisible( visible );
+ ui->labelRootPasswordError->setVisible( visible );
+ ui->textBoxRootPassword->setVisible( visible );
+ ui->textBoxVerifiedRootPassword->setVisible( visible );
checkReady( isReady() );
} );
- m_customUsername = false;
- m_customHostname = false;
+ connect( ui->textBoxFullName, &QLineEdit::textEdited, config, &Config::setFullName );
+ connect( config, &Config::fullNameChanged, this, &UsersPage::onFullNameTextEdited );
- setWriteRootPassword( true );
+ connect( ui->textBoxHostName, &QLineEdit::textEdited, config, &Config::setHostName );
+ connect( config, &Config::hostNameChanged, ui->textBoxHostName, &QLineEdit::setText );
+ connect( config, &Config::hostNameStatusChanged, this, &UsersPage::reportHostNameStatus );
+
+ connect( ui->textBoxLoginName, &QLineEdit::textEdited, config, &Config::setLoginName );
+ connect( config, &Config::loginNameChanged, ui->textBoxLoginName, &QLineEdit::setText );
+ connect( config, &Config::loginNameStatusChanged, this, &UsersPage::reportLoginNameStatus );
+
+ connect( ui->checkBoxDoAutoLogin, &QCheckBox::stateChanged, this, [this]( int checked ) {
+ m_config->setAutoLogin( checked != Qt::Unchecked );
+ } );
+ connect( config, &Config::autoLoginChanged, ui->checkBoxDoAutoLogin, &QCheckBox::setChecked );
+
+ ui->checkBoxReusePassword->setVisible( m_config->writeRootPassword() );
ui->checkBoxReusePassword->setChecked( true );
ui->checkBoxValidatePassword->setChecked( true );
@@ -146,15 +167,15 @@ UsersPage::retranslate()
ui->retranslateUi( this );
if ( Calamares::Settings::instance()->isSetupMode() )
{
- ui->textBoxUsername->setToolTip( tr( "If more than one person will "
- "use this computer, you can create multiple "
- "accounts after setup." ) );
+ ui->textBoxLoginName->setToolTip( tr( "If more than one person will "
+ "use this computer, you can create multiple "
+ "accounts after setup." ) );
}
else
{
- ui->textBoxUsername->setToolTip( tr( "If more than one person will "
- "use this computer, you can create multiple "
- "accounts after installation." ) );
+ ui->textBoxLoginName->setToolTip( tr( "If more than one person will "
+ "use this computer, you can create multiple "
+ "accounts after installation." ) );
}
// Re-do password checks (with output messages) as well.
// .. the password-checking methods get their values from the text boxes,
@@ -165,27 +186,19 @@ UsersPage::retranslate()
bool
-UsersPage::isReady()
+UsersPage::isReady() const
{
bool readyFields = m_readyFullName && m_readyHostname && m_readyPassword && m_readyUsername;
- if ( !m_writeRootPassword || ui->checkBoxReusePassword->isChecked() )
- {
- return readyFields;
- }
-
- return readyFields && m_readyRootPassword;
-}
-
-QString
-UsersPage::getHostname() const
-{
- return ui->textBoxHostname->text();
+ // If we're going to write a root password, we need a valid one (or reuse the user's password)
+ readyFields
+ &= m_config->writeRootPassword() ? ( m_readyRootPassword || ui->checkBoxReusePassword->isChecked() ) : true;
+ return readyFields;
}
QString
UsersPage::getRootPassword() const
{
- if ( m_writeRootPassword )
+ if ( m_config->writeRootPassword() )
{
if ( ui->checkBoxReusePassword->isChecked() )
{
@@ -205,42 +218,24 @@ UsersPage::getRootPassword() const
QPair< QString, QString >
UsersPage::getUserPassword() const
{
- return QPair< QString, QString >( ui->textBoxUsername->text(), ui->textBoxUserPassword->text() );
+ return QPair< QString, QString >( m_config->loginName(), ui->textBoxUserPassword->text() );
}
-QList< Calamares::job_ptr >
-UsersPage::createJobs( const QStringList& defaultGroupsList )
+void
+UsersPage::fillGlobalStorage() const
{
- QList< Calamares::job_ptr > list;
if ( !isReady() )
{
- return list;
+ return;
}
Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
- Calamares::Job* j;
- j = new CreateUserJob( ui->textBoxUsername->text(),
- ui->textBoxFullName->text().isEmpty() ? ui->textBoxUsername->text()
- : ui->textBoxFullName->text(),
- ui->checkBoxAutoLogin->isChecked(),
- defaultGroupsList );
- list.append( Calamares::job_ptr( j ) );
-
- if ( m_writeRootPassword )
+ if ( m_config->writeRootPassword() )
{
gs->insert( "reuseRootPassword", ui->checkBoxReusePassword->isChecked() );
}
- gs->insert( "hostname", ui->textBoxHostname->text() );
- if ( ui->checkBoxAutoLogin->isChecked() )
- {
- gs->insert( "autologinUser", ui->textBoxUsername->text() );
- }
-
- gs->insert( "username", ui->textBoxUsername->text() );
gs->insert( "password", CalamaresUtils::obscure( ui->textBoxUserPassword->text() ) );
-
- return list;
}
@@ -254,215 +249,23 @@ UsersPage::onActivate()
void
-UsersPage::setWriteRootPassword( bool write )
+UsersPage::onFullNameTextEdited( const QString& fullName )
{
- m_writeRootPassword = write;
- ui->checkBoxReusePassword->setVisible( write );
-}
-
-
-void
-UsersPage::onFullNameTextEdited( const QString& textRef )
-{
- if ( textRef.isEmpty() )
- {
- ui->labelFullNameError->clear();
- ui->labelFullName->clear();
- if ( !m_customUsername )
- {
- ui->textBoxUsername->clear();
- }
- if ( !m_customHostname )
- {
- ui->textBoxHostname->clear();
- }
- m_readyFullName = false;
- }
- else
- {
- ui->labelFullName->setPixmap(
- CalamaresUtils::defaultPixmap( CalamaresUtils::Yes, CalamaresUtils::Original, ui->labelFullName->size() ) );
- m_readyFullName = true;
- fillSuggestions();
- }
+ labelStatus( ui->labelFullName, ui->labelFullNameError, fullName, QString(), m_readyFullName );
checkReady( isReady() );
}
-/** @brief Guess the machine's name
- *
- * If there is DMI data, use that; otherwise, just call the machine "-pc".
- * Reads the DMI data just once.
- */
-static QString
-guessProductName()
-{
- static bool tried = false;
- static QString dmiProduct;
-
- if ( !tried )
- {
- // yes validateHostnameText() but these files can be a mess
- QRegExp dmirx( "[^a-zA-Z0-9]", Qt::CaseInsensitive );
- QFile dmiFile( QStringLiteral( "/sys/devices/virtual/dmi/id/product_name" ) );
-
- if ( dmiFile.exists() && dmiFile.open( QIODevice::ReadOnly ) )
- {
- dmiProduct = QString::fromLocal8Bit( dmiFile.readAll().simplified().data() )
- .toLower()
- .replace( dmirx, " " )
- .remove( ' ' );
- }
- if ( dmiProduct.isEmpty() )
- {
- dmiProduct = QStringLiteral( "-pc" );
- }
- tried = true;
- }
- return dmiProduct;
-}
-
void
-UsersPage::fillSuggestions()
+UsersPage::reportLoginNameStatus( const QString& status )
{
- QString fullName = ui->textBoxFullName->text();
- QRegExp rx( "[^a-zA-Z0-9 ]", Qt::CaseInsensitive );
- QString cleanName = CalamaresUtils::removeDiacritics( fullName ).toLower().replace( rx, " " ).simplified();
- QStringList cleanParts = cleanName.split( ' ' );
-
- if ( !m_customUsername )
- {
- if ( !cleanParts.isEmpty() && !cleanParts.first().isEmpty() )
- {
- QString usernameSuggestion = cleanParts.first();
- for ( int i = 1; i < cleanParts.length(); ++i )
- {
- if ( !cleanParts.value( i ).isEmpty() )
- {
- usernameSuggestion.append( cleanParts.value( i ).at( 0 ) );
- }
- }
- if ( USERNAME_RX.indexIn( usernameSuggestion ) != -1 )
- {
- ui->textBoxUsername->setText( usernameSuggestion );
- validateUsernameText( usernameSuggestion );
- m_customUsername = false;
- }
- }
- }
-
- if ( !m_customHostname )
- {
- if ( !cleanParts.isEmpty() && !cleanParts.first().isEmpty() )
- {
- QString hostnameSuggestion;
- QString productName = guessProductName();
- hostnameSuggestion = QString( "%1-%2" ).arg( cleanParts.first() ).arg( productName );
- if ( HOSTNAME_RX.indexIn( hostnameSuggestion ) != -1 )
- {
- ui->textBoxHostname->setText( hostnameSuggestion );
- validateHostnameText( hostnameSuggestion );
- m_customHostname = false;
- }
- }
- }
-}
-
-
-void
-UsersPage::onUsernameTextEdited( const QString& textRef )
-{
- m_customUsername = true;
- validateUsernameText( textRef );
-}
-
-
-void
-UsersPage::validateUsernameText( const QString& textRef )
-{
- QString text( textRef );
- QRegExpValidator val_whole( USERNAME_RX );
- QRegExpValidator val_start( QRegExp( "[a-z_].*" ) ); // anchors are implicit in QRegExpValidator
- int pos = -1;
-
- if ( text.isEmpty() )
- {
- ui->labelUsernameError->clear();
- ui->labelUsername->clear();
- m_readyUsername = false;
- }
- else if ( text.length() > USERNAME_MAX_LENGTH )
- {
- labelError( ui->labelUsername, ui->labelUsernameError, tr( "Your username is too long." ) );
- m_readyUsername = false;
- }
- else if ( val_start.validate( text, pos ) == QValidator::Invalid )
- {
- labelError( ui->labelUsername,
- ui->labelUsernameError,
- tr( "Your username must start with a lowercase letter or underscore." ) );
- m_readyUsername = false;
- }
- else if ( val_whole.validate( text, pos ) == QValidator::Invalid )
- {
- labelError( ui->labelUsername,
- ui->labelUsernameError,
- tr( "Only lowercase letters, numbers, underscore and hyphen are allowed." ) );
- m_readyUsername = false;
- }
- else
- {
- labelOk( ui->labelUsername, ui->labelUsernameError );
- m_readyUsername = true;
- }
-
+ labelStatus( ui->labelUsername, ui->labelUsernameError, m_config->loginName(), status, m_readyUsername );
emit checkReady( isReady() );
}
-
void
-UsersPage::onHostnameTextEdited( const QString& textRef )
+UsersPage::reportHostNameStatus( const QString& status )
{
- m_customHostname = true;
- validateHostnameText( textRef );
-}
-
-
-void
-UsersPage::validateHostnameText( const QString& textRef )
-{
- QString text = textRef;
- QRegExpValidator val( HOSTNAME_RX );
- int pos = -1;
-
- if ( text.isEmpty() )
- {
- ui->labelHostnameError->clear();
- ui->labelHostname->clear();
- m_readyHostname = false;
- }
- else if ( text.length() < HOSTNAME_MIN_LENGTH )
- {
- labelError( ui->labelHostname, ui->labelHostnameError, tr( "Your hostname is too short." ) );
- m_readyHostname = false;
- }
- else if ( text.length() > HOSTNAME_MAX_LENGTH )
- {
- labelError( ui->labelHostname, ui->labelHostnameError, tr( "Your hostname is too long." ) );
- m_readyHostname = false;
- }
- else if ( val.validate( text, pos ) == QValidator::Invalid )
- {
- labelError( ui->labelHostname,
- ui->labelHostnameError,
- tr( "Only letters, numbers, underscore and hyphen are allowed." ) );
- m_readyHostname = false;
- }
- else
- {
- labelOk( ui->labelHostname, ui->labelHostnameError );
- m_readyHostname = true;
- }
-
+ labelStatus( ui->labelHostname, ui->labelHostnameError, m_config->hostName(), status, m_readyHostname );
emit checkReady( isReady() );
}
@@ -546,13 +349,6 @@ UsersPage::setValidatePasswordDefault( bool checked )
emit checkReady( isReady() );
}
-void
-UsersPage::setAutologinDefault( bool checked )
-{
- ui->checkBoxAutoLogin->setChecked( checked );
- emit checkReady( isReady() );
-}
-
void
UsersPage::setReusePasswordDefault( bool checked )
{
diff --git a/src/modules/users/UsersPage.h b/src/modules/users/UsersPage.h
index 3382e9335..b8cb0f06a 100644
--- a/src/modules/users/UsersPage.h
+++ b/src/modules/users/UsersPage.h
@@ -25,10 +25,11 @@
#define USERSPAGE_H
#include "CheckPWQuality.h"
-#include "Job.h"
#include
+class Config;
+
class QLabel;
namespace Ui
@@ -40,19 +41,17 @@ class UsersPage : public QWidget
{
Q_OBJECT
public:
- explicit UsersPage( QWidget* parent = nullptr );
+ explicit UsersPage( Config* config, QWidget* parent = nullptr );
virtual ~UsersPage();
- bool isReady();
+ bool isReady() const;
- Calamares::JobList createJobs( const QStringList& defaultGroupsList );
+ void fillGlobalStorage() const;
void onActivate();
- void setWriteRootPassword( bool show );
void setPasswordCheckboxVisible( bool visible );
void setValidatePasswordDefault( bool checked );
- void setAutologinDefault( bool checked );
void setReusePasswordDefault( bool checked );
/** @brief Process entries in the passwordRequirements config entry
@@ -63,8 +62,6 @@ public:
*/
void addPasswordCheck( const QString& key, const QVariant& value );
- ///@brief Hostname as entered / auto-filled
- QString getHostname() const;
///@brief Root password, depends on settings, may be empty
QString getRootPassword() const;
///@brief User name and password
@@ -72,11 +69,8 @@ public:
protected slots:
void onFullNameTextEdited( const QString& );
- void fillSuggestions();
- void onUsernameTextEdited( const QString& );
- void validateUsernameText( const QString& );
- void onHostnameTextEdited( const QString& );
- void validateHostnameText( const QString& );
+ void reportLoginNameStatus( const QString& );
+ void reportHostNameStatus( const QString& );
void onPasswordTextChanged( const QString& );
void onRootPasswordTextChanged( const QString& );
@@ -95,19 +89,16 @@ private:
void retranslate();
Ui::Page_UserSetup* ui;
+ Config* m_config;
PasswordCheckList m_passwordChecks;
bool m_passwordChecksChanged = false;
bool m_readyFullName;
bool m_readyUsername;
- bool m_customUsername;
bool m_readyHostname;
- bool m_customHostname;
bool m_readyPassword;
bool m_readyRootPassword;
-
- bool m_writeRootPassword;
};
#endif // USERSPAGE_H
diff --git a/src/modules/users/UsersViewStep.cpp b/src/modules/users/UsersViewStep.cpp
index fe633b1c2..b9ea3ae17 100644
--- a/src/modules/users/UsersViewStep.cpp
+++ b/src/modules/users/UsersViewStep.cpp
@@ -20,17 +20,18 @@
#include "UsersViewStep.h"
+#include "Config.h"
+#include "CreateUserJob.h"
#include "SetHostNameJob.h"
#include "SetPasswordJob.h"
#include "UsersPage.h"
+#include "GlobalStorage.h"
+#include "JobQueue.h"
#include "utils/Logger.h"
#include "utils/NamedEnum.h"
#include "utils/Variant.h"
-#include "GlobalStorage.h"
-#include "JobQueue.h"
-
CALAMARES_PLUGIN_FACTORY_DEFINITION( UsersViewStepFactory, registerPlugin< UsersViewStep >(); )
static const NamedEnumTable< SetHostNameJob::Action >&
@@ -53,11 +54,11 @@ hostnameActions()
UsersViewStep::UsersViewStep( QObject* parent )
: Calamares::ViewStep( parent )
- , m_widget( new UsersPage() )
+ , m_widget( nullptr )
, m_actions( SetHostNameJob::Action::None )
+ , m_config( new Config( this ) )
{
emit nextStatusChanged( true );
- connect( m_widget, &UsersPage::checkReady, this, &UsersViewStep::nextStatusChanged );
}
@@ -80,6 +81,11 @@ UsersViewStep::prettyName() const
QWidget*
UsersViewStep::widget()
{
+ if ( !m_widget )
+ {
+ m_widget = new UsersPage( m_config );
+ connect( m_widget, &UsersPage::checkReady, this, &UsersViewStep::nextStatusChanged );
+ }
return m_widget;
}
@@ -87,7 +93,7 @@ UsersViewStep::widget()
bool
UsersViewStep::isNextEnabled() const
{
- return m_widget->isReady();
+ return m_widget ? m_widget->isReady() : true;
}
@@ -122,7 +128,10 @@ UsersViewStep::jobs() const
void
UsersViewStep::onActivate()
{
- m_widget->onActivate();
+ if ( m_widget )
+ {
+ m_widget->onActivate();
+ }
}
@@ -130,9 +139,17 @@ void
UsersViewStep::onLeave()
{
m_jobs.clear();
- m_jobs.append( m_widget->createJobs( m_defaultGroups ) );
+ if ( !m_widget || !m_widget->isReady() )
+ {
+ return;
+ }
Calamares::Job* j;
+ // TODO: Config object should create jobs, like this one, that depend only on config values
+ j = new CreateUserJob( m_config->loginName(),
+ m_config->fullName().isEmpty() ? m_config->loginName() : m_config->fullName(),
+ m_config->doAutoLogin(),
+ m_config->defaultGroups() );
auto userPW = m_widget->getUserPassword();
j = new SetPasswordJob( userPW.first, userPW.second );
@@ -141,46 +158,20 @@ UsersViewStep::onLeave()
j = new SetPasswordJob( "root", m_widget->getRootPassword() );
m_jobs.append( Calamares::job_ptr( j ) );
- j = new SetHostNameJob( m_widget->getHostname(), m_actions );
+ j = new SetHostNameJob( m_config->hostName(), m_actions );
m_jobs.append( Calamares::job_ptr( j ) );
+
+ m_widget->fillGlobalStorage();
}
void
UsersViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
+ // Create the widget, after all .. as long as writing configuration to the UI is needed
+ (void)this->widget();
using CalamaresUtils::getBool;
- if ( configurationMap.contains( "defaultGroups" )
- && configurationMap.value( "defaultGroups" ).type() == QVariant::List )
- {
- m_defaultGroups = configurationMap.value( "defaultGroups" ).toStringList();
- }
- else
- {
- cWarning() << "Using fallback groups. Please check defaultGroups in users.conf";
- m_defaultGroups = QStringList { "lp", "video", "network", "storage", "wheel", "audio" };
- }
-
- if ( configurationMap.contains( "autologinGroup" )
- && configurationMap.value( "autologinGroup" ).type() == QVariant::String )
- {
- Calamares::JobQueue::instance()->globalStorage()->insert(
- "autologinGroup", configurationMap.value( "autologinGroup" ).toString() );
- }
-
- if ( configurationMap.contains( "sudoersGroup" )
- && configurationMap.value( "sudoersGroup" ).type() == QVariant::String )
- {
- Calamares::JobQueue::instance()->globalStorage()->insert( "sudoersGroup",
- configurationMap.value( "sudoersGroup" ).toString() );
- }
-
- bool setRootPassword = getBool( configurationMap, "setRootPassword", true );
- Calamares::JobQueue::instance()->globalStorage()->insert( "setRootPassword", setRootPassword );
-
- m_widget->setWriteRootPassword( setRootPassword );
- m_widget->setAutologinDefault( getBool( configurationMap, "doAutologin", false ) );
m_widget->setReusePasswordDefault( getBool( configurationMap, "doReusePassword", false ) );
if ( configurationMap.contains( "passwordRequirements" )
@@ -197,15 +188,6 @@ UsersViewStep::setConfigurationMap( const QVariantMap& configurationMap )
m_widget->setPasswordCheckboxVisible( getBool( configurationMap, "allowWeakPasswords", false ) );
m_widget->setValidatePasswordDefault( !getBool( configurationMap, "allowWeakPasswordsDefault", false ) );
- QString shell( QLatin1String( "/bin/bash" ) ); // as if it's not set at all
- if ( configurationMap.contains( "userShell" ) )
- {
- shell = CalamaresUtils::getString( configurationMap, "userShell" );
- }
- // Now it might be explicitly set to empty, which is ok
-
- Calamares::JobQueue::instance()->globalStorage()->insert( "userShell", shell );
-
using Action = SetHostNameJob::Action;
QString hostnameActionString = CalamaresUtils::getString( configurationMap, "setHostname" );
@@ -222,4 +204,6 @@ UsersViewStep::setConfigurationMap( const QVariantMap& configurationMap )
Action hostsfileAction = getBool( configurationMap, "writeHostsFile", true ) ? Action::WriteEtcHosts : Action::None;
m_actions = hostsfileAction | hostnameAction;
+
+ m_config->setConfigurationMap( configurationMap );
}
diff --git a/src/modules/users/UsersViewStep.h b/src/modules/users/UsersViewStep.h
index 03cc83819..5c3674571 100644
--- a/src/modules/users/UsersViewStep.h
+++ b/src/modules/users/UsersViewStep.h
@@ -29,6 +29,7 @@
#include
#include
+class Config;
class UsersPage;
class PLUGINDLLEXPORT UsersViewStep : public Calamares::ViewStep
@@ -60,8 +61,9 @@ private:
UsersPage* m_widget;
QList< Calamares::job_ptr > m_jobs;
- QStringList m_defaultGroups;
SetHostNameJob::Actions m_actions;
+
+ Config* m_config;
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( UsersViewStepFactory )
diff --git a/src/modules/users/page_usersetup.ui b/src/modules/users/page_usersetup.ui
index b778647d8..d880673b8 100644
--- a/src/modules/users/page_usersetup.ui
+++ b/src/modules/users/page_usersetup.ui
@@ -127,7 +127,7 @@
-
+ 0
@@ -226,7 +226,7 @@
-
+ 0
@@ -456,7 +456,7 @@
-
+ Log in automatically without asking for the password.