use std::{any::Any, sync::Arc};
use crate::{
channel::{
AbandonedReply, HidppChannel, HidppMessage, LONG_REPORT_LENGTH, MessageListenerGuard,
RequestSwIds,
},
emitter::EventEmitter,
nibble::U4,
protocol::v20::{self, Hidpp20Error},
};
pub mod adc_measurement;
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 led_control;
pub mod lock_key_state;
pub mod mode_status;
pub mod mouse_pointer;
pub mod multi_platform;
pub mod per_key_lighting;
pub mod persistent_remappable_action;
pub mod pointer_motion_scaling;
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;
fn min_payload_len(_sub_id: u8) -> usize {
0
}
fn decode_message(msg: &v20::Message) -> Option<Self>
where
Self: Sized,
{
let sub_id = msg.header().function_id.to_lo();
if msg.payload_len() < Self::min_payload_len(sub_id) {
return None;
}
Self::decode(sub_id, &msg.extend_payload())
}
fn decode_foreign_response(_response: &v20::Message) -> Option<Self>
where
Self: Sized,
{
None
}
}
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 own = chan.request_sw_ids();
let listener = chan.add_msg_listener_guarded({
let emitter = Arc::clone(&emitter);
move |raw, matched| {
let event = match inbound_report(raw, matched, device_index, feature_index, own) {
Some(InboundReport::Broadcast(msg)) => E::decode_message(&msg),
Some(InboundReport::ForeignResponse(response)) => {
E::decode_foreign_response(&response)
}
None => None,
};
if let Some(event) = event {
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.call_with(function, args, AbandonedReply::Quarantine)
.await
}
pub(crate) async fn call_with(
&self,
function: u8,
args: [u8; 3],
abandoned: AbandonedReply,
) -> Result<v20::Message, Hidpp20Error> {
self.chan
.send_v20_with(v20::Message::Short(self.header(function), args), abandoned)
.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 call_echoed_write_through(
&self,
function: u8,
args: [u8; 3],
) -> Result<v20::Message, Hidpp20Error> {
self.chan
.send_v20_write_through(
v20::Message::Short(self.header(function), args),
move |response| response.extend_payload()[..3] == args,
)
.await
}
pub(crate) async fn call_long_write_through(
&self,
function: u8,
args: [u8; 16],
) -> Result<v20::Message, Hidpp20Error> {
self.chan
.send_v20_write_through(v20::Message::Long(self.header(function), args), |_| true)
.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(())
}
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum InboundReport {
Broadcast(v20::Message),
ForeignResponse(v20::Message),
}
pub(crate) fn inbound_report(
raw: HidppMessage,
matched: bool,
device_index: u8,
feature_index: u8,
own: RequestSwIds,
) -> Option<InboundReport> {
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 {
return None;
}
if header.software_id.to_lo() == 0 {
Some(InboundReport::Broadcast(msg))
} else if own.contains(header.software_id) {
None
} else {
Some(InboundReport::ForeignResponse(msg))
}
}
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)]
mod tests {
use super::{DecodeEvent, InboundReport, inbound_report};
use crate::{
channel::{HidppMessage, RequestSwIds, SwIdPolicy},
nibble::U4,
protocol::v20::{Message, MessageHeader},
};
fn own() -> RequestSwIds {
SwIdPolicy::default().request_sw_ids()
}
fn header(device_index: u8, feature_index: u8, function: u8, software: u8) -> MessageHeader {
MessageHeader {
device_index,
feature_index,
function_id: U4::from_lo(function),
software_id: U4::from_lo(software),
}
}
fn message(device_index: u8, feature_index: u8, function: u8, software: u8) -> Message {
Message::Long(
header(device_index, feature_index, function, software),
[0xab; 16],
)
}
fn report(device_index: u8, feature_index: u8, function: u8, software: u8) -> HidppMessage {
message(device_index, feature_index, function, software).into()
}
#[test]
fn accepts_matching_broadcast_and_returns_sub_id() {
let Some(InboundReport::Broadcast(msg)) =
inbound_report(report(2, 5, 1, 0), false, 2, 5, own())
else {
panic!("broadcast should pass");
};
assert_eq!(msg.header().function_id.to_lo(), 1);
assert_eq!(msg.extend_payload(), [0xab; 16]);
}
#[test]
fn rejects_request_matched_report() {
assert!(inbound_report(report(2, 5, 0, 0), true, 2, 5, own()).is_none());
assert!(inbound_report(report(2, 5, 3, 9), true, 2, 5, own()).is_none());
}
#[test]
fn rejects_other_device_or_feature() {
for software in [0, 9] {
assert!(inbound_report(report(9, 5, 3, software), false, 2, 5, own()).is_none());
assert!(inbound_report(report(2, 9, 3, software), false, 2, 5, own()).is_none());
}
}
#[test]
fn gates_on_software_id_only_not_sub_id() {
assert!(matches!(
inbound_report(report(2, 5, 7, 0), false, 2, 5, own()),
Some(InboundReport::Broadcast(..))
));
}
#[test]
fn drops_an_unmatched_response_this_channel_may_have_asked_for() {
assert!(inbound_report(report(2, 5, 3, 1), false, 2, 5, own()).is_none());
}
#[test]
fn keeps_another_clients_response_whole() {
for software in [2, 9, 15] {
assert_eq!(
inbound_report(report(2, 5, 3, software), false, 2, 5, own()),
Some(InboundReport::ForeignResponse(message(2, 5, 3, software))),
"software id {software}"
);
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct Probe(u8, u8);
impl DecodeEvent for Probe {
fn decode(sub_id: u8, payload: &[u8; 16]) -> Option<Self> {
Some(Self(sub_id, payload[0]))
}
fn min_payload_len(sub_id: u8) -> usize {
if sub_id == 1 { 4 } else { 0 }
}
}
#[test]
fn decode_message_drops_a_wire_payload_below_the_minimum() {
let short = |function| Message::Short(header(2, 5, function, 0), [7, 8, 9]);
assert_eq!(Probe::decode_message(&short(1)), None);
assert_eq!(Probe::decode_message(&short(0)), Some(Probe(0, 7)));
let long = Message::Long(header(2, 5, 1, 0), [7; 16]);
assert_eq!(Probe::decode_message(&long), Some(Probe(1, 7)));
}
}