use std::{any::Any, sync::Arc};
use crate::{
channel::{HidppChannel, HidppMessage, LONG_REPORT_LENGTH, MessageListenerGuard},
event::EventEmitter,
nibble::U4,
protocol::v20::{self, Hidpp20Error},
};
pub mod adjustable_dpi;
pub mod backlight;
pub mod battery_status;
pub mod battery_voltage;
pub mod brightness_control;
pub mod change_host;
pub mod color_led_effects;
pub mod crown;
pub mod device_friendly_name;
pub mod device_information;
pub mod device_type_and_name;
pub mod disable_keys;
pub mod disable_keys_by_usage;
pub mod dual_platform;
pub mod equalizer;
pub mod extended_dpi;
pub mod extended_report_rate;
pub mod feature_set;
pub mod fn_inversion;
pub mod gestures2;
pub mod haptic_feedback;
pub mod hires_wheel;
pub mod hosts_info;
pub mod illumination;
pub mod mode_status;
pub mod mouse_pointer;
pub mod multi_platform;
pub mod per_key_lighting;
pub mod persistent_remappable_action;
pub mod registry;
pub mod report_rate;
pub mod reprog_controls;
pub mod rgb_effects;
pub mod root;
pub mod sidetone;
pub mod smartshift;
pub mod smartshift_enhanced;
pub mod solar_dashboard;
pub mod thumbwheel;
pub mod touch_mouse_raw;
pub mod touchpad_raw_xy;
pub mod unified_battery;
pub mod vertical_scrolling;
pub mod wireless_device_status;
pub trait Feature: Any + Send + Sync {}
pub trait CreatableFeature: Feature {
const ID: u16;
const STARTING_VERSION: u8;
fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self;
}
pub trait EmittingFeature<T>: Feature {
fn listen(&self) -> async_channel::Receiver<T>;
}
pub(crate) trait DecodeEvent: Clone + Send + Sync + 'static {
fn decode(sub_id: u8, payload: &[u8; LONG_REPORT_LENGTH - 4]) -> Option<Self>
where
Self: Sized;
}
pub(crate) struct EventSource<E: DecodeEvent> {
emitter: Arc<EventEmitter<E>>,
_listener: MessageListenerGuard,
}
impl<E: DecodeEvent> EventSource<E> {
pub(crate) fn attach(chan: &Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
let emitter = Arc::new(EventEmitter::new());
let listener = chan.add_msg_listener_guarded({
let emitter = Arc::clone(&emitter);
move |raw, matched| {
let Some((func, payload)) =
event_payload(raw, matched, device_index, feature_index)
else {
return;
};
if let Some(event) = E::decode(func.to_lo(), &payload) {
emitter.emit(event);
}
}
});
Self {
emitter,
_listener: listener,
}
}
pub(crate) fn listen(&self) -> async_channel::Receiver<E> {
self.emitter.create_receiver()
}
}
#[derive(Clone)]
pub(crate) struct FeatureEndpoint {
chan: Arc<HidppChannel>,
device_index: u8,
feature_index: u8,
}
impl FeatureEndpoint {
pub(crate) fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
Self {
chan,
device_index,
feature_index,
}
}
fn header(&self, function: u8) -> v20::MessageHeader {
debug_assert!(
function < 16,
"HID++2.0 function id {function} exceeds 4 bits"
);
v20::MessageHeader {
device_index: self.device_index,
feature_index: self.feature_index,
function_id: U4::from_lo(function),
software_id: self.chan.get_sw_id(),
}
}
pub(crate) async fn call(
&self,
function: u8,
args: [u8; 3],
) -> Result<v20::Message, Hidpp20Error> {
self.chan
.send_v20(v20::Message::Short(self.header(function), args))
.await
}
pub(crate) async fn call_long(
&self,
function: u8,
args: [u8; 16],
) -> Result<v20::Message, Hidpp20Error> {
self.chan
.send_v20(v20::Message::Long(self.header(function), args))
.await
}
pub(crate) async fn notify(&self, function: u8, args: [u8; 3]) -> Result<(), Hidpp20Error> {
self.chan
.send_and_forget(v20::Message::Short(self.header(function), args).into())
.await?;
Ok(())
}
}
pub(crate) fn event_payload(
raw: HidppMessage,
matched: bool,
device_index: u8,
feature_index: u8,
) -> Option<(U4, [u8; LONG_REPORT_LENGTH - 4])> {
if matched {
return None;
}
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 None;
}
Some((header.function_id, msg.extend_payload()))
}
bitflags::bitflags! {
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct FeatureType: u8 {
const OBSOLETE = 1 << 7;
const HIDDEN = 1 << 6;
const ENGINEERING = 1 << 5;
const MANUFACTURING_DEACTIVATABLE = 1 << 4;
const COMPLIANCE_DEACTIVATABLE = 1 << 3;
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "expect/unwrap are idiomatic in tests"
)]
mod tests {
use super::event_payload;
use crate::{
channel::HidppMessage,
nibble::U4,
protocol::v20::{Message, MessageHeader},
};
fn broadcast(device_index: u8, feature_index: u8, function: u8, software: u8) -> HidppMessage {
Message::Long(
MessageHeader {
device_index,
feature_index,
function_id: U4::from_lo(function),
software_id: U4::from_lo(software),
},
[0xab; 16],
)
.into()
}
#[test]
fn accepts_matching_broadcast_and_returns_sub_id() {
let (func, payload) =
event_payload(broadcast(2, 5, 1, 0), false, 2, 5).expect("broadcast should pass");
assert_eq!(func.to_lo(), 1);
assert_eq!(payload, [0xab; 16]);
}
#[test]
fn rejects_request_matched_report() {
assert!(event_payload(broadcast(2, 5, 0, 0), true, 2, 5).is_none());
}
#[test]
fn rejects_other_device_or_feature() {
assert!(event_payload(broadcast(9, 5, 0, 0), false, 2, 5).is_none());
assert!(event_payload(broadcast(2, 9, 0, 0), false, 2, 5).is_none());
}
#[test]
fn gates_on_software_id_only_not_sub_id() {
assert!(event_payload(broadcast(2, 5, 0, 1), false, 2, 5).is_none());
assert!(event_payload(broadcast(2, 5, 7, 0), false, 2, 5).is_some());
}
}