From a8cbf4cacb526ebdd18a288c04d260ac84c9373d Mon Sep 17 00:00:00 2001 From: segfault Date: Wed, 29 Jan 2020 17:07:47 +0100 Subject: [PATCH] Greeter: Refactor: Redesign how settings are saved and loaded Settings are now saved to file immediately when the user changes them, instead of before login. Beside making the code simpler, this makes it easier to handle saving the admin password file (which we want to delete if the user disabled the feature after loading a persistent password file, but want to overwrite or leave untouched in other cases). --- .../dist-packages/tailsgreeter/greeter.py | 43 ++---- .../tailsgreeter/settings/admin.py | 49 +++---- .../tailsgreeter/settings/formats.py | 34 ++--- .../tailsgreeter/settings/keyboard.py | 46 +++---- .../tailsgreeter/settings/language.py | 36 +++-- .../tailsgreeter/settings/localization.py | 12 +- .../settings/localization_settings.py | 8 +- .../tailsgreeter/settings/macspoof.py | 21 +-- .../tailsgreeter/settings/network.py | 27 ++-- .../tailsgreeter/settings/utils.py | 5 +- .../tailsgreeter/ui/additional_settings.py | 54 ++++++-- .../tailsgreeter/ui/main_window.py | 15 ++- .../tailsgreeter/ui/persistent_storage.py | 9 +- .../tailsgreeter/ui/region_settings.py | 127 +++++++++++++++--- .../dist-packages/tailsgreeter/ui/setting.py | 4 +- 15 files changed, 281 insertions(+), 209 deletions(-) diff --git a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/greeter.py b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/greeter.py index 1f1809f336..d1a37b9d73 100644 --- a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/greeter.py +++ b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/greeter.py @@ -71,7 +71,6 @@ class GreeterApplication(object): persistence = PersistenceSettings() self.localisationsettings = LocalisationSettings( usermanager_loaded_cb=self.usermanager_loaded, - locale_selected_cb=self.on_language_changed ) self.admin_setting = AdminSetting() self.network_setting = NetworkSetting() @@ -79,7 +78,7 @@ class GreeterApplication(object): # Initialize the settings self.settings = GreeterSettingsCollection( - LanguageSettingUI(self.localisationsettings.language), + LanguageSettingUI(self.localisationsettings.language, self.on_language_changed), KeyboardSettingUI(self.localisationsettings.keyboard), FormatsSettingUI(self.localisationsettings.formats), AdminSettingUI(self.admin_setting), @@ -90,6 +89,10 @@ class GreeterApplication(object): # Initialize main window self.mainwindow = GreeterMainWindow(self, persistence, self.settings) + # Apply the default settings + for setting in self.settings: + setting.apply() + # Inhibit the session being marked as idle self.inhibit_idle() @@ -97,38 +100,15 @@ class GreeterApplication(object): """Translate all windows to target language""" TranslatableWindow.translate_all(lang) - def load_settings(self): - if self.localisationsettings.language.load(): - self.settings["language"].selected_code = self.localisationsettings.language.value - self.settings["language"].apply() - if self.localisationsettings.formats.load(): - self.settings["formats"].selected_code = self.localisationsettings.formats.value - self.settings["formats"].apply() - if self.localisationsettings.keyboard.load(): - self.settings["keyboard"].selected_code = self.localisationsettings.keyboard.value - self.settings["keyboard"].apply() - - if self.admin_setting.load(): - self.settings["admin"].password = self.admin_setting.password - self.mainwindow.add_setting("admin") - if self.network_setting.load(): - if self.network_setting.value != self.settings["network"].value: - self.settings["network"].value = self.network_setting.value - self.mainwindow.add_setting("network") - if self.macspoof_setting.load(): - if self.settings["macspoof"].spoofing_enabled != self.macspoof_setting.value: - self.settings["macspoof"].spoofing_enabled = self.macspoof_setting.value - self.mainwindow.add_setting("macspoof") - def login(self): """Login GDM to the server""" logging.debug("login called") # Apply settings + # We now apply all settings immediately when they are + # changed. The only thing that still happens here is + # concatenating the locale settings files. self.localisationsettings.apply_to_upcoming_session() - self.admin_setting.apply_to_upcoming_session() - self.macspoof_setting.apply_to_upcoming_session() - self.network_setting.apply_to_upcoming_session() self.mainwindow.hide() self.gdmclient.do_login() @@ -142,10 +122,9 @@ class GreeterApplication(object): def on_language_changed(self, locale_code: str): """Translate to the given locale""" - self.localisationsettings.formats.on_language_changed(locale_code) # XXX: notify - self.settings["formats"].update_value_label() - self.localisationsettings.keyboard.on_language_changed(locale_code) # XXX: notify - self.settings["keyboard"].update_value_label() + for setting in self.settings.region_settings: + setting.on_language_changed(locale_code) + self.translate_to(locale_code) self.mainwindow.current_language = localization.language_from_locale(locale_code) diff --git a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/admin.py b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/admin.py index e8affe84ea..aad0e3b7b3 100644 --- a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/admin.py +++ b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/admin.py @@ -11,45 +11,38 @@ from tailsgreeter.settings.utils import read_settings, write_settings class AdminSetting(object): """Setting controlling the sudo password""" - def __init__(self): - self.password = None - self.settings_file = tailsgreeter.config.admin_password_path - - def apply_to_upcoming_session(self): - if self.password: - proc = subprocess.run( - ["mkpasswd", "-s", "--method=sha512crypt"], - input=pipes.quote(self.password).encode(), - capture_output=True, - check=True, - ) - hashed_and_salted_pw = proc.stdout.decode().strip() - - write_settings(self.settings_file, { - 'TAILS_USER_PASSWORD': pipes.quote(hashed_and_salted_pw), - }) - logging.debug('password written to %s', self.settings_file) - return - + settings_file = tailsgreeter.config.admin_password_path + + def save(self, password: str): + proc = subprocess.run( + ["mkpasswd", "-s", "--method=sha512crypt"], + input=pipes.quote(password).encode(), + capture_output=True, + check=True, + ) + hashed_and_salted_pw = proc.stdout.decode().strip() + + write_settings(self.settings_file, { + 'TAILS_USER_PASSWORD': pipes.quote(hashed_and_salted_pw), + }) + logging.debug('password written to %s', self.settings_file) + + def delete(self): # Try to remove the password file try: os.unlink(self.settings_file) logging.debug('removed %s', self.settings_file) except OSError: # It's bad if the file exists and couldn't be removed, so we - # we raise the exception in that case (which prevents the login) + # we raise the exception in that case if os.path.exists(self.settings_file): raise - def load(self) -> bool: + def load(self) -> {str, None}: try: settings = read_settings(self.settings_file) except FileNotFoundError: logging.debug("No persistent admin settings file found (path: %s)", self.settings_file) - return False + return None - password = settings.get('TAILS_USER_PASSWORD') - if password: - self.password = password - logging.debug("Loaded admin password setting") - return True + return settings.get('TAILS_USER_PASSWORD') diff --git a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/formats.py b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/formats.py index 4e1283efe3..13b1ca0dec 100644 --- a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/formats.py +++ b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/formats.py @@ -1,6 +1,5 @@ import gi import logging -import os import tailsgreeter.config from tailsgreeter.settings.localization import LocalizationSetting, language_from_locale, country_from_locale @@ -15,30 +14,30 @@ from gi.repository import GObject, GnomeDesktop, Gtk class FormatsSetting(LocalizationSetting): def __init__(self, language_codes: [str]): super().__init__() - self.value = 'en_US' self.locales_per_country = self._make_locales_per_country_dict(language_codes) self.settings_file = tailsgreeter.config.formats_setting_path - def apply_to_upcoming_session(self): + def save(self, locale: str, is_default: bool): write_settings(self.settings_file, { - 'TAILS_FORMATS': self.get_value(), - 'IS_DEFAULT': str(not self.value_changed_by_user).lower(), + 'TAILS_FORMATS': locale, + 'IS_DEFAULT': str(is_default).lower(), }) - def load(self) -> bool: + def load(self) -> ({str, None}, bool): try: settings = read_settings(self.settings_file) except FileNotFoundError: logging.debug("No persistent formats settings file found (path: %s)", self.settings_file) - return False + return None, False formats = settings.get('TAILS_FORMATS') - if not formats: - return False + if formats is None: + logging.debug("No formats setting found in settings file (path: %s)", self.settings_file) + return None, False + is_default = settings.get('IS_DEFAULT') == 'true' - self.set_value(formats, chosen_by_user=not is_default) logging.debug("Loaded formats setting '%s' (is default: %s)", formats, is_default) - return True + return formats, is_default def get_tree(self) -> Gtk.TreeStore: treestore = Gtk.TreeStore(GObject.TYPE_STRING, # id @@ -65,8 +64,8 @@ class FormatsSetting(LocalizationSetting): treestore.set(treeiter_locale, 1, self._locale_name(locale)) return treestore - def get_name(self) -> str: - return self._locale_name(self.get_value()) + def get_name(self, locale: str) -> str: + return self._locale_name(locale) def get_default_locale(self, country_code=None) -> str: """Return default locale for given country @@ -141,12 +140,3 @@ class FormatsSetting(LocalizationSetting): if language_code not in res[country_code]: res[country_code].append(language_code) return res - - def on_language_changed(self, language_code: str): - """Set the formats according to the new language""" - # Don't overwrite user chosen values - if self.value_changed_by_user: - return - - logging.debug("setting formats to %s", language_code) - self.set_value(language_code) diff --git a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/keyboard.py b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/keyboard.py index 45fffc497b..0d1f493473 100644 --- a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/keyboard.py +++ b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/keyboard.py @@ -19,14 +19,13 @@ class KeyboardSetting(LocalizationSetting): def __init__(self): super().__init__() self.xkbinfo = GnomeDesktop.XkbInfo() - self.value = 'us' self.settings_file = tailsgreeter.config.keyboard_setting_path - def apply_to_upcoming_session(self): + def save(self, value: str, is_default: bool): try: - layout, variant = self.get_value().split('+') + layout, variant = value.split('+') except ValueError: - layout = self.get_value() + layout = value variant = '' write_settings(self.settings_file, { @@ -34,29 +33,28 @@ class KeyboardSetting(LocalizationSetting): 'TAILS_XKBMODEL': 'pc105', 'TAILS_XKBLAYOUT': layout, 'TAILS_XKBVARIANT': variant, - 'IS_DEFAULT': str(not self.value_changed_by_user).lower(), + 'IS_DEFAULT': str(is_default).lower(), }) - def load(self) -> bool: + def load(self) -> ({str, None}, bool): try: settings = read_settings(self.settings_file) except FileNotFoundError: logging.debug("No persistent keyboard settings file found (path: %s)", self.settings_file) - return False + return None, False keyboard_layout = settings.get('TAILS_XKBLAYOUT') - if not keyboard_layout: - return False + if keyboard_layout is None: + logging.debug("No keyboard setting found in settings file (path: %s)", self.settings_file) + return None, False keyboard_variant = settings.get('TAILS_XKBVARIANT') if keyboard_variant: keyboard_layout += "+" + keyboard_variant is_default = settings.get('IS_DEFAULT') == 'true' - self.set_value(keyboard_layout, chosen_by_user=not is_default) - logging.debug("Loaded keyboard setting '%s' (is default: %s)", keyboard_layout, is_default) - return True + return keyboard_layout, is_default def get_tree(self, layout_codes=None) -> Gtk.TreeStore: if not layout_codes: @@ -79,8 +77,8 @@ class KeyboardSetting(LocalizationSetting): treestore.set(treeiter_layout, 1, self._layout_name(layout_code)) return treestore - def get_name(self) -> str: - return self._layout_name(self.get_value()) + def get_name(self, value: str) -> str: + return self._layout_name(value) def get_all(self) -> [str]: """Return a list of all keyboard layout codes @@ -88,11 +86,6 @@ class KeyboardSetting(LocalizationSetting): """ return self.xkbinfo.get_all_layouts() - def set_value(self, layout, chosen_by_user=False): - super().set_value(layout) - self.value_changed_by_user = chosen_by_user - self._apply_layout_to_current_screen() - def _layout_name(self, layout_code) -> str: layout_exists, display_name, short_name, xkb_layout, xkb_variant = \ self.xkbinfo.get_layout_info(layout_code) @@ -192,13 +185,7 @@ class KeyboardSetting(LocalizationSetting): layouts = filtered_layouts return layouts - def on_language_changed(self, locale: str): - """Set the keyboard layout according to the new language""" - - # Don't overwrite a user chosen value - if self.value_changed_by_user: - return - + def get_layout_for_locale(self, locale: str): language = language_from_locale(locale) country = country_from_locale(locale) @@ -243,11 +230,10 @@ class KeyboardSetting(LocalizationSetting): else: default_layout = 'us' logging.debug("Using us as fallback default layout") - self.set_value(default_layout) + return default_layout - def _apply_layout_to_current_screen(self): - layout = self.get_value() - logging.debug("layout=%s", layout) + def apply_layout_to_current_screen(self, layout: str): + logging.debug("applying keyboard layout '%s'", layout) settings = Gio.Settings('org.gnome.desktop.input-sources') settings.set_value('sources', GLib.Variant('a(ss)', [('xkb', layout)])) diff --git a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/language.py b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/language.py index 5a707848a1..ccd8b6a512 100644 --- a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/language.py +++ b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/language.py @@ -21,7 +21,7 @@ from collections import OrderedDict import gi import logging import locale -from typing import Callable +import pipes import tailsgreeter.config from tailsgreeter.settings.localization import LocalizationSetting, \ @@ -37,11 +37,9 @@ from gi.repository import GLib, GObject, GnomeDesktop, Gtk class LanguageSetting(LocalizationSetting): - def __init__(self, locales: [str], language_changed_cb: Callable): + def __init__(self, locales: [str]): super().__init__() - self.value = 'en_US' self.locales = locales - self.language_changed_cb = language_changed_cb self._user_account = None self.settings_file = tailsgreeter.config.language_setting_path @@ -49,26 +47,27 @@ class LanguageSetting(LocalizationSetting): self.locales_per_language = self._make_language_to_locale_dict(locales) self.language_names_per_language = self._make_language_to_language_name_dict(self.lang_codes) - def apply_to_upcoming_session(self): + def save(self, language: str, is_default: bool): write_settings(self.settings_file, { - 'TAILS_LOCALE_NAME': self.get_value(), - 'IS_DEFAULT': str(not self.value_changed_by_user).lower(), + 'TAILS_LOCALE_NAME': pipes.quote(language), + 'IS_DEFAULT': str(is_default).lower(), }) - def load(self) -> bool: + def load(self) -> ({str, None}, bool): try: settings = read_settings(self.settings_file) except FileNotFoundError: logging.debug("No persistent language settings file found (path: %s)", self.settings_file) - return False + return None, False language = settings.get('TAILS_LOCALE_NAME') - if not language: - return False + if language is None: + logging.debug("No language setting found in settings file (path: %s)", self.settings_file) + return None, False + is_default = settings.get('IS_DEFAULT') == 'true' - self.set_value(language, chosen_by_user=not is_default) logging.debug("Loaded language setting '%s' (is default: %s)", language, is_default) - return True + return language, is_default def get_tree(self) -> Gtk.TreeStore: treestore = Gtk.TreeStore(GObject.TYPE_STRING, # id @@ -92,8 +91,8 @@ class LanguageSetting(LocalizationSetting): treestore.set(treeiter_locale, 1, self._locale_name(locale_code)) return treestore - def get_name(self) -> str: - return self._locale_name(self.get_value()) + def get_name(self, value: str) -> str: + return self._locale_name(value) def get_default_locale(self, lang_code: str) -> str: """Try to find a default locale for the given language @@ -114,11 +113,6 @@ class LanguageSetting(LocalizationSetting): return locales[0] - def set_value(self, locale_code: str, chosen_by_user=False): - super().set_value(locale_code, chosen_by_user) - self._apply_language(locale_code) - self.language_changed_cb(locale_code) - def _language_name(self, lang_code: str) -> str: default_locale = 'C' @@ -189,7 +183,7 @@ class LanguageSetting(LocalizationSetting): except AttributeError: return locale_code - def _apply_language(self, language_code: str): + def apply_language(self, language_code: str): normalized_code = locale.normalize(language_code + '.' + locale.getpreferredencoding()) logging.debug("Setting session language to %s", normalized_code) if self._user_account: diff --git a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/localization.py b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/localization.py index 8b0c6a395e..73fec0926f 100644 --- a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/localization.py +++ b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/localization.py @@ -38,16 +38,18 @@ class LocalizationSetting(GObject.Object, object): def get_value(self) -> str: return self.value - def set_value(self, value, chosen_by_user=False): - self.value = value - self.value_changed_by_user = chosen_by_user - - def get_name(self) -> str: + def get_name(self, value: str) -> str: raise NotImplementedError def get_tree(self) -> "Gtk.Treestore": raise NotImplementedError + def save(self, value: str, is_default: bool): + pass + + def load(self) -> ({str, None}, bool): + pass + def ln_iso639_tri(ln_CC): """get iso639 3-letter code from a language code diff --git a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/localization_settings.py b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/localization_settings.py index 18e1766d47..820f6c62a2 100644 --- a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/localization_settings.py +++ b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/localization_settings.py @@ -16,7 +16,7 @@ class LocalisationSettings(object): """Controller for localisation settings """ - def __init__(self, usermanager_loaded_cb: Callable, locale_selected_cb: Callable): + def __init__(self, usermanager_loaded_cb: Callable): self._usermanager_loaded_cb = usermanager_loaded_cb self._user_account = None @@ -28,7 +28,7 @@ class LocalisationSettings(object): self._actusermanager_loadedid = self._actusermanager.connect( "notify::is-loaded", self.__on_usermanager_loaded) - self.language = LanguageSetting(locales, locale_selected_cb) + self.language = LanguageSetting(locales) self.keyboard = KeyboardSetting() self.formats = FormatsSetting(locales) @@ -53,10 +53,6 @@ class LocalisationSettings(object): self._usermanager_loaded_cb() def apply_to_upcoming_session(self): - self.language.apply_to_upcoming_session() - self.formats.apply_to_upcoming_session() - self.keyboard.apply_to_upcoming_session() - with open(tailsgreeter.config.locale_setting_path, 'w') as outfile: for path in (tailsgreeter.config.language_setting_path, tailsgreeter.config.formats_setting_path, diff --git a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/macspoof.py b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/macspoof.py index 5e5dcaa8bb..d8033e61b1 100644 --- a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/macspoof.py +++ b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/macspoof.py @@ -9,24 +9,25 @@ class MacSpoofSetting(object): """Setting controlling whether the MAC address is spoofed or not""" def __init__(self): - self.value = True self.settings_file = tailsgreeter.config.macspoof_setting_path - def apply_to_upcoming_session(self): + def save(self, value: bool): write_settings(self.settings_file, { - 'TAILS_MACSPOOF_ENABLED': pipes.quote(str(self.value)).lower(), + 'TAILS_MACSPOOF_ENABLED': pipes.quote(str(value)).lower(), }) logging.debug('macspoof setting written to %s', self.settings_file) - def load(self) -> bool: + def load(self) -> {bool, None}: try: settings = read_settings(self.settings_file) except FileNotFoundError: logging.debug("No persistent macspoof settings file found (path: %s)", self.settings_file) - return False + return None - value = settings.get('TAILS_MACSPOOF_ENABLED') == "true" - if value: - self.value = value - logging.debug("Loaded macspoof setting '%s'", value) - return True + value_str = settings.get('TAILS_MACSPOOF_ENABLED') + if value_str is None: + logging.debug("No macspoof setting found in settings file (path: %s)", self.settings_file) + return None + value = value_str == "true" + logging.debug("Loaded macspoof setting '%s'", value) + return value diff --git a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/network.py b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/network.py index f52fc4b8a8..cb60bbffb3 100644 --- a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/network.py +++ b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/network.py @@ -4,33 +4,34 @@ import pipes import tailsgreeter.config from tailsgreeter.settings.utils import read_settings, write_settings +NETCONF_DIRECT = "direct" +NETCONF_OBSTACLE = "obstacle" +NETCONF_DISABLED = "disabled" + class NetworkSetting(object): """Setting controlling how Tails connects to Tor""" - NETCONF_DIRECT = "direct" - NETCONF_OBSTACLE = "obstacle" - NETCONF_DISABLED = "disabled" - def __init__(self): - self.value = self.NETCONF_DIRECT self.settings_file = tailsgreeter.config.network_setting_path - def apply_to_upcoming_session(self): + def save(self, value: str): write_settings(self.settings_file, { - 'TAILS_NETCONF': pipes.quote(self.value), + 'TAILS_NETCONF': pipes.quote(value), }) logging.debug('network setting written to %s', self.settings_file) - def load(self) -> bool: + def load(self) -> {bool, None}: try: settings = read_settings(self.settings_file) except FileNotFoundError: logging.debug("No persistent network settings file found (path: %s)", self.settings_file) - return False + return None value = settings.get('TAILS_NETCONF') - if value: - self.value = value - logging.debug("Loaded network setting '%s'", value) - return True + if value is None: + logging.debug("No network setting found in settings file (path: %s)", self.settings_file) + return None + + logging.debug("Loaded network setting '%s'", value) + return value diff --git a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/utils.py b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/utils.py index f057726fae..07536c4bf6 100644 --- a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/utils.py +++ b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/settings/utils.py @@ -1,14 +1,15 @@ import os +from typing import Dict -def write_settings(filename: str, settings: dict): +def write_settings(filename: str, settings: Dict[str, str]): with open(filename, 'w') as f: os.chmod(filename, 0o600) for key, value in settings.items(): f.write('%s=%s\n' % (key, value)) -def read_settings(filename: str) -> dict: +def read_settings(filename: str) -> Dict[str, str]: with open(filename) as f: lines = f.readlines() diff --git a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/additional_settings.py b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/additional_settings.py index 57a247b2ea..6e55fa861f 100644 --- a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/additional_settings.py +++ b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/additional_settings.py @@ -3,6 +3,7 @@ import gi from tailsgreeter import TRANSLATION_DOMAIN import tailsgreeter.config import tailsgreeter.utils +from tailsgreeter.settings.network import NETCONF_DIRECT, NETCONF_DISABLED, NETCONF_OBSTACLE from tailsgreeter.ui import _ from tailsgreeter.ui.setting import GreeterSetting from tailsgreeter.ui.popover import Popover @@ -42,6 +43,9 @@ class AdditionalSetting(GreeterSetting): def on_opened_in_dialog(self): pass + def load(self) -> bool: + pass + class AdminSettingUI(AdditionalSetting): @property @@ -114,9 +118,19 @@ class AdminSettingUI(AdditionalSetting): def apply(self): # This writes the password to a file from which it will be set # as the amnesia password when the greeter is closed. - self._admin_setting.password = self.password + if self.password: + self._admin_setting.save(self.password) + else: + self._admin_setting.delete() super().apply() + def load(self) -> bool: + password = self._admin_setting.load() + if password: + self.password = password + return True + return False + def cb_entry_admin_changed(self, editable, user_data=None): self.update_check_icon() passwords_match = self.passwords_match() @@ -176,9 +190,18 @@ class MACSpoofSettingUI(AdditionalSetting): self.listboxrow_macspoof_off = self.builder.get_object('listboxrow_macspoof_off') def apply(self): - self._macspoof_setting.value = self.spoofing_enabled + self._macspoof_setting.save(self.spoofing_enabled) super().apply() + def load(self) -> bool: + value = self._macspoof_setting.load() + if value is None: + return False + if value == self.spoofing_enabled: + return False + self.spoofing_enabled = value + return True + def cb_listbox_macspoof_row_activated(self, listbox, row, user_data=None): self.spoofing_enabled = row == self.listboxrow_macspoof_on self.image_macspoof_on.set_visible(self.spoofing_enabled) @@ -210,16 +233,16 @@ class NetworkSettingUI(AdditionalSetting): @property def value_for_display(self) -> str: - if self.value == self._network_setting.NETCONF_DIRECT: + if self.value == NETCONF_DIRECT: return _("Direct (default)") - if self.value == self._network_setting.NETCONF_OBSTACLE: + if self.value == NETCONF_OBSTACLE: return _("Bridge & Proxy") - if self.value == self._network_setting.NETCONF_DISABLED: + if self.value == NETCONF_DISABLED: return _("Offline") def __init__(self, network_setting: "NetworkSetting"): self._network_setting = network_setting - self.value = self._network_setting.NETCONF_DIRECT + self.value = NETCONF_DIRECT super().__init__() self.accel_key = Gdk.KEY_n self.icon_network_clear_chosen = self.builder.get_object('image_network_clear') @@ -233,11 +256,20 @@ class NetworkSettingUI(AdditionalSetting): self.listboxrow_network_off = self.builder.get_object('listboxrow_network_off') def apply(self): - self._network_setting.value = self.value - is_bridge = self.value == self._network_setting.NETCONF_OBSTACLE + self._network_setting.save(self.value) + is_bridge = self.value == NETCONF_OBSTACLE self.main_window.set_bridge_infobar_visibility(is_bridge) super().apply() + def load(self) -> bool: + value = self._network_setting.load() + if value is None: + return False + if value == self.value: + return False + self.value = value + return True + def cb_listbox_network_button_press(self, widget, event, user_data=None): # On double-click: Close the window and apply chosen setting if event.type == Gdk.EventType._2BUTTON_PRESS: @@ -250,13 +282,13 @@ class NetworkSettingUI(AdditionalSetting): self.icon_network_off_chosen.set_visible(False) if row == self.listboxrow_network_clear: - self.value = self._network_setting.NETCONF_DIRECT + self.value = NETCONF_DIRECT self.icon_network_clear_chosen.set_visible(True) elif row == self.listboxrow_network_specific: - self.value = self._network_setting.NETCONF_OBSTACLE + self.value = NETCONF_OBSTACLE self.icon_network_specific_chosen.set_visible(True) elif row == self.listboxrow_network_off: - self.value = self._network_setting.NETCONF_DISABLED + self.value = NETCONF_DISABLED self.icon_network_off_chosen.set_visible(True) if self.has_popover() and self.popover.is_open(): diff --git a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/main_window.py b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/main_window.py index 3517a09180..997b8e4a46 100644 --- a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/main_window.py +++ b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/main_window.py @@ -30,7 +30,7 @@ from tailsgreeter.ui import _ from tailsgreeter.ui.add_settings_dialog import AddSettingsDialog from tailsgreeter.ui.additional_settings import AdditionalSetting from tailsgreeter.ui.help_window import GreeterHelpWindow -from tailsgreeter.ui.region_settings import LocalizationSettingUI, LanguageSettingUI +from tailsgreeter.ui.region_settings import LocalizationSettingUI from tailsgreeter import TRANSLATION_DOMAIN from tailsgreeter.ui.persistent_storage import PersistentStorage @@ -131,7 +131,7 @@ class GreeterMainWindow(Gtk.Window, TranslatableWindow): self.listbox_settings.set_placeholder(self.label_settings_default) # Persistent storage - self.persistent_storage = PersistentStorage(self.persistence_setting, greeter, builder) + self.persistent_storage = PersistentStorage(self.persistence_setting, self.load_settings, builder) # Add children to ApplicationWindow self.add(self.box_main) @@ -176,6 +176,17 @@ class GreeterMainWindow(Gtk.Window, TranslatableWindow): # Actions + def load_settings(self): + # We have to load formats and keyboard before language, because + # changing the language also changes the other two, which causes + # the settings files to be overwritten. So we load the region + # settings in reversed order. + for setting in reversed(list(self.settings.region_settings)): + setting.load() + for setting in self.settings.additional_settings: + if setting.load(): + self.add_setting(setting.id) + def run_add_setting_dialog(self, id_=None): response = self.dialog_add_setting.run(id_) if response == Gtk.ResponseType.YES: diff --git a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/persistent_storage.py b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/persistent_storage.py index 46258b4eb0..62a08871f4 100644 --- a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/persistent_storage.py +++ b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/persistent_storage.py @@ -1,7 +1,7 @@ import logging import gi import threading -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from tailsgreeter.ui import _ @@ -14,9 +14,9 @@ if TYPE_CHECKING: class PersistentStorage(object): - def __init__(self, persistence_setting: "PersistenceSettings", greeter, builder): + def __init__(self, persistence_setting: "PersistenceSettings", load_settings_cb: Callable, builder): self.persistence_setting = persistence_setting - self.greeter = greeter + self.load_settings_cb = load_settings_cb self.box_storage = builder.get_object('box_storage') self.box_storage_unlock = builder.get_object('box_storage_unlock') @@ -109,7 +109,8 @@ class PersistentStorage(object): self.image_storage_state.set_visible(True) self.box_storage_unlocked.set_visible(True) self.button_start.set_sensitive(True) - self.greeter.load_settings() + + self.load_settings_cb() def cb_checkbutton_storage_show_passphrase_toggled(self, widget): self.entry_storage_passphrase.set_visibility(widget.get_active()) diff --git a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/region_settings.py b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/region_settings.py index 05fda4be46..042c72e06c 100644 --- a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/region_settings.py +++ b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/region_settings.py @@ -6,7 +6,7 @@ import tailsgreeter.config from tailsgreeter.ui import _ from tailsgreeter.ui.setting import GreeterSetting from tailsgreeter.ui.popover import Popover -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable gi.require_version('Gtk', '3.0') gi.require_version('Pango', '1.0') @@ -14,20 +14,24 @@ from gi.repository import Gtk, Pango if TYPE_CHECKING: from tailsgreeter.settings.localization import LocalizationSetting + from tailsgreeter.settings.language import LanguageSetting + from tailsgreeter.settings.formats import FormatsSetting + from tailsgreeter.settings.keyboard import KeyboardSetting + REGION_SETTINGS_UI_FILE = "region_settings.ui" class LocalizationSettingUI(GreeterSetting): def __init__(self, localization_setting: "LocalizationSetting"): - self._localization_setting = localization_setting + self._setting = localization_setting + self.value = self.default # type: {str, None} + self.value_changed_by_user = False super().__init__() - self.selected_code = "" # type: str - self.selected_name = "" # type: str - self._localization_setting.connect("notify::value", self.cb_value_changed) + self._setting.connect("notify::value", self.cb_value_changed) - self.treestore = self._localization_setting.get_tree() + self.treestore = self._setting.get_tree() self.builder = Gtk.Builder() self.builder.set_translation_domain(TRANSLATION_DOMAIN) @@ -53,9 +57,24 @@ class LocalizationSettingUI(GreeterSetting): self.treeview.set_model(self.treestore_filtered) def apply(self): - self._localization_setting.set_value(self.selected_code, chosen_by_user=True) + self._setting.save(self.value, is_default=False) super().apply() + def load(self): + value, is_default = self._setting.load() + if value is None: + return + self.value = value + self.value_changed_by_user = not is_default + self.apply() + + @property + def default(self) -> {str, None}: + return None + + def on_language_changed(self, locale: str): + pass + def cb_searchentry_activate(self, searchentry, user_data=None): """Selects the topmost item in the treeview when pressing Enter""" if searchentry.get_text(): @@ -76,12 +95,12 @@ class LocalizationSettingUI(GreeterSetting): def cb_treeview_row_activated(self, treeview, path, column, user_data=None): treemodel = treeview.get_model() - self.selected_code = treemodel.get_value(treemodel.get_iter(path), 0) - self.selected_name = treemodel.get_value(treemodel.get_iter(path), 1) + self.value = treemodel.get_value(treemodel.get_iter(path), 0) + self.value_changed_by_user = True self.popover.close(Gtk.ResponseType.YES) def cb_value_changed(self, obj, param): - logging.debug("refreshing {}".format(self._localization_setting.get_name())) + logging.debug("refreshing {}".format(self._setting.get_name(self.value))) def treeview_select_line(model, path, iter_, data): if model.get_value(iter_, 0) == data: @@ -94,7 +113,7 @@ class LocalizationSettingUI(GreeterSetting): self.treestore_filtered.foreach( treeview_select_line, - self._localization_setting.get_value()) + self._setting.value) def cb_liststore_filtered_visible_func(self, model, treeiter, searchentry): search_query = searchentry.get_text().lower() @@ -127,6 +146,8 @@ class LocalizationSettingUI(GreeterSetting): class LanguageSettingUI(LocalizationSettingUI): + _setting = None # type: LanguageSetting + @property def id(self) -> str: return "language" @@ -141,40 +162,102 @@ class LanguageSettingUI(LocalizationSettingUI): @property def value_for_display(self) -> str: - return self._localization_setting.get_name() + return self._setting.get_name(self.value) + @property + def default(self) -> str: + return 'en_US' + + def __init__(self, setting: "LanguageSetting", changed_cb: Callable): + self.changed_cb = changed_cb + super().__init__(setting) + + def apply(self): + super().apply() + self._setting.apply_language(self.value) + self.changed_cb(self.value) + + def load(self): + super().load() + self._setting.apply_language(self.value) + self.changed_cb(self.value) + + +class FormatsSettingUI(LocalizationSettingUI): + _setting = None # type: FormatsSetting -class KeyboardSettingUI(LocalizationSettingUI): @property def id(self) -> str: - return "keyboard" + return "formats" @property def title(self) -> str: - return _("_Keyboard Layout") + return _("_Formats") @property def icon_name(self): - return "tails-keyboard-layout" + return "tails-formats" @property def value_for_display(self) -> str: - return self._localization_setting.get_name() + return self._setting.get_name(self.value) + @property + def default(self) -> str: + return 'en_US' + + def on_language_changed(self, locale: str): + """Set the formats according to the new language""" + # Don't overwrite user chosen values + if self.value_changed_by_user: + return + + if self.value == locale: + return + + self.value = locale + self.update_value_label() + self._setting.save(locale, is_default=True) + + +class KeyboardSettingUI(LocalizationSettingUI): + _setting = None # type: KeyboardSetting -class FormatsSettingUI(LocalizationSettingUI): @property def id(self) -> str: - return "formats" + return "keyboard" @property def title(self) -> str: - return _("_Formats") + return _("_Keyboard Layout") @property def icon_name(self): - return "tails-formats" + return "tails-keyboard-layout" @property def value_for_display(self) -> str: - return self._localization_setting.get_name() + return self._setting.get_name(self.value) + + @property + def default(self) -> str: + return 'us' + + def apply(self): + super().apply() + self._setting.apply_layout_to_current_screen(self.value) + + def on_language_changed(self, locale: str): + """Set the keyboard layout according to the new language""" + + # Don't overwrite a user chosen value + if self.value_changed_by_user: + return + + layout = self._setting.get_layout_for_locale(locale) + if self.value == layout: + return + + self.value = layout + self.update_value_label() + self._setting.save(layout, is_default=True) diff --git a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/setting.py b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/setting.py index cfbee7d930..39d4dfb9d5 100644 --- a/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/setting.py +++ b/config/chroot_local-includes/usr/lib/python3/dist-packages/tailsgreeter/ui/setting.py @@ -47,11 +47,13 @@ class GreeterSetting(object): self.title_label = self.builder.get_object("label_caption") self.value_label = self.builder.get_object("label_value") self.title_label.set_label(self.title) - self.update_value_label() def apply(self): self.update_value_label() + def load(self): + pass + def update_value_label(self): self.value_label.set_label(self.value_for_display) -- GitLab