Skip to main content

hidpp/feature/
registry.rs

1//! Maintains a registry of well-known HID++2.0 features and their default
2//! implementations.
3
4use std::{
5    any::TypeId,
6    collections::HashMap,
7    sync::{Arc, LazyLock},
8};
9
10use super::Feature;
11use crate::{
12    channel::HidppChannel,
13    feature::{
14        CreatableFeature, adjustable_dpi::AdjustableDpiFeature,
15        device_friendly_name::DeviceFriendlyNameFeature,
16        device_information::DeviceInformationFeature,
17        device_type_and_name::DeviceTypeAndNameFeature, feature_set::FeatureSetFeature,
18        hires_wheel::HiResWheelFeature, root::RootFeature, smartshift::SmartShiftFeature,
19        thumbwheel::ThumbwheelFeature, unified_battery::UnifiedBatteryFeature,
20        wireless_device_status::WirelessDeviceStatusFeature,
21    },
22};
23
24/// Represents a function that creates a new dynamically sized feature
25/// implementation.
26pub type FeatureImplProducer =
27    fn(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> (TypeId, Arc<dyn Feature>);
28
29/// Represents a known feature implementation starting from a specific feature
30/// version.
31#[derive(Clone, Copy, Debug, Hash)]
32pub struct FeatureVersion {
33    /// The minimum feature version the implementation supports.
34    pub starting_version: u8,
35
36    /// A pointer to a function producing the feature implementation.
37    pub producer: FeatureImplProducer,
38}
39
40/// Represents a known HID++2.0 device feature.
41#[derive(Clone, Copy, Debug, Hash)]
42pub struct KnownFeature {
43    /// The name of the feature.
44    /// This is usually a slightly modified version of the name found in
45    /// Logitech's documentation.
46    pub name: &'static str,
47
48    /// A list of concrete implementations of the feature, each supporting the
49    /// feature starting from a specific version.
50    pub versions: &'static [FeatureVersion],
51}
52
53/// Looks up a feature by its ID.
54pub fn lookup(feature_id: u16) -> Option<KnownFeature> {
55    KNOWN_FEATURES.get(&feature_id).copied()
56}
57
58/// Looks up all implementations supporting a specific feature ID and version
59/// combination.
60pub fn lookup_version(feature_id: u16, feature_version: u8) -> Option<Vec<FeatureVersion>> {
61    lookup(feature_id).map(|feat| {
62        feat.versions
63            .iter()
64            .filter(|&ver| ver.starting_version <= feature_version)
65            .copied()
66            .collect::<Vec<FeatureVersion>>()
67    })
68}
69
70/// Creates a new feature with a dynamic return type.
71fn new_dyn<F: CreatableFeature>(
72    chan: Arc<HidppChannel>,
73    device_index: u8,
74    feature_index: u8,
75) -> (TypeId, Arc<dyn Feature>) {
76    (
77        TypeId::of::<F>(),
78        Arc::new(F::new(chan, device_index, feature_index)),
79    )
80}
81
82/// Builds [`KNOWN_FEATURES`]. Each row is `id "Name"` for a feature we only know
83/// by name, or `id "Name" => Impl, ...` to also register one or more default
84/// implementations through [`new_dyn`]. Listing several impls mirrors a feature
85/// that ships multiple versions, each contributing its own
86/// [`CreatableFeature::STARTING_VERSION`] in declaration order.
87macro_rules! known_features {
88    ( $( $id:literal $name:literal $( => $($feat:ty),+ )? ),* $(,)? ) => {
89        HashMap::from([ $(
90            ($id, KnownFeature { name: $name, versions: known_features!(@versions $( $($feat),+ )?) }),
91        )* ])
92    };
93    (@versions) => { &[] };
94    (@versions $($feat:ty),+) => {
95        &[$(FeatureVersion {
96            starting_version: <$feat>::STARTING_VERSION,
97            producer: new_dyn::<$feat>,
98        }),+]
99    };
100}
101
102static KNOWN_FEATURES: LazyLock<HashMap<u16, KnownFeature>> = LazyLock::new(|| {
103    known_features! {
104    0x0000 "Root" => RootFeature,
105    0x0001 "FeatureSet" => FeatureSetFeature,
106    0x0002 "FeatureInfo",
107    0x0003 "DeviceInformation" => DeviceInformationFeature,
108    0x0004 "UnitId",
109    0x0005 "DeviceTypeAndName" => DeviceTypeAndNameFeature,
110    0x0006 "DeviceGroups",
111    0x0007 "DeviceFriendlyName" => DeviceFriendlyNameFeature,
112    0x0008 "KeepAlive",
113    0x0020 "ConfigChange",
114    0x0021 "UniqueRandomId",
115    0x0030 "TargetSoftware",
116    0x0080 "WirelessSignalStrength",
117    0x00c0 "DfuControlLegacy",
118    0x00c1 "DfuControlUnsigned",
119    0x00c2 "DfuControlSigned",
120    0x00c3 "DfuControlBolt",
121    0x00d0 "Dfu",
122    0x00d1 "DfuResumable",
123    0x1000 "BatteryStatus",
124    0x1001 "BatteryVoltage",
125    0x1004 "UnifiedBattery" => UnifiedBatteryFeature,
126    0x1010 "ChargingControl",
127    0x1300 "LedControl",
128    0x1800 "GenericTest",
129    0x1802 "DeviceReset",
130    0x1805 "OobState",
131    0x1806 "ConfigDeviceProps",
132    0x1814 "ChangeHost",
133    0x1815 "HostsInfo",
134    0x1981 "Backlight1",
135    0x1982 "Backlight2",
136    0x1983 "Backlight3",
137    0x1990 "Illumination",
138    0x19b0 "HapticFeedback",
139    0x19c0 "ForceSensingButton",
140    0x1a00 "PresenterControl",
141    0x1a01 "Sensor3D",
142    0x1b00 "ReprogControls",
143    0x1b01 "ReprogControls2",
144    0x1b02 "ReprogControls3",
145    0x1b03 "ReprogControls4",
146    0x1b04 "ReprogControls5",
147    0x1bc0 "ReportHidUsages",
148    0x1c00 "PersistentRemappableAction",
149    0x1d4b "WirelessDeviceStatus" => WirelessDeviceStatusFeature,
150    0x1df0 "RemainingPairings",
151    0x1f1f "FirmwareProperties",
152    0x1f20 "AdcMeasurement",
153    0x2001 "SwapLeftRightButton",
154    0x2005 "ButtonSwapCancel",
155    0x2006 "PointerAxesOrientation",
156    0x2100 "VerticalScrolling",
157    0x2110 "SmartShiftWheel" => SmartShiftFeature,
158    0x2111 "SmartShiftWheelEnhanced",
159    0x2120 "HighResolutionScrolling",
160    0x2121 "HiResWheel" => HiResWheelFeature,
161    0x2130 "RatchetWheel",
162    0x2150 "Thumbwheel" => ThumbwheelFeature,
163    0x2200 "MousePointer",
164    0x2201 "AdjustableDpi" => AdjustableDpiFeature,
165    0x2202 "ExtendedAdjustableDpi",
166    0x2205 "PointerMotionScaling",
167    0x2230 "SensorAngleSnapping",
168    0x2240 "SurfaceTuning",
169    0x2250 "XyStats",
170    0x2251 "WheelStats",
171    0x2400 "HybridTrackingEngine",
172    0x40a0 "FnInversion",
173    0x40a2 "FnInversionWithDefaultState",
174    0x40a3 "FnInversionForMultiHostDevices",
175    0x4100 "Encryption",
176    0x4220 "LockKeyState",
177    0x4301 "SolarKeyboardDashboard",
178    0x4520 "KeyboardLayout",
179    0x4521 "DisableKeys",
180    0x4522 "DisableKeysByUsage",
181    0x4530 "DualPlatform",
182    0x4531 "MultiPlatform",
183    0x4540 "KeyboardInternationalLayouts",
184    0x4600 "Crown",
185    0x6010 "TouchpadFwItems",
186    0x6011 "TouchpadSwItems",
187    0x6012 "TouchpadWin8FwItems",
188    0x6020 "TapEnable",
189    0x6021 "TapEnableExtended",
190    0x6030 "CursorBallistic",
191    0x6040 "TouchpadResolutionDivider",
192    0x6100 "TouchpadRawXy",
193    0x6110 "TouchMouseRawTouchPoints",
194    0x6120 "BtTouchMouseSettings",
195    0x6500 "Gestures1",
196    0x6501 "Gestures2",
197    0x8010 "GamingGKeys",
198    0x8020 "GamingMKeys",
199    0x8030 "MacroRecord",
200    0x8040 "BrightnessControl",
201    0x8060 "AdjustableReportRate",
202    0x8061 "ExtendedAdjustableReportRate",
203    0x8070 "ColorLedEffects",
204    0x8071 "RgbEffects",
205    0x8080 "PerKeyLighting",
206    0x8081 "PerKeyLighting2",
207    0x8090 "ModeStatus",
208    0x8100 "OnboardProfiles",
209    0x8110 "MouseButtonFilter",
210    0x8111 "LatencyMonitoring",
211    0x8120 "GamingAttachments",
212    0x8123 "ForceFeedback",
213    0x8300 "Sidetone",
214    0x8310 "Equalizer",
215    0x8320 "HeadsetOut",
216    }
217});
218
219#[cfg(test)]
220mod tests {
221    use std::collections::HashMap;
222
223    use super::{FeatureVersion, KnownFeature, new_dyn};
224    use crate::feature::{CreatableFeature, feature_set::FeatureSetFeature, root::RootFeature};
225
226    #[test]
227    fn macro_registers_one_version_per_listed_impl() {
228        // The `=> A, B` form keeps the original table's ability to register
229        // several versioned implementations under a single feature id.
230        let map: HashMap<u16, KnownFeature> = known_features! {
231            0x0000 "NameOnly",
232            0x0001 "OneImpl" => RootFeature,
233            0xffff "TwoImpls" => RootFeature, FeatureSetFeature,
234        };
235
236        assert_eq!(map[&0x0000].versions.len(), 0);
237        assert_eq!(map[&0x0001].versions.len(), 1);
238        assert_eq!(map[&0xffff].versions.len(), 2);
239    }
240}