anaconda/anaconda-40.22.3.13/pyanaconda/keyboard.py
2024-11-14 21:39:56 -08:00

241 lines
8 KiB
Python

#
# Copyright (C) 2012 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.
#
"""
This module provides functions for dealing with keyboard layouts/keymaps in Anaconda.
"""
import re
import langtable
from pyanaconda.core.configuration.anaconda import conf
from pyanaconda import localization
from pyanaconda.core.constants import DEFAULT_KEYBOARD
from pyanaconda.core.util import execWithRedirect
from pyanaconda.modules.common.task import sync_run_task
from pyanaconda.modules.common.constants.services import LOCALIZATION
from pyanaconda.anaconda_loggers import get_module_logger
log = get_module_logger(__name__)
# should match and parse strings like 'cz' or 'cz (qwerty)' regardless of white
# space
LAYOUT_VARIANT_RE = re.compile(r'^\s*([/\w]+)\s*' # layout plus
r'(?:(?:\(\s*([-\w]+)\s*\))' # variant in parentheses
r'|(?:$))\s*') # or nothing
class KeyboardConfigError(Exception):
"""Exception class for keyboard configuration related problems"""
pass
class InvalidLayoutVariantSpec(Exception):
"""
Exception class for errors related to parsing layout and variant specification strings.
"""
pass
def _is_xwayland():
"""Is Anaconda running in XWayland environment?
This can't be easily detected from the Anaconda because Anaconda
is running as XWayland app. Use xisxwayland tool for the detection.
"""
try:
rc = execWithRedirect('xisxwayland', [])
if rc == 0:
return True
log.debug(
"Anaconda doesn't run on XWayland. "
"See xisxwayland --help for more info."
)
except FileNotFoundError:
log.warning(
"The xisxwayland tool is not available! "
"Taking the environment as not Wayland."
)
return False
def can_configure_keyboard():
"""Can we configure the keyboard?
FIXME: This is a temporary solution.
The is_wayland logic is not part of the configuration so we would
have to add it to the configuration otherwise it won't be accessible
in the Anaconda modules.
"""
if not conf.system.can_configure_keyboard:
return False
if conf.system.can_run_on_xwayland and _is_xwayland():
return False
return True
def parse_layout_variant(layout_variant_str):
"""
Parse layout and variant from the string that may look like 'layout' or
'layout (variant)'.
:param layout_variant_str: keyboard layout and variant string specification
:type layout_variant_str: str
:return: the (layout, variant) pair, where variant can be ""
:rtype: tuple
:raise InvalidLayoutVariantSpec: if the given string isn't a valid layout
and variant specification string
"""
match = LAYOUT_VARIANT_RE.match(layout_variant_str)
if not match:
msg = "'%s' is not a valid keyboard layout and variant specification" % layout_variant_str
raise InvalidLayoutVariantSpec(msg)
layout, variant = match.groups()
# groups may be (layout, None) if no variant was specified
return (layout, variant or "")
def join_layout_variant(layout, variant=""):
"""
Join layout and variant to form the commonly used 'layout (variant)'
or 'layout' (if variant is missing) format.
:type layout: string
:type variant: string
:return: 'layout (variant)' or 'layout' string
:rtype: string
"""
if variant:
return "%s (%s)" % (layout, variant)
else:
return layout
def normalize_layout_variant(layout_str):
"""
Normalize keyboard layout and variant specification given as a single
string. E.g. for a 'layout(variant) string missing the space between the
left parenthesis return 'layout (variant)' which is a proper layout and
variant specification we use.
:param layout_str: a string specifying keyboard layout and its variant
:type layout_str: string
"""
layout, variant = parse_layout_variant(layout_str)
return join_layout_variant(layout, variant)
def populate_missing_items(localization_proxy=None):
"""
Function that populates virtual console keymap and X layouts if they
are missing. By invoking systemd-localed methods this function READS AND
WRITES CONFIGURATION FILES (but tries to keep their content unchanged).
:param localization_proxy: DBus proxy of the localization module or None
"""
task_path = localization_proxy.PopulateMissingKeyboardConfigurationWithTask()
task_proxy = LOCALIZATION.get_proxy(task_path)
sync_run_task(task_proxy)
def activate_keyboard(localization_proxy):
"""
Try to setup VConsole keymap and X11 layouts as specified in kickstart.
:param localization_proxy: DBus proxy of the localization module or None
"""
task_path = localization_proxy.ApplyKeyboardWithTask()
task_proxy = LOCALIZATION.get_proxy(task_path)
sync_run_task(task_proxy)
def set_x_keyboard_defaults(localization_proxy, xkl_wrapper):
"""
Set default keyboard settings (layouts, layout switching).
:param localization_proxy: DBus proxy of the localization module or None
:type ksdata: object instance
:param xkl_wrapper: XklWrapper instance
:type xkl_wrapper: object instance
:raise InvalidLocaleSpec: if an invalid locale is given (see
localization.is_valid_langcode)
"""
x_layouts = localization_proxy.XLayouts
# remove all X layouts that are not valid X layouts (unsupported)
valid_layouts = []
for layout in x_layouts:
if xkl_wrapper.is_valid_layout(layout):
valid_layouts.append(layout)
localization_proxy.XLayouts = valid_layouts
if valid_layouts:
# do not add layouts if there are any specified in the kickstart
# (the x_layouts list comes from kickstart)
return
locale = localization_proxy.Language
layouts = localization.get_locale_keyboards(locale)
if layouts:
# take the first locale (with highest rank) from the list and
# store it normalized
new_layouts = [normalize_layout_variant(layouts[0])]
# annoyingly, langtable expects *no* space between layout and
# (variant) here
if not langtable.supports_ascii(layouts[0].replace(" ", "")):
# The default keymap setting should have "us" before the native layout
# which does not support ascii,
# refer: https://bugzilla.redhat.com/show_bug.cgi?id=1039185
new_layouts.insert(0, DEFAULT_KEYBOARD)
else:
log.error("Failed to get layout for chosen locale '%s'", locale)
new_layouts = [DEFAULT_KEYBOARD]
localization_proxy.XLayouts = new_layouts
if can_configure_keyboard():
xkl_wrapper.replace_layouts(new_layouts)
if len(new_layouts) >= 2 and not localization_proxy.LayoutSwitchOptions:
# initialize layout switching if needed
localization_proxy.LayoutSwitchOptions = ["grp:alt_shift_toggle"]
if can_configure_keyboard():
xkl_wrapper.set_switching_options(["grp:alt_shift_toggle"])
# activate the language-default layout instead of the additional
# one
xkl_wrapper.activate_default_layout()