mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-11-01 02:10:50 +08:00
feat(hid): 支持 CH9329 + OTG 组合模式
CH9329 负责键鼠,OTG 提供虚拟媒体、USB 网络和音频;分别配置设备、报告运行状态,并在 OTG 配置变更和回滚时保留未变化的 CH9329 实例。 补齐双设备校验、串口冲突检查、初始化引导和功能入口,修复组合模式下鼠标兼容选项缺失。 验证:独立功能版本的 591 项 Rust 库测试、前端生产构建、3 项引导逻辑测试及 2 组浏览器回归通过。未进行目标 KVM 硬件验证。
This commit is contained in:
@@ -149,8 +149,11 @@ fn detect_libcomposite_available(gadget_root: &std::path::Path) -> bool {
|
||||
|
||||
/// OTG self-check status for troubleshooting USB gadget issues
|
||||
pub fn run(config: &crate::config::AppConfig) -> OtgSelfCheckResponse {
|
||||
let hid_backend_is_otg = matches!(config.hid.backend, crate::config::HidBackend::Otg);
|
||||
let gadget_expected = hid_backend_is_otg || config.msd.enabled || config.otg_network.enabled;
|
||||
let hid_backend_is_otg = config.hid.backend.uses_otg_hid();
|
||||
let gadget_expected = hid_backend_is_otg
|
||||
|| config.msd.enabled
|
||||
|| config.otg_network.enabled
|
||||
|| config.uac.enabled;
|
||||
let mut checks = Vec::new();
|
||||
|
||||
let build_response = |checks: Vec<OtgSelfCheckItem>,
|
||||
@@ -173,7 +176,7 @@ pub fn run(config: &crate::config::AppConfig) -> OtgSelfCheckResponse {
|
||||
overall_ok: error_count == 0,
|
||||
error_count,
|
||||
warning_count,
|
||||
hid_backend: format!("{:?}", config.hid.backend).to_lowercase(),
|
||||
hid_backend: config.hid.backend.as_str().to_string(),
|
||||
selected_udc,
|
||||
bound_udc,
|
||||
udc_state,
|
||||
@@ -415,7 +418,7 @@ pub fn run(config: &crate::config::AppConfig) -> OtgSelfCheckResponse {
|
||||
OtgSelfCheckLevel::Warn
|
||||
},
|
||||
"Check one-kvm gadget presence",
|
||||
Some("Enable OTG HID, MSD, or USB Ethernet to create the one-kvm gadget"),
|
||||
Some("Enable OTG HID, MSD, USB Ethernet, or USB audio to create the one-kvm gadget"),
|
||||
Some(one_kvm_path.display().to_string()),
|
||||
);
|
||||
}
|
||||
@@ -459,7 +462,7 @@ pub fn run(config: &crate::config::AppConfig) -> OtgSelfCheckResponse {
|
||||
false,
|
||||
OtgSelfCheckLevel::Warn,
|
||||
"Check one-kvm UDC binding",
|
||||
Some("Ensure HID/MSD is enabled and initialized successfully"),
|
||||
Some("Ensure an OTG function is enabled and initialized successfully"),
|
||||
Some(one_kvm_udc_path.display().to_string()),
|
||||
);
|
||||
} else {
|
||||
@@ -494,18 +497,19 @@ pub fn run(config: &crate::config::AppConfig) -> OtgSelfCheckResponse {
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
if hid_functions.is_empty() {
|
||||
let hid_functions_ok = hid_backend_is_otg != hid_functions.is_empty();
|
||||
if !hid_functions_ok {
|
||||
push_otg_check(
|
||||
&mut checks,
|
||||
"hid_functions_present",
|
||||
false,
|
||||
if hid_backend_is_otg {
|
||||
OtgSelfCheckLevel::Error
|
||||
OtgSelfCheckLevel::Error,
|
||||
"Check OTG HID function selection",
|
||||
Some(if hid_backend_is_otg {
|
||||
"Check OTG HID config and enable at least one HID function"
|
||||
} else {
|
||||
OtgSelfCheckLevel::Warn
|
||||
},
|
||||
"Check HID function creation",
|
||||
Some("Check OTG HID config and enable at least one HID function"),
|
||||
"OTG HID must be disabled when input is handled by another backend"
|
||||
}),
|
||||
Some(functions_path.display().to_string()),
|
||||
);
|
||||
} else {
|
||||
@@ -514,7 +518,7 @@ pub fn run(config: &crate::config::AppConfig) -> OtgSelfCheckResponse {
|
||||
"hid_functions_present",
|
||||
true,
|
||||
OtgSelfCheckLevel::Info,
|
||||
"Check HID function creation",
|
||||
"Check OTG HID function selection",
|
||||
None::<String>,
|
||||
Some(functions_path.display().to_string()),
|
||||
);
|
||||
@@ -569,9 +573,13 @@ pub fn run(config: &crate::config::AppConfig) -> OtgSelfCheckResponse {
|
||||
.map(|name| name.to_string_lossy().to_string())
|
||||
})
|
||||
.is_some_and(|name| {
|
||||
name == "okvm-br0" || name == config.otg_network.bridge_interface
|
||||
name == "okvm-br0"
|
||||
|| name == config.otg_network.bridge_interface
|
||||
|| (config.otg_network.bridge_interface.is_empty()
|
||||
&& std::path::Path::new("/sys/class/net").join(name).join("bridge").is_dir())
|
||||
&& std::path::Path::new("/sys/class/net")
|
||||
.join(name)
|
||||
.join("bridge")
|
||||
.is_dir())
|
||||
});
|
||||
push_otg_check(
|
||||
&mut checks,
|
||||
@@ -843,7 +851,7 @@ pub fn run(config: &crate::config::AppConfig) -> OtgSelfCheckResponse {
|
||||
overall_ok: error_count == 0,
|
||||
error_count,
|
||||
warning_count,
|
||||
hid_backend: format!("{:?}", config.hid.backend).to_lowercase(),
|
||||
hid_backend: config.hid.backend.as_str().to_string(),
|
||||
selected_udc,
|
||||
bound_udc,
|
||||
udc_state,
|
||||
|
||||
@@ -8,8 +8,7 @@ use super::bridge::NetworkBridgeRuntime;
|
||||
use super::manager::{wait_for_hid_devices, GadgetDescriptor, OtgGadgetManager};
|
||||
use super::msd::{MsdFunction, MsdInquiryStrings, MsdLunConfig};
|
||||
use crate::config::{
|
||||
HidBackend, HidConfig, MsdConfig, OtgDescriptorConfig, OtgHidFunctions, OtgNetworkConfig,
|
||||
UacConfig,
|
||||
HidConfig, MsdConfig, OtgDescriptorConfig, OtgHidFunctions, OtgNetworkConfig, UacConfig,
|
||||
};
|
||||
use crate::error::{AppError, Result};
|
||||
|
||||
@@ -40,6 +39,17 @@ pub struct OtgNetworkStatus {
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct OtgRuntimeStatus {
|
||||
pub enabled: bool,
|
||||
pub health: OtgRuntimeHealth,
|
||||
pub gadget_active: bool,
|
||||
pub udc: Option<String>,
|
||||
pub udc_state: Option<String>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
impl HidDevicePaths {
|
||||
pub fn existing_paths(&self) -> Vec<PathBuf> {
|
||||
[
|
||||
@@ -93,7 +103,7 @@ impl OtgDesiredState {
|
||||
network.validate()?;
|
||||
uac.validate()?;
|
||||
msd.validate()?;
|
||||
let hid_functions = if hid.backend == HidBackend::Otg {
|
||||
let hid_functions = if hid.backend.uses_otg_hid() {
|
||||
let functions = hid.constrained_otg_functions();
|
||||
Some(functions)
|
||||
} else {
|
||||
@@ -107,7 +117,13 @@ impl OtgDesiredState {
|
||||
.as_ref()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.or_else(OtgGadgetManager::find_udc)
|
||||
.or_else(|| {
|
||||
if hid.backend.uses_ch9329() {
|
||||
None
|
||||
} else {
|
||||
OtgGadgetManager::find_udc()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -249,6 +265,25 @@ impl OtgService {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn runtime_status(&self, enabled: bool) -> OtgRuntimeStatus {
|
||||
let state = self.state.read().await;
|
||||
let udc_state = state.configured_udc.as_ref().and_then(|udc| {
|
||||
// Only UDC names, never a path supplied through a configuration file.
|
||||
if udc.contains('/') || udc == "." || udc == ".." {
|
||||
return None;
|
||||
}
|
||||
crate::utils::read_trimmed(&PathBuf::from(format!("/sys/class/udc/{udc}/state")))
|
||||
});
|
||||
OtgRuntimeStatus {
|
||||
enabled,
|
||||
health: state.health,
|
||||
gadget_active: state.gadget_active,
|
||||
udc: state.configured_udc.clone(),
|
||||
udc_state,
|
||||
error: state.error.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn apply_config(
|
||||
&self,
|
||||
hid: &HidConfig,
|
||||
@@ -687,6 +722,38 @@ impl From<&OtgDescriptorConfig> for GadgetDescriptor {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::HidBackend;
|
||||
|
||||
#[test]
|
||||
fn composite_gadget_supports_every_advanced_function_combination_without_hid() {
|
||||
let hid = HidConfig {
|
||||
backend: HidBackend::Ch9329Otg,
|
||||
otg_udc: Some("test.udc".into()),
|
||||
otg_keyboard_leds: true,
|
||||
..Default::default()
|
||||
};
|
||||
for mask in 0..8 {
|
||||
let msd = MsdConfig {
|
||||
enabled: mask & 1 != 0,
|
||||
..Default::default()
|
||||
};
|
||||
let network = OtgNetworkConfig {
|
||||
enabled: mask & 2 != 0,
|
||||
..Default::default()
|
||||
};
|
||||
let uac = UacConfig {
|
||||
enabled: mask & 4 != 0,
|
||||
..Default::default()
|
||||
};
|
||||
let desired = OtgDesiredState::from_config(&hid, &msd, &network, &uac).unwrap();
|
||||
assert!(!desired.hid_enabled());
|
||||
assert!(!desired.keyboard_leds);
|
||||
assert_eq!(desired.msd_enabled, msd.enabled);
|
||||
assert_eq!(desired.network_enabled(), network.enabled);
|
||||
assert_eq!(desired.uac.enabled, uac.enabled);
|
||||
assert_eq!(desired.udc.is_some(), mask != 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn service_new_and_availability_probe() {
|
||||
|
||||
Reference in New Issue
Block a user