use std::{hash::Hash, sync::Arc};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use crate::{
channel::HidppChannel,
event::EventEmitter,
feature::{CreatableFeature, EmittingFeature, Feature},
nibble::U4,
protocol::v20::{self, Hidpp20Error},
};
pub struct HiResWheelFeatureV0 {
chan: Arc<HidppChannel>,
device_index: u8,
feature_index: u8,
emitter: Arc<EventEmitter<HiResWheelEvent>>,
msg_listener_hdl: u32,
}
impl CreatableFeature for HiResWheelFeatureV0 {
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| {
if matched {
return;
}
let msg = v20::Message::from(raw);
let header = msg.header();
if header.device_index != device_index
|| header.feature_index != feature_index
|| header.software_id.to_lo() != 0
{
return;
}
let payload = msg.extend_payload();
let event = match header.function_id.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 {
chan,
device_index,
feature_index,
emitter,
msg_listener_hdl: hdl,
}
}
}
impl Feature for HiResWheelFeatureV0 {
}
impl EmittingFeature<HiResWheelEvent> for HiResWheelFeatureV0 {
fn listen(&self) -> async_channel::Receiver<HiResWheelEvent> {
self.emitter.create_receiver()
}
}
impl Drop for HiResWheelFeatureV0 {
fn drop(&mut self) {
self.chan.remove_msg_listener(self.msg_listener_hdl);
}
}
impl HiResWheelFeatureV0 {
pub async fn get_wheel_capabilities(&self) -> Result<WheelCapabilities, Hidpp20Error> {
let response = self
.chan
.send_v20(v20::Message::Short(
v20::MessageHeader {
device_index: self.device_index,
feature_index: self.feature_index,
function_id: U4::from_lo(0),
software_id: self.chan.get_sw_id(),
},
[0x00, 0x00, 0x00],
))
.await?;
let payload = response.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 response = self
.chan
.send_v20(v20::Message::Short(
v20::MessageHeader {
device_index: self.device_index,
feature_index: self.feature_index,
function_id: U4::from_lo(1),
software_id: self.chan.get_sw_id(),
},
[0x00, 0x00, 0x00],
))
.await?;
let payload = response.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 response = self
.chan
.send_v20(v20::Message::Short(
v20::MessageHeader {
device_index: self.device_index,
feature_index: self.feature_index,
function_id: U4::from_lo(2),
software_id: self.chan.get_sw_id(),
},
[mode_byte, 0x00, 0x00],
))
.await?;
let payload = response.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 response = self
.chan
.send_v20(v20::Message::Short(
v20::MessageHeader {
device_index: self.device_index,
feature_index: self.feature_index,
function_id: U4::from_lo(3),
software_id: self.chan.get_sw_id(),
},
[0x00, 0x00, 0x00],
))
.await?;
let payload = response.extend_payload();
WheelRatchetState::try_from(payload[0] & 1).map_err(|_| Hidpp20Error::UnsupportedResponse)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[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)]
#[non_exhaustive]
pub struct WheelMode {
pub inverted: bool,
pub resolution: WheelResolution,
pub target: WheelEventTarget,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[non_exhaustive]
#[repr(u8)]
pub enum WheelResolution {
Low = 0,
High = 1,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[non_exhaustive]
#[repr(u8)]
pub enum WheelEventTarget {
Native = 0,
Diverted = 1,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[non_exhaustive]
#[repr(u8)]
pub enum WheelRatchetState {
Freespin = 0,
Ratchet = 1,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum HiResWheelEvent {
WheelMovement(WheelMovementData),
RatchetSwitch(WheelRatchetState),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct WheelMovementData {
pub resolution: WheelResolution,
pub periods: U4,
pub delta_vertical: i16,
}