libinput-rs 0.3.10

100% drop-in Rust replacement for the libinput 1.31.3 C ABI and tools
Documentation
use input::chwd_input::{detect_device, profile_inventory, scan_event_node, scan_system_facts};
use std::path::{Path, PathBuf};

fn main() {
    if let Err(error) = run() {
        eprintln!("libinput-rs-chwd: {error}");
        std::process::exit(1);
    }
}

fn run() -> Result<(), String> {
    let arguments = std::env::args_os().skip(1).collect::<Vec<_>>();
    match arguments.first().and_then(|argument| argument.to_str()) {
        Some("--list-profiles") if arguments.len() == 1 => {
            for profile in profile_inventory() {
                println!("{}\tpriority={}", profile.id, profile.priority);
            }
            Ok(())
        }
        Some("--identify") if arguments.len() == 2 => identify(Path::new(&arguments[1])),
        Some("--auto") if arguments.len() == 1 => auto(),
        Some("--help" | "-h") | None => {
            println!(
                "Usage: libinput-rs-chwd --auto | --list-profiles | --identify /dev/input/eventN"
            );
            Ok(())
        }
        _ => Err("invalid arguments (use --help)".to_string()),
    }
}

fn identify(path: &Path) -> Result<(), String> {
    let system = scan_system_facts();
    let device = scan_event_node(path).map_err(|error| format!("{}: {error}", path.display()))?;
    print_plan(&system, &device);
    Ok(())
}

fn auto() -> Result<(), String> {
    let system = scan_system_facts();
    let mut nodes = std::fs::read_dir("/dev/input")
        .map_err(|error| format!("cannot scan /dev/input: {error}"))?
        .filter_map(Result::ok)
        .map(|entry| entry.path())
        .filter(|path| {
            path.file_name()
                .and_then(|name| name.to_str())
                .is_some_and(|name| {
                    name.strip_prefix("event").is_some_and(|suffix| {
                        !suffix.is_empty() && suffix.bytes().all(|b| b.is_ascii_digit())
                    })
                })
        })
        .collect::<Vec<PathBuf>>();
    nodes.sort();
    for path in nodes {
        match scan_event_node(&path) {
            Ok(device) => print_plan(&system, &device),
            Err(error) => eprintln!("{}: {error}", path.display()),
        }
    }
    Ok(())
}

fn print_plan(system: &input::chwd_input::SystemFacts, device: &input::chwd_input::ScannedDevice) {
    let result = detect_device(system, &device.facts(), true);
    println!(
        "{}\t{}\t{:?}\tprofile={}\tpriority={}\tconfidence={:.3}\tsource={:?}",
        device.path.display(),
        device.name,
        result.class,
        result.profile_id,
        result.priority,
        result.confidence,
        result.source
    );
}