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,
event::EventEmitter,
protocol::v10::{self, Hidpp10Error},
};
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>>,
msg_listener_hdl: u32,
}
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 hdl = chan.add_msg_listener({
let emitter = Arc::clone(&emitter);
move |raw, matched| {
if matched {
return;
}
let parsed = v10::Message::from(raw);
let header = parsed.header();
let payload = parsed.extend_payload();
if header.device_index != RECEIVER_DEVICE_INDEX && header.sub_id != 0x41 {
return;
}
match header.sub_id {
0x41 => {
let Ok(kind) = DeviceKind::try_from(payload[1] & 0x0f) else {
return;
};
emitter.emit(Event::DeviceConnection(DeviceConnection {
index: header.device_index,
kind,
encrypted: payload[1] & (1 << 5) != 0,
online: payload[1] & (1 << 6) == 0,
wpid: u16::from_le_bytes(payload[2..=3].try_into().unwrap()),
}));
}
0x4f => {
match payload[2] {
0 => {
let Ok(kind) = DeviceKind::try_from(payload[4] & 0x0f) else {
return;
};
emitter.emit(Event::DeviceDiscoveryDeviceDetails {
counter: payload[0] as u16 + payload[1] as u16 * 256,
kind,
wpid: u16::from_le_bytes(payload[5..=6].try_into().unwrap()),
address: payload[7..=12].try_into().unwrap(),
authentication: payload[15],
});
}
1 => {
let Ok(name) =
str::from_utf8(&payload[4..(4 + payload[3] as usize)])
else {
return;
};
emitter.emit(Event::DeviceDiscoveryDeviceName {
counter: payload[0] as u16 + payload[1] as u16 * 256,
name: name.to_string(),
});
}
_ => (),
}
}
0x53 => {
emitter.emit(Event::DeviceDiscoveryStatus {
discovery_enabled: payload[0] == 0x00,
});
}
0x54 => {
let error = if payload[1] == 0x00 {
None
} else {
let Ok(parsed) = PairingError::try_from(payload[1]) else {
return;
};
Some(parsed)
};
emitter.emit(Event::PairingStatus {
device_address: payload[2..=7].try_into().unwrap(),
pairing_error: error,
slot: if payload[8] == 0x00 {
None
} else {
Some(payload[8])
},
});
}
0x4d => {
let Ok(passkey) = str::from_utf8(&payload[1..=6]) else {
return;
};
emitter.emit(Event::PairingPasskeyRequest {
device_address: payload[7..=12].try_into().unwrap(),
passkey: passkey.to_string(),
});
}
0x4e => {
let Ok(press_type) = PairingPasskeyPressType::try_from(payload[0]) else {
return;
};
emitter.emit(Event::PairingPasskeyPressed {
device_address: payload[1..=6].try_into().unwrap(),
press_type,
});
}
_ => (),
}
}
});
Ok(Receiver {
chan,
emitter,
msg_listener_hdl: hdl,
})
}
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, if state.wireless_notifications { 1 } else { 0 }, 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..=3].try_into().unwrap()),
kind: DeviceKind::try_from(response[1] & 0x0f)
.map_err(|_| Hidpp10Error::UnsupportedResponse)?,
encrypted: response[1] & (1 << 5) != 0,
online: response[1] & (1 << 6) == 0,
unit_id: response[4..=7].try_into().unwrap(),
})
}
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?;
let end_idx = 3 + response[2] as usize;
Ok(str::from_utf8(&response[3..end_idx])
.map_err(|_| 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(())
}
}
impl Drop for Receiver {
fn drop(&mut self) {
self.chan.remove_msg_listener(self.msg_listener_hdl);
}
}
#[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],
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum DeviceKind {
Unknown = 0x00,
Keyboard = 0x01,
Mouse = 0x02,
Numpad = 0x03,
Presenter = 0x04,
Remote = 0x07,
Trackball = 0x08,
Touchpad = 0x09,
Tablet = 0x0a,
Gamepad = 0x0b,
Joystick = 0x0c,
Headset = 0x0d,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum Event {
DeviceConnection(DeviceConnection),
DeviceDiscoveryStatus { discovery_enabled: bool },
DeviceDiscoveryDeviceDetails {
counter: u16,
kind: DeviceKind,
wpid: u16,
address: [u8; 6],
authentication: u8,
},
DeviceDiscoveryDeviceName {
counter: u16,
name: String,
},
PairingStatus {
device_address: [u8; 6],
pairing_error: Option<PairingError>,
slot: Option<u8>,
},
PairingPasskeyRequest {
device_address: [u8; 6],
passkey: String,
},
PairingPasskeyPressed {
device_address: [u8; 6],
press_type: PairingPasskeyPressType,
},
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct DeviceConnection {
pub index: u8,
pub kind: DeviceKind,
pub encrypted: bool,
pub online: bool,
pub wpid: u16,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum PairingError {
DeviceTimeout = 0x01,
Failed = 0x02,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum PairingPasskeyPressType {
Initialization = 0x00,
Keypress = 0x01,
Submit = 0x04,
}