Extended the Thumb button functionalities#227
Open
Joseph-Azzam wants to merge 1 commit into
Open
Conversation
-Added on Thumb Button Press and Release events -Added ability to run custom CLI commands -Added an option for the mouse to move while thumb button is pressed P.S. I used AI for this for personal use, so if you want to use it, inspect it well.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
tl;dr
-Added on Thumb Button Press and Release events
-Added ability to run custom CLI commands
-Added an option for the mouse to move while thumb button is pressed
P.S. I used AI for this for personal use, so if you want to use it, inspect it well.
Summary
Extends Mouser's handling of the MX Master-style gesture/thumb button with three related improvements:
Along the way, this also fixes a real bug where the gesture button's cursor-freeze behavior could persist on the device after Mouser closed, and adds a "lock cursor while swiping" toggle with synthetic movement injection as a workaround for the freeze while swipe detection is active.
1. Thumb button press + release detection
Previously the gesture/thumb button only exposed a single "tap action" mapping, fired on release. This PR:
gesture→gesture_release(UI label: "TAP RELEASE ACTION").gesture_pressmapping (UI label: "TAP PRESS ACTION"), wired to two new internal events (gesture_down/gesture_up) fired directly from each platform's HID++ gesture button handler (mouse_hook_windows.py,mouse_hook_macos.py,mouse_hook_linux.py).gesture_down/gesture_up(notpress/release) deliberately, since the codebase already usesup/down/left/rightfor swipe directions —gesture_press/gesture_releasestay reserved for the mapping keys,gesture_down/gesture_upfor the physical button state, and swipe direction names are untouched.core/config.py::_migrate) that renames any existinggesturemapping togesture_releaseper profile and seedsgesture_press: "none".gesture_release/gesture_presskey rename through the device/button catalogs (core/logi_devices.py,core/logi_device_catalog.py,core/device_layouts.py) so per-device supported-button lists and hotspot layouts stay consistent.MousePage.qmlto show both dropdowns, plus a combined summary label (e.g.Press: Ctrl | Tap: Copy | Swipes configured).2. Hold-to-press semantics for
gesture_pressIf a user maps
gesture_pressto a plain keyboard shortcut or a mouse click, it now behaves like actually holding that key/button down, not clicking it once:core/key_simulator.pyaddssend_key_down(keys)/send_key_up(keys)(all three platforms — WindowsSendInput, macOSCGEventCreateKeyboardEvent, Linuxuinput), plus higher-level helpers:_resolve_holdable_keys(action_id)— returns the underlying key list for an action if it's a plain shortcut/custom combo, orNonefor actions that can't sensibly be "held" (multi-tap zoom, screenshots, run-command, media-key sequences, browser-nav phased taps, etc).is_holdable_key_action(action_id),press_action_down(action_id),press_action_up(action_id).core/engine.py::_setup_hooks()now checksis_holdable_key_action()when wiring up any button event pair that has a matching_down/_upevent (this already applied to mouse-button actions; it's now extended to holdable keyboard actions). Registers_make_key_down_handler/_make_key_up_handler, mirroring the existing mouse-button-hold pattern, including a 20s safety auto-release timer in case the_upevent is ever missed (e.g. device disconnects mid-hold).3. Run Terminal/CLI Command action
Adds a new action type,
run:<command line>, available from every action dropdown (gesture press/release, swipes, back/forward, scroll-tilt, other mouse buttons):core/key_simulator.py:parse_run_command()splits the command line withshlex(POSIX mode on macOS/Linux, non-POSIX + quote-stripping on Windows so backslash paths survive).execute_run_command()always launches withsubprocess.Popen(argv, shell=False, ...)on a daemon worker thread, stdio redirected toDEVNULL,CREATE_NO_WINDOWon Windows — so a slow-starting process can't block the mouse hook, and shell metacharacters (|,&&,>, ...) in the text are never interpreted, only passed as literal argv.ui/backend.py: adds the__run_command__sentinel to the action list,run_command_label()/runCommandTextFor()for display, andrunCommandValidationErrorInfo()for inline validation.ui/qml/RunCommandDialog.qml— a small modal for entering/editing the command line, wired into every action-picker combo box and button chip inMousePage.qml.run_command.*) inui/locale_manager.py(en, zh-Hans, zh-Hant).4. "Lock cursor position while swiping" + root-cause fix for the freeze
This part went through a couple of iterations after testing surfaced a real bug — documenting the full story since it affects how this should be reviewed:
The setting: a new
gesture_lock_cursorconfig flag (defaultTrue, matches prior behavior) and QML switch. When unchecked, and swipe actions need raw sensor data, Mouser synthesizes cursor movement (inject_mouse_move()—SendInput/CGEventCreateMouseEvent/uinputEV_RELper platform) from the same raw XY deltas already used for swipe detection, since the gesture button suppresses normal cursor movement at the firmware level while held, independent of any HID++ divert request.Root cause found:
HidGestureListener.stop()(core/hid_gesture.py) closed the HID device handle directly without ever calling_undivert(). Since_undivert()only ran inside the listener thread's own loop-exit cleanup, andstop()never gave it a chance to run, whatever divert/raw-XY state was last active on the device stayed in effect for the rest of that wireless session — even after Mouser fully quit. This is what made the freeze look hardware-inherent during earlier testing (a stale raw-XY divert from a previous session was still active), and also what made the "still moving when the checkbox is checked" regression possible when the last session had turned raw-XY off. Fixed by calling_undivert()(best-effort, exception-swallowed) before closing the device instop().Also fixed: toggling the setting used to call
force_reconnect(), which drops and fully re-establishes the whole HID++ connection — visible to the user as the mouse disappearing for a few seconds. Replaced with an in-place re-divert:set_raw_xy_wanted()now just flags_redivert_requested, which the listener thread's existing pending-command loop (same pattern as_pending_dpi/_pending_smart_shift) picks up and applies via a singleSetCidReportingHID++ write — no reconnect needed.core/mouse_hook_base.pyadds_wants_gesture_raw_xy()(raw XY needed if swipes are configured or the user opted out of locking) and_wants_gesture_movement_injection()(not gesture_lock_cursor), plus_sync_gesture_raw_xy()to push live changes to an already-connected listener.ui/backend.pyaddsgestureLockCursorproperty /setGestureLockCursor()slot, wired throughengine.reload_mappings().Config migration
DEFAULT_CONFIG["version"]bumped9 → 10. Migration renamesgesture→gesture_releaseper profile mapping, seedsgesture_press: "none", and defaultssettings.gesture_lock_cursor = True. Existing users keep their current tap-release mapping; nothing changes in default behavior unless they explicitly configure the new options.Testing
tests/test_config.py,tests/test_engine.py,tests/test_hid_gesture.py,tests/test_key_simulator.py,tests/test_mouse_hook.py,tests/test_backend.py,tests/test_logi_devices.py,tests/test_smart_shift.pycovering: config migration, holdable-key resolution/press/release on all three platforms, run-command parsing/execution/validation,gesture_down/gesture_updispatch per platform, raw-XY want/redivert logic, andstop()calling_undivert()before close.python -m unittest discover -s tests -q— all relevant tests pass; a handful of pre-existing failures remain that are unrelated environment mismatches (Linux/macOS-specific screenshot/startup helpers failing when run on Windows), not caused by this change.Manual test plan
gesture_pressto a plain shortcut (e.g. Ctrl) and verify it acts as a held modifier for as long as the thumb button is held.gesture_releaseto an action and confirm it still fires only on release, as before.|/&&/etc.Disclosure
Built with AI assistance for personal use — please review carefully, especially the HID++ divert/undivert flag handling in
core/hid_gesture.py, before merging.