From 611854dc9d9f4a6ccc76d9f3125f0e2d7163596b Mon Sep 17 00:00:00 2001 From: Shubhadeep Date: Sun, 9 Aug 2026 00:02:09 +0530 Subject: [PATCH 1/2] fix(logitech): connect G305 receivers by probing slot 0x01 first --- src/devices/logitech/TESTING.md | 1 + src/devices/logitech/hidpp.ts | 10 ++++++---- src/devices/vendors.ts | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/devices/logitech/TESTING.md b/src/devices/logitech/TESTING.md index 4ab450c5..e9aa2a27 100644 --- a/src/devices/logitech/TESTING.md +++ b/src/devices/logitech/TESTING.md @@ -9,6 +9,7 @@ Supported identifiers: - `046d:c54d`, `046d:c547` — Lightspeed receivers - `046d:c539` — HERO-era Lightspeed receiver +- `046d:c53f`, `046d:c543` — Nano Lightspeed 1.1 / 1.2 receivers (G305) - `046d:c0a8` — PRO X 2 Superstrike (USB) - `046d:c07e` — G402 / G402 Hyperion Fury (wired) - `046d:c08f` — G403 HERO (wired) diff --git a/src/devices/logitech/hidpp.ts b/src/devices/logitech/hidpp.ts index c68d9deb..3d2cbb32 100644 --- a/src/devices/logitech/hidpp.ts +++ b/src/devices/logitech/hidpp.ts @@ -119,7 +119,7 @@ export class NotAMouseError extends Error { const LOGITECH_VENDOR_ID = 0x046d; // HID++ control interfaces, including the PRO X 2 Superstrike USB interface. -const LOGITECH_RECEIVER_PRODUCT_IDS = new Set([0xc54d, 0xc539, 0xc0a8, 0xc547]); +const LOGITECH_RECEIVER_PRODUCT_IDS = new Set([0xc54d, 0xc539, 0xc0a8, 0xc547, 0xc53f, 0xc543]); /** * Models whose 0x8090 mode-status feature drives the power-mode switch only. @@ -320,11 +320,13 @@ export class LogitechHidppClient { for (const candidate of candidates) { this.resolvedDeviceIndex = candidate; - // Any reply at all proves something is listening, including a HID++ - // error reply. Only silence rules the index out. + // Only a successful reply proves a HID++ 2.0 mouse is on this index. A + // receiver answers the direct index with a HID++ 1.0 error reply, which + // is not a mouse and must not count — else we latch onto it and every + // later request fails with "invalid command". const answered = await this.request(0x00, 0x00, FEATURE.firmware >> 8, FEATURE.firmware & 0xff) .then(() => true) - .catch((error: unknown) => !(error instanceof HidppTimeoutError)); + .catch(() => false); if (answered) return; } this.resolvedDeviceIndex = null; diff --git a/src/devices/vendors.ts b/src/devices/vendors.ts index 18b4e4fd..3001636a 100644 --- a/src/devices/vendors.ts +++ b/src/devices/vendors.ts @@ -55,8 +55,9 @@ export const TEEVOLUTION_PRODUCT_IDS = [0xf520, 0xf523, 0xf5bb, 0xf522] as const // Logitech HID++ control interfaces addressed through a receiver slot (HID++ // device index 0x01). 0xc54d and 0xc547 are newer Lightspeed receivers, 0xc539 -// is HERO-era Lightspeed, and 0xc0a8 is the PRO X 2 Superstrike USB interface. -export const LOGITECH_RECEIVER_PRODUCT_IDS = [0xc54d, 0xc539, 0xc0a8, 0xc547] as const; +// is HERO-era Lightspeed, 0xc53f and 0xc543 are Nano Lightspeed 1.1/1.2 +// receivers (G305), and 0xc0a8 is the PRO X 2 Superstrike USB interface. +export const LOGITECH_RECEIVER_PRODUCT_IDS = [0xc54d, 0xc539, 0xc0a8, 0xc547, 0xc53f, 0xc543] as const; // Every Logitech product with an HID++ control interface, receiver-addressed or // not. Direct-connect product IDs live in ./logitech/protocol so the driver and From 1cedf56427c8387b360c591622a1d3c3dfaf9e08 Mon Sep 17 00:00:00 2001 From: Shubhadeep Date: Sun, 9 Aug 2026 00:30:54 +0530 Subject: [PATCH 2/2] fix(logitech): hide surface and LightForce controls on the G305 --- src/devices/logitech/TESTING.md | 14 ++++++++++++++ src/devices/logitech/hidpp.test.ts | 2 ++ src/devices/logitech/hidpp.ts | 1 + 3 files changed, 17 insertions(+) diff --git a/src/devices/logitech/TESTING.md b/src/devices/logitech/TESTING.md index e9aa2a27..756919cb 100644 --- a/src/devices/logitech/TESTING.md +++ b/src/devices/logitech/TESTING.md @@ -91,3 +91,17 @@ hidden. the LightForce switch are all hidden. 3. Change the DPI and polling rate and confirm each write persists after a reload. + +## G305 LIGHTSPEED (receiver-attached, Model ID `407400000000`) + +Like the G309, the G305 exposes Mode Status `0x8090` but only the power-mode +half is meaningful: the status1 byte that would carry the gaming-surface and +LightForce fields is reserved and reads 0. The G305 also has no lift-off +control. OpenMouse treats all three as absent, so those cards stay hidden. + +1. Confirm the model, battery, connection type, DPI, and polling rate are read + correctly. +2. Confirm the sensor card (lift-off distance), the gaming-surface card, and + the LightForce switch are all hidden. +3. Change the DPI and polling rate and confirm each write persists after a + reload. diff --git a/src/devices/logitech/hidpp.test.ts b/src/devices/logitech/hidpp.test.ts index fb5374e6..6a5e0805 100644 --- a/src/devices/logitech/hidpp.test.ts +++ b/src/devices/logitech/hidpp.test.ts @@ -162,6 +162,8 @@ test("an onboard-only mouse is told how to get itself supported", () => { test("the G309's mode status is power-only and exposes no surface or LightForce controls", () => { // Model id captured from hardware: 0x8090 V2 with only the power-mode half. assert.equal(isPowerOnlyModeStatus("B03C40B10000"), true); + // The G305 reports the same reserved status1 byte. + assert.equal(isPowerOnlyModeStatus("407400000000"), true); // Every other model keeps the status1 fields, and unknown/absent ids must // not be silently downgraded. assert.equal(isPowerOnlyModeStatus("B03C40B10001"), false); diff --git a/src/devices/logitech/hidpp.ts b/src/devices/logitech/hidpp.ts index 3d2cbb32..0df46d53 100644 --- a/src/devices/logitech/hidpp.ts +++ b/src/devices/logitech/hidpp.ts @@ -130,6 +130,7 @@ const LOGITECH_RECEIVER_PRODUCT_IDS = new Set([0xc54d, 0xc539, 0xc0a8, 0xc547, 0 */ const MODE_STATUS_POWER_ONLY_MODEL_IDS: ReadonlySet = new Set([ "B03C40B10000", // G309 LIGHTSPEED + "407400000000", // G305 LIGHTSPEED ]); /** Whether 0x8090 on this model is the power-mode-only variant. */