use std::sync::Arc;
use num_enum::{FromPrimitive, IntoPrimitive, TryFromPrimitive};
use crate::{
channel::{HidppChannel, MessageListenerGuard},
event::EventEmitter,
protocol::v10,
receiver::{RECEIVER_DEVICE_INDEX, ReceiverError},
};
pub const VPID_PAIRS: &[(u16, u16)] = &[
(0x046d, 0xc52b),
(0x046d, 0xc532),
(0x046d, 0xc539),
(0x046d, 0xc53f),
(0x046d, 0xc547),
];
#[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,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum InfoSubRegister {
ReceiverInfo = 0x03,
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) = decode_notification(&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 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 set_wireless_notifications(&self, enabled: bool) -> Result<(), ReceiverError> {
const WIRELESS: u8 = 0x01;
let mut flags = self
.chan
.read_register(
RECEIVER_DEVICE_INDEX,
Register::Notifications.into(),
[0; 3],
)
.await?;
if enabled {
flags[1] |= WIRELESS;
} else {
flags[1] &= !WIRELESS;
}
self.chan
.write_register(RECEIVER_DEVICE_INDEX, Register::Notifications.into(), flags)
.await?;
Ok(())
}
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 get_receiver_info(&self) -> Result<ReceiverInfo, ReceiverError> {
let response = self
.chan
.read_long_register(
RECEIVER_DEVICE_INDEX,
Register::ReceiverInfo.into(),
[InfoSubRegister::ReceiverInfo.into(), 0, 0],
)
.await?;
Ok(ReceiverInfo {
serial_number: hex::encode_upper(&response[1..=4]),
pairing_slots: response[6],
})
}
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 << 4) != 0,
online: response[1] & (1 << 6) == 0,
unit_id: [response[4], response[5], response[6], response[7]],
})
}
pub async fn get_unique_id(&self) -> Result<String, ReceiverError> {
self.get_receiver_info().await.map(|i| i.serial_number)
}
}
const DEVICE_CONNECTION_SUB_ID: u8 = 0x41;
fn decode_notification(msg: &v10::Message) -> Option<Event> {
let header = msg.header();
if header.sub_id != DEVICE_CONNECTION_SUB_ID {
return None;
}
let payload = msg.extend_payload();
Some(Event::DeviceConnection(DeviceConnection {
index: header.device_index,
kind: DeviceKind::from(payload[1] & 0x0f),
encrypted: payload[1] & (1 << 4) != 0,
online: payload[1] & (1 << 6) == 0,
wpid: u16::from_le_bytes([payload[2], payload[3]]),
}))
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct ReceiverInfo {
pub serial_number: String,
pub pairing_slots: u8,
}
#[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, FromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum DeviceKind {
#[num_enum(default)]
Unknown = 0x00,
Keyboard = 0x01,
Mouse = 0x02,
Numpad = 0x03,
Presenter = 0x04,
Remote = 0x05,
Trackball = 0x06,
Touchpad = 0x07,
}
#[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, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum Event {
DeviceConnection(DeviceConnection),
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "expect/unwrap are idiomatic in tests"
)]
mod tests {
use super::{DeviceConnection, DeviceKind, Event, decode_notification};
use crate::protocol::v10::{Message, MessageHeader};
fn notification(device_index: u8, sub_id: u8, payload: [u8; 17]) -> Message {
Message::Long(
MessageHeader {
device_index,
sub_id,
},
payload,
)
}
#[test]
fn device_connection_reads_the_slot_from_the_header() {
let mut payload = [0u8; 17];
payload[1] = 0x02; payload[2] = 0x74;
payload[3] = 0x40;
assert_eq!(
decode_notification(¬ification(5, 0x41, payload)).unwrap(),
Event::DeviceConnection(DeviceConnection {
index: 5,
kind: DeviceKind::Mouse,
encrypted: false,
online: true,
wpid: 0x4074,
})
);
}
#[test]
fn encryption_sits_on_bit_4_unlike_bolt() {
let connection = |status: u8| {
let mut payload = [0u8; 17];
payload[1] = status;
match decode_notification(¬ification(1, 0x41, payload)) {
Some(Event::DeviceConnection(connection)) => connection,
other => panic!("expected a device connection, got {other:?}"),
}
};
assert!(connection(1 << 4).encrypted);
assert!(!connection(1 << 5).encrypted);
}
#[test]
fn bit_6_is_set_when_the_device_is_offline() {
let mut payload = [0u8; 17];
payload[1] = 1 << 6;
let Some(Event::DeviceConnection(connection)) =
decode_notification(¬ification(1, 0x41, payload))
else {
panic!("expected a device connection");
};
assert!(!connection.online);
}
#[test]
fn device_kind_uses_the_unifying_table_not_bolts() {
let kind = |nibble: u8| {
let mut payload = [0u8; 17];
payload[1] = nibble;
match decode_notification(¬ification(1, 0x41, payload)) {
Some(Event::DeviceConnection(connection)) => connection.kind,
other => panic!("expected a device connection, got {other:?}"),
}
};
assert_eq!(kind(0x05), DeviceKind::Remote);
assert_eq!(kind(0x06), DeviceKind::Trackball);
assert_eq!(kind(0x07), DeviceKind::Touchpad);
}
#[test]
fn unmodelled_device_kind_folds_to_unknown_instead_of_dropping_the_event() {
let mut payload = [0u8; 17];
payload[1] = 0x0d;
let Some(Event::DeviceConnection(connection)) =
decode_notification(¬ification(1, 0x41, payload))
else {
panic!("an unknown kind must still produce an event");
};
assert_eq!(connection.kind, DeviceKind::Unknown);
}
#[test]
fn other_sub_ids_are_dropped() {
assert_eq!(decode_notification(¬ification(1, 0x40, [0u8; 17])), None);
assert_eq!(decode_notification(¬ification(1, 0x4f, [0u8; 17])), None);
}
#[test]
fn short_notifications_decode_from_the_zero_padded_payload() {
let short = Message::Short(
MessageHeader {
device_index: 2,
sub_id: 0x41,
},
[0x00, 0x01, 0x74, 0x40],
);
assert_eq!(
decode_notification(&short).unwrap(),
Event::DeviceConnection(DeviceConnection {
index: 2,
kind: DeviceKind::Keyboard,
encrypted: false,
online: true,
wpid: 0x4074,
})
);
}
}