mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-02-03 11:31:53 +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:
142
src/hid/otg.rs
142
src/hid/otg.rs
@@ -30,9 +30,11 @@ use tracing::{debug, info, trace, warn};
|
||||
|
||||
use super::backend::HidBackend;
|
||||
use super::keymap;
|
||||
use super::types::{ConsumerEvent, KeyEventType, KeyboardEvent, KeyboardReport, MouseEvent, MouseEventType};
|
||||
use super::types::{
|
||||
ConsumerEvent, KeyEventType, KeyboardEvent, KeyboardReport, MouseEvent, MouseEventType,
|
||||
};
|
||||
use crate::error::{AppError, Result};
|
||||
use crate::otg::{HidDevicePaths, wait_for_hid_devices};
|
||||
use crate::otg::{wait_for_hid_devices, HidDevicePaths};
|
||||
|
||||
/// Device type for ensure_device operations
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -73,11 +75,21 @@ impl LedState {
|
||||
/// Convert to raw byte
|
||||
pub fn to_byte(&self) -> u8 {
|
||||
let mut b = 0u8;
|
||||
if self.num_lock { b |= 0x01; }
|
||||
if self.caps_lock { b |= 0x02; }
|
||||
if self.scroll_lock { b |= 0x04; }
|
||||
if self.compose { b |= 0x08; }
|
||||
if self.kana { b |= 0x10; }
|
||||
if self.num_lock {
|
||||
b |= 0x01;
|
||||
}
|
||||
if self.caps_lock {
|
||||
b |= 0x02;
|
||||
}
|
||||
if self.scroll_lock {
|
||||
b |= 0x04;
|
||||
}
|
||||
if self.compose {
|
||||
b |= 0x08;
|
||||
}
|
||||
if self.kana {
|
||||
b |= 0x10;
|
||||
}
|
||||
b
|
||||
}
|
||||
}
|
||||
@@ -145,7 +157,9 @@ impl OtgBackend {
|
||||
keyboard_path: paths.keyboard,
|
||||
mouse_rel_path: paths.mouse_relative,
|
||||
mouse_abs_path: paths.mouse_absolute,
|
||||
consumer_path: paths.consumer.unwrap_or_else(|| PathBuf::from("/dev/hidg3")),
|
||||
consumer_path: paths
|
||||
.consumer
|
||||
.unwrap_or_else(|| PathBuf::from("/dev/hidg3")),
|
||||
keyboard_dev: Mutex::new(None),
|
||||
mouse_rel_dev: Mutex::new(None),
|
||||
mouse_abs_dev: Mutex::new(None),
|
||||
@@ -198,7 +212,8 @@ impl OtgBackend {
|
||||
Ok(1) => {
|
||||
// Device ready, check for errors
|
||||
if let Some(revents) = pollfd[0].revents() {
|
||||
if revents.contains(PollFlags::POLLERR) || revents.contains(PollFlags::POLLHUP) {
|
||||
if revents.contains(PollFlags::POLLERR) || revents.contains(PollFlags::POLLHUP)
|
||||
{
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::BrokenPipe,
|
||||
"Device error or hangup",
|
||||
@@ -297,7 +312,10 @@ impl OtgBackend {
|
||||
// Close the device if open (device was removed)
|
||||
let mut dev = dev_mutex.lock();
|
||||
if dev.is_some() {
|
||||
debug!("Device path {} no longer exists, closing handle", path.display());
|
||||
debug!(
|
||||
"Device path {} no longer exists, closing handle",
|
||||
path.display()
|
||||
);
|
||||
*dev = None;
|
||||
}
|
||||
self.online.store(false, Ordering::Relaxed);
|
||||
@@ -335,20 +353,24 @@ impl OtgBackend {
|
||||
.custom_flags(libc::O_NONBLOCK)
|
||||
.open(path)
|
||||
.map_err(|e| {
|
||||
AppError::Internal(format!("Failed to open HID device {}: {}", path.display(), e))
|
||||
AppError::Internal(format!(
|
||||
"Failed to open HID device {}: {}",
|
||||
path.display(),
|
||||
e
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert I/O error to HidError with appropriate error code
|
||||
fn io_error_to_hid_error(e: std::io::Error, operation: &str) -> AppError {
|
||||
let error_code = match e.raw_os_error() {
|
||||
Some(32) => "epipe", // EPIPE - broken pipe
|
||||
Some(108) => "eshutdown", // ESHUTDOWN - transport endpoint shutdown
|
||||
Some(11) => "eagain", // EAGAIN - resource temporarily unavailable
|
||||
Some(6) => "enxio", // ENXIO - no such device or address
|
||||
Some(19) => "enodev", // ENODEV - no such device
|
||||
Some(5) => "eio", // EIO - I/O error
|
||||
Some(2) => "enoent", // ENOENT - no such file or directory
|
||||
Some(32) => "epipe", // EPIPE - broken pipe
|
||||
Some(108) => "eshutdown", // ESHUTDOWN - transport endpoint shutdown
|
||||
Some(11) => "eagain", // EAGAIN - resource temporarily unavailable
|
||||
Some(6) => "enxio", // ENXIO - no such device or address
|
||||
Some(19) => "enodev", // ENODEV - no such device
|
||||
Some(5) => "eio", // EIO - I/O error
|
||||
Some(2) => "enoent", // ENOENT - no such file or directory
|
||||
_ => "io_error",
|
||||
};
|
||||
|
||||
@@ -361,9 +383,7 @@ impl OtgBackend {
|
||||
|
||||
/// Check if all HID device files exist
|
||||
pub fn check_devices_exist(&self) -> bool {
|
||||
self.keyboard_path.exists()
|
||||
&& self.mouse_rel_path.exists()
|
||||
&& self.mouse_abs_path.exists()
|
||||
self.keyboard_path.exists() && self.mouse_rel_path.exists() && self.mouse_abs_path.exists()
|
||||
}
|
||||
|
||||
/// Get list of missing device paths
|
||||
@@ -415,7 +435,10 @@ impl OtgBackend {
|
||||
self.eagain_count.store(0, Ordering::Relaxed);
|
||||
debug!("Keyboard ESHUTDOWN, closing for recovery");
|
||||
*dev = None;
|
||||
Err(Self::io_error_to_hid_error(e, "Failed to write keyboard report"))
|
||||
Err(Self::io_error_to_hid_error(
|
||||
e,
|
||||
"Failed to write keyboard report",
|
||||
))
|
||||
}
|
||||
Some(11) => {
|
||||
// EAGAIN after poll - should be rare, silently drop
|
||||
@@ -426,7 +449,10 @@ impl OtgBackend {
|
||||
self.online.store(false, Ordering::Relaxed);
|
||||
self.eagain_count.store(0, Ordering::Relaxed);
|
||||
warn!("Keyboard write error: {}", e);
|
||||
Err(Self::io_error_to_hid_error(e, "Failed to write keyboard report"))
|
||||
Err(Self::io_error_to_hid_error(
|
||||
e,
|
||||
"Failed to write keyboard report",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -472,7 +498,10 @@ impl OtgBackend {
|
||||
self.eagain_count.store(0, Ordering::Relaxed);
|
||||
debug!("Relative mouse ESHUTDOWN, closing for recovery");
|
||||
*dev = None;
|
||||
Err(Self::io_error_to_hid_error(e, "Failed to write mouse report"))
|
||||
Err(Self::io_error_to_hid_error(
|
||||
e,
|
||||
"Failed to write mouse report",
|
||||
))
|
||||
}
|
||||
Some(11) => {
|
||||
// EAGAIN after poll - should be rare, silently drop
|
||||
@@ -482,7 +511,10 @@ impl OtgBackend {
|
||||
self.online.store(false, Ordering::Relaxed);
|
||||
self.eagain_count.store(0, Ordering::Relaxed);
|
||||
warn!("Relative mouse write error: {}", e);
|
||||
Err(Self::io_error_to_hid_error(e, "Failed to write mouse report"))
|
||||
Err(Self::io_error_to_hid_error(
|
||||
e,
|
||||
"Failed to write mouse report",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -534,7 +566,10 @@ impl OtgBackend {
|
||||
self.eagain_count.store(0, Ordering::Relaxed);
|
||||
debug!("Absolute mouse ESHUTDOWN, closing for recovery");
|
||||
*dev = None;
|
||||
Err(Self::io_error_to_hid_error(e, "Failed to write mouse report"))
|
||||
Err(Self::io_error_to_hid_error(
|
||||
e,
|
||||
"Failed to write mouse report",
|
||||
))
|
||||
}
|
||||
Some(11) => {
|
||||
// EAGAIN after poll - should be rare, silently drop
|
||||
@@ -544,7 +579,10 @@ impl OtgBackend {
|
||||
self.online.store(false, Ordering::Relaxed);
|
||||
self.eagain_count.store(0, Ordering::Relaxed);
|
||||
warn!("Absolute mouse write error: {}", e);
|
||||
Err(Self::io_error_to_hid_error(e, "Failed to write mouse report"))
|
||||
Err(Self::io_error_to_hid_error(
|
||||
e,
|
||||
"Failed to write mouse report",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -590,7 +628,10 @@ impl OtgBackend {
|
||||
self.online.store(false, Ordering::Relaxed);
|
||||
debug!("Consumer control ESHUTDOWN, closing for recovery");
|
||||
*dev = None;
|
||||
Err(Self::io_error_to_hid_error(e, "Failed to write consumer report"))
|
||||
Err(Self::io_error_to_hid_error(
|
||||
e,
|
||||
"Failed to write consumer report",
|
||||
))
|
||||
}
|
||||
Some(11) => {
|
||||
// EAGAIN after poll - silently drop
|
||||
@@ -599,7 +640,10 @@ impl OtgBackend {
|
||||
_ => {
|
||||
self.online.store(false, Ordering::Relaxed);
|
||||
warn!("Consumer control write error: {}", e);
|
||||
Err(Self::io_error_to_hid_error(e, "Failed to write consumer report"))
|
||||
Err(Self::io_error_to_hid_error(
|
||||
e,
|
||||
"Failed to write consumer report",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -632,7 +676,10 @@ impl OtgBackend {
|
||||
}
|
||||
Ok(_) => Ok(None), // No data available
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(None),
|
||||
Err(e) => Err(AppError::Internal(format!("Failed to read LED state: {}", e))),
|
||||
Err(e) => Err(AppError::Internal(format!(
|
||||
"Failed to read LED state: {}",
|
||||
e
|
||||
))),
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -677,34 +724,55 @@ impl HidBackend for OtgBackend {
|
||||
*self.keyboard_dev.lock() = Some(file);
|
||||
info!("Keyboard device opened: {}", self.keyboard_path.display());
|
||||
} else {
|
||||
warn!("Keyboard device not found: {}", self.keyboard_path.display());
|
||||
warn!(
|
||||
"Keyboard device not found: {}",
|
||||
self.keyboard_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
// Open relative mouse device
|
||||
if self.mouse_rel_path.exists() {
|
||||
let file = Self::open_device(&self.mouse_rel_path)?;
|
||||
*self.mouse_rel_dev.lock() = Some(file);
|
||||
info!("Relative mouse device opened: {}", self.mouse_rel_path.display());
|
||||
info!(
|
||||
"Relative mouse device opened: {}",
|
||||
self.mouse_rel_path.display()
|
||||
);
|
||||
} else {
|
||||
warn!("Relative mouse device not found: {}", self.mouse_rel_path.display());
|
||||
warn!(
|
||||
"Relative mouse device not found: {}",
|
||||
self.mouse_rel_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
// Open absolute mouse device
|
||||
if self.mouse_abs_path.exists() {
|
||||
let file = Self::open_device(&self.mouse_abs_path)?;
|
||||
*self.mouse_abs_dev.lock() = Some(file);
|
||||
info!("Absolute mouse device opened: {}", self.mouse_abs_path.display());
|
||||
info!(
|
||||
"Absolute mouse device opened: {}",
|
||||
self.mouse_abs_path.display()
|
||||
);
|
||||
} else {
|
||||
warn!("Absolute mouse device not found: {}", self.mouse_abs_path.display());
|
||||
warn!(
|
||||
"Absolute mouse device not found: {}",
|
||||
self.mouse_abs_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
// Open consumer control device (optional, may not exist on older setups)
|
||||
if self.consumer_path.exists() {
|
||||
let file = Self::open_device(&self.consumer_path)?;
|
||||
*self.consumer_dev.lock() = Some(file);
|
||||
info!("Consumer control device opened: {}", self.consumer_path.display());
|
||||
info!(
|
||||
"Consumer control device opened: {}",
|
||||
self.consumer_path.display()
|
||||
);
|
||||
} else {
|
||||
debug!("Consumer control device not found: {}", self.consumer_path.display());
|
||||
debug!(
|
||||
"Consumer control device not found: {}",
|
||||
self.consumer_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
// Mark as online if all devices opened successfully
|
||||
|
||||
Reference in New Issue
Block a user