mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-28 16:41:52 +08:00
227 lines
6.9 KiB
Rust
227 lines
6.9 KiB
Rust
//! HID Function implementation for USB Gadget
|
|
|
|
use std::path::{Path, PathBuf};
|
|
use tracing::debug;
|
|
|
|
use super::configfs::{create_dir, create_symlink, remove_dir, remove_file, write_bytes, write_file};
|
|
use super::function::{FunctionMeta, GadgetFunction};
|
|
use super::report_desc::{KEYBOARD_WITH_LED, MOUSE_ABSOLUTE, MOUSE_RELATIVE};
|
|
use crate::error::Result;
|
|
|
|
/// HID function type
|
|
#[derive(Debug, Clone)]
|
|
pub enum HidFunctionType {
|
|
/// Keyboard with LED feedback support
|
|
/// Uses 2 endpoints: IN (reports) + OUT (LED status)
|
|
Keyboard,
|
|
/// Relative mouse (traditional mouse movement)
|
|
/// Uses 1 endpoint: IN
|
|
MouseRelative,
|
|
/// Absolute mouse (touchscreen-like positioning)
|
|
/// Uses 1 endpoint: IN
|
|
MouseAbsolute,
|
|
}
|
|
|
|
impl HidFunctionType {
|
|
/// Get endpoints required for this function type
|
|
pub fn endpoints(&self) -> u8 {
|
|
match self {
|
|
HidFunctionType::Keyboard => 2, // IN + OUT for LED
|
|
HidFunctionType::MouseRelative => 1,
|
|
HidFunctionType::MouseAbsolute => 1,
|
|
}
|
|
}
|
|
|
|
/// Get HID protocol
|
|
pub fn protocol(&self) -> u8 {
|
|
match self {
|
|
HidFunctionType::Keyboard => 1, // Keyboard
|
|
HidFunctionType::MouseRelative => 2, // Mouse
|
|
HidFunctionType::MouseAbsolute => 2, // Mouse
|
|
}
|
|
}
|
|
|
|
/// Get HID subclass
|
|
pub fn subclass(&self) -> u8 {
|
|
match self {
|
|
HidFunctionType::Keyboard => 1, // Boot interface
|
|
HidFunctionType::MouseRelative => 1, // Boot interface
|
|
HidFunctionType::MouseAbsolute => 0, // No boot interface (absolute not in boot protocol)
|
|
}
|
|
}
|
|
|
|
/// Get report length in bytes
|
|
pub fn report_length(&self) -> u8 {
|
|
match self {
|
|
HidFunctionType::Keyboard => 8,
|
|
HidFunctionType::MouseRelative => 4,
|
|
HidFunctionType::MouseAbsolute => 6,
|
|
}
|
|
}
|
|
|
|
/// Get report descriptor
|
|
pub fn report_desc(&self) -> &'static [u8] {
|
|
match self {
|
|
HidFunctionType::Keyboard => KEYBOARD_WITH_LED,
|
|
HidFunctionType::MouseRelative => MOUSE_RELATIVE,
|
|
HidFunctionType::MouseAbsolute => MOUSE_ABSOLUTE,
|
|
}
|
|
}
|
|
|
|
/// Get description
|
|
pub fn description(&self) -> &'static str {
|
|
match self {
|
|
HidFunctionType::Keyboard => "Keyboard",
|
|
HidFunctionType::MouseRelative => "Relative Mouse",
|
|
HidFunctionType::MouseAbsolute => "Absolute Mouse",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// HID Function for USB Gadget
|
|
#[derive(Debug, Clone)]
|
|
pub struct HidFunction {
|
|
/// Instance number (usb0, usb1, ...)
|
|
instance: u8,
|
|
/// Function type
|
|
func_type: HidFunctionType,
|
|
/// Cached function name (avoids repeated allocation)
|
|
name: String,
|
|
}
|
|
|
|
impl HidFunction {
|
|
/// Create a keyboard function
|
|
pub fn keyboard(instance: u8) -> Self {
|
|
Self {
|
|
instance,
|
|
func_type: HidFunctionType::Keyboard,
|
|
name: format!("hid.usb{}", instance),
|
|
}
|
|
}
|
|
|
|
/// Create a relative mouse function
|
|
pub fn mouse_relative(instance: u8) -> Self {
|
|
Self {
|
|
instance,
|
|
func_type: HidFunctionType::MouseRelative,
|
|
name: format!("hid.usb{}", instance),
|
|
}
|
|
}
|
|
|
|
/// Create an absolute mouse function
|
|
pub fn mouse_absolute(instance: u8) -> Self {
|
|
Self {
|
|
instance,
|
|
func_type: HidFunctionType::MouseAbsolute,
|
|
name: format!("hid.usb{}", instance),
|
|
}
|
|
}
|
|
|
|
/// Get function path in gadget
|
|
fn function_path(&self, gadget_path: &Path) -> PathBuf {
|
|
gadget_path.join("functions").join(self.name())
|
|
}
|
|
|
|
/// Get expected device path (e.g., /dev/hidg0)
|
|
pub fn device_path(&self) -> PathBuf {
|
|
PathBuf::from(format!("/dev/hidg{}", self.instance))
|
|
}
|
|
}
|
|
|
|
impl GadgetFunction for HidFunction {
|
|
fn name(&self) -> &str {
|
|
&self.name
|
|
}
|
|
|
|
fn endpoints_required(&self) -> u8 {
|
|
self.func_type.endpoints()
|
|
}
|
|
|
|
fn meta(&self) -> FunctionMeta {
|
|
FunctionMeta {
|
|
name: self.name().to_string(),
|
|
description: self.func_type.description().to_string(),
|
|
endpoints: self.endpoints_required(),
|
|
enabled: true,
|
|
}
|
|
}
|
|
|
|
fn create(&self, gadget_path: &Path) -> Result<()> {
|
|
let func_path = self.function_path(gadget_path);
|
|
create_dir(&func_path)?;
|
|
|
|
// Set HID parameters
|
|
write_file(&func_path.join("protocol"), &self.func_type.protocol().to_string())?;
|
|
write_file(&func_path.join("subclass"), &self.func_type.subclass().to_string())?;
|
|
write_file(&func_path.join("report_length"), &self.func_type.report_length().to_string())?;
|
|
|
|
// For keyboard, enable OUT endpoint for LED feedback
|
|
// no_out_endpoint: 0 = enable OUT endpoint, 1 = disable
|
|
if matches!(self.func_type, HidFunctionType::Keyboard) {
|
|
let no_out_path = func_path.join("no_out_endpoint");
|
|
if no_out_path.exists() || func_path.exists() {
|
|
// Try to write, ignore error if file doesn't exist yet
|
|
let _ = write_file(&no_out_path, "0");
|
|
}
|
|
}
|
|
|
|
// Write report descriptor
|
|
write_bytes(&func_path.join("report_desc"), self.func_type.report_desc())?;
|
|
|
|
debug!("Created HID function: {} at {}", self.name(), func_path.display());
|
|
Ok(())
|
|
}
|
|
|
|
fn link(&self, config_path: &Path, gadget_path: &Path) -> Result<()> {
|
|
let func_path = self.function_path(gadget_path);
|
|
let link_path = config_path.join(self.name());
|
|
|
|
if !link_path.exists() {
|
|
create_symlink(&func_path, &link_path)?;
|
|
debug!("Linked HID function {} to config", self.name());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn unlink(&self, config_path: &Path) -> Result<()> {
|
|
let link_path = config_path.join(self.name());
|
|
remove_file(&link_path)?;
|
|
debug!("Unlinked HID function {}", self.name());
|
|
Ok(())
|
|
}
|
|
|
|
fn cleanup(&self, gadget_path: &Path) -> Result<()> {
|
|
let func_path = self.function_path(gadget_path);
|
|
remove_dir(&func_path)?;
|
|
debug!("Cleaned up HID function {}", self.name());
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_hid_function_types() {
|
|
assert_eq!(HidFunctionType::Keyboard.endpoints(), 2);
|
|
assert_eq!(HidFunctionType::MouseRelative.endpoints(), 1);
|
|
assert_eq!(HidFunctionType::MouseAbsolute.endpoints(), 1);
|
|
|
|
assert_eq!(HidFunctionType::Keyboard.report_length(), 8);
|
|
assert_eq!(HidFunctionType::MouseRelative.report_length(), 4);
|
|
assert_eq!(HidFunctionType::MouseAbsolute.report_length(), 6);
|
|
}
|
|
|
|
#[test]
|
|
fn test_hid_function_names() {
|
|
let kb = HidFunction::keyboard(0);
|
|
assert_eq!(kb.name(), "hid.usb0");
|
|
assert_eq!(kb.device_path(), PathBuf::from("/dev/hidg0"));
|
|
|
|
let mouse = HidFunction::mouse_relative(1);
|
|
assert_eq!(mouse.name(), "hid.usb1");
|
|
}
|
|
}
|