use crate::protocol::opcode::{
CATCH_CLS_ANY, CATCH_CLS_AXIS, CATCH_CLS_BTN, CATCH_CLS_BUS, CATCH_CLS_CLIP_XFER,
CATCH_CLS_CONTROL, CATCH_CLS_EMIT, CATCH_CLS_HID_IN, CATCH_CLS_HID_OUT, CATCH_CLS_KEY,
CATCH_CLS_MEDIA, CATCH_CLS_VEND_BULK, CATCH_CLS_VEND_INTR, CATCH_ID_ANY,
};
use crate::types::{Axis, Class, ClockEstimate, Direction, Usage};
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum CatchClass {
Button = CATCH_CLS_BTN,
Key = CATCH_CLS_KEY,
Media = CATCH_CLS_MEDIA,
Axis = CATCH_CLS_AXIS,
HidIn = CATCH_CLS_HID_IN,
HidOut = CATCH_CLS_HID_OUT,
VendorInterrupt = CATCH_CLS_VEND_INTR,
VendorBulk = CATCH_CLS_VEND_BULK,
Control = CATCH_CLS_CONTROL,
Emit = CATCH_CLS_EMIT,
Bus = CATCH_CLS_BUS,
ClipTransfer = CATCH_CLS_CLIP_XFER,
}
impl CatchClass {
pub fn as_u8(self) -> u8 {
self as u8
}
pub fn from_u8(v: u8) -> Option<CatchClass> {
Some(match v {
CATCH_CLS_BTN => CatchClass::Button,
CATCH_CLS_KEY => CatchClass::Key,
CATCH_CLS_MEDIA => CatchClass::Media,
CATCH_CLS_AXIS => CatchClass::Axis,
CATCH_CLS_HID_IN => CatchClass::HidIn,
CATCH_CLS_HID_OUT => CatchClass::HidOut,
CATCH_CLS_VEND_INTR => CatchClass::VendorInterrupt,
CATCH_CLS_VEND_BULK => CatchClass::VendorBulk,
CATCH_CLS_CONTROL => CatchClass::Control,
CATCH_CLS_EMIT => CatchClass::Emit,
CATCH_CLS_BUS => CatchClass::Bus,
CATCH_CLS_CLIP_XFER => CatchClass::ClipTransfer,
_ => return None,
})
}
pub fn is_input(self) -> bool {
matches!(
self,
CatchClass::Button | CatchClass::Key | CatchClass::Media | CatchClass::Axis
)
}
pub fn is_traffic(self) -> bool {
!self.is_input()
}
pub fn direction_meaning(self) -> DirectionMeaning {
match self {
CatchClass::Button | CatchClass::Key | CatchClass::Media => DirectionMeaning::Edge,
CatchClass::Axis => DirectionMeaning::Sign,
_ => DirectionMeaning::Flow,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DirectionMeaning {
Edge,
Sign,
Flow,
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum TrafficClass {
HidIn = CATCH_CLS_HID_IN,
HidOut = CATCH_CLS_HID_OUT,
VendorInterrupt = CATCH_CLS_VEND_INTR,
VendorBulk = CATCH_CLS_VEND_BULK,
Control = CATCH_CLS_CONTROL,
Emit = CATCH_CLS_EMIT,
Bus = CATCH_CLS_BUS,
ClipTransfer = CATCH_CLS_CLIP_XFER,
}
impl TrafficClass {
pub const ALL: [TrafficClass; 8] = [
TrafficClass::HidIn,
TrafficClass::HidOut,
TrafficClass::VendorInterrupt,
TrafficClass::VendorBulk,
TrafficClass::Control,
TrafficClass::Emit,
TrafficClass::Bus,
TrafficClass::ClipTransfer,
];
pub fn as_u8(self) -> u8 {
self as u8
}
}
impl From<TrafficClass> for CatchClass {
fn from(t: TrafficClass) -> CatchClass {
match t {
TrafficClass::HidIn => CatchClass::HidIn,
TrafficClass::HidOut => CatchClass::HidOut,
TrafficClass::VendorInterrupt => CatchClass::VendorInterrupt,
TrafficClass::VendorBulk => CatchClass::VendorBulk,
TrafficClass::Control => CatchClass::Control,
TrafficClass::Emit => CatchClass::Emit,
TrafficClass::Bus => CatchClass::Bus,
TrafficClass::ClipTransfer => CatchClass::ClipTransfer,
}
}
}
impl TryFrom<CatchClass> for TrafficClass {
type Error = CatchClass;
fn try_from(c: CatchClass) -> Result<TrafficClass, CatchClass> {
Ok(match c {
CatchClass::HidIn => TrafficClass::HidIn,
CatchClass::HidOut => TrafficClass::HidOut,
CatchClass::VendorInterrupt => TrafficClass::VendorInterrupt,
CatchClass::VendorBulk => TrafficClass::VendorBulk,
CatchClass::Control => TrafficClass::Control,
CatchClass::Emit => TrafficClass::Emit,
CatchClass::Bus => TrafficClass::Bus,
CatchClass::ClipTransfer => TrafficClass::ClipTransfer,
input => return Err(input),
})
}
}
impl From<Class> for CatchClass {
fn from(c: Class) -> CatchClass {
match c {
Class::Button => CatchClass::Button,
Class::Key => CatchClass::Key,
Class::Media => CatchClass::Media,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub enum Capture {
#[default]
Whole,
First(u8),
}
impl Capture {
pub fn bytes(self) -> Option<u8> {
match self {
Capture::Whole | Capture::First(0) => None,
Capture::First(n) => Some(n),
}
}
pub fn widest(self, other: Capture) -> Capture {
match (self.bytes(), other.bytes()) {
(Some(a), Some(b)) => Capture::First(a.max(b)),
_ => Capture::Whole,
}
}
pub fn as_u8(self) -> u8 {
self.bytes().unwrap_or(0)
}
pub fn from_u8(v: u8) -> Capture {
match v {
0 => Capture::Whole,
n => Capture::First(n),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) struct FilterKey {
class: Option<CatchClass>,
id: Option<u16>,
direction: Direction,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct CatchFilter {
class: Option<CatchClass>,
id: Option<u16>,
direction: Direction,
capture: Capture,
}
impl CatchFilter {
pub fn watch(usage: impl Into<Usage>) -> CatchFilter {
let u = usage.into();
CatchFilter::exact(CatchClass::from(u.class), u.id)
}
pub fn watch_axis(axis: Axis) -> CatchFilter {
CatchFilter::exact(CatchClass::Axis, axis.as_u16())
}
pub fn watch_class(class: Class) -> CatchFilter {
CatchFilter::blanket(CatchClass::from(class))
}
pub fn watch_axes() -> CatchFilter {
CatchFilter::blanket(CatchClass::Axis)
}
pub fn all_input() -> [CatchFilter; 4] {
[
CatchFilter::watch_class(Class::Button),
CatchFilter::watch_class(Class::Key),
CatchFilter::watch_class(Class::Media),
CatchFilter::watch_axes(),
]
}
pub fn traffic(class: TrafficClass, id: u16) -> CatchFilter {
CatchFilter::exact(class.into(), id)
}
pub fn traffic_class(class: TrafficClass) -> CatchFilter {
CatchFilter::blanket(class.into())
}
pub fn everything() -> CatchFilter {
CatchFilter {
class: None,
id: None,
direction: Direction::Both,
capture: Capture::Whole,
}
}
fn exact(class: CatchClass, id: u16) -> CatchFilter {
CatchFilter {
class: Some(class),
id: Some(id),
direction: Direction::Both,
capture: Capture::Whole,
}
}
fn blanket(class: CatchClass) -> CatchFilter {
CatchFilter {
class: Some(class),
id: None,
direction: Direction::Both,
capture: Capture::Whole,
}
}
pub fn with_direction(mut self, direction: Direction) -> CatchFilter {
self.direction = direction;
self
}
pub fn on_press(self) -> CatchFilter {
self.with_direction(Direction::PRESS)
}
pub fn on_release(self) -> CatchFilter {
self.with_direction(Direction::RELEASE)
}
pub fn inbound(self) -> CatchFilter {
self.with_direction(Direction::IN)
}
pub fn outbound(self) -> CatchFilter {
self.with_direction(Direction::OUT)
}
pub fn with_capture(mut self, capture: Capture) -> CatchFilter {
self.capture = capture.bytes().map_or(Capture::Whole, Capture::First);
self
}
pub fn class(self) -> Option<CatchClass> {
self.class
}
pub fn id(self) -> Option<u16> {
self.id
}
pub fn direction(self) -> Direction {
self.direction
}
pub fn capture(self) -> Capture {
self.capture
}
pub fn same_address(self, other: CatchFilter) -> bool {
self.key() == other.key()
}
pub(crate) fn capture_is_meaningful(self) -> bool {
self.capture == Capture::Whole || !self.class.is_some_and(CatchClass::is_input)
}
pub(crate) fn key(self) -> FilterKey {
FilterKey {
class: self.class,
id: self.id,
direction: self.direction,
}
}
pub(crate) fn matches_class_only(self, class: CatchClass) -> bool {
self.class.is_none_or(|c| c == class)
}
pub(crate) fn matches(self, class: CatchClass, id: u16, direction: Direction) -> bool {
if let Some(c) = self.class {
if c != class {
return false;
}
if self.id.is_some_and(|i| i != id) {
return false;
}
}
self.direction.admits(direction)
}
pub fn wire(self) -> (u8, u16) {
(
self.class.map_or(CATCH_CLS_ANY, CatchClass::as_u8),
self.id.unwrap_or(CATCH_ID_ANY),
)
}
pub fn from_wire(class: u8, id: u16, direction: u8, capture: u8) -> Option<CatchFilter> {
let class = if class == CATCH_CLS_ANY {
None
} else {
Some(CatchClass::from_u8(class)?)
};
let id = if id == CATCH_ID_ANY { None } else { Some(id) };
if class.is_none() && id.is_some() {
return None;
}
Some(CatchFilter {
class,
id,
direction: Direction::from_u8(direction)?,
capture: Capture::from_u8(capture),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CatchEntry {
pub filter: CatchFilter,
pub dropped: u16,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CatchState {
pub table_full: bool,
pub dropped: u32,
pub clock: ClockEstimate,
pub entries: Vec<CatchEntry>,
}
impl CatchState {
pub(crate) const HDR: usize = 19;
pub(crate) const ENTRY: usize = 7;
pub(crate) fn from_payload(p: &[u8]) -> Option<CatchState> {
if p.len() < Self::HDR {
return None;
}
let n = p[18] as usize;
let mut entries = Vec::with_capacity(n);
for i in 0..n {
let o = Self::HDR + Self::ENTRY * i;
if o + Self::ENTRY > p.len() {
break;
}
let Some(filter) = CatchFilter::from_wire(
p[o],
u16::from_le_bytes([p[o + 1], p[o + 2]]),
p[o + 3],
p[o + 4],
) else {
continue;
};
entries.push(CatchEntry {
filter,
dropped: u16::from_le_bytes([p[o + 5], p[o + 6]]),
});
}
Some(CatchState {
table_full: p[1] & 0x01 != 0,
dropped: u32::from_le_bytes([p[2], p[3], p[4], p[5]]),
clock: ClockEstimate::from_payload(&p[6..18])?,
entries,
})
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}