diff options
Diffstat (limited to 'rust/qmk-hid-com/src')
-rw-r--r-- | rust/qmk-hid-com/src/cli.rs | 40 | ||||
-rw-r--r-- | rust/qmk-hid-com/src/hid/mod.rs | 44 | ||||
-rw-r--r-- | rust/qmk-hid-com/src/main.rs | 107 | ||||
-rw-r--r-- | rust/qmk-hid-com/src/search.rs | 51 |
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(()) +} |