summaryrefslogtreecommitdiffstats
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(())
+}