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