Commit 85e9ffd6 authored by segfault's avatar segfault

Major refactoring of the Greeter (refs: #17098)

parent b94b3550
......@@ -57,6 +57,7 @@
/config/chroot_local-includes/usr/share/desktop-directories/Tails.directory
/config/chroot_local-includes/usr/share/polkit-1/actions/org.boum.tails.root-terminal.policy
/config/chroot_local-includes/usr/share/polkit-1/actions/org.boum.tails.additional-software.policy
/config/chroot_local-includes/usr/share/tails/greeter/*.ui
/config/chroot_local-includes/usr/share/tails/unlock-veracrypt-volumes/*.ui
/tmp/
......
import logging
from typing import TYPE_CHECKING
import gi
from tailsgreeter.settings.localization import LocalizationSetting, language_from_locale, country_from_locale, \
countries_from_locales
gi.require_version('GObject', '2.0')
gi.require_version('GnomeDesktop', '3.0')
gi.require_version('Gtk', '3.0')
from gi.repository import GObject, GnomeDesktop, Gtk
if TYPE_CHECKING:
from tailsgreeter.settings.localization_settings import LocalisationSettings
class FormatsSetting(LocalizationSetting):
def __init__(self, settings_object: "LocalisationSettings"):
super().__init__(settings_object)
super().set_value('en_US', is_default=True)
def get_tree(self):
treestore = Gtk.TreeStore(GObject.TYPE_STRING, # id
GObject.TYPE_STRING) # name
format_codes = countries_from_locales(
self._settings.system_locales_list)
format_codes.sort(key=lambda x: self._country_name(x).lower())
logging.debug("format_codes=%s", format_codes)
for format_code in format_codes:
format_name = self._country_name(format_code)
if not format_name:
# Don't display languages without a name
continue
treeiter_format = treestore.append(parent=None)
treestore.set(treeiter_format,
0, self.get_default_locale(format_code))
treestore.set(treeiter_format, 1, format_name)
locale_codes = sorted(
self.get_default_locales(format_code),
key=lambda x: self._locale_name(x).lower())
if len(locale_codes) > 1:
for locale_code in locale_codes:
treeiter_locale = treestore.append(
parent=treeiter_format)
treestore.set(treeiter_locale, 0, locale_code)
treestore.set(treeiter_locale, 1,
self._locale_name(locale_code))
return treestore
def get_name(self):
return self._locale_name(self.get_value())
def get_default_locales(self, country_code):
"""Return available locales for given country
"""
if country_code in self._settings.system_formats_dict:
return self._settings.system_formats_dict[country_code]
def get_default_locale(self, country_code=None):
"""Return default locale for given country
Returns the 1st locale among:
- the locale whose country name matches country name
- the 1st locale for the language
- en_US
"""
default_locales = self.get_default_locales(country_code)
if default_locales:
for locale_code in default_locales:
if (country_from_locale(locale_code).lower() ==
language_from_locale(locale_code)):
return locale_code
return default_locales[0]
else:
return 'en_US'
def _country_name(self, country_code):
default_locale = 'C'
local_locale = self.get_default_locale(country_code)
native_name = GnomeDesktop.get_country_from_code(
country_code, local_locale)
if not native_name:
return ""
localized_name = GnomeDesktop.get_country_from_code(
country_code, default_locale)
if native_name == localized_name:
return native_name
else:
return "{native} ({localized})".format(
native=native_name, localized=localized_name)
@staticmethod
def _locale_name(locale_code):
lang_code = language_from_locale(locale_code)
country_code = country_from_locale(locale_code)
language_name_locale = GnomeDesktop.get_language_from_code(lang_code)
language_name_native = GnomeDesktop.get_language_from_code(
lang_code, locale_code)
country_name_locale = GnomeDesktop.get_country_from_code(country_code)
country_name_native = GnomeDesktop.get_country_from_code(
country_code, locale_code)
try:
if (language_name_native == language_name_locale and
country_name_native == country_name_locale):
return "{country} - {language}".format(
language=language_name_native.capitalize(),
country=country_name_native)
else:
return "{country} - {language} " \
"({local_country} - {local_language})".format(
language=language_name_native.capitalize(),
country=country_name_native,
local_language=language_name_locale.capitalize(),
local_country=country_name_locale)
except AttributeError:
return locale_code
def set_default(self):
"""Set default format for current language
Select the same locale for formats that the language
"""
default_format = self._settings.language.get_value()
logging.debug("setting default formats to %s", default_format)
self.set_value(default_format, is_default=True)
import logging
import gi
from tailsgreeter.settings.localization import LocalizationSetting, ln_iso639_tri, \
ln_iso639_2_T_to_B, language_from_locale, country_from_locale
gi.require_version('Gio', '2.0')
gi.require_version('GLib', '2.0')
gi.require_version('GnomeDesktop', '3.0')
gi.require_version('GObject', '2.0')
gi.require_version('Gtk', '3.0')
from gi.repository import Gio, GLib, GnomeDesktop, GObject, Gtk
class KeyboardSetting(LocalizationSetting):
def __init__(self, settings_object):
super().__init__(settings_object)
self.xkbinfo = GnomeDesktop.XkbInfo()
super().set_value('us', is_default=True)
def get_tree(self, layout_codes=None):
if not layout_codes:
layout_codes = self.get_all()
treestore = Gtk.TreeStore(GObject.TYPE_STRING, # id
GObject.TYPE_STRING) # name
layouts = self._layouts_split_names(layout_codes)
for group_name in sorted(layouts.keys()):
layout_codes = sorted(
layouts[group_name],
key=lambda x: self._layout_name(x).lower())
treeiter_group = treestore.append(parent=None)
# we fill the title with the 1st layout of the group
treestore.set(treeiter_group, 0, layout_codes[0])
treestore.set(treeiter_group, 1, group_name)
if len(layout_codes) > 1:
for layout_code in layout_codes:
treeiter_layout = treestore.append(parent=treeiter_group)
treestore.set(treeiter_layout, 0, layout_code)
treestore.set(treeiter_layout, 1,
self._layout_name(layout_code))
return treestore
def get_name(self):
return self._layout_name(self.get_value())
def get_all(self):
"""Return a list of all keyboard layout codes
"""
return self.xkbinfo.get_all_layouts()
def get_defaults(self):
"""Return list of supported keyboard layouts for current language
"""
lang_code = language_from_locale(self._settings.language.get_value())
layouts = self._layouts_for_language(lang_code)
if not layouts:
country_code = country_from_locale(self._settings.language.get_value())
layouts = self._layouts_for_country(country_code)
if not layouts:
layouts = ['us']
return layouts
def set_value(self, layout, is_default=False):
super().set_value(layout, is_default)
self._apply_layout_to_current_screen()
def _layout_name(self, layout_code):
layout_exists, display_name, short_name, xkb_layout, xkb_variant = \
self.xkbinfo.get_layout_info(layout_code)
if not layout_exists:
logging.warning("Layout code '%s' does not exist", layout_code)
return display_name
def _layouts_split_names(self, layout_codes):
layouts_names = {}
for layout_code in layout_codes:
layout_name = self._layout_name(layout_code)
country_name, s, v = layout_name.partition(' (')
if country_name not in layouts_names:
layouts_names[country_name] = {layout_code}
else:
layouts_names[country_name].add(layout_code)
return layouts_names
def _layouts_for_language(self, lang_code):
"""Return the list of available layouts for given language
"""
try:
t_code = ln_iso639_tri(lang_code)
except KeyError:
t_code = lang_code
if t_code == 'nno' or t_code == 'nob':
t_code = 'nor'
layouts = self.xkbinfo.get_layouts_for_language(t_code)
if t_code == 'hrv':
layouts.append('hr')
if len(layouts) == 0:
b_code = ln_iso639_2_T_to_B(t_code)
logging.debug(
"got no layout for ISO-639-2/T code %s, "
"trying with ISO-639-2/B code %s",
t_code, b_code)
layouts = self.xkbinfo.get_layouts_for_language(b_code)
logging.debug('got %d layouts for %s', len(layouts), lang_code)
return layouts
def _layouts_for_country(self, country):
"""Return the list of available layouts for given country
"""
# XXX: it would be logical to use:
# self.__xklinfo.get_layouts_for_language(country)
# but it doesn't actually return the list of all layouts matching a
# country.
def country_filter(layout):
cc = country.lower()
return ((layout == cc)
or ('+' in layout) and (layout.split('+')[0] == cc))
layouts = list(filter(country_filter, self.get_all()))
logging.debug('got %d layouts for %s', len(layouts), country)
return layouts
@staticmethod
def _split_variant(layout_code):
if '+' in layout_code:
return layout_code.split('+')
else:
return layout_code, None
def _filter_layouts(self, layouts, country, language):
"""Try to select the best layout in a layout list
"""
if len(layouts) > 1:
def variant_filter(layout):
layout_name, layout_variant = self._split_variant(layout)
return layout_variant is None
filtered_layouts = list(filter(variant_filter, layouts))
logging.debug("Filter by variant: %s", filtered_layouts)
if len(filtered_layouts) > 0:
layouts = filtered_layouts
if len(layouts) > 1:
def country_filter(layout):
layout_name, layout_variant = self._split_variant(layout)
return layout_variant == country.lower()
filtered_layouts = list(filter(country_filter, layouts))
logging.debug("Filter by country %s: %s", country,
filtered_layouts)
if len(filtered_layouts) > 0:
layouts = filtered_layouts
if len(layouts) > 1:
def language_filter(layout):
layout_name, layout_variant = self._split_variant(layout)
return layout_variant == language
filtered_layouts = list(filter(language_filter, layouts))
logging.debug("Filter by language %s: %s", language,
filtered_layouts)
if len(filtered_layouts) > 0:
layouts = filtered_layouts
return layouts
def set_default(self):
"""Sets the best default layout for the current locale
"""
language = language_from_locale(self._settings.language.get_value())
country = country_from_locale(self._settings.language.get_value())
# First, build a list of layouts to consider for the language
language_layouts = self._layouts_for_language(language)
logging.debug("Language %s layouts: %s", language, language_layouts)
country_layouts = self._layouts_for_country(country)
logging.debug("Country %s layouts: %s", country, country_layouts)
layouts = set(language_layouts).intersection(country_layouts)
logging.debug("Intersection of language %s and country %s: %s",
language, country, layouts)
if not len(layouts) > 0:
def country_filter(layout):
layout_name, layout_variant = self._split_variant(layout)
return layout_name == country.lower()
layouts = list(filter(country_filter, language_layouts))
logging.debug("Empty intersection of language and country, filter "
"by country %s only: %s", country, layouts)
if not len(layouts) > 0:
def language_filter(layout):
layout_name, layout_variant = self._split_variant(layout)
return layout_name == language
layouts = list(filter(language_filter, language_layouts))
logging.debug("List still empty, filter by language %s only: %s",
language, layouts)
if not len(layouts) > 0:
layouts = language_layouts
logging.debug("List still empty, use all language %s layouts: %s",
language, layouts)
# Then, filter the list
layouts = self._filter_layouts(layouts, country, language)
if len(layouts) != 1:
# Can't find a single result, build a new list for the country
layouts = country_layouts
logging.debug("Still not 1 layouts. Try again using all country "
"%s layouts: %s", country, layouts)
layouts = self._filter_layouts(layouts, country, language)
if len(layouts) == 1:
default_layout = layouts.pop()
logging.debug("Selecting single matching layout %s",
default_layout)
elif len(layouts) > 1:
default_layout = layouts.pop()
logging.debug("No good layout, arbitrary using layout %s",
default_layout)
else:
default_layout = 'us'
logging.debug("Using us as fallback default layout")
self.set_value(default_layout, is_default=True)
def _apply_layout_to_current_screen(self):
layout = self.get_value()
logging.debug("layout=%s", layout)
settings = Gio.Settings('org.gnome.desktop.input-sources')
settings.set_value('sources', GLib.Variant('a(ss)', [('xkb', layout)]))
# -*- coding: utf-8 -*-
#
# Copyright 2012-2019 Tails developers <tails@boum.org>
# Copyright 2011 Max <govnototalitarizm@gmail.com>
# Copyright 2011 Martin Owens
#
# 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 <http://www.gnu.org/licenses/>
import gi
import logging
import locale
from tailsgreeter.settings.localization import LocalizationSetting, \
language_from_locale, languages_from_locales, country_from_locale
gi.require_version('GLib', '2.0')
gi.require_version('GObject', '2.0')
gi.require_version('GnomeDesktop', '3.0')
gi.require_version('Gtk', '3.0')
from gi.repository import GLib, GObject, GnomeDesktop, Gtk
class LanguageSetting(LocalizationSetting):
def __init__(self, settings_object):
super().__init__(settings_object)
super().set_value('en_US', is_default=True)
def get_tree(self):
treestore = Gtk.TreeStore(GObject.TYPE_STRING, # id
GObject.TYPE_STRING) # name
lang_codes = languages_from_locales(
self._settings.system_locales_list)
lang_codes.sort(key=lambda x: self._language_name(x).lower())
for lang_code in lang_codes:
language_name = self._language_name(lang_code)
if not language_name:
# Don't display languages without a name
continue
treeiter_language = treestore.append(parent=None)
treestore.set(treeiter_language,
0, self.get_default_locale(lang_code))
treestore.set(treeiter_language, 1, language_name)
locale_codes = sorted(
self.get_default_locales(lang_code),
key=lambda x: self._locale_name(x).lower())
if len(locale_codes) > 1:
for locale_code in locale_codes:
treeiter_locale = treestore.append(
parent=treeiter_language)
treestore.set(treeiter_locale, 0, locale_code)
treestore.set(treeiter_locale, 1,
self._locale_name(locale_code))
return treestore
def get_name(self):
return self._locale_name(self.get_value())
def get_default_locales(self, lang_code):
"""Return available locales for given language
"""
if lang_code in self._settings._system_languages_dict:
return self._settings._system_languages_dict[lang_code]
def get_default_locale(self, lang_code=None):
"""Return default locale for given language
Returns the 1st locale among:
- the locale whose country name matches language name
- the 1st locale for the language
- en_US
"""
default_locales = self.get_default_locales(lang_code)
if default_locales:
for locale_code in default_locales:
if (country_from_locale(locale_code).lower() ==
language_from_locale(locale_code)):
return locale_code
return default_locales[0]
else:
return 'en_US'
def set_value(self, locale, is_default=False):
super().set_value(locale, is_default)
self.__apply_locale()
self._settings.formats.set_default_if_needed() # XXX: notify
self._settings.keyboard.set_default_if_needed() # XXX: notify
if self._settings._locale_selected_cb:
self._settings._locale_selected_cb(locale)
def _language_name(self, lang_code):
default_locale = 'C'
local_locale = self.get_default_locale(lang_code)
try:
native_name = GnomeDesktop.get_language_from_code(
lang_code, local_locale).capitalize()
except AttributeError:
return ""
localized_name = GnomeDesktop.get_language_from_code(
lang_code, default_locale).capitalize()
if native_name == localized_name:
return native_name
else:
return "{native} ({localized})".format(
native=native_name, localized=localized_name)
def _locale_name(self, locale_code):
lang_code = language_from_locale(locale_code)
country_code = country_from_locale(locale_code)
language_name_locale = GnomeDesktop.get_language_from_code(lang_code)
language_name_native = GnomeDesktop.get_language_from_code(
lang_code, locale_code)
country_name_locale = GnomeDesktop.get_country_from_code(country_code)
country_name_native = GnomeDesktop.get_country_from_code(
country_code, locale_code)
try:
if (language_name_native == language_name_locale and
country_name_native == country_name_locale):
return "{language} - {country}".format(
language=language_name_native.capitalize(),
country=country_name_native)
else:
return "{language} - {country} " \
"({local_language} - {local_country})".format(
language=language_name_native.capitalize(),
country=country_name_native,
local_language=language_name_locale.capitalize(),
local_country=country_name_locale)
except AttributeError:
return locale_code
def __apply_locale(self):
locale_code = locale.normalize(
self.get_value() + '.' + locale.getpreferredencoding())
logging.debug("Setting session language to %s", locale_code)
if self._settings._act_user:
GLib.idle_add(
lambda: self._settings._act_user.set_language(locale_code))
else:
logging.warning("AccountsManager not ready")
# -*- coding: utf-8 -*-
#
# Copyright 2012-2019 Tails developers <tails@boum.org>
# Copyright 2011 Max <govnototalitarizm@gmail.com>
# Copyright 2011 Martin Owens
#
# 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 <http://www.gnu.org/licenses/>
import gi
import pycountry