-
Notifications
You must be signed in to change notification settings - Fork 358
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use keyboard configuration from Gnome Shell live #4976
Changes from 1 commit
21fbce0
d1b6645
a915755
b8d4dbf
5dc8832
696f1f5
a9ecfc4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
This configuration will be used if no keyboard layout is set by user and Anaconda is running on supported live environment. This solution is integrated to the already existing `get_missing_keyboard_configuration` function before using the DEFAULT_KEYBOARD layout (US). Before this change the logic was: - use configuration if set by user - if only keyboard layout or virtual console is set convert the other by localed - if nothing set use DEFAULT_KEYBOARD After this change: - use configuration if set by user - if on supported live environment, read the current keyboard configuration - if only keyboard layout or virtual console is set convert the other by localed - if nothing set use DEFAULT_KEYBOARD Currently, only Gnome Shell is supported but we should extend this solution ideally to all Wayland based spins. Wayland is hostile to Anaconda because it don't allow us to control live keyboard configuration. Live environment now have configuration of the keyboard separate from the Anaconda configuration, which is used for the installed system. That means that users can write passwords with other layout than what is installed (especially problem for LUKS).
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
# Copyright (C) 2023 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 abc import ABC, abstractmethod | ||
from pyanaconda.core.util import execWithCaptureAsLiveUser | ||
from pyanaconda.core.configuration.anaconda import conf | ||
|
||
from pyanaconda.anaconda_loggers import get_module_logger | ||
log = get_module_logger(__name__) | ||
|
||
|
||
def get_live_keyboard_instance(): | ||
"""Return instance of a class based on the current running live system. | ||
|
||
:return: instance of a LiveSystemKeyboardBase based class for the current live environment | ||
:rtype: instance of class inherited from LiveSystemKeyboardBase class | ||
""" | ||
if conf.system.provides_liveuser: | ||
return GnomeShellKeyboard() | ||
|
||
# TODO: Add support for other Live systems | ||
return None | ||
|
||
|
||
class LiveSystemKeyboardBase(ABC): | ||
|
||
@abstractmethod | ||
def read_keyboard_layouts(self): | ||
"""Read keyboard configuration from the current system. | ||
|
||
The configuration have to be returned in format which is understandable by us for the | ||
installation. That means localed will understand it. | ||
|
||
:return: a list of "layout (variant)" or "layout" layout specifications | ||
:rtype: list(str) | ||
""" | ||
pass | ||
|
||
@staticmethod | ||
def _run_as_liveuser(argv): | ||
"""Run the command in a system as liveuser user. | ||
|
||
:param list argv: list of arguments for the command. | ||
:return: output of the command | ||
:rtype: str | ||
""" | ||
return execWithCaptureAsLiveUser(argv[0], argv[1:]) | ||
|
||
|
||
class GnomeShellKeyboard(LiveSystemKeyboardBase): | ||
|
||
def read_keyboard_layouts(self): | ||
"""Read keyboard configuration from the current system. | ||
|
||
The configuration have to be returned in format which is understandable by us for the | ||
installation. That means localed will understand it. | ||
|
||
:return: a list of "layout (variant)" or "layout" layout specifications | ||
:rtype: list(str) | ||
""" | ||
command_args = ["gsettings", "get", "org.gnome.desktop.input-sources", "sources"] | ||
sources = self._run_as_liveuser(command_args) | ||
|
||
# convert output "[('xkb', 'us'), ('xkb', 'cz+qwerty')]\n" | ||
try: | ||
sources = eval(sources.rstrip()) | ||
except SyntaxError: | ||
log.error("Gnome Shell keyboard configuration can't be obtained from source %s!", | ||
sources) | ||
return [] | ||
|
||
result = [] | ||
|
||
for t in sources: | ||
# keep only 'xkb' type and ignore 'ibus' variants which can't be used in localed | ||
if t[0] == "xkb": | ||
layout = t[1] | ||
# change layout variant from 'cz+qwerty' to 'cz (qwerty)' | ||
if '+' in layout: | ||
layout, variant = layout.split('+') | ||
result.append(f"{layout} ({variant})") | ||
else: | ||
result.append(layout) | ||
|
||
return result |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,6 +38,8 @@ | |
from pyanaconda.modules.localization.installation import LanguageInstallationTask, \ | ||
KeyboardInstallationTask, write_vc_configuration, VC_CONF_FILE_PATH, write_x_configuration, \ | ||
X_CONF_DIR, X_CONF_FILE_NAME | ||
from pyanaconda.modules.localization.live_keyboard import GnomeShellKeyboard, \ | ||
get_live_keyboard_instance | ||
from pyanaconda.modules.localization.localization import LocalizationService | ||
from pyanaconda.modules.localization.localed import LocaledWrapper | ||
from pyanaconda.modules.localization.localization_interface import LocalizationInterface | ||
|
@@ -657,6 +659,7 @@ def test_get_missing_keyboard_configuration_task(self, get_missing_mock): | |
def _get_missing_keyboard_configuration_test(self, | ||
x_layouts, | ||
converted_x_layouts, | ||
live_x_layouts, | ||
vc_keymap, | ||
converted_vc_keymap, | ||
result_x_layouts, | ||
|
@@ -665,12 +668,20 @@ def _get_missing_keyboard_configuration_test(self, | |
localed.convert_keymap.return_value = converted_vc_keymap | ||
localed.convert_layouts.return_value = converted_x_layouts | ||
|
||
result = get_missing_keyboard_configuration( | ||
localed, | ||
x_layouts, | ||
vc_keymap | ||
) | ||
assert result == (result_x_layouts, result_vc_keymap) | ||
live_keyboard = Mock() | ||
live_keyboard.read_keyboard_layouts.return_value = live_x_layouts | ||
|
||
with patch("pyanaconda.modules.localization.utils.get_live_keyboard_instance") as \ | ||
get_live_keyboard_mock: | ||
|
||
get_live_keyboard_mock.return_value = live_keyboard | ||
|
||
result = get_missing_keyboard_configuration( | ||
localed, | ||
x_layouts, | ||
vc_keymap | ||
) | ||
assert result == (result_x_layouts, result_vc_keymap) | ||
|
||
def test_get_missing_keyboard_configuration(self): | ||
"""Test the get_missing_keyboard_configuration.""" | ||
|
@@ -679,6 +690,7 @@ def test_get_missing_keyboard_configuration(self): | |
self._get_missing_keyboard_configuration_test( | ||
x_layouts=[], | ||
converted_x_layouts="", | ||
live_x_layouts=[], | ||
vc_keymap="", | ||
converted_vc_keymap=[DEFAULT_KEYBOARD], | ||
result_x_layouts=[DEFAULT_KEYBOARD], | ||
|
@@ -688,6 +700,7 @@ def test_get_missing_keyboard_configuration(self): | |
self._get_missing_keyboard_configuration_test( | ||
x_layouts=["cz (qwerty)"], | ||
converted_x_layouts="cz-qwerty", | ||
live_x_layouts=[], | ||
vc_keymap="us", | ||
converted_vc_keymap=["us"], | ||
result_x_layouts=["cz (qwerty)"], | ||
|
@@ -697,6 +710,7 @@ def test_get_missing_keyboard_configuration(self): | |
self._get_missing_keyboard_configuration_test( | ||
x_layouts=["cz (qwerty)"], | ||
converted_x_layouts="cz-qwerty", | ||
live_x_layouts=[], | ||
vc_keymap="", | ||
converted_vc_keymap=[""], | ||
result_x_layouts=["cz (qwerty)"], | ||
|
@@ -706,11 +720,22 @@ def test_get_missing_keyboard_configuration(self): | |
self._get_missing_keyboard_configuration_test( | ||
x_layouts=[], | ||
converted_x_layouts="", | ||
live_x_layouts=[], | ||
vc_keymap="us", | ||
converted_vc_keymap=["us"], | ||
result_x_layouts=["us"], | ||
result_vc_keymap="us", | ||
) | ||
# Take layouts from Live system (vc will be converted from Live layouts) | ||
self._get_missing_keyboard_configuration_test( | ||
x_layouts=[], | ||
converted_x_layouts="cz", | ||
live_x_layouts=["cz", "cz (qwerty)"], | ||
vc_keymap="", | ||
converted_vc_keymap=[""], | ||
result_x_layouts=["cz", "cz (qwerty)"], | ||
result_vc_keymap="cz", | ||
) | ||
|
||
@patch("pyanaconda.modules.localization.runtime.conf") | ||
@patch("pyanaconda.modules.localization.runtime.try_to_load_keymap") | ||
|
@@ -1088,3 +1113,72 @@ def test_localed_wrapper_no_systembus(self, mocked_system_bus): | |
mocked_system_bus.check_connection.return_value = False | ||
localed_wrapper = LocaledWrapper() | ||
self._guarded_localed_wrapper_calls_check(localed_wrapper) | ||
|
||
|
||
class LiveSystemKeyboardTestCase(unittest.TestCase): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider doing this in a new test file matching the new module file. This whole file could use some splitting anyway, but that's another independent PR... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I do that I should also split localed test from this to keep consistency. However, not sure if it should be part of this PR (it is already big). Let's handle it as followup. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes - #5029. |
||
@patch("pyanaconda.modules.localization.live_keyboard.conf") | ||
def test_get_live_keyboard_instance(self, mocked_conf): | ||
"""Test get_live_keyboard_instance function.""" | ||
mocked_conf.system.provides_liveuser = True | ||
assert isinstance(get_live_keyboard_instance(), GnomeShellKeyboard) | ||
|
||
mocked_conf.reset_mock() | ||
mocked_conf.system.provides_liveuser = False | ||
assert get_live_keyboard_instance() is None | ||
|
||
def _check_gnome_shell_layouts_conversion(self, mocked_exec_with_capture, system_input, output): | ||
mocked_exec_with_capture.reset_mock() | ||
mocked_exec_with_capture.return_value = system_input | ||
|
||
gs = GnomeShellKeyboard() | ||
|
||
assert gs.read_keyboard_layouts() == output | ||
mocked_exec_with_capture.assert_called_once_with( | ||
"gsettings", | ||
["get", "org.gnome.desktop.input-sources", "sources"] | ||
) | ||
|
||
@patch("pyanaconda.modules.localization.live_keyboard.execWithCaptureAsLiveUser") | ||
def test_gnome_shell_keyboard(self, mocked_exec_with_capture): | ||
"""Test GnomeShellKeyboard live instance.""" | ||
# test one simple layout set | ||
self._check_gnome_shell_layouts_conversion( | ||
mocked_exec_with_capture=mocked_exec_with_capture, | ||
system_input=r"[('xkb', 'cz')]", | ||
output=["cz"] | ||
) | ||
|
||
# test one complex layout is set | ||
self._check_gnome_shell_layouts_conversion( | ||
mocked_exec_with_capture=mocked_exec_with_capture, | ||
system_input=r"[('xkb', 'cz+qwerty')]", | ||
output=["cz (qwerty)"] | ||
) | ||
|
||
# test multiple layouts are set | ||
self._check_gnome_shell_layouts_conversion( | ||
mocked_exec_with_capture=mocked_exec_with_capture, | ||
system_input=r"[('xkb', 'cz+qwerty'), ('xkb', 'us'), ('xkb', 'cz+dvorak-ucw')]", | ||
output=["cz (qwerty)", "us", "cz (dvorak-ucw)"] | ||
) | ||
|
||
# test layouts with ibus (ibus is ignored) | ||
self._check_gnome_shell_layouts_conversion( | ||
mocked_exec_with_capture=mocked_exec_with_capture, | ||
system_input=r"[('xkb', 'cz'), ('ibus', 'libpinyin')]", | ||
output=["cz"] | ||
) | ||
|
||
# test only ibus layout | ||
self._check_gnome_shell_layouts_conversion( | ||
mocked_exec_with_capture=mocked_exec_with_capture, | ||
system_input=r"[('ibus', 'libpinyin')]", | ||
output=[] | ||
) | ||
|
||
# test wrong input | ||
self._check_gnome_shell_layouts_conversion( | ||
mocked_exec_with_capture=mocked_exec_with_capture, | ||
system_input=r"wrong input", | ||
output=[] | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it would be a good idea to note there what formats are supported ? Or at least what tooling is going to work with them, so one can find out the supported formats transitively. Is it just
localed
or also something else ?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is specified in the return value of the docstring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh right, now I see it. :)