hidpp/feature/
disable_keys_by_usage.rs1use openlogi_hidpp_derive::Feature;
8
9use crate::{
10 feature::FeatureEndpoint,
11 protocol::v20::{ErrorType, Hidpp20Error},
12};
13
14const USAGES_PER_PACKET: usize = 16;
16
17#[derive(Clone, Feature)]
19#[creatable(id = 0x4522, version = 0)]
20pub struct DisableKeysByUsageFeature {
21 endpoint: FeatureEndpoint,
23}
24
25impl DisableKeysByUsageFeature {
26 pub async fn get_capabilities(&self) -> Result<u8, Hidpp20Error> {
28 Ok(self.endpoint.call(0, [0; 3]).await?.extend_payload()[0])
29 }
30
31 pub async fn disable_keys(&self, usages: &[u8]) -> Result<(), Hidpp20Error> {
38 validate_usages(usages)?;
39 for packet in usage_packets(usages) {
40 self.endpoint.call_long(1, packet).await?;
41 }
42 Ok(())
43 }
44
45 pub async fn enable_keys(&self, usages: &[u8]) -> Result<(), Hidpp20Error> {
51 validate_usages(usages)?;
52 for packet in usage_packets(usages) {
53 self.endpoint.call_long(2, packet).await?;
54 }
55 Ok(())
56 }
57
58 pub async fn enable_all_keys(&self) -> Result<(), Hidpp20Error> {
60 self.endpoint.call(3, [0; 3]).await?;
61 Ok(())
62 }
63}
64
65fn validate_usages(usages: &[u8]) -> Result<(), Hidpp20Error> {
66 if usages.contains(&0) {
67 return Err(Hidpp20Error::Feature(ErrorType::InvalidArgument));
68 }
69 Ok(())
70}
71
72fn usage_packets(usages: &[u8]) -> Vec<[u8; USAGES_PER_PACKET]> {
79 usages
80 .chunks(USAGES_PER_PACKET)
81 .map(|chunk| {
82 let mut packet = [0u8; USAGES_PER_PACKET];
83 packet[..chunk.len()].copy_from_slice(chunk);
84 packet
85 })
86 .collect()
87}
88
89#[cfg(test)]
90mod tests {
91 use std::assert_matches;
92
93 use super::{usage_packets, validate_usages};
94 use crate::protocol::v20::{ErrorType, Hidpp20Error};
95
96 #[test]
97 fn empty_usage_list_sends_no_packets() {
98 assert!(usage_packets(&[]).is_empty());
99 }
100
101 #[test]
102 fn short_list_is_zero_terminated() {
103 let packets = usage_packets(&[0x39, 0x3a, 0x3b]);
104 assert_eq!(packets.len(), 1);
105 assert_eq!(packets[0][..3], [0x39, 0x3a, 0x3b]);
106 assert!(packets[0][3..].iter().all(|&b| b == 0));
108 }
109
110 #[test]
111 fn rejects_zero_usage_before_packetizing() {
112 assert_matches!(
113 validate_usages(&[0x39, 0, 0x3a]),
114 Err(Hidpp20Error::Feature(ErrorType::InvalidArgument))
115 );
116 }
117
118 #[test]
119 fn full_packet_has_no_terminator() {
120 let usages: Vec<u8> = (1..=16).collect();
121 let packets = usage_packets(&usages);
122 assert_eq!(packets.len(), 1);
123 assert_eq!(packets[0], usages.as_slice());
124 }
125
126 #[test]
127 fn overflow_splits_into_cumulative_packets() {
128 let usages: Vec<u8> = (1..=18).collect();
129 let packets = usage_packets(&usages);
130 assert_eq!(packets.len(), 2);
131 assert_eq!(packets[0], (1..=16).collect::<Vec<u8>>().as_slice());
132 assert_eq!(packets[1][..2], [17, 18]);
133 assert!(packets[1][2..].iter().all(|&b| b == 0));
134 }
135}