Files
One-KVM/docs/modules/hid.md
mofeng-git cb7d9882a2 feat(hid): 添加 Consumer Control 多媒体按键和多平台键盘布局
- 新增 Consumer Control HID 支持(播放/暂停、音量控制等)
- 虚拟键盘支持 Windows/Mac/Android 三种布局切换
- 移除键盘 LED 反馈以节省 USB 端点(从 2 减至 1)
- InfoBar 优化:按键名称友好显示,移除未实现的 Num/Scroll 指示器
- 更新 HID 模块文档
2026-01-02 23:52:12 +08:00

23 KiB
Raw Blame History

HID 模块文档

1. 模块概述

HID (Human Interface Device) 模块负责将键盘和鼠标事件转发到目标计算机,是 One-KVM 实现远程控制的核心模块。

1.1 主要功能

  • 键盘事件处理 (按键、修饰键)
  • 鼠标事件处理 (移动、点击、滚轮)
  • 多媒体键支持 (Consumer Control)
  • 支持绝对和相对鼠标模式
  • 多后端支持 (OTG、CH9329)
  • WebSocket 和 DataChannel 输入

1.2 USB Endpoint 使用

OTG 模式下的 endpoint 分配:

功能 IN 端点 OUT 端点 说明
Keyboard 1 0 无 LED 反馈
MouseRelative 1 0 相对鼠标
MouseAbsolute 1 0 绝对鼠标
ConsumerControl 1 0 多媒体键
HID 总计 4 0
MSD 1 1 大容量存储
全部总计 5 1 兼容 6 endpoint 设备

EP0 (控制端点) 独立于数据端点,不计入上述统计。

1.3 文件结构

src/hid/
├── mod.rs              # HidController (16KB)
├── backend.rs          # 后端抽象
├── otg.rs              # OTG 后端 (33KB)
├── ch9329.rs           # CH9329 串口后端 (46KB)
├── consumer.rs         # Consumer Control 常量定义
├── keymap.rs           # 按键映射 (14KB)
├── types.rs            # 类型定义
├── monitor.rs          # 健康监视 (14KB)
├── datachannel.rs      # DataChannel 适配 (8KB)
└── websocket.rs        # WebSocket 适配 (6KB)

2. 架构设计

2.1 整体架构

┌─────────────────────────────────────────────────────────────────────────────┐
│                          HID Architecture                                    │
└─────────────────────────────────────────────────────────────────────────────┘

           Browser Input Events
                   │
         ┌─────────┴─────────┐
         │                   │
         ▼                   ▼
┌─────────────────┐  ┌─────────────────┐
│   WebSocket     │  │  DataChannel    │
│   Handler       │  │   Handler       │
│ (websocket.rs)  │  │(datachannel.rs) │
└────────┬────────┘  └────────┬────────┘
         │                    │
         └──────────┬─────────┘
                    │
                    ▼
         ┌─────────────────────┐
         │   HidController     │
         │     (mod.rs)        │
         │  - send_keyboard()  │
         │  - send_mouse()     │
         │  - select_backend() │
         └──────────┬──────────┘
                    │
         ┌──────────┼──────────┐
         │          │          │
         ▼          ▼          ▼
┌─────────────┐ ┌──────────┐ ┌──────────┐
│  OTG Backend│ │  CH9329  │ │   None   │
│  (otg.rs)   │ │ Backend  │ │ (dummy)  │
└──────┬──────┘ └────┬─────┘ └──────────┘
       │             │
       ▼             ▼
┌─────────────┐ ┌──────────┐
│ /dev/hidg*  │ │ Serial   │
│ USB Gadget  │ │ Port     │
└─────────────┘ └──────────┘
       │             │
       └──────┬──────┘
              │
              ▼
      ┌─────────────┐
      │  Target PC  │
      └─────────────┘

2.2 后端选择

┌─────────────────────────────────────────────────────────────────────────────┐
│                        Backend Selection                                     │
└─────────────────────────────────────────────────────────────────────────────┘

HidBackendType::Otg
    │
    ├── 检查 OtgService 是否可用
    │
    ├── 请求 HID 函数 (4个设备, 共4个IN端点)
    │   ├── /dev/hidg0 (键盘, 1 IN)
    │   ├── /dev/hidg1 (相对鼠标, 1 IN)
    │   ├── /dev/hidg2 (绝对鼠标, 1 IN)
    │   └── /dev/hidg3 (Consumer Control, 1 IN)
    │
    └── 创建 OtgHidBackend

HidBackendType::Ch9329 { port, baud_rate }
    │
    ├── 打开串口设备
    │
    ├── 初始化 CH9329 芯片
    │
    └── 创建 Ch9329HidBackend

HidBackendType::None
    │
    └── 创建空后端 (丢弃所有事件)

3. 核心组件

3.1 HidController (mod.rs)

HID 控制器主类,统一管理所有 HID 操作。

pub struct HidController {
    /// 当前后端
    backend: Arc<RwLock<Box<dyn HidBackend>>>,

    /// 后端类型
    backend_type: Arc<RwLock<HidBackendType>>,

    /// OTG 服务引用
    otg_service: Arc<OtgService>,

    /// 健康监视器
    monitor: Arc<HidHealthMonitor>,

    /// 配置
    config: Arc<RwLock<HidConfig>>,

    /// 事件总线
    events: Arc<EventBus>,

    /// 鼠标模式
    mouse_mode: Arc<RwLock<MouseMode>>,
}

impl HidController {
    /// 初始化控制器
    pub async fn init(
        otg_service: Arc<OtgService>,
        config: &HidConfig,
        events: Arc<EventBus>,
    ) -> Result<Arc<Self>>;

    /// 发送键盘事件
    pub async fn send_keyboard(&self, event: &KeyboardEvent) -> Result<()>;

    /// 发送鼠标事件
    pub async fn send_mouse(&self, event: &MouseEvent) -> Result<()>;

    /// 发送多媒体键事件 (Consumer Control)
    pub async fn send_consumer(&self, event: &ConsumerEvent) -> Result<()>;

    /// 设置鼠标模式
    pub fn set_mouse_mode(&self, mode: MouseMode);

    /// 获取鼠标模式
    pub fn get_mouse_mode(&self) -> MouseMode;

    /// 重新加载配置
    pub async fn reload(&self, config: &HidConfig) -> Result<()>;

    /// 重置 HID 状态
    pub async fn reset(&self) -> Result<()>;

    /// 获取状态信息
    pub fn info(&self) -> HidInfo;
}

pub struct HidInfo {
    pub backend: String,
    pub initialized: bool,
    pub keyboard_connected: bool,
    pub mouse_connected: bool,
    pub mouse_mode: MouseMode,
    pub error: Option<String>,
}

3.2 HidBackend Trait (backend.rs)

#[async_trait]
pub trait HidBackend: Send + Sync {
    /// 发送键盘事件
    async fn send_keyboard(&self, event: &KeyboardEvent) -> Result<()>;

    /// 发送鼠标事件
    async fn send_mouse(&self, event: &MouseEvent, mode: MouseMode) -> Result<()>;

    /// 重置状态
    async fn reset(&self) -> Result<()>;

    /// 获取后端信息
    fn info(&self) -> HidBackendInfo;

    /// 检查连接状态
    fn is_connected(&self) -> bool;
}

pub struct HidBackendInfo {
    pub name: String,
    pub backend_type: HidBackendType,
    pub keyboard_connected: bool,
    pub mouse_connected: bool,
}

#[derive(Clone, Debug)]
pub enum HidBackendType {
    /// USB OTG gadget 模式
    Otg,

    /// CH9329 串口 HID 控制器
    Ch9329 {
        port: String,
        baud_rate: u32,
    },

    /// 禁用 HID
    None,
}

3.3 OTG 后端 (otg.rs)

通过 Linux USB OTG gadget 模拟 HID 设备。

pub struct OtgHidBackend {
    /// HID 设备路径
    paths: HidDevicePaths,

    /// 键盘设备文件
    keyboard_fd: RwLock<Option<File>>,

    /// 相对鼠标设备文件
    mouse_rel_fd: RwLock<Option<File>>,

    /// 绝对鼠标设备文件
    mouse_abs_fd: RwLock<Option<File>>,

    /// 当前键盘状态
    keyboard_state: Mutex<KeyboardState>,

    /// OTG 服务引用
    otg_service: Arc<OtgService>,
}

impl OtgHidBackend {
    /// 创建 OTG 后端
    pub async fn new(otg_service: Arc<OtgService>) -> Result<Self>;

    /// 打开 HID 设备
    async fn open_devices(&self) -> Result<()>;

    /// 关闭 HID 设备
    async fn close_devices(&self);

    /// 写入键盘报告
    fn write_keyboard_report(&self, report: &KeyboardReport) -> Result<()>;

    /// 写入鼠标报告
    fn write_mouse_report(&self, report: &[u8], absolute: bool) -> Result<()>;
}

pub struct HidDevicePaths {
    pub keyboard: PathBuf,       // /dev/hidg0
    pub mouse_relative: PathBuf, // /dev/hidg1
    pub mouse_absolute: PathBuf, // /dev/hidg2
}

HID 报告格式

/// 键盘报告 (8 字节)
#[repr(C, packed)]
pub struct KeyboardReport {
    pub modifiers: u8,    // Ctrl, Shift, Alt, GUI
    pub reserved: u8,     // 保留
    pub keys: [u8; 6],    // 最多 6 个按键 scancode
}

/// 相对鼠标报告 (4 字节)
#[repr(C, packed)]
pub struct MouseRelativeReport {
    pub buttons: u8,      // 按钮状态
    pub x: i8,            // X 移动 (-127 ~ 127)
    pub y: i8,            // Y 移动 (-127 ~ 127)
    pub wheel: i8,        // 滚轮 (-127 ~ 127)
}

/// 绝对鼠标报告 (6 字节)
#[repr(C, packed)]
pub struct MouseAbsoluteReport {
    pub buttons: u8,      // 按钮状态
    pub x: u16,           // X 坐标 (0 ~ 32767)
    pub y: u16,           // Y 坐标 (0 ~ 32767)
    pub wheel: i8,        // 滚轮 (-127 ~ 127)
}

/// Consumer Control 报告 (2 字节)
#[repr(C, packed)]
pub struct ConsumerControlReport {
    pub usage: u16,       // Consumer Control Usage Code (LE)
}

// 常用 Consumer Control Usage Codes
pub mod consumer_usage {
    pub const PLAY_PAUSE: u16 = 0x00CD;
    pub const STOP: u16 = 0x00B7;
    pub const NEXT_TRACK: u16 = 0x00B5;
    pub const PREV_TRACK: u16 = 0x00B6;
    pub const MUTE: u16 = 0x00E2;
    pub const VOLUME_UP: u16 = 0x00E9;
    pub const VOLUME_DOWN: u16 = 0x00EA;
}

3.4 CH9329 后端 (ch9329.rs)

通过 CH9329 芯片(串口转 HID实现 HID 功能。

pub struct Ch9329HidBackend {
    /// 串口设备
    port: Mutex<Box<dyn SerialPort>>,

    /// 设备路径
    device_path: String,

    /// 波特率
    baud_rate: u32,

    /// 当前键盘状态
    keyboard_state: Mutex<KeyboardState>,

    /// 连接状态
    connected: AtomicBool,
}

impl Ch9329HidBackend {
    /// 创建 CH9329 后端
    pub fn new(device: &str, baud_rate: u32) -> Result<Self>;

    /// 发送命令
    fn send_command(&self, cmd: &[u8]) -> Result<Vec<u8>>;

    /// 发送键盘数据包
    fn send_keyboard_packet(&self, report: &KeyboardReport) -> Result<()>;

    /// 发送鼠标数据包
    fn send_mouse_packet(&self, report: &[u8], absolute: bool) -> Result<()>;
}

CH9329 协议

帧格式:
┌──────┬──────┬──────┬──────────┬──────────┬──────┐
│ HEAD │ ADDR │ CMD  │ LEN      │ DATA     │ SUM  │
│ 0x57 │ 0xAB │ 0xXX │ data_len │ payload  │ csum │
└──────┴──────┴──────┴──────────┴──────────┴──────┘

命令码:
0x02 - 发送键盘数据
0x04 - 发送绝对鼠标数据
0x05 - 发送相对鼠标数据
0x0E - 获取芯片信息

4. 事件类型

4.1 键盘事件 (types.rs)

pub struct KeyboardEvent {
    /// 按下的键列表
    pub keys: Vec<KeyCode>,

    /// 修饰键状态
    pub modifiers: KeyboardModifiers,
}

#[derive(Default)]
pub struct KeyboardModifiers {
    pub left_ctrl: bool,
    pub left_shift: bool,
    pub left_alt: bool,
    pub left_gui: bool,
    pub right_ctrl: bool,
    pub right_shift: bool,
    pub right_alt: bool,
    pub right_gui: bool,
}

impl KeyboardModifiers {
    /// 转换为 USB HID 修饰符字节
    pub fn to_byte(&self) -> u8 {
        let mut byte = 0u8;
        if self.left_ctrl { byte |= 0x01; }
        if self.left_shift { byte |= 0x02; }
        if self.left_alt { byte |= 0x04; }
        if self.left_gui { byte |= 0x08; }
        if self.right_ctrl { byte |= 0x10; }
        if self.right_shift { byte |= 0x20; }
        if self.right_alt { byte |= 0x40; }
        if self.right_gui { byte |= 0x80; }
        byte
    }
}

4.2 鼠标事件 (types.rs)

pub struct MouseEvent {
    /// 按钮
    pub button: Option<MouseButton>,

    /// 事件类型
    pub event_type: MouseEventType,

    /// 相对移动 X
    pub dx: i16,

    /// 相对移动 Y
    pub dy: i16,

    /// 绝对位置 X (0-32767)
    pub x: u32,

    /// 绝对位置 Y (0-32767)
    pub y: u32,

    /// 滚轮移动
    pub wheel: i8,
}

pub enum MouseButton {
    Left,
    Right,
    Middle,
    Button4,
    Button5,
}

pub enum MouseEventType {
    Press,
    Release,
    Move,
    Wheel,
}

pub enum MouseMode {
    /// 相对模式 (用于普通操作)
    Relative,

    /// 绝对模式 (用于 BIOS/精确定位)
    Absolute,
}

4.3 Consumer Control 事件 (types.rs)

/// Consumer Control 事件 (多媒体键)
pub struct ConsumerEvent {
    /// USB HID Consumer Control Usage Code
    pub usage: u16,
}

// 常用 Usage Codes (参考 USB HID Usage Tables)
// 0x00CD - Play/Pause
// 0x00B7 - Stop
// 0x00B5 - Next Track
// 0x00B6 - Previous Track
// 0x00E2 - Mute
// 0x00E9 - Volume Up
// 0x00EA - Volume Down

5. 按键映射

5.1 KeyCode 枚举 (keymap.rs)

pub enum KeyCode {
    // 字母键
    KeyA, KeyB, KeyC, /* ... */ KeyZ,

    // 数字键
    Digit1, Digit2, /* ... */ Digit0,

    // 功能键
    F1, F2, /* ... */ F12,

    // 控制键
    Escape, Tab, CapsLock, Space, Enter, Backspace,
    Insert, Delete, Home, End, PageUp, PageDown,

    // 方向键
    ArrowUp, ArrowDown, ArrowLeft, ArrowRight,

    // 修饰键
    ShiftLeft, ShiftRight,
    ControlLeft, ControlRight,
    AltLeft, AltRight,
    MetaLeft, MetaRight,

    // 小键盘
    Numpad0, Numpad1, /* ... */ Numpad9,
    NumpadAdd, NumpadSubtract, NumpadMultiply, NumpadDivide,
    NumpadEnter, NumpadDecimal, NumLock,

    // 其他
    PrintScreen, ScrollLock, Pause,
    /* ... */
}

impl KeyCode {
    /// 转换为 USB HID scancode
    pub fn to_scancode(&self) -> u8;

    /// 从 JavaScript keyCode 转换
    pub fn from_js_code(code: &str) -> Option<Self>;

    /// 是否为修饰键
    pub fn is_modifier(&self) -> bool;
}

5.2 JavaScript 键码映射

// 前端发送的格式
{
    "type": "keyboard",
    "keys": ["KeyA", "KeyB"],
    "modifiers": {
        "ctrl": false,
        "shift": true,
        "alt": false,
        "meta": false
    }
}

6. 输入处理器

6.1 WebSocket Handler (websocket.rs)

pub struct WsHidHandler {
    hid: Arc<HidController>,
}

impl WsHidHandler {
    pub fn new(hid: Arc<HidController>) -> Self;

    /// 处理 WebSocket 消息
    pub async fn handle_message(&self, msg: &str) -> Result<()> {
        let event: HidMessage = serde_json::from_str(msg)?;

        match event {
            HidMessage::Keyboard(kb) => {
                self.hid.send_keyboard(&kb).await?;
            }
            HidMessage::Mouse(mouse) => {
                self.hid.send_mouse(&mouse).await?;
            }
            HidMessage::SetMouseMode(mode) => {
                self.hid.set_mouse_mode(mode);
            }
        }

        Ok(())
    }
}

#[derive(Deserialize)]
#[serde(tag = "type")]
pub enum HidMessage {
    #[serde(rename = "keyboard")]
    Keyboard(KeyboardEvent),

    #[serde(rename = "mouse")]
    Mouse(MouseEvent),

    #[serde(rename = "mouse_mode")]
    SetMouseMode(MouseMode),
}

6.2 DataChannel Handler (datachannel.rs)

用于 WebRTC 模式下的 HID 事件处理,使用二进制协议。

二进制消息格式

消息类型常量:
MSG_KEYBOARD = 0x01  // 键盘事件
MSG_MOUSE    = 0x02  // 鼠标事件
MSG_CONSUMER = 0x03  // Consumer Control 事件

键盘消息 (4 字节):
┌──────────┬──────────┬──────────┬──────────┐
│ MSG_TYPE │ EVENT    │ KEY_CODE │ MODIFIER │
│  0x01    │ 0/1      │ scancode │ bitmask  │
└──────────┴──────────┴──────────┴──────────┘
EVENT: 0=keydown, 1=keyup

鼠标消息 (7 字节):
┌──────────┬──────────┬──────────┬──────────┬──────────┐
│ MSG_TYPE │ EVENT    │ X (i16)  │ Y (i16)  │ BTN/SCRL │
│  0x02    │ 0-4      │ LE       │ LE       │ u8/i8    │
└──────────┴──────────┴──────────┴──────────┴──────────┘
EVENT: 0=move, 1=moveabs, 2=down, 3=up, 4=scroll

Consumer Control 消息 (3 字节):
┌──────────┬──────────────────────┐
│ MSG_TYPE │ USAGE CODE (u16 LE)  │
│  0x03    │ e.g. 0x00CD          │
└──────────┴──────────────────────┘
pub struct HidDataChannelHandler {
    hid: Arc<HidController>,
}

impl HidDataChannelHandler {
    pub fn new(hid: Arc<HidController>) -> Self;

    /// 处理 DataChannel 消息
    pub async fn handle_message(&self, data: &[u8]) -> Result<()>;

    /// 创建 DataChannel 配置
    pub fn datachannel_config() -> RTCDataChannelInit;
}

7. 健康监视

7.1 HidHealthMonitor (monitor.rs)

pub struct HidHealthMonitor {
    /// 错误计数
    error_count: AtomicU32,

    /// 连续错误计数
    consecutive_errors: AtomicU32,

    /// 最后错误时间
    last_error: RwLock<Option<Instant>>,

    /// 最后错误消息
    last_error_msg: RwLock<Option<String>>,

    /// 重试配置
    config: MonitorConfig,
}

impl HidHealthMonitor {
    /// 记录错误
    pub fn record_error(&self, error: &str);

    /// 记录成功
    pub fn record_success(&self);

    /// 是否应该重试
    pub fn should_retry(&self) -> bool;

    /// 是否需要重新初始化
    pub fn needs_reinit(&self) -> bool;

    /// 获取健康状态
    pub fn health_status(&self) -> HealthStatus;
}

pub enum HealthStatus {
    Healthy,
    Degraded { error_rate: f32 },
    Unhealthy { consecutive_errors: u32 },
}

8. 配置

8.1 HID 配置结构

#[derive(Serialize, Deserialize)]
#[typeshare]
pub struct HidConfig {
    /// 后端类型
    pub backend: HidBackendType,

    /// CH9329 设备路径 (如果使用 CH9329)
    pub ch9329_device: Option<String>,

    /// CH9329 波特率
    pub ch9329_baud_rate: Option<u32>,

    /// 默认鼠标模式
    pub default_mouse_mode: MouseMode,

    /// 鼠标灵敏度 (1-10)
    pub mouse_sensitivity: u8,

    /// 启用滚轮
    pub enable_wheel: bool,
}

impl Default for HidConfig {
    fn default() -> Self {
        Self {
            backend: HidBackendType::Otg,
            ch9329_device: None,
            ch9329_baud_rate: Some(9600),
            default_mouse_mode: MouseMode::Absolute,
            mouse_sensitivity: 5,
            enable_wheel: true,
        }
    }
}

9. API 端点

端点 方法 描述
/api/hid/status GET 获取 HID 状态
/api/hid/reset POST 重置 HID 状态
/api/hid/keyboard POST 发送键盘事件
/api/hid/mouse POST 发送鼠标事件
/api/hid/mouse/mode GET 获取鼠标模式
/api/hid/mouse/mode POST 设置鼠标模式

响应格式

// GET /api/hid/status
{
    "backend": "otg",
    "initialized": true,
    "keyboard_connected": true,
    "mouse_connected": true,
    "mouse_mode": "absolute",
    "error": null
}

10. 事件

pub enum SystemEvent {
    HidStateChanged {
        backend: String,
        initialized: bool,
        keyboard_connected: bool,
        mouse_connected: bool,
        mouse_mode: String,
        error: Option<String>,
    },
}

11. 错误处理

#[derive(Debug, thiserror::Error)]
pub enum HidError {
    #[error("Backend not initialized")]
    NotInitialized,

    #[error("Device not found: {0}")]
    DeviceNotFound(String),

    #[error("Device busy: {0}")]
    DeviceBusy(String),

    #[error("Write error: {0}")]
    WriteError(String),

    #[error("Serial port error: {0}")]
    SerialError(String),

    #[error("Invalid key code: {0}")]
    InvalidKeyCode(String),

    #[error("OTG service error: {0}")]
    OtgError(String),
}

12. 使用示例

12.1 初始化 HID 控制器

let otg_service = Arc::new(OtgService::new()?);
let events = Arc::new(EventBus::new());

let hid = HidController::init(
    otg_service,
    &HidConfig::default(),
    events,
).await?;

12.2 发送键盘事件

// 按下 Ctrl+C
hid.send_keyboard(&KeyboardEvent {
    keys: vec![KeyCode::KeyC],
    modifiers: KeyboardModifiers {
        left_ctrl: true,
        ..Default::default()
    },
}).await?;

// 释放所有键
hid.send_keyboard(&KeyboardEvent {
    keys: vec![],
    modifiers: KeyboardModifiers::default(),
}).await?;

12.3 发送鼠标事件

// 移动鼠标到绝对位置
hid.send_mouse(&MouseEvent {
    button: None,
    event_type: MouseEventType::Move,
    dx: 0,
    dy: 0,
    x: 16384,  // 屏幕中心
    y: 16384,
    wheel: 0,
}).await?;

// 点击左键
hid.send_mouse(&MouseEvent {
    button: Some(MouseButton::Left),
    event_type: MouseEventType::Press,
    ..Default::default()
}).await?;

hid.send_mouse(&MouseEvent {
    button: Some(MouseButton::Left),
    event_type: MouseEventType::Release,
    ..Default::default()
}).await?;

13. 常见问题

Q: OTG 模式下键盘/鼠标不工作?

  1. 检查 /dev/hidg* 设备是否存在
  2. 检查 USB gadget 是否正确配置
  3. 检查目标 PC 是否识别 USB 设备
  4. 查看 dmesg 日志

Q: CH9329 无法初始化?

  1. 检查串口设备路径
  2. 检查波特率设置
  3. 使用 minicom 测试串口连接

Q: 鼠标定位不准确?

  1. 使用绝对鼠标模式
  2. 校准屏幕分辨率
  3. 检查缩放设置

Q: 按键有延迟?

  1. 检查网络延迟
  2. 使用 WebRTC 模式
  3. 减少中间代理