use std::{
collections::{HashMap, VecDeque},
sync::{
Arc, Mutex, Weak,
atomic::{AtomicU8, AtomicU32, AtomicU64, Ordering},
},
thread::{self, JoinHandle},
time::Duration,
};
use futures::{FutureExt, channel::oneshot, select};
use tracing::trace;
use crate::{nibble::U4, sync::lock};
mod error;
mod message;
mod observation;
mod raw;
#[cfg(test)]
pub(crate) mod tests;
pub use error::ChannelError;
pub use message::{
HidppMessage, LONG_REPORT_ID, LONG_REPORT_LENGTH, SHORT_REPORT_ID, SHORT_REPORT_LENGTH,
};
pub use observation::{ChannelObservation, ChannelObserver, ObservedReport, RequestOutcome};
pub use raw::RawHidChannel;
use observation::{RequestObservation, emit_report};
use raw::supports_short_long_hidpp;
const MAX_REPORT_LENGTH: usize = LONG_REPORT_LENGTH;
const MAX_RAW_REPORT_LENGTH: usize = 64;
pub const SEND_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5);
type MessageListener = Arc<dyn Fn(HidppMessage, bool) + Send + Sync + 'static>;
pub struct MessageListenerGuard {
message_listeners: Weak<Mutex<HashMap<u32, MessageListener>>>,
hdl: u32,
}
impl Drop for MessageListenerGuard {
fn drop(&mut self) {
if let Some(message_listeners) = self.message_listeners.upgrade() {
lock(&message_listeners).remove(&self.hdl);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RequestSwId(U4);
impl RequestSwId {
#[must_use]
pub fn new(id: U4) -> Option<Self> {
(id.to_lo() != 0).then_some(Self(id))
}
#[must_use]
pub fn get(self) -> U4 {
self.0
}
}
pub enum SwIdPolicy {
Fixed(RequestSwId),
Rotating(AtomicU8),
Leased {
id: RequestSwId,
free: fn(u8),
},
}
impl SwIdPolicy {
#[must_use]
pub fn rotating() -> Self {
Self::Rotating(AtomicU8::new(0x01))
}
}
impl Default for SwIdPolicy {
fn default() -> Self {
Self::Fixed(RequestSwId(U4::from_lo(0x01)))
}
}
impl Drop for SwIdPolicy {
fn drop(&mut self) {
if let Self::Leased { id, free } = self {
free(id.get().to_lo());
}
}
}
pub struct HidppChannel {
pub supports_short: bool,
pub supports_long: bool,
pub vendor_id: u16,
pub product_id: u16,
raw_channel: Arc<dyn RawHidChannel>,
observer: Option<Arc<dyn ChannelObserver>>,
sw_id_policy: SwIdPolicy,
pending_messages: Arc<Mutex<VecDeque<PendingMessage>>>,
pending_message_id: AtomicU64,
message_listeners: Arc<Mutex<HashMap<u32, MessageListener>>>,
next_listener_hdl: AtomicU32,
read_thread_close: Option<oneshot::Sender<()>>,
read_thread_hdl: Option<JoinHandle<()>>,
}
impl Drop for HidppChannel {
fn drop(&mut self) {
if let Some(read_thread_close) = self.read_thread_close.take() {
let _ = read_thread_close.send(());
}
if let Some(read_thread_hdl) = self.read_thread_hdl.take() {
#[expect(
clippy::unwrap_used,
reason = "propagate a read-thread panic instead of ignoring a crashed background worker"
)]
read_thread_hdl.join().unwrap();
}
}
}
struct PendingMessage {
id: u64,
response_predicate: Box<dyn Fn(&HidppMessage) -> bool + Send>,
sender: oneshot::Sender<HidppMessage>,
}
struct PendingRequest {
id: u64,
pending_messages: Arc<Mutex<VecDeque<PendingMessage>>>,
receiver: oneshot::Receiver<HidppMessage>,
}
impl PendingRequest {
fn register(
id: u64,
pending_messages: Arc<Mutex<VecDeque<PendingMessage>>>,
response_predicate: impl Fn(&HidppMessage) -> bool + Send + 'static,
) -> Self {
let (sender, receiver) = oneshot::channel();
let request = Self {
id,
pending_messages,
receiver,
};
lock(&request.pending_messages).push_back(PendingMessage {
id,
response_predicate: Box::new(response_predicate),
sender,
});
request
}
async fn receive(mut self) -> Option<HidppMessage> {
(&mut self.receiver).await.ok()
}
}
impl Drop for PendingRequest {
fn drop(&mut self) {
let mut pending = lock(&self.pending_messages);
if let Some(pos) = pending.iter().position(|message| message.id == self.id) {
pending.remove(pos);
}
}
}
enum PendingRequestCompletion {
Response(HidppMessage),
WriteFailed(ChannelError),
NoResponse,
TimedOut,
}
fn finish_request(
completion: PendingRequestCompletion,
mut observation: RequestObservation<'_>,
dev: u8,
feat: u8,
) -> Result<HidppMessage, ChannelError> {
match completion {
PendingRequestCompletion::Response(response) => {
observation.complete(RequestOutcome::Succeeded);
trace!(dev, feat, "hidpp response");
Ok(response)
}
PendingRequestCompletion::WriteFailed(error) => {
observation.complete(RequestOutcome::WriteFailed);
trace!(dev, feat, error = ?error, "hidpp no response");
Err(error)
}
PendingRequestCompletion::NoResponse => {
observation.complete(RequestOutcome::NoResponse);
trace!(dev, feat, error = ?ChannelError::NoResponse, "hidpp no response");
Err(ChannelError::NoResponse)
}
PendingRequestCompletion::TimedOut => {
observation.complete(RequestOutcome::TimedOut);
trace!(dev, feat, error = ?ChannelError::Timeout, "hidpp no response");
Err(ChannelError::Timeout)
}
}
}
impl HidppChannel {
pub async fn from_raw_channel(raw: impl RawHidChannel) -> Result<Self, ChannelError> {
Self::from_raw_channel_inner(raw, None).await
}
pub async fn from_raw_channel_with_observer(
raw: impl RawHidChannel,
observer: Arc<dyn ChannelObserver>,
) -> Result<Self, ChannelError> {
Self::from_raw_channel_inner(raw, Some(observer)).await
}
async fn from_raw_channel_inner(
raw: impl RawHidChannel,
observer: Option<Arc<dyn ChannelObserver>>,
) -> Result<Self, ChannelError> {
let (supports_short, supports_long) = supports_short_long_hidpp(&raw).await?;
if !supports_short && !supports_long {
return Err(ChannelError::HidppNotSupported);
}
let raw_channel_rc = Arc::new(raw);
let pending_messages_rc = Arc::new(Mutex::new(VecDeque::<PendingMessage>::new()));
let message_listeners_rc = Arc::new(Mutex::new(HashMap::<u32, MessageListener>::new()));
let (close_sender, close_receiver) = oneshot::channel::<()>();
let read_thread_hdl = thread::spawn({
let raw_channel = Arc::clone(&raw_channel_rc);
let pending_messages = Arc::clone(&pending_messages_rc);
let message_listeners = Arc::clone(&message_listeners_rc);
let observer = observer.clone();
move || {
futures::executor::block_on(read_loop(
&*raw_channel,
&pending_messages,
&message_listeners,
observer.as_deref(),
close_receiver,
));
}
});
Ok(Self {
supports_short,
supports_long,
vendor_id: raw_channel_rc.vendor_id(),
product_id: raw_channel_rc.product_id(),
raw_channel: raw_channel_rc,
observer,
sw_id_policy: SwIdPolicy::default(),
pending_messages: pending_messages_rc,
pending_message_id: AtomicU64::new(1),
next_listener_hdl: AtomicU32::new(1),
message_listeners: message_listeners_rc,
read_thread_close: Some(close_sender),
read_thread_hdl: Some(read_thread_hdl),
})
}
pub fn is_connected(&self) -> bool {
self.raw_channel.is_connected()
}
pub fn set_sw_id_policy(&mut self, policy: SwIdPolicy) {
self.sw_id_policy = policy;
}
pub fn get_sw_id(&self) -> U4 {
match &self.sw_id_policy {
SwIdPolicy::Fixed(id) | SwIdPolicy::Leased { id, .. } => id.get(),
SwIdPolicy::Rotating(counter) => {
let previous =
match counter.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |old| {
Some(if old & 0x0f == 0x0f {
0x01
} else {
old.wrapping_add(1)
})
}) {
Ok(previous) | Err(previous) => previous,
};
U4::from_lo(previous)
}
}
}
pub fn supports_msg(&self, msg: &HidppMessage) -> bool {
match msg {
HidppMessage::Short(_) => self.supports_short,
HidppMessage::Long(_) => self.supports_long,
}
}
fn normalize_outgoing(&self, msg: HidppMessage) -> HidppMessage {
match msg {
HidppMessage::Short(_) if !self.supports_short && self.supports_long => msg.widened(),
other => other,
}
}
pub async fn send(
&self,
msg: HidppMessage,
response_predicate: impl Fn(&HidppMessage) -> bool + Send + 'static,
) -> Result<HidppMessage, ChannelError> {
self.send_with_timeout(msg, response_predicate, SEND_RESPONSE_TIMEOUT)
.await
}
pub async fn send_with_timeout(
&self,
msg: HidppMessage,
response_predicate: impl Fn(&HidppMessage) -> bool + Send + 'static,
timeout: Duration,
) -> Result<HidppMessage, ChannelError> {
let msg = self.normalize_outgoing(msg);
if !self.supports_msg(&msg) {
return Err(ChannelError::MessageTypeNotSupported);
}
let (dev, feat, func) = msg.header();
trace!(dev, feat, func, "hidpp request");
let pending_id = self.pending_message_id.fetch_add(1, Ordering::SeqCst);
let observation = RequestObservation::new(self.observer.as_deref(), pending_id);
let pending_request = PendingRequest::register(
pending_id,
Arc::clone(&self.pending_messages),
response_predicate,
);
let completion = {
let mut request = std::pin::pin!(
async move {
if let Err(error) = self.write_hidpp_report(msg, Some(pending_id)).await {
return PendingRequestCompletion::WriteFailed(error);
}
pending_request.receive().await.map_or(
PendingRequestCompletion::NoResponse,
PendingRequestCompletion::Response,
)
}
.fuse()
);
select! {
completion = request => completion,
() = futures_timer::Delay::new(timeout).fuse() => PendingRequestCompletion::TimedOut,
}
};
finish_request(completion, observation, dev, feat)
}
pub async fn send_write_through(
&self,
msg: HidppMessage,
response_predicate: impl Fn(&HidppMessage) -> bool + Send + 'static,
response_timeout: Duration,
) -> Result<HidppMessage, ChannelError> {
let msg = self.normalize_outgoing(msg);
if !self.supports_msg(&msg) {
return Err(ChannelError::MessageTypeNotSupported);
}
let (dev, feat, func) = msg.header();
trace!(dev, feat, func, "hidpp request");
let pending_id = self.pending_message_id.fetch_add(1, Ordering::SeqCst);
let observation = RequestObservation::new(self.observer.as_deref(), pending_id);
let pending_request = PendingRequest::register(
pending_id,
Arc::clone(&self.pending_messages),
response_predicate,
);
let completion = if let Err(error) = self.write_hidpp_report(msg, Some(pending_id)).await {
drop(pending_request);
PendingRequestCompletion::WriteFailed(error)
} else {
let mut response = std::pin::pin!(pending_request.receive().fuse());
select! {
response = response => response.map_or(
PendingRequestCompletion::NoResponse,
PendingRequestCompletion::Response,
),
() = futures_timer::Delay::new(response_timeout).fuse() => {
PendingRequestCompletion::TimedOut
},
}
};
finish_request(completion, observation, dev, feat)
}
pub async fn send_and_forget(&self, msg: HidppMessage) -> Result<(), ChannelError> {
let msg = self.normalize_outgoing(msg);
if !self.supports_msg(&msg) {
return Err(ChannelError::MessageTypeNotSupported);
}
self.write_hidpp_report(msg, None).await
}
async fn write_hidpp_report(
&self,
msg: HidppMessage,
request_id: Option<u64>,
) -> Result<(), ChannelError> {
let mut buf = [0u8; LONG_REPORT_LENGTH];
let len = msg.write_raw(&mut buf);
emit_report(self.observer.as_deref(), &buf[..len], |report| {
ChannelObservation::OutgoingReport { request_id, report }
});
self.raw_channel
.write_report(&buf[..len])
.await
.map(|_| ())
.map_err(ChannelError::Implementation)
}
pub async fn write_raw_report(&self, report: &[u8]) -> Result<usize, ChannelError> {
self.write_raw_report_with_timeout(report, SEND_RESPONSE_TIMEOUT)
.await
}
async fn write_raw_report_with_timeout(
&self,
report: &[u8],
timeout: Duration,
) -> Result<usize, ChannelError> {
if !(1..=MAX_RAW_REPORT_LENGTH).contains(&report.len()) {
return Err(ChannelError::InvalidRawReportLength(report.len()));
}
emit_report(self.observer.as_deref(), report, |report| {
ChannelObservation::OutgoingReport {
request_id: None,
report,
}
});
let mut write = std::pin::pin!(self.raw_channel.write_report(report).fuse());
select! {
result = write => result.map_err(ChannelError::Implementation),
() = futures_timer::Delay::new(timeout).fuse() => Err(ChannelError::Timeout),
}
}
pub fn add_msg_listener(
&self,
listener: impl Fn(HidppMessage, bool) + Send + Sync + 'static,
) -> u32 {
let hdl = self.next_listener_hdl.fetch_add(1, Ordering::Relaxed);
lock(&self.message_listeners).insert(hdl, Arc::new(listener));
hdl
}
pub fn add_msg_listener_guarded(
&self,
listener: impl Fn(HidppMessage, bool) + Send + Sync + 'static,
) -> MessageListenerGuard {
let hdl = self.add_msg_listener(listener);
MessageListenerGuard {
message_listeners: Arc::downgrade(&self.message_listeners),
hdl,
}
}
pub fn remove_msg_listener(&self, hdl: u32) -> bool {
lock(&self.message_listeners).remove(&hdl).is_some()
}
}
async fn read_loop(
raw_channel: &dyn RawHidChannel,
pending_messages: &Mutex<VecDeque<PendingMessage>>,
message_listeners: &Mutex<HashMap<u32, MessageListener>>,
observer: Option<&dyn ChannelObserver>,
mut close: oneshot::Receiver<()>,
) {
let mut buf = [0u8; MAX_REPORT_LENGTH];
loop {
let res = select! {
_ = close => break,
res = raw_channel.read_report(&mut buf).fuse() => res,
};
let len = match res {
Ok(len) => len,
Err(error) => {
trace!(?error, "read_report error");
continue;
}
};
let Some(msg) = HidppMessage::read_raw(&buf[..len]) else {
emit_report(observer, &buf[..len], |report| {
ChannelObservation::MalformedIncomingReport { report }
});
trace!(len, "report not HID++ — dropped");
continue;
};
let mut matched_id = None;
let pending_count;
{
let mut msgs = lock(pending_messages);
pending_count = msgs.len();
if let Some(pos) = msgs.iter().position(|elem| (elem.response_predicate)(&msg))
&& let Some(waiting) = msgs.remove(pos)
{
matched_id = Some(waiting.id);
let _ = waiting.sender.send(msg);
}
}
emit_report(observer, &buf[..len], |report| {
ChannelObservation::IncomingReport {
request_id: matched_id,
report,
}
});
trace!(
len,
matched = matched_id.is_some(),
pending_count,
payload = format_args!("{:02x?}", &buf[..len.min(16)]),
"raw report received"
);
let listeners: Vec<_> = lock(message_listeners).values().cloned().collect();
for listener in listeners {
listener(msg, matched_id.is_some());
}
}
}