use std::sync::Arc;
use crate::{
channel::HidppChannel,
feature::{CreatableFeature, Feature, FeatureEndpoint},
protocol::v20::{ErrorType, Hidpp20Error},
};
const USAGES_PER_PACKET: usize = 16;
#[derive(Clone)]
pub struct DisableKeysByUsageFeature {
endpoint: FeatureEndpoint,
}
impl CreatableFeature for DisableKeysByUsageFeature {
const ID: u16 = 0x4522;
const STARTING_VERSION: u8 = 0;
fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
Self {
endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
}
}
}
impl Feature for DisableKeysByUsageFeature {}
impl DisableKeysByUsageFeature {
pub async fn get_capabilities(&self) -> Result<u8, Hidpp20Error> {
Ok(self.endpoint.call(0, [0; 3]).await?.extend_payload()[0])
}
pub async fn disable_keys(&self, usages: &[u8]) -> Result<(), Hidpp20Error> {
validate_usages(usages)?;
for packet in usage_packets(usages) {
self.endpoint.call_long(1, packet).await?;
}
Ok(())
}
pub async fn enable_keys(&self, usages: &[u8]) -> Result<(), Hidpp20Error> {
validate_usages(usages)?;
for packet in usage_packets(usages) {
self.endpoint.call_long(2, packet).await?;
}
Ok(())
}
pub async fn enable_all_keys(&self) -> Result<(), Hidpp20Error> {
self.endpoint.call(3, [0; 3]).await?;
Ok(())
}
}
fn validate_usages(usages: &[u8]) -> Result<(), Hidpp20Error> {
if usages.contains(&0) {
return Err(Hidpp20Error::Feature(ErrorType::InvalidArgument));
}
Ok(())
}
fn usage_packets(usages: &[u8]) -> Vec<[u8; USAGES_PER_PACKET]> {
usages
.chunks(USAGES_PER_PACKET)
.map(|chunk| {
let mut packet = [0u8; USAGES_PER_PACKET];
packet[..chunk.len()].copy_from_slice(chunk);
packet
})
.collect()
}
#[cfg(test)]
mod tests {
use std::assert_matches;
use super::{usage_packets, validate_usages};
use crate::protocol::v20::{ErrorType, Hidpp20Error};
#[test]
fn empty_usage_list_sends_no_packets() {
assert!(usage_packets(&[]).is_empty());
}
#[test]
fn short_list_is_zero_terminated() {
let packets = usage_packets(&[0x39, 0x3a, 0x3b]);
assert_eq!(packets.len(), 1);
assert_eq!(packets[0][..3], [0x39, 0x3a, 0x3b]);
assert!(packets[0][3..].iter().all(|&b| b == 0));
}
#[test]
fn rejects_zero_usage_before_packetizing() {
assert_matches!(
validate_usages(&[0x39, 0, 0x3a]),
Err(Hidpp20Error::Feature(ErrorType::InvalidArgument))
);
}
#[test]
fn full_packet_has_no_terminator() {
let usages: Vec<u8> = (1..=16).collect();
let packets = usage_packets(&usages);
assert_eq!(packets.len(), 1);
assert_eq!(packets[0], usages.as_slice());
}
#[test]
fn overflow_splits_into_cumulative_packets() {
let usages: Vec<u8> = (1..=18).collect();
let packets = usage_packets(&usages);
assert_eq!(packets.len(), 2);
assert_eq!(packets[0], (1..=16).collect::<Vec<u8>>().as_slice());
assert_eq!(packets[1][..2], [17, 18]);
assert!(packets[1][2..].iter().all(|&b| b == 0));
}
}