use std::sync::Arc;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use crate::{
channel::HidppChannel,
event::EventEmitter,
feature::{CreatableFeature, EmittingFeature, Feature},
nibble::{self, U4},
protocol::v20::{self, Hidpp20Error},
};
pub struct ThumbwheelFeatureV0 {
chan: Arc<HidppChannel>,
device_index: u8,
feature_index: u8,
emitter: Arc<EventEmitter<ThumbwheelEvent>>,
msg_listener_hdl: u32,
}
impl CreatableFeature for ThumbwheelFeatureV0 {
const ID: u16 = 0x2150;
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
|| nibble::combine(header.software_id, header.function_id) != 0
{
return;
}
let payload = msg.extend_payload();
let Ok(rotation_status) = ThumbwheelRotationStatus::try_from(payload[4]) else {
return;
};
emitter.emit(ThumbwheelEvent::StatusUpdate(ThumbwheelStatusUpdate {
rotation: i16::from_be_bytes(payload[0..=1].try_into().unwrap()),
time_elapsed: u16::from_be_bytes(payload[2..=3].try_into().unwrap()),
rotation_status,
touch: payload[5] & (1 << 1) != 0,
proxy: payload[5] & (1 << 2) != 0,
single_tap: payload[5] & (1 << 3) != 0,
}));
}
});
Self {
chan,
device_index,
feature_index,
emitter,
msg_listener_hdl: hdl,
}
}
}
impl Feature for ThumbwheelFeatureV0 {
}
impl EmittingFeature<ThumbwheelEvent> for ThumbwheelFeatureV0 {
fn listen(&self) -> async_channel::Receiver<ThumbwheelEvent> {
self.emitter.create_receiver()
}
}
impl Drop for ThumbwheelFeatureV0 {
fn drop(&mut self) {
self.chan.remove_msg_listener(self.msg_listener_hdl);
}
}
impl ThumbwheelFeatureV0 {
pub async fn get_thumbwheel_info(&self) -> Result<ThumbwheelInfo, 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(ThumbwheelInfo {
native_resolution: u16::from_be_bytes(payload[0..=1].try_into().unwrap()),
diverted_resolution: u16::from_be_bytes(payload[2..=3].try_into().unwrap()),
time_unit: u16::from_be_bytes(payload[6..=7].try_into().unwrap()),
default_direction: ThumbwheelDirection::try_from(payload[4] & 1)
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
capabilities: ThumbwheelCapabilities::from(payload[5]),
})
}
pub async fn get_thumbwheel_status(&self) -> Result<ThumbwheelStatus, 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(ThumbwheelStatus {
reporting_mode: ThumbwheelReportingMode::try_from(payload[0])
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
direction_inverted: payload[1] & 1 != 0,
touch: payload[1] & (1 << 1) != 0,
proxy: payload[1] & (1 << 2) != 0,
})
}
pub async fn set_thumbwheel_reporting(
&self,
mode: ThumbwheelReportingMode,
invert_direction: bool,
) -> Result<(), Hidpp20Error> {
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.into(),
if invert_direction {
1
} else {
0
},
0x00,
],
))
.await?;
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct ThumbwheelInfo {
pub native_resolution: u16,
pub diverted_resolution: u16,
pub time_unit: u16,
pub default_direction: ThumbwheelDirection,
pub capabilities: ThumbwheelCapabilities,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[non_exhaustive]
#[repr(u8)]
pub enum ThumbwheelDirection {
PositiveWhenLeftOrBack = 0,
PositiveWhenRightOrFront = 1,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct ThumbwheelCapabilities {
pub time_stamp: bool,
pub touch: bool,
pub proxy: bool,
pub single_tap: bool,
}
impl From<u8> for ThumbwheelCapabilities {
fn from(value: u8) -> Self {
Self {
time_stamp: value & 1 != 0,
touch: value & (1 << 1) != 0,
proxy: value & (1 << 2) != 0,
single_tap: value & (1 << 3) != 0,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct ThumbwheelStatus {
pub reporting_mode: ThumbwheelReportingMode,
pub direction_inverted: bool,
pub touch: bool,
pub proxy: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[non_exhaustive]
#[repr(u8)]
pub enum ThumbwheelReportingMode {
Native = 0,
Diverted = 1,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ThumbwheelEvent {
StatusUpdate(ThumbwheelStatusUpdate),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct ThumbwheelStatusUpdate {
pub rotation: i16,
pub time_elapsed: u16,
pub rotation_status: ThumbwheelRotationStatus,
pub touch: bool,
pub proxy: bool,
pub single_tap: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[non_exhaustive]
#[repr(u8)]
pub enum ThumbwheelRotationStatus {
Inactive = 0,
Start = 1,
Active = 2,
Stop = 3,
}