mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-10-27 16:00:49 +08:00
@@ -30,7 +30,8 @@ pub struct SetupRequest {
|
|||||||
// Audio settings
|
// Audio settings
|
||||||
pub audio_device: Option<String>,
|
pub audio_device: Option<String>,
|
||||||
// HID settings
|
// 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_port: Option<String>,
|
||||||
pub hid_ch9329_baudrate: Option<u32>,
|
pub hid_ch9329_baudrate: Option<u32>,
|
||||||
pub hid_otg_udc: Option<String>,
|
pub hid_otg_udc: Option<String>,
|
||||||
@@ -42,6 +43,38 @@ pub struct SetupRequest {
|
|||||||
pub rustdesk_enabled: Option<bool>,
|
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(
|
pub async fn setup_init(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
Json(mut req): Json<SetupRequest>,
|
Json(mut req): Json<SetupRequest>,
|
||||||
@@ -97,6 +130,8 @@ pub async fn setup_init(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let old_config = state.config.get();
|
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
|
// Create single system user
|
||||||
state
|
state
|
||||||
@@ -134,30 +169,7 @@ pub async fn setup_init(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HID settings
|
// HID settings
|
||||||
if let Some(backend) = req.hid_backend.clone() {
|
config.hid = hid_config.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;
|
|
||||||
}
|
|
||||||
if let Some(enabled) = req.msd_enabled {
|
if let Some(enabled) = req.msd_enabled {
|
||||||
config.msd.enabled = enabled;
|
config.msd.enabled = enabled;
|
||||||
}
|
}
|
||||||
@@ -265,3 +277,106 @@ pub async fn setup_init(
|
|||||||
message: Some("Setup completed".to_string()),
|
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(¤t).unwrap(), current);
|
||||||
|
let disabled = request(json!({ "hid_backend": "none" }))
|
||||||
|
.hid_config(¤t)
|
||||||
|
.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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,14 +27,10 @@
|
|||||||
"vue-sonner": "^2.0.9"
|
"vue-sonner": "^2.0.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.11",
|
|
||||||
"@tailwindcss/typography": "^0.5.20",
|
|
||||||
"@tailwindcss/vite": "^4.3.3",
|
"@tailwindcss/vite": "^4.3.3",
|
||||||
"@types/node": "^24.13.3",
|
"@types/node": "^24.13.3",
|
||||||
"@vitejs/plugin-vue": "^6.0.8",
|
"@vitejs/plugin-vue": "^6.0.8",
|
||||||
"@vue/tsconfig": "^0.8.1",
|
"@vue/tsconfig": "^0.8.1",
|
||||||
"autoprefixer": "^10.5.4",
|
|
||||||
"postcss": "^8.5.19",
|
|
||||||
"tailwindcss": "^4.3.3",
|
"tailwindcss": "^4.3.3",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "^7.3.6",
|
"vite": "^7.3.6",
|
||||||
|
|||||||
@@ -169,7 +169,8 @@ export const systemApi = {
|
|||||||
video_width?: number
|
video_width?: number
|
||||||
video_height?: number
|
video_height?: number
|
||||||
video_fps?: 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_port?: string
|
||||||
hid_ch9329_baudrate?: number
|
hid_ch9329_baudrate?: number
|
||||||
hid_otg_udc?: string
|
hid_otg_udc?: string
|
||||||
|
|||||||
@@ -107,26 +107,7 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setup(data: {
|
async function setup(data: Parameters<typeof systemApi.setup>[0]) {
|
||||||
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
|
|
||||||
}) {
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
|
|
||||||
|
|||||||
@@ -347,7 +347,7 @@ function prevStep() {
|
|||||||
async function handleSetup() {
|
async function handleSetup() {
|
||||||
error.value = ''
|
error.value = ''
|
||||||
|
|
||||||
if (loading.value) return
|
if (loading.value || !validateStep3()) return
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
// Reconcile a previous timed-out account request before submitting again.
|
// Reconcile a previous timed-out account request before submitting again.
|
||||||
@@ -385,7 +385,16 @@ async function handleSetup() {
|
|||||||
setupData.video_fps = toConfigFps(videoFps.value)
|
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
|
setupData.msd_enabled = false
|
||||||
|
|
||||||
// Encoder backend setting
|
// Encoder backend setting
|
||||||
|
|||||||
Reference in New Issue
Block a user