Want access to keystrokes & mouse inputs without calling system-level APIs or even before host kernel processing? This library makes it easy by proxying devices through a microcomputer (Raspberry PI 4b & 5), which is then in turn routed into any computer. This allows for Injection, modification, and state viewing of every device proxied.
This implementation is an improvement compared to the C# version as multiple keyboards can function simultaneously.
- Ensure that the SBC is connected to the host PC through the OTG port. If you're using a Raspberry Pi 4 Model B or 5, use a USBC-USBA Adapter or USBC cable.
- Follow First Installation
- Connect a mouse or keyboard (spare if you have one initially to make it easier) to the SBC.
- Find where your mouse is in
/dev/input/
(it should be/dev/input/mice/
). Skip this step if you didn't plug a mouse in. - If you're using a keyboard, it will differ and should show up in
/dev/input/by-id/
named...-event-kbd
. Skip this step if you didn't plug a keyboard in. - Now, find your gadget device path. It should be
/dev/hidg0
if not, attempt to brute forcehidg0-..x
. This part is important and is required to function. - Once you've found the paths, plug them into a project or the example, build / run and viola! it should pass through your inputs from the microcomputer to the host PC.
Run the following commands on your Raspberry Pi
echo "dwc2" | sudo tee -a /etc/modules && echo "libcomposite" | sudo tee -a /etc/modules
, these enable OTG Host/Slave drivers & ConfigFSecho "dtoverlay=dwc2, dr_mode=peripheral" | sudo tee -a /boot/firmware/config.txt
or/boot/config.txt
for 4b or oldersudo wget -O /usr/bin/example_gadget https://raw.githubusercontent.com/StrateimTech/hid-api-rs/master/example_gadget.sh
sudo chmod +x /usr/bin/example_gadget
sudo sed -i '19s/^/\n\/usr\/bin\/example_gadget\n/' /etc/rc.local
"Adds /usr/bin/example_gadget to rc.local"- Reboot or run
sudo /usr/bin/example_gadget
- Done, you'll most likely never have to do this again. /dev/hidg0... should auto generate on boot.
- (Optional) Modify the example gadget to your liking.
If one of these commands didn't work you can follow this external but still relevant guide by Tobi.
Building an Example
git clone https://github.com/StrateimTech/hid-api-rs
cd ./hid-api-rs
Architectures
- 64bit Armv8 -
aarch64-unknown-linux-gnu
- 32bit Armv7 -
armv7-unknown-linux-gnueabihf
cargo build --bin hid_api_example --target armv7-unknown-linux-gnueabihf
Once built transfer to pi using preferred method, before running make sure to use elevated permissions since its accessing /dev/ directory.
(chmod +x hid_api_example
)
- Microcomputer / spare computer that supports USB OTG (Raspberry Pi 4 Model B or 5)
hid_api_example
, Both keyboard & mouse example code, with state injection.hid_api_example_mouse
, Mouse only, shows current state of mouse every 500 millis.hid_api_example_keyboard
, Keyboard only, printshi
when h is pressed.hid_api_example_injection
, Injection only no device pass through. It should move the mouse right 25px every second.
Mouse pass-through with state interception:
use hid_api_rs::{HidSpecification, HidMouse};
pub fn main() {
hid_api_rs::start_pass_through(HidSpecification {
mouse_inputs: Some([HidMouse { mouse_path: String::from("/dev/input/mice"), mouse_poll_rate: Some(1000), mouse_side_buttons: true }].to_vec()),
keyboard_inputs: None,
gadget_output: String::from("/dev/hidg0")
}).unwrap();
loop {
let mouses = hid_api_rs::get_mouses();
for mouse in mouses.iter_mut() {
let mouse_state = *mouse.get_state();
println!("Left: {}, Right: {}, Middle: {}, Side-4: {}, Side-5: {}", mouse_state.left_button, mouse_state.right_button, mouse_state.middle_button, mouse_state.four_button, mouse_state.five_button);
}
}
}
Keyboard pass-through with state interception:
use hid_api_rs::{HidSpecification, gadgets::keyboard::UsbKeyCode};
use std::{time::Duration, thread};
pub fn main() {
hid_api_rs::start_pass_through(HidSpecification {
mouse_inputs: None,
// Use your own keyboard by-id here!
keyboard_inputs: Some([String::from("/dev/input/by-id/usb-Keychron_K4_Keychron_K4-event-kbd")].to_vec()),
gadget_output: String::from("/dev/hidg0"),
}).unwrap();
loop {
let keyboard_state = hid_api_rs::get_keyboard();
if let Ok(keys_down) = &keyboard_state.keys_down.try_read() {
println!("Keys down: \n {:?}", keys_down.iter().map(|key| format!("{},", UsbKeyCode::from(*key as i16).to_string())).collect::<String>());
}
thread::sleep(Duration::from_millis(100))
}
}
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x02, // Usage (Mouse)
0xA1, 0x01, // Collection (Application)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection (Physical)
0x85, 0x01, // Report ID (1)
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (0x01)
0x29, 0x05, // Usage Maximum (0x05)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x95, 0x05, // Report Count (5)
0x75, 0x01, // Report Size (1)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01, // Report Count (1)
0x75, 0x03, // Report Size (3)
0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x16, 0x01, 0x80, // Logical Minimum (-32767)
0x26, 0xFF, 0x7F, // Logical Maximum (32767)
0x75, 0x10, // Report Size (16)
0x95, 0x02, // Report Count (2)
0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0x38, // Usage (Wheel)
0x15, 0x81, // Logical Minimum (-127)
0x25, 0x7F, // Logical Maximum (127)
0x75, 0x08, // Report Size (8)
0x95, 0x01, // Report Count (1)
0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
0xC0, // End Collection
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x85, 0x02, // Report ID (2)
0x05, 0x07, // Usage Page (Kbrd/Keypad)
0x19, 0xE0, // Usage Minimum (0xE0)
0x29, 0xE7, // Usage Maximum (0xE7)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x75, 0x08, // Report Size (8)
0x95, 0x01, // Report Count (1)
0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x75, 0x01, // Report Size (1)
0x95, 0x03, // Report Count (3)
0x05, 0x08, // Usage Page (LEDs)
0x19, 0x01, // Usage Minimum (Num Lock)
0x29, 0x03, // Usage Maximum (Scroll Lock)
0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x75, 0x01, // Report Size (1)
0x95, 0x05, // Report Count (5)
0x91, 0x01, // Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x75, 0x08, // Report Size (8)
0x95, 0x06, // Report Count (6)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x05, 0x07, // Usage Page (Kbrd/Keypad)
0x19, 0x00, // Usage Minimum (0x00)
0x2A, 0xFF, 0x00, // Usage Maximum (0xFF)
0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
// 133 bytes
0x05 0x01 0x09 0x02 0xA1 0x01 0x09 0x01 0xA1 0x00 0x85 0x01 0x05 0x09 0x19 0x01 0x29 0x03 0x15 0x00 0x25 0x01 0x95 0x03 0x75 0x01 0x81 0x02 0x95 0x01 0x75 0x05 0x81 0x03 0x05 0x01 0x09 0x30 0x09 0x31 0x16 0x01 0x80 0x26 0xFF 0x7F 0x75 0x10 0x95 0x02 0x81 0x06 0x09 0x38 0x15 0x81 0x25 0x7F 0x75 0x08 0x95 0x01 0x81 0x06 0xC0 0xC0 0x05 0x01 0x09 0x06 0xA1 0x01 0x85 0x02 0x05 0x07 0x19 0xe0 0x29 0xe7 0x15 0x00 0x25 0x01 0x75 0x01 0x95 0x08 0x81 0x02 0x75 0x08 0x95 0x01 0x81 0x01 0x75 0x01 0x95 0x03 0x05 0x08 0x19 0x01 0x29 0x03 0x91 0x02 0x75 0x01 0x95 0x05 0x91 0x01 0x75 0x08 0x95 0x06 0x15 0x00 0x26 0xff 0x00 0x05 0x07 0x19 0x00 0x2a 0xff 0x00 0x81 0x00