Files
One-KVM/docs/modules/hid.md
mofeng-git 0c82d1a840 feat(rustdesk): 完整实现RustDesk协议和P2P连接
重大变更:
- 从prost切换到protobuf 3.4实现完整的RustDesk协议栈
- 新增P2P打洞模块(punch.rs)支持直连和中继回退
- 重构加密系统:临时Curve25519密钥对+Ed25519签名
- 完善HID适配器:支持CapsLock状态同步和修饰键映射
- 添加音频流支持:Opus编码+音频帧适配器
- 优化视频流:改进帧适配器和编码器协商
- 移除pacer.rs简化视频管道

扩展系统:
- 在设置向导中添加扩展步骤(ttyd/rustdesk切换)
- 扩展可用性检测和自动启动
- 新增WebConfig handler用于Web服务器配置

前端改进:
- SetupView增加第4步扩展配置
- 音频设备列表和配置界面
- 新增多语言支持(en-US/zh-CN)
- TypeScript类型生成更新

文档:
- 更新系统架构文档
- 完善config/hid/rustdesk/video/webrtc模块文档
2026-01-03 19:34:07 +08:00

1154 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 | 1 | 带 LED 反馈 |
| MouseRelative | 1 | 0 | 相对鼠标 |
| MouseAbsolute | 1 | 0 | 绝对鼠标 |
| ConsumerControl | 1 | 0 | 多媒体键 |
| **HID 总计** | **4** | **1** | |
| MSD | 1 | 1 | 大容量存储 |
| **全部总计** | **5** | **2** | 兼容 6 endpoint 设备 |
> EP0 (控制端点) 独立于数据端点,不计入上述统计。
### 1.3 文件结构
```
src/hid/
├── mod.rs # HidController 主控制器
├── backend.rs # HidBackend trait 和 HidBackendType
├── otg.rs # OTG USB Gadget 后端实现
├── ch9329.rs # CH9329 串口 HID 控制器后端
├── consumer.rs # Consumer Control usage codes
├── keymap.rs # JS keyCode 到 USB HID 的转换表
├── types.rs # 事件类型定义 (KeyboardEvent, MouseEvent等)
├── monitor.rs # 健康监视器 (HidHealthMonitor)
├── datachannel.rs # DataChannel 二进制协议解析
└── websocket.rs # WebSocket 二进制协议适配
```
---
## 2. 架构设计
### 2.1 整体架构
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ HID Architecture │
└─────────────────────────────────────────────────────────────────────────────┘
Browser Input Events
┌─────────┴─────────┐
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ WebSocket │ │ DataChannel │
│ Handler │ │ Handler │
│ (websocket.rs) │ │(datachannel.rs) │
└────────┬────────┘ └────────┬────────┘
│ │
└──────────┬─────────┘
┌─────────────────────┐
│ HidController │
│ (mod.rs) │
│ - send_keyboard() │
│ - send_mouse() │
│ - send_consumer() │
│ - monitor (health) │
└──────────┬──────────┘
┌──────────┼──────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌──────────┐ ┌──────────┐
│ 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端点, 1个OUT端点)
│ ├── /dev/hidg0 (键盘, 1 IN, 1 OUT for LED)
│ ├── /dev/hidg1 (相对鼠标, 1 IN)
│ ├── /dev/hidg2 (绝对鼠标, 1 IN)
│ └── /dev/hidg3 (Consumer Control, 1 IN)
└── 创建 OtgBackend (从 HidDevicePaths)
HidBackendType::Ch9329 { port, baud_rate }
├── 打开串口设备
├── 初始化 CH9329 芯片
└── 创建 Ch9329Backend
HidBackendType::None
└── 不创建后端 (HID 功能禁用)
```
---
## 3. 核心组件
### 3.1 HidController (mod.rs)
HID 控制器主类,统一管理所有 HID 操作。
```rust
pub struct HidController {
/// OTG Service reference (only used when backend is OTG)
otg_service: Option<Arc<OtgService>>,
/// Active backend
backend: Arc<RwLock<Option<Box<dyn HidBackend>>>>,
/// Backend type (mutable for reload)
backend_type: RwLock<HidBackendType>,
/// Event bus for broadcasting state changes (optional)
events: RwLock<Option<Arc<EventBus>>>,
/// Health monitor for error tracking and recovery
monitor: Arc<HidHealthMonitor>,
}
impl HidController {
/// Create a new HID controller with specified backend
pub fn new(backend_type: HidBackendType, otg_service: Option<Arc<OtgService>>) -> Self;
/// Set event bus for broadcasting state changes
pub async fn set_event_bus(&self, events: Arc<EventBus>);
/// Initialize the HID backend
pub async fn init(&self) -> Result<()>;
/// Shutdown the HID backend and release resources
pub async fn shutdown(&self) -> Result<()>;
/// Send keyboard event
pub async fn send_keyboard(&self, event: KeyboardEvent) -> Result<()>;
/// Send mouse event
pub async fn send_mouse(&self, event: MouseEvent) -> Result<()>;
/// Send consumer control event (multimedia keys)
pub async fn send_consumer(&self, event: ConsumerEvent) -> Result<()>;
/// Reset all keys (release all pressed keys)
pub async fn reset(&self) -> Result<()>;
/// Check if backend is available
pub async fn is_available(&self) -> bool;
/// Get backend type
pub async fn backend_type(&self) -> HidBackendType;
/// Get backend info
pub async fn info(&self) -> Option<HidInfo>;
/// Get current state as SystemEvent
pub async fn current_state_event(&self) -> SystemEvent;
/// Get the health monitor reference
pub fn monitor(&self) -> &Arc<HidHealthMonitor>;
/// Get current health status
pub async fn health_status(&self) -> HidHealthStatus;
/// Check if the HID backend is healthy
pub async fn is_healthy(&self) -> bool;
/// Reload the HID backend with new type
pub async fn reload(&self, new_backend_type: HidBackendType) -> Result<()>;
}
pub struct HidInfo {
/// Backend name
pub name: &'static str,
/// Whether backend is initialized
pub initialized: bool,
/// Whether absolute mouse positioning is supported
pub supports_absolute_mouse: bool,
/// Screen resolution for absolute mouse
pub screen_resolution: Option<(u32, u32)>,
}
```
### 3.2 HidBackend Trait (backend.rs)
```rust
#[async_trait]
pub trait HidBackend: Send + Sync {
/// Get backend name
fn name(&self) -> &'static str;
/// Initialize the backend
async fn init(&self) -> Result<()>;
/// Send a keyboard event
async fn send_keyboard(&self, event: KeyboardEvent) -> Result<()>;
/// Send a mouse event
async fn send_mouse(&self, event: MouseEvent) -> Result<()>;
/// Send a consumer control event (multimedia keys)
async fn send_consumer(&self, event: ConsumerEvent) -> Result<()>;
/// Reset all inputs (release all keys/buttons)
async fn reset(&self) -> Result<()>;
/// Shutdown the backend
async fn shutdown(&self) -> Result<()>;
/// Check if backend supports absolute mouse positioning
fn supports_absolute_mouse(&self) -> bool;
/// Get screen resolution (for absolute mouse)
fn screen_resolution(&self) -> Option<(u32, u32)>;
/// Set screen resolution (for absolute mouse)
fn set_screen_resolution(&mut self, width: u32, height: u32);
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum HidBackendType {
/// USB OTG gadget mode
Otg,
/// CH9329 serial HID controller
Ch9329 {
port: String,
baud_rate: u32,
},
/// No HID backend (disabled)
None,
}
impl HidBackendType {
/// Check if OTG backend is available on this system
pub fn otg_available() -> bool;
/// Detect the best available backend
pub fn detect() -> Self;
/// Get backend name as string
pub fn name_str(&self) -> &str;
}
```
### 3.3 OTG 后端 (otg.rs)
通过 Linux USB OTG gadget 模拟 HID 设备。
```rust
pub struct OtgBackend {
/// Keyboard device path (/dev/hidg0)
keyboard_path: PathBuf,
/// Relative mouse device path (/dev/hidg1)
mouse_rel_path: PathBuf,
/// Absolute mouse device path (/dev/hidg2)
mouse_abs_path: PathBuf,
/// Consumer control device path (/dev/hidg3)
consumer_path: PathBuf,
/// Keyboard device file
keyboard_dev: Mutex<Option<File>>,
/// Relative mouse device file
mouse_rel_dev: Mutex<Option<File>>,
/// Absolute mouse device file
mouse_abs_dev: Mutex<Option<File>>,
/// Consumer control device file
consumer_dev: Mutex<Option<File>>,
/// Current keyboard state
keyboard_state: Mutex<KeyboardReport>,
/// Current mouse button state
mouse_buttons: AtomicU8,
/// Last known LED state
led_state: RwLock<LedState>,
/// Screen resolution for absolute mouse
screen_resolution: RwLock<Option<(u32, u32)>>,
/// UDC name for state checking
udc_name: RwLock<Option<String>>,
/// Whether the device is currently online
online: AtomicBool,
/// Error tracking (for log throttling)
last_error_log: Mutex<Instant>,
error_count: AtomicU8,
eagain_count: AtomicU8,
}
impl OtgBackend {
/// Create OTG backend from device paths provided by OtgService
pub fn from_handles(paths: HidDevicePaths) -> Result<Self>;
/// Set the UDC name for state checking
pub fn set_udc_name(&self, udc: &str);
/// Check if the UDC is in "configured" state
pub fn is_udc_configured(&self) -> bool;
/// Check if device is online
pub fn is_online(&self) -> bool;
/// Read keyboard LED state (non-blocking)
pub fn read_led_state(&self) -> Result<Option<LedState>>;
/// Get last known LED state
pub fn led_state(&self) -> LedState;
}
/// Keyboard LED state
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct LedState {
pub num_lock: bool,
pub caps_lock: bool,
pub scroll_lock: bool,
pub compose: bool,
pub kana: bool,
}
impl LedState {
/// Create from raw byte
pub fn from_byte(b: u8) -> Self;
/// Convert to raw byte
pub fn to_byte(&self) -> u8;
}
```
#### HID 报告格式
```rust
/// 键盘报告 (8 字节)
#[derive(Debug, Clone, Default)]
pub struct KeyboardReport {
pub modifiers: u8, // Ctrl, Shift, Alt, Meta
pub reserved: u8, // 保留
pub keys: [u8; 6], // 最多 6 个按键 USB HID code
}
impl KeyboardReport {
/// Convert to bytes for USB HID
pub fn to_bytes(&self) -> [u8; 8];
/// Add a key to the report
pub fn add_key(&mut self, key: u8) -> bool;
/// Remove a key from the report
pub fn remove_key(&mut self, key: u8);
/// Clear all keys
pub fn clear(&mut self);
}
/// 鼠标报告 (相对模式 4 字节, 绝对模式 6 字节)
#[derive(Debug, Clone, Default)]
pub struct MouseReport {
pub buttons: u8, // 按钮状态
pub x: i8, // X 移动 (-127 ~ 127) for relative
pub y: i8, // Y 移动 (-127 ~ 127) for relative
pub wheel: i8, // 滚轮 (-127 ~ 127)
}
impl MouseReport {
/// Convert to bytes for USB HID (relative mouse)
pub fn to_bytes_relative(&self) -> [u8; 4];
/// Convert to bytes for USB HID (absolute mouse)
pub fn to_bytes_absolute(&self, x: u16, y: u16) -> [u8; 6];
}
```
#### 错误恢复机制
OTG 后端实现了基于 PiKVM 和 JetKVM 的自动错误恢复:
- **EAGAIN (errno 11)**: 资源暂时不可用 - 使用 `poll()` 等待设备可写,超时后静默丢弃
- **ESHUTDOWN (errno 108)**: 传输端点关闭 - 关闭设备句柄,下次操作时自动重新打开
- **Write Timeout**: 使用 500ms 超时 (`HID_WRITE_TIMEOUT_MS`),超时后静默丢弃数据
- **日志限流**: 防止大量错误日志泛滥
### 3.4 CH9329 后端 (ch9329.rs)
通过 CH9329 芯片(串口转 HID实现 HID 功能。
```rust
pub struct Ch9329Backend {
/// 串口设备
port: Mutex<Box<dyn SerialPort>>,
/// 设备路径
device_path: String,
/// 波特率
baud_rate: u32,
/// 当前键盘状态
keyboard_state: Mutex<KeyboardState>,
/// 连接状态
connected: AtomicBool,
}
impl Ch9329Backend {
/// 创建 CH9329 后端
pub fn with_baud_rate(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)
```rust
/// Keyboard event type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum KeyEventType {
Down, // 按键按下
Up, // 按键释放
}
/// Keyboard modifier flags
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct KeyboardModifiers {
pub left_ctrl: bool,
pub left_shift: bool,
pub left_alt: bool,
pub left_meta: bool,
pub right_ctrl: bool,
pub right_shift: bool,
pub right_alt: bool,
pub right_meta: bool,
}
impl KeyboardModifiers {
/// Convert to USB HID modifier byte
pub fn to_hid_byte(&self) -> u8;
/// Create from USB HID modifier byte
pub fn from_hid_byte(byte: u8) -> Self;
/// Check if any modifier is active
pub fn any(&self) -> bool;
}
/// Keyboard event
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyboardEvent {
/// Event type (down/up)
#[serde(rename = "type")]
pub event_type: KeyEventType,
/// Key code (USB HID usage code or JavaScript keyCode)
pub key: u8,
/// Modifier keys state
#[serde(default)]
pub modifiers: KeyboardModifiers,
/// If true, key is already USB HID code (skip js_to_usb conversion)
#[serde(default)]
pub is_usb_hid: bool,
}
```
### 4.2 鼠标事件 (types.rs)
```rust
/// Mouse button
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MouseButton {
Left,
Right,
Middle,
Back,
Forward,
}
impl MouseButton {
/// Convert to USB HID button bit
pub fn to_hid_bit(&self) -> u8;
}
/// Mouse event type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MouseEventType {
Move, // 相对移动
MoveAbs, // 绝对位置
Down, // 按钮按下
Up, // 按钮释放
Scroll, // 滚轮滚动
}
/// Mouse event
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MouseEvent {
/// Event type
#[serde(rename = "type")]
pub event_type: MouseEventType,
/// X coordinate or delta
#[serde(default)]
pub x: i32,
/// Y coordinate or delta
#[serde(default)]
pub y: i32,
/// Button (for down/up events)
#[serde(default)]
pub button: Option<MouseButton>,
/// Scroll delta (for scroll events)
#[serde(default)]
pub scroll: i8,
}
```
### 4.3 Consumer Control 事件 (types.rs)
```rust
/// Consumer control event (multimedia keys)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsumerEvent {
/// USB HID Consumer Control Usage Code
pub usage: u16,
}
// 常用 Usage Codes (定义在 consumer.rs)
pub mod 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;
}
```
---
## 5. 按键映射
### 5.1 按键转换 (keymap.rs)
模块使用固定大小的查找表 (256 字节) 实现 JavaScript keyCode 到 USB HID usage code 的 O(1) 转换。
```rust
/// Convert JavaScript keyCode to USB HID keyCode
pub fn js_to_usb(js_code: u8) -> Option<u8>;
/// Check if a key code is a modifier key
pub fn is_modifier_key(usb_code: u8) -> bool;
/// Get modifier bit for a modifier key
pub fn modifier_bit(usb_code: u8) -> Option<u8>;
// USB HID key codes 定义在 usb 子模块
pub mod usb {
pub const KEY_A: u8 = 0x04;
pub const KEY_ENTER: u8 = 0x28;
pub const KEY_LEFT_CTRL: u8 = 0xE0;
// ... 等等
}
// JavaScript key codes 定义在 js 子模块
pub mod js {
pub const KEY_A: u8 = 65;
pub const KEY_ENTER: u8 = 13;
// ... 等等
}
```
### 5.2 转换示例
```
JavaScript → USB HID
65 (KEY_A) → 0x04
13 (ENTER) → 0x28
37 (LEFT) → 0x50
17 (CTRL) → 0xE0
```
---
## 6. 输入处理器
### 6.1 WebSocket Handler (websocket.rs)
使用二进制协议 (与 DataChannel 格式相同):
```rust
/// Binary response codes
const RESP_OK: u8 = 0x00;
const RESP_ERR_HID_UNAVAILABLE: u8 = 0x01;
const RESP_ERR_INVALID_MESSAGE: u8 = 0x02;
/// WebSocket HID upgrade handler
pub async fn ws_hid_handler(
ws: WebSocketUpgrade,
State(state): State<Arc<AppState>>
) -> Response;
/// Handle HID WebSocket connection
async fn handle_hid_socket(socket: WebSocket, state: Arc<AppState>);
/// Handle binary HID message (same format as DataChannel)
async fn handle_binary_message(data: &[u8], state: &AppState) -> Result<(), String>;
```
### 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 │ JS code │ bitmask │
└──────────┴──────────┴──────────┴──────────┘
EVENT: 0=down, 1=up
鼠标消息 (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 │
└──────────┴──────────────────────┘
```
```rust
/// Parsed HID event from DataChannel
#[derive(Debug, Clone)]
pub enum HidChannelEvent {
Keyboard(KeyboardEvent),
Mouse(MouseEvent),
Consumer(ConsumerEvent),
}
/// Parse a binary HID message from DataChannel
pub fn parse_hid_message(data: &[u8]) -> Option<HidChannelEvent>;
/// Encode events to binary format (for sending to client if needed)
pub fn encode_keyboard_event(event: &KeyboardEvent) -> Vec<u8>;
pub fn encode_mouse_event(event: &MouseEvent) -> Vec<u8>;
```
---
## 7. 健康监视
### 7.1 HidHealthMonitor (monitor.rs)
```rust
/// HID health status
#[derive(Debug, Clone, PartialEq)]
pub enum HidHealthStatus {
/// Device is healthy and operational
Healthy,
/// Device has an error, attempting recovery
Error {
reason: String,
error_code: String,
retry_count: u32,
},
/// Device is disconnected
Disconnected,
}
/// HID health monitor configuration
#[derive(Debug, Clone)]
pub struct HidMonitorConfig {
/// Health check interval in milliseconds
pub check_interval_ms: u64,
/// Retry interval when device is lost (milliseconds)
pub retry_interval_ms: u64,
/// Maximum retry attempts before giving up (0 = infinite)
pub max_retries: u32,
/// Log throttle interval in seconds
pub log_throttle_secs: u64,
/// Recovery cooldown in milliseconds
pub recovery_cooldown_ms: u64,
}
/// HID health monitor
pub struct HidHealthMonitor {
/// Current health status
status: RwLock<HidHealthStatus>,
/// Event bus for notifications
events: RwLock<Option<Arc<EventBus>>>,
/// Log throttler to prevent log flooding
throttler: LogThrottler,
/// Configuration
config: HidMonitorConfig,
/// Current retry count
retry_count: AtomicU32,
/// Last error code (for change detection)
last_error_code: RwLock<Option<String>>,
/// Last recovery timestamp (for cooldown)
last_recovery_ms: AtomicU64,
}
impl HidHealthMonitor {
/// Create a new HID health monitor
pub fn new(config: HidMonitorConfig) -> Self;
/// Create with default configuration
pub fn with_defaults() -> Self;
/// Set the event bus for broadcasting state changes
pub async fn set_event_bus(&self, events: Arc<EventBus>);
/// Report an error from HID operations
pub async fn report_error(
&self,
backend: &str,
device: Option<&str>,
reason: &str,
error_code: &str,
);
/// Report that a reconnection attempt is starting
pub async fn report_reconnecting(&self, backend: &str);
/// Report that the device has recovered
pub async fn report_recovered(&self, backend: &str);
/// Get the current health status
pub async fn status(&self) -> HidHealthStatus;
/// Get the current retry count
pub fn retry_count(&self) -> u32;
/// Check if the monitor is in an error state
pub async fn is_error(&self) -> bool;
/// Check if the monitor is healthy
pub async fn is_healthy(&self) -> bool;
/// Reset the monitor to healthy state
pub async fn reset(&self);
/// Check if we should continue retrying
pub fn should_retry(&self) -> bool;
/// Get the retry interval
pub fn retry_interval(&self) -> Duration;
}
```
#### 错误处理流程
1. **报告错误**: `report_error()` - 更新状态、限流日志、发布事件
2. **重连通知**: `report_reconnecting()` - 每5次尝试发布一次事件
3. **恢复通知**: `report_recovered()` - 重置状态、发布恢复事件
4. **日志限流**: 5秒内不重复日志相同错误
5. **恢复冷却**: 恢复后1秒内抑制错误日志
---
## 8. 系统事件
```rust
pub enum SystemEvent {
/// HID state changed
HidStateChanged {
backend: String,
initialized: bool,
error: Option<String>,
error_code: Option<String>,
},
/// HID device lost
HidDeviceLost {
backend: String,
device: Option<String>,
reason: String,
error_code: String,
},
/// HID reconnecting
HidReconnecting {
backend: String,
attempt: u32,
},
/// HID recovered
HidRecovered {
backend: String,
},
}
```
---
## 9. 错误处理
```rust
pub enum AppError {
/// HID backend error
HidError {
backend: String,
reason: String,
error_code: String,
},
// ... 其他错误类型
}
```
常见错误码:
- `enoent` - 设备文件不存在 (ENOENT)
- `epipe` - 管道断开 (EPIPE)
- `eshutdown` - 端点关闭 (ESHUTDOWN)
- `eagain` - 资源暂时不可用 (EAGAIN)
- `eagain_retry` - EAGAIN 重试中 (内部使用,不报告给监视器)
- `enxio` - 设备或地址不存在 (ENXIO)
- `enodev` - 设备不存在 (ENODEV)
- `eio` - I/O 错误 (EIO)
- `io_error` - 其他 I/O 错误
- `not_opened` - 设备未打开
- `init_failed` - 初始化失败
---
## 10. 使用示例
### 10.1 初始化 HID 控制器
```rust
// 创建 HID 控制器 (OTG 模式)
let hid = HidController::new(
HidBackendType::Otg,
Some(otg_service.clone())
);
// 设置事件总线
hid.set_event_bus(event_bus.clone()).await;
// 初始化后端
hid.init().await?;
```
### 10.2 发送键盘事件
```rust
// 按下 Ctrl+C
hid.send_keyboard(KeyboardEvent {
event_type: KeyEventType::Down,
key: 67, // JS keyCode for 'C'
modifiers: KeyboardModifiers {
left_ctrl: true,
..Default::default()
},
is_usb_hid: false,
}).await?;
// 释放 Ctrl+C
hid.send_keyboard(KeyboardEvent {
event_type: KeyEventType::Up,
key: 67,
modifiers: KeyboardModifiers::default(),
is_usb_hid: false,
}).await?;
```
### 10.3 发送鼠标事件
```rust
// 移动鼠标到绝对位置 (屏幕中心)
hid.send_mouse(MouseEvent {
event_type: MouseEventType::MoveAbs,
x: 16384, // 0-32767 范围 (HID 标准)
y: 16384,
button: None,
scroll: 0,
}).await?;
// 点击左键
hid.send_mouse(MouseEvent::button_down(MouseButton::Left)).await?;
hid.send_mouse(MouseEvent::button_up(MouseButton::Left)).await?;
// 相对移动
hid.send_mouse(MouseEvent::move_rel(10, -10)).await?;
// 滚轮滚动
hid.send_mouse(MouseEvent::scroll(-1)).await?;
```
### 10.4 发送多媒体键
```rust
use crate::hid::consumer::usage;
// 播放/暂停
hid.send_consumer(ConsumerEvent {
usage: usage::PLAY_PAUSE,
}).await?;
// 音量增加
hid.send_consumer(ConsumerEvent {
usage: usage::VOLUME_UP,
}).await?;
```
### 10.5 重新加载后端
```rust
// 切换到 CH9329 后端
hid.reload(HidBackendType::Ch9329 {
port: "/dev/ttyUSB0".to_string(),
baud_rate: 9600,
}).await?;
```
---
## 11. 常见问题
### Q: OTG 模式下键盘/鼠标不工作?
1. 检查 `/dev/hidg*` 设备是否存在: `ls -l /dev/hidg*`
2. 检查 USB gadget 是否正确配置: `ls /sys/kernel/config/usb_gadget/`
3. 检查 UDC 是否绑定: `cat /sys/kernel/config/usb_gadget/*/UDC`
4. 检查目标 PC 是否识别 USB 设备 (在目标 PC 上运行 `dmesg` 或查看设备管理器)
5. 查看 One-KVM 日志: `journalctl -u one-kvm -f`
### Q: CH9329 无法初始化?
1. 检查串口设备路径: `ls -l /dev/ttyUSB*`
2. 检查串口权限: `sudo chmod 666 /dev/ttyUSB0`
3. 检查波特率设置 (默认 9600)
4. 使用 `minicom``screen` 测试串口连接:
```bash
minicom -D /dev/ttyUSB0 -b 9600
```
### Q: 鼠标定位不准确?
1. 使用绝对鼠标模式 (默认)
2. 确保前端发送的坐标在 0-32767 范围内
3. 检查前端是否正确处理屏幕缩放
4. 检查浏览器缩放级别 (应为 100%)
### Q: 按键有延迟?
1. 检查网络延迟: `ping <kvm-ip>`
2. 使用 WebRTC 模式 (DataChannel) 而不是 WebSocket
3. 减少网络跳数 (避免多层代理)
4. 检查服务器 CPU 负载
### Q: 频繁出现 ESHUTDOWN 错误?
这是正常现象,通常发生在:
- MSD (大容量存储) 设备挂载/卸载时
- USB 主机重新枚举设备时
- 目标 PC 进入休眠/唤醒时
OTG 后端会自动处理这些错误并重新打开设备,无需人工干预。
### Q: 如何查看 LED 状态 (Num Lock, Caps Lock)?
```rust
let led_state = otg_backend.led_state();
println!("Caps Lock: {}", led_state.caps_lock);
println!("Num Lock: {}", led_state.num_lock);
```
LED 状态会在键盘设备的 OUT endpoint 接收到数据时自动更新。
---
## 12. 性能优化
### 12.1 零拷贝写入
OTG 后端使用 `write_all()` 直接写入设备文件,避免额外的内存拷贝。
### 12.2 非阻塞 I/O
所有设备文件以 `O_NONBLOCK` 模式打开,配合 `poll()` 实现超时控制。
### 12.3 事件批处理
前端可以批量发送多个事件,后端逐个处理。对于鼠标移动,超时的帧会被静默丢弃。
### 12.4 日志限流
使用 `LogThrottler` 防止大量重复日志影响性能:
- HID 错误日志: 5秒限流
- 恢复后冷却: 1秒内不记录新错误
---
## 13. 安全考虑
### 13.1 设备权限
- OTG gadget 设备文件 (`/dev/hidg*`) 需要读写权限
- 通常需要 `root` 权限或添加用户到 `input` 组
- 建议使用 udev 规则自动设置权限
### 13.2 输入验证
- 所有来自前端的事件都经过验证
- 无效的按键码会被忽略或映射到默认值
- 鼠标坐标会被限制在有效范围内
### 13.3 资源限制
- 键盘报告最多支持 6 个同时按键 (USB HID 标准)
- 鼠标移动范围限制在 -127~127 (相对) 或 0~32767 (绝对)
- 超时的 HID 写入会被丢弃,不会无限等待
---
## 14. 调试技巧
### 14.1 启用详细日志
```bash
RUST_LOG=one_kvm::hid=debug ./one-kvm
```
### 14.2 监控 HID 设备
```bash
# 监控键盘事件
sudo cat /dev/hidg0 | hexdump -C
# 监控鼠标事件
sudo cat /dev/hidg1 | hexdump -C
```
### 14.3 检查 USB 枚举
在目标 PC 上:
```bash
# Linux
dmesg | grep -i hid
lsusb
# Windows
# 打开设备管理器 -> 人体学输入设备
```
### 14.4 测试 CH9329
```bash
# 发送测试命令 (获取版本)
echo -ne '\x57\xAB\x0E\x00\x0E' > /dev/ttyUSB0
```
---
## 15. 参考资料
- [USB HID Usage Tables 1.12](https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf)
- [Linux USB Gadget API](https://www.kernel.org/doc/html/latest/usb/gadget_configfs.html)
- [PiKVM HID Implementation](https://github.com/pikvm/kvmd/blob/master/kvmd/apps/otg/hid/)
- [JetKVM HID Write Timeout](https://github.com/jetkvm/jetkvm/blob/main/jetkvm/hid.c#L25)
- [CH9329 Datasheet](http://www.wch.cn/downloads/CH9329DS1_PDF.html)