From b03f65010745a4ff9bb6bf9fcc8e97f2498d5bf8 Mon Sep 17 00:00:00 2001 From: Hiroaki Tagawa Date: Sat, 18 Jul 2026 19:46:44 +0900 Subject: [PATCH 1/2] fix: divert thumbwheel for rotation rebinds even without single-tap capability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit arm_controls refused to divert the thumb wheel unless getThumbwheelInfo reported the single-tap capability. Rotation rebinds and the sensitivity multiplier need the diverted event stream too, and devices like the MX Master 4 report no single-tap bit — leaving ThumbwheelScrollUp/Down bindings silently inert. Divert whenever capture is requested; a missing single tap now only means a bound click can never fire. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01N76prZoUHMrHZajXq4jET1 --- crates/openlogi-hid/src/gesture.rs | 19 +++++++++++-------- crates/openlogi-hid/src/thumbwheel.rs | 3 ++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/crates/openlogi-hid/src/gesture.rs b/crates/openlogi-hid/src/gesture.rs index 1783494b..26b84c21 100644 --- a/crates/openlogi-hid/src/gesture.rs +++ b/crates/openlogi-hid/src/gesture.rs @@ -206,8 +206,8 @@ impl ArmedControls { /// Resolve features off the device's root and divert the controls we capture: /// the gesture button (raw-XY) and DPI/ModeShift buttons over `0x1b04`, and — -/// when `capture_thumbwheel` and the wheel reports a single tap — the thumb -/// wheel over `0x2150`. The root-feature lookup mirrors `write::open_feature`, +/// when `capture_thumbwheel` — the thumb wheel over `0x2150`. The +/// root-feature lookup mirrors `write::open_feature`, /// since hidpp 0.2's registry doesn't carry the features OpenLogi reimplements. async fn arm_controls( chan: &Arc, @@ -273,14 +273,17 @@ async fn arm_controls( false } }; - if supports_single_tap { - tw.set_reporting(true, false) - .await - .map_err(|e| GestureError::Hidpp(format!("{e:?}")))?; - thumb = Some((tw, info.index)); - } else { + // Divert whenever capture was requested: rotation rebinds and the + // sensitivity multiplier need the diverted event stream even on wheels + // that report no single-tap capability (e.g. MX Master 4) — lacking the + // tap only means a bound click can never fire. + if !supports_single_tap { debug!("thumb wheel reports no single tap — click not capturable"); } + tw.set_reporting(true, false) + .await + .map_err(|e| GestureError::Hidpp(format!("{e:?}")))?; + thumb = Some((tw, info.index)); } if !gesture_diverted && dpi_cids.is_empty() && thumb.is_none() { diff --git a/crates/openlogi-hid/src/thumbwheel.rs b/crates/openlogi-hid/src/thumbwheel.rs index e0be78f3..d26485ea 100644 --- a/crates/openlogi-hid/src/thumbwheel.rs +++ b/crates/openlogi-hid/src/thumbwheel.rs @@ -4,7 +4,8 @@ //! //! The wheel only has two reporting modes — Native (HID scroll) or Diverted //! (HID++ events) — there is no "report taps but keep scrolling" mode. So the -//! capture session diverts the wheel only when the user has bound its click, +//! capture session diverts the wheel whenever the user's thumbwheel config +//! leaves its defaults (click bound, rotation rebound, or sensitivity changed), //! and re-synthesises horizontal scroll from the rotation deltas to keep //! scrolling working. //! From 45a22e54ada811a09a008c356de5b5400ce4717d Mon Sep 17 00:00:00 2001 From: Hiroaki Tagawa Date: Sat, 18 Jul 2026 20:29:32 +0900 Subject: [PATCH 2/2] docs: update remaining doc comments to the broadened divert trigger Review follow-up: the module-level doc and the CapturedInput::Scroll doc still described diversion as click-bound-only. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01N76prZoUHMrHZajXq4jET1 --- crates/openlogi-hid/src/gesture.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/openlogi-hid/src/gesture.rs b/crates/openlogi-hid/src/gesture.rs index 26b84c21..1f0da4e1 100644 --- a/crates/openlogi-hid/src/gesture.rs +++ b/crates/openlogi-hid/src/gesture.rs @@ -13,7 +13,8 @@ //! it, mirroring how the CGEventTap hook handles the side buttons. The thumb //! wheel is special: diverting it stops native horizontal scroll, so the GUI //! re-synthesises scroll from the [`CapturedInput::Scroll`] deltas — the wheel -//! is therefore only diverted when its click is actually bound. +//! is therefore only diverted when the user's thumbwheel config leaves its +//! defaults (click bound, rotation rebound, or sensitivity changed). use std::sync::{Arc, Mutex, PoisonError, RwLock}; @@ -44,8 +45,8 @@ pub enum CapturedInput { /// ([`ButtonId::Thumbwheel`]). ButtonPressed(ButtonId), /// Thumb-wheel rotation to re-synthesise as horizontal scroll, in the - /// wheel's `diverted_res` increments. Emitted only while the wheel is - /// diverted to capture its click. + /// wheel's `diverted_res` increments. Emitted while the wheel is diverted + /// (click bound, rotation rebound, or sensitivity changed). Scroll(i16), }