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