pub const USAGE_PAGE_GENERIC_DESKTOP: u16 = 0x01;
pub const USAGE_PAGE_BUTTON: u16 = 0x09;
pub const USAGE_MOUSE: u16 = 0x02;
pub const USAGE_JOYSTICK: u16 = 0x04;
pub const USAGE_GAMEPAD: u16 = 0x05;
pub const USAGE_KEYBOARD: u16 = 0x06;
pub const USAGE_MULTI_AXIS: u16 = 0x08;
pub const USAGE_X: u16 = 0x30;
pub const USAGE_Y: u16 = 0x31;
pub const USAGE_Z: u16 = 0x32;
pub const USAGE_RX: u16 = 0x33;
pub const USAGE_RY: u16 = 0x34;
pub const USAGE_RZ: u16 = 0x35;
pub const USAGE_HAT_SWITCH: u16 = 0x39;
pub const MAX_FIELDS: usize = 64;
const MAX_REPORTS: usize = 8;
const MAX_USAGES: usize = 16;
const ITEM_TYPE_MAIN: u8 = 0;
const ITEM_TYPE_GLOBAL: u8 = 1;
const ITEM_TYPE_LOCAL: u8 = 2;
const MAIN_INPUT: u8 = 0x8;
const MAIN_COLLECTION: u8 = 0xa;
const COLLECTION_APPLICATION: u32 = 0x01;
const GLOBAL_USAGE_PAGE: u8 = 0x0;
const GLOBAL_LOGICAL_MIN: u8 = 0x1;
const GLOBAL_LOGICAL_MAX: u8 = 0x2;
const GLOBAL_REPORT_SIZE: u8 = 0x7;
const GLOBAL_REPORT_ID: u8 = 0x8;
const GLOBAL_REPORT_COUNT: u8 = 0x9;
const LOCAL_USAGE: u8 = 0x0;
const LOCAL_USAGE_MIN: u8 = 0x1;
const LOCAL_USAGE_MAX: u8 = 0x2;
const INPUT_CONSTANT: u32 = 0x01;
const INPUT_VARIABLE: u32 = 0x02;
const INPUT_RELATIVE: u32 = 0x04;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Field {
pub report_id: u8,
pub bit_offset: u16,
pub bit_size: u8,
pub usage_page: u16,
pub usage: u16,
pub logical_min: i32,
pub logical_max: i32,
pub relative: bool,
}
impl Field {
pub fn extract(&self, payload: &[u8]) -> u32 {
let mut value: u32 = 0;
for i in 0..self.bit_size as usize {
let bit = self.bit_offset as usize + i;
let byte = bit / 8;
if byte >= payload.len() {
return 0;
}
if payload[byte] & (1 << (bit % 8)) != 0 {
value |= 1 << i;
}
}
value
}
pub fn extract_signed(&self, payload: &[u8]) -> i32 {
let raw = self.extract(payload);
if self.logical_min < 0 && self.bit_size < 32 && raw & (1 << (self.bit_size - 1)) != 0 {
(raw | (!0u32 << self.bit_size)) as i32
} else {
raw as i32
}
}
}
pub struct ReportDescriptor {
fields: [Field; MAX_FIELDS],
count: usize,
uses_report_ids: bool,
overflow: bool,
application: Option<(u16, u16)>,
}
impl ReportDescriptor {
pub fn parse(desc: &[u8]) -> Self {
let mut out = ReportDescriptor {
fields: [Field {
report_id: 0,
bit_offset: 0,
bit_size: 0,
usage_page: 0,
usage: 0,
logical_min: 0,
logical_max: 0,
relative: false,
}; MAX_FIELDS],
count: 0,
uses_report_ids: false,
overflow: false,
application: None,
};
let mut usage_page: u16 = 0;
let mut logical_min: i32 = 0;
let mut logical_max: i32 = 0;
let mut report_size: u8 = 0;
let mut report_count: u16 = 0;
let mut report_id: u8 = 0;
let mut usages = [0u16; MAX_USAGES];
let mut usage_len = 0usize;
let mut usage_min: u16 = 0;
let mut usage_max: u16 = 0;
let mut have_usage_range = false;
let mut report_bits = [(0u8, 0u16); MAX_REPORTS];
let mut report_bits_len = 0usize;
let mut i = 0;
while i < desc.len() {
let prefix = desc[i];
let size = match prefix & 0x03 {
0 => 0,
1 => 1,
2 => 2,
_ => 4,
};
let item_type = (prefix >> 2) & 0x03;
let tag = prefix >> 4;
let data_start = i + 1;
if data_start + size > desc.len() {
break;
}
let data = &desc[data_start..data_start + size];
let uval = le_u32(data);
let sval = sign_extend(data);
match item_type {
ITEM_TYPE_GLOBAL => match tag {
GLOBAL_USAGE_PAGE => usage_page = uval as u16,
GLOBAL_LOGICAL_MIN => logical_min = sval,
GLOBAL_LOGICAL_MAX => logical_max = sval,
GLOBAL_REPORT_SIZE => report_size = uval as u8,
GLOBAL_REPORT_ID => {
report_id = uval as u8;
out.uses_report_ids = true;
}
GLOBAL_REPORT_COUNT => report_count = uval as u16,
_ => {}
},
ITEM_TYPE_LOCAL => match tag {
LOCAL_USAGE => {
if usage_len < usages.len() {
usages[usage_len] = uval as u16;
usage_len += 1;
}
}
LOCAL_USAGE_MIN => {
usage_min = uval as u16;
have_usage_range = true;
}
LOCAL_USAGE_MAX => {
usage_max = uval as u16;
have_usage_range = true;
}
_ => {}
},
ITEM_TYPE_MAIN => {
if tag == MAIN_COLLECTION
&& uval == COLLECTION_APPLICATION
&& out.application.is_none()
{
out.application = Some((
usage_page,
usages[..usage_len].first().copied().unwrap_or(0),
));
}
if tag == MAIN_INPUT {
emit_input(
&mut out,
uval,
report_id,
report_size,
report_count,
usage_page,
logical_min,
logical_max,
&usages[..usage_len],
have_usage_range,
usage_min,
usage_max,
&mut report_bits,
&mut report_bits_len,
);
}
usage_len = 0;
usage_min = 0;
usage_max = 0;
have_usage_range = false;
}
_ => {}
}
i = data_start + size;
}
out
}
pub fn fields(&self) -> &[Field] {
&self.fields[..self.count]
}
pub fn uses_report_ids(&self) -> bool {
self.uses_report_ids
}
pub fn overflowed(&self) -> bool {
self.overflow
}
pub fn application_usage(&self) -> Option<(u16, u16)> {
self.application
}
pub fn is_gamepad(&self) -> bool {
matches!(
self.application,
Some((USAGE_PAGE_GENERIC_DESKTOP, USAGE_GAMEPAD))
| Some((USAGE_PAGE_GENERIC_DESKTOP, USAGE_JOYSTICK))
| Some((USAGE_PAGE_GENERIC_DESKTOP, USAGE_MULTI_AXIS))
)
}
pub fn find(&self, report_id: u8, usage_page: u16, usage: u16) -> Option<&Field> {
self.fields()
.iter()
.find(|f| f.report_id == report_id && f.usage_page == usage_page && f.usage == usage)
}
}
#[allow(clippy::too_many_arguments)]
fn emit_input(
out: &mut ReportDescriptor,
flags: u32,
report_id: u8,
report_size: u8,
report_count: u16,
usage_page: u16,
logical_min: i32,
logical_max: i32,
usages: &[u16],
have_usage_range: bool,
usage_min: u16,
usage_max: u16,
report_bits: &mut [(u8, u16)],
report_bits_len: &mut usize,
) {
let base = report_bit_offset(report_bits, report_bits_len, report_id);
let total_bits = report_count * report_size as u16;
if flags & INPUT_CONSTANT == 0 && flags & INPUT_VARIABLE != 0 {
let relative = flags & INPUT_RELATIVE != 0;
for k in 0..report_count as usize {
let usage = if have_usage_range {
(usage_min as usize + k).min(usage_max as usize) as u16
} else if !usages.is_empty() {
usages[k.min(usages.len() - 1)]
} else {
0
};
let bit_offset = base + (k as u16) * report_size as u16;
if out.count < out.fields.len() {
out.fields[out.count] = Field {
report_id,
bit_offset,
bit_size: report_size,
usage_page,
usage,
logical_min,
logical_max,
relative,
};
out.count += 1;
} else {
out.overflow = true;
}
}
}
advance_report_bits(report_bits, report_bits_len, report_id, total_bits);
}
fn report_bit_offset(bits: &[(u8, u16)], len: &usize, report_id: u8) -> u16 {
bits[..*len]
.iter()
.find(|(id, _)| *id == report_id)
.map(|(_, b)| *b)
.unwrap_or(0)
}
fn advance_report_bits(bits: &mut [(u8, u16)], len: &mut usize, report_id: u8, delta: u16) {
if let Some(slot) = bits[..*len].iter_mut().find(|(id, _)| *id == report_id) {
slot.1 = slot.1.saturating_add(delta);
} else if *len < bits.len() {
bits[*len] = (report_id, delta);
*len += 1;
}
}
fn le_u32(data: &[u8]) -> u32 {
let mut v = 0u32;
for (k, b) in data.iter().enumerate().take(4) {
v |= (*b as u32) << (8 * k);
}
v
}
fn sign_extend(data: &[u8]) -> i32 {
match data.len() {
1 => data[0] as i8 as i32,
2 => i16::from_le_bytes([data[0], data[1]]) as i32,
4 => i32::from_le_bytes([data[0], data[1], data[2], data[3]]),
_ => 0,
}
}