Skip to content

fix(agent): include MiddleClick in the macOS HID++ gesture ownership - #1380

Open
tims2002 wants to merge 2 commits into
AprilNEA:masterfrom
tims2002:fix/middleclick-hidpp-gestures
Open

tims2002 wants to merge 2 commits into
AprilNEA:masterfrom
tims2002:fix/middleclick-hidpp-gestures

Conversation

@tims2002

Copy link
Copy Markdown

Problem

Since 0.8.x, gesture-mode Back/Forward are owned by device-specific HID++ raw-XY capture on macOS (hidpp_side_gesture_maps_for), which correctly sidesteps macOS 26 delivering aux-button CGEvents with no backing IOHIDEvent (CGEventCopyIOHIDEvent returns null, so the hook's fail-closed attribution gate drops them — context in #733).

But the filter is Back | Forward only, so a MiddleClick gesture map still rides the OS hook and is silently dead on macOS 26 — no swipes, and its Click action doesn't fire either (the plain click passes through natively). Side buttons work, the wheel button doesn't, which makes it look device-specific when it isn't.

Fix

Include ButtonId::MiddleClick in the filter. Its CID (0x0052) is already in DIVERTABLE_STANDARD_BUTTONS, and the capture spec / gesture dispatch (side_gesture_bindings.get(&button)) are button-generic, so this one line is the whole change.

Testing

ERGO M575 (Unifying receiver, macOS 26.6, arm64):

  • openlogi diag controls reports 0x0052 as divertable, raw-xy
  • Stock 0.8.3: control capture active … gesture_buttons=2; wheel gesture map dead (all five slots), Back/Forward gestures fine
  • With this change: gesture_buttons=3; a five-slot wheel map (Click/Up/Down/Left/Right → shortcut actions) works end to end, Back/Forward unchanged
  • Wheel single-action binding (plain divert) unaffected in both cases

🤖 Generated with Claude Code

hidpp_side_gesture_maps_for hands gesture-mode buttons to device-owned
HID++ raw-XY capture because macOS 26 delivers aux-button CGEvents with
no backing IOHIDEvent, but it filters to Back | Forward — so a
MiddleClick gesture map still rides the OS hook, whose fail-closed
attribution gate drops those unattributed events. Result: on macOS 26,
side-button gestures work while wheel-button gestures (and their Click
action) are silently dead.

Include MiddleClick in the filter. Its CID (0x0052) is already in
DIVERTABLE_STANDARD_BUTTONS, and the capture/dispatch path is
button-generic, so no other change is needed.

Verified on an ERGO M575 (Unifying receiver, macOS 26.6, arm64):
0x0052 reports `divertable, raw-xy` in `openlogi diag controls`; with
this change the capture session arms `gesture_buttons=3` (was 2) and a
five-slot wheel gesture map (click/up/down/left/right) works end to
end, alongside unchanged Back/Forward gestures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tims2002
tims2002 requested a review from AprilNEA as a code owner September 13, 2026 16:54
@greptile-apps

greptile-apps Bot commented Sep 13, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

The PR does not yet appear safe to merge because the existing capability-fallback issue remains unresolved.

Fix All in CodexFindings

  1. P1 Capture capability fallback missing
  2. P2 MiddleClick branch lacks coverage

Summary

  • Includes MiddleClick alongside Back and Forward in device-specific gesture capture.
  • Verifies MiddleClick and Forward behavior with mouse-hook availability and platform-specific routing.

Reviews (2) · Last reviewed commit: "test(agent): cover MiddleClick in the si..."

oshook_gestures_for(config, Some(config_key), app)
.into_iter()
.filter(|(button, _)| matches!(button, ButtonId::Back | ButtonId::Forward))
.filter(|(button, _)| matches!(button, ButtonId::MiddleClick | ButtonId::Back | ButtonId::Forward))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Capture capability fallback missing

If a device's middle-click control does not support raw XY, adding MiddleClick here removes its gesture map from the macOS hook, while HID++ capture silently skips the unsupported control. Gesture mode is available without checking that capability, so the configured click and swipe actions are handled by neither path and stop working. Keep the OS-hook fallback unless the control can actually be armed.

Fix in Codex Fix in Claude Code

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point in principle, but note the same property holds for the existing Back | Forward ownership: the transfer is unconditional there too, with no raw-XY capability check before the hook gives the button up. This PR deliberately follows that established pattern rather than introducing a divergent policy for one button. A capability-aware fallback (restoring hook ownership when the raw-XY divert can't actually arm) would need the capture session's arm result fed back into the capture plan — a design change that should cover all three buttons together; happy to take a swing at it as a follow-up if the maintainer wants that direction. Test coverage for the MiddleClick branch added in eb6e746.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Since Back/Forward already use unconditional ownership transfer, requiring a capability check only for MiddleClick would introduce an inconsistent policy. A correct fallback needs the capture arm result fed back into planning for all three buttons, so that is appropriately scoped as a follow-up. The added test covers MiddleClick’s hook-availability transitions and raw-XY ownership. This comment is resolved.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

oshook_gestures_for(config, Some(config_key), app)
.into_iter()
.filter(|(button, _)| matches!(button, ButtonId::Back | ButtonId::Forward))
.filter(|(button, _)| matches!(button, ButtonId::MiddleClick | ButtonId::Back | ButtonId::Forward))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 MiddleClick branch lacks coverage

The new MiddleClick ownership branch has no capture-plan regression test, although nearby tests verify the same behavior for Forward. Add a MiddleClick gesture-mode case that checks side_gesture_bindings, the (0x0052, MiddleClick) capture request, and the platform and opt-out behavior; otherwise this macOS fix can regress without failing the test suite.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex Fix in Claude Code

Generalize macos_side_gesture_capture_follows_mouse_hook_availability
into a per-button helper and run it for Forward and MiddleClick, so the
new MiddleClick ownership branch cannot regress silently: the test
checks the raw-XY capture request, the hook-map exclusion on macOS, and
the hook-availability opt-in/opt-out transitions for both buttons.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@davidbudnick davidbudnick added type: bug Something is broken or behaves incorrectly platform: macos macOS-specific issue labels Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

platform: macos macOS-specific issue type: bug Something is broken or behaves incorrectly

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants