Repository navigation
Description
What happens
A Logitech Pro X2 Superstrike connected through its LIGHTSPEED receiver (046d:c54d) is listed as a keyboard:
- Devices home: card titled USB Receiver, subtitle
Logitech · Keyboard, keyboard placeholder art,68%/1000 Hz. - Sidebar: same
USB Receiverrow, and it keeps that label even after the mouse page is open. - Pressing Configure opens the correct
PRO X2 SUPERSTRIKEpage (DPI, polling, battery, firmware all read fine).
So the device is never actually on the keyboard path — it is only classified as one, and every connect has to fail the keyboard probe before the mouse client takes over.
Reported from the support thread with full diagnostics (Windows 11, build 2.0.904, wireless). Diagnostics: VID 0x046D, PID 0xC54D, productName "USB Receiver", collections 0xff00:1 | 0xff00:2 and a second interface entry with 0x0c:1; model ids 40bd c0a8 (the Superstrike's own wireless/USB transport ids).
Steps to reproduce
- Pair a Pro X2 Superstrike with its LIGHTSPEED receiver.
- Authorize it in the WebHID picker and open Devices.
- The card reads
USB Receiver · Logitech · Keyboardwith keyboard placeholder art. - Press Configure →
PRO X2 SUPERSTRIKEmouse page, all settings present.
Root cause
Keyboard clients are checked first and short-circuit the mouse registry, so any device a keyboard driver merely claims stops being a mouse:
src/device/device-clients.ts:86
export async function createSupportedClient(device: HIDDevice): Promise<SupportedClient | null> {
if (createKeyboardClient(device)) return null;
return createMouseClient(device);
}LogitechKeyboardHidClient.isSupported() accepts any PID in LOGITECH_RECEIVER_PRODUCTS that exposes a HID++ collection:
keyboard-protocol/src/logitech/index.ts:115—[0xc54d, "LIGHTSPEED receiver"]keyboard-protocol/src/drivers/logitech/hid.ts:78-91— receiver PID +0xff00:1/0xff00:2(or0xff43) collection ⇒ keyboard clientsrc/device/device-clients.ts:120—if (LogitechKeyboardClient.isSupported(device)) return new LogitechKeyboardClient(device);
A receiver has no keyboard product id of its own; whether a keyboard is paired is only knowable by probing its slots at open time. 0xc54d is the receiver family LIGHTSPEED mice use too, so a receiver with a mouse paired is claimed as a keyboard.
The diagnostics show the whole chain:
getDevices()returns the same046d:c54dtwice — one with0xff00:1 | 0xff00:2, one with0x0c:1. They merge into one logical device (physicalDeviceKey= vendor:product:name), andpickLogicalDevicekeeps the HID++ entry (the0x0c:1consumer collection is claimed by no driver).- The log then runs "Read keyboard status", opens the receiver and walks pairing slots (
01,02, …) until device-information reads come back with model ids40bd c0a8. Those are the Superstrike's ids, not a catalogued keyboard id, so the probe ends with "The Logitech receiver has no supported keyboard paired." - That error is caught by the receiver fallback in
src/device/controller.ts:3826-3850:
const receiverMouse = (error instanceof Error && /no supported keyboard paired/i.test(error.message))
? await createMouseClient(client.device).catch(() => null)
: null;…which is why Configure lands on the working mouse page. The mouse driver is fine; only the listing is wrong, and every connect pays for a keyboard probe that cannot succeed.
Because the listing classifies it as a keyboard, sidebarEntries takes the keyboard branch (src/device/controller.ts:3059-3072): kind: "keyboard", and the name falls back to device.productName — i.e. the receiver's name "USB Receiver", since the paired model is unknown until a status read.
The new keyboard placeholder (904ef1f / 10096fa)
The reporter's build 2.0.904 is commit 904ef1f, the merge of #1357 feat(devices): keyboard placeholder image for keyboards with no art. That commit is behaving as designed: withPlaceholder() in src/shared/device-images.ts:228-233 swaps unknown-device.png for unknown-keyboard.png whenever kind === "keyboard", so a misclassified mouse now gets keyboard art instead of the mouse art, with placeholderImage(kind) on load error.
It is not the cause, but it is what makes the bug obvious in the screenshot — the card now clearly claims to be a keyboard. The classification short-circuit predates it (keyboard clients landed in c81d446, 2026-10-29).
Suggested fix
- Don't let a receiver claim short-circuit the mouse registry. In
createSupportedClient, only returnnullfor a device with a keyboard product id; for a receiver PID, score both claims (clientSupportScore) and let the mouse registry win when it recognizes the PID. The keyboard probe then only runs when the mouse driver has nothing. - Remember a failed receiver probe. Cache the negative result of
resolveReceiverKeyboard()per device so the second refresh lists it as a mouse instead of retrying the keyboard path on every sidebar rebuild. - Don't title a card with the receiver's product name. In the keyboard branch of
sidebarEntries,device.productName("USB Receiver") should not be shown as the device; once a mouse client is active, use the status name like the mouse branch does.
Test to add next to src/device/device-clients.test.ts: a 046d:c54d receiver exposing 0xff00:1 | 0xff00:2 with the Superstrike's model ids must resolve to a mouse client / kind: "mouse" in the list, while a receiver whose slots report a catalogued keyboard id still resolves to the keyboard client.
Activity