├── .gitignore ├── cli ├── Cargo.toml └── src │ ├── args.rs │ ├── main.rs │ └── cli.rs ├── core ├── Cargo.toml ├── src │ ├── either_report.rs │ ├── macros.rs │ ├── lib.rs │ ├── mouse.rs │ └── keyboard.rs └── README.md ├── async ├── Cargo.toml ├── src │ └── lib.rs └── README.md ├── LICENSE ├── tokio ├── Cargo.toml ├── src │ └── lib.rs └── README.md ├── Cargo.toml ├── src └── lib.rs ├── README.md └── .github └── workflows └── ci.yml /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hidg-cli" 3 | description = "Linux USB HID Gadget emulation. Command-line tool" 4 | version.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | edition.workspace = true 8 | repository.workspace = true 9 | homepage.workspace = true 10 | readme = "README.md" 11 | categories = ["command-line-utilities"] 12 | keywords = ["USB", "Gadget", "HID", "Linux", "Input"] 13 | publish = false 14 | 15 | [[bin]] 16 | name = "hidg" 17 | path = "src/main.rs" 18 | 19 | [dependencies] 20 | anyhow.workspace = true 21 | rustyline.workspace = true 22 | 23 | [dependencies.hidg] 24 | workspace = true 25 | default-features = false 26 | features = ["fromstr", "display", "phf", "keyboard", "mouse"] 27 | 28 | [dependencies.clap] 29 | workspace = true 30 | features = ["derive"] 31 | -------------------------------------------------------------------------------- /cli/src/args.rs: -------------------------------------------------------------------------------- 1 | #[derive(clap::Parser)] 2 | #[command( 3 | name = "hidg", 4 | version, 5 | about, 6 | propagate_version = true, 7 | // Command::trailing_var_ar is required to use ValueHint::CommandWithArguments 8 | trailing_var_arg = true, 9 | )] 10 | pub struct Args { 11 | /// HIDG commands 12 | #[clap(subcommand)] 13 | pub cmd: Cmd, 14 | } 15 | 16 | #[derive(Clone, Copy, PartialEq, Eq, clap::ValueEnum)] 17 | pub enum Class { 18 | /// Keyboard 19 | #[clap(aliases = ["kbd", "k"])] 20 | Keyboard, 21 | 22 | /// Mouse 23 | #[clap(aliases = ["m"])] 24 | Mouse, 25 | } 26 | 27 | #[derive(clap::Parser)] 28 | pub enum Cmd { 29 | /// Read-write reports in interactive mode 30 | Repl { 31 | #[arg(short, long, value_enum, default_value = "keyboard")] 32 | class: Class, 33 | 34 | #[arg(value_parser, default_value = "hidg0")] 35 | path: std::path::PathBuf, 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hidg-core" 3 | description = "Linux USB HID Gadget emulation" 4 | version.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | edition.workspace = true 8 | repository.workspace = true 9 | homepage.workspace = true 10 | readme = "README.md" 11 | categories = ["os", "os::linux-apis"] 12 | keywords = ["USB", "Gadget", "HID", "Linux", "Input"] 13 | 14 | [dependencies] 15 | static_assertions.workspace = true 16 | bitflags.workspace = true 17 | 18 | [dependencies.serde] 19 | workspace = true 20 | optional = true 21 | 22 | [dependencies.either] 23 | workspace = true 24 | optional = true 25 | 26 | [dependencies.phf] 27 | workspace = true 28 | optional = true 29 | 30 | #[dependencies.unicase] 31 | #workspace = true 32 | #optional = true 33 | 34 | [features] 35 | default = ["fromstr", "display", "phf", "keyboard", "mouse"] 36 | fromstr = [] 37 | display = [] 38 | #unicase = ["dep:unicase", "phf?/unicase"] 39 | keyboard = [] 40 | mouse = [] 41 | -------------------------------------------------------------------------------- /async/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-hidg" 3 | description = "Linux USB HID Gadget emulation with async interface" 4 | version.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | edition.workspace = true 8 | repository.workspace = true 9 | homepage.workspace = true 10 | readme = "README.md" 11 | categories = ["os", "os::linux-apis", "asynchronous"] 12 | keywords = ["USB", "Gadget", "HID", "Linux", "Input"] 13 | 14 | [dependencies.hidg-core] 15 | workspace = true 16 | default-features = false 17 | 18 | [dependencies.async-io] 19 | workspace = true 20 | 21 | [dependencies.blocking] 22 | workspace = true 23 | 24 | [dev-dependencies.smol] 25 | workspace = true 26 | 27 | [dev-dependencies.smol-potat] 28 | workspace = true 29 | 30 | [features] 31 | default = ["fromstr", "display", "phf", "keyboard", "mouse"] 32 | fromstr = ["hidg-core/fromstr"] 33 | display = ["hidg-core/display"] 34 | phf = ["hidg-core/phf"] 35 | #unicase = ["hidg-core/unicase"] 36 | either = ["hidg-core/either"] 37 | serde = ["hidg-core/serde"] 38 | keyboard = ["hidg-core/keyboard"] 39 | mouse = ["hidg-core/mouse"] 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 K. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tokio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tokio-hidg" 3 | description = "Linux USB HID Gadget emulation for tokio async runtime" 4 | version.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | edition.workspace = true 8 | repository.workspace = true 9 | homepage.workspace = true 10 | readme = "README.md" 11 | categories = ["os", "os::linux-apis", "asynchronous"] 12 | keywords = ["USB", "Gadget", "HID", "Linux", "Input"] 13 | 14 | [dependencies] 15 | libc.workspace = true 16 | 17 | [dependencies.hidg-core] 18 | workspace = true 19 | default-features = false 20 | 21 | [dependencies.tokio] 22 | workspace = true 23 | default-features = false 24 | features = ["fs", "sync", "io-util", "rt", "net"] 25 | 26 | [dev-dependencies.tokio] 27 | workspace = true 28 | features = ["macros", "rt-multi-thread"] 29 | 30 | [features] 31 | default = ["fromstr", "display", "phf", "keyboard", "mouse"] 32 | fromstr = ["hidg-core/fromstr"] 33 | display = ["hidg-core/display"] 34 | phf = ["hidg-core/phf"] 35 | #unicase = ["hidg-core/unicase"] 36 | either = ["hidg-core/either"] 37 | serde = ["hidg-core/serde"] 38 | keyboard = ["hidg-core/keyboard"] 39 | mouse = ["hidg-core/mouse"] 40 | -------------------------------------------------------------------------------- /core/src/either_report.rs: -------------------------------------------------------------------------------- 1 | use crate::Class; 2 | use either::Either; 3 | 4 | pub struct EitherReport { 5 | inner: Either, 6 | } 7 | 8 | impl EitherReport { 9 | pub fn new(inner: Either) -> Self { 10 | Self { inner } 11 | } 12 | } 13 | 14 | deref_impl! { 15 | EitherReport => inner: Either, 16 | } 17 | 18 | impl AsRef<[u8]> for EitherReport 19 | where 20 | L: AsRef<[u8]>, 21 | R: AsRef<[u8]>, 22 | { 23 | fn as_ref(&self) -> &[u8] { 24 | match &self.inner { 25 | Either::Left(left) => left.as_ref(), 26 | Either::Right(right) => right.as_ref(), 27 | } 28 | } 29 | } 30 | 31 | impl AsMut<[u8]> for EitherReport 32 | where 33 | L: AsMut<[u8]>, 34 | R: AsMut<[u8]>, 35 | { 36 | fn as_mut(&mut self) -> &mut [u8] { 37 | match &mut self.inner { 38 | Either::Left(left) => left.as_mut(), 39 | Either::Right(right) => right.as_mut(), 40 | } 41 | } 42 | } 43 | 44 | impl Class for Either { 45 | type Input = EitherReport; 46 | type Output = EitherReport; 47 | 48 | fn input(&self) -> Self::Input { 49 | EitherReport::new( 50 | self.as_ref() 51 | .map_left(|class| class.input()) 52 | .map_right(|class| class.input()), 53 | ) 54 | } 55 | 56 | fn output(&self) -> Self::Output { 57 | EitherReport::new( 58 | self.as_ref() 59 | .map_left(|class| class.output()) 60 | .map_right(|class| class.output()), 61 | ) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["core", "tokio", "async"] 3 | resolver = "2" 4 | 5 | [workspace.package] 6 | version = "0.2.0" 7 | authors = ["K. "] 8 | license = "MIT" 9 | edition = "2021" 10 | repository = "https://github.com/katyo/hidg-rs" 11 | homepage = "https://github.com/katyo/hidg-rs" 12 | 13 | [workspace.dependencies] 14 | libc = "0.2" 15 | static_assertions = "1" 16 | bitflags = "2" 17 | either = "1" 18 | unicase = "2" 19 | async-io = "2" 20 | blocking = "1" 21 | smol = "2" 22 | smol-potat = "1" 23 | anyhow = "1" 24 | rustyline = "10" 25 | clap = "4" 26 | 27 | [workspace.dependencies.serde] 28 | version = "1" 29 | features = ["derive"] 30 | 31 | [workspace.dependencies.phf] 32 | version = "0.11" 33 | features = ["macros"] 34 | 35 | [workspace.dependencies.tokio] 36 | version = "1" 37 | default-features = false 38 | 39 | [workspace.dependencies.hidg-core] 40 | path = "core" 41 | version = "0.2" 42 | default-features = false 43 | 44 | [workspace.dependencies.hidg] 45 | path = "" 46 | version = "0.2" 47 | default-features = false 48 | 49 | [package] 50 | name = "hidg" 51 | description = "Linux USB HID Gadget emulation" 52 | version.workspace = true 53 | authors.workspace = true 54 | license.workspace = true 55 | edition.workspace = true 56 | repository.workspace = true 57 | homepage.workspace = true 58 | readme = "README.md" 59 | categories = ["os", "os::linux-apis"] 60 | keywords = ["USB", "Gadget", "HID", "Linux", "Input"] 61 | 62 | [dependencies.hidg-core] 63 | workspace = true 64 | default-features = false 65 | 66 | [features] 67 | default = ["fromstr", "display", "phf", "keyboard", "mouse"] 68 | fromstr = ["hidg-core/fromstr"] 69 | display = ["hidg-core/display"] 70 | phf = ["hidg-core/phf"] 71 | #unicase = ["hidg-core/unicase"] 72 | either = ["hidg-core/either"] 73 | serde = ["hidg-core/serde"] 74 | keyboard = ["hidg-core/keyboard"] 75 | mouse = ["hidg-core/mouse"] 76 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![forbid(future_incompatible)] 2 | #![deny(bad_style, missing_docs)] 3 | #![doc = include_str!("../README.md")] 4 | 5 | use core::marker::PhantomData; 6 | use std::{ 7 | fs::{File, OpenOptions}, 8 | io::{Read, Write}, 9 | }; 10 | 11 | use hidg_core::{check_read, check_write, AsDevicePath}; 12 | 13 | pub use hidg_core::{Class, Result, StateChange, ValueChange}; 14 | 15 | #[cfg(feature = "keyboard")] 16 | pub use hidg_core::{ 17 | Key, KeyStateChanges, Keyboard, KeyboardInput, KeyboardOutput, Led, LedStateChanges, Leds, 18 | Modifiers, 19 | }; 20 | 21 | #[cfg(feature = "mouse")] 22 | pub use hidg_core::{ 23 | Button, Buttons, Mouse, MouseInput, MouseInputChange, MouseInputChanges, MouseOutput, 24 | }; 25 | 26 | /// HID Gadget Device 27 | pub struct Device { 28 | file: File, 29 | _class: PhantomData, 30 | } 31 | 32 | impl Device { 33 | /// Open device by path or name or number 34 | pub fn open(device: impl AsDevicePath) -> Result { 35 | let path = device.as_device_path(); 36 | 37 | let file = OpenOptions::new().read(true).write(true).open(path)?; 38 | 39 | Ok(Self { 40 | file, 41 | _class: PhantomData, 42 | }) 43 | } 44 | 45 | /// Send input report 46 | pub fn input(&mut self, input: &C::Input) -> Result<()> 47 | where 48 | C::Input: AsRef<[u8]>, 49 | { 50 | let raw = input.as_ref(); 51 | let len = self.file.write(raw)?; 52 | 53 | check_write(len, raw.len()) 54 | } 55 | 56 | /// Receive output report 57 | pub fn output(&mut self, output: &mut C::Output) -> Result<()> 58 | where 59 | C::Output: AsMut<[u8]>, 60 | { 61 | let raw = output.as_mut(); 62 | let len = self.file.read(raw)?; 63 | 64 | check_read(len, raw.len())?; 65 | 66 | Ok(()) 67 | } 68 | 69 | /// Try clone device 70 | pub fn try_clone(&self) -> Result { 71 | let file = self.file.try_clone()?; 72 | 73 | Ok(Self { 74 | file, 75 | _class: PhantomData, 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /async/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![forbid(future_incompatible)] 2 | #![deny(bad_style, missing_docs)] 3 | #![doc = include_str!("../README.md")] 4 | 5 | use hidg_core::{check_read, check_write, AsDevicePath}; 6 | 7 | pub use hidg_core::{Class, Error, Result, StateChange, ValueChange}; 8 | 9 | #[cfg(feature = "keyboard")] 10 | pub use hidg_core::{ 11 | Key, KeyStateChanges, Keyboard, KeyboardInput, KeyboardOutput, Led, LedStateChanges, Leds, 12 | Modifiers, 13 | }; 14 | 15 | #[cfg(feature = "mouse")] 16 | pub use hidg_core::{ 17 | Button, Buttons, Mouse, MouseInput, MouseInputChange, MouseInputChanges, MouseOutput, 18 | }; 19 | 20 | use core::marker::PhantomData; 21 | use std::{ 22 | fs::{File, OpenOptions}, 23 | io::{Read, Write}, 24 | }; 25 | 26 | use async_io::Async; 27 | use blocking::unblock as asyncify; 28 | 29 | /// HID Gadget Device 30 | pub struct Device { 31 | file: Async, 32 | _class: PhantomData, 33 | } 34 | 35 | impl Device { 36 | /// Open device by path or name or number 37 | pub async fn open(device: impl AsDevicePath) -> Result { 38 | let path = device.as_device_path(); 39 | let file = Async::new( 40 | asyncify(move || OpenOptions::new().read(true).write(true).open(path)).await?, 41 | )?; 42 | Ok(Self { 43 | file, 44 | _class: PhantomData, 45 | }) 46 | } 47 | 48 | /// Send input report 49 | pub async fn input(&mut self, input: &C::Input) -> Result<()> 50 | where 51 | C::Input: AsRef<[u8]>, 52 | { 53 | self.file.writable().await?; 54 | let raw = input.as_ref(); 55 | let len = self.file.get_ref().write(raw)?; 56 | 57 | check_write(len, raw.len()) 58 | } 59 | 60 | /// Receive output report 61 | pub async fn output(&mut self, output: &mut C::Output) -> Result<()> 62 | where 63 | C::Output: AsMut<[u8]>, 64 | { 65 | self.file.readable().await?; 66 | let raw = output.as_mut(); 67 | let len = self.file.get_ref().read(raw)?; 68 | 69 | check_read(len, raw.len())?; 70 | 71 | Ok(()) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tokio/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![forbid(future_incompatible)] 2 | #![deny(bad_style, missing_docs)] 3 | #![doc = include_str!("../README.md")] 4 | 5 | use hidg_core::{check_read, check_write, AsDevicePath}; 6 | 7 | pub use hidg_core::{Class, Error, Result, StateChange, ValueChange}; 8 | 9 | #[cfg(feature = "keyboard")] 10 | pub use hidg_core::{ 11 | Key, KeyStateChanges, Keyboard, KeyboardInput, KeyboardOutput, Led, LedStateChanges, Leds, 12 | Modifiers, 13 | }; 14 | 15 | #[cfg(feature = "mouse")] 16 | pub use hidg_core::{ 17 | Button, Buttons, Mouse, MouseInput, MouseInputChange, MouseInputChanges, MouseOutput, 18 | }; 19 | 20 | use core::marker::PhantomData; 21 | use std::{ 22 | fs::File, 23 | io::{Read, Write}, 24 | }; 25 | 26 | use tokio::{fs::OpenOptions, io::unix::AsyncFd}; 27 | 28 | /// HID Gadget Device 29 | pub struct Device { 30 | file: AsyncFd, 31 | _class: PhantomData, 32 | } 33 | 34 | impl Device { 35 | /// Open device by path or name or number 36 | pub async fn open(device: impl AsDevicePath) -> Result { 37 | let path = device.as_device_path(); 38 | let file = OpenOptions::new() 39 | .read(true) 40 | .write(true) 41 | .custom_flags(libc::O_NONBLOCK) 42 | .open(path) 43 | .await? 44 | .into_std() 45 | .await; 46 | let file = AsyncFd::new(file)?; 47 | Ok(Self { 48 | file, 49 | _class: PhantomData, 50 | }) 51 | } 52 | 53 | /// Send input report 54 | pub async fn input(&mut self, input: &C::Input) -> Result<()> 55 | where 56 | C::Input: AsRef<[u8]>, 57 | { 58 | let _ = self.file.writable().await?; 59 | let raw = input.as_ref(); 60 | let len = self.file.get_ref().write(raw)?; 61 | 62 | check_write(len, raw.len()) 63 | } 64 | 65 | /// Receive output report 66 | pub async fn output(&mut self, output: &mut C::Output) -> Result<()> 67 | where 68 | C::Output: AsMut<[u8]>, 69 | { 70 | let _ = self.file.readable().await?; 71 | let raw = output.as_mut(); 72 | let len = self.file.get_ref().read(raw)?; 73 | 74 | check_read(len, raw.len())?; 75 | 76 | Ok(()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /core/README.md: -------------------------------------------------------------------------------- 1 | # HID Gadget Emulation in Rust 2 | 3 | [![github](https://img.shields.io/badge/github-katyo/hidg--rs-8da0cb.svg?style=for-the-badge&logo=github)](https://github.com/katyo/hidg-rs) 4 | [![crate](https://img.shields.io/crates/v/hidg-core.svg?style=for-the-badge&color=fc8d62&logo=rust)](https://crates.io/crates/hidg-core) 5 | [![docs](https://img.shields.io/badge/docs.rs-hidg--core-66c2a5?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K)](https://docs.rs/hidg-core) 6 | [![MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg?style=for-the-badge)](https://opensource.org/licenses/MIT) 7 | [![CI](https://img.shields.io/github/actions/workflow/status/katyo/hidg-rs/ci.yml?branch=master&style=for-the-badge&logo=github-actions&logoColor=white)](https://github.com/katyo/hidg-rs/actions?query=workflow%3ARust) 8 | 9 | Rust crate for interfacing with Linux HID Gadget devices (/dev/hidgX). 10 | 11 | Since all functionality is dependent on Linux function calls, this crate only compiles for Linux systems. 12 | 13 | ## Crates 14 | 15 | - **[hidg-core](https://crates.io/crates/hidg-core)** - core abstractions and low level interface (not for end users) 16 | - [hidg](https://crates.io/crates/hidg) - std interface which supports synchronous operation only 17 | - [tokio-hidg](https://crates.io/crates/tokio-hidg) - async interface for [tokio](https://tokio.rs/) async runtime 18 | - [async-hidg](https://crates.io/crates/async-hidg) - async interface for other async runtimes 19 | 20 | ## Features 21 | 22 | - *fromstr* - implements [core::str::FromStr] implementation for some types 23 | - *display* - implements [std::fmt::Display] implementation for some types 24 | - *phf* - use [phf](https://crates.io/crates/phf) in [core::str::FromStr] trait implementations 25 | - *serde* - enables [serde](https://crates.io/crates/serde) support for some types 26 | - *keyboard* - enables keyboard class support 27 | - *mouse* - enables mouse class support 28 | -------------------------------------------------------------------------------- /cli/src/main.rs: -------------------------------------------------------------------------------- 1 | mod args; 2 | mod cli; 3 | 4 | use args::{Args, Class, Cmd}; 5 | use cli::Cli; 6 | use hidg::{Button, Device, Key, Keyboard, Mouse, StateChange, ValueChange}; 7 | use rustyline::{error::ReadlineError, Editor}; 8 | 9 | fn main() -> anyhow::Result<()> { 10 | let args: Args = clap::Parser::parse(); 11 | 12 | match args.cmd { 13 | Cmd::Repl { class, path } => match class { 14 | Class::Keyboard => { 15 | let mut dev = Device::::open(path)?; 16 | 17 | let mut rl = Editor::::new()?; 18 | rl.set_helper(Cli::new(class).into()); 19 | loop { 20 | let readline = rl.readline(">> "); 21 | match readline { 22 | Ok(line) => { 23 | rl.add_history_entry(line.as_str()); 24 | 25 | let mut words = line.split(char::is_whitespace); 26 | if let Some(cmd) = words.next() { 27 | match cmd { 28 | "" => { 29 | let keys = dev 30 | .input() 31 | .pressed() 32 | .map(|k| k.to_string()) 33 | .collect::>() 34 | .join(" "); 35 | 36 | let leds = dev 37 | .output() 38 | .lit() 39 | .map(|l| l.to_string()) 40 | .collect::>() 41 | .join(" "); 42 | 43 | println!("Keys pressed: {}, Leds lit: {}", keys, leds); 44 | } 45 | "press" => { 46 | let keys = words 47 | .map(|k| k.parse().map(StateChange::press)) 48 | .collect::>, _>>()?; 49 | dev.updates(keys)?; 50 | } 51 | "release" => { 52 | let keys = words 53 | .map(|k| k.parse().map(StateChange::release)) 54 | .collect::>, _>>()?; 55 | dev.updates(keys)?; 56 | } 57 | other => { 58 | println!("Unknown command: {}", other); 59 | } 60 | } 61 | } 62 | } 63 | Err(ReadlineError::Interrupted) => { 64 | println!("CTRL-C"); 65 | break; 66 | } 67 | Err(ReadlineError::Eof) => { 68 | println!("CTRL-D"); 69 | break; 70 | } 71 | Err(err) => { 72 | println!("Error: {:?}", err); 73 | break; 74 | } 75 | } 76 | } 77 | } 78 | Class::Mouse => { 79 | let mut dev = Device::::open(path)?; 80 | } 81 | }, 82 | } 83 | 84 | Ok(()) 85 | } 86 | -------------------------------------------------------------------------------- /cli/src/cli.rs: -------------------------------------------------------------------------------- 1 | use crate::args::Class; 2 | use hidg::{Button, Key}; 3 | use rustyline::{ 4 | completion::{Candidate, Completer}, 5 | highlight::Highlighter, 6 | hint::{Hint, Hinter}, 7 | validate::Validator, 8 | Context, Helper, Result, 9 | }; 10 | 11 | pub struct Cli { 12 | class: Class, 13 | keys: Vec, 14 | } 15 | 16 | impl Helper for Cli {} 17 | 18 | impl Cli { 19 | pub fn new(class: Class) -> Self { 20 | let keys = match class { 21 | Class::Keyboard => hidg::Key::VARIANTS 22 | .iter() 23 | .copied() 24 | .map(Entry::Key) 25 | .collect(), 26 | Class::Mouse => hidg::Button::VARIANTS 27 | .iter() 28 | .copied() 29 | .map(Entry::Btn) 30 | .collect(), 31 | }; 32 | 33 | Self { class, keys } 34 | } 35 | } 36 | 37 | #[derive(Clone, Copy, Debug)] 38 | pub enum Command { 39 | State, 40 | Press, 41 | Release, 42 | Move, 43 | Wheel, 44 | } 45 | 46 | impl AsRef for Command { 47 | fn as_ref(&self) -> &str { 48 | use Command::*; 49 | match self { 50 | State => "state", 51 | Press => "press", 52 | Release => "release", 53 | Move => "move", 54 | Wheel => "wheel", 55 | } 56 | } 57 | } 58 | 59 | #[derive(Clone, Copy, Debug)] 60 | pub enum Entry { 61 | Cmd(Command), 62 | Key(Key), 63 | Btn(Button), 64 | } 65 | 66 | impl AsRef for Entry { 67 | fn as_ref(&self) -> &str { 68 | use Entry::*; 69 | match self { 70 | Cmd(cmd) => cmd.as_ref(), 71 | Key(key) => key.as_ref(), 72 | Btn(btn) => btn.as_ref(), 73 | } 74 | } 75 | } 76 | 77 | impl Hinter for Cli { 78 | type Hint = Entry; 79 | 80 | fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option { 81 | None 82 | } 83 | } 84 | 85 | impl Highlighter for Cli {} 86 | 87 | impl Validator for Cli {} 88 | 89 | impl Hint for Entry { 90 | fn display(&self) -> &str { 91 | self.as_ref() 92 | } 93 | fn completion(&self) -> Option<&str> { 94 | Some(self.as_ref()) 95 | } 96 | } 97 | 98 | impl Candidate for Entry { 99 | fn display(&self) -> &str { 100 | self.as_ref() 101 | } 102 | fn replacement(&self) -> &str { 103 | self.as_ref() 104 | } 105 | } 106 | 107 | impl Completer for Cli { 108 | type Candidate = Entry; 109 | 110 | fn complete( 111 | &self, 112 | line: &str, 113 | pos: usize, 114 | _ctx: &Context<'_>, 115 | ) -> Result<(usize, Vec)> { 116 | let line = line.split_at(pos).0; 117 | if let Some((cmd, args)) = line.split_once(' ') { 118 | let pos = cmd.len() 119 | + 1 120 | + if let Some(pos) = args.rfind(' ') { 121 | pos 122 | } else { 123 | 0 124 | }; 125 | match cmd { 126 | "press" | "release" => Ok((pos, self.keys.clone())), 127 | _ => Ok((0, vec![])), 128 | } 129 | } else { 130 | Ok(( 131 | 0, 132 | match self.class { 133 | Class::Keyboard => vec![ 134 | Entry::Cmd(Command::State), 135 | Entry::Cmd(Command::Press), 136 | Entry::Cmd(Command::Release), 137 | ], 138 | Class::Mouse => vec![ 139 | Entry::Cmd(Command::State), 140 | Entry::Cmd(Command::Press), 141 | Entry::Cmd(Command::Release), 142 | Entry::Cmd(Command::Move), 143 | Entry::Cmd(Command::Wheel), 144 | ], 145 | }, 146 | )) 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HID Gadget Emulation in Rust 2 | 3 | [![github](https://img.shields.io/badge/github-katyo/hidg--rs-8da0cb.svg?style=for-the-badge&logo=github)](https://github.com/katyo/hidg-rs) 4 | [![crate](https://img.shields.io/crates/v/hidg.svg?style=for-the-badge&color=fc8d62&logo=rust)](https://crates.io/crates/hidg) 5 | [![docs](https://img.shields.io/badge/docs.rs-hidg-66c2a5?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K)](https://docs.rs/hidg) 6 | [![MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg?style=for-the-badge)](https://opensource.org/licenses/MIT) 7 | [![CI](https://img.shields.io/github/actions/workflow/status/katyo/hidg-rs/ci.yml?branch=master&style=for-the-badge&logo=github-actions&logoColor=white)](https://github.com/katyo/hidg-rs/actions?query=workflow%3ARust) 8 | 9 | Rust crate for interfacing with Linux HID Gadget devices (/dev/hidgX). 10 | 11 | Since all functionality is dependent on Linux function calls, this crate only compiles for Linux systems. 12 | 13 | ## Crates 14 | 15 | - [hidg-core](https://crates.io/crates/hidg-core) - core abstractions and low level interface (not for end users) 16 | - **[hidg](https://crates.io/crates/hidg)** - std interface which supports synchronous operation only 17 | - [tokio-hidg](https://crates.io/crates/tokio-hidg) - async interface for [tokio](https://tokio.rs/) runtime 18 | - [async-hidg](https://crates.io/crates/async-hidg) - async interface for other runtimes 19 | 20 | ## Features 21 | 22 | - *fromstr* - implements [core::str::FromStr] implementation for some types 23 | - *display* - implements [std::fmt::Display] implementation for some types 24 | - *phf* - use [phf](https://crates.io/crates/phf) in [core::str::FromStr] trait implementations 25 | - *serde* - enables [serde](https://crates.io/crates/serde) support for some types 26 | - *keyboard* - enables keyboard class support 27 | - *mouse* - enables mouse class support 28 | 29 | ## Usage examples 30 | 31 | Keyboard input simulation: 32 | 33 | ```rust,no_run 34 | use hidg::{Class, Device, Keyboard, Key, Led, StateChange}; 35 | 36 | fn main() -> std::io::Result<()> { 37 | let mut device = Device::::open(0)?; // open device 38 | 39 | // Create input report 40 | let mut input = Keyboard.input(); 41 | 42 | // Press left ctrl modifier 43 | input.press_key(Key::LeftCtrl); 44 | 45 | // Press key 'A' 46 | input.press_key(Key::A); 47 | 48 | // Send input report 49 | device.input(&input)?; 50 | 51 | // Get pressed keys 52 | println!("Keys: {:?}", input.pressed().collect::>()); 53 | 54 | // Release left ctrl modifier 55 | input.release_key(Key::LeftCtrl); 56 | 57 | // Release key 'A' 58 | input.release_key(Key::A); 59 | 60 | // Send input report 61 | device.input(&input)?; 62 | 63 | // Create output report 64 | let mut output = Keyboard.output(); 65 | 66 | // Receive output report 67 | device.output(&mut output)?; 68 | 69 | // Print lit LEDs 70 | println!("LEDs: {:?}", output.lit().collect::>()); 71 | 72 | Ok(()) 73 | } 74 | ``` 75 | 76 | Mouse input simulation: 77 | 78 | ```rust,no_run 79 | use hidg::{Button, Class, Device, Mouse, StateChange, ValueChange}; 80 | 81 | fn main() -> std::io::Result<()> { 82 | let mut device = Device::::open("hidg0")?; // open device 83 | 84 | // Create input report 85 | let mut input = Mouse.input(); 86 | 87 | // Press primary button 88 | input.press_button(Button::Primary); 89 | 90 | // Update pointer coordinates 91 | input.change_pointer((150, 50), false); 92 | 93 | // Send input report 94 | device.input(&input)?; 95 | 96 | // Move pointer relatively 97 | input.change_pointer((70, -30), true); 98 | 99 | // Get pressed buttons 100 | println!("Buttons: {:?}", input.pressed().collect::>()); 101 | 102 | // Release primary button 103 | input.release_button(Button::Primary); 104 | 105 | // Send input report 106 | device.input(&input)?; 107 | 108 | Ok(()) 109 | } 110 | ``` 111 | -------------------------------------------------------------------------------- /tokio/README.md: -------------------------------------------------------------------------------- 1 | # HID Gadget Emulation in Rust 2 | 3 | [![github](https://img.shields.io/badge/github-katyo/hidg--rs-8da0cb.svg?style=for-the-badge&logo=github)](https://github.com/katyo/hidg-rs) 4 | [![crate](https://img.shields.io/crates/v/tokio-hidg.svg?style=for-the-badge&color=fc8d62&logo=rust)](https://crates.io/crates/tokio-hidg) 5 | [![docs](https://img.shields.io/badge/docs.rs-tokio--hidg-66c2a5?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K)](https://docs.rs/tokio-hidg) 6 | [![MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg?style=for-the-badge)](https://opensource.org/licenses/MIT) 7 | [![CI](https://img.shields.io/github/actions/workflow/status/katyo/hidg-rs/ci.yml?branch=master&style=for-the-badge&logo=github-actions&logoColor=white)](https://github.com/katyo/hidg-rs/actions?query=workflow%3ARust) 8 | 9 | Rust crate for interfacing with Linux HID Gadget devices (/dev/hidgX). 10 | This crate supports [tokio](https://tokio.rs/) async runtime. 11 | 12 | Since all functionality is dependent on Linux function calls, this crate only compiles for Linux systems. 13 | 14 | ## Crates 15 | 16 | - [hidg-core](https://crates.io/crates/hidg-core) - core abstractions and low level interface (not for end users) 17 | - [hidg](https://crates.io/crates/hidg) - std interface which supports synchronous operation only 18 | - **[tokio-hidg](https://crates.io/crates/tokio-hidg)** - async interface for [tokio](https://tokio.rs/) runtime 19 | - [async-hidg](https://crates.io/crates/async-hidg) - async interface for other runtimes 20 | 21 | ## Features 22 | 23 | - *fromstr* - implements [core::str::FromStr] implementation for some types 24 | - *display* - implements [std::fmt::Display] implementation for some types 25 | - *phf* - use [phf](https://crates.io/crates/phf) in [core::str::FromStr] trait implementations 26 | - *serde* - enables [serde](https://crates.io/crates/serde) support for some types 27 | - *keyboard* - enables keyboard class support 28 | - *mouse* - enables mouse class support 29 | 30 | ## Usage examples 31 | 32 | Keyboard input simulation: 33 | 34 | ```rust,no_run 35 | use tokio_hidg::{Class, Device, Keyboard, Key, Led, StateChange}; 36 | 37 | #[tokio::main] 38 | async fn main() -> std::io::Result<()> { 39 | let mut device = Device::::open(0).await?; // open device 40 | 41 | // Create input report 42 | let mut input = Keyboard.input(); 43 | 44 | // Press left ctrl modifier 45 | input.press_key(Key::LeftCtrl); 46 | 47 | // Press key 'A' 48 | input.press_key(Key::A); 49 | 50 | // Print pressed keys 51 | println!("Keys: {:?}", input.pressed().collect::>()); 52 | 53 | // Send input report 54 | device.input(&input).await?; 55 | 56 | // Release left ctrl modifier 57 | input.release_key(Key::LeftCtrl); 58 | 59 | // Release key 'A' 60 | input.release_key(Key::A); 61 | 62 | // Send input report 63 | device.input(&input).await?; 64 | 65 | // Create output report 66 | let mut output = Keyboard.output(); 67 | 68 | // Receive output report 69 | device.output(&mut output).await?; 70 | 71 | // Print lit LEDs 72 | println!("LEDs: {:?}", output.lit().collect::>()); 73 | 74 | Ok(()) 75 | } 76 | ``` 77 | 78 | Mouse input simulation: 79 | 80 | ```rust,no_run 81 | use tokio_hidg::{Button, Class, Device, Mouse, StateChange, ValueChange}; 82 | 83 | #[tokio::main] 84 | async fn main() -> std::io::Result<()> { 85 | let mut device = Device::::open("hidg0").await?; // open device 86 | 87 | // Create input report 88 | let mut input = Mouse.input(); 89 | 90 | // Press primary button 91 | input.press_button(Button::Primary); 92 | 93 | // Set pointer coordinates 94 | input.change_pointer((150, 50), false); 95 | 96 | // Send input report 97 | device.input(&input).await?; 98 | 99 | // Move pointer relatively 100 | input.change_pointer((70, -30), true); 101 | 102 | // Print pressed buttons 103 | println!("Buttons: {:?}", input.pressed().collect::>()); 104 | 105 | // Release primary button 106 | input.release_button(Button::Primary); 107 | 108 | // Send input report 109 | device.input(&input).await?; 110 | 111 | Ok(()) 112 | } 113 | ``` 114 | -------------------------------------------------------------------------------- /async/README.md: -------------------------------------------------------------------------------- 1 | # HID Gadget Emulation in Rust 2 | 3 | [![github](https://img.shields.io/badge/github-katyo/hidg--rs-8da0cb.svg?style=for-the-badge&logo=github)](https://github.com/katyo/hidg-rs) 4 | [![crate](https://img.shields.io/crates/v/async-hidg.svg?style=for-the-badge&color=fc8d62&logo=rust)](https://crates.io/crates/async-hidg) 5 | [![docs](https://img.shields.io/badge/docs.rs-async--hidg-66c2a5?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K)](https://docs.rs/async-hidg) 6 | [![MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg?style=for-the-badge)](https://opensource.org/licenses/MIT) 7 | [![CI](https://img.shields.io/github/actions/workflow/status/katyo/hidg-rs/ci.yml?branch=master&style=for-the-badge&logo=github-actions&logoColor=white)](https://github.com/katyo/hidg-rs/actions?query=workflow%3ARust) 8 | 9 | Rust crate for interfacing with Linux HID Gadget devices (/dev/hidgX). 10 | This crate supports non-tokio async runtimes like the [smol](https://github.com/smol-rs/smol) and [async-std](https://async.rs/). 11 | 12 | Since all functionality is dependent on Linux function calls, this crate only compiles for Linux systems. 13 | 14 | ## Crates 15 | 16 | - [hidg-core](https://crates.io/crates/hidg-core) - core abstractions and low level interface (not for end users) 17 | - [hidg](https://crates.io/crates/hidg) - std interface which supports synchronous operation only 18 | - [tokio-hidg](https://crates.io/crates/tokio-hidg) - async interface for [tokio](https://tokio.rs/) runtime 19 | - **[async-hidg](https://crates.io/crates/async-hidg)** - async interface for other runtimes 20 | 21 | ## Features 22 | 23 | - *fromstr* - implements [core::str::FromStr] implementation for some types 24 | - *display* - implements [std::fmt::Display] implementation for some types 25 | - *phf* - use [phf](https://crates.io/crates/phf) in [core::str::FromStr] trait implementations 26 | - *serde* - enables [serde](https://crates.io/crates/serde) support for some types 27 | - *keyboard* - enables keyboard class support 28 | - *mouse* - enables mouse class support 29 | 30 | ## Usage examples 31 | 32 | Keyboard input simulation: 33 | 34 | ```rust,no_run 35 | use async_hidg::{Class, Device, Keyboard, Key, Led, StateChange}; 36 | 37 | #[smol_potat::main] 38 | async fn main() -> std::io::Result<()> { 39 | let mut device = Device::::open(0).await?; // open device 40 | 41 | // Create input report 42 | let mut input = Keyboard.input(); 43 | 44 | // Press left ctrl modifier 45 | input.press_key(Key::LeftCtrl); 46 | 47 | // Press key 'A' 48 | input.press_key(Key::A); 49 | 50 | // Print pressed keys 51 | println!("Keys: {:?}", input.pressed().collect::>()); 52 | 53 | // Send input report 54 | device.input(&input).await?; 55 | 56 | // Release left ctrl modifier 57 | input.release_key(Key::LeftCtrl); 58 | 59 | // Release key 'A' 60 | input.release_key(Key::A); 61 | 62 | // Send input report 63 | device.input(&input).await?; 64 | 65 | // Create output report 66 | let mut output = Keyboard.output(); 67 | 68 | // Receive output report 69 | device.output(&mut output).await?; 70 | 71 | // Print lit LEDs 72 | println!("LEDs: {:?}", output.lit().collect::>()); 73 | 74 | Ok(()) 75 | } 76 | ``` 77 | 78 | Mouse input simulation: 79 | 80 | ```rust,no_run 81 | use async_hidg::{Button, Class, Device, Mouse, StateChange, ValueChange}; 82 | 83 | #[smol_potat::main] 84 | async fn main() -> std::io::Result<()> { 85 | let mut device = Device::::open("hidg0").await?; // open device 86 | 87 | // Create input report 88 | let mut input = Mouse.input(); 89 | 90 | // Press primary button 91 | input.press_button(Button::Primary); 92 | 93 | // Set pointer coordinates 94 | input.change_pointer((150, 50), false); 95 | 96 | // Send input report 97 | device.input(&input).await?; 98 | 99 | // Move pointer relatively 100 | input.change_pointer((70, -30), true); 101 | 102 | // Print pressed buttons 103 | println!("Buttons: {:?}", input.pressed().collect::>()); 104 | 105 | // Release primary button 106 | input.release_button(Button::Primary); 107 | 108 | // Send input report 109 | device.input(&input).await?; 110 | 111 | Ok(()) 112 | } 113 | ``` 114 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | on: 3 | push: 4 | branches: 5 | - master 6 | tags: 7 | - '[0-9]+.[0-9]+.[0-9]+' 8 | pull_request: 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | format: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Setup Rust 20 | uses: dtolnay/rust-toolchain@v1 21 | with: 22 | toolchain: stable 23 | components: rustfmt 24 | - uses: Swatinem/rust-cache@v2 25 | - name: Format 26 | run: cargo fmt --all -- --check 27 | 28 | doc: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v4 32 | - name: Setup Rust 33 | uses: dtolnay/rust-toolchain@v1 34 | with: 35 | toolchain: nightly 36 | components: rust-docs 37 | - uses: Swatinem/rust-cache@v2 38 | - name: Documentation 39 | env: 40 | DOCS_RS: 1 41 | run: cargo doc --all --all-features 42 | 43 | check: 44 | runs-on: ubuntu-latest 45 | steps: 46 | - uses: actions/checkout@v4 47 | - uses: dtolnay/rust-toolchain@v1 48 | with: 49 | toolchain: nightly 50 | components: clippy 51 | - uses: Swatinem/rust-cache@v2 52 | - name: Code check 53 | run: cargo clippy --all --all-targets 54 | 55 | # minver: 56 | # runs-on: ubuntu-latest 57 | # steps: 58 | # - uses: actions/checkout@v4 59 | # - uses: dtolnay/rust-toolchain@v1 60 | # with: 61 | # toolchain: nightly 62 | # - uses: Swatinem/rust-cache@v2 63 | # - run: clippy check --all --all-features --all-targets -Z minimal-versions 64 | 65 | test: 66 | needs: 67 | - format 68 | - doc 69 | - check 70 | strategy: 71 | fail-fast: ${{ startsWith(github.ref, 'refs/tags/') }} 72 | matrix: 73 | include: 74 | # Test features 75 | - task: test 76 | rust: stable 77 | target: x86_64-unknown-linux-gnu 78 | features: "''" 79 | test_args: --lib 80 | - task: test 81 | rust: stable 82 | target: x86_64-unknown-linux-gnu 83 | features: either 84 | test_args: --lib 85 | - task: test 86 | rust: stable 87 | target: x86_64-unknown-linux-gnu 88 | features: fromstr 89 | test_args: --lib 90 | - task: test 91 | rust: stable 92 | target: x86_64-unknown-linux-gnu 93 | features: display 94 | test_args: --lib 95 | - task: test 96 | rust: stable 97 | target: x86_64-unknown-linux-gnu 98 | features: serde 99 | test_args: --lib 100 | - task: test 101 | rust: stable 102 | target: x86_64-unknown-linux-gnu 103 | features: phf 104 | test_args: --lib 105 | - task: test 106 | rust: stable 107 | target: x86_64-unknown-linux-gnu 108 | features: keyboard 109 | test_args: --lib 110 | - task: test 111 | rust: stable 112 | target: x86_64-unknown-linux-gnu 113 | features: mouse 114 | test_args: --lib 115 | - task: test 116 | rust: stable 117 | target: x86_64-unknown-linux-gnu 118 | features: default 119 | 120 | # Test targets 121 | - task: test 122 | rust: stable 123 | target: i686-unknown-linux-gnu 124 | features: default 125 | - task: test 126 | rust: stable 127 | target: x86_64-unknown-linux-gnu 128 | features: default 129 | - task: test 130 | rust: stable 131 | target: arm-unknown-linux-gnueabihf 132 | features: default 133 | test_args: --no-run 134 | - task: test 135 | rust: stable 136 | target: armv7-unknown-linux-gnueabihf 137 | features: default 138 | test_args: --no-run 139 | - task: test 140 | rust: stable 141 | target: aarch64-unknown-linux-gnu 142 | features: default 143 | test_args: --no-run 144 | #- task: test 145 | # rust: stable 146 | # target: mips-unknown-linux-gnu 147 | # features: default 148 | # test_args: --no-run 149 | #- task: test 150 | # rust: stable 151 | # target: mips64-unknown-linux-gnuabi64 152 | # features: default 153 | # test_args: --no-run 154 | #- task: test 155 | # rust: stable 156 | # target: mipsel-unknown-linux-gnu 157 | # features: default 158 | # test_args: --no-run 159 | #- task: test 160 | # rust: stable 161 | # target: mips64el-unknown-linux-gnuabi64 162 | # features: default 163 | # test_args: --no-run 164 | - task: test 165 | rust: stable 166 | target: powerpc-unknown-linux-gnu 167 | features: default 168 | test_args: --no-run 169 | - task: test 170 | rust: stable 171 | target: powerpc64-unknown-linux-gnu 172 | features: default 173 | test_args: --no-run 174 | #- task: test 175 | # rust: stable 176 | # target: sparc64-unknown-linux-gnu 177 | # features: default 178 | # test_args: --no-run 179 | 180 | # Test channels 181 | - task: channels 182 | rust: stable 183 | target: x86_64-unknown-linux-gnu 184 | features: default 185 | - task: channels 186 | rust: beta 187 | target: x86_64-unknown-linux-gnu 188 | features: default 189 | - task: channels 190 | rust: nightly 191 | target: x86_64-unknown-linux-gnu 192 | features: default 193 | 194 | runs-on: ubuntu-latest 195 | steps: 196 | - uses: actions/checkout@v4 197 | - name: Setup cross linux toolchain 198 | if: contains(matrix.target, '-linux-') && !startsWith(matrix.target, 'x86_64-') 199 | run: | 200 | GCC_TARGET=$(printf "${{ matrix.target }}" | sed 's/-unknown-/-/' | sed 's/arm[^-]*/arm/g') 201 | ENV_TARGET=$(printf "${{ matrix.target }}" | tr '-' '_') 202 | ENV_TARGET_UC=$(printf "${ENV_TARGET}" | tr '[[:lower:]]' '[[:upper:]]') 203 | sudo apt-get update -y 204 | sudo apt-get install -y --install-recommends gcc-${GCC_TARGET} 205 | echo "CC_${ENV_TARGET}=${GCC_TARGET}-gcc" >> $GITHUB_ENV 206 | echo "CARGO_TARGET_${ENV_TARGET_UC}_LINKER=${GCC_TARGET}-gcc" >> $GITHUB_ENV 207 | - name: Setup Rust 208 | uses: dtolnay/rust-toolchain@v1 209 | with: 210 | toolchain: ${{ matrix.rust }} 211 | target: ${{ matrix.target }} 212 | - uses: Swatinem/rust-cache@v2 213 | - name: Update deps 214 | run: cargo update 215 | - name: Build 216 | run: cargo build --target ${{ matrix.target }} --no-default-features --features ${{ matrix.features }} 217 | - name: Test 218 | timeout-minutes: 2 219 | env: 220 | RUST_BACKTRACE: full 221 | run: cargo test --all --target ${{ matrix.target }} --no-default-features --features ${{ matrix.features }} ${{ matrix.test_args }} 222 | 223 | publish: 224 | if: github.repository == 'katyo/hidg-rs' && startsWith(github.ref, 'refs/tags/') 225 | needs: 226 | - test 227 | runs-on: ubuntu-latest 228 | steps: 229 | - uses: actions/checkout@v4 230 | - name: Setup Rust 231 | uses: dtolnay/rust-toolchain@v1 232 | with: 233 | toolchain: stable 234 | - name: Publish crates 235 | uses: katyo/publish-crates@v2 236 | with: 237 | registry-token: ${{ secrets.CRATES_TOKEN }} 238 | args: --no-verify 239 | #dry-run: true 240 | -------------------------------------------------------------------------------- /core/src/macros.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_macros)] 2 | macro_rules! code_enum { 3 | ($($(#[$($type_meta:meta)*])* $type:ident: $repr_type:ty { $($(#[$($var_meta:meta)*])* $var:ident = $val:literal => $str:literal $(| $strs:literal)*,)* })*) => { 4 | $( 5 | $(#[$($type_meta)*])* 6 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 7 | #[repr($repr_type)] 8 | pub enum $type { 9 | $($(#[$($var_meta)*])* $var = $val,)* 10 | } 11 | 12 | const_assert_eq!(size_of::<$type>(), size_of::<$repr_type>()); 13 | 14 | impl From for $type { 15 | fn from(raw: u8) -> Self { 16 | unsafe { transmute(raw) } 17 | } 18 | } 19 | 20 | impl From<$type> for u8 { 21 | fn from(key: $type) -> Self { 22 | key as _ 23 | } 24 | } 25 | 26 | #[cfg(feature = "fromstr")] 27 | impl core::str::FromStr for $type { 28 | type Err = $crate::Unknown; 29 | 30 | fn from_str(s: &str) -> core::result::Result { 31 | #[cfg(all(feature = "phf", not(feature = "unicase")))] 32 | static MAP: phf::Map<&'static str, $type> = phf::phf_map! { 33 | $( 34 | $str => $type::$var, 35 | $($strs => $type::$var,)* 36 | )* 37 | }; 38 | 39 | #[cfg(all(feature = "phf", feature = "unicase"))] 40 | static MAP: phf::Map, $type> = phf::phf_map! { 41 | $( 42 | UniCase::ascii($str) => $type::$var, 43 | $(UniCase::ascii($strs) => $type::$var,)* 44 | )* 45 | }; 46 | 47 | #[cfg(all(feature = "phf", feature = "unicase"))] 48 | let s = &unicase::UniCase::ascii(s); 49 | 50 | #[cfg(feature = "phf")] 51 | { 52 | MAP.get(s).cloned().ok_or($crate::Unknown) 53 | } 54 | 55 | #[cfg(not(feature = "phf"))] 56 | Ok(match s { 57 | $($str $(| $strs)* => $type::$var,)* 58 | _ => return Err($crate::Unknown), 59 | }) 60 | } 61 | } 62 | 63 | #[cfg(feature = "display")] 64 | impl $type { 65 | /// List of all enum variants 66 | pub const VARIANTS: &'static [$type] = &[ 67 | $($type::$var,)* 68 | ]; 69 | } 70 | 71 | #[cfg(feature = "display")] 72 | impl AsRef for $type { 73 | fn as_ref(&self) -> &str { 74 | match self { 75 | $($type::$var => $str,)* 76 | } 77 | } 78 | } 79 | 80 | #[cfg(feature = "display")] 81 | impl core::fmt::Display for $type { 82 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 83 | f.write_str(self.as_ref()) 84 | } 85 | } 86 | )* 87 | }; 88 | } 89 | 90 | #[allow(unused_macros)] 91 | macro_rules! serde_num { 92 | ($($type:ty: $rtype:tt, $expect:literal;)*) => { 93 | $( 94 | #[cfg(feature = "serde")] 95 | impl serde::Serialize for $type { 96 | fn serialize(&self, serializer: S) -> core::result::Result 97 | where 98 | S: serde::Serializer, 99 | { 100 | serde_num!(@ser $rtype, serializer, <$rtype>::from(*self)) 101 | } 102 | } 103 | 104 | #[cfg(feature = "serde")] 105 | impl<'de> serde::Deserialize<'de> for $type { 106 | fn deserialize(deserializer: D) -> core::result::Result 107 | where 108 | D: serde::Deserializer<'de>, 109 | { 110 | struct Visitor; 111 | 112 | impl<'de> serde::de::Visitor<'de> for Visitor { 113 | type Value = $type; 114 | 115 | fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 116 | f.write_str($expect) 117 | } 118 | 119 | serde_num! { 120 | @visit { 121 | $rtype, u8: visit_u8; 122 | $rtype, i8: visit_i8; 123 | $rtype, u16: visit_u16; 124 | $rtype, i16: visit_i16; 125 | $rtype, u32: visit_u32; 126 | $rtype, i32: visit_i32; 127 | $rtype, u64: visit_u64; 128 | $rtype, i64: visit_i64; 129 | } 130 | } 131 | } 132 | 133 | serde_num!(@de $rtype, deserializer, Visitor) 134 | } 135 | } 136 | )* 137 | }; 138 | 139 | (@ser u8, $serializer:ident, $value:expr) => { 140 | $serializer.serialize_u8($value) 141 | }; 142 | 143 | (@ser u16, $serializer:ident, $value:expr) => { 144 | $serializer.serialize_u16($value) 145 | }; 146 | 147 | (@ser u32, $serializer:ident, $value:expr) => { 148 | $serializer.serialize_u32($value) 149 | }; 150 | 151 | (@de u8, $deserializer:ident, $visitor:ident) => { 152 | $deserializer.deserialize_u8($visitor) 153 | }; 154 | 155 | (@de u16, $deserializer:ident, $visitor:ident) => { 156 | $deserializer.deserialize_u16($visitor) 157 | }; 158 | 159 | (@de u32, $deserializer:ident, $visitor:ident) => { 160 | $deserializer.deserialize_u32($visitor) 161 | }; 162 | 163 | (@visit { $($rtype:ty, $type:ty: $name:ident;)* }) => { 164 | $( 165 | fn $name(self, value: $type) -> core::result::Result 166 | where 167 | E: serde::de::Error, 168 | { 169 | Self::Value::safe_from(value as $rtype) 170 | .ok_or_else(|| serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(value as _), &self)) 171 | } 172 | )* 173 | } 174 | } 175 | 176 | #[allow(unused_macros)] 177 | macro_rules! raw_ref { 178 | ($($type:ty;)*) => { 179 | $( 180 | impl AsRef<[u8]> for $type { 181 | fn as_ref(&self) -> &[u8] { 182 | unsafe { 183 | core::slice::from_raw_parts( 184 | self as *const _ as *const _, 185 | core::mem::size_of::(), 186 | ) 187 | } 188 | } 189 | } 190 | 191 | impl AsMut<[u8]> for $type { 192 | fn as_mut(&mut self) -> &mut [u8] { 193 | unsafe { 194 | core::slice::from_raw_parts_mut( 195 | self as *mut _ as *mut _, 196 | core::mem::size_of::(), 197 | ) 198 | } 199 | } 200 | } 201 | )* 202 | }; 203 | } 204 | 205 | macro_rules! deref_impl { 206 | ($($type:ident $(<$($param:ident),*>)* => $field:ident: $field_type:ty,)*) => { 207 | $( 208 | impl $(<$($param),*>)* core::ops::Deref for $type $(<$($param),*>)* { 209 | type Target = $field_type; 210 | 211 | fn deref(&self) -> &Self::Target { 212 | &self.$field 213 | } 214 | } 215 | 216 | impl $(<$($param),*>)* core::ops::DerefMut for $type $(<$($param),*>)* { 217 | fn deref_mut(&mut self) -> &mut Self::Target { 218 | &mut self.$field 219 | } 220 | } 221 | )* 222 | }; 223 | } 224 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![forbid(future_incompatible)] 2 | #![deny(bad_style, missing_docs)] 3 | #![doc = include_str!("../README.md")] 4 | 5 | #[cfg(not(target_os = "linux"))] 6 | compile_error!("This crate support Linux only"); 7 | 8 | #[cfg(feature = "serde")] 9 | use serde::{Deserialize, Serialize}; 10 | 11 | #[macro_use] 12 | mod macros; 13 | 14 | #[cfg(feature = "either")] 15 | mod either_report; 16 | 17 | #[cfg(feature = "keyboard")] 18 | mod keyboard; 19 | 20 | #[cfg(feature = "mouse")] 21 | mod mouse; 22 | 23 | #[cfg(feature = "keyboard")] 24 | pub use keyboard::{ 25 | Key, KeyStateChanges, Keyboard, KeyboardInput, KeyboardOutput, Led, LedStateChanges, Leds, 26 | Modifiers, 27 | }; 28 | 29 | #[cfg(feature = "mouse")] 30 | pub use mouse::{ 31 | Button, Buttons, Mouse, MouseInput, MouseInputChange, MouseInputChanges, MouseOutput, 32 | }; 33 | 34 | use std::{ 35 | io::ErrorKind, 36 | path::{Path, PathBuf}, 37 | }; 38 | 39 | pub use std::io::{Error, Result}; 40 | 41 | /// Unknown error 42 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 43 | pub struct Unknown; 44 | 45 | impl std::error::Error for Unknown {} 46 | 47 | impl core::fmt::Display for Unknown { 48 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 49 | f.write_str("Unknown") 50 | } 51 | } 52 | 53 | /// Key/button/LED state change event 54 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 55 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 56 | pub struct StateChange { 57 | data: T, 58 | state: bool, 59 | } 60 | 61 | impl StateChange { 62 | /// Create new state change event 63 | pub fn new(data: T, state: bool) -> Self { 64 | Self { data, state } 65 | } 66 | 67 | /// Create new press event 68 | pub fn press(data: T) -> Self { 69 | Self::new(data, true) 70 | } 71 | 72 | /// Create new on event 73 | pub fn on(data: T) -> Self { 74 | Self::new(data, true) 75 | } 76 | 77 | /// Create new release event 78 | pub fn release(data: T) -> Self { 79 | Self::new(data, false) 80 | } 81 | 82 | /// Create new off event 83 | pub fn off(data: T) -> Self { 84 | Self::new(data, false) 85 | } 86 | 87 | /// Get data 88 | pub fn data(&self) -> T 89 | where 90 | T: Copy, 91 | { 92 | self.data 93 | } 94 | 95 | /// Get state 96 | pub fn state(&self) -> bool { 97 | self.state 98 | } 99 | 100 | /// Is key/button press event 101 | pub fn is_press(&self) -> bool { 102 | self.state 103 | } 104 | 105 | /// Is LED on event 106 | pub fn is_on(&self) -> bool { 107 | self.state 108 | } 109 | 110 | /// Is key/button release event 111 | pub fn is_release(&self) -> bool { 112 | !self.state 113 | } 114 | 115 | /// Is LED off event 116 | pub fn is_off(&self) -> bool { 117 | !self.state 118 | } 119 | } 120 | 121 | impl From<(T, bool)> for StateChange { 122 | fn from((data, state): (T, bool)) -> Self { 123 | Self { data, state } 124 | } 125 | } 126 | 127 | impl From> for (T, bool) { 128 | fn from(StateChange { data, state }: StateChange) -> Self { 129 | (data, state) 130 | } 131 | } 132 | 133 | /// Pointer/cursor position change event 134 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 135 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 136 | pub struct ValueChange { 137 | data: T, 138 | #[cfg_attr(feature = "serde", serde(rename = "rel"))] 139 | relative: bool, 140 | } 141 | 142 | impl ValueChange { 143 | /// Create new value change event 144 | pub fn new(data: T, relative: bool) -> Self { 145 | Self { data, relative } 146 | } 147 | 148 | /// Get underlying data 149 | pub fn data(&self) -> T 150 | where 151 | T: Copy, 152 | { 153 | self.data 154 | } 155 | 156 | /// Create new absolute value change event 157 | pub fn absolute(data: T) -> Self { 158 | Self::new(data, false) 159 | } 160 | 161 | /// Create new relative value change event 162 | pub fn relative(data: T) -> Self { 163 | Self::new(data, true) 164 | } 165 | 166 | /// Is value change relative 167 | pub fn is_relative(&self) -> bool { 168 | self.relative 169 | } 170 | 171 | /// Is value change absolute 172 | pub fn is_absolute(&self) -> bool { 173 | !self.relative 174 | } 175 | } 176 | 177 | impl From<(T, bool)> for ValueChange { 178 | fn from((data, relative): (T, bool)) -> Self { 179 | Self { data, relative } 180 | } 181 | } 182 | 183 | impl From> for (T, bool) { 184 | fn from(ValueChange { data, relative }: ValueChange) -> Self { 185 | (data, relative) 186 | } 187 | } 188 | 189 | deref_impl! { 190 | StateChange => data: T, 191 | ValueChange => data: T, 192 | } 193 | 194 | /// Device class trait 195 | pub trait Class { 196 | /// Input report type 197 | type Input; 198 | 199 | /// Output report type 200 | type Output; 201 | 202 | /// Create input report 203 | fn input(&self) -> Self::Input; 204 | 205 | /// Create output report 206 | fn output(&self) -> Self::Output; 207 | } 208 | 209 | /// Device path trait 210 | pub trait AsDevicePath { 211 | /// Get absolute device path 212 | fn as_device_path(&self) -> PathBuf; 213 | } 214 | 215 | impl AsDevicePath for Path { 216 | fn as_device_path(&self) -> PathBuf { 217 | if self.is_absolute() { 218 | self.to_path_buf() 219 | } else { 220 | Path::new("/dev").join(self) 221 | } 222 | } 223 | } 224 | 225 | impl AsDevicePath for &Path { 226 | fn as_device_path(&self) -> PathBuf { 227 | if self.is_absolute() { 228 | self.to_path_buf() 229 | } else { 230 | Path::new("/dev").join(self) 231 | } 232 | } 233 | } 234 | 235 | impl AsDevicePath for PathBuf { 236 | fn as_device_path(&self) -> PathBuf { 237 | let path: &Path = self; 238 | path.as_device_path() 239 | } 240 | } 241 | 242 | impl AsDevicePath for &PathBuf { 243 | fn as_device_path(&self) -> PathBuf { 244 | let path: &Path = self; 245 | path.as_device_path() 246 | } 247 | } 248 | 249 | impl AsDevicePath for &str { 250 | fn as_device_path(&self) -> PathBuf { 251 | Path::new(self).as_device_path() 252 | } 253 | } 254 | 255 | impl AsDevicePath for String { 256 | fn as_device_path(&self) -> PathBuf { 257 | let s: &str = self; 258 | s.as_device_path() 259 | } 260 | } 261 | 262 | impl AsDevicePath for &String { 263 | fn as_device_path(&self) -> PathBuf { 264 | let s: &str = self; 265 | s.as_device_path() 266 | } 267 | } 268 | 269 | impl AsDevicePath for usize { 270 | fn as_device_path(&self) -> PathBuf { 271 | format!("/dev/hidg{self}").as_device_path() 272 | } 273 | } 274 | 275 | macro_rules! as_device_path { 276 | ($($type:ty),*) => { 277 | $( 278 | impl AsDevicePath for $type { 279 | fn as_device_path(&self) -> PathBuf { 280 | (*self as usize).as_device_path() 281 | } 282 | } 283 | )* 284 | }; 285 | } 286 | 287 | as_device_path!(u8, u16, u32, u64, i8, i16, i32, i64, isize); 288 | 289 | /// Wrapper to hide internals 290 | #[derive(Clone, Copy, Default)] 291 | pub struct Internal(T); 292 | 293 | impl core::ops::Deref for Internal { 294 | type Target = T; 295 | 296 | fn deref(&self) -> &Self::Target { 297 | &self.0 298 | } 299 | } 300 | 301 | impl core::ops::DerefMut for Internal { 302 | fn deref_mut(&mut self) -> &mut Self::Target { 303 | &mut self.0 304 | } 305 | } 306 | 307 | /// Check write report length 308 | pub fn check_write(actual: usize, expected: usize) -> Result<()> { 309 | if actual == expected { 310 | Ok(()) 311 | } else { 312 | Err(Error::new(ErrorKind::Other, "Error when writing report")) 313 | } 314 | } 315 | 316 | /// Check read report length 317 | pub fn check_read(actual: usize, expected: usize) -> Result<()> { 318 | if actual == expected { 319 | Ok(()) 320 | } else { 321 | Err(Error::new(ErrorKind::Other, "Error when reading report")) 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /core/src/mouse.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | use core::mem::{size_of, transmute}; 3 | use static_assertions::const_assert_eq; 4 | 5 | #[cfg(feature = "serde")] 6 | use serde::{Deserialize, Serialize}; 7 | 8 | use crate::{Class, StateChange, ValueChange}; 9 | 10 | /// Mouse HID class 11 | #[derive(Clone, Copy, Debug)] 12 | pub struct Mouse; 13 | 14 | impl Class for Mouse { 15 | type Input = MouseInput; 16 | type Output = MouseOutput; 17 | 18 | fn input(&self) -> Self::Input { 19 | Self::Input::default() 20 | } 21 | 22 | fn output(&self) -> Self::Output { 23 | Self::Output::default() 24 | } 25 | } 26 | 27 | impl AsRef for Mouse { 28 | fn as_ref(&self) -> &str { 29 | "mouse" 30 | } 31 | } 32 | 33 | impl core::fmt::Display for Mouse { 34 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 35 | f.write_str(self.as_ref()) 36 | } 37 | } 38 | 39 | bitflags! { 40 | /// Button mask 41 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 42 | pub struct Buttons: u8 { 43 | /// Primary button 44 | /// 45 | /// Usually left for right hand. 46 | const Primary = 0x01; 47 | 48 | /// Secondary button 49 | /// 50 | /// Usually right for right hand. 51 | const Secondary = 0x02; 52 | 53 | /// Tertiary button 54 | /// 55 | /// Usually middle. 56 | const Tertiary = 0x04; 57 | } 58 | } 59 | 60 | const_assert_eq!(size_of::(), 1); 61 | 62 | impl Default for Buttons { 63 | fn default() -> Self { 64 | Self::empty() 65 | } 66 | } 67 | 68 | impl Buttons { 69 | /// Converts from raw value safely 70 | pub fn safe_from(raw: u8) -> Option { 71 | Self::from_bits(raw) 72 | } 73 | } 74 | 75 | impl From for u8 { 76 | fn from(btns: Buttons) -> Self { 77 | btns.bits() 78 | } 79 | } 80 | 81 | code_enum! { 82 | /// Button code 83 | Button: u8 { 84 | /// No button 85 | None = 0x00 => "none" | "0", 86 | 87 | /// Primary button 88 | /// 89 | /// Usually left for right hand. 90 | Primary = 0x01 => "primary" | "first" | "1", 91 | 92 | /// Secondary button 93 | /// 94 | /// Usually right for right hand. 95 | Secondary = 0x02 => "secondary" | "second" | "2", 96 | 97 | /// Tertiary button 98 | /// 99 | /// Usually middle. 100 | Tertiary = 0x03 => "tertiary" | "third" | "3", 101 | } 102 | } 103 | 104 | impl From for Button { 105 | fn from(mods: Buttons) -> Self { 106 | let off = mods.bits().trailing_zeros() as u8; 107 | if off < 3 { 108 | Button::from(off + 1) 109 | } else { 110 | Button::None 111 | } 112 | } 113 | } 114 | 115 | impl From