use std::{hash::Hash, sync::Arc};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use crate::{
channel::HidppChannel,
event::EventEmitter,
feature::{CreatableFeature, EmittingFeature, Feature, FeatureEndpoint, event_payload},
nibble::U4,
protocol::v20::Hidpp20Error,
};
pub struct HiResWheelFeature {
endpoint: FeatureEndpoint,
emitter: Arc<EventEmitter<HiResWheelEvent>>,
msg_listener_hdl: u32,
}
impl CreatableFeature for HiResWheelFeature {
const ID: u16 = 0x2121;
const STARTING_VERSION: u8 = 0;
fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
let emitter = Arc::new(EventEmitter::new());
let hdl = chan.add_msg_listener({
let emitter = Arc::clone(&emitter);
move |raw, matched| {
let Some((func, payload)) =
event_payload(raw, matched, device_index, feature_index)
else {
return;
};
let event = match func.to_lo() {
0 => {
let Ok(resolution) =
WheelResolution::try_from((payload[0] & (1 << 4)) >> 4)
else {
return;
};
HiResWheelEvent::WheelMovement(WheelMovementData {
resolution,
periods: U4::from_lo(payload[0]),
delta_vertical: i16::from_be_bytes(payload[1..=2].try_into().unwrap()),
})
}
1 => {
let Ok(state) = WheelRatchetState::try_from(payload[0] & 1) else {
return;
};
HiResWheelEvent::RatchetSwitch(state)
}
_ => return,
};
emitter.emit(event);
}
});
Self {
endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
emitter,
msg_listener_hdl: hdl,
}
}
}
impl Feature for HiResWheelFeature {}
impl EmittingFeature<HiResWheelEvent> for HiResWheelFeature {
fn listen(&self) -> async_channel::Receiver<HiResWheelEvent> {
self.emitter.create_receiver()
}
}
impl Drop for HiResWheelFeature {
fn drop(&mut self) {
self.endpoint
.chan()
.remove_msg_listener(self.msg_listener_hdl);
}
}
impl HiResWheelFeature {
pub async fn get_wheel_capabilities(&self) -> Result<WheelCapabilities, Hidpp20Error> {
let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
Ok(WheelCapabilities {
multiplier: payload[0],
has_invert: payload[1] & (1 << 3) != 0,
has_switch: payload[1] & (1 << 2) != 0,
ratches_per_rotation: payload[2],
wheel_diameter: payload[3],
})
}
pub async fn get_wheel_mode(&self) -> Result<WheelMode, Hidpp20Error> {
let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
Ok(WheelMode {
inverted: payload[0] & (1 << 2) != 0,
resolution: WheelResolution::try_from((payload[0] & (1 << 1)) >> 1)
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
target: WheelEventTarget::try_from(payload[0] & 1)
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
})
}
pub async fn set_wheel_mode(
&self,
target: WheelEventTarget,
resolution: WheelResolution,
inverted: bool,
) -> Result<WheelMode, Hidpp20Error> {
let mut mode_byte = 0u8;
if inverted {
mode_byte |= 1 << 2;
}
mode_byte |= u8::from(resolution) << 1;
mode_byte |= u8::from(target);
let payload = self
.endpoint
.call(2, [mode_byte, 0x00, 0x00])
.await?
.extend_payload();
Ok(WheelMode {
inverted: payload[0] & (1 << 2) != 0,
resolution: WheelResolution::try_from((payload[0] & (1 << 1)) >> 1)
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
target: WheelEventTarget::try_from(payload[0] & 1)
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
})
}
pub async fn get_ratchet_switch_state(&self) -> Result<WheelRatchetState, Hidpp20Error> {
let payload = self.endpoint.call(3, [0; 3]).await?.extend_payload();
WheelRatchetState::try_from(payload[0] & 1).map_err(|_| Hidpp20Error::UnsupportedResponse)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct WheelCapabilities {
pub multiplier: u8,
pub has_invert: bool,
pub has_switch: bool,
pub ratches_per_rotation: u8,
pub wheel_diameter: u8,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct WheelMode {
pub inverted: bool,
pub resolution: WheelResolution,
pub target: WheelEventTarget,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum WheelResolution {
Low = 0,
High = 1,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum WheelEventTarget {
Native = 0,
Diverted = 1,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum WheelRatchetState {
Freespin = 0,
Ratchet = 1,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum HiResWheelEvent {
WheelMovement(WheelMovementData),
RatchetSwitch(WheelRatchetState),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct WheelMovementData {
pub resolution: WheelResolution,
pub periods: U4,
pub delta_vertical: i16,
}