summary refs log tree commit diff stats
path: root/rust/qmk-hid-com/src
diff options
context:
space:
mode:
Diffstat (limited to 'rust/qmk-hid-com/src')
-rw-r--r--rust/qmk-hid-com/src/cli.rs40
-rw-r--r--rust/qmk-hid-com/src/hid/mod.rs44
-rw-r--r--rust/qmk-hid-com/src/main.rs107
-rw-r--r--rust/qmk-hid-com/src/search.rs51
4 files changed, 242 insertions, 0 deletions
diff --git a/rust/qmk-hid-com/src/cli.rs b/rust/qmk-hid-com/src/cli.rs
new file mode 100644
index 0000000..afef1a9
--- /dev/null
+++ b/rust/qmk-hid-com/src/cli.rs
@@ -0,0 +1,40 @@
+use clap::{command, Parser, Subcommand};
+
+#[derive(Parser, Debug)]
+pub struct CliArgs {
+    #[clap(subcommand)]
+    pub command: Command,
+}
+#[derive(Clone, Debug, Subcommand)]
+pub enum Command {
+    /// List all devices, where the manufacturer string contains the search key
+    Search {
+        /// The part that must be contained in the manufacturer name
+        vendor_name: String,
+    },
+
+    /// Talk to the device specified by the [`vendor_id`]  and [`product_id`].
+    Send {
+        usage_page: u16,
+        usage_id: u16,
+        message: u8,
+    },
+
+    /// Talk to the device specified by the [`vendor_id`]  and [`product_id`].
+    Monitor { usage_page: u16, usage_id: u16 },
+
+    Inform {
+        #[command(subcommand)]
+        command: Inform,
+    },
+
+    ArrInform { values: Vec<u8> },
+}
+
+#[derive(Subcommand, Clone, Debug)]
+pub enum Inform {
+    Hex { val: String },
+    Dec { val: i64 },
+    Bin { val: String },
+    Char { val: char },
+}
diff --git a/rust/qmk-hid-com/src/hid/mod.rs b/rust/qmk-hid-com/src/hid/mod.rs
new file mode 100644
index 0000000..b80c835
--- /dev/null
+++ b/rust/qmk-hid-com/src/hid/mod.rs
@@ -0,0 +1,44 @@
+use hidapi::{HidApi, HidDevice};
+
+fn find_device(api: &HidApi, usage_id: u16, usage_page: u16) -> anyhow::Result<HidDevice> {
+    Ok({
+        let devs = api
+            .device_list()
+            .filter(|dev| dev.usage() == usage_id && dev.usage_page() == usage_page)
+            .collect::<Vec<_>>();
+
+        assert_eq!(
+            devs.len(),
+            1,
+            "Only one device should have such a usage:usage_page value"
+        );
+
+        api.open(devs[0].vendor_id(), devs[0].product_id())?
+    })
+}
+
+pub fn monitor(api: &HidApi, usage_id: u16, usage_page: u16) -> anyhow::Result<()> {
+    let device = find_device(api, usage_id, usage_page)?;
+
+    loop {
+        // Read data from device
+        let mut buf = [0u8; 4];
+        let res = device.read(&mut buf[..]).unwrap();
+
+        let value = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
+
+        println!("Read: {:?} ({} bytes) -> {value:x}", &buf[..res], res);
+    }
+}
+pub fn send(api: &HidApi, usage_id: u16, usage_page: u16, message: u8) -> anyhow::Result<()> {
+    let device = find_device(api, usage_id, usage_page)?;
+
+    // Write data to device
+    let mut buf = [0u8; 33];
+    buf[1] = message;
+
+    let res = device.write(&buf)?;
+    println!("Wrote: {:#?} byte(s)", res);
+
+    Ok(())
+}
diff --git a/rust/qmk-hid-com/src/main.rs b/rust/qmk-hid-com/src/main.rs
new file mode 100644
index 0000000..ebaa91b
--- /dev/null
+++ b/rust/qmk-hid-com/src/main.rs
@@ -0,0 +1,107 @@
+// Qmk Hid Com - A small middelware between a qmk keyboard and a wayland virtual
+// keyboard. For unicode input
+//
+// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This file is part of Qmk Hid Com.
+//
+// You should have received a copy of the License along with this program.
+// If not, see <https://www.gnu.org/licenses/agpl.txt>.
+
+use clap::Parser;
+use cli::{CliArgs, Command, Inform};
+
+mod cli;
+mod hid;
+mod search;
+
+fn main() -> anyhow::Result<()> {
+    let args = CliArgs::parse();
+    let api = hidapi::HidApi::new().unwrap();
+
+    match args.command {
+        Command::Search { vendor_name } => search::search(&api, vendor_name),
+        Command::Monitor {
+            usage_id,
+            usage_page,
+        } => hid::monitor(&api, usage_id, usage_page),
+        Command::Send {
+            usage_id,
+            usage_page,
+            message,
+        } => hid::send(&api, usage_id, usage_page, message),
+
+        Command::Inform { command } => {
+            let (decimal, _) = match command {
+                Inform::Hex { val } => (i64::from_str_radix(&val.to_lowercase(), 16)?, val),
+                Inform::Dec { val } => (val, val.to_string()),
+                Inform::Bin { val } => (i64::from_str_radix(&val.to_lowercase(), 2)?, val),
+                Inform::Char { val } => ((val as u32) as i64, val.to_string()),
+            };
+
+            let character = char::from_u32(decimal as u32).unwrap();
+
+            let binary = {
+                decimal
+                    .to_ne_bytes()
+                    .map(|sm| format!("{:<08b}", sm))
+                    .join(" ")
+            };
+
+            let split = {
+                let first;
+                let second;
+                let third;
+                let fourth;
+
+                if cfg!(target_endian = "big") {
+                    let decimal: u32 = decimal as u32;
+                    let decimal = decimal.to_be();
+
+                    dbg!(decimal
+                        .to_be_bytes()
+                        .map(|sm| format!("{:<08b}", sm))
+                        .join(" "));
+
+                    fourth = decimal as u8;
+                    third = (decimal >> 8) as u8;
+                    second = (decimal >> 16) as u8;
+                    first = (decimal >> 24) as u8;
+                } else {
+                    first = decimal as u8;
+                    second = (decimal >> 8) as u8;
+                    third = (decimal >> 16) as u8;
+                    fourth = (decimal >> 24) as u8;
+                }
+                [first, second, third, fourth]
+            };
+
+            println!(
+                "dec: {}, hex: {:x}, char: {:#?}, bin: {}, split: {:?}/[{}] ({})",
+                decimal,
+                decimal,
+                character,
+                binary,
+                split,
+                split
+                    .iter()
+                    .map(|val| format!("{:x}", val))
+                    .collect::<Vec<_>>()
+                    .join(", "),
+                i32::from_ne_bytes(split)
+            );
+            Ok(())
+        }
+        Command::ArrInform { values } => {
+            assert_eq!(values.len(), 4);
+            let arr = [values[0], values[1], values[2], values[3]];
+
+            let output = u32::from_le_bytes(arr);
+
+            println!("{:?} -> {output}", arr);
+
+            Ok(())
+        }
+    }
+}
diff --git a/rust/qmk-hid-com/src/search.rs b/rust/qmk-hid-com/src/search.rs
new file mode 100644
index 0000000..3c954ba
--- /dev/null
+++ b/rust/qmk-hid-com/src/search.rs
@@ -0,0 +1,51 @@
+use hidapi::HidApi;
+
+pub fn search(api: &HidApi, vendor_name: String) -> anyhow::Result<()> {
+    for device in api.device_list() {
+        if let Some(string) = device.manufacturer_string() {
+            if string.to_lowercase().contains(&vendor_name.to_lowercase()) {
+                println!(
+                    "Device matches: {} (VID) - {} (PID) {}:{} - at {} by {} via {:#?}",
+                    device.vendor_id(),
+                    device.product_id(),
+                    device.usage_page(),
+                    device.usage(),
+                    device.path().to_str()?,
+                    string,
+                    device.bus_type(),
+                );
+
+                println!(
+                    "  {} (Interface {}):",
+                    match device.product_string() {
+                        Some(s) => s,
+                        _ => "<COULD NOT FETCH>",
+                    },
+                    device.interface_number()
+                );
+                let mut descriptor = vec![0u8; 2048];
+                match device
+                    .open_device(api)
+                    .and_then(|dev| dev.get_report_descriptor(&mut descriptor))
+                {
+                    Ok(length) => {
+                        println!("    {} (descriptor)", {
+                            &descriptor[..length]
+                                .iter()
+                                .map(|val| val.to_string())
+                                .collect::<String>()
+                        })
+                    }
+                    Err(err) => println!("    Failed to retrieve descriptor ({:?})", err),
+                }
+                println!();
+            }
+        } else {
+            eprintln!(
+                "Device without manufacturer string ({})",
+                device.product_id()
+            );
+        }
+    }
+    Ok(())
+}