Skip to content

Commit 1782a7d

Browse files
committed
add ability to change keyboard layout in addition to language
such as for example using colemak
1 parent 77640a5 commit 1782a7d

File tree

3 files changed

+40
-24
lines changed

3 files changed

+40
-24
lines changed

plover/gui_qt/config_window.py

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ class BooleanOption(QCheckBox):
5656

5757
def __init__(self):
5858
super().__init__()
59-
self.stateChanged.connect(lambda: self.valueChanged.emit(self.isChecked()))
59+
self.stateChanged.connect(
60+
lambda: self.valueChanged.emit(self.isChecked()))
6061

6162
def setValue(self, value):
6263
self.setChecked(value)
@@ -83,6 +84,7 @@ def __init__(self):
8384
def setValue(self, value):
8485
self.setText(value)
8586

87+
8688
class ChoiceOption(QComboBox):
8789

8890
valueChanged = pyqtSignal(str)
@@ -272,7 +274,8 @@ def setValue(self, value):
272274
item.setFlags(item.flags() & ~Qt.ItemIsEditable)
273275
self.setItem(row, 0, item)
274276
item = QTableWidgetItem()
275-
item.setFlags((item.flags() & ~Qt.ItemIsEditable) | Qt.ItemIsUserCheckable)
277+
item.setFlags((item.flags() & ~Qt.ItemIsEditable)
278+
| Qt.ItemIsUserCheckable)
276279
item.setCheckState(Qt.Checked if choice in value else Qt.Unchecked)
277280
self.setItem(row, 1, item)
278281
self.resizeColumnsToContents()
@@ -296,7 +299,7 @@ class BooleanAsDualChoiceOption(ChoiceOption):
296299
valueChanged = pyqtSignal(bool)
297300

298301
def __init__(self, choice_false, choice_true):
299-
choices = { False: choice_false, True: choice_true }
302+
choices = {False: choice_false, True: choice_true}
300303
super().__init__(choices)
301304

302305

@@ -363,10 +366,13 @@ def __init__(self, engine):
363366
(_('Machine'), (
364367
ConfigOption(_('Machine:'), 'machine_type', partial(ChoiceOption, choices=machines),
365368
dependents=(
366-
('machine_specific_options', self._update_machine_options),
367-
('system_keymap', lambda v: self._update_keymap(machine_type=v)),
368-
)),
369-
ConfigOption(_('Options:'), 'machine_specific_options', self._machine_option),
369+
('machine_specific_options',
370+
self._update_machine_options),
371+
('system_keymap', lambda v: self._update_keymap(
372+
machine_type=v)),
373+
)),
374+
ConfigOption(
375+
_('Options:'), 'machine_specific_options', self._machine_option),
370376
ConfigOption(_('Keymap:'), 'system_keymap', KeymapOption),
371377
)),
372378
# i18n: Widget: “ConfigWindow”.
@@ -404,16 +410,15 @@ def __init__(self, engine):
404410
'programs time to process each key press.\n'
405411
'Setting the delay too high will negatively impact the\n'
406412
'performance of key stroke output.')),
413+
# There are also the rules, model and options, but i don't think they affect the output of alphanumeric characters
407414
ConfigOption(_('Keyboard Layout:'), 'xkb_layout', StrOption,
408415
_('Set the keyboard layout configured in your system.\n'
409416
'Examples: "us", "gb", "fr", "no"\n'
410417
'\n'
411418
'This only applies when using Linux/BSD and not using X11.\n'
412419
'If you\'re unsure, you probably don\'t need to change it.\n'
413-
'If you need to configure more options about your layout,\n'
414-
'such as setting the variant to a different layout like colemak,\n'
415-
'you can set environment variables starting with XKB_DEFAULT_\n'
416-
'for the RULES, MODEL, VARIANT and OPTIONS')),
420+
'If you use a different layout variant, format it as\n'
421+
'"language:layout", for example "us:colemak"')),
417422
)),
418423
# i18n: Widget: “ConfigWindow”.
419424
(_('Plugins'), (
@@ -432,16 +437,18 @@ def __init__(self, engine):
432437
for plugin in registry.list_plugins('system')
433438
}),
434439
dependents=(
435-
('system_keymap', lambda v: self._update_keymap(system_name=v)),
436-
)),
440+
('system_keymap', lambda v: self._update_keymap(
441+
system_name=v)),
442+
)),
437443
)),
438444
)
439445
# Only keep supported options, to avoid messing with things like
440446
# dictionaries, that are handled by another (possibly concurrent)
441447
# dialog.
442448
self._supported_options = set()
443449
for section, option_list in mappings:
444-
self._supported_options.update(option.option_name for option in option_list)
450+
self._supported_options.update(
451+
option.option_name for option in option_list)
445452
self._update_config()
446453
# Create and fill tabs.
447454
option_by_name = {}
@@ -471,7 +478,8 @@ def __init__(self, engine):
471478
for option_name, update_fn in option.dependents
472479
]
473480
self.buttons.button(QDialogButtonBox.Ok).clicked.connect(self.on_apply)
474-
self.buttons.button(QDialogButtonBox.Apply).clicked.connect(self.on_apply)
481+
self.buttons.button(
482+
QDialogButtonBox.Apply).clicked.connect(self.on_apply)
475483
self.tabs.currentWidget().setFocus()
476484
self.restore_state()
477485
self.finished.connect(self.save_state)

plover/oslayer/linux/keyboardcontrol_uinput.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -166,16 +166,21 @@
166166
KEY_TO_KEYCODE = {**keys, **modifiers}
167167
KEYCODE_TO_KEY = dict(zip(KEY_TO_KEYCODE.values(), KEY_TO_KEYCODE.keys()))
168168

169+
169170
class KeyboardEmulation(GenericKeyboardEmulation):
170171
def __init__(self):
171172
super().__init__()
172173
# Initialize UInput with all keys available
173174
self._res = util.find_ecodes_by_regex(r"KEY_.*")
174175
self._ui = UInput(self._res)
175176

176-
def _update_layout(self, layout):
177-
log.info("Using keyboard layout " + layout + " for keyboard emulation.")
178-
symbols = generate_symbols(layout)
177+
def _update_layout(self, _layout):
178+
log.info("Using keyboard layout " +
179+
_layout + " for keyboard emulation.")
180+
_layout_options = _layout.split(":")
181+
layout = _layout_options[0]
182+
variant = _layout_options[1] if len(_layout_options) > 1 else ""
183+
symbols = generate_symbols(layout, variant)
179184
# Remove symbols not in KEY_TO_KEYCODE
180185
syms_to_remove = []
181186
for sym in symbols:
@@ -203,6 +208,7 @@ def _send_key(self, key):
203208
the same keybinding, it's too fast for it to handle and ends up writing random stuff. I don't
204209
think there is a way to fix that other than increasing the delay.
205210
"""
211+
206212
def _send_unicode(self, hex):
207213
self.send_key_combination("ctrl_l(shift(u))")
208214
self.delay()
@@ -248,7 +254,7 @@ def send_key_combination(self, combo):
248254
else:
249255
k = KEY_TO_KEYCODE[key.lower()]
250256
self._press_key(k, pressed)
251-
except KeyError: # In case the key does not exist
257+
except KeyError: # In case the key does not exist
252258
log.warning("Key " + key + " is not valid!")
253259

254260

@@ -265,7 +271,8 @@ def __init__(self):
265271

266272
def _get_devices(self):
267273
input_devices = [InputDevice(path) for path in list_devices()]
268-
keyboard_devices = [dev for dev in input_devices if self._filter_devices(dev)]
274+
keyboard_devices = [
275+
dev for dev in input_devices if self._filter_devices(dev)]
269276
return {dev.fd: dev for dev in keyboard_devices}
270277

271278
def _filter_devices(self, device):
@@ -274,7 +281,7 @@ def _filter_devices(self, device):
274281
"""
275282
is_uinput = device.name == "py-evdev-uinput" or device.phys == "py-evdev-uinput"
276283
capabilities = device.capabilities()
277-
is_mouse = e.EV_REL in capabilities or e.EV_ABS in capabilities
284+
is_mouse = e.EV_REL in capabilities or e.EV_ABS in capabilities
278285
return not is_uinput and not is_mouse
279286

280287
def start(self):
@@ -321,6 +328,7 @@ def _run(self):
321328
if key_name in self._suppressed_keys:
322329
pressed = event.value == 1
323330
(self.key_down if pressed else self.key_up)(key_name)
324-
continue # Go to the next iteration, skipping the below code:
331+
# Go to the next iteration, skipping the below code:
332+
continue
325333
self._ui.write(e.EV_KEY, event.code, event.value)
326334
self._ui.syn()

plover/oslayer/linux/xkb_symbols.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88

99

1010
# layout can be "no", "us", "gb", "fr" or any other xkb layout
11-
def generate_symbols(layout="us"):
11+
def generate_symbols(layout="us", variant=""):
1212
ctx = xkb.Context()
13-
keymap = ctx.keymap_new_from_names(layout=layout)
13+
keymap = ctx.keymap_new_from_names(layout=layout, variant=variant)
1414
# The keymaps have to be "translated" to a US layout keyboard for evdev
1515
keymap_us = ctx.keymap_new_from_names(layout="us")
1616

0 commit comments

Comments
 (0)