From 6f697e56c8d6f0b9b1da87c6f8b68feb379e4bcf Mon Sep 17 00:00:00 2001 From: mofeng-git Date: Sun, 19 Jul 2026 23:44:34 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20windows=20=E6=9E=84?= =?UTF-8?q?=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/audio/capture_windows.rs | 4 - src/audio/device_windows.rs | 5 +- src/web/handlers/config/apply.rs | 85 ++++++----- src/web/handlers/config/hid.rs | 16 +- src/web/handlers/config/mod.rs | 1 + src/web/handlers/config/otg.rs | 141 +++--------------- src/web/handlers/config/usb_update.rs | 203 ++++++++++++++++++++++++++ 7 files changed, 278 insertions(+), 177 deletions(-) create mode 100644 src/web/handlers/config/usb_update.rs diff --git a/src/audio/capture_windows.rs b/src/audio/capture_windows.rs index ab283cf0..2f059df3 100644 --- a/src/audio/capture_windows.rs +++ b/src/audio/capture_windows.rs @@ -508,9 +508,5 @@ fn device_label(device: &cpal::Device) -> String { device .description() .map(|desc| desc.to_string()) - .or_else(|_| { - #[allow(deprecated)] - device.name() - }) .unwrap_or_else(|_| "Unknown WASAPI capture device".to_string()) } diff --git a/src/audio/device_windows.rs b/src/audio/device_windows.rs index c73305a8..8ea1aaba 100644 --- a/src/audio/device_windows.rs +++ b/src/audio/device_windows.rs @@ -142,10 +142,7 @@ fn device_labels(device: &cpal::Device) -> DeviceLabels { } } Err(_) => { - #[allow(deprecated)] - let display = device - .name() - .unwrap_or_else(|_| "Unknown WASAPI capture device".to_string()); + let display = "Unknown WASAPI capture device".to_string(); DeviceLabels { display: display.clone(), search_text: display, diff --git a/src/web/handlers/config/apply.rs b/src/web/handlers/config/apply.rs index 741331a9..65d59ef0 100644 --- a/src/web/handlers/config/apply.rs +++ b/src/web/handlers/config/apply.rs @@ -355,53 +355,68 @@ pub async fn apply_msd_config( Ok(()) } -#[cfg(unix)] -pub async fn apply_otg_config( +pub async fn apply_usb_config( state: &Arc, old_config: &AppConfig, new_config: &AppConfig, ) -> Result<()> { - let transitioning_away_from_otg = - old_config.hid.backend == HidBackend::Otg && new_config.hid.backend != HidBackend::Otg; + #[cfg(unix)] + { + let transitioning_away_from_otg = + old_config.hid.backend == HidBackend::Otg && new_config.hid.backend != HidBackend::Otg; - if transitioning_away_from_otg { - apply_hid_config( + if transitioning_away_from_otg { + apply_hid_config( + state, + &old_config.hid, + &new_config.hid, + &new_config.msd, + &new_config.otg_network, + ConfigApplyOptions::default(), + ) + .await?; + } else { + reconcile_otg_config( + state, + &new_config.hid, + &new_config.msd, + &new_config.otg_network, + ) + .await?; + apply_hid_config( + state, + &old_config.hid, + &new_config.hid, + &new_config.msd, + &new_config.otg_network, + ConfigApplyOptions::default(), + ) + .await?; + } + + apply_msd_config( state, - &old_config.hid, - &new_config.hid, + &old_config.msd, &new_config.msd, + &new_config.hid, &new_config.otg_network, ConfigApplyOptions::default(), ) - .await?; - } else { - reconcile_otg_config( - state, - &new_config.hid, - &new_config.msd, - &new_config.otg_network, - ) - .await?; - apply_hid_config( - state, - &old_config.hid, - &new_config.hid, - &new_config.msd, - &new_config.otg_network, - ConfigApplyOptions::default(), - ) - .await?; + .await } - apply_msd_config( - state, - &old_config.msd, - &new_config.msd, - &new_config.hid, - &new_config.otg_network, - ConfigApplyOptions::default(), - ) - .await + #[cfg(not(unix))] + { + apply_hid_config( + state, + &old_config.hid, + &new_config.hid, + &new_config.msd, + &new_config.otg_network, + ConfigApplyOptions::default(), + ) + .await + } } pub async fn apply_atx_config( diff --git a/src/web/handlers/config/hid.rs b/src/web/handlers/config/hid.rs index e5e67a72..3dcf45f6 100644 --- a/src/web/handlers/config/hid.rs +++ b/src/web/handlers/config/hid.rs @@ -5,8 +5,8 @@ use crate::config::HidConfig; use crate::error::Result; use crate::state::AppState; -use super::otg::update_otg_config_inner; -use super::types::{HidConfigUpdate, OtgConfigUpdate}; +use super::types::HidConfigUpdate; +use super::usb_update::{stage_hid_config_update, update_usb_config}; pub async fn get_hid_config(State(state): State>) -> Json { Json(state.config.get().hid.clone()) @@ -16,13 +16,9 @@ pub async fn update_hid_config( State(state): State>, Json(req): Json, ) -> Result> { - let response = update_otg_config_inner( - &state, - OtgConfigUpdate { - hid: Some(req), - ..Default::default() - }, - ) + let config = update_usb_config(&state, move |staged| { + stage_hid_config_update(&mut staged.hid, &req) + }) .await?; - Ok(Json(response.hid)) + Ok(Json(config.hid)) } diff --git a/src/web/handlers/config/mod.rs b/src/web/handlers/config/mod.rs index 22442630..250d0aff 100644 --- a/src/web/handlers/config/mod.rs +++ b/src/web/handlers/config/mod.rs @@ -15,6 +15,7 @@ mod redfish; mod rtsp; mod rustdesk; mod stream; +mod usb_update; pub(crate) mod video; mod vnc; mod watchdog; diff --git a/src/web/handlers/config/otg.rs b/src/web/handlers/config/otg.rs index 3cfed051..b06bee9b 100644 --- a/src/web/handlers/config/otg.rs +++ b/src/web/handlers/config/otg.rs @@ -4,13 +4,13 @@ use axum::{extract::State, Json}; use serde::Serialize; use typeshare::typeshare; -use crate::config::{HidBackend, HidConfig, MsdConfig, OtgNetworkConfig}; -use crate::error::{AppError, Result}; +use crate::config::{HidConfig, MsdConfig, OtgNetworkConfig}; +use crate::error::Result; use crate::otg::OtgNetworkStatus; use crate::state::AppState; -use super::apply::{apply_otg_config, try_apply_lock}; use super::types::OtgConfigUpdate; +use super::usb_update::{stage_hid_config_update, update_usb_config}; #[typeshare] #[derive(Debug, Serialize)] @@ -32,99 +32,23 @@ pub(super) async fn update_otg_config_inner( state: &Arc, request: OtgConfigUpdate, ) -> Result { - let _guard = try_apply_lock(&state.config_apply_locks.otg, "otg")?; + let staged_config = update_usb_config(state, move |staged| { + let requested_ch9329_descriptor = match request.hid.as_ref() { + Some(update) => stage_hid_config_update(&mut staged.hid, update)?, + None => None, + }; - if let Some(ref update) = request.hid { - update.validate()?; - } - if let Some(ref update) = request.msd { - update.validate()?; - } - - let old_config = state.config.get(); - let mut staged_config = (*old_config).clone(); - let requested_ch9329_descriptor = request.hid.as_ref().and_then(|update| { - update.ch9329_descriptor.as_ref().map(|_| { - let mut hid = staged_config.hid.clone(); - update.apply_to(&mut hid); - hid.ch9329_descriptor - }) - }); - - if let Some(ref update) = request.hid { - update.apply_to(&mut staged_config.hid); - } - if requested_ch9329_descriptor.is_some() { - staged_config.hid.ch9329_descriptor = old_config.hid.ch9329_descriptor.clone(); - } - if let Some(ref update) = request.msd { - update.apply_to(&mut staged_config.msd); - } - if let Some(ref update) = request.network { - update.apply_to(&mut staged_config.otg_network); - } - staged_config.enforce_invariants(); - - if staged_config.otg_network.enabled - && (staged_config.otg_network.device_mac.is_empty() - || staged_config.otg_network.host_mac.is_empty()) - { - let (device_mac, host_mac) = - crate::otg::network::resolved_mac_pair(&staged_config.otg_network); - staged_config.otg_network.device_mac = device_mac; - staged_config.otg_network.host_mac = host_mac; - } - staged_config.hid.validate_otg_functions()?; - staged_config.otg_network.validate()?; - - if let Err(error) = apply_otg_config(state, &old_config, &staged_config).await { - return Err(rollback_after_failure(state, &staged_config, &old_config, error, false).await); - } - - let descriptor_was_applied = if let Some(ref descriptor) = requested_ch9329_descriptor { - if staged_config.hid.backend == HidBackend::Ch9329 { - match state.hid.apply_ch9329_descriptor(descriptor).await { - Ok(actual) => { - staged_config.hid.ch9329_descriptor = actual.descriptor; - true - } - Err(error) => { - return Err(rollback_after_failure( - state, - &staged_config, - &old_config, - error, - true, - ) - .await); - } - } - } else { - false + if let Some(ref update) = request.msd { + update.validate()?; + update.apply_to(&mut staged.msd); + } + if let Some(ref update) = request.network { + update.apply_to(&mut staged.otg_network); } - } else { - false - }; - if let Err(error) = state - .config - .update(|config| { - config.hid = staged_config.hid.clone(); - config.msd = staged_config.msd.clone(); - config.otg_network = staged_config.otg_network.clone(); - config.enforce_invariants(); - }) - .await - { - return Err(rollback_after_failure( - state, - &staged_config, - &old_config, - AppError::Config(format!("Failed to persist OTG config after apply: {error}")), - descriptor_was_applied, - ) - .await); - } + Ok(requested_ch9329_descriptor) + }) + .await?; Ok(OtgConfigResponse { hid: staged_config.hid, @@ -133,34 +57,3 @@ pub(super) async fn update_otg_config_inner( status: state.otg_service.network_status().await, }) } - -async fn rollback_after_failure( - state: &Arc, - failed_config: &crate::config::AppConfig, - old_config: &crate::config::AppConfig, - primary_error: AppError, - restore_descriptor: bool, -) -> AppError { - let mut rollback_errors = Vec::new(); - - if let Err(error) = apply_otg_config(state, failed_config, old_config).await { - rollback_errors.push(format!("runtime rollback failed: {error}")); - } - if restore_descriptor && old_config.hid.backend == HidBackend::Ch9329 { - if let Err(error) = state - .hid - .apply_ch9329_descriptor(&old_config.hid.ch9329_descriptor) - .await - { - rollback_errors.push(format!("CH9329 descriptor rollback failed: {error}")); - } - } - - if rollback_errors.is_empty() { - return primary_error; - } - - let message = format!("{primary_error}; {}", rollback_errors.join("; ")); - state.otg_service.mark_degraded(message.clone()).await; - AppError::Config(message) -} diff --git a/src/web/handlers/config/usb_update.rs b/src/web/handlers/config/usb_update.rs new file mode 100644 index 00000000..4ed19d2a --- /dev/null +++ b/src/web/handlers/config/usb_update.rs @@ -0,0 +1,203 @@ +use std::sync::Arc; + +use crate::config::{AppConfig, Ch9329DescriptorConfig, HidBackend, HidConfig}; +use crate::error::{AppError, Result}; +use crate::state::AppState; + +use super::apply::{apply_usb_config, try_apply_lock}; +use super::types::HidConfigUpdate; + +pub(super) fn stage_hid_config_update( + staged_hid: &mut HidConfig, + update: &HidConfigUpdate, +) -> Result> { + update.validate()?; + + let old_descriptor = staged_hid.ch9329_descriptor.clone(); + update.apply_to(staged_hid); + + let requested_descriptor = update + .ch9329_descriptor + .as_ref() + .map(|_| staged_hid.ch9329_descriptor.clone()); + if requested_descriptor.is_some() { + staged_hid.ch9329_descriptor = old_descriptor; + } + + Ok(requested_descriptor) +} + +pub(super) async fn update_usb_config( + state: &Arc, + stage_update: F, +) -> Result +where + F: FnOnce(&mut AppConfig) -> Result>, +{ + let _guard = try_apply_lock(&state.config_apply_locks.otg, "otg")?; + + let old_config = state.config.get(); + let mut staged_config = old_config.as_ref().clone(); + let requested_ch9329_descriptor = stage_update(&mut staged_config)?; + + staged_config.enforce_invariants(); + staged_config.hid.validate_otg_functions()?; + + #[cfg(unix)] + { + if staged_config.otg_network.enabled + && (staged_config.otg_network.device_mac.is_empty() + || staged_config.otg_network.host_mac.is_empty()) + { + let (device_mac, host_mac) = + crate::otg::network::resolved_mac_pair(&staged_config.otg_network); + staged_config.otg_network.device_mac = device_mac; + staged_config.otg_network.host_mac = host_mac; + } + staged_config.otg_network.validate()?; + } + + if let Err(error) = apply_usb_config(state, &old_config, &staged_config).await { + return Err(rollback_after_failure(state, &staged_config, &old_config, error, false).await); + } + + let descriptor_was_applied = if let Some(ref descriptor) = requested_ch9329_descriptor { + if staged_config.hid.backend == HidBackend::Ch9329 { + match state.hid.apply_ch9329_descriptor(descriptor).await { + Ok(actual) => { + staged_config.hid.ch9329_descriptor = actual.descriptor; + true + } + Err(error) => { + return Err(rollback_after_failure( + state, + &staged_config, + &old_config, + error, + true, + ) + .await); + } + } + } else { + false + } + } else { + false + }; + + if let Err(error) = state + .config + .update(|config| { + config.hid = staged_config.hid.clone(); + config.msd = staged_config.msd.clone(); + config.otg_network = staged_config.otg_network.clone(); + config.enforce_invariants(); + }) + .await + { + return Err(rollback_after_failure( + state, + &staged_config, + &old_config, + AppError::Config(format!( + "Failed to persist USB configuration after apply: {error}" + )), + descriptor_was_applied, + ) + .await); + } + + Ok(staged_config) +} + +async fn rollback_after_failure( + state: &Arc, + failed_config: &AppConfig, + old_config: &AppConfig, + primary_error: AppError, + restore_descriptor: bool, +) -> AppError { + let mut rollback_errors = Vec::new(); + + if let Err(error) = apply_usb_config(state, failed_config, old_config).await { + rollback_errors.push(format!("runtime rollback failed: {error}")); + } + if restore_descriptor && old_config.hid.backend == HidBackend::Ch9329 { + if let Err(error) = state + .hid + .apply_ch9329_descriptor(&old_config.hid.ch9329_descriptor) + .await + { + rollback_errors.push(format!("CH9329 descriptor rollback failed: {error}")); + } + } + + if rollback_errors.is_empty() { + return primary_error; + } + + let message = format!("{primary_error}; {}", rollback_errors.join("; ")); + #[cfg(unix)] + state.otg_service.mark_degraded(message.clone()).await; + AppError::Config(message) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::config::HidBackend; + use crate::web::handlers::config::types::Ch9329DescriptorConfigUpdate; + + fn hid_update() -> HidConfigUpdate { + HidConfigUpdate { + backend: None, + ch9329_port: None, + ch9329_baudrate: None, + ch9329_hybrid_mouse: None, + ch9329_descriptor: None, + otg_udc: None, + otg_descriptor: None, + otg_profile: None, + otg_functions: None, + otg_keyboard_leds: None, + mouse_absolute: None, + } + } + + #[test] + fn stages_regular_hid_fields_immediately() { + let mut hid = HidConfig::default(); + let mut update = hid_update(); + update.backend = Some(HidBackend::Ch9329); + update.ch9329_port = Some("COM7".to_string()); + + let requested_descriptor = stage_hid_config_update(&mut hid, &update).unwrap(); + + assert_eq!(hid.backend, HidBackend::Ch9329); + assert_eq!(hid.ch9329_port, "COM7"); + assert!(requested_descriptor.is_none()); + } + + #[test] + fn defers_ch9329_descriptor_until_runtime_apply() { + let mut hid = HidConfig::default(); + let old_descriptor = hid.ch9329_descriptor.clone(); + let mut update = hid_update(); + update.ch9329_descriptor = Some(Ch9329DescriptorConfigUpdate { + vendor_id: Some(0x1234), + product_id: Some(0x5678), + manufacturer: None, + product: None, + serial_number: None, + }); + + let requested_descriptor = stage_hid_config_update(&mut hid, &update) + .unwrap() + .expect("descriptor update should be deferred"); + + assert_eq!(hid.ch9329_descriptor, old_descriptor); + assert_eq!(requested_descriptor.vendor_id, 0x1234); + assert_eq!(requested_descriptor.product_id, 0x5678); + } +}