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