mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-09-12 10:34:24 +08:00
@@ -508,9 +508,5 @@ fn device_label(device: &cpal::Device) -> String {
|
|||||||
device
|
device
|
||||||
.description()
|
.description()
|
||||||
.map(|desc| desc.to_string())
|
.map(|desc| desc.to_string())
|
||||||
.or_else(|_| {
|
|
||||||
#[allow(deprecated)]
|
|
||||||
device.name()
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|_| "Unknown WASAPI capture device".to_string())
|
.unwrap_or_else(|_| "Unknown WASAPI capture device".to_string())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,10 +142,7 @@ fn device_labels(device: &cpal::Device) -> DeviceLabels {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
#[allow(deprecated)]
|
let display = "Unknown WASAPI capture device".to_string();
|
||||||
let display = device
|
|
||||||
.name()
|
|
||||||
.unwrap_or_else(|_| "Unknown WASAPI capture device".to_string());
|
|
||||||
DeviceLabels {
|
DeviceLabels {
|
||||||
display: display.clone(),
|
display: display.clone(),
|
||||||
search_text: display,
|
search_text: display,
|
||||||
|
|||||||
@@ -355,12 +355,13 @@ pub async fn apply_msd_config(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
pub async fn apply_usb_config(
|
||||||
pub async fn apply_otg_config(
|
|
||||||
state: &Arc<AppState>,
|
state: &Arc<AppState>,
|
||||||
old_config: &AppConfig,
|
old_config: &AppConfig,
|
||||||
new_config: &AppConfig,
|
new_config: &AppConfig,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
let transitioning_away_from_otg =
|
let transitioning_away_from_otg =
|
||||||
old_config.hid.backend == HidBackend::Otg && new_config.hid.backend != HidBackend::Otg;
|
old_config.hid.backend == HidBackend::Otg && new_config.hid.backend != HidBackend::Otg;
|
||||||
|
|
||||||
@@ -404,6 +405,20 @@ pub async fn apply_otg_config(
|
|||||||
.await
|
.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(
|
pub async fn apply_atx_config(
|
||||||
state: &Arc<AppState>,
|
state: &Arc<AppState>,
|
||||||
_old_config: &AtxConfig,
|
_old_config: &AtxConfig,
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ use crate::config::HidConfig;
|
|||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
use super::otg::update_otg_config_inner;
|
use super::types::HidConfigUpdate;
|
||||||
use super::types::{HidConfigUpdate, OtgConfigUpdate};
|
use super::usb_update::{stage_hid_config_update, update_usb_config};
|
||||||
|
|
||||||
pub async fn get_hid_config(State(state): State<Arc<AppState>>) -> Json<HidConfig> {
|
pub async fn get_hid_config(State(state): State<Arc<AppState>>) -> Json<HidConfig> {
|
||||||
Json(state.config.get().hid.clone())
|
Json(state.config.get().hid.clone())
|
||||||
@@ -16,13 +16,9 @@ pub async fn update_hid_config(
|
|||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
Json(req): Json<HidConfigUpdate>,
|
Json(req): Json<HidConfigUpdate>,
|
||||||
) -> Result<Json<HidConfig>> {
|
) -> Result<Json<HidConfig>> {
|
||||||
let response = update_otg_config_inner(
|
let config = update_usb_config(&state, move |staged| {
|
||||||
&state,
|
stage_hid_config_update(&mut staged.hid, &req)
|
||||||
OtgConfigUpdate {
|
})
|
||||||
hid: Some(req),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
Ok(Json(response.hid))
|
Ok(Json(config.hid))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ mod redfish;
|
|||||||
mod rtsp;
|
mod rtsp;
|
||||||
mod rustdesk;
|
mod rustdesk;
|
||||||
mod stream;
|
mod stream;
|
||||||
|
mod usb_update;
|
||||||
pub(crate) mod video;
|
pub(crate) mod video;
|
||||||
mod vnc;
|
mod vnc;
|
||||||
mod watchdog;
|
mod watchdog;
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ use axum::{extract::State, Json};
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use typeshare::typeshare;
|
use typeshare::typeshare;
|
||||||
|
|
||||||
use crate::config::{HidBackend, HidConfig, MsdConfig, OtgNetworkConfig};
|
use crate::config::{HidConfig, MsdConfig, OtgNetworkConfig};
|
||||||
use crate::error::{AppError, Result};
|
use crate::error::Result;
|
||||||
use crate::otg::OtgNetworkStatus;
|
use crate::otg::OtgNetworkStatus;
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
use super::apply::{apply_otg_config, try_apply_lock};
|
|
||||||
use super::types::OtgConfigUpdate;
|
use super::types::OtgConfigUpdate;
|
||||||
|
use super::usb_update::{stage_hid_config_update, update_usb_config};
|
||||||
|
|
||||||
#[typeshare]
|
#[typeshare]
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
@@ -32,99 +32,23 @@ pub(super) async fn update_otg_config_inner(
|
|||||||
state: &Arc<AppState>,
|
state: &Arc<AppState>,
|
||||||
request: OtgConfigUpdate,
|
request: OtgConfigUpdate,
|
||||||
) -> Result<OtgConfigResponse> {
|
) -> Result<OtgConfigResponse> {
|
||||||
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() {
|
||||||
if let Some(ref update) = request.hid {
|
Some(update) => stage_hid_config_update(&mut staged.hid, update)?,
|
||||||
update.validate()?;
|
None => None,
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(error) = state
|
if let Some(ref update) = request.msd {
|
||||||
.config
|
update.validate()?;
|
||||||
.update(|config| {
|
update.apply_to(&mut staged.msd);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
if let Some(ref update) = request.network {
|
||||||
|
update.apply_to(&mut staged.otg_network);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(requested_ch9329_descriptor)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(OtgConfigResponse {
|
Ok(OtgConfigResponse {
|
||||||
hid: staged_config.hid,
|
hid: staged_config.hid,
|
||||||
@@ -133,34 +57,3 @@ pub(super) async fn update_otg_config_inner(
|
|||||||
status: state.otg_service.network_status().await,
|
status: state.otg_service.network_status().await,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn rollback_after_failure(
|
|
||||||
state: &Arc<AppState>,
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|||||||
203
src/web/handlers/config/usb_update.rs
Normal file
203
src/web/handlers/config/usb_update.rs
Normal file
@@ -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<Option<Ch9329DescriptorConfig>> {
|
||||||
|
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<F>(
|
||||||
|
state: &Arc<AppState>,
|
||||||
|
stage_update: F,
|
||||||
|
) -> Result<AppConfig>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut AppConfig) -> Result<Option<Ch9329DescriptorConfig>>,
|
||||||
|
{
|
||||||
|
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<AppState>,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -368,7 +368,7 @@ watch(() => props.open, (isOpen) => {
|
|||||||
:model-value="String(baudrate)"
|
:model-value="String(baudrate)"
|
||||||
@update:model-value="handleBaudrateChange"
|
@update:model-value="handleBaudrateChange"
|
||||||
>
|
>
|
||||||
<SelectTrigger size="sm" class="text-xs">
|
<SelectTrigger size="sm" class="w-full text-xs">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
|
|||||||
@@ -816,7 +816,7 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
|||||||
{{ t('setup.encoderHint') }}
|
{{ t('setup.encoderHint') }}
|
||||||
</p>
|
</p>
|
||||||
<Select v-model="encoderBackend">
|
<Select v-model="encoderBackend">
|
||||||
<SelectTrigger>
|
<SelectTrigger class="w-full">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -838,7 +838,7 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
|||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="hidBackend">{{ t('setup.hidBackend') }}</Label>
|
<Label for="hidBackend">{{ t('setup.hidBackend') }}</Label>
|
||||||
<Select v-model="hidBackend">
|
<Select v-model="hidBackend">
|
||||||
<SelectTrigger>
|
<SelectTrigger class="w-full">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -873,7 +873,7 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
|||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="ch9329Baudrate">{{ t('setup.baudRate') }}</Label>
|
<Label for="ch9329Baudrate">{{ t('setup.baudRate') }}</Label>
|
||||||
<Select :model-value="ch9329Baudrate" @update:model-value="value => ch9329Baudrate = Number(value)">
|
<Select :model-value="ch9329Baudrate" @update:model-value="value => ch9329Baudrate = Number(value)">
|
||||||
<SelectTrigger>
|
<SelectTrigger class="w-full">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
|
|||||||
Reference in New Issue
Block a user