Skip to main content

openlogi_device_registry/
receiver.rs

1//! Logitech receiver identities.
2
3use crate::LOGITECH_VENDOR_ID;
4
5/// Logitech receiver product family, independent of its wire protocol.
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub enum ReceiverBrand {
8    /// Logi Bolt receiver.
9    Bolt,
10    /// Logitech Unifying receiver.
11    Unifying,
12    /// Logitech Nano receiver.
13    Nano,
14    /// Logitech Lightspeed receiver.
15    Lightspeed,
16}
17
18/// Receiver protocol implementation used to enumerate and address devices.
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub enum ReceiverProtocol {
21    /// Logi Bolt receiver registers and pairing flow.
22    Bolt,
23    /// HID++ 1.0 Unifying-compatible receiver registers and pairing flow.
24    Unifying,
25}
26
27/// A known receiver's USB identity and the behavior OpenLogi assigns to it.
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub struct ReceiverDescriptor {
30    /// USB vendor ID.
31    pub vendor_id: u16,
32    /// USB product ID.
33    pub product_id: u16,
34    /// Receiver's marketed product family.
35    pub brand: ReceiverBrand,
36    /// Protocol implementation used for receiver operations.
37    pub protocol: ReceiverProtocol,
38}
39
40impl ReceiverDescriptor {
41    const fn logitech(product_id: u16, brand: ReceiverBrand, protocol: ReceiverProtocol) -> Self {
42        Self {
43            vendor_id: LOGITECH_VENDOR_ID,
44            product_id,
45            brand,
46            protocol,
47        }
48    }
49}
50
51/// All receiver identities supported by OpenLogi.
52///
53/// Each entry is a protocol claim and must be backed by hardware verification
54/// or an established reference implementation. Marketing families and protocol
55/// families are recorded separately: Nano and Lightspeed currently use
56/// [`ReceiverProtocol::Unifying`].
57pub const RECEIVERS: &[ReceiverDescriptor] = &[
58    ReceiverDescriptor::logitech(0xc52b, ReceiverBrand::Unifying, ReceiverProtocol::Unifying),
59    ReceiverDescriptor::logitech(0xc532, ReceiverBrand::Unifying, ReceiverProtocol::Unifying),
60    // Nano receiver bundled with the G602.
61    ReceiverDescriptor::logitech(0xc537, ReceiverBrand::Nano, ReceiverProtocol::Unifying),
62    // Lightspeed gaming receiver used by the G502, G Pro Wireless, G604, and
63    // other G-series devices.
64    ReceiverDescriptor::logitech(
65        0xc539,
66        ReceiverBrand::Lightspeed,
67        ReceiverProtocol::Unifying,
68    ),
69    // Lightspeed nano receiver, verified with a G305 (WPID 0x4074).
70    ReceiverDescriptor::logitech(
71        0xc53f,
72        ReceiverBrand::Lightspeed,
73        ReceiverProtocol::Unifying,
74    ),
75    // Lightspeed receiver, verified with a G915 (WPID 0x407c) and G502 X.
76    ReceiverDescriptor::logitech(
77        0xc547,
78        ReceiverBrand::Lightspeed,
79        ReceiverProtocol::Unifying,
80    ),
81    ReceiverDescriptor::logitech(0xc548, ReceiverBrand::Bolt, ReceiverProtocol::Bolt),
82    // Lightspeed receiver, verified with a PRO X SUPERLIGHT 2 DEX (WPID 0x40b8).
83    ReceiverDescriptor::logitech(
84        0xc54d,
85        ReceiverBrand::Lightspeed,
86        ReceiverProtocol::Unifying,
87    ),
88];
89
90/// Finds the descriptor for an exact USB vendor/product identity.
91#[must_use]
92pub fn find_receiver(vendor_id: u16, product_id: u16) -> Option<&'static ReceiverDescriptor> {
93    RECEIVERS
94        .iter()
95        .find(|receiver| receiver.vendor_id == vendor_id && receiver.product_id == product_id)
96}
97
98#[cfg(test)]
99mod tests {
100    use super::{RECEIVERS, ReceiverBrand, ReceiverProtocol, find_receiver};
101    use crate::LOGITECH_VENDOR_ID;
102
103    #[test]
104    fn receiver_identities_are_unique() {
105        for (index, receiver) in RECEIVERS.iter().enumerate() {
106            assert!(
107                RECEIVERS[..index].iter().all(|other| {
108                    (other.vendor_id, other.product_id) != (receiver.vendor_id, receiver.product_id)
109                }),
110                "duplicate receiver identity {:04x}:{:04x}",
111                receiver.vendor_id,
112                receiver.product_id
113            );
114        }
115    }
116
117    #[test]
118    fn superlight_dex_receiver_is_lightspeed_over_unifying_protocol() {
119        let receiver = find_receiver(LOGITECH_VENDOR_ID, 0xc54d).expect("c54d receiver");
120
121        assert_eq!(receiver.brand, ReceiverBrand::Lightspeed);
122        assert_eq!(receiver.protocol, ReceiverProtocol::Unifying);
123    }
124
125    #[test]
126    fn lookup_requires_the_matching_vendor() {
127        assert!(find_receiver(0xffff, 0xc54d).is_none());
128    }
129}