use std::sync::Arc;
use derive_builder::Builder;
use futures::{FutureExt, pin_mut, select};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use super::{RECEIVER_DEVICE_INDEX, ReceiverError};
use crate::{
channel::{HidppChannel, MessageListenerGuard},
event::EventEmitter,
protocol::v10::{self, Hidpp10Error},
};
mod event;
pub use event::{DeviceConnection, DeviceKind, Event, PairingError, PairingPasskeyPressType};
pub const VPID_PAIRS: &[(u16, u16)] = &[(0x046d, 0xc548)];
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum Register {
Notifications = 0x00,
Connections = 0x02,
ReceiverInfo = 0xb5,
DeviceDiscovery = 0xc0,
Pairing = 0xc1,
UniqueId = 0xfb,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum InfoSubRegister {
DevicePairingInformation = 0x50,
DeviceCodename = 0x60, }
#[derive(Clone)]
pub struct Receiver {
chan: Arc<HidppChannel>,
emitter: Arc<EventEmitter<Event>>,
_listener: Arc<MessageListenerGuard>,
}
impl Receiver {
pub fn new(chan: Arc<HidppChannel>) -> Result<Self, ReceiverError> {
if !VPID_PAIRS.contains(&(chan.vendor_id, chan.product_id)) {
return Err(ReceiverError::UnknownReceiver);
}
let emitter = Arc::new(EventEmitter::new());
let listener = chan.add_msg_listener_guarded({
let emitter = Arc::clone(&emitter);
move |raw, matched| {
if matched {
return;
}
if let Some(event) = event::decode(&v10::Message::from(raw)) {
emitter.emit(event);
}
}
});
Ok(Receiver {
_listener: Arc::new(listener),
chan,
emitter,
})
}
#[must_use]
pub fn listen(&self) -> async_channel::Receiver<Event> {
self.emitter.create_receiver()
}
pub async fn get_notification_state(&self) -> Result<NotificationState, ReceiverError> {
let response = self
.chan
.read_register(
RECEIVER_DEVICE_INDEX,
Register::Notifications.into(),
[0u8; 3],
)
.await?;
Ok(NotificationState {
wireless_notifications: (response[1] & 1) != 0,
})
}
pub async fn set_notification_state(
&self,
state: NotificationState,
) -> Result<(), ReceiverError> {
self.chan
.write_register(
RECEIVER_DEVICE_INDEX,
Register::Notifications.into(),
[0, u8::from(state.wireless_notifications), 0],
)
.await?;
Ok(())
}
pub async fn count_pairings(&self) -> Result<u8, ReceiverError> {
let response = self
.chan
.read_register(
RECEIVER_DEVICE_INDEX,
Register::Connections.into(),
[0u8; 3],
)
.await?;
Ok(response[1])
}
pub async fn trigger_device_arrival(&self) -> Result<(), ReceiverError> {
self.chan
.write_register(
RECEIVER_DEVICE_INDEX,
Register::Connections.into(),
[0x02, 0x00, 0x00],
)
.await?;
Ok(())
}
pub async fn collect_paired_devices(&self) -> Result<Vec<DeviceConnection>, ReceiverError> {
let mut devices = vec![];
let rx = self.listen();
let fin = self.trigger_device_arrival().fuse();
pin_mut!(fin);
loop {
select! {
_ = fin => break,
res = rx.recv().fuse() => {
let Ok(Event::DeviceConnection(connection)) = res else {
continue;
};
devices.push(connection);
}
}
}
Ok(devices)
}
pub async fn get_unique_id(&self) -> Result<String, ReceiverError> {
let response = self
.chan
.read_long_register(RECEIVER_DEVICE_INDEX, Register::UniqueId.into(), [0u8; 3])
.await?;
Ok(str::from_utf8(&response)
.map_err(|_| Hidpp10Error::UnsupportedResponse)?
.to_string())
}
pub async fn get_device_pairing_information(
&self,
device_index: u8,
) -> Result<DevicePairingInformation, ReceiverError> {
let response = self
.chan
.read_long_register(
RECEIVER_DEVICE_INDEX,
Register::ReceiverInfo.into(),
[
u8::from(InfoSubRegister::DevicePairingInformation) + (device_index & 0x0f),
0x00,
0x00,
],
)
.await?;
Ok(DevicePairingInformation {
wpid: u16::from_le_bytes([response[2], response[3]]),
kind: DeviceKind::from(response[1] & 0x0f),
encrypted: response[1] & (1 << 5) != 0,
online: response[1] & (1 << 6) == 0,
unit_id: [response[4], response[5], response[6], response[7]],
})
}
pub async fn get_device_codename(&self, device_index: u8) -> Result<String, ReceiverError> {
let response = self
.chan
.read_long_register(
RECEIVER_DEVICE_INDEX,
Register::ReceiverInfo.into(),
[
u8::from(InfoSubRegister::DeviceCodename) + (device_index & 0x0f),
0x01,
0x00,
],
)
.await?;
Ok(parse_codename(&response)
.ok_or(Hidpp10Error::UnsupportedResponse)?
.to_string())
}
pub async fn unpair_device(&self, device_index: u8) -> Result<(), ReceiverError> {
let mut payload = [0u8; 16];
payload[0] = 0x03;
payload[1] = device_index;
self.chan
.write_long_register(RECEIVER_DEVICE_INDEX, Register::Pairing.into(), payload)
.await?;
Ok(())
}
pub async fn pair_device(
&self,
slot: u8,
address: [u8; 6],
authentication: u8,
entropy: u8,
) -> Result<(), ReceiverError> {
let mut payload = [0u8; 16];
payload[0] = 0x01;
payload[1] = slot;
payload[2..=7].copy_from_slice(&address);
payload[8] = authentication;
payload[9] = entropy;
self.chan
.write_long_register(RECEIVER_DEVICE_INDEX, Register::Pairing.into(), payload)
.await?;
Ok(())
}
pub async fn discover_devices(&self, timeout: Option<u8>) -> Result<(), ReceiverError> {
self.chan
.write_register(
RECEIVER_DEVICE_INDEX,
Register::DeviceDiscovery.into(),
[timeout.unwrap_or(0x00), 0x01, 0x00],
)
.await?;
Ok(())
}
pub async fn cancel_device_discovery(&self) -> Result<(), ReceiverError> {
self.chan
.write_register(
RECEIVER_DEVICE_INDEX,
Register::DeviceDiscovery.into(),
[0x00, 0x02, 0x00],
)
.await?;
Ok(())
}
}
fn parse_codename(response: &[u8; 16]) -> Option<&str> {
let end = 3usize.saturating_add(usize::from(response[2]));
let raw = response.get(3..end.min(response.len()))?;
str::from_utf8(raw).ok()
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Builder)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct NotificationState {
pub wireless_notifications: bool,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct DevicePairingInformation {
pub wpid: u16,
pub kind: DeviceKind,
pub encrypted: bool,
pub online: bool,
pub unit_id: [u8; 4],
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn codename_with_oversized_length_clamps_to_available_chunk() {
let mut response = [0u8; 16];
response[2] = 200;
response[3..16].copy_from_slice(b"MX Anywhere 3");
assert_eq!(parse_codename(&response), Some("MX Anywhere 3"));
}
#[test]
fn codename_within_bounds_parses() {
let mut response = [0u8; 16];
response[2] = 5;
response[3..8].copy_from_slice(b"Casa!");
assert_eq!(parse_codename(&response), Some("Casa!"));
}
#[test]
fn codename_rejects_invalid_utf8() {
let mut response = [0u8; 16];
response[2] = 2;
response[3] = 0xff;
response[4] = 0xfe;
assert_eq!(parse_codename(&response), None);
}
}