From 1782a7d58e7b75bd503d8d6815ecefb45c4dca53 Mon Sep 17 00:00:00 2001 From: LilleAila Date: Sat, 27 Jul 2024 09:41:49 +0200 Subject: [PATCH] add ability to change keyboard layout in addition to language such as for example using colemak --- plover/gui_qt/config_window.py | 38 +++++++++++-------- .../oslayer/linux/keyboardcontrol_uinput.py | 22 +++++++---- plover/oslayer/linux/xkb_symbols.py | 4 +- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/plover/gui_qt/config_window.py b/plover/gui_qt/config_window.py index c24818f75..d2a4ad848 100644 --- a/plover/gui_qt/config_window.py +++ b/plover/gui_qt/config_window.py @@ -56,7 +56,8 @@ class BooleanOption(QCheckBox): def __init__(self): super().__init__() - self.stateChanged.connect(lambda: self.valueChanged.emit(self.isChecked())) + self.stateChanged.connect( + lambda: self.valueChanged.emit(self.isChecked())) def setValue(self, value): self.setChecked(value) @@ -83,6 +84,7 @@ def __init__(self): def setValue(self, value): self.setText(value) + class ChoiceOption(QComboBox): valueChanged = pyqtSignal(str) @@ -272,7 +274,8 @@ def setValue(self, value): item.setFlags(item.flags() & ~Qt.ItemIsEditable) self.setItem(row, 0, item) item = QTableWidgetItem() - item.setFlags((item.flags() & ~Qt.ItemIsEditable) | Qt.ItemIsUserCheckable) + item.setFlags((item.flags() & ~Qt.ItemIsEditable) + | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Checked if choice in value else Qt.Unchecked) self.setItem(row, 1, item) self.resizeColumnsToContents() @@ -296,7 +299,7 @@ class BooleanAsDualChoiceOption(ChoiceOption): valueChanged = pyqtSignal(bool) def __init__(self, choice_false, choice_true): - choices = { False: choice_false, True: choice_true } + choices = {False: choice_false, True: choice_true} super().__init__(choices) @@ -363,10 +366,13 @@ def __init__(self, engine): (_('Machine'), ( ConfigOption(_('Machine:'), 'machine_type', partial(ChoiceOption, choices=machines), dependents=( - ('machine_specific_options', self._update_machine_options), - ('system_keymap', lambda v: self._update_keymap(machine_type=v)), - )), - ConfigOption(_('Options:'), 'machine_specific_options', self._machine_option), + ('machine_specific_options', + self._update_machine_options), + ('system_keymap', lambda v: self._update_keymap( + machine_type=v)), + )), + ConfigOption( + _('Options:'), 'machine_specific_options', self._machine_option), ConfigOption(_('Keymap:'), 'system_keymap', KeymapOption), )), # i18n: Widget: “ConfigWindow”. @@ -404,16 +410,15 @@ def __init__(self, engine): 'programs time to process each key press.\n' 'Setting the delay too high will negatively impact the\n' 'performance of key stroke output.')), + # There are also the rules, model and options, but i don't think they affect the output of alphanumeric characters ConfigOption(_('Keyboard Layout:'), 'xkb_layout', StrOption, _('Set the keyboard layout configured in your system.\n' 'Examples: "us", "gb", "fr", "no"\n' '\n' 'This only applies when using Linux/BSD and not using X11.\n' 'If you\'re unsure, you probably don\'t need to change it.\n' - 'If you need to configure more options about your layout,\n' - 'such as setting the variant to a different layout like colemak,\n' - 'you can set environment variables starting with XKB_DEFAULT_\n' - 'for the RULES, MODEL, VARIANT and OPTIONS')), + 'If you use a different layout variant, format it as\n' + '"language:layout", for example "us:colemak"')), )), # i18n: Widget: “ConfigWindow”. (_('Plugins'), ( @@ -432,8 +437,9 @@ def __init__(self, engine): for plugin in registry.list_plugins('system') }), dependents=( - ('system_keymap', lambda v: self._update_keymap(system_name=v)), - )), + ('system_keymap', lambda v: self._update_keymap( + system_name=v)), + )), )), ) # Only keep supported options, to avoid messing with things like @@ -441,7 +447,8 @@ def __init__(self, engine): # dialog. self._supported_options = set() for section, option_list in mappings: - self._supported_options.update(option.option_name for option in option_list) + self._supported_options.update( + option.option_name for option in option_list) self._update_config() # Create and fill tabs. option_by_name = {} @@ -471,7 +478,8 @@ def __init__(self, engine): for option_name, update_fn in option.dependents ] self.buttons.button(QDialogButtonBox.Ok).clicked.connect(self.on_apply) - self.buttons.button(QDialogButtonBox.Apply).clicked.connect(self.on_apply) + self.buttons.button( + QDialogButtonBox.Apply).clicked.connect(self.on_apply) self.tabs.currentWidget().setFocus() self.restore_state() self.finished.connect(self.save_state) diff --git a/plover/oslayer/linux/keyboardcontrol_uinput.py b/plover/oslayer/linux/keyboardcontrol_uinput.py index 071138131..1233a6c1d 100644 --- a/plover/oslayer/linux/keyboardcontrol_uinput.py +++ b/plover/oslayer/linux/keyboardcontrol_uinput.py @@ -166,6 +166,7 @@ KEY_TO_KEYCODE = {**keys, **modifiers} KEYCODE_TO_KEY = dict(zip(KEY_TO_KEYCODE.values(), KEY_TO_KEYCODE.keys())) + class KeyboardEmulation(GenericKeyboardEmulation): def __init__(self): super().__init__() @@ -173,9 +174,13 @@ def __init__(self): self._res = util.find_ecodes_by_regex(r"KEY_.*") self._ui = UInput(self._res) - def _update_layout(self, layout): - log.info("Using keyboard layout " + layout + " for keyboard emulation.") - symbols = generate_symbols(layout) + def _update_layout(self, _layout): + log.info("Using keyboard layout " + + _layout + " for keyboard emulation.") + _layout_options = _layout.split(":") + layout = _layout_options[0] + variant = _layout_options[1] if len(_layout_options) > 1 else "" + symbols = generate_symbols(layout, variant) # Remove symbols not in KEY_TO_KEYCODE syms_to_remove = [] for sym in symbols: @@ -203,6 +208,7 @@ def _send_key(self, key): the same keybinding, it's too fast for it to handle and ends up writing random stuff. I don't think there is a way to fix that other than increasing the delay. """ + def _send_unicode(self, hex): self.send_key_combination("ctrl_l(shift(u))") self.delay() @@ -248,7 +254,7 @@ def send_key_combination(self, combo): else: k = KEY_TO_KEYCODE[key.lower()] self._press_key(k, pressed) - except KeyError: # In case the key does not exist + except KeyError: # In case the key does not exist log.warning("Key " + key + " is not valid!") @@ -265,7 +271,8 @@ def __init__(self): def _get_devices(self): input_devices = [InputDevice(path) for path in list_devices()] - keyboard_devices = [dev for dev in input_devices if self._filter_devices(dev)] + keyboard_devices = [ + dev for dev in input_devices if self._filter_devices(dev)] return {dev.fd: dev for dev in keyboard_devices} def _filter_devices(self, device): @@ -274,7 +281,7 @@ def _filter_devices(self, device): """ is_uinput = device.name == "py-evdev-uinput" or device.phys == "py-evdev-uinput" capabilities = device.capabilities() - is_mouse = e.EV_REL in capabilities or e.EV_ABS in capabilities + is_mouse = e.EV_REL in capabilities or e.EV_ABS in capabilities return not is_uinput and not is_mouse def start(self): @@ -321,6 +328,7 @@ def _run(self): if key_name in self._suppressed_keys: pressed = event.value == 1 (self.key_down if pressed else self.key_up)(key_name) - continue # Go to the next iteration, skipping the below code: + # Go to the next iteration, skipping the below code: + continue self._ui.write(e.EV_KEY, event.code, event.value) self._ui.syn() diff --git a/plover/oslayer/linux/xkb_symbols.py b/plover/oslayer/linux/xkb_symbols.py index 72b863aae..a90745f17 100644 --- a/plover/oslayer/linux/xkb_symbols.py +++ b/plover/oslayer/linux/xkb_symbols.py @@ -8,9 +8,9 @@ # layout can be "no", "us", "gb", "fr" or any other xkb layout -def generate_symbols(layout="us"): +def generate_symbols(layout="us", variant=""): ctx = xkb.Context() - keymap = ctx.keymap_new_from_names(layout=layout) + keymap = ctx.keymap_new_from_names(layout=layout, variant=variant) # The keymaps have to be "translated" to a US layout keyboard for evdev keymap_us = ctx.keymap_new_from_names(layout="us")