1use std::{any::Any, sync::Arc};
4
5use crate::{
6 channel::{HidppChannel, HidppMessage, LONG_REPORT_LENGTH},
7 nibble::U4,
8 protocol::v20::{self, Hidpp20Error},
9};
10
11pub mod adjustable_dpi;
12pub mod backlight;
13pub mod battery_status;
14pub mod battery_voltage;
15pub mod brightness_control;
16pub mod change_host;
17pub mod color_led_effects;
18pub mod crown;
19pub mod device_friendly_name;
20pub mod device_information;
21pub mod device_type_and_name;
22pub mod disable_keys;
23pub mod disable_keys_by_usage;
24pub mod dual_platform;
25pub mod equalizer;
26pub mod extended_dpi;
27pub mod extended_report_rate;
28pub mod feature_set;
29pub mod fn_inversion;
30pub mod gestures2;
31pub mod haptic_feedback;
32pub mod hires_wheel;
33pub mod hosts_info;
34pub mod illumination;
35pub mod mode_status;
36pub mod mouse_pointer;
37pub mod multi_platform;
38pub mod per_key_lighting;
39pub mod persistent_remappable_action;
40pub mod registry;
41pub mod report_rate;
42pub mod reprog_controls;
43pub mod rgb_effects;
44pub mod root;
45pub mod sidetone;
46pub mod smartshift;
47pub mod smartshift_enhanced;
48pub mod solar_dashboard;
49pub mod thumbwheel;
50pub mod touch_mouse_raw;
51pub mod touchpad_raw_xy;
52pub mod unified_battery;
53pub mod vertical_scrolling;
54pub mod wireless_device_status;
55
56pub trait Feature: Any + Send + Sync {}
58
59pub trait CreatableFeature: Feature {
61 const ID: u16;
63
64 const STARTING_VERSION: u8;
66
67 fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self;
69}
70
71pub trait EmittingFeature<T>: Feature {
73 fn listen(&self) -> async_channel::Receiver<T>;
76}
77
78#[derive(Clone)]
85pub(crate) struct FeatureEndpoint {
86 chan: Arc<HidppChannel>,
88
89 device_index: u8,
91
92 feature_index: u8,
94}
95
96impl FeatureEndpoint {
97 pub(crate) fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
99 Self {
100 chan,
101 device_index,
102 feature_index,
103 }
104 }
105
106 fn header(&self, function: u8) -> v20::MessageHeader {
113 debug_assert!(
114 function < 16,
115 "HID++2.0 function id {function} exceeds 4 bits"
116 );
117 v20::MessageHeader {
118 device_index: self.device_index,
119 feature_index: self.feature_index,
120 function_id: U4::from_lo(function),
121 software_id: self.chan.get_sw_id(),
122 }
123 }
124
125 pub(crate) async fn call(
128 &self,
129 function: u8,
130 args: [u8; 3],
131 ) -> Result<v20::Message, Hidpp20Error> {
132 self.chan
133 .send_v20(v20::Message::Short(self.header(function), args))
134 .await
135 }
136
137 pub(crate) async fn call_long(
140 &self,
141 function: u8,
142 args: [u8; 16],
143 ) -> Result<v20::Message, Hidpp20Error> {
144 self.chan
145 .send_v20(v20::Message::Long(self.header(function), args))
146 .await
147 }
148
149 pub(crate) async fn notify(&self, function: u8, args: [u8; 3]) -> Result<(), Hidpp20Error> {
156 self.chan
157 .send_and_forget(v20::Message::Short(self.header(function), args).into())
158 .await?;
159 Ok(())
160 }
161}
162
163pub(crate) fn event_payload(
172 raw: HidppMessage,
173 matched: bool,
174 device_index: u8,
175 feature_index: u8,
176) -> Option<(U4, [u8; LONG_REPORT_LENGTH - 4])> {
177 if matched {
178 return None;
179 }
180
181 let msg = v20::Message::from(raw);
182 let header = msg.header();
183 if header.device_index != device_index
184 || header.feature_index != feature_index
185 || header.software_id.to_lo() != 0
186 {
187 return None;
188 }
189
190 Some((header.function_id, msg.extend_payload()))
191}
192
193#[derive(Clone, Copy, Hash, Debug)]
197#[cfg_attr(feature = "serde", derive(serde::Serialize))]
198#[non_exhaustive]
199pub struct FeatureType {
200 pub obsolete: bool,
204
205 pub hidden: bool,
209
210 pub engineering: bool,
213
214 pub manufacturing_deactivatable: bool,
220
221 pub compliance_deactivatable: bool,
227}
228
229impl From<u8> for FeatureType {
230 fn from(value: u8) -> Self {
231 Self {
232 obsolete: value & (1 << 7) != 0,
233 hidden: value & (1 << 6) != 0,
234 engineering: value & (1 << 5) != 0,
235 manufacturing_deactivatable: value & (1 << 4) != 0,
236 compliance_deactivatable: value & (1 << 3) != 0,
237 }
238 }
239}
240
241impl From<FeatureType> for u8 {
242 fn from(value: FeatureType) -> Self {
243 let mut raw = 0;
244
245 if value.obsolete {
246 raw |= 1 << 7
247 }
248 if value.hidden {
249 raw |= 1 << 6
250 }
251 if value.engineering {
252 raw |= 1 << 5
253 }
254 if value.manufacturing_deactivatable {
255 raw |= 1 << 4
256 }
257 if value.compliance_deactivatable {
258 raw |= 1 << 3
259 }
260
261 raw
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::event_payload;
268 use crate::{
269 channel::HidppMessage,
270 nibble::U4,
271 protocol::v20::{Message, MessageHeader},
272 };
273
274 fn broadcast(device_index: u8, feature_index: u8, function: u8, software: u8) -> HidppMessage {
277 Message::Long(
278 MessageHeader {
279 device_index,
280 feature_index,
281 function_id: U4::from_lo(function),
282 software_id: U4::from_lo(software),
283 },
284 [0xab; 16],
285 )
286 .into()
287 }
288
289 #[test]
290 fn accepts_matching_broadcast_and_returns_sub_id() {
291 let (func, payload) =
292 event_payload(broadcast(2, 5, 1, 0), false, 2, 5).expect("broadcast should pass");
293 assert_eq!(func.to_lo(), 1);
294 assert_eq!(payload, [0xab; 16]);
295 }
296
297 #[test]
298 fn rejects_request_matched_report() {
299 assert!(event_payload(broadcast(2, 5, 0, 0), true, 2, 5).is_none());
302 }
303
304 #[test]
305 fn rejects_other_device_or_feature() {
306 assert!(event_payload(broadcast(9, 5, 0, 0), false, 2, 5).is_none());
307 assert!(event_payload(broadcast(2, 9, 0, 0), false, 2, 5).is_none());
308 }
309
310 #[test]
311 fn gates_on_software_id_only_not_sub_id() {
312 assert!(event_payload(broadcast(2, 5, 0, 1), false, 2, 5).is_none());
318 assert!(event_payload(broadcast(2, 5, 7, 0), false, 2, 5).is_some());
319 }
320}