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(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
96#[cfg_attr(feature = "serde", derive(serde::Serialize))]
97pub struct ControlId(pub u16);
98
99impl ControlId {
100 fn from_payload(bytes: &[u8]) -> Self {
101 Self(u16_from_be_payload(bytes))
102 }
103}
104
105impl From<u16> for ControlId {
106 fn from(value: u16) -> Self {
107 Self(value)
108 }
109}
110
111impl From<ControlId> for u16 {
112 fn from(value: ControlId) -> Self {
113 value.0
114 }
115}
116
117fn u16_from_be_payload(bytes: &[u8]) -> u16 {
118 u16::from_be_bytes([bytes[0], bytes[1]])
119}
120
121#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
123#[cfg_attr(feature = "serde", derive(serde::Serialize))]
124pub struct TaskId(pub u16);
125
126impl From<u16> for TaskId {
127 fn from(value: u16) -> Self {
128 Self(value)
129 }
130}
131
132impl From<TaskId> for u16 {
133 fn from(value: TaskId) -> Self {
134 value.0
135 }
136}
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 #[must_use]
336 pub fn temporary_diversion(diverted: bool, raw_xy: bool) -> Self {
337 Self {
338 diverted: Some(diverted),
339 raw_xy: Some(raw_xy),
340 ..Self::default()
341 }
342 }
343
344 fn to_payload(self, cid: ControlId) -> [u8; 16] {
345 let mut payload = [0u8; 16];
346 let [cid_hi, cid_lo] = cid.0.to_be_bytes();
347 payload[0] = cid_hi;
348 payload[1] = cid_lo;
349
350 if let Some(value) = self.diverted {
351 payload[2] |= 1 << 1;
352 payload[2] |= u8::from(value);
353 }
354 if let Some(value) = self.persistently_diverted {
355 payload[2] |= 1 << 3;
356 payload[2] |= u8::from(value) << 2;
357 }
358 if let Some(value) = self.raw_xy {
359 payload[2] |= 1 << 5;
360 payload[2] |= u8::from(value) << 4;
361 }
362 if let Some(value) = self.force_raw_xy {
363 payload[2] |= 1 << 7;
364 payload[2] |= u8::from(value) << 6;
365 }
366 if let Some(remap) = self.remap {
367 let [remap_hi, remap_lo] = remap.0.to_be_bytes();
368 payload[3] = remap_hi;
369 payload[4] = remap_lo;
370 }
371 if let Some(value) = self.analytics_key_events {
372 payload[5] |= 1 << 1;
373 payload[5] |= u8::from(value);
374 }
375 if let Some(value) = self.raw_wheel {
376 payload[5] |= 1 << 3;
377 payload[5] |= u8::from(value) << 2;
378 }
379
380 payload
381 }
382}
383
384#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
386#[cfg_attr(feature = "serde", derive(serde::Serialize))]
387pub struct CidReportingChangeEcho {
388 pub cid: ControlId,
390 pub diverted: Option<bool>,
392 pub persistently_diverted: Option<bool>,
394 pub force_raw_xy: Option<bool>,
396 pub raw_xy: Option<bool>,
398 pub remap: Option<ControlId>,
400 pub analytics_key_events: Option<bool>,
402 pub raw_wheel: Option<bool>,
404}
405
406impl CidReportingChangeEcho {
407 fn from_payload(payload: [u8; 16]) -> Self {
408 let remap = ControlId::from_payload(&payload[3..=4]);
409 Self {
410 cid: ControlId::from_payload(&payload[0..=1]),
411 diverted: (payload[2] & (1 << 1) != 0).then_some(payload[2] & (1 << 0) != 0),
412 persistently_diverted: (payload[2] & (1 << 3) != 0)
413 .then_some(payload[2] & (1 << 2) != 0),
414 raw_xy: (payload[2] & (1 << 5) != 0).then_some(payload[2] & (1 << 4) != 0),
415 force_raw_xy: (payload[2] & (1 << 7) != 0).then_some(payload[2] & (1 << 6) != 0),
416 remap: (remap.0 != 0).then_some(remap),
417 analytics_key_events: (payload[5] & (1 << 1) != 0)
418 .then_some(payload[5] & (1 << 0) != 0),
419 raw_wheel: (payload[5] & (1 << 3) != 0).then_some(payload[5] & (1 << 2) != 0),
420 }
421 }
422}
423
424#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
426#[cfg_attr(feature = "serde", derive(serde::Serialize))]
427pub struct ReprogControlsCapabilities {
428 pub reset_all_cid_report_settings: bool,
430}
431
432#[cfg(test)]
433mod tests;