fix(web): 修复初始化向导未应用所选 HID 配置

This commit is contained in:
mofeng-git
2026-09-26 11:46:32 +08:00
parent 0e8c323659
commit f8fb64ad8c
4 changed files with 154 additions and 48 deletions

View File

@@ -30,7 +30,8 @@ pub struct SetupRequest {
// Audio settings
pub audio_device: Option<String>,
// HID settings
pub hid_backend: Option<String>,
pub hid_backend: Option<crate::config::HidBackend>,
pub hid_bluetooth: Option<crate::config::BluetoothHidConfig>,
pub hid_ch9329_port: Option<String>,
pub hid_ch9329_baudrate: Option<u32>,
pub hid_otg_udc: Option<String>,
@@ -42,6 +43,38 @@ pub struct SetupRequest {
pub rustdesk_enabled: Option<bool>,
}
impl SetupRequest {
fn hid_config(&self, current: &crate::config::HidConfig) -> Result<crate::config::HidConfig> {
let mut hid = current.clone();
if let Some(backend) = &self.hid_backend {
hid.backend = backend.clone();
}
if let Some(bluetooth) = &self.hid_bluetooth {
hid.bluetooth = bluetooth.clone();
}
if let Some(port) = &self.hid_ch9329_port {
hid.ch9329_port = port.clone();
}
if let Some(baudrate) = self.hid_ch9329_baudrate {
hid.ch9329_baudrate = baudrate;
}
if let Some(udc) = &self.hid_otg_udc {
hid.otg_udc = Some(udc.clone());
}
if let Some(profile) = &self.hid_otg_profile {
if let Some(parsed) = crate::config::OtgHidProfile::from_legacy_str(profile) {
hid.otg_profile = parsed;
}
}
if let Some(enabled) = self.hid_otg_keyboard_leds {
hid.otg_keyboard_leds = enabled;
}
hid.bluetooth.validate()?;
hid.validate_otg_functions()?;
Ok(hid)
}
}
pub async fn setup_init(
State(state): State<Arc<AppState>>,
Json(mut req): Json<SetupRequest>,
@@ -97,6 +130,8 @@ pub async fn setup_init(
}
let old_config = state.config.get();
// Validate the selected HID configuration before creating the one-time account.
let hid_config = req.hid_config(&old_config.hid)?;
// Create single system user
state
@@ -134,30 +169,7 @@ pub async fn setup_init(
}
// HID settings
if let Some(backend) = req.hid_backend.clone() {
config.hid.backend = match backend.as_str() {
"otg" => crate::config::HidBackend::Otg,
"ch9329" => crate::config::HidBackend::Ch9329,
_ => crate::config::HidBackend::None,
};
}
if let Some(port) = req.hid_ch9329_port.clone() {
config.hid.ch9329_port = port;
}
if let Some(baudrate) = req.hid_ch9329_baudrate {
config.hid.ch9329_baudrate = baudrate;
}
if let Some(udc) = req.hid_otg_udc.clone() {
config.hid.otg_udc = Some(udc);
}
if let Some(profile) = req.hid_otg_profile.clone() {
if let Some(parsed) = crate::config::OtgHidProfile::from_legacy_str(&profile) {
config.hid.otg_profile = parsed;
}
}
if let Some(enabled) = req.hid_otg_keyboard_leds {
config.hid.otg_keyboard_leds = enabled;
}
config.hid = hid_config.clone();
if let Some(enabled) = req.msd_enabled {
config.msd.enabled = enabled;
}
@@ -265,3 +277,106 @@ pub async fn setup_init(
message: Some("Setup completed".to_string()),
}))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{AppConfig, HidBackend, HidConfig, OtgHidProfile};
use serde_json::{json, Value};
fn request(mut hid: Value) -> SetupRequest {
hid["username"] = json!("admin");
hid["password"] = json!("test-password");
serde_json::from_value(hid).unwrap()
}
#[test]
fn setup_applies_selected_otg_device_and_legacy_options() {
let req = request(json!({
"hid_backend": "otg",
"hid_otg_udc": "fe800000.usb",
"hid_otg_profile": "full_no_consumer_no_msd",
"hid_otg_keyboard_leds": true
}));
let hid = req.hid_config(&HidConfig::default()).unwrap();
assert_eq!(hid.backend, HidBackend::Otg);
assert_eq!(hid.otg_udc.as_deref(), Some("fe800000.usb"));
assert_eq!(hid.otg_profile, OtgHidProfile::FullNoConsumer);
assert!(hid.effective_otg_keyboard_leds());
}
#[test]
fn setup_applies_selected_ch9329_port_and_baudrate() {
let req = request(json!({
"hid_backend": "ch9329",
"hid_ch9329_port": "/dev/ttyUSB2",
"hid_ch9329_baudrate": 115200
}));
let hid = req.hid_config(&HidConfig::default()).unwrap();
assert_eq!(hid.backend, HidBackend::Ch9329);
assert_eq!(hid.ch9329_port, "/dev/ttyUSB2");
assert_eq!(hid.ch9329_baudrate, 115200);
}
#[test]
fn setup_applies_bluetooth_selection_and_usb_invariants() {
let req = request(json!({
"hid_backend": "bluetooth",
"hid_bluetooth": { "adapter": "hci1", "name": "My KVM" }
}));
let mut config = AppConfig::default();
config.hid = req.hid_config(&config.hid).unwrap();
config.hid.mouse_absolute = true;
config.msd.enabled = true;
config.otg_network.enabled = true;
config.uac.enabled = true;
config.enforce_invariants();
assert_eq!(config.hid.backend, HidBackend::Bluetooth);
assert_eq!(config.hid.bluetooth.adapter, "hci1");
assert_eq!(config.hid.bluetooth.name, "My KVM");
assert_eq!(config.hid.bluetooth.peer, None);
assert!(!config.hid.mouse_absolute);
assert!(!config.msd.enabled && !config.otg_network.enabled && !config.uac.enabled);
}
#[test]
fn setup_preserves_omitted_settings_and_allows_explicit_disable() {
let current = HidConfig {
backend: HidBackend::Ch9329,
ch9329_port: "COM7".into(),
ch9329_baudrate: 57600,
..Default::default()
};
assert_eq!(request(json!({})).hid_config(&current).unwrap(), current);
let disabled = request(json!({ "hid_backend": "none" }))
.hid_config(&current)
.unwrap();
assert_eq!(disabled.backend, HidBackend::None);
assert_eq!(disabled.ch9329_port, current.ch9329_port);
assert_eq!(disabled.ch9329_baudrate, current.ch9329_baudrate);
}
#[test]
fn setup_rejects_unknown_backend_instead_of_disabling_hid() {
assert!(serde_json::from_value::<SetupRequest>(json!({
"username": "admin", "password": "test-password",
"hid_backend": "unsupported"
}))
.is_err());
}
#[test]
fn setup_rejects_invalid_bluetooth_configuration() {
for bluetooth in [
json!({ "adapter": "/dev/hci0", "name": "My KVM" }),
json!({ "adapter": "hci0", "name": "" }),
json!({ "adapter": "hci0", "name": "蓝".repeat(24) }),
] {
let req = request(json!({
"hid_backend": "bluetooth", "hid_bluetooth": bluetooth
}));
assert!(req.hid_config(&HidConfig::default()).is_err());
}
}
}