use std::{
error::Error,
fs::{self, File},
io::BufReader,
iter,
path::PathBuf,
};
use clap::{Parser, ValueEnum};
use hidparser::{parse_report_descriptor, ReportDescriptor, ReportField};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
#[derive(Parser, Debug, Clone, PartialEq, Eq, ValueEnum)]
enum ReportType {
Input,
Output,
Feature,
}
#[derive(Parser, Debug)]
struct Arguments {
#[arg(short, long)]
path: std::path::PathBuf,
#[arg(short, long)]
report_type: Option<ReportType>,
#[arg(short = 'i', long)]
report_id: Option<u32>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
struct UsageId {
id: u16,
name: String,
kinds: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
struct UsagePage {
kind: String,
id: u16,
name: String,
usage_ids: Vec<UsageId>,
#[serde(skip)]
_usage_id_generator: Map<String, Value>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
struct HidUsageTable {
usage_table_version: u8,
usage_table_revision: u8,
usage_table_sub_revision_internal: u8,
last_generated: String,
usage_pages: Vec<UsagePage>,
}
fn usage_tables() -> Result<HidUsageTable, Box<dyn Error>> {
let file = File::open(
[env!("CARGO_MANIFEST_DIR"), "examples", "resources", "HidUsageTables.json"].iter().collect::<PathBuf>(),
)?;
let us = serde_json::from_reader(BufReader::new(file))?;
Ok(us)
}
fn main() -> Result<(), Box<dyn Error>> {
let args = Arguments::parse();
let raw_descriptor = fs::read(args.path)?;
let ReportDescriptor {
input_reports,
bad_input_reports,
output_reports,
bad_output_reports,
features,
bad_features,
} = parse_report_descriptor(&raw_descriptor).map_err(|_| "Failed to parse descriptor.")?;
if !bad_input_reports.is_empty() {
println!("Bad input reports:");
for bad_report in bad_input_reports {
println!("{:x?}", bad_report);
}
}
if !bad_output_reports.is_empty() {
println!("Bad output reports:");
for bad_report in bad_output_reports {
println!("{:x?}", bad_report);
}
}
if !bad_features.is_empty() {
println!("Bad feature reports:");
for bad_report in bad_features {
println!("{:x?}", bad_report);
}
}
let reports = (iter::repeat(ReportType::Input).zip(input_reports))
.chain(iter::repeat(ReportType::Output).zip(output_reports))
.chain(iter::repeat(ReportType::Feature).zip(features));
let filtered_reports = reports.filter(|(report_type, report)| match (&args.report_type, args.report_id) {
(Some(requested_type), _) if requested_type != report_type => false,
(_, id @ Some(_)) if id != report.report_id.map(|report_id| report_id.into()) => false,
_ => true,
});
let ut = usage_tables()?;
for (report_type, report) in filtered_reports {
println!("{report_type:?} id: {:?}", report.report_id);
for field in &report.fields {
match field {
ReportField::Variable(v) => {
match v.bits.len() {
1 => print!("\tbit: {}", v.bits.start),
_ => print!("\tbits: {}..={}", v.bits.start, v.bits.end - 1),
};
print!("\tusage: ({:?}:{:?})\t- ", v.usage.page(), v.usage.id());
let usage_page = ut.usage_pages.iter().find(|x| x.id == v.usage.page());
if let Some(page) = usage_page {
print!(" page: {:?} ", page.name);
let usage = page.usage_ids.iter().find(|x| x.id == v.usage.id());
if let Some(usage) = usage {
print!("id: {:?}", usage.name);
} else {
print!("id: <unrecognized>");
}
} else {
print!("page: <unrecognized>")
}
println!()
}
ReportField::Array(a) => {
let bits = match a.bits.len() {
1 => format!("bit: {}", a.bits.start),
_ => format!("bits: {}..={}", a.bits.start, a.bits.end - 1),
};
println!("\t{bits}\tusages: {:?} \t-", a.usage_list)
}
ReportField::Padding(p) => {
let bits = match p.bits.len() {
1 => format!("bit: {}", p.bits.start),
_ => format!("bits: {}..={}", p.bits.start, p.bits.end - 1),
};
println!("\t{bits}\tpadding");
}
}
}
}
Ok(())
}