libinput-rs 0.3.3

100% drop-in Rust replacement for the libinput 1.31.3 C ABI and tools
Documentation
use std::env;
use std::fs;
use std::path::PathBuf;
use std::process::Command;

fn run(command: &mut Command) {
    let status = command.status().expect("failed to start native build tool");
    assert!(status.success(), "native build tool failed");
}

fn main() {
    println!("cargo:rerun-if-changed=libinput.map");
    println!("cargo:rerun-if-changed=src/log_bridge.c");
    println!("cargo:rerun-if-changed=src/capforge.f90");
    println!("cargo:rerun-if-env-changed=FC");

    let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR is not set"));
    let object = out_dir.join("log_bridge.o");
    let archive = out_dir.join("libinput_log_bridge.a");

    let mut compiler = Command::new(env::var_os("CC").unwrap_or_else(|| "cc".into()));
    compiler.args([
        "-c",
        "-fPIC",
        "-fvisibility=hidden",
        "-Wall",
        "-Wextra",
        "-Werror",
        "src/log_bridge.c",
        "-o",
    ]);
    compiler.arg(&object);
    run(&mut compiler);

    let mut archiver = Command::new(env::var_os("AR").unwrap_or_else(|| "ar".into()));
    archiver.arg("crs").arg(&archive).arg(&object);
    run(&mut archiver);

    println!("cargo:rustc-link-search=native={}", out_dir.display());
    println!("cargo:rustc-link-lib=static=input_log_bridge");

    let fortran = env::var_os("FC").unwrap_or_else(|| "gfortran".into());
    let available = Command::new(&fortran)
        .arg("--version")
        .output()
        .is_ok_and(|output| output.status.success());
    let bindings = if available {
        let object = out_dir.join("capforge.o");
        let archive = out_dir.join("libcapforge.a");
        let mut compiler = Command::new(&fortran);
        compiler
            .args(["-c", "-fPIC", "-std=f2018", "-Wall", "-Wextra", "-Werror"])
            .arg("-J")
            .arg(&out_dir)
            .arg("src/capforge.f90")
            .arg("-o")
            .arg(&object);
        run(&mut compiler);
        let mut archiver = Command::new(env::var_os("AR").unwrap_or_else(|| "ar".into()));
        archiver.arg("crs").arg(&archive).arg(&object);
        run(&mut archiver);
        println!("cargo:rustc-link-lib=static=capforge");
        println!("cargo:rustc-link-lib=gfortran");
        r#"
extern "C" {
    fn cf_classify(
        ev: *const i64, nev: i32, key: *const i64, nkey: i32,
        rel: *const i64, nrel: i32, absolute: *const i64, nabs: i32,
        prop: *const i64, nprop: i32,
    ) -> i32;
    fn cf_parse_hex_words(input: *const u8, length: i32, output: *mut i64, words: i32);
    fn cf_knn_scores(
        features: *const f64, nfeatures: i32, centroids: *const f64,
        nprofiles: i32, scores: *mut f64,
    );
    fn cf_tiny_mlp_scores(
        features: *const f64, nfeatures: i32, input_weights: *const f64,
        hidden_bias: *const f64, nhidden: i32, output_weights: *const f64,
        output_bias: *const f64, nprofiles: i32, scores: *mut f64,
    );
}

pub(super) fn native_classify(bits: &CapabilityBits) -> Option<i32> {
    Some(unsafe {
        cf_classify(
            bits.ev.as_ptr(), bits.ev.len() as i32,
            bits.key.as_ptr(), bits.key.len() as i32,
            bits.rel.as_ptr(), bits.rel.len() as i32,
            bits.absolute.as_ptr(), bits.absolute.len() as i32,
            bits.properties.as_ptr(), bits.properties.len() as i32,
        )
    })
}

pub(super) fn native_parse(input: &str, output: &mut [i64]) -> bool {
    unsafe {
        cf_parse_hex_words(
            input.as_ptr(), input.len() as i32, output.as_mut_ptr(), output.len() as i32,
        );
    }
    true
}

pub(super) fn native_knn_scores(
    features: &[f64], centroids: &[f64], scores: &mut [f64],
) -> bool {
    if features.is_empty() || scores.is_empty() || centroids.len() != features.len() * scores.len() {
        return false;
    }
    unsafe {
        cf_knn_scores(
            features.as_ptr(), features.len() as i32, centroids.as_ptr(),
            scores.len() as i32, scores.as_mut_ptr(),
        );
    }
    true
}

pub(super) fn native_mlp_scores(
    features: &[f64], input_weights: &[f64], hidden_bias: &[f64],
    output_weights: &[f64], output_bias: &[f64], scores: &mut [f64],
) -> bool {
    if features.is_empty()
        || hidden_bias.is_empty()
        || scores.is_empty()
        || input_weights.len() != features.len() * hidden_bias.len()
        || output_weights.len() != hidden_bias.len() * scores.len()
        || output_bias.len() != scores.len()
    {
        return false;
    }
    unsafe {
        cf_tiny_mlp_scores(
            features.as_ptr(), features.len() as i32, input_weights.as_ptr(),
            hidden_bias.as_ptr(), hidden_bias.len() as i32, output_weights.as_ptr(),
            output_bias.as_ptr(), scores.len() as i32, scores.as_mut_ptr(),
        );
    }
    true
}

#[cfg(test)]
pub const NATIVE_CAPFORGE: bool = true;
"#
    } else {
        r#"
pub(super) fn native_classify(_bits: &CapabilityBits) -> Option<i32> {
    None
}

pub(super) fn native_parse(_input: &str, _output: &mut [i64]) -> bool {
    false
}

pub(super) fn native_knn_scores(
    _features: &[f64], _centroids: &[f64], _scores: &mut [f64],
) -> bool {
    false
}

pub(super) fn native_mlp_scores(
    _features: &[f64], _input_weights: &[f64], _hidden_bias: &[f64],
    _output_weights: &[f64], _output_bias: &[f64], _scores: &mut [f64],
) -> bool {
    false
}

#[cfg(test)]
pub const NATIVE_CAPFORGE: bool = false;
"#
    };
    fs::write(out_dir.join("capforge_bindings.rs"), bindings)
        .expect("failed to write capforge bindings");
}