mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-28 08:31:52 +08:00
重大变更: - 从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模块文档
31 KiB
31 KiB
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 操作。
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)
#[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 设备。
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 报告格式
/// 键盘报告 (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 功能。
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)
/// 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)
/// 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)
/// 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) 转换。
/// 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 格式相同):
/// 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 │
└──────────┴──────────────────────┘
/// 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)
/// 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;
}
错误处理流程
- 报告错误:
report_error()- 更新状态、限流日志、发布事件 - 重连通知:
report_reconnecting()- 每5次尝试发布一次事件 - 恢复通知:
report_recovered()- 重置状态、发布恢复事件 - 日志限流: 5秒内不重复日志相同错误
- 恢复冷却: 恢复后1秒内抑制错误日志
8. 系统事件
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. 错误处理
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 控制器
// 创建 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 发送键盘事件
// 按下 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 发送鼠标事件
// 移动鼠标到绝对位置 (屏幕中心)
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 发送多媒体键
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 重新加载后端
// 切换到 CH9329 后端
hid.reload(HidBackendType::Ch9329 {
port: "/dev/ttyUSB0".to_string(),
baud_rate: 9600,
}).await?;
11. 常见问题
Q: OTG 模式下键盘/鼠标不工作?
- 检查
/dev/hidg*设备是否存在:ls -l /dev/hidg* - 检查 USB gadget 是否正确配置:
ls /sys/kernel/config/usb_gadget/ - 检查 UDC 是否绑定:
cat /sys/kernel/config/usb_gadget/*/UDC - 检查目标 PC 是否识别 USB 设备 (在目标 PC 上运行
dmesg或查看设备管理器) - 查看 One-KVM 日志:
journalctl -u one-kvm -f
Q: CH9329 无法初始化?
- 检查串口设备路径:
ls -l /dev/ttyUSB* - 检查串口权限:
sudo chmod 666 /dev/ttyUSB0 - 检查波特率设置 (默认 9600)
- 使用
minicom或screen测试串口连接:minicom -D /dev/ttyUSB0 -b 9600
Q: 鼠标定位不准确?
- 使用绝对鼠标模式 (默认)
- 确保前端发送的坐标在 0-32767 范围内
- 检查前端是否正确处理屏幕缩放
- 检查浏览器缩放级别 (应为 100%)
Q: 按键有延迟?
- 检查网络延迟:
ping <kvm-ip> - 使用 WebRTC 模式 (DataChannel) 而不是 WebSocket
- 减少网络跳数 (避免多层代理)
- 检查服务器 CPU 负载
Q: 频繁出现 ESHUTDOWN 错误?
这是正常现象,通常发生在:
- MSD (大容量存储) 设备挂载/卸载时
- USB 主机重新枚举设备时
- 目标 PC 进入休眠/唤醒时
OTG 后端会自动处理这些错误并重新打开设备,无需人工干预。
Q: 如何查看 LED 状态 (Num Lock, Caps Lock)?
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 (相对) 或 032767 (绝对) - 超时的 HID 写入会被丢弃,不会无限等待
14. 调试技巧
14.1 启用详细日志
RUST_LOG=one_kvm::hid=debug ./one-kvm
14.2 监控 HID 设备
# 监控键盘事件
sudo cat /dev/hidg0 | hexdump -C
# 监控鼠标事件
sudo cat /dev/hidg1 | hexdump -C
14.3 检查 USB 枚举
在目标 PC 上:
# Linux
dmesg | grep -i hid
lsusb
# Windows
# 打开设备管理器 -> 人体学输入设备
14.4 测试 CH9329
# 发送测试命令 (获取版本)
echo -ne '\x57\xAB\x0E\x00\x0E' > /dev/ttyUSB0