use std::sync::Arc;
use hidpp::{
channel::HidppChannel,
nibble::U4,
protocol::v20::{self, Hidpp20Error},
};
pub const FEATURE_ID: u16 = 0x2150;
const FN_GET_INFO: u8 = 0;
const FN_SET_REPORTING: u8 = 2;
const MODE_NATIVE: u8 = 0;
const MODE_DIVERTED: u8 = 1;
const CAP_SINGLE_TAP: u8 = 0x08;
const EV_SINGLE_TAP: u8 = 0x08;
const EV_PROXY: u8 = 0x04;
const EV_TOUCH: u8 = 0x02;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ThumbwheelInfo {
pub native_res: u16,
pub diverted_res: u16,
pub default_dir: u8,
pub supports_single_tap: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ThumbwheelEvent {
pub rotation: i16,
pub single_tap: bool,
pub touch: bool,
pub proxy: bool,
}
#[must_use]
pub fn decode_event(
msg: &v20::Message,
device_index: u8,
feature_index: u8,
) -> Option<ThumbwheelEvent> {
let header = msg.header();
if header.device_index != device_index
|| header.feature_index != feature_index
|| header.software_id.to_lo() != 0
|| header.function_id.to_lo() != 0
{
return None;
}
let p = msg.extend_payload();
Some(ThumbwheelEvent {
rotation: i16::from_be_bytes([p[0], p[1]]),
single_tap: p[5] & EV_SINGLE_TAP != 0,
touch: p[5] & EV_TOUCH != 0,
proxy: p[5] & EV_PROXY != 0,
})
}
#[derive(Clone)]
pub struct Thumbwheel {
chan: Arc<HidppChannel>,
device_index: u8,
feature_index: u8,
}
impl Thumbwheel {
#[must_use]
pub fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
Self {
chan,
device_index,
feature_index,
}
}
#[must_use]
pub fn feature_index(&self) -> u8 {
self.feature_index
}
async fn call(&self, function_id: u8, params: [u8; 16]) -> Result<[u8; 16], Hidpp20Error> {
let response = self
.chan
.send_v20(v20::Message::Long(
v20::MessageHeader {
device_index: self.device_index,
feature_index: self.feature_index,
function_id: U4::from_lo(function_id),
software_id: self.chan.get_sw_id(),
},
params,
))
.await?;
Ok(response.extend_payload())
}
pub async fn get_info(&self) -> Result<ThumbwheelInfo, Hidpp20Error> {
let p = self.call(FN_GET_INFO, [0; 16]).await?;
Ok(ThumbwheelInfo {
native_res: u16::from_be_bytes([p[0], p[1]]),
diverted_res: u16::from_be_bytes([p[2], p[3]]),
default_dir: p[4] & 0x01,
supports_single_tap: p[5] & CAP_SINGLE_TAP != 0,
})
}
pub async fn set_reporting(&self, diverted: bool, inv_dir: bool) -> Result<(), Hidpp20Error> {
let mut params = [0u8; 16];
params[0] = if diverted { MODE_DIVERTED } else { MODE_NATIVE };
params[1] = u8::from(inv_dir);
self.call(FN_SET_REPORTING, params).await?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn event(function_id: u8, software_id: u8, payload: [u8; 16]) -> v20::Message {
v20::Message::Long(
v20::MessageHeader {
device_index: 2,
feature_index: 6,
function_id: U4::from_lo(function_id),
software_id: U4::from_lo(software_id),
},
payload,
)
}
#[test]
fn decodes_rotation_and_tap() {
let mut p = [0u8; 16];
p[0..2].copy_from_slice(&(-7i16).to_be_bytes());
p[5] = EV_SINGLE_TAP | EV_TOUCH;
assert_eq!(
decode_event(&event(0, 0, p), 2, 6),
Some(ThumbwheelEvent {
rotation: -7,
single_tap: true,
touch: true,
proxy: false,
})
);
}
#[test]
fn ignores_responses_and_foreign_messages() {
let p = [0u8; 16];
assert_eq!(decode_event(&event(0, 5, p), 2, 6), None);
assert_eq!(decode_event(&event(0, 0, p), 2, 9), None);
}
}