From e590a1736153f3a512ffbd06fdede6787d7fca0d Mon Sep 17 00:00:00 2001 From: Adam Kankovsky Date: Tue, 14 Jan 2025 10:28:32 +0100 Subject: [PATCH] Creating a dbus interface to get local keyboard layouts Adding dbus interface for loading locale keyboards Remove duplicate function and migrate to localization one --- pyanaconda/localization.py | 31 +++++++++- .../common/structures/keyboard_layout.py | 59 +++++++++++++++++++ .../modules/localization/localization.py | 28 +++++++++ .../localization/localization_interface.py | 16 +++++ pyanaconda/ui/gui/xkl_wrapper.py | 23 +------- 5 files changed, 135 insertions(+), 22 deletions(-) create mode 100644 pyanaconda/modules/common/structures/keyboard_layout.py diff --git a/pyanaconda/localization.py b/pyanaconda/localization.py index 3e44ebb462a..0c1fd42bf26 100644 --- a/pyanaconda/localization.py +++ b/pyanaconda/localization.py @@ -25,7 +25,9 @@ import re from collections import namedtuple +import iso639 import langtable +from xkbregistry import rxkb from pyanaconda.anaconda_loggers import get_module_logger from pyanaconda.core import constants @@ -36,7 +38,7 @@ log = get_module_logger(__name__) SCRIPTS_SUPPORTED_BY_CONSOLE = {'Latn', 'Cyrl', 'Grek'} - +LayoutInfo = namedtuple("LayoutInfo", ["langs", "desc"]) class LocalizationConfigError(Exception): """Exception class for localization configuration related problems""" @@ -390,6 +392,33 @@ def get_territory_locales(territory): return langtable.list_locales(territoryId=territory) +def _build_layout_infos(): + """Build localized information for keyboard layouts. + + :param rxkb_context: RXKB context (e.g., rxkb.Context()) + :return: Dictionary with layouts and their descriptions + """ + rxkb_context = rxkb.Context() + layout_infos = {} + + for layout in rxkb_context.layouts.values(): + name = layout.name + if layout.variant: + name += f" ({layout.variant})" + + langs = [] + for lang in layout.iso639_codes: + if iso639.find(iso639_2=lang): + langs.append(iso639.to_name(lang)) + + if name not in layout_infos: + layout_infos[name] = LayoutInfo(langs, layout.description) + else: + layout_infos[name].langs.extend(langs) + + return layout_infos + + def get_locale_keyboards(locale): """Function returning preferred keyboard layouts for the given locale. diff --git a/pyanaconda/modules/common/structures/keyboard_layout.py b/pyanaconda/modules/common/structures/keyboard_layout.py new file mode 100644 index 00000000000..c784e92454b --- /dev/null +++ b/pyanaconda/modules/common/structures/keyboard_layout.py @@ -0,0 +1,59 @@ +# +# DBus structure for keyboard layout in localization module. +# +# Copyright (C) 2025 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +from dasbus.structure import DBusData +from dasbus.typing import List, Str # Pylint: disable=wildcard-import + +__all__ = ["KeyboardLayout"] + + +class KeyboardLayout(DBusData): + """Structure representing a keyboard layout.""" + + def __init__(self, layout_id="", description="", langs=""): + self._layout_id = layout_id + self._description = description + self._langs = langs + + @property + def layout_id(self) -> Str: + """Return the keyboard layout ID.""" + return self._layout_id + + @layout_id.setter + def layout_id(self, value: Str): + self._layout_id = value + + @property + def description(self) -> Str: + """Return the description of the layout.""" + return self._description + + @description.setter + def description(self, value: Str): + self._description = value + + @property + def langs(self) -> List[Str]: + """Return the list of associated languages.""" + return self._langs + + @langs.setter + def langs(self, value: List[Str]): + self._langs = value diff --git a/pyanaconda/modules/localization/localization.py b/pyanaconda/modules/localization/localization.py index 627f39dc2f8..05bf6f36c62 100644 --- a/pyanaconda/modules/localization/localization.py +++ b/pyanaconda/modules/localization/localization.py @@ -17,6 +17,8 @@ # License and may only be used or replicated with the express permission of # Red Hat, Inc. # +import gettext + import langtable from pyanaconda.anaconda_loggers import get_module_logger @@ -24,6 +26,7 @@ from pyanaconda.core.dbus import DBus from pyanaconda.core.signal import Signal from pyanaconda.localization import ( + _build_layout_infos, get_available_translations, get_common_languages, get_english_name, @@ -34,6 +37,7 @@ from pyanaconda.modules.common.base import KickstartService from pyanaconda.modules.common.constants.services import LOCALIZATION from pyanaconda.modules.common.containers import TaskContainer +from pyanaconda.modules.common.structures.keyboard_layout import KeyboardLayout from pyanaconda.modules.common.structures.language import LanguageData, LocaleData from pyanaconda.modules.localization.installation import ( KeyboardInstallationTask, @@ -50,6 +54,8 @@ log = get_module_logger(__name__) +Xkb_ = lambda x: gettext.translation("xkeyboard-config", fallback=True).gettext(x) +iso_ = lambda x: gettext.translation("iso_639", fallback=True).gettext(x) class LocalizationService(KickstartService): """The Localization service.""" @@ -80,6 +86,8 @@ def __init__(self): self.compositor_selected_layout_changed = Signal() self.compositor_layouts_changed = Signal() + self._layout_infos = _build_layout_infos() + self._localed_wrapper = None def publish(self): @@ -177,6 +185,26 @@ def get_locale_data(self, locale_id): return tdata + def get_locale_keyboard_layouts(self, lang): + """Get localized keyboard layouts for a given locale. + + :param lang: locale string (e.g., "cs_CZ.UTF-8") + :return: list of dictionaries with keyboard layout information + """ + language_id = lang.split("_")[0].lower() + english_name = get_english_name(language_id) + + return [ + KeyboardLayout( + layout_id=name, + description=Xkb_(info.desc), + langs=info.langs, + ) + # Include only the layouts of the current language + for name, info in self._layout_infos.items() + if english_name in info.langs + ] + @property def language(self): """Return the language.""" diff --git a/pyanaconda/modules/localization/localization_interface.py b/pyanaconda/modules/localization/localization_interface.py index 0b46702583c..76b36e97089 100644 --- a/pyanaconda/modules/localization/localization_interface.py +++ b/pyanaconda/modules/localization/localization_interface.py @@ -25,6 +25,7 @@ from pyanaconda.modules.common.base import KickstartModuleInterface from pyanaconda.modules.common.constants.services import LOCALIZATION from pyanaconda.modules.common.containers import TaskContainer +from pyanaconda.modules.common.structures.keyboard_layout import KeyboardLayout from pyanaconda.modules.common.structures.language import LanguageData, LocaleData @@ -91,6 +92,21 @@ def GetLocaleData(self, locale_id: Str) -> Structure: locale_data = self.implementation.get_locale_data(locale_id) return LocaleData.to_structure(locale_data) + def GetLocaleKeyboardLayouts(self, lang: Str) -> List[Structure]: + """Get keyboard layouts for the specified language. + + For example: [ + {"description": "US layout", "layoutId": "us", "variantId": ""}, + {"description": "Czech QWERTY", "layoutId": "cz", "variantId": "qwerty"} + ] + + :param lang: Language code string (e.g., "en_US.UTF-8") + :return: List of keyboard layout dictionaries + """ + return KeyboardLayout.to_structure_list( + self.implementation.get_locale_keyboard_layouts(lang) + ) + @property def Language(self) -> Str: """The language the system will use.""" diff --git a/pyanaconda/ui/gui/xkl_wrapper.py b/pyanaconda/ui/gui/xkl_wrapper.py index c2261667759..11a2997304f 100644 --- a/pyanaconda/ui/gui/xkl_wrapper.py +++ b/pyanaconda/ui/gui/xkl_wrapper.py @@ -18,23 +18,19 @@ import gettext import threading -from collections import namedtuple -import iso639 from xkbregistry import rxkb from pyanaconda import localization from pyanaconda.core.async_utils import async_action_wait from pyanaconda.core.string import upcase_first_letter from pyanaconda.keyboard import normalize_layout_variant +from pyanaconda.localization import _build_layout_infos from pyanaconda.modules.common.constants.services import LOCALIZATION Xkb_ = lambda x: gettext.translation("xkeyboard-config", fallback=True).gettext(x) iso_ = lambda x: gettext.translation("iso_639", fallback=True).gettext(x) -# namedtuple for information about a keyboard layout (its language and description) -LayoutInfo = namedtuple("LayoutInfo", ["langs", "desc"]) - class XklWrapper: """ Class that used to wrap libxklavier functionality. @@ -62,26 +58,11 @@ def __init__(self): self._rxkb = rxkb.Context() self._layout_infos = {} - self._build_layout_infos() + self._layout_infos = _build_layout_infos() self._switch_opt_infos = {} self._build_switch_opt_infos() - def _build_layout_infos(self): - for layout in self._rxkb.layouts.values(): - name = layout.name - if layout.variant: - name += ' (' + layout.variant + ')' - - langs = [] - for lang in layout.iso639_codes: - if iso639.find(iso639_2=lang): - langs.append(iso639.to_name(lang)) - - if name not in self._layout_infos: - self._layout_infos[name] = LayoutInfo(langs, layout.description) - else: - self._layout_infos[name].langs.extend(langs) def _build_switch_opt_infos(self): for group in self._rxkb.option_groups: