fix(macos): capture side-button gestures over HID++ - #722
Conversation
Greptile SummaryThe PR replaces anonymous macOS Back/Forward button handling with device-specific HID++ edges while retaining global pointer movement for swipe resolution.
Confidence Score: 3/5The PR does not yet appear safe to merge because unrelated global pointer movement can resolve a device-owned gesture, and Accessibility teardown can still divert a side button after its movement source is gone. The HID++ edge path establishes button ownership but the macOS movement event remains unattributed and is accumulated without an ownership check; separately, hook revocation does not wait for asynchronous HID++ disarm, leaving a window where a gesture cannot receive movement and may resolve as its click action. Files Needing Attention: crates/openlogi-agent-core/src/hook_runtime.rs, crates/openlogi-agent-core/src/side_gesture.rs, crates/openlogi-agent-core/src/watchers/gesture.rs, crates/openlogi-agent/src/main.rs
|
| Filename | Overview |
|---|---|
| crates/openlogi-agent-core/src/capture_plan.rs | Builds macOS HID++ side-gesture plans while honoring capture opt-out and hook availability. |
| crates/openlogi-agent-core/src/hook_runtime.rs | Integrates device-owned gesture state into the global movement callback. |
| crates/openlogi-agent-core/src/side_gesture.rs | Implements synchronized per-device hold, swipe, click, cancellation, and stale-hold handling. |
| crates/openlogi-agent-core/src/watchers/gesture.rs | Reconciles per-device capture sessions and dispatches verified HID++ button edges. |
| crates/openlogi-agent/src/main.rs | Connects hook availability and shared side-gesture state to daemon lifecycle management. |
| crates/openlogi-hid/src/session/gesture.rs | Adds both-edge standard-button capture, registry-owned channels, reconnect re-arming, and channel replacement detection. |
Sequence Diagram
sequenceDiagram
participant Mouse as Logitech mouse
participant HID as HID++ session
participant State as SideGestureRuntime
participant Hook as Global OS movement hook
participant Dispatch as Action dispatcher
Mouse->>HID: Back/Forward down
HID->>State: Begin verified device hold
Hook->>State: Global pointer deltas
State-->>Dispatch: Swipe action on threshold
Mouse->>HID: Back/Forward up
HID->>State: End verified hold
State-->>Dispatch: Click action if no swipe committed
Reviews (5): Last reviewed commit: "fix(agent): preserve side buttons during..." | Re-trigger Greptile
| let _ = try_queue_action(action_tx, action, None); | ||
| } | ||
| } | ||
| let device_commit = side_gesture.try_accumulate(delta_x, delta_y); |
There was a problem hiding this comment.
Unrelated movement resolves device gesture
When a Logitech Back or Forward hold is active while a trackpad or another mouse moves the pointer, every global movement delta enters the shared side-gesture accumulator, causing the unrelated device's movement to trigger the Logitech mouse's configured swipe action.
Knowledge Base Used: openlogi-agent-core
Summary
I ran into this while configuring gestures on the Forward button of my MX Master 3. The configuration looked valid, but clicking Forward only performed the normal browser action, and holding the button while moving the mouse never triggered a gesture.
While debugging it, I captured the macOS Back/Forward events directly and found that every Forward press and release arrived with
device=None. OpenLogi correctly rejects unattributed events in its global macOS hook to avoid remapping input from unrelated devices, so the gesture hold never began.My first attempt allowed senderless Back/Forward events through that hook. Review feedback pointed out the important flaw in that approach: without a source identity, OpenLogi cannot prove that the event came from the configured Logitech mouse.
This changes the implementation to get the Back/Forward button lifecycle from the mouse's device-specific HID++ channel instead. The global hook is only used for pointer movement while a verified HID++ hold is active, so anonymous button events remain blocked.
Changes
Testing
I first built a small event-source diagnostic and confirmed that the MX Master 3 consistently reported Forward events as
device=None.I then built and ran the agent against an isolated copy of my real configuration. On the physical mouse I verified:
During reconnect testing, turning the mouse off and back on exposed a second issue: diversion was briefly restored and then lost when inventory replaced the HID++ connection. After moving capture onto the inventory-owned channel and restarting it when that channel changes, I repeated the power-cycle test and confirmed that all gestures continued working afterward.
The repository validation also passes:
cargo fmt --all -- --checkcargo clippy --workspace --all-targets -- -D warningscargo test --workspaceFixes #716