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 brightness_control;
15pub mod change_host;
16pub mod color_led_effects;
17pub mod crown;
18pub mod device_friendly_name;
19pub mod device_information;
20pub mod device_type_and_name;
21pub mod disable_keys;
22pub mod disable_keys_by_usage;
23pub mod dual_platform;
24pub mod equalizer;
25pub mod extended_dpi;
26pub mod extended_report_rate;
27pub mod feature_set;
28pub mod fn_inversion;
29pub mod gestures2;
30pub mod hires_wheel;
31pub mod hosts_info;
32pub mod illumination;
33pub mod mode_status;
34pub mod mouse_pointer;
35pub mod multi_platform;
36pub mod per_key_lighting;
37pub mod persistent_remappable_action;
38pub mod registry;
39pub mod report_rate;
40pub mod reprog_controls;
41pub mod rgb_effects;
42pub mod root;
43pub mod sidetone;
44pub mod smartshift;
45pub mod smartshift_enhanced;
46pub mod solar_dashboard;
47pub mod thumbwheel;
48pub mod touch_mouse_raw;
49pub mod touchpad_raw_xy;
50pub mod unified_battery;
51pub mod vertical_scrolling;
52pub mod wireless_device_status;
53
54pub trait Feature: Any + Send + Sync {}
56
57pub trait CreatableFeature: Feature {
59 const ID: u16;
61
62 const STARTING_VERSION: u8;
64
65 fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self;
67}
68
69pub trait EmittingFeature<T>: Feature {
71 fn listen(&self) -> async_channel::Receiver<T>;
74}
75
76#[derive(Clone)]
83pub(crate) struct FeatureEndpoint {
84 chan: Arc<HidppChannel>,
86
87 device_index: u8,
89
90 feature_index: u8,
92}
93
94impl FeatureEndpoint {
95 pub(crate) fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
97 Self {
98 chan,
99 device_index,
100 feature_index,
101 }
102 }
103
104 fn header(&self, function: u8) -> v20::MessageHeader {
111 debug_assert!(
112 function < 16,
113 "HID++2.0 function id {function} exceeds 4 bits"
114 );
115 v20::MessageHeader {
116 device_index: self.device_index,
117 feature_index: self.feature_index,
118 function_id: U4::from_lo(function),
119 software_id: self.chan.get_sw_id(),
120 }
121 }
122
123 pub(crate) async fn call(
126 &self,
127 function: u8,
128 args: [u8; 3],
129 ) -> Result<v20::Message, Hidpp20Error> {
130 self.chan
131 .send_v20(v20::Message::Short(self.header(function), args))
132 .await
133 }
134
135 pub(crate) async fn call_long(
138 &self,
139 function: u8,
140 args: [u8; 16],
141 ) -> Result<v20::Message, Hidpp20Error> {
142 self.chan
143 .send_v20(v20::Message::Long(self.header(function), args))
144 .await
145 }
146
147 pub(crate) async fn notify(&self, function: u8, args: [u8; 3]) -> Result<(), Hidpp20Error> {
154 self.chan
155 .send_and_forget(v20::Message::Short(self.header(function), args).into())
156 .await?;
157 Ok(())
158 }
159}
160
161pub(crate) fn event_payload(
170 raw: HidppMessage,
171 matched: bool,
172 device_index: u8,
173 feature_index: u8,
174) -> Option<(U4, [u8; LONG_REPORT_LENGTH - 4])> {
175 if matched {
176 return None;
177 }
178
179 let msg = v20::Message::from(raw);
180 let header = msg.header();
181 if header.device_index != device_index
182 || header.feature_index != feature_index
183 || header.software_id.to_lo() != 0
184 {
185 return None;
186 }
187
188 Some((header.function_id, msg.extend_payload()))
189}
190
191#[derive(Clone, Copy, Hash, Debug)]
195#[cfg_attr(feature = "serde", derive(serde::Serialize))]
196#[non_exhaustive]
197pub struct FeatureType {
198 pub obsolete: bool,
202
203 pub hidden: bool,
207
208 pub engineering: bool,
211
212 pub manufacturing_deactivatable: bool,
218
219 pub compliance_deactivatable: bool,
225}
226
227impl From<u8> for FeatureType {
228 fn from(value: u8) -> Self {
229 Self {
230 obsolete: value & (1 << 7) != 0,
231 hidden: value & (1 << 6) != 0,
232 engineering: value & (1 << 5) != 0,
233 manufacturing_deactivatable: value & (1 << 4) != 0,
234 compliance_deactivatable: value & (1 << 3) != 0,
235 }
236 }
237}
238
239impl From<FeatureType> for u8 {
240 fn from(value: FeatureType) -> Self {
241 let mut raw = 0;
242
243 if value.obsolete {
244 raw |= 1 << 7
245 }
246 if value.hidden {
247 raw |= 1 << 6
248 }
249 if value.engineering {
250 raw |= 1 << 5
251 }
252 if value.manufacturing_deactivatable {
253 raw |= 1 << 4
254 }
255 if value.compliance_deactivatable {
256 raw |= 1 << 3
257 }
258
259 raw
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::event_payload;
266 use crate::{
267 channel::HidppMessage,
268 nibble::U4,
269 protocol::v20::{Message, MessageHeader},
270 };
271
272 fn broadcast(device_index: u8, feature_index: u8, function: u8, software: u8) -> HidppMessage {
275 Message::Long(
276 MessageHeader {
277 device_index,
278 feature_index,
279 function_id: U4::from_lo(function),
280 software_id: U4::from_lo(software),
281 },
282 [0xab; 16],
283 )
284 .into()
285 }
286
287 #[test]
288 fn accepts_matching_broadcast_and_returns_sub_id() {
289 let (func, payload) =
290 event_payload(broadcast(2, 5, 1, 0), false, 2, 5).expect("broadcast should pass");
291 assert_eq!(func.to_lo(), 1);
292 assert_eq!(payload, [0xab; 16]);
293 }
294
295 #[test]
296 fn rejects_request_matched_report() {
297 assert!(event_payload(broadcast(2, 5, 0, 0), true, 2, 5).is_none());
300 }
301
302 #[test]
303 fn rejects_other_device_or_feature() {
304 assert!(event_payload(broadcast(9, 5, 0, 0), false, 2, 5).is_none());
305 assert!(event_payload(broadcast(2, 9, 0, 0), false, 2, 5).is_none());
306 }
307
308 #[test]
309 fn gates_on_software_id_only_not_sub_id() {
310 assert!(event_payload(broadcast(2, 5, 0, 1), false, 2, 5).is_none());
316 assert!(event_payload(broadcast(2, 5, 7, 0), false, 2, 5).is_some());
317 }
318}