Merge pull request #314 from mofeng-git/dev

Dev
This commit is contained in:
SilentWind
2026-09-26 13:15:07 +08:00
committed by GitHub
5 changed files with 154 additions and 52 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());
}
}
}

View File

@@ -27,14 +27,10 @@
"vue-sonner": "^2.0.9"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.11",
"@tailwindcss/typography": "^0.5.20",
"@tailwindcss/vite": "^4.3.3",
"@types/node": "^24.13.3",
"@vitejs/plugin-vue": "^6.0.8",
"@vue/tsconfig": "^0.8.1",
"autoprefixer": "^10.5.4",
"postcss": "^8.5.19",
"tailwindcss": "^4.3.3",
"typescript": "~5.9.3",
"vite": "^7.3.6",

View File

@@ -169,7 +169,8 @@ export const systemApi = {
video_width?: number
video_height?: number
video_fps?: number
hid_backend?: string
hid_backend?: 'otg' | 'ch9329' | 'bluetooth' | 'none'
hid_bluetooth?: { adapter: string; name: string }
hid_ch9329_port?: string
hid_ch9329_baudrate?: number
hid_otg_udc?: string

View File

@@ -107,26 +107,7 @@ export const useAuthStore = defineStore('auth', () => {
}
}
async function setup(data: {
username: string
password: string
video_device?: string
video_format?: string
video_width?: number
video_height?: number
video_fps?: number
hid_backend?: string
hid_ch9329_port?: string
hid_ch9329_baudrate?: number
hid_otg_udc?: string
hid_otg_profile?: string
hid_otg_keyboard_leds?: boolean
msd_enabled?: boolean
encoder_backend?: string
audio_device?: string
ttyd_enabled?: boolean
rustdesk_enabled?: boolean
}) {
async function setup(data: Parameters<typeof systemApi.setup>[0]) {
loading.value = true
error.value = null

View File

@@ -347,7 +347,7 @@ function prevStep() {
async function handleSetup() {
error.value = ''
if (loading.value) return
if (loading.value || !validateStep3()) return
loading.value = true
// Reconcile a previous timed-out account request before submitting again.
@@ -385,7 +385,16 @@ async function handleSetup() {
setupData.video_fps = toConfigFps(videoFps.value)
}
setupData.hid_backend = 'none'
const hid = hidSelection.value
setupData.hid_backend = hid.backend
if (hid.backend === 'ch9329') {
setupData.hid_ch9329_port = hid.ch9329_port
setupData.hid_ch9329_baudrate = hid.ch9329_baudrate
} else if (hid.backend === 'otg') {
setupData.hid_otg_udc = hid.otg_udc
} else if (hid.backend === 'bluetooth') {
setupData.hid_bluetooth = { ...hid.bluetooth }
}
setupData.msd_enabled = false
// Encoder backend setting