mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-31 18:11:54 +08:00
feat(video): 事务化切换与前端统一编排,增强视频输入格式支持
- 后端:切换事务+transition_id,/stream/mode 返回 switching/transition_id 与实际 codec - 事件:新增 mode_switching/mode_ready,config/webrtc_ready/mode_changed 关联事务 - 编码/格式:扩展 NV21/NV16/NV24/RGB/BGR 输入与转换链路,RKMPP direct input 优化 - 前端:useVideoSession 统一切换,失败回退真实切回 MJPEG,菜单格式同步修复 - 清理:useVideoStream 降级为 MJPEG-only
This commit is contained in:
@@ -109,15 +109,25 @@ pub fn read_file(path: &Path) -> Result<String> {
|
||||
|
||||
/// Create directory if not exists
|
||||
pub fn create_dir(path: &Path) -> Result<()> {
|
||||
fs::create_dir_all(path)
|
||||
.map_err(|e| AppError::Internal(format!("Failed to create directory {}: {}", path.display(), e)))
|
||||
fs::create_dir_all(path).map_err(|e| {
|
||||
AppError::Internal(format!(
|
||||
"Failed to create directory {}: {}",
|
||||
path.display(),
|
||||
e
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Remove directory
|
||||
pub fn remove_dir(path: &Path) -> Result<()> {
|
||||
if path.exists() {
|
||||
fs::remove_dir(path)
|
||||
.map_err(|e| AppError::Internal(format!("Failed to remove directory {}: {}", path.display(), e)))?;
|
||||
fs::remove_dir(path).map_err(|e| {
|
||||
AppError::Internal(format!(
|
||||
"Failed to remove directory {}: {}",
|
||||
path.display(),
|
||||
e
|
||||
))
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -125,14 +135,21 @@ pub fn remove_dir(path: &Path) -> Result<()> {
|
||||
/// Remove file
|
||||
pub fn remove_file(path: &Path) -> Result<()> {
|
||||
if path.exists() {
|
||||
fs::remove_file(path)
|
||||
.map_err(|e| AppError::Internal(format!("Failed to remove file {}: {}", path.display(), e)))?;
|
||||
fs::remove_file(path).map_err(|e| {
|
||||
AppError::Internal(format!("Failed to remove file {}: {}", path.display(), e))
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create symlink
|
||||
pub fn create_symlink(src: &Path, dest: &Path) -> Result<()> {
|
||||
std::os::unix::fs::symlink(src, dest)
|
||||
.map_err(|e| AppError::Internal(format!("Failed to create symlink {} -> {}: {}", dest.display(), src.display(), e)))
|
||||
std::os::unix::fs::symlink(src, dest).map_err(|e| {
|
||||
AppError::Internal(format!(
|
||||
"Failed to create symlink {} -> {}: {}",
|
||||
dest.display(),
|
||||
src.display(),
|
||||
e
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use tracing::debug;
|
||||
|
||||
use super::configfs::{create_dir, create_symlink, remove_dir, remove_file, write_bytes, write_file};
|
||||
use super::configfs::{
|
||||
create_dir, create_symlink, remove_dir, remove_file, write_bytes, write_file,
|
||||
};
|
||||
use super::function::{FunctionMeta, GadgetFunction};
|
||||
use super::report_desc::{CONSUMER_CONTROL, KEYBOARD, MOUSE_ABSOLUTE, MOUSE_RELATIVE};
|
||||
use crate::error::Result;
|
||||
@@ -39,20 +41,20 @@ impl HidFunctionType {
|
||||
/// Get HID protocol
|
||||
pub fn protocol(&self) -> u8 {
|
||||
match self {
|
||||
HidFunctionType::Keyboard => 1, // Keyboard
|
||||
HidFunctionType::MouseRelative => 2, // Mouse
|
||||
HidFunctionType::MouseAbsolute => 2, // Mouse
|
||||
HidFunctionType::ConsumerControl => 0, // None
|
||||
HidFunctionType::Keyboard => 1, // Keyboard
|
||||
HidFunctionType::MouseRelative => 2, // Mouse
|
||||
HidFunctionType::MouseAbsolute => 2, // Mouse
|
||||
HidFunctionType::ConsumerControl => 0, // None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get HID subclass
|
||||
pub fn subclass(&self) -> u8 {
|
||||
match self {
|
||||
HidFunctionType::Keyboard => 1, // Boot interface
|
||||
HidFunctionType::MouseRelative => 1, // Boot interface
|
||||
HidFunctionType::MouseAbsolute => 0, // No boot interface
|
||||
HidFunctionType::ConsumerControl => 0, // No boot interface
|
||||
HidFunctionType::Keyboard => 1, // Boot interface
|
||||
HidFunctionType::MouseRelative => 1, // Boot interface
|
||||
HidFunctionType::MouseAbsolute => 0, // No boot interface
|
||||
HidFunctionType::ConsumerControl => 0, // No boot interface
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,14 +171,27 @@ impl GadgetFunction for HidFunction {
|
||||
create_dir(&func_path)?;
|
||||
|
||||
// Set HID parameters
|
||||
write_file(&func_path.join("protocol"), &self.func_type.protocol().to_string())?;
|
||||
write_file(&func_path.join("subclass"), &self.func_type.subclass().to_string())?;
|
||||
write_file(&func_path.join("report_length"), &self.func_type.report_length().to_string())?;
|
||||
write_file(
|
||||
&func_path.join("protocol"),
|
||||
&self.func_type.protocol().to_string(),
|
||||
)?;
|
||||
write_file(
|
||||
&func_path.join("subclass"),
|
||||
&self.func_type.subclass().to_string(),
|
||||
)?;
|
||||
write_file(
|
||||
&func_path.join("report_length"),
|
||||
&self.func_type.report_length().to_string(),
|
||||
)?;
|
||||
|
||||
// Write report descriptor
|
||||
write_bytes(&func_path.join("report_desc"), self.func_type.report_desc())?;
|
||||
|
||||
debug!("Created HID function: {} at {}", self.name(), func_path.display());
|
||||
debug!(
|
||||
"Created HID function: {} at {}",
|
||||
self.name(),
|
||||
func_path.display()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ use tracing::{debug, error, info, warn};
|
||||
|
||||
use super::configfs::{
|
||||
create_dir, find_udc, is_configfs_available, remove_dir, write_file, CONFIGFS_PATH,
|
||||
DEFAULT_GADGET_NAME, DEFAULT_USB_BCD_DEVICE, USB_BCD_USB, DEFAULT_USB_PRODUCT_ID, DEFAULT_USB_VENDOR_ID,
|
||||
DEFAULT_GADGET_NAME, DEFAULT_USB_BCD_DEVICE, DEFAULT_USB_PRODUCT_ID, DEFAULT_USB_VENDOR_ID,
|
||||
USB_BCD_USB,
|
||||
};
|
||||
use super::endpoint::{EndpointAllocator, DEFAULT_MAX_ENDPOINTS};
|
||||
use super::function::{FunctionMeta, GadgetFunction};
|
||||
@@ -77,7 +78,11 @@ impl OtgGadgetManager {
|
||||
}
|
||||
|
||||
/// Create a new gadget manager with custom descriptor
|
||||
pub fn with_descriptor(gadget_name: &str, max_endpoints: u8, descriptor: GadgetDescriptor) -> Self {
|
||||
pub fn with_descriptor(
|
||||
gadget_name: &str,
|
||||
max_endpoints: u8,
|
||||
descriptor: GadgetDescriptor,
|
||||
) -> Self {
|
||||
let gadget_path = PathBuf::from(CONFIGFS_PATH).join(gadget_name);
|
||||
let config_path = gadget_path.join("configs/c.1");
|
||||
|
||||
@@ -303,10 +308,22 @@ impl OtgGadgetManager {
|
||||
|
||||
/// Set USB device descriptors
|
||||
fn set_device_descriptors(&self) -> Result<()> {
|
||||
write_file(&self.gadget_path.join("idVendor"), &format!("0x{:04x}", self.descriptor.vendor_id))?;
|
||||
write_file(&self.gadget_path.join("idProduct"), &format!("0x{:04x}", self.descriptor.product_id))?;
|
||||
write_file(&self.gadget_path.join("bcdDevice"), &format!("0x{:04x}", self.descriptor.device_version))?;
|
||||
write_file(&self.gadget_path.join("bcdUSB"), &format!("0x{:04x}", USB_BCD_USB))?;
|
||||
write_file(
|
||||
&self.gadget_path.join("idVendor"),
|
||||
&format!("0x{:04x}", self.descriptor.vendor_id),
|
||||
)?;
|
||||
write_file(
|
||||
&self.gadget_path.join("idProduct"),
|
||||
&format!("0x{:04x}", self.descriptor.product_id),
|
||||
)?;
|
||||
write_file(
|
||||
&self.gadget_path.join("bcdDevice"),
|
||||
&format!("0x{:04x}", self.descriptor.device_version),
|
||||
)?;
|
||||
write_file(
|
||||
&self.gadget_path.join("bcdUSB"),
|
||||
&format!("0x{:04x}", USB_BCD_USB),
|
||||
)?;
|
||||
write_file(&self.gadget_path.join("bDeviceClass"), "0x00")?; // Composite device
|
||||
write_file(&self.gadget_path.join("bDeviceSubClass"), "0x00")?;
|
||||
write_file(&self.gadget_path.join("bDeviceProtocol"), "0x00")?;
|
||||
@@ -319,8 +336,14 @@ impl OtgGadgetManager {
|
||||
let strings_path = self.gadget_path.join("strings/0x409");
|
||||
create_dir(&strings_path)?;
|
||||
|
||||
write_file(&strings_path.join("serialnumber"), &self.descriptor.serial_number)?;
|
||||
write_file(&strings_path.join("manufacturer"), &self.descriptor.manufacturer)?;
|
||||
write_file(
|
||||
&strings_path.join("serialnumber"),
|
||||
&self.descriptor.serial_number,
|
||||
)?;
|
||||
write_file(
|
||||
&strings_path.join("manufacturer"),
|
||||
&self.descriptor.manufacturer,
|
||||
)?;
|
||||
write_file(&strings_path.join("product"), &self.descriptor.product)?;
|
||||
debug!("Created USB strings");
|
||||
Ok(())
|
||||
@@ -349,7 +372,10 @@ impl OtgGadgetManager {
|
||||
|
||||
/// Get endpoint usage info
|
||||
pub fn endpoint_info(&self) -> (u8, u8) {
|
||||
(self.endpoint_allocator.used(), self.endpoint_allocator.max())
|
||||
(
|
||||
self.endpoint_allocator.used(),
|
||||
self.endpoint_allocator.max(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get gadget path
|
||||
|
||||
@@ -161,7 +161,10 @@ impl MsdFunction {
|
||||
// Write only changed attributes
|
||||
let cdrom_changed = current_cdrom != new_cdrom;
|
||||
if cdrom_changed {
|
||||
debug!("Updating LUN {} cdrom: {} -> {}", lun, current_cdrom, new_cdrom);
|
||||
debug!(
|
||||
"Updating LUN {} cdrom: {} -> {}",
|
||||
lun, current_cdrom, new_cdrom
|
||||
);
|
||||
write_file(&lun_path.join("cdrom"), new_cdrom)?;
|
||||
}
|
||||
if current_ro != new_ro {
|
||||
@@ -169,11 +172,17 @@ impl MsdFunction {
|
||||
write_file(&lun_path.join("ro"), new_ro)?;
|
||||
}
|
||||
if current_removable != new_removable {
|
||||
debug!("Updating LUN {} removable: {} -> {}", lun, current_removable, new_removable);
|
||||
debug!(
|
||||
"Updating LUN {} removable: {} -> {}",
|
||||
lun, current_removable, new_removable
|
||||
);
|
||||
write_file(&lun_path.join("removable"), new_removable)?;
|
||||
}
|
||||
if current_nofua != new_nofua {
|
||||
debug!("Updating LUN {} nofua: {} -> {}", lun, current_nofua, new_nofua);
|
||||
debug!(
|
||||
"Updating LUN {} nofua: {} -> {}",
|
||||
lun, current_nofua, new_nofua
|
||||
);
|
||||
write_file(&lun_path.join("nofua"), new_nofua)?;
|
||||
}
|
||||
|
||||
@@ -258,11 +267,17 @@ impl MsdFunction {
|
||||
// forced_eject forcibly detaches the backing file regardless of host state
|
||||
let forced_eject_path = lun_path.join("forced_eject");
|
||||
if forced_eject_path.exists() {
|
||||
debug!("Using forced_eject to disconnect LUN {} at {:?}", lun, forced_eject_path);
|
||||
debug!(
|
||||
"Using forced_eject to disconnect LUN {} at {:?}",
|
||||
lun, forced_eject_path
|
||||
);
|
||||
match write_file(&forced_eject_path, "1") {
|
||||
Ok(_) => debug!("forced_eject write succeeded"),
|
||||
Err(e) => {
|
||||
warn!("forced_eject write failed: {}, falling back to clearing file", e);
|
||||
warn!(
|
||||
"forced_eject write failed: {}, falling back to clearing file",
|
||||
e
|
||||
);
|
||||
write_file(&lun_path.join("file"), "")?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,17 +135,17 @@ pub const MOUSE_ABSOLUTE: &[u8] = &[
|
||||
/// [0-1] Consumer Control Usage (16-bit little-endian)
|
||||
/// Supports: Play/Pause, Stop, Next/Prev Track, Mute, Volume Up/Down, etc.
|
||||
pub const CONSUMER_CONTROL: &[u8] = &[
|
||||
0x05, 0x0C, // Usage Page (Consumer)
|
||||
0x09, 0x01, // Usage (Consumer Control)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x05, 0x0C, // Usage Page (Consumer)
|
||||
0x09, 0x01, // Usage (Consumer Control)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x03, // Logical Maximum (1023)
|
||||
0x19, 0x00, // Usage Minimum (0)
|
||||
0x19, 0x00, // Usage Minimum (0)
|
||||
0x2A, 0xFF, 0x03, // Usage Maximum (1023)
|
||||
0x75, 0x10, // Report Size (16)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x00, // Input (Data, Array)
|
||||
0xC0, // End Collection
|
||||
0x75, 0x10, // Report Size (16)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x00, // Input (Data, Array)
|
||||
0xC0, // End Collection
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -27,8 +27,8 @@ use tracing::{debug, info, warn};
|
||||
|
||||
use super::manager::{wait_for_hid_devices, GadgetDescriptor, OtgGadgetManager};
|
||||
use super::msd::MsdFunction;
|
||||
use crate::error::{AppError, Result};
|
||||
use crate::config::OtgDescriptorConfig;
|
||||
use crate::error::{AppError, Result};
|
||||
|
||||
/// Bitflags for requested functions (lock-free)
|
||||
const FLAG_HID: u8 = 0b01;
|
||||
@@ -254,8 +254,9 @@ impl OtgService {
|
||||
|
||||
// Get MSD function
|
||||
let msd = self.msd_function.read().await;
|
||||
msd.clone()
|
||||
.ok_or_else(|| AppError::Internal("MSD function not set after gadget setup".to_string()))
|
||||
msd.clone().ok_or_else(|| {
|
||||
AppError::Internal("MSD function not set after gadget setup".to_string())
|
||||
})
|
||||
}
|
||||
|
||||
/// Disable MSD function
|
||||
@@ -465,7 +466,10 @@ impl OtgService {
|
||||
device_version: super::configfs::DEFAULT_USB_BCD_DEVICE,
|
||||
manufacturer: config.manufacturer.clone(),
|
||||
product: config.product.clone(),
|
||||
serial_number: config.serial_number.clone().unwrap_or_else(|| "0123456789".to_string()),
|
||||
serial_number: config
|
||||
.serial_number
|
||||
.clone()
|
||||
.unwrap_or_else(|| "0123456789".to_string()),
|
||||
};
|
||||
|
||||
// Update stored descriptor
|
||||
|
||||
Reference in New Issue
Block a user