Files
voculinux/tests/test_shortcut_capture.py
T
b3605bf3c6 feat(shortcuts): configurable modifier+key hotkeys (e.g. Alt+R) (#493)
* 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>
2026-07-13 14:43:36 -07:00

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