mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-06-14 11:42:02 +08:00
refactor: 删除部分多余的代码和注释
This commit is contained in:
234
src/hid/otg.rs
234
src/hid/otg.rs
@@ -1,28 +1,11 @@
|
||||
//! OTG USB Gadget HID backend
|
||||
//! Linux gadget HID: `/dev/hidg*` opened from [`crate::otg::OtgService`].
|
||||
//! Typical nodes: hidg0 keyboard, hidg1 relative mouse, hidg2 absolute, hidg3 consumer control.
|
||||
//!
|
||||
//! This backend uses Linux USB Gadget API to emulate USB HID devices.
|
||||
//! It opens the HID gadget device nodes created by `OtgService`.
|
||||
//! Depending on the configured OTG profile, this may include:
|
||||
//! - hidg0: Keyboard
|
||||
//! - hidg1: Relative Mouse
|
||||
//! - hidg2: Absolute Mouse
|
||||
//! - hidg3: Consumer Control Keyboard
|
||||
//!
|
||||
//! Requirements:
|
||||
//! - USB OTG/Device controller (UDC)
|
||||
//! - ConfigFS with USB gadget support
|
||||
//! - Root privileges for gadget setup
|
||||
//!
|
||||
//! Error Recovery:
|
||||
//! This module implements automatic device reconnection based on PiKVM's approach.
|
||||
//! When ESHUTDOWN or EAGAIN errors occur (common during MSD operations), the device
|
||||
//! file handles are closed and reopened on the next operation.
|
||||
//! See: https://github.com/raspberrypi/linux/issues/4373
|
||||
//! Polled timed writes (JetKVM-style). Treat `ESHUTDOWN` (108) by closing handles and reopening; keep fd on `EAGAIN` (11). Host/gadget teardown during MSD resembles PiKVM. <https://github.com/raspberrypi/linux/issues/4373>
|
||||
|
||||
use async_trait::async_trait;
|
||||
use nix::poll::{poll, PollFd, PollFlags, PollTimeout};
|
||||
use parking_lot::Mutex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{Read, Write};
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
@@ -40,9 +23,9 @@ use super::types::{
|
||||
ConsumerEvent, KeyEventType, KeyboardEvent, KeyboardReport, MouseEvent, MouseEventType,
|
||||
};
|
||||
use crate::error::{AppError, Result};
|
||||
use crate::events::LedState;
|
||||
use crate::otg::{wait_for_hid_devices, HidDevicePaths};
|
||||
|
||||
/// Device type for ensure_device operations
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum DeviceType {
|
||||
Keyboard,
|
||||
@@ -51,23 +34,7 @@ enum DeviceType {
|
||||
ConsumerControl,
|
||||
}
|
||||
|
||||
/// Keyboard LED state
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
pub struct LedState {
|
||||
/// Num Lock LED
|
||||
pub num_lock: bool,
|
||||
/// Caps Lock LED
|
||||
pub caps_lock: bool,
|
||||
/// Scroll Lock LED
|
||||
pub scroll_lock: bool,
|
||||
/// Compose LED
|
||||
pub compose: bool,
|
||||
/// Kana LED
|
||||
pub kana: bool,
|
||||
}
|
||||
|
||||
impl LedState {
|
||||
/// Create from raw byte
|
||||
pub fn from_byte(b: u8) -> Self {
|
||||
Self {
|
||||
num_lock: b & 0x01 != 0,
|
||||
@@ -78,7 +45,6 @@ impl LedState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert to raw byte
|
||||
pub fn to_byte(&self) -> u8 {
|
||||
let mut b = 0u8;
|
||||
if self.num_lock {
|
||||
@@ -100,76 +66,37 @@ impl LedState {
|
||||
}
|
||||
}
|
||||
|
||||
/// OTG HID backend with 4 devices
|
||||
///
|
||||
/// This backend opens HID device files created by OtgService.
|
||||
/// It does NOT manage the USB gadget itself - that's handled by OtgService.
|
||||
///
|
||||
/// ## Error Recovery
|
||||
///
|
||||
/// Based on PiKVM's implementation, this backend automatically handles:
|
||||
/// - EAGAIN (errno 11): Resource temporarily unavailable - just retry later, don't close device
|
||||
/// - ESHUTDOWN (errno 108): Transport endpoint shutdown - close and reopen device
|
||||
///
|
||||
/// When ESHUTDOWN occurs, the device file handle is closed and will be
|
||||
/// reopened on the next operation attempt.
|
||||
/// Opens `/dev/hidg*` nodes provisioned by `OtgService`; gadget lifecycle is not handled here.
|
||||
pub struct OtgBackend {
|
||||
/// Keyboard device path (/dev/hidg0)
|
||||
keyboard_path: Option<PathBuf>,
|
||||
/// Relative mouse device path (/dev/hidg1)
|
||||
mouse_rel_path: Option<PathBuf>,
|
||||
/// Absolute mouse device path (/dev/hidg2)
|
||||
mouse_abs_path: Option<PathBuf>,
|
||||
/// Consumer control device path (/dev/hidg3)
|
||||
consumer_path: Option<PathBuf>,
|
||||
/// Keyboard device file
|
||||
keyboard_dev: Mutex<Option<File>>,
|
||||
/// Relative mouse device file
|
||||
mouse_rel_dev: Mutex<Option<File>>,
|
||||
/// Absolute mouse device file
|
||||
mouse_abs_dev: Mutex<Option<File>>,
|
||||
/// Consumer control device file
|
||||
consumer_dev: Mutex<Option<File>>,
|
||||
/// Whether keyboard LED/status feedback is enabled.
|
||||
keyboard_leds_enabled: bool,
|
||||
/// Current keyboard state
|
||||
keyboard_state: Mutex<KeyboardReport>,
|
||||
/// Current mouse button state
|
||||
mouse_buttons: AtomicU8,
|
||||
/// Last known LED state (using parking_lot::RwLock for sync access)
|
||||
led_state: Arc<parking_lot::RwLock<LedState>>,
|
||||
/// Screen resolution for absolute mouse (using parking_lot::RwLock for sync access)
|
||||
screen_resolution: parking_lot::RwLock<Option<(u32, u32)>>,
|
||||
/// UDC name for state checking (e.g., "fcc00000.usb")
|
||||
udc_name: Arc<parking_lot::RwLock<Option<String>>>,
|
||||
/// Whether the backend has been initialized.
|
||||
initialized: AtomicBool,
|
||||
/// Whether the device is currently online (UDC configured and devices accessible)
|
||||
online: AtomicBool,
|
||||
/// Last backend error state.
|
||||
last_error: parking_lot::RwLock<Option<(String, String)>>,
|
||||
/// Last error log time for throttling (using parking_lot for sync)
|
||||
last_error_log: parking_lot::Mutex<std::time::Instant>,
|
||||
/// Error count since last successful operation (for log throttling)
|
||||
error_count: AtomicU8,
|
||||
/// Consecutive EAGAIN count (for offline threshold detection)
|
||||
eagain_count: AtomicU8,
|
||||
/// Runtime change notifier.
|
||||
runtime_notify_tx: watch::Sender<()>,
|
||||
/// Runtime monitor stop flag.
|
||||
runtime_worker_stop: Arc<AtomicBool>,
|
||||
/// Runtime monitor thread.
|
||||
runtime_worker: Mutex<Option<thread::JoinHandle<()>>>,
|
||||
}
|
||||
|
||||
/// Write timeout in milliseconds (same as JetKVM's hidWriteTimeout)
|
||||
const HID_WRITE_TIMEOUT_MS: i32 = 20;
|
||||
|
||||
impl OtgBackend {
|
||||
/// Create OTG backend from device paths provided by OtgService
|
||||
///
|
||||
/// This is the ONLY way to create an OtgBackend - it no longer manages
|
||||
/// the USB gadget itself. The gadget must already be set up by OtgService.
|
||||
/// Gadget must already exist; paths come from `OtgService`.
|
||||
pub fn from_handles(paths: HidDevicePaths) -> Result<Self> {
|
||||
let (runtime_notify_tx, _runtime_notify_rx) = watch::channel(());
|
||||
Ok(Self {
|
||||
@@ -234,7 +161,6 @@ impl OtgBackend {
|
||||
}
|
||||
}
|
||||
|
||||
/// Log throttled error message (max once per second)
|
||||
fn log_throttled_error(&self, msg: &str) {
|
||||
let mut last_log = self.last_error_log.lock();
|
||||
let now = std::time::Instant::now();
|
||||
@@ -251,24 +177,17 @@ impl OtgBackend {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset error count on successful operation
|
||||
fn reset_error_count(&self) {
|
||||
self.error_count.store(0, Ordering::Relaxed);
|
||||
// Also reset EAGAIN count - successful operation means device is working
|
||||
self.eagain_count.store(0, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Write data to HID device with timeout (JetKVM style)
|
||||
///
|
||||
/// Uses poll() to wait for device to be ready for writing.
|
||||
/// If timeout expires, silently drops the data (acceptable for mouse movement).
|
||||
/// Returns Ok(true) if write succeeded, Ok(false) if timed out (silently dropped).
|
||||
/// Poll-based write with `HID_WRITE_TIMEOUT_MS`; timeout → drop (JetKVM-style).
|
||||
fn write_with_timeout(&self, file: &mut File, data: &[u8]) -> std::io::Result<bool> {
|
||||
let mut pollfd = [PollFd::new(file.as_fd(), PollFlags::POLLOUT)];
|
||||
|
||||
match poll(&mut pollfd, PollTimeout::from(HID_WRITE_TIMEOUT_MS as u16)) {
|
||||
Ok(1) => {
|
||||
// Device ready, check for errors
|
||||
if let Some(revents) = pollfd[0].revents() {
|
||||
if revents.contains(PollFlags::POLLERR) || revents.contains(PollFlags::POLLHUP)
|
||||
{
|
||||
@@ -278,12 +197,10 @@ impl OtgBackend {
|
||||
));
|
||||
}
|
||||
}
|
||||
// Write the data
|
||||
file.write_all(data)?;
|
||||
Ok(true)
|
||||
}
|
||||
Ok(0) => {
|
||||
// Timeout - silently drop (JetKVM behavior)
|
||||
trace!("HID write timeout, dropping data");
|
||||
Ok(false)
|
||||
}
|
||||
@@ -292,7 +209,6 @@ impl OtgBackend {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the UDC name for state checking
|
||||
pub fn set_udc_name(&self, udc: &str) {
|
||||
*self.udc_name.write() = Some(udc.to_string());
|
||||
}
|
||||
@@ -324,15 +240,11 @@ impl OtgBackend {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the UDC is in "configured" state
|
||||
///
|
||||
/// This is based on PiKVM's `__is_udc_configured()` method.
|
||||
/// The UDC state file indicates whether the USB host has enumerated and configured the gadget.
|
||||
/// `true` when `/sys/class/udc/<name>/state` reads `configured` (PiKVM-style).
|
||||
pub fn is_udc_configured(&self) -> bool {
|
||||
Self::read_udc_configured(&self.udc_name)
|
||||
}
|
||||
|
||||
/// Find the first available UDC
|
||||
fn find_udc() -> Option<String> {
|
||||
let udc_path = PathBuf::from("/sys/class/udc");
|
||||
if let Ok(entries) = fs::read_dir(&udc_path) {
|
||||
@@ -345,12 +257,7 @@ impl OtgBackend {
|
||||
None
|
||||
}
|
||||
|
||||
/// Ensure a device is open and ready for I/O
|
||||
///
|
||||
/// This method is based on PiKVM's `__ensure_device()` pattern:
|
||||
/// 1. Check if device path exists, close handle if not
|
||||
/// 2. If handle is None but path exists, reopen the device
|
||||
/// 3. Return whether the device is ready for I/O
|
||||
/// PiKVM-style: drop handle if node missing; reopen when path reappears.
|
||||
fn ensure_device(&self, device_type: DeviceType) -> Result<()> {
|
||||
let (path_opt, dev_mutex) = match device_type {
|
||||
DeviceType::Keyboard => (&self.keyboard_path, &self.keyboard_dev),
|
||||
@@ -372,9 +279,7 @@ impl OtgBackend {
|
||||
}
|
||||
};
|
||||
|
||||
// Check if device path exists
|
||||
if !path.exists() {
|
||||
// Close the device if open (device was removed)
|
||||
let mut dev = dev_mutex.lock();
|
||||
if dev.is_some() {
|
||||
debug!(
|
||||
@@ -392,7 +297,6 @@ impl OtgBackend {
|
||||
});
|
||||
}
|
||||
|
||||
// If device is not open, try to open it
|
||||
let mut dev = dev_mutex.lock();
|
||||
if dev.is_none() {
|
||||
match Self::open_device(path) {
|
||||
@@ -415,7 +319,6 @@ impl OtgBackend {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Open a HID device file with read/write access
|
||||
fn open_device(path: &PathBuf) -> Result<File> {
|
||||
OpenOptions::new()
|
||||
.read(true)
|
||||
@@ -431,16 +334,15 @@ impl OtgBackend {
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert I/O error to HidError with appropriate error code
|
||||
fn io_error_code(e: &std::io::Error) -> &'static str {
|
||||
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",
|
||||
Some(108) => "eshutdown",
|
||||
Some(11) => "eagain",
|
||||
Some(6) => "enxio",
|
||||
Some(19) => "enodev",
|
||||
Some(5) => "eio",
|
||||
Some(2) => "enoent",
|
||||
_ => "io_error",
|
||||
}
|
||||
}
|
||||
@@ -455,7 +357,6 @@ impl OtgBackend {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if all HID device files exist
|
||||
pub fn check_devices_exist(&self) -> bool {
|
||||
self.keyboard_path.as_ref().is_none_or(|p| p.exists())
|
||||
&& self.mouse_rel_path.as_ref().is_none_or(|p| p.exists())
|
||||
@@ -463,7 +364,6 @@ impl OtgBackend {
|
||||
&& self.consumer_path.as_ref().is_none_or(|p| p.exists())
|
||||
}
|
||||
|
||||
/// Get list of missing device paths
|
||||
pub fn get_missing_devices(&self) -> Vec<String> {
|
||||
let mut missing = Vec::new();
|
||||
if let Some(ref path) = self.keyboard_path {
|
||||
@@ -484,17 +384,11 @@ impl OtgBackend {
|
||||
missing
|
||||
}
|
||||
|
||||
/// Send keyboard report (8 bytes)
|
||||
///
|
||||
/// This method ensures the device is open before writing, and handles
|
||||
/// ESHUTDOWN errors by closing the device handle for later reconnection.
|
||||
/// Uses write_with_timeout to avoid blocking on busy devices.
|
||||
fn send_keyboard_report(&self, report: &KeyboardReport) -> Result<()> {
|
||||
if self.keyboard_path.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Ensure device is ready
|
||||
self.ensure_device(DeviceType::Keyboard)?;
|
||||
|
||||
let mut dev = self.keyboard_dev.lock();
|
||||
@@ -508,7 +402,6 @@ impl OtgBackend {
|
||||
Ok(())
|
||||
}
|
||||
Ok(false) => {
|
||||
// Timeout - silently dropped (JetKVM behavior)
|
||||
self.log_throttled_error("HID keyboard write timeout, dropped");
|
||||
Ok(())
|
||||
}
|
||||
@@ -517,7 +410,6 @@ impl OtgBackend {
|
||||
|
||||
match error_code {
|
||||
Some(108) => {
|
||||
// ESHUTDOWN - endpoint closed, need to reopen device
|
||||
self.eagain_count.store(0, Ordering::Relaxed);
|
||||
debug!("Keyboard ESHUTDOWN, closing for recovery");
|
||||
*dev = None;
|
||||
@@ -531,7 +423,6 @@ impl OtgBackend {
|
||||
))
|
||||
}
|
||||
Some(11) => {
|
||||
// EAGAIN after poll - should be rare, silently drop
|
||||
trace!("Keyboard EAGAIN after poll, dropping");
|
||||
Ok(())
|
||||
}
|
||||
@@ -559,17 +450,11 @@ impl OtgBackend {
|
||||
}
|
||||
}
|
||||
|
||||
/// Send relative mouse report (4 bytes: buttons, dx, dy, wheel)
|
||||
///
|
||||
/// This method ensures the device is open before writing, and handles
|
||||
/// ESHUTDOWN errors by closing the device handle for later reconnection.
|
||||
/// Uses write_with_timeout to avoid blocking on busy devices.
|
||||
fn send_mouse_report_relative(&self, buttons: u8, dx: i8, dy: i8, wheel: i8) -> Result<()> {
|
||||
if self.mouse_rel_path.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Ensure device is ready
|
||||
self.ensure_device(DeviceType::MouseRelative)?;
|
||||
|
||||
let mut dev = self.mouse_rel_dev.lock();
|
||||
@@ -582,10 +467,7 @@ impl OtgBackend {
|
||||
trace!("Sent relative mouse report: {:02X?}", data);
|
||||
Ok(())
|
||||
}
|
||||
Ok(false) => {
|
||||
// Timeout - silently dropped (JetKVM behavior)
|
||||
Ok(())
|
||||
}
|
||||
Ok(false) => Ok(()),
|
||||
Err(e) => {
|
||||
let error_code = e.raw_os_error();
|
||||
|
||||
@@ -603,10 +485,7 @@ impl OtgBackend {
|
||||
"Failed to write mouse report",
|
||||
))
|
||||
}
|
||||
Some(11) => {
|
||||
// EAGAIN after poll - should be rare, silently drop
|
||||
Ok(())
|
||||
}
|
||||
Some(11) => Ok(()),
|
||||
_ => {
|
||||
self.eagain_count.store(0, Ordering::Relaxed);
|
||||
warn!("Relative mouse write error: {}", e);
|
||||
@@ -631,17 +510,11 @@ impl OtgBackend {
|
||||
}
|
||||
}
|
||||
|
||||
/// Send absolute mouse report (6 bytes: buttons, x_lo, x_hi, y_lo, y_hi, wheel)
|
||||
///
|
||||
/// This method ensures the device is open before writing, and handles
|
||||
/// ESHUTDOWN errors by closing the device handle for later reconnection.
|
||||
/// Uses write_with_timeout to avoid blocking on busy devices.
|
||||
fn send_mouse_report_absolute(&self, buttons: u8, x: u16, y: u16, wheel: i8) -> Result<()> {
|
||||
if self.mouse_abs_path.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Ensure device is ready
|
||||
self.ensure_device(DeviceType::MouseAbsolute)?;
|
||||
|
||||
let mut dev = self.mouse_abs_dev.lock();
|
||||
@@ -660,10 +533,7 @@ impl OtgBackend {
|
||||
self.reset_error_count();
|
||||
Ok(())
|
||||
}
|
||||
Ok(false) => {
|
||||
// Timeout - silently dropped (JetKVM behavior)
|
||||
Ok(())
|
||||
}
|
||||
Ok(false) => Ok(()),
|
||||
Err(e) => {
|
||||
let error_code = e.raw_os_error();
|
||||
|
||||
@@ -681,10 +551,7 @@ impl OtgBackend {
|
||||
"Failed to write mouse report",
|
||||
))
|
||||
}
|
||||
Some(11) => {
|
||||
// EAGAIN after poll - should be rare, silently drop
|
||||
Ok(())
|
||||
}
|
||||
Some(11) => Ok(()),
|
||||
_ => {
|
||||
self.eagain_count.store(0, Ordering::Relaxed);
|
||||
warn!("Absolute mouse write error: {}", e);
|
||||
@@ -709,35 +576,27 @@ impl OtgBackend {
|
||||
}
|
||||
}
|
||||
|
||||
/// Send consumer control report (2 bytes: usage_lo, usage_hi)
|
||||
///
|
||||
/// Sends a consumer control usage code and then releases it (sends 0x0000).
|
||||
/// Press (`usage`) then release (`0x0000`).
|
||||
fn send_consumer_report(&self, usage: u16) -> Result<()> {
|
||||
if self.consumer_path.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Ensure device is ready
|
||||
self.ensure_device(DeviceType::ConsumerControl)?;
|
||||
|
||||
let mut dev = self.consumer_dev.lock();
|
||||
if let Some(ref mut file) = *dev {
|
||||
// Send the usage code
|
||||
let data = [(usage & 0xFF) as u8, (usage >> 8) as u8];
|
||||
match self.write_with_timeout(file, &data) {
|
||||
Ok(true) => {
|
||||
trace!("Sent consumer report: {:02X?}", data);
|
||||
// Send release (0x0000)
|
||||
let release = [0u8, 0u8];
|
||||
let _ = self.write_with_timeout(file, &release);
|
||||
self.mark_online();
|
||||
self.reset_error_count();
|
||||
Ok(())
|
||||
}
|
||||
Ok(false) => {
|
||||
// Timeout - silently dropped
|
||||
Ok(())
|
||||
}
|
||||
Ok(false) => Ok(()),
|
||||
Err(e) => {
|
||||
let error_code = e.raw_os_error();
|
||||
match error_code {
|
||||
@@ -753,10 +612,7 @@ impl OtgBackend {
|
||||
"Failed to write consumer report",
|
||||
))
|
||||
}
|
||||
Some(11) => {
|
||||
// EAGAIN after poll - silently drop
|
||||
Ok(())
|
||||
}
|
||||
Some(11) => Ok(()),
|
||||
_ => {
|
||||
warn!("Consumer control write error: {}", e);
|
||||
self.record_error(
|
||||
@@ -780,12 +636,10 @@ impl OtgBackend {
|
||||
}
|
||||
}
|
||||
|
||||
/// Send consumer control event
|
||||
pub fn send_consumer(&self, event: ConsumerEvent) -> Result<()> {
|
||||
self.send_consumer_report(event.usage)
|
||||
}
|
||||
|
||||
/// Get last known LED state
|
||||
pub fn led_state(&self) -> LedState {
|
||||
*self.led_state.read()
|
||||
}
|
||||
@@ -975,7 +829,6 @@ impl HidBackend for OtgBackend {
|
||||
async fn init(&self) -> Result<()> {
|
||||
info!("Initializing OTG HID backend");
|
||||
|
||||
// Auto-detect UDC name for state checking only if OtgService did not provide one
|
||||
if self.udc_name.read().is_none() {
|
||||
if let Some(udc) = Self::find_udc() {
|
||||
info!("Auto-detected UDC: {}", udc);
|
||||
@@ -985,7 +838,6 @@ impl HidBackend for OtgBackend {
|
||||
info!("Using configured UDC: {}", udc);
|
||||
}
|
||||
|
||||
// Wait for devices to appear (they should already exist from OtgService)
|
||||
let mut device_paths = Vec::new();
|
||||
if let Some(ref path) = self.keyboard_path {
|
||||
device_paths.push(path.clone());
|
||||
@@ -1010,7 +862,6 @@ impl HidBackend for OtgBackend {
|
||||
return Err(AppError::Internal("HID devices did not appear".into()));
|
||||
}
|
||||
|
||||
// Open keyboard device
|
||||
if let Some(ref path) = self.keyboard_path {
|
||||
if path.exists() {
|
||||
let file = Self::open_device(path)?;
|
||||
@@ -1021,7 +872,6 @@ impl HidBackend for OtgBackend {
|
||||
}
|
||||
}
|
||||
|
||||
// Open relative mouse device
|
||||
if let Some(ref path) = self.mouse_rel_path {
|
||||
if path.exists() {
|
||||
let file = Self::open_device(path)?;
|
||||
@@ -1032,7 +882,6 @@ impl HidBackend for OtgBackend {
|
||||
}
|
||||
}
|
||||
|
||||
// Open absolute mouse device
|
||||
if let Some(ref path) = self.mouse_abs_path {
|
||||
if path.exists() {
|
||||
let file = Self::open_device(path)?;
|
||||
@@ -1043,7 +892,6 @@ impl HidBackend for OtgBackend {
|
||||
}
|
||||
}
|
||||
|
||||
// Open consumer control device (optional, may not exist on older setups)
|
||||
if let Some(ref path) = self.consumer_path {
|
||||
if path.exists() {
|
||||
let file = Self::open_device(path)?;
|
||||
@@ -1054,7 +902,6 @@ impl HidBackend for OtgBackend {
|
||||
}
|
||||
}
|
||||
|
||||
// Mark as online if all devices opened successfully
|
||||
self.initialized.store(true, Ordering::Relaxed);
|
||||
self.notify_runtime_changed();
|
||||
self.start_runtime_worker();
|
||||
@@ -1066,7 +913,6 @@ impl HidBackend for OtgBackend {
|
||||
async fn send_keyboard(&self, event: KeyboardEvent) -> Result<()> {
|
||||
let usb_key = event.key.to_hid_usage();
|
||||
|
||||
// Handle modifier keys separately
|
||||
if event.key.is_modifier() {
|
||||
let mut state = self.keyboard_state.lock();
|
||||
|
||||
@@ -1084,7 +930,6 @@ impl HidBackend for OtgBackend {
|
||||
} else {
|
||||
let mut state = self.keyboard_state.lock();
|
||||
|
||||
// Update modifiers from event
|
||||
state.modifiers = event.modifiers.to_hid_byte();
|
||||
|
||||
match event.event_type {
|
||||
@@ -1110,15 +955,12 @@ impl HidBackend for OtgBackend {
|
||||
|
||||
match event.event_type {
|
||||
MouseEventType::Move => {
|
||||
// Relative movement - use hidg1
|
||||
let dx = event.x.clamp(-127, 127) as i8;
|
||||
let dy = event.y.clamp(-127, 127) as i8;
|
||||
self.send_mouse_report_relative(buttons, dx, dy, 0)?;
|
||||
}
|
||||
MouseEventType::MoveAbs => {
|
||||
// Absolute movement - use hidg2
|
||||
// Frontend sends 0-32767 range directly (standard HID absolute mouse range)
|
||||
// Don't send button state with move - buttons are handled separately on relative device
|
||||
// Coordinates 0–32767; buttons are sent only on the relative endpoint.
|
||||
let x = event.x.clamp(0, 32767) as u16;
|
||||
let y = event.y.clamp(0, 32767) as u16;
|
||||
self.send_mouse_report_absolute(0, x, y, 0)?;
|
||||
@@ -1127,7 +969,6 @@ impl HidBackend for OtgBackend {
|
||||
if let Some(button) = event.button {
|
||||
let bit = button.to_hid_bit();
|
||||
let new_buttons = self.mouse_buttons.fetch_or(bit, Ordering::Relaxed) | bit;
|
||||
// Send on relative device for button clicks
|
||||
self.send_mouse_report_relative(new_buttons, 0, 0, 0)?;
|
||||
}
|
||||
}
|
||||
@@ -1147,7 +988,6 @@ impl HidBackend for OtgBackend {
|
||||
}
|
||||
|
||||
async fn reset(&self) -> Result<()> {
|
||||
// Reset keyboard
|
||||
{
|
||||
let mut state = self.keyboard_state.lock();
|
||||
state.clear();
|
||||
@@ -1156,7 +996,6 @@ impl HidBackend for OtgBackend {
|
||||
self.send_keyboard_report(&report)?;
|
||||
}
|
||||
|
||||
// Reset mouse
|
||||
self.mouse_buttons.store(0, Ordering::Relaxed);
|
||||
self.send_mouse_report_relative(0, 0, 0, 0)?;
|
||||
self.send_mouse_report_absolute(0, 0, 0, 0)?;
|
||||
@@ -1168,16 +1007,13 @@ impl HidBackend for OtgBackend {
|
||||
async fn shutdown(&self) -> Result<()> {
|
||||
self.stop_runtime_worker();
|
||||
|
||||
// Reset before closing
|
||||
self.reset().await?;
|
||||
|
||||
// Close devices
|
||||
*self.keyboard_dev.lock() = None;
|
||||
*self.mouse_rel_dev.lock() = None;
|
||||
*self.mouse_abs_dev.lock() = None;
|
||||
*self.consumer_dev.lock() = None;
|
||||
|
||||
// Gadget cleanup is handled by OtgService, not here
|
||||
self.initialized.store(false, Ordering::Relaxed);
|
||||
self.online.store(false, Ordering::Relaxed);
|
||||
self.clear_error();
|
||||
@@ -1199,31 +1035,18 @@ impl HidBackend for OtgBackend {
|
||||
self.send_consumer_report(event.usage)
|
||||
}
|
||||
|
||||
fn set_screen_resolution(&mut self, width: u32, height: u32) {
|
||||
fn set_screen_resolution(&self, width: u32, height: u32) {
|
||||
*self.screen_resolution.write() = Some((width, height));
|
||||
self.notify_runtime_changed();
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if OTG HID gadget is available
|
||||
pub fn is_otg_available() -> bool {
|
||||
// Check for existing HID devices (they should be created by OtgService)
|
||||
let kb = PathBuf::from("/dev/hidg0");
|
||||
let mouse_rel = PathBuf::from("/dev/hidg1");
|
||||
let mouse_abs = PathBuf::from("/dev/hidg2");
|
||||
|
||||
kb.exists() || mouse_rel.exists() || mouse_abs.exists()
|
||||
}
|
||||
|
||||
/// Implement Drop for OtgBackend to close device files
|
||||
impl Drop for OtgBackend {
|
||||
fn drop(&mut self) {
|
||||
self.runtime_worker_stop.store(true, Ordering::Relaxed);
|
||||
if let Some(handle) = self.runtime_worker.get_mut().take() {
|
||||
let _ = handle.join();
|
||||
}
|
||||
// Close device files
|
||||
// Note: Gadget cleanup is handled by OtgService, not here
|
||||
*self.keyboard_dev.lock() = None;
|
||||
*self.mouse_rel_dev.lock() = None;
|
||||
*self.mouse_abs_dev.lock() = None;
|
||||
@@ -1236,12 +1059,6 @@ impl Drop for OtgBackend {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_otg_availability_check() {
|
||||
// This just tests the function runs without panicking
|
||||
let _available = is_otg_available();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_led_state() {
|
||||
let state = LedState::from_byte(0b00000011);
|
||||
@@ -1254,7 +1071,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_report_sizes() {
|
||||
// Keyboard report is 8 bytes
|
||||
let kb_report = KeyboardReport::default();
|
||||
assert_eq!(kb_report.to_bytes().len(), 8);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user