mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-31 18:11:54 +08:00
feat(hid): 添加 Consumer Control 多媒体按键和多平台键盘布局
- 新增 Consumer Control HID 支持(播放/暂停、音量控制等) - 虚拟键盘支持 Windows/Mac/Android 三种布局切换 - 移除键盘 LED 反馈以节省 USB 端点(从 2 减至 1) - InfoBar 优化:按键名称友好显示,移除未实现的 Num/Scroll 指示器 - 更新 HID 模块文档
This commit is contained in:
@@ -5,14 +5,14 @@ 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 super::report_desc::{CONSUMER_CONTROL, KEYBOARD, 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 (no LED feedback)
|
||||
/// Uses 1 endpoint: IN
|
||||
Keyboard,
|
||||
/// Relative mouse (traditional mouse movement)
|
||||
/// Uses 1 endpoint: IN
|
||||
@@ -20,33 +20,39 @@ pub enum HidFunctionType {
|
||||
/// Absolute mouse (touchscreen-like positioning)
|
||||
/// Uses 1 endpoint: IN
|
||||
MouseAbsolute,
|
||||
/// Consumer control (multimedia keys)
|
||||
/// Uses 1 endpoint: IN
|
||||
ConsumerControl,
|
||||
}
|
||||
|
||||
impl HidFunctionType {
|
||||
/// Get endpoints required for this function type
|
||||
pub fn endpoints(&self) -> u8 {
|
||||
match self {
|
||||
HidFunctionType::Keyboard => 2, // IN + OUT for LED
|
||||
HidFunctionType::Keyboard => 1,
|
||||
HidFunctionType::MouseRelative => 1,
|
||||
HidFunctionType::MouseAbsolute => 1,
|
||||
HidFunctionType::ConsumerControl => 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get HID protocol
|
||||
pub fn protocol(&self) -> u8 {
|
||||
match self {
|
||||
HidFunctionType::Keyboard => 1, // Keyboard
|
||||
HidFunctionType::MouseRelative => 2, // Mouse
|
||||
HidFunctionType::MouseAbsolute => 2, // Mouse
|
||||
HidFunctionType::Keyboard => 1, // Keyboard
|
||||
HidFunctionType::MouseRelative => 2, // Mouse
|
||||
HidFunctionType::MouseAbsolute => 2, // Mouse
|
||||
HidFunctionType::ConsumerControl => 0, // None
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
HidFunctionType::Keyboard => 1, // Boot interface
|
||||
HidFunctionType::MouseRelative => 1, // Boot interface
|
||||
HidFunctionType::MouseAbsolute => 0, // No boot interface
|
||||
HidFunctionType::ConsumerControl => 0, // No boot interface
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,15 +62,17 @@ impl HidFunctionType {
|
||||
HidFunctionType::Keyboard => 8,
|
||||
HidFunctionType::MouseRelative => 4,
|
||||
HidFunctionType::MouseAbsolute => 6,
|
||||
HidFunctionType::ConsumerControl => 2,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get report descriptor
|
||||
pub fn report_desc(&self) -> &'static [u8] {
|
||||
match self {
|
||||
HidFunctionType::Keyboard => KEYBOARD_WITH_LED,
|
||||
HidFunctionType::Keyboard => KEYBOARD,
|
||||
HidFunctionType::MouseRelative => MOUSE_RELATIVE,
|
||||
HidFunctionType::MouseAbsolute => MOUSE_ABSOLUTE,
|
||||
HidFunctionType::ConsumerControl => CONSUMER_CONTROL,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +82,7 @@ impl HidFunctionType {
|
||||
HidFunctionType::Keyboard => "Keyboard",
|
||||
HidFunctionType::MouseRelative => "Relative Mouse",
|
||||
HidFunctionType::MouseAbsolute => "Absolute Mouse",
|
||||
HidFunctionType::ConsumerControl => "Consumer Control",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,6 +126,15 @@ impl HidFunction {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a consumer control function
|
||||
pub fn consumer_control(instance: u8) -> Self {
|
||||
Self {
|
||||
instance,
|
||||
func_type: HidFunctionType::ConsumerControl,
|
||||
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())
|
||||
@@ -155,16 +173,6 @@ impl GadgetFunction for HidFunction {
|
||||
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())?;
|
||||
|
||||
@@ -205,7 +213,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_hid_function_types() {
|
||||
assert_eq!(HidFunctionType::Keyboard.endpoints(), 2);
|
||||
assert_eq!(HidFunctionType::Keyboard.endpoints(), 1);
|
||||
assert_eq!(HidFunctionType::MouseRelative.endpoints(), 1);
|
||||
assert_eq!(HidFunctionType::MouseAbsolute.endpoints(), 1);
|
||||
|
||||
|
||||
@@ -118,6 +118,15 @@ impl OtgGadgetManager {
|
||||
Ok(device_path)
|
||||
}
|
||||
|
||||
/// Add consumer control function (multimedia keys)
|
||||
pub fn add_consumer_control(&mut self) -> Result<PathBuf> {
|
||||
let func = HidFunction::consumer_control(self.hid_instance);
|
||||
let device_path = func.device_path();
|
||||
self.add_function(Box::new(func))?;
|
||||
self.hid_instance += 1;
|
||||
Ok(device_path)
|
||||
}
|
||||
|
||||
/// Add MSD function (returns MsdFunction handle for LUN configuration)
|
||||
pub fn add_msd(&mut self) -> Result<MsdFunction> {
|
||||
let func = MsdFunction::new(self.msd_instance);
|
||||
|
||||
@@ -31,5 +31,5 @@ pub use function::{FunctionMeta, GadgetFunction};
|
||||
pub use hid::{HidFunction, HidFunctionType};
|
||||
pub use manager::{wait_for_hid_devices, OtgGadgetManager};
|
||||
pub use msd::{MsdFunction, MsdLunConfig};
|
||||
pub use report_desc::{KEYBOARD_WITH_LED, MOUSE_ABSOLUTE, MOUSE_RELATIVE};
|
||||
pub use report_desc::{KEYBOARD, MOUSE_ABSOLUTE, MOUSE_RELATIVE};
|
||||
pub use service::{HidDevicePaths, OtgService, OtgServiceState};
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
//! HID Report Descriptors
|
||||
|
||||
/// Keyboard HID Report Descriptor with LED output support
|
||||
/// Keyboard HID Report Descriptor (no LED output - saves 1 endpoint)
|
||||
/// Report format (8 bytes input):
|
||||
/// [0] Modifier keys (8 bits)
|
||||
/// [1] Reserved
|
||||
/// [2-7] Key codes (6 keys)
|
||||
/// LED output (1 byte):
|
||||
/// Bit 0: Num Lock
|
||||
/// Bit 1: Caps Lock
|
||||
/// Bit 2: Scroll Lock
|
||||
/// Bit 3: Compose
|
||||
/// Bit 4: Kana
|
||||
pub const KEYBOARD_WITH_LED: &[u8] = &[
|
||||
pub const KEYBOARD: &[u8] = &[
|
||||
0x05, 0x01, // Usage Page (Generic Desktop)
|
||||
0x09, 0x06, // Usage (Keyboard)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
@@ -28,17 +22,6 @@ pub const KEYBOARD_WITH_LED: &[u8] = &[
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x81, 0x01, // Input (Constant) - Reserved byte
|
||||
// LED output (5 bits)
|
||||
0x95, 0x05, // Report Count (5)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x05, 0x08, // Usage Page (LEDs)
|
||||
0x19, 0x01, // Usage Minimum (1) - Num Lock
|
||||
0x29, 0x05, // Usage Maximum (5) - Kana
|
||||
0x91, 0x02, // Output (Data, Variable, Absolute) - LED bits
|
||||
// LED padding (3 bits)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x75, 0x03, // Report Size (3)
|
||||
0x91, 0x01, // Output (Constant) - Padding
|
||||
// Key array (6 bytes)
|
||||
0x95, 0x06, // Report Count (6)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
@@ -147,14 +130,33 @@ pub const MOUSE_ABSOLUTE: &[u8] = &[
|
||||
0xC0, // End Collection
|
||||
];
|
||||
|
||||
/// Consumer Control HID Report Descriptor (2 bytes report)
|
||||
/// Report format:
|
||||
/// [0-1] Consumer Control Usage (16-bit little-endian)
|
||||
/// Supports: Play/Pause, Stop, Next/Prev Track, Mute, Volume Up/Down, etc.
|
||||
pub const CONSUMER_CONTROL: &[u8] = &[
|
||||
0x05, 0x0C, // Usage Page (Consumer)
|
||||
0x09, 0x01, // Usage (Consumer Control)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x03, // Logical Maximum (1023)
|
||||
0x19, 0x00, // Usage Minimum (0)
|
||||
0x2A, 0xFF, 0x03, // Usage Maximum (1023)
|
||||
0x75, 0x10, // Report Size (16)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x00, // Input (Data, Array)
|
||||
0xC0, // End Collection
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_report_descriptor_sizes() {
|
||||
assert!(!KEYBOARD_WITH_LED.is_empty());
|
||||
assert!(!KEYBOARD.is_empty());
|
||||
assert!(!MOUSE_RELATIVE.is_empty());
|
||||
assert!(!MOUSE_ABSOLUTE.is_empty());
|
||||
assert!(!CONSUMER_CONTROL.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ pub struct HidDevicePaths {
|
||||
pub keyboard: PathBuf,
|
||||
pub mouse_relative: PathBuf,
|
||||
pub mouse_absolute: PathBuf,
|
||||
pub consumer: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Default for HidDevicePaths {
|
||||
@@ -47,6 +48,7 @@ impl Default for HidDevicePaths {
|
||||
keyboard: PathBuf::from("/dev/hidg0"),
|
||||
mouse_relative: PathBuf::from("/dev/hidg1"),
|
||||
mouse_absolute: PathBuf::from("/dev/hidg2"),
|
||||
consumer: Some(PathBuf::from("/dev/hidg3")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -353,16 +355,18 @@ impl OtgService {
|
||||
manager.add_keyboard(),
|
||||
manager.add_mouse_relative(),
|
||||
manager.add_mouse_absolute(),
|
||||
manager.add_consumer_control(),
|
||||
) {
|
||||
(Ok(kb), Ok(rel), Ok(abs)) => {
|
||||
(Ok(kb), Ok(rel), Ok(abs), Ok(consumer)) => {
|
||||
hid_paths = Some(HidDevicePaths {
|
||||
keyboard: kb,
|
||||
mouse_relative: rel,
|
||||
mouse_absolute: abs,
|
||||
consumer: Some(consumer),
|
||||
});
|
||||
debug!("HID functions added to gadget");
|
||||
}
|
||||
(Err(e), _, _) | (_, Err(e), _) | (_, _, Err(e)) => {
|
||||
(Err(e), _, _, _) | (_, Err(e), _, _) | (_, _, Err(e), _) | (_, _, _, Err(e)) => {
|
||||
let error = format!("Failed to add HID functions: {}", e);
|
||||
let mut state = self.state.write().await;
|
||||
state.error = Some(error.clone());
|
||||
|
||||
Reference in New Issue
Block a user