241 lines
8 KiB
Python
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()
|