Skip to content

feat(thumbwheel-tap): expose thumbwheel tap separately#341

Open
MoosaTae wants to merge 3 commits into
AprilNEA:masterfrom
MoosaTae:codex/fix-thumbwheel-tap
Open

feat(thumbwheel-tap): expose thumbwheel tap separately#341
MoosaTae wants to merge 3 commits into
AprilNEA:masterfrom
MoosaTae:codex/fix-thumbwheel-tap

Conversation

@MoosaTae

@MoosaTae MoosaTae commented Jul 1, 2026

Copy link
Copy Markdown

What changed

  • default the capacitive thumbwheel tap to Do Nothing
  • expose separate Thumb Wheel Up, Tap, and Down controls in the mouse model
  • migrate the historical pre-v4 Thumbwheel = AppExpose default away while preserving tap actions configured with the new schema
  • keep rotation capture active on devices that do not advertise single-tap capability
  • add translations, documentation, and focused regression tests

Why

The MX Master 3S reports capacitive tap separately from rotation while the thumbwheel is diverted. OpenLogi hid that tap binding while defaulting it to App Exposé, so rolling and releasing the wheel could trigger App Exposé even when both rotation directions were configured as Do Nothing.

The capture setup also used single-tap support as a prerequisite for all thumbwheel diversion, unintentionally disabling customized rotation on devices without tap support.

How to reproduce the original issue

  1. Run an OpenLogi build before this MR with an MX Master mouse that exposes HID++ feature 0x2150 and supports capacitive single-tap.
  2. Leave the hidden Thumbwheel binding at its historical default, App Exposé.
  3. Set Thumb Wheel Up and Thumb Wheel Down to Do Nothing.
  4. Roll the side thumbwheel and release/lift your thumb without intentionally tapping it.
  5. Observe that App Exposé can open even though rotation is configured as Do Nothing. The GUI provides no separate tap control with which to discover or disable the triggering input.

After this MR, the mouse model shows Thumb Wheel Up / Thumb Wheel Tap / Thumb Wheel Down as separate controls. A fresh or migrated config leaves Tap at Do Nothing, so step 4 does not arm capture or dispatch App Exposé. Assigning an action to Tap intentionally enables capacitive-tap capture.

Representative app log

Run the agent with debug tracing enabled (OPENLOGI_LOG=debug). Timestamps and module prefixes are omitted below; the messages and fields match the application tracing calls.

Before the fix, the hidden default could dispatch App Exposé:

INFO  control capture active index=1 gesture=true dpi_buttons=0 thumbwheel=true
DEBUG HID++ button → action button=Thumbwheel action="App Exposé"

After the fix with Tap left at Do Nothing, the thumbwheel stays in native rotation mode when no sensitivity or rotation customization requires capture:

INFO  control capture active index=1 gesture=true dpi_buttons=0 thumbwheel=false

After explicitly assigning App Exposé to Thumb Wheel Tap, capture and dispatch are expected:

INFO  control capture active index=1 gesture=true dpi_buttons=0 thumbwheel=true
DEBUG HID++ button → action button=Thumbwheel action="App Exposé"

The exact index, gesture, and dpi_buttons values depend on the connected device and its configured controls.

User impact

Users can configure rotation and tap independently. Tap is inert by default, while horizontal scrolling remains the default for rotation. The v4 migration removes the historical implicit App Exposé tap from older configs; tap actions intentionally selected after migration remain intact and functional.

Validation

  • cargo test -p openlogi-core -p openlogi-hid -p openlogi-agent-core --lib — 182 passed
  • cargo +1.94.0 test -p openlogi-gui — 40 passed
  • cargo fmt --all --check
  • git diff --check
  • release macOS app bundle built and codesign verification passed

Rust 1.94 was used for the GUI test and bundle because the current locked smol_str 0.3.6 requires Rust 1.89+, while the local default toolchain is Rust 1.88.

Fixes #340

@MoosaTae MoosaTae marked this pull request as ready for review July 1, 2026 05:19
@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown

Greptile Summary

This PR exposes thumbwheel tap as its own configurable control. The main changes are:

  • Default thumbwheel tap to Do Nothing.
  • Add separate Thumb Wheel Up, Tap, and Down controls.
  • Migrate older configs away from the old App Exposé tap default.
  • Keep rotation capture available on devices without single-tap support.
  • Add capability probing, UI gating, translations, docs, and regression tests.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
crates/openlogi-core/src/config.rs Bumps the config schema and removes legacy thumbwheel tap App Exposé defaults from older configs.
crates/openlogi-core/src/binding.rs Renames the exposed thumbwheel action label and changes the tap default to Do Nothing.
crates/openlogi-agent-core/src/watchers/gesture.rs Adds coverage for thumbwheel capture arming around default tap, rotation, and sensitivity behavior.
crates/openlogi-hid/src/gesture.rs Allows thumbwheel rotation capture without single-tap support while gating tap dispatch on the probed capability.
crates/openlogi-hid/src/inventory/features.rs Probes thumbwheel single-tap support and exposes it through device capabilities.
crates/openlogi-gui/src/mouse_model/geometry.rs Splits the thumbwheel marker into separate rotation controls and an optional tap control.
crates/openlogi-gui/src/mouse_model/view.rs Uses the probed capability to decide whether the tap control is shown.
crates/openlogi-agent-core/src/ipc.rs Bumps the IPC protocol version for the new capability field.

Reviews (4): Last reviewed commit: "fix: gate thumbwheel tap on device capab..." | Re-trigger Greptile

Comment thread crates/openlogi-agent-core/src/watchers/gesture.rs
@MoosaTae MoosaTae changed the title [codex] expose thumbwheel tap separately feat(thumbwheel-tap): expose thumbwheel tap separately Jul 1, 2026
@rajatkb

rajatkb commented Jul 10, 2026

Copy link
Copy Markdown

Is this getting worked on ? Honestly this is the first time I come to know my mouse even supports this ! Does the MR keeps adds confguring this capacitive input ?

@MoosaTae MoosaTae force-pushed the codex/fix-thumbwheel-tap branch from 5100cac to 17bfdf0 Compare July 11, 2026 14:09
.expect("hook maps")
.bindings
.insert(ButtonId::Thumbwheel, Action::AppExpose);
assert!(thumbwheel_armed(&maps, DEFAULT_THUMBWHEEL_SENSITIVITY));

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 Legacy Tap Still Arms Capture

This expectation keeps the upgraded-config failure reachable. Older configs can contain an explicit Thumbwheel = AppExpose tap mapping while rotation bindings and sensitivity are still default. After this PR changes the tap default to None, the arming check treats that preserved tap action as customized behavior and enables HID++ thumbwheel diversion, so native thumbwheel rotation can be captured and re-synthesized even though the user only kept the old tap mapping. The arming decision needs to avoid using the preserved tap binding alone as a reason to capture rotation.

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

@MoosaTae

Copy link
Copy Markdown
Author

Legacy Tap Still Arms Capture
Older configs can contain an explicit Thumbwheel = AppExpose tap mapping while rotation bindings and sensitivity are still default.

Fixed in 5f18315 with a v4 config migration. When loading a pre-v4 config, OpenLogi removes only the historical Thumbwheel = AppExpose default, so that legacy value no longer arms diversion or changes native rotation handling. Other bindings are preserved.

The migration is intentionally version-gated: a user who explicitly configures AppExpose (or another tap action) in a v4 config still gets functional capacitive-tap capture. Changing thumbwheel_armed to ignore tap-only bindings globally would make the new tap control nonfunctional.

Regression coverage verifies both sides: legacy v3 defaults are removed, while intentional v4 tap bindings are preserved. cargo test -p openlogi-core -p openlogi-hid -p openlogi-agent-core --lib passes (182 tests), along with formatting and diff checks.

@MoosaTae

Copy link
Copy Markdown
Author

Does every thumbwheel really expose this tap input?
The UI must not present Thumb Wheel Tap merely because the asset has a thumbwheel marker; support has to come from the device-reported HID++ 0x2150 single-tap capability.

Fixed in c6beeeb. Inventory now reads getThumbwheelInfo, records capabilities.single_tap as Capabilities::thumbwheel_tap, persists it with device identity, and carries it over IPC (protocol v9). The UI always keeps thumbwheel Up/Down when a thumbwheel exists, but only adds Tap when the measured capability is true. The existing HID dispatch guard remains in place as defense in depth.

Debug builds now provide direct hardware evidence:

DEBUG thumb wheel single-tap capability probed slot=1 supported=true

Unsupported devices report supported=false and do not show a Tap control. Capability probe failures also fail closed and are logged. Added geometry regression coverage for both supported three-control and unsupported rotation-only layouts. Core/HID/agent tests pass (189 tests, including IPC wire-format coverage), as do formatting and diff checks. The local GUI build remains blocked before crate compilation by the missing Xcode Metal Toolchain.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Thumbwheel rotation can trigger hidden App Exposé tap binding

2 participants