use std::sync::Arc;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use crate::{
channel::HidppChannel,
feature::{CreatableFeature, Feature, FeatureEndpoint},
protocol::v20::Hidpp20Error,
};
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SupportedWaveforms: u32 {
const DAMP_STATE_CHANGE = 1 << 1;
const SUBTLE_COLLISION = 1 << 4;
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[repr(u8)]
pub enum HapticWaveform {
DampStateChange = 1,
SubtleCollision = 4,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct HapticIntensity(u8);
impl HapticIntensity {
pub const MAX: u8 = 100;
#[must_use]
pub const fn new(value: u8) -> Option<Self> {
if value <= Self::MAX {
Some(Self(value))
} else {
None
}
}
#[must_use]
pub const fn get(self) -> u8 {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct HapticConfiguration {
pub enabled: bool,
pub intensity: HapticIntensity,
pub level_count: u8,
pub level_step: u8,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct HapticCapabilities {
pub unknown_prefix: [u8; 4],
pub waveforms: SupportedWaveforms,
}
#[derive(Clone)]
pub struct HapticFeedbackFeature {
endpoint: FeatureEndpoint,
}
impl CreatableFeature for HapticFeedbackFeature {
const ID: u16 = 0x19b0;
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 HapticFeedbackFeature {}
impl HapticFeedbackFeature {
pub async fn get_capabilities(&self) -> Result<HapticCapabilities, Hidpp20Error> {
let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
Ok(HapticCapabilities {
unknown_prefix: payload[0..4]
.try_into()
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
waveforms: SupportedWaveforms::from_bits_retain(u32::from_be_bytes(
payload[4..8]
.try_into()
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
)),
})
}
pub async fn get_configuration(&self) -> Result<HapticConfiguration, Hidpp20Error> {
let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
let enabled = match payload[0] {
0 => false,
1 => true,
_ => return Err(Hidpp20Error::UnsupportedResponse),
};
let Some(intensity) = HapticIntensity::new(payload[1]) else {
return Err(Hidpp20Error::UnsupportedResponse);
};
Ok(HapticConfiguration {
enabled,
intensity,
level_step: payload[2] >> 4,
level_count: payload[2] & 0x0f,
})
}
pub async fn set_configuration(
&self,
enabled: bool,
intensity: HapticIntensity,
) -> Result<(), Hidpp20Error> {
self.endpoint
.call(2, [u8::from(enabled), intensity.get(), 0])
.await?;
Ok(())
}
pub async fn play(&self, waveform: HapticWaveform) -> Result<(), Hidpp20Error> {
self.endpoint.call(4, [waveform.into(), 0, 0]).await?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn intensity_rejects_values_above_one_hundred() {
assert_eq!(
HapticIntensity::new(100).map(HapticIntensity::get),
Some(100)
);
assert_eq!(HapticIntensity::new(101), None);
}
#[test]
fn waveform_mask_retains_unknown_bits() {
let mask = SupportedWaveforms::from_bits_retain((1 << 4) | (1 << 31));
assert!(mask.contains(SupportedWaveforms::SUBTLE_COLLISION));
assert_eq!(mask.bits(), (1 << 4) | (1 << 31));
}
}