Skip to content

Feature/mx master - #111

Open
mgenc2077 wants to merge 3 commits into
mmaher88:masterfrom
mgenc2077:feature/mx-master
Open

mgenc2077 wants to merge 3 commits into
mmaher88:masterfrom
mgenc2077:feature/mx-master

Conversation

@mgenc2077

Copy link
Copy Markdown

feature(device): MX Master (1st gen)

I added the original MX Master descriptor and the C++ plumbing it needs for thumb wheel.
Tested on CachyOS (6.18.24-1-cachyos-lts kernel) over Bluetooth (PID 0xb01e)
with the standard pacman build flow.

  • Descriptor uses MX Master 2S images — body shape, button positions, and
    hotspot coordinates are nearly identical between the two generations.
  • Buttons, DPI, SmartShift, gesture button (CID 0xC3 → swipe gestures),
    and Easy-Switch all work via the existing capability paths.
  • Thumb wheel needed new C++ — see below.
  • All tests touching MX Master pass in isolation; remaining make test
    failures on my host are pre-existing test-infra brittleness unrelated to
    this PR (see "Test status" section).

Disclaimer: I basically vibecoded this pr with manual research and with the help of claude on a second monitor. Explanation below is also from claude.

C++ implementation — thumb wheel via Gesture 2 diversion

The 1st-gen MX Master is the only MX Master generation that does not
expose HID++ feature 0x2150 (ThumbWheel). Verified two ways:

  • Solaar's solaar show output has no entry for 0x2150.
  • Logitune's own init log prints feature 2150 -> index 0 (= not in the
    device's feature table).

Instead the thumb wheel rides on 0x6501 (Gesture 2) — when a gesture is
diverted, rotation events arrive as long-form HID++ notifications. Captured
event for one tick right:

11 ff 0f 00 23 00 03 01 00 00 00 ...
└──┬──┘└┬┘└┬┘└┬┘ │  │  │  │
   │   │  │  │   │  │  │  signed int8 delta (1 = +1, FE = -2, …)
   │   │  │  │   │  │  static `0x03` thumb-wheel marker (constant across
   │   │  │  │   │  │   MX Master generations)
   │   │  │  │   │  reserved
   │   │  │  │   status: 0x11 touch-start, 0x23 movement, 0x31 touch-end
   │   │  │  function/event ID = 0
   │   │  feature index 0x0F = 0x6501 on this device
   │   device index 0xFF (direct)
   long-report ID

To make this work end-to-end Logitune needs to:

  1. Enable the thumb-wheel gesture (function 0x02, gesture index 1 on
    this device — second entry in the gesture table after NATURAL_SCROLLING
    id 45).
  2. Divert it to the host (function 0x04, diversion index 0 — the only
    divertible gesture).
  3. Decode incoming events and emit thumbWheelRotation(delta) so the
    existing ButtonActionDispatcher routes through volume / scroll / zoom
    per thumbWheelMode.
  4. Re-divert / undivert when the user changes mode (scroll releases
    the wheel back to native HID horizontal scroll).

What was added

  • src/core/hidpp/features/GestureV2.{h,cpp} — extended with
    parseDivertedGestureEvent, buildSetDiverted (offset/mask request
    builder shared with setEnabled), function-ID constants kFnSetEnabled = 0x02 and kFnSetDiverted = 0x04, and a kThumbWheelEventMarker = 0x03
    constant.
  • src/core/hidpp/capabilities/GestureV2Capability.{h,cpp} — new variant
    table following the same pattern as Battery / SmartShift / ReprogControls.
  • src/core/CMakeLists.txt — added the new capability source.

What was touched in non-MX-Master code

Every behavioral change is gated on the new descriptor flag
thumbWheelGestureV2, which only the original MX Master sets. For every
other descriptor the flag defaults to false and the new code paths are
unreachable.

File Change Effect on other descriptors
interfaces/IDevice.h Added bool thumbWheelGestureV2 = false; to FeatureSupport Defaults to false
devices/JsonDevice.cpp One additive parser line Their JSON lacks the key → false
devices/schema.json (×2) One optional property No validation impact
DeviceSession::enumerateAndSetup New init block Gated, skipped
DeviceSession::handleNotification New decode block Gated, skipped
DeviceSession::setThumbWheelMode New else branch after the existing if (hasFeature(ThumbWheel)) { …; return; } Existing path returns first
app/models/DeviceModel::thumbWheelSupported f.thumbWheel → f.thumbWheel || f.thumbWheelGestureV2 OR clause is no-op when the new flag is false
scripts/generate-readme-devices.py "Thumb wheel" predicate gained an OR for the new flag; "Gestures" predicate switched from reading features.thumbWheel to scanning controls[] for defaultActionType == "gesture-trigger" Same output for every existing row — verified by inspection

The Gestures predicate change is technically a non-MX-Master edit but it
fixes a predicate that worked by coincidence on every other device (every
HID++ 0x2150 device also has a gesture button, so reading thumbWheel
happened to give the right answer). The new predicate is semantically
correct, and the regenerated README is byte-identical for every row except
this one.

Known limitations

  • Invert direction toggle is a no-op on this device. The 0x2150 path
    passes invert into the firmware; 0x6501 has no equivalent. A one-line
    fix in ButtonActionDispatcher::onThumbWheelRotation (multiply
    normalized by -1 when session->thumbWheelInvert() is true) would
    honor it in software for every device, but that's a behavior change
    outside this PR's scope. Left as-is.
  • Diversion indices are hardcoded at gesture index 1 / diversion
    index 0 — derived from Solaar's runtime gesture-table dump on this
    device. A more thorough implementation would enumerate the table at
    startup via getCount (function 0x00) + getGestureInfo (function
    0x10) and find the THUMBWHEEL entry (gesture id 46) dynamically.
    Left out for size; the indices are stable across firmware revisions on
    this generation.
  • README "Gestures" column reads ✅ thanks to the predicate fix above
    — gesture button (CID 0xC3) works; verified by hand on the device.

Test status

make test shows 14 failures on my host. They are all pre-existing
test-infrastructure brittleness, none caused by this branch:

  • DeviceRegistry.ReloadByPathRefreshesSingleDevice does
    qputenv("XDG_DATA_HOME", tmp) then qunsetenv("XDG_DATA_HOME") at the
    end without restoring. Subsequent registry constructions then fall back
    to whatever ~/.local/share/logitune/devices and
    /usr/share/logitune/devices happen to hold on the host.
  • On a host with the older pacman logitune package installed,
    /usr/share/logitune/devices lacks both mx-master and mx-master-3
    → 12 of the 14 failures, plus MxVerticalForBusinessRegistered and
    IdentifyDirectDevice flake when the env mutation prevents the right
    descriptors loading.

Both isolated subsets are green:

./build/tests/logitune-tests --gtest_filter='AllDevices/*/MX_Master:GestureV2*'
14 PASSED   # 6 device-registry + 8 capability-dispatch tests

CI / a fresh devcontainer should be unaffected — there's no
/usr/share/logitune/devices shadowing the fork's own devices/ folder.

Sources for thumb-wheel implementation

@mmaher88 mmaher88 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for digging into this. The original MX Master isn't supported on master today, and your Gesture 2 analysis lines up with Solaar (fn 2/4 offset-mask writes, gesture 46 = THUMBWHEEL). The branch is 15 commits behind and DeviceSession has been reworked since (#174, #176), so it needs a rebase either way. To get this merged, could you split it in two?

  1. A descriptor-only PR:
    • List all three hardware revisions (0xb012/0x4041, 0xb017/0x4060, 0xb01e/0x4071). Bluetooth devices are only picked up if their PID is in the descriptor.
    • Set thumbWheel: false and drop the thumb-wheel control and hotspots for now; the wheel keeps its native horizontal scroll.
    • Either point images at ../mx-master-2s/ or use downscaled copies, rather than adding another 15 MB of identical PNGs.
  2. A follow-up for the thumb wheel, written as a ThumbWheel capability variant table (0x2150 and 0x6501) chosen from the device's feature table, rather than a new descriptor flag. The current flag isn't in schema.json, which descriptors are validated against. Please also find the gesture and diversion indices by walking the 0x6501 field table, the way Solaar's Gestures class does, instead of hardcoding 1/0, and fix the comment that says the index is 3.

The README "Gestures" predicate fix is a good catch, but please send it as its own small PR.

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.

2 participants