* feat(shortcuts): configurable modifier+key hotkeys (e.g. Alt+R) Generalize the shortcut system beyond single-modifier double-tap/hold to support modifier+key combos (alt+r, ctrl+alt+r, super+space) in both toggle (press once) and push-to-talk (hold) modes. - base.py: ShortcutSpec + parse_shortcut_spec/is_valid_shortcut/format label; parse_shortcut kept for back-compat (bare modifiers still rejected). - evdev + pynput backends: combo detection with global (cross-device) modifier tracking so split keyboards work; legacy gestures unchanged. - keyboard_shortcuts: validate via is_valid_shortcut instead of a fixed allowlist. - settings_dialog: additive Custom Shortcut row (typed entry + live Record capture). - tests: 34 new tests (parser, back-compat, evdev + pynput combo, split keyboard). * fix(shortcuts): apply saved shortcut (not stale preset) on mode change When a custom shortcut (e.g. alt+r) is active, the preset combo still points at a default/previous preset. _on_shortcut_mode_changed read the combo, so toggling Toggle<->Push-to-Talk restarted the live listener on the wrong shortcut while config still said alt+r. Read the saved shortcut from config instead. Addresses Codex P2 review on PR #493. * style: apply black formatting to configurable-hotkeys files * test: cover _gdk_keyname_to_token shortcut-capture mapping * fix(shortcuts): reset combo state after dropped/lost evdev events SYN_DROPPED and device-disconnect recovery cleared key_pressed_devices but not the combo state. A dropped modifier *release* left _combo_pressed thinking the modifier was held, so the main key alone could falsely toggle/start dictation, and a push-to-talk hold could stay stuck. Add _reset_combo_state() (clears the pressed set and ends any live hold) and call it from both recovery paths. Addresses maintainer review on PR #493. * fix(settings): combo-aware toggle label (single press, not double-tap) _update_shortcut_ui_for_mode always said 'Double-tap the key' for toggle mode, but a modifier+key combo (e.g. alt+r) toggles on a single press. Read the saved shortcut and use get_shortcut_display_name so a combo shows 'Press Alt+R'. Addresses maintainer review on PR #493. --------- Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
38 lines
1.2 KiB
Python
38 lines
1.2 KiB
Python
"""Tests for the shortcut-capture keyname mapping in the settings dialog.
|
|
|
|
Covers the pure `_gdk_keyname_to_token` helper that turns a GDK key-symbol name
|
|
(from the "Record shortcut" capture) into a canonical main-key token. The GTK
|
|
dialog itself can't be instantiated under the mocked-GTK test harness, but this
|
|
module-level helper is pure and directly testable.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from vocalinux.ui.settings_dialog import _gdk_keyname_to_token
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"name,expected",
|
|
[
|
|
("r", "r"),
|
|
("A", "a"), # capture may report a shifted/upper name
|
|
("5", "5"),
|
|
("F5", "f5"),
|
|
("f12", "f12"),
|
|
("space", "space"),
|
|
("Return", "enter"),
|
|
("Escape", "esc"),
|
|
("Page_Up", "pageup"),
|
|
("comma", "comma"),
|
|
("bracketleft", "leftbracket"),
|
|
],
|
|
)
|
|
def test_maps_known_keys(name, expected):
|
|
assert _gdk_keyname_to_token(name) == expected
|
|
|
|
|
|
@pytest.mark.parametrize("name", ["Control_L", "Alt_R", "Shift_L", "Super_L", "", None, "F25"])
|
|
def test_rejects_modifiers_and_unknown(name):
|
|
# Modifiers, empty/None, and out-of-range function keys are not main keys.
|
|
assert _gdk_keyname_to_token(name) is None
|