Skip to content

Thumb wheel horizontal scroll can't be reversed: default binding pair is inverted vs. native scroll direction #619

Description

@isleofgreg

Thumb wheel horizontal scroll can't be reversed: default binding pair is inverted vs. native scroll direction

Device: MX Master 4 (Bolt receiver) · OS: macOS 15.7.9 · Version: v0.6.27 (official cask)

Summary

On the MX Master 4, the thumb wheel's native (undiverted) scroll direction is up-rotation → scroll left, down-rotation → scroll right. OpenLogi's default binding pair says the opposite:

https://github.com/AprilNEA/OpenLogi/blob/v0.6.27/crates/openlogi-core/src/binding/defaults.rs#L36-L37

ButtonId::ThumbwheelScrollUp => Action::HorizontalScrollRight,
ButtonId::ThumbwheelScrollDown => Action::HorizontalScrollLeft,

Because the wheel is only diverted when something differs from defaults (thumbwheel_armed, watchers/gesture.rs#L100: sensitivity ≠ DEFAULT_THUMBWHEEL_SENSITIVITY or a thumbwheel binding ≠ default, computed in capture_plan.rs#L96), this inversion is invisible in the default state — native scroll is what users feel, and it's fine. But it surfaces as two real bugs:

1. Scroll direction cannot be reversed at all

Swapping the two bindings (ThumbwheelScrollUp = "HorizontalScrollLeft", ThumbwheelScrollDown = "HorizontalScrollRight") is non-default → the wheel is diverted → advance() injects sign from the action (gesture.rs#L474-L484) → up injects Left, down injects Right — which is exactly the native direction. Verified on hardware: the swapped config feels identical to the default config. Meanwhile the mapping that would reverse it (up→Right/down→Left, injected) equals the defaults, so it never diverts and never takes effect. Net: no combination of scroll bindings changes the felt direction, and the GUI (correctly, given the model) exposes no reverse option.

2. Moving the sensitivity slider silently flips scroll direction

Changing thumbwheel_sensitivity off 14 — with untouched default bindings — arms the divert by itself, switching the user from native (up→left) to injected defaults (up→right). A user who only wanted faster/slower scrolling gets their horizontal scroll direction reversed as a side effect.

Repro

  1. MX Master 4, default config: rotate thumb wheel up → content scrolls left (native).
  2. Set only thumbwheel_sensitivity = 15, restart agent: rotate up → content scrolls right (bug 2).
  3. Set bindings Up→HorizontalScrollLeft / Down→HorizontalScrollRight (the "swap"): rotate up → content scrolls left — same as step 1, no reversal possible (bug 1).

Proposed fix

Swap the two defaults in defaults.rs:

ButtonId::ThumbwheelScrollUp => Action::HorizontalScrollLeft,
ButtonId::ThumbwheelScrollDown => Action::HorizontalScrollRight,

(+ the GUI's default pair and affected tests, + the doc comment above the defaults.) Then:

  • injected-default matches native → sensitivity changes no longer flip direction;
  • the binding swap becomes non-default → diverts → genuinely reverses, giving users a working (if TOML-only) reverse today;
  • a GUI "reverse direction" toggle becomes a trivial follow-up (it just writes the swapped pair).

Caveats worth a maintainer call: I've verified the native direction on one device (MX Master 4 via Bolt, macOS — the injected CGEvent and native scroll both pass through the same system scroll-inversion handling, so macOS "natural scrolling" doesn't affect the relative comparison). If other thumb-wheel devices share the firmware convention this is universal; and existing configs that explicitly wrote the old default pair would start diverting after the swap (behavior-preserving direction-wise, but they lose native-scroll latency — may warrant a migration touch).

Happy to send the PR if the direction (pun intended) looks right to you.

Workaround for anyone hitting this now: keep the default bindings and set thumbwheel_sensitivity to 15 (forces the diverted path, whose defaults are the reverse of native; ~5% faster than 14, imperceptible).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions