1use openlogi_hidpp_derive::Feature;
9
10use crate::{
11 feature::{EventSource, FeatureEndpoint},
12 protocol::v20::Hidpp20Error,
13};
14
15pub mod control_ids;
16mod event;
17pub mod task_ids;
18
19pub use event::{AnalyticsKeyEvent, RawWheelResolution, ReprogControlsEvent, decode_event};
20
21#[derive(Feature)]
23#[creatable(id = 0x1b04, version = 0)]
24pub struct ReprogControlsFeature {
25 endpoint: FeatureEndpoint,
26 events: EventSource<ReprogControlsEvent>,
27}
28
29impl ReprogControlsFeature {
30 pub async fn get_count(&self) -> Result<u8, Hidpp20Error> {
32 Ok(self.endpoint.call(0, [0; 3]).await?.extend_payload()[0])
33 }
34
35 pub async fn get_cid_info(&self, index: u8) -> Result<CidInfo, Hidpp20Error> {
37 let mut params = [0u8; 16];
38 params[0] = index;
39 let payload = self.endpoint.call_long(1, params).await?.extend_payload();
40 Ok(CidInfo::from_payload(payload))
41 }
42
43 pub async fn get_cid_reporting(&self, cid: ControlId) -> Result<CidReporting, Hidpp20Error> {
45 let [cid_hi, cid_lo] = cid.0.to_be_bytes();
46 let payload = self
47 .endpoint
48 .call(2, [cid_hi, cid_lo, 0])
49 .await?
50 .extend_payload();
51 Ok(CidReporting::from_payload(payload))
52 }
53
54 pub async fn set_cid_reporting(
61 &self,
62 cid: ControlId,
63 change: CidReportingChange,
64 ) -> Result<CidReportingChangeEcho, Hidpp20Error> {
65 let payload = self
66 .endpoint
67 .call_long(3, change.to_payload(cid))
68 .await?
69 .extend_payload();
70 Ok(CidReportingChangeEcho::from_payload(payload))
71 }
72
73 pub async fn get_capabilities(&self) -> Result<ReprogControlsCapabilities, Hidpp20Error> {
78 let payload = self.endpoint.call(4, [0; 3]).await?.extend_payload();
79 Ok(ReprogControlsCapabilities {
80 reset_all_cid_report_settings: payload[0] & 1 != 0,
81 })
82 }
83
84 pub async fn reset_all_cid_report_settings(&self) -> Result<(), Hidpp20Error> {
89 self.endpoint.call(5, [0; 3]).await?;
90 Ok(())
91 }
92}
93
94#[derive(
96 Clone,
97 Copy,
98 Debug,
99 Default,
100 PartialEq,
101 Eq,
102 PartialOrd,
103 Ord,
104 Hash,
105 derive_more::From,
106 derive_more::Into,
107)]
108#[cfg_attr(feature = "serde", derive(serde::Serialize))]
109pub struct ControlId(pub u16);
110
111impl ControlId {
112 fn from_payload(bytes: &[u8]) -> Self {
113 Self(u16_from_be_payload(bytes))
114 }
115}
116
117fn u16_from_be_payload(bytes: &[u8]) -> u16 {
118 u16::from_be_bytes([bytes[0], bytes[1]])
119}
120
121#[derive(
123 Clone,
124 Copy,
125 Debug,
126 Default,
127 PartialEq,
128 Eq,
129 PartialOrd,
130 Ord,
131 Hash,
132 derive_more::From,
133 derive_more::Into,
134)]
135#[cfg_attr(feature = "serde", derive(serde::Serialize))]
136pub struct TaskId(pub u16);
137
138#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
140#[cfg_attr(feature = "serde", derive(serde::Serialize))]
141pub struct CidInfo {
142 pub cid: ControlId,
144 pub task_id: TaskId,
146 pub flags: CidFlags,
148 pub position: u8,
150 pub group: u8,
152 pub group_mask: GroupMask,
154}
155
156impl CidInfo {
157 fn from_payload(payload: [u8; 16]) -> Self {
158 Self {
159 cid: ControlId::from_payload(&payload[0..=1]),
160 task_id: TaskId(u16_from_be_payload(&payload[2..=3])),
161 flags: CidFlags::from_bytes(payload[4], payload[8]),
162 position: payload[5],
163 group: payload[6],
164 group_mask: GroupMask(payload[7]),
165 }
166 }
167}
168
169bitflags::bitflags! {
170 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
172 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
173 pub struct CidFlags: u16 {
174 const MOUSE = 1 << 0;
176 const FUNCTION_KEY = 1 << 1;
178 const HOTKEY = 1 << 2;
180 const FN_TOGGLE = 1 << 3;
182 const REPROGRAMMABLE = 1 << 4;
184 const DIVERTABLE = 1 << 5;
186 const PERSISTENTLY_DIVERTABLE = 1 << 6;
188 const VIRTUAL_CONTROL = 1 << 7;
190 const RAW_XY = 1 << 8;
192 const FORCE_RAW_XY = 1 << 9;
194 const ANALYTICS_KEY_EVENTS = 1 << 10;
196 const RAW_WHEEL = 1 << 11;
198 }
199}
200
201impl CidFlags {
202 fn from_bytes(primary: u8, additional: u8) -> Self {
203 Self::from_bits_retain(u16::from(primary) | (u16::from(additional) << 8))
204 }
205
206 #[must_use]
209 pub fn raw(self) -> u16 {
210 self.bits()
211 }
212
213 #[must_use]
215 pub fn is_mouse(self) -> bool {
216 self.contains(Self::MOUSE)
217 }
218
219 #[must_use]
221 pub fn is_divertable(self) -> bool {
222 self.contains(Self::DIVERTABLE)
223 }
224
225 #[must_use]
227 pub fn is_persistently_divertable(self) -> bool {
228 self.contains(Self::PERSISTENTLY_DIVERTABLE)
229 }
230
231 #[must_use]
233 pub fn is_virtual_control(self) -> bool {
234 self.contains(Self::VIRTUAL_CONTROL)
235 }
236
237 #[must_use]
239 pub fn supports_raw_xy(self) -> bool {
240 self.contains(Self::RAW_XY)
241 }
242
243 #[must_use]
245 pub fn supports_force_raw_xy(self) -> bool {
246 self.contains(Self::FORCE_RAW_XY)
247 }
248
249 #[must_use]
251 pub fn supports_analytics_key_events(self) -> bool {
252 self.contains(Self::ANALYTICS_KEY_EVENTS)
253 }
254
255 #[must_use]
257 pub fn supports_raw_wheel(self) -> bool {
258 self.contains(Self::RAW_WHEEL)
259 }
260}
261
262#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
264#[cfg_attr(feature = "serde", derive(serde::Serialize))]
265pub struct GroupMask(pub u8);
266
267#[expect(
269 clippy::struct_excessive_bools,
270 reason = "each field is an independent wire flag from getCidReporting, not a state machine"
271)]
272#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
273#[cfg_attr(feature = "serde", derive(serde::Serialize))]
274pub struct CidReporting {
275 pub cid: ControlId,
277 pub diverted: bool,
279 pub persistently_diverted: bool,
281 pub force_raw_xy: bool,
283 pub raw_xy: bool,
285 pub remap: Option<ControlId>,
287 pub analytics_key_events: bool,
289 pub raw_wheel: bool,
291}
292
293impl CidReporting {
294 fn from_payload(payload: [u8; 16]) -> Self {
295 let remap = ControlId::from_payload(&payload[3..=4]);
296 Self {
297 cid: ControlId::from_payload(&payload[0..=1]),
298 diverted: payload[2] & (1 << 0) != 0,
299 persistently_diverted: payload[2] & (1 << 2) != 0,
300 raw_xy: payload[2] & (1 << 4) != 0,
301 force_raw_xy: payload[2] & (1 << 6) != 0,
302 remap: (remap.0 != 0).then_some(remap),
303 analytics_key_events: payload[5] & (1 << 0) != 0,
304 raw_wheel: payload[5] & (1 << 2) != 0,
305 }
306 }
307}
308
309#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
314#[cfg_attr(feature = "serde", derive(serde::Serialize))]
315pub struct CidReportingChange {
316 pub diverted: Option<bool>,
318 pub persistently_diverted: Option<bool>,
320 pub force_raw_xy: Option<bool>,
322 pub raw_xy: Option<bool>,
324 pub remap: Option<ControlId>,
327 pub analytics_key_events: Option<bool>,
329 pub raw_wheel: Option<bool>,
331}
332
333impl CidReportingChange {
334 fn to_payload(self, cid: ControlId) -> [u8; 16] {
335 let mut payload = [0u8; 16];
336 let [cid_hi, cid_lo] = cid.0.to_be_bytes();
337 payload[0] = cid_hi;
338 payload[1] = cid_lo;
339
340 if let Some(value) = self.diverted {
341 payload[2] |= 1 << 1;
342 payload[2] |= u8::from(value);
343 }
344 if let Some(value) = self.persistently_diverted {
345 payload[2] |= 1 << 3;
346 payload[2] |= u8::from(value) << 2;
347 }
348 if let Some(value) = self.raw_xy {
349 payload[2] |= 1 << 5;
350 payload[2] |= u8::from(value) << 4;
351 }
352 if let Some(value) = self.force_raw_xy {
353 payload[2] |= 1 << 7;
354 payload[2] |= u8::from(value) << 6;
355 }
356 if let Some(remap) = self.remap {
357 let [remap_hi, remap_lo] = remap.0.to_be_bytes();
358 payload[3] = remap_hi;
359 payload[4] = remap_lo;
360 }
361 if let Some(value) = self.analytics_key_events {
362 payload[5] |= 1 << 1;
363 payload[5] |= u8::from(value);
364 }
365 if let Some(value) = self.raw_wheel {
366 payload[5] |= 1 << 3;
367 payload[5] |= u8::from(value) << 2;
368 }
369
370 payload
371 }
372}
373
374#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
376#[cfg_attr(feature = "serde", derive(serde::Serialize))]
377pub struct CidReportingChangeEcho {
378 pub cid: ControlId,
380 pub diverted: Option<bool>,
382 pub persistently_diverted: Option<bool>,
384 pub force_raw_xy: Option<bool>,
386 pub raw_xy: Option<bool>,
388 pub remap: Option<ControlId>,
390 pub analytics_key_events: Option<bool>,
392 pub raw_wheel: Option<bool>,
394}
395
396impl CidReportingChangeEcho {
397 fn from_payload(payload: [u8; 16]) -> Self {
398 let remap = ControlId::from_payload(&payload[3..=4]);
399 Self {
400 cid: ControlId::from_payload(&payload[0..=1]),
401 diverted: (payload[2] & (1 << 1) != 0).then_some(payload[2] & (1 << 0) != 0),
402 persistently_diverted: (payload[2] & (1 << 3) != 0)
403 .then_some(payload[2] & (1 << 2) != 0),
404 raw_xy: (payload[2] & (1 << 5) != 0).then_some(payload[2] & (1 << 4) != 0),
405 force_raw_xy: (payload[2] & (1 << 7) != 0).then_some(payload[2] & (1 << 6) != 0),
406 remap: (remap.0 != 0).then_some(remap),
407 analytics_key_events: (payload[5] & (1 << 1) != 0)
408 .then_some(payload[5] & (1 << 0) != 0),
409 raw_wheel: (payload[5] & (1 << 3) != 0).then_some(payload[5] & (1 << 2) != 0),
410 }
411 }
412}
413
414#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
416#[cfg_attr(feature = "serde", derive(serde::Serialize))]
417pub struct ReprogControlsCapabilities {
418 pub reset_all_cid_report_settings: bool,
420}
421
422#[cfg(test)]
423mod tests;