From 97561f883b9e676b1cea2ef7117be2d988a79a8c Mon Sep 17 00:00:00 2001 From: bobaoapae Date: Tue, 11 Aug 2026 16:09:13 -0300 Subject: [PATCH 1/2] feat(hid): recognise Lightspeed receiver 046d:c547 (G915, G502 X) The Lightspeed receiver that ships with newer G-series devices (the G915 keyboard, the G502 X LIGHTSPEED) answers the same HID++ 1.0 enumeration and pairing-information registers as Unifying receivers, but its PID is not in any known-receiver list, so devices paired through it are never discovered: the receiver falls through detect() and the paired device stays invisible to list and the GUI. Add the PID to LIGHTSPEED_PIDS (openlogi-hid) and unifying::VPID_PAIRS (openlogi-hidpp) so the receiver routes as DeviceRoute::Unifying and its pairing slots are walked. Verified on real hardware with a G915 (paired device wpid 0x407c). Refs #512 --- crates/openlogi-hid/src/route.rs | 15 +++++++++------ crates/openlogi-hidpp/src/receiver/unifying.rs | 14 +++++++++----- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/crates/openlogi-hid/src/route.rs b/crates/openlogi-hid/src/route.rs index 6ef415d7..c09b2e5d 100644 --- a/crates/openlogi-hid/src/route.rs +++ b/crates/openlogi-hid/src/route.rs @@ -91,12 +91,14 @@ pub const BOLT_PIDS: &[u16] = &[0xc548]; /// Unifying, so it routes as [`DeviceRoute::Unifying`]. pub const UNIFYING_PIDS: &[u16] = &[0xc52b, 0xc532, 0xc539]; -/// USB product IDs that identify Logitech Lightspeed nano receivers — the -/// receivers bundled with G-series wireless mice such as the G305. They speak -/// the same HID++ 1.0 receiver register protocol as Unifying, so they are -/// enumerated, routed, and paired through the Unifying code path; only the -/// user-facing receiver name (see [`receiver_display_name`]) differs. -pub const LIGHTSPEED_PIDS: &[u16] = &[0xc53f]; +/// USB product IDs that identify Logitech Lightspeed receivers — the +/// receivers bundled with G-series wireless devices. `0xc53f` is the nano +/// receiver of wireless mice such as the G305; `0xc547` ships with newer +/// G-series devices such as the G915 keyboard and the G502 X LIGHTSPEED. +/// They speak the same HID++ 1.0 receiver register protocol as Unifying, so +/// they are enumerated, routed, and paired through the Unifying code path; +/// only the user-facing receiver name (see [`receiver_display_name`]) differs. +pub const LIGHTSPEED_PIDS: &[u16] = &[0xc53f, 0xc547]; /// Whether `product_id` is a receiver that speaks the Unifying HID++ 1.0 /// register protocol — a Unifying receiver proper, or a protocol-compatible @@ -336,6 +338,7 @@ mod tests { #[test] fn lightspeed_receiver_has_its_own_display_name() { assert_eq!(receiver_display_name(0xc53f), "Lightspeed Receiver"); + assert_eq!(receiver_display_name(0xc547), "Lightspeed Receiver"); assert_eq!(receiver_display_name(0xc52b), "Unifying Receiver"); } diff --git a/crates/openlogi-hidpp/src/receiver/unifying.rs b/crates/openlogi-hidpp/src/receiver/unifying.rs index 1fac152b..acef9314 100755 --- a/crates/openlogi-hidpp/src/receiver/unifying.rs +++ b/crates/openlogi-hidpp/src/receiver/unifying.rs @@ -23,16 +23,20 @@ use crate::{ /// receivers. /// /// `046d:c539` is the Lightspeed gaming receiver; `046d:c53f` is the Lightspeed -/// nano receiver (bundled with G-series wireless mice such as the G305). Both -/// answer the same HID++ 1.0 registers (pairing count, connection state, pairing -/// information) as Unifying receivers. Callers that surface a user-facing -/// receiver name label Lightspeed PIDs separately (see `openlogi-hid`). -/// `0xc53f` was verified against a G305 (paired device wpid `0x4074`). +/// nano receiver (bundled with G-series wireless mice such as the G305); +/// `046d:c547` is the Lightspeed receiver bundled with newer G-series devices +/// such as the G915 keyboard and the G502 X LIGHTSPEED. All answer the same +/// HID++ 1.0 registers (pairing count, connection state, pairing information) +/// as Unifying receivers. Callers that surface a user-facing receiver name +/// label Lightspeed PIDs separately (see `openlogi-hid`). +/// `0xc53f` was verified against a G305 (paired device wpid `0x4074`); +/// `0xc547` against a G915 (paired device wpid `0x407c`). pub const VPID_PAIRS: &[(u16, u16)] = &[ (0x046d, 0xc52b), (0x046d, 0xc532), (0x046d, 0xc539), (0x046d, 0xc53f), + (0x046d, 0xc547), ]; /// All known registers of the Unifying receiver. From 1298f63645d3f3a2cf8ada939ccd0c679d5b5048 Mon Sep 17 00:00:00 2001 From: bobaoapae Date: Tue, 11 Aug 2026 16:36:13 -0300 Subject: [PATCH 2/2] fix(hid): filter Lightspeed receiver children on Linux is_receiver_child_sysfs_path builds its parent markers from BOLT_PIDS and UNIFYING_PIDS only, so child HID nodes beneath a Lightspeed receiver (LIGHTSPEED_PIDS: c53f, and c547 added here) were treated as direct-device candidates on Linux - extra probes and potential duplicate inventory entries. Chain LIGHTSPEED_PIDS into the marker list and cover it with a c547-child sysfs fixture. --- crates/openlogi-hid/src/transport.rs | 1 + crates/openlogi-hid/src/transport/tests.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/crates/openlogi-hid/src/transport.rs b/crates/openlogi-hid/src/transport.rs index afe57815..1d5b7a56 100644 --- a/crates/openlogi-hid/src/transport.rs +++ b/crates/openlogi-hid/src/transport.rs @@ -241,6 +241,7 @@ fn is_receiver_child_sysfs_path(path: &str) -> bool { crate::BOLT_PIDS .iter() .chain(crate::UNIFYING_PIDS.iter()) + .chain(crate::LIGHTSPEED_PIDS.iter()) .any(|&pid| { let marker = format!(":{LOGITECH_VID:04X}:{pid:04X}."); // A parent component contains the marker followed by at least one diff --git a/crates/openlogi-hid/src/transport/tests.rs b/crates/openlogi-hid/src/transport/tests.rs index a499e8b7..6a05340a 100644 --- a/crates/openlogi-hid/src/transport/tests.rs +++ b/crates/openlogi-hid/src/transport/tests.rs @@ -85,6 +85,9 @@ const UNIFYING_RECEIVER: &str = "/sys/devices/pci0000:00/0000:00:14.0/usb3/3-5/3 // Sysfs path: child of Bolt receiver const BOLT_CHILD: &str = "/sys/devices/pci0000:00/0000:00:14.0/usb3/3-5/\ 0003:046D:C548.0001/0003:046D:B037.0002"; +// Sysfs path: child of a Lightspeed receiver (a G915, wpid 407C, on c547) +const LIGHTSPEED_CHILD: &str = "/sys/devices/pci0000:00/0000:00:14.0/usb3/3-5/\ + 0003:046D:C547.0003/0003:046D:407C.0004"; // Sysfs path: unrelated non-Logitech device const UNRELATED: &str = "/sys/devices/pci0000:00/0000:00:15.0/i2c-0/0018:06CB:CE67.0001"; @@ -103,6 +106,11 @@ fn child_of_bolt_receiver_is_detected() { assert!(is_receiver_child_sysfs_path(BOLT_CHILD)); } +#[test] +fn child_of_lightspeed_receiver_is_detected() { + assert!(is_receiver_child_sysfs_path(LIGHTSPEED_CHILD)); +} + #[test] fn unrelated_device_is_not_a_child() { assert!(!is_receiver_child_sysfs_path(UNRELATED));