hidpp/feature/disable_keys_by_usage/
mod.rs1use std::sync::Arc;
8
9use crate::{
10 channel::HidppChannel,
11 feature::{CreatableFeature, Feature, FeatureEndpoint},
12 protocol::v20::{ErrorType, Hidpp20Error},
13};
14
15const USAGES_PER_PACKET: usize = 16;
17
18#[derive(Clone)]
20pub struct DisableKeysByUsageFeature {
21 endpoint: FeatureEndpoint,
23}
24
25impl CreatableFeature for DisableKeysByUsageFeature {
26 const ID: u16 = 0x4522;
27 const STARTING_VERSION: u8 = 0;
28
29 fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
30 Self {
31 endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
32 }
33 }
34}
35
36impl Feature for DisableKeysByUsageFeature {}
37
38impl DisableKeysByUsageFeature {
39 pub async fn get_capabilities(&self) -> Result<u8, Hidpp20Error> {
41 Ok(self.endpoint.call(0, [0; 3]).await?.extend_payload()[0])
42 }
43
44 pub async fn disable_keys(&self, usages: &[u8]) -> Result<(), Hidpp20Error> {
51 validate_usages(usages)?;
52 for packet in usage_packets(usages) {
53 self.endpoint.call_long(1, packet).await?;
54 }
55 Ok(())
56 }
57
58 pub async fn enable_keys(&self, usages: &[u8]) -> Result<(), Hidpp20Error> {
64 validate_usages(usages)?;
65 for packet in usage_packets(usages) {
66 self.endpoint.call_long(2, packet).await?;
67 }
68 Ok(())
69 }
70
71 pub async fn enable_all_keys(&self) -> Result<(), Hidpp20Error> {
73 self.endpoint.call(3, [0; 3]).await?;
74 Ok(())
75 }
76}
77
78fn validate_usages(usages: &[u8]) -> Result<(), Hidpp20Error> {
79 if usages.contains(&0) {
80 return Err(Hidpp20Error::Feature(ErrorType::InvalidArgument));
81 }
82 Ok(())
83}
84
85fn usage_packets(usages: &[u8]) -> Vec<[u8; USAGES_PER_PACKET]> {
92 usages
93 .chunks(USAGES_PER_PACKET)
94 .map(|chunk| {
95 let mut packet = [0u8; USAGES_PER_PACKET];
96 packet[..chunk.len()].copy_from_slice(chunk);
97 packet
98 })
99 .collect()
100}
101
102#[cfg(test)]
103mod tests {
104 use std::assert_matches;
105
106 use super::{usage_packets, validate_usages};
107 use crate::protocol::v20::{ErrorType, Hidpp20Error};
108
109 #[test]
110 fn empty_usage_list_sends_no_packets() {
111 assert!(usage_packets(&[]).is_empty());
112 }
113
114 #[test]
115 fn short_list_is_zero_terminated() {
116 let packets = usage_packets(&[0x39, 0x3a, 0x3b]);
117 assert_eq!(packets.len(), 1);
118 assert_eq!(packets[0][..3], [0x39, 0x3a, 0x3b]);
119 assert!(packets[0][3..].iter().all(|&b| b == 0));
121 }
122
123 #[test]
124 fn rejects_zero_usage_before_packetizing() {
125 assert_matches!(
126 validate_usages(&[0x39, 0, 0x3a]),
127 Err(Hidpp20Error::Feature(ErrorType::InvalidArgument))
128 );
129 }
130
131 #[test]
132 fn full_packet_has_no_terminator() {
133 let usages: Vec<u8> = (1..=16).collect();
134 let packets = usage_packets(&usages);
135 assert_eq!(packets.len(), 1);
136 assert_eq!(packets[0], usages.as_slice());
137 }
138
139 #[test]
140 fn overflow_splits_into_cumulative_packets() {
141 let usages: Vec<u8> = (1..=18).collect();
142 let packets = usage_packets(&usages);
143 assert_eq!(packets.len(), 2);
144 assert_eq!(packets[0], (1..=16).collect::<Vec<u8>>().as_slice());
145 assert_eq!(packets[1][..2], [17, 18]);
146 assert!(packets[1][2..].iter().all(|&b| b == 0));
147 }
148}