Skip to content

Add remappable support for horizontal scroll wheel tilt actions - #359

Open
srako wants to merge 3 commits into
AprilNEA:masterfrom
srako:master
Open

Add remappable support for horizontal scroll wheel tilt actions#359
srako wants to merge 3 commits into
AprilNEA:masterfrom
srako:master

Conversation

@srako

@srako srako commented Jul 6, 2026

Copy link
Copy Markdown

…buttons

Add WheelLeft and WheelRight to the ButtonId enum so the main scroll wheel's horizontal tilt — which arrives as a horizontal scroll event at the OS hook, not as a button press — can be intercepted and remapped to any Action, just like a physical button.

Core changes:

  • binding.rs: WheelLeft/WheelRight variants, ALL array, labels, and default bindings (native horizontal scroll, pass-through)
  • hook_runtime.rs: intercept MouseEvent::Scroll with non-zero delta_x from a non-trackpad source; look up the bound action and dispatch it with a 250ms per-direction cooldown so one deliberate tilt fires once
  • mouse_buttons.rs: GUI hotspots flanking the middle-click area
  • geometry.rs: with_wheel_tilt() auto-derives tilt hotspots from the MiddleClick position; default_labels() updated for the new buttons

Default behavior is unchanged: unbound or default-bound tilt events pass through as native horizontal scroll. Only when the user maps WheelLeft/WheelRight to a non-default action does the hook suppress the scroll event and fire the bound action instead.

@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown

Greptile Summary

This PR makes horizontal main-wheel tilt remappable like other mouse buttons. The main changes are:

  • New WheelLeft and WheelRight button IDs with default horizontal-scroll actions.
  • Hook handling for horizontal scroll events with pass-through defaults and cooldown suppression.
  • GUI hotspots and labels for selecting the new tilt directions.

Confidence Score: 4/5

This is close, but the horizontal-scroll capture should be fixed before merging.

  • Non-trackpad horizontal wheel input is still routed through the new tilt remapping path.
  • Users with a thumbwheel or dedicated horizontal wheel can lose native horizontal scrolling when tilt is mapped to a custom action.
  • The default and disabled binding paths now pass native scroll through.

Files Needing Attention: crates/openlogi-agent-core/src/hook_runtime.rs

Important Files Changed

Filename Overview
crates/openlogi-agent-core/src/hook_runtime.rs Adds horizontal scroll interception for wheel tilt, including default pass-through and cooldown handling.
crates/openlogi-core/src/binding.rs Adds the new tilt button IDs, labels, ordering, and default bindings.
crates/openlogi-gui/src/data/mouse_buttons.rs Adds fallback hotspots for remapping left and right wheel tilt.
crates/openlogi-gui/src/mouse_model/geometry.rs Derives tilt hotspots from the middle-click geometry and updates fallback label positions.

Fix All in Codex Fix All in Claude Code

Reviews (4): Last reviewed commit: "fix(wheel): suppress entire tilt burst t..." | Re-trigger Greptile

Comment thread crates/openlogi-agent-core/src/hook_runtime.rs Outdated
Comment thread crates/openlogi-agent-core/src/hook_runtime.rs Outdated
Comment thread crates/openlogi-agent-core/src/hook_runtime.rs Outdated
@tomByrer

Copy link
Copy Markdown

Any progress on this?

srako added 3 commits August 1, 2026 19:50
…buttons

Add WheelLeft and WheelRight to the ButtonId enum so the main scroll
wheel's horizontal tilt — which arrives as a horizontal scroll event at
the OS hook, not as a button press — can be intercepted and remapped
to any Action, just like a physical button.

Core changes:
- binding.rs: WheelLeft/WheelRight variants, ALL array, labels, and
  default bindings (native horizontal scroll, pass-through)
- hook_runtime.rs: intercept MouseEvent::Scroll with non-zero delta_x
  from a non-trackpad source; look up the bound action and dispatch it
  with a 250ms per-direction cooldown so one deliberate tilt fires once
- mouse_buttons.rs: GUI hotspots flanking the middle-click area
- geometry.rs: with_wheel_tilt() auto-derives tilt hotspots from the
  MiddleClick position; default_labels() updated for the new buttons

Default behavior is unchanged: unbound or default-bound tilt events
pass through as native horizontal scroll. Only when the user maps
WheelLeft/WheelRight to a non-default action does the hook suppress
the scroll event and fire the bound action instead.
…mbwheel

Two bugs found in code review (PR AprilNEA#359):

1. (P1) Horizontal scroll from a thumbwheel (or any non-tilt source)
   was suppressed whenever WheelLeft/WheelRight had a custom binding,
   breaking native thumbwheel scrolling. Fix: only suppress when we
   actually fire the action; pass through when the cooldown blocks
   the fire so continuous horizontal wheels keep scrolling.

2. (P2) Action::None suppressed native horizontal scroll instead of
   passing it through. Fix: treat None the same as the default binding
   — pass through so the user keeps native scrolling.
Greptile review (PR AprilNEA#359, comment 3) found that the previous fix for
thumbwheel compatibility (passing through when the cooldown blocks)
introduced a scroll leak: the remaining events in a wheel-tilt burst
pass through as native horizontal scroll, so a tilt mapped to e.g.
Mission Control also nudges the page sideways.

Fix: always Suppress when a custom action is bound, regardless of
whether the cooldown allows a fire. The cooldown still prevents
repeated dispatches, and the entire burst is consumed so no native
scroll leaks.

Document the known limitation: on devices with both a tilt wheel and
a thumbwheel (e.g. MX Master 3S), mapping WheelLeft/WheelRight will
also intercept the thumbwheel's native scroll. Users who need native
thumbwheel scroll should not remap these buttons.
Comment on lines +247 to +250
return EventDisposition::PassThrough;
}

let (button, is_left) = if delta_x < 0.0 {

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 Horizontal wheels still captured

This condition still treats every non-trackpad horizontal scroll event as WheelLeft or WheelRight. A mouse thumbwheel or dedicated horizontal wheel can also arrive with from_trackpad == false and nonzero delta_x, so mapping either tilt direction to a custom action suppresses that native horizontal scroll and runs the remapped action instead. The comment below documents this case, but the runtime behavior is unchanged for users with both tilt and horizontal-wheel input.

Fix in Codex Fix in Claude Code

@kdeldycke

Copy link
Copy Markdown

Data relevant to the layer choice, since this and #357 intercept the same two controls at different points. Source: an MX Ergo's 0x1B04 getCidInfo, via Logi Options' HID++ response cache.

idx  CID   TID   flags  decoded
6    0x5B  0x3F  0x71   mouse|reprog|divertable|persist
7    0x5D  0x40  0x71   mouse|reprog|divertable|persist

Tilt is a pair of discrete divertable controls on this device, with the same flags as the already-bindable 0x52, 0x53, 0x56 and 0xED.

The delta_x observation in this PR and the CID above are consistent: the factory assignment of 0x5B/0x5D is AC Pan, the HID horizontal-scroll usage. Logi Options names them LeftScrollAsAcPan / RightScrollAsAcPan. Tilt therefore reaches the OS hook as horizontal scroll only while the CID is undiverted; diverting it suppresses the scroll event.

Two consequences for the hook approach:

  1. Diversion yields direction exactly; delta_x interception infers it, which is what the 250ms per-direction cooldown compensates for. Devices with both a tilting main wheel and a thumbwheel emit horizontal scroll from both.
  2. The non-trackpad source test is worth checking against a Bluetooth-direct connection, where the same unit presents as a different HID source.

A hook path still covers devices whose tilt exposes no CID, so both layers may be wanted, preferring diversion when 0x1B04 advertises the control.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

platform: all Cross-platform issue type: feature New feature request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants