From 394baca9381c3fbb83fd61497bbbd49fd9a4fbf0 Mon Sep 17 00:00:00 2001 From: mofeng-git Date: Tue, 10 Feb 2026 21:37:33 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=A1=A5=E9=BD=90=20ATX=20=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E5=99=A8=E7=BC=BA=E5=A4=B1=E6=8E=A5=E5=8F=A3=E5=B9=B6?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=85=A8=E9=A1=B9=E7=9B=AE=20clippy=20-D=20w?= =?UTF-8?q?arnings=20=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/hwcodec/src/ffmpeg_hw/mod.rs | 6 +- libs/hwcodec/src/ffmpeg_ram/decode.rs | 3 +- src/atx/controller.rs | 406 ++++++++++---------------- src/atx/mod.rs | 5 +- src/atx/types.rs | 79 +---- src/atx/wol.rs | 6 +- src/audio/capture.rs | 9 +- src/audio/controller.rs | 2 + src/audio/device.rs | 4 +- src/audio/monitor.rs | 9 +- src/audio/streamer.rs | 22 +- src/auth/middleware.rs | 6 +- src/auth/user.rs | 13 +- src/config/schema.rs | 58 +--- src/events/types.rs | 1 + src/extensions/types.rs | 12 +- src/hid/backend.rs | 7 +- src/hid/ch9329.rs | 14 +- src/hid/mod.rs | 37 ++- src/hid/monitor.rs | 9 +- src/hid/otg.rs | 25 +- src/main.rs | 8 +- src/msd/controller.rs | 5 +- src/msd/image.rs | 4 +- src/msd/monitor.rs | 7 +- src/msd/types.rs | 7 +- src/msd/ventoy_drive.rs | 11 +- src/otg/manager.rs | 6 +- src/otg/service.rs | 18 +- src/rustdesk/bytes_codec.rs | 2 +- src/rustdesk/config.rs | 18 +- src/rustdesk/connection.rs | 15 +- src/rustdesk/crypto.rs | 2 +- src/rustdesk/frame_adapters.rs | 3 +- src/rustdesk/mod.rs | 3 +- src/rustdesk/rendezvous.rs | 2 +- src/state.rs | 1 + src/stream/mjpeg_streamer.rs | 8 +- src/utils/mod.rs | 4 +- src/video/capture.rs | 2 +- src/video/device.rs | 39 +-- src/video/encoder/h264.rs | 14 +- src/video/encoder/h265.rs | 16 +- src/video/encoder/registry.rs | 1 + src/video/encoder/traits.rs | 7 +- src/video/encoder/vp8.rs | 16 +- src/video/encoder/vp9.rs | 16 +- src/video/frame.rs | 5 + src/video/shared_video_pipeline.rs | 68 +++-- src/video/stream_manager.rs | 14 +- src/video/streamer.rs | 12 +- src/video/v4l2r_capture.rs | 36 +-- src/video/video_session.rs | 1 - src/web/handlers/config/apply.rs | 4 +- src/web/handlers/extensions.rs | 40 +-- src/web/handlers/mod.rs | 21 +- src/web/static_files.rs | 6 +- src/webrtc/config.rs | 7 +- src/webrtc/peer.rs | 1 - src/webrtc/rtp.rs | 15 +- src/webrtc/track.rs | 2 +- src/webrtc/universal_session.rs | 19 +- src/webrtc/video_track.rs | 3 +- src/webrtc/webrtc_streamer.rs | 12 +- 64 files changed, 474 insertions(+), 760 deletions(-) diff --git a/libs/hwcodec/src/ffmpeg_hw/mod.rs b/libs/hwcodec/src/ffmpeg_hw/mod.rs index 222c9d14..a80e4ba4 100644 --- a/libs/hwcodec/src/ffmpeg_hw/mod.rs +++ b/libs/hwcodec/src/ffmpeg_hw/mod.rs @@ -31,8 +31,10 @@ unsafe impl Send for HwMjpegH26xPipeline {} impl HwMjpegH26xPipeline { pub fn new(config: HwMjpegH26xConfig) -> Result { unsafe { - let dec = CString::new(config.decoder.as_str()).map_err(|_| "decoder name invalid".to_string())?; - let enc = CString::new(config.encoder.as_str()).map_err(|_| "encoder name invalid".to_string())?; + let dec = CString::new(config.decoder.as_str()) + .map_err(|_| "decoder name invalid".to_string())?; + let enc = CString::new(config.encoder.as_str()) + .map_err(|_| "encoder name invalid".to_string())?; let ctx = ffmpeg_hw_mjpeg_h26x_new( dec.as_ptr(), enc.as_ptr(), diff --git a/libs/hwcodec/src/ffmpeg_ram/decode.rs b/libs/hwcodec/src/ffmpeg_ram/decode.rs index df0512a3..0cd9caf4 100644 --- a/libs/hwcodec/src/ffmpeg_ram/decode.rs +++ b/libs/hwcodec/src/ffmpeg_ram/decode.rs @@ -1,8 +1,7 @@ use crate::{ ffmpeg::{init_av_log, AVPixelFormat}, ffmpeg_ram::{ - ffmpeg_ram_decode, ffmpeg_ram_free_decoder, ffmpeg_ram_last_error, - ffmpeg_ram_new_decoder, + ffmpeg_ram_decode, ffmpeg_ram_free_decoder, ffmpeg_ram_last_error, ffmpeg_ram_new_decoder, }, }; use std::{ diff --git a/src/atx/controller.rs b/src/atx/controller.rs index 16e8aa07..31dd3841 100644 --- a/src/atx/controller.rs +++ b/src/atx/controller.rs @@ -8,11 +8,11 @@ use tracing::{debug, info, warn}; use super::executor::{timing, AtxKeyExecutor}; use super::led::LedSensor; -use super::types::{AtxKeyConfig, AtxLedConfig, AtxState, PowerStatus}; +use super::types::{AtxKeyConfig, AtxLedConfig, AtxState, AtxAction, PowerStatus}; use crate::error::{AppError, Result}; /// ATX power control configuration -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct AtxControllerConfig { /// Whether ATX is enabled pub enabled: bool, @@ -24,17 +24,6 @@ pub struct AtxControllerConfig { pub led: AtxLedConfig, } -impl Default for AtxControllerConfig { - fn default() -> Self { - Self { - enabled: false, - power: AtxKeyConfig::default(), - reset: AtxKeyConfig::default(), - led: AtxLedConfig::default(), - } - } -} - /// Internal state holding all ATX components /// Grouped together to reduce lock acquisitions struct AtxInner { @@ -54,34 +43,7 @@ pub struct AtxController { } impl AtxController { - /// Create a new ATX controller with the specified configuration - pub fn new(config: AtxControllerConfig) -> Self { - Self { - inner: RwLock::new(AtxInner { - config, - power_executor: None, - reset_executor: None, - led_sensor: None, - }), - } - } - - /// Create a disabled ATX controller - pub fn disabled() -> Self { - Self::new(AtxControllerConfig::default()) - } - - /// Initialize the ATX controller and its executors - pub async fn init(&self) -> Result<()> { - let mut inner = self.inner.write().await; - - if !inner.config.enabled { - info!("ATX disabled in configuration"); - return Ok(()); - } - - info!("Initializing ATX controller"); - + async fn init_components(inner: &mut AtxInner) { // Initialize power executor if inner.config.power.is_configured() { let mut executor = AtxKeyExecutor::new(inner.config.power.clone()); @@ -123,234 +85,180 @@ impl AtxController { inner.led_sensor = Some(sensor); } } - - info!("ATX controller initialized successfully"); - Ok(()) } - /// Reload the ATX controller with new configuration - /// - /// This is called when configuration changes and supports hot-reload. - pub async fn reload(&self, new_config: AtxControllerConfig) -> Result<()> { - info!("Reloading ATX controller with new configuration"); + async fn shutdown_components(inner: &mut AtxInner) { + if let Some(executor) = inner.power_executor.as_mut() { + if let Err(e) = executor.shutdown().await { + warn!("Failed to shutdown power executor: {}", e); + } + } + inner.power_executor = None; - // Shutdown existing executors - self.shutdown_internal().await?; + if let Some(executor) = inner.reset_executor.as_mut() { + if let Err(e) = executor.shutdown().await { + warn!("Failed to shutdown reset executor: {}", e); + } + } + inner.reset_executor = None; - // Update configuration and re-initialize - { - let mut inner = self.inner.write().await; - inner.config = new_config; + if let Some(sensor) = inner.led_sensor.as_mut() { + if let Err(e) = sensor.shutdown().await { + warn!("Failed to shutdown LED sensor: {}", e); + } + } + inner.led_sensor = None; + } + + /// Create a new ATX controller with the specified configuration + pub fn new(config: AtxControllerConfig) -> Self { + Self { + inner: RwLock::new(AtxInner { + config, + power_executor: None, + reset_executor: None, + led_sensor: None, + }), + } + } + + /// Create a disabled ATX controller + pub fn disabled() -> Self { + Self::new(AtxControllerConfig::default()) + } + + /// Initialize the ATX controller and its executors + pub async fn init(&self) -> Result<()> { + let mut inner = self.inner.write().await; + + if !inner.config.enabled { + info!("ATX disabled in configuration"); + return Ok(()); } - // Re-initialize - self.init().await?; + info!("Initializing ATX controller"); + + Self::init_components(&mut inner).await; - info!("ATX controller reloaded successfully"); Ok(()) } - /// Get current ATX state (single lock acquisition) + /// Reload ATX controller configuration + pub async fn reload(&self, config: AtxControllerConfig) -> Result<()> { + let mut inner = self.inner.write().await; + + info!("Reloading ATX controller configuration"); + + // Shutdown existing components first, then rebuild with new config. + Self::shutdown_components(&mut inner).await; + inner.config = config; + + if !inner.config.enabled { + info!("ATX disabled after reload"); + return Ok(()); + } + + Self::init_components(&mut inner).await; + info!("ATX controller reloaded"); + + Ok(()) + } + + /// Shutdown ATX controller and release all resources + pub async fn shutdown(&self) -> Result<()> { + let mut inner = self.inner.write().await; + Self::shutdown_components(&mut inner).await; + info!("ATX controller shutdown complete"); + Ok(()) + } + + /// Trigger a power action (short/long/reset) + pub async fn trigger_power_action(&self, action: AtxAction) -> Result<()> { + let inner = self.inner.read().await; + + match action { + AtxAction::Short | AtxAction::Long => { + if let Some(executor) = &inner.power_executor { + let duration = match action { + AtxAction::Short => timing::SHORT_PRESS, + AtxAction::Long => timing::LONG_PRESS, + _ => unreachable!(), + }; + executor.pulse(duration).await?; + } else { + return Err(AppError::Config( + "Power button not configured for ATX controller".to_string(), + )); + } + } + AtxAction::Reset => { + if let Some(executor) = &inner.reset_executor { + executor.pulse(timing::RESET_PRESS).await?; + } else { + return Err(AppError::Config( + "Reset button not configured for ATX controller".to_string(), + )); + } + } + } + + Ok(()) + } + + /// Trigger a short power button press + pub async fn power_short(&self) -> Result<()> { + self.trigger_power_action(AtxAction::Short).await + } + + /// Trigger a long power button press + pub async fn power_long(&self) -> Result<()> { + self.trigger_power_action(AtxAction::Long).await + } + + /// Trigger a reset button press + pub async fn reset(&self) -> Result<()> { + self.trigger_power_action(AtxAction::Reset).await + } + + /// Get the current power status using the LED sensor (if configured) + pub async fn power_status(&self) -> PowerStatus { + let inner = self.inner.read().await; + + if let Some(sensor) = &inner.led_sensor { + match sensor.read().await { + Ok(status) => status, + Err(e) => { + debug!("Failed to read ATX LED sensor: {}", e); + PowerStatus::Unknown + } + } + } else { + PowerStatus::Unknown + } + } + + /// Get a snapshot of the ATX state for API responses pub async fn state(&self) -> AtxState { let inner = self.inner.read().await; - let power_status = if let Some(sensor) = inner.led_sensor.as_ref() { - sensor.read().await.unwrap_or(PowerStatus::Unknown) + let power_status = if let Some(sensor) = &inner.led_sensor { + match sensor.read().await { + Ok(status) => status, + Err(e) => { + debug!("Failed to read ATX LED sensor: {}", e); + PowerStatus::Unknown + } + } } else { PowerStatus::Unknown }; AtxState { available: inner.config.enabled, - power_configured: inner - .power_executor - .as_ref() - .map(|e| e.is_initialized()) - .unwrap_or(false), - reset_configured: inner - .reset_executor - .as_ref() - .map(|e| e.is_initialized()) - .unwrap_or(false), + power_configured: inner.power_executor.is_some(), + reset_configured: inner.reset_executor.is_some(), power_status, - led_supported: inner - .led_sensor - .as_ref() - .map(|s| s.is_initialized()) - .unwrap_or(false), + led_supported: inner.led_sensor.is_some(), } } - - /// Get current state as SystemEvent - pub async fn current_state_event(&self) -> crate::events::SystemEvent { - let state = self.state().await; - crate::events::SystemEvent::AtxStateChanged { - power_status: state.power_status, - } - } - - /// Check if ATX is available - pub async fn is_available(&self) -> bool { - let inner = self.inner.read().await; - inner.config.enabled - } - - /// Check if power button is configured and initialized - pub async fn is_power_ready(&self) -> bool { - let inner = self.inner.read().await; - inner - .power_executor - .as_ref() - .map(|e| e.is_initialized()) - .unwrap_or(false) - } - - /// Check if reset button is configured and initialized - pub async fn is_reset_ready(&self) -> bool { - let inner = self.inner.read().await; - inner - .reset_executor - .as_ref() - .map(|e| e.is_initialized()) - .unwrap_or(false) - } - - /// Short press power button (turn on or graceful shutdown) - pub async fn power_short(&self) -> Result<()> { - let inner = self.inner.read().await; - let executor = inner - .power_executor - .as_ref() - .ok_or_else(|| AppError::Internal("Power button not configured".to_string()))?; - - info!( - "ATX: Short press power button ({}ms)", - timing::SHORT_PRESS.as_millis() - ); - executor.pulse(timing::SHORT_PRESS).await - } - - /// Long press power button (force power off) - pub async fn power_long(&self) -> Result<()> { - let inner = self.inner.read().await; - let executor = inner - .power_executor - .as_ref() - .ok_or_else(|| AppError::Internal("Power button not configured".to_string()))?; - - info!( - "ATX: Long press power button ({}ms)", - timing::LONG_PRESS.as_millis() - ); - executor.pulse(timing::LONG_PRESS).await - } - - /// Press reset button - pub async fn reset(&self) -> Result<()> { - let inner = self.inner.read().await; - let executor = inner - .reset_executor - .as_ref() - .ok_or_else(|| AppError::Internal("Reset button not configured".to_string()))?; - - info!( - "ATX: Press reset button ({}ms)", - timing::RESET_PRESS.as_millis() - ); - executor.pulse(timing::RESET_PRESS).await - } - - /// Get current power status from LED sensor - pub async fn power_status(&self) -> Result { - let inner = self.inner.read().await; - match inner.led_sensor.as_ref() { - Some(sensor) => sensor.read().await, - None => Ok(PowerStatus::Unknown), - } - } - - /// Shutdown the ATX controller - pub async fn shutdown(&self) -> Result<()> { - info!("Shutting down ATX controller"); - self.shutdown_internal().await?; - info!("ATX controller shutdown complete"); - Ok(()) - } - - /// Internal shutdown helper - async fn shutdown_internal(&self) -> Result<()> { - let mut inner = self.inner.write().await; - - // Shutdown power executor - if let Some(mut executor) = inner.power_executor.take() { - executor.shutdown().await.ok(); - } - - // Shutdown reset executor - if let Some(mut executor) = inner.reset_executor.take() { - executor.shutdown().await.ok(); - } - - // Shutdown LED sensor - if let Some(mut sensor) = inner.led_sensor.take() { - sensor.shutdown().await.ok(); - } - - Ok(()) - } -} - -impl Drop for AtxController { - fn drop(&mut self) { - debug!("ATX controller dropped"); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_controller_config_default() { - let config = AtxControllerConfig::default(); - assert!(!config.enabled); - assert!(!config.power.is_configured()); - assert!(!config.reset.is_configured()); - assert!(!config.led.is_configured()); - } - - #[test] - fn test_controller_creation() { - let controller = AtxController::disabled(); - assert!(controller.inner.try_read().is_ok()); - } - - #[tokio::test] - async fn test_controller_disabled_state() { - let controller = AtxController::disabled(); - let state = controller.state().await; - assert!(!state.available); - assert!(!state.power_configured); - assert!(!state.reset_configured); - } - - #[tokio::test] - async fn test_controller_init_disabled() { - let controller = AtxController::disabled(); - let result = controller.init().await; - assert!(result.is_ok()); - } - - #[tokio::test] - async fn test_controller_is_available() { - let controller = AtxController::disabled(); - assert!(!controller.is_available().await); - - let config = AtxControllerConfig { - enabled: true, - ..Default::default() - }; - let controller = AtxController::new(config); - assert!(controller.is_available().await); - } } diff --git a/src/atx/mod.rs b/src/atx/mod.rs index dd7c90b4..1f28a509 100644 --- a/src/atx/mod.rs +++ b/src/atx/mod.rs @@ -88,10 +88,7 @@ mod tests { #[test] fn test_discover_devices() { - let devices = discover_devices(); - // Just verify the function runs without error - assert!(devices.gpio_chips.len() >= 0); - assert!(devices.usb_relays.len() >= 0); + let _devices = discover_devices(); } #[test] diff --git a/src/atx/types.rs b/src/atx/types.rs index cea0e176..4523bdc6 100644 --- a/src/atx/types.rs +++ b/src/atx/types.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use typeshare::typeshare; /// Power status -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "lowercase")] pub enum PowerStatus { /// Power is on @@ -15,18 +15,13 @@ pub enum PowerStatus { /// Power is off Off, /// Power status unknown (no LED connected) + #[default] Unknown, } -impl Default for PowerStatus { - fn default() -> Self { - Self::Unknown - } -} - /// Driver type for ATX key operations #[typeshare] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "lowercase")] pub enum AtxDriverType { /// GPIO control via Linux character device @@ -34,36 +29,26 @@ pub enum AtxDriverType { /// USB HID relay module UsbRelay, /// Disabled / Not configured + #[default] None, } -impl Default for AtxDriverType { - fn default() -> Self { - Self::None - } -} - /// Active level for GPIO pins #[typeshare] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "lowercase")] pub enum ActiveLevel { /// Active high (default for most cases) + #[default] High, /// Active low (inverted) Low, } -impl Default for ActiveLevel { - fn default() -> Self { - Self::High - } -} - /// Configuration for a single ATX key (power or reset) /// This is the "four-tuple" configuration: (driver, device, pin/channel, level) #[typeshare] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] #[serde(default)] pub struct AtxKeyConfig { /// Driver type (GPIO or USB Relay) @@ -80,17 +65,6 @@ pub struct AtxKeyConfig { pub active_level: ActiveLevel, } -impl Default for AtxKeyConfig { - fn default() -> Self { - Self { - driver: AtxDriverType::None, - device: String::new(), - pin: 0, - active_level: ActiveLevel::High, - } - } -} - impl AtxKeyConfig { /// Check if this key is configured pub fn is_configured(&self) -> bool { @@ -100,7 +74,7 @@ impl AtxKeyConfig { /// LED sensing configuration (optional) #[typeshare] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] #[serde(default)] pub struct AtxLedConfig { /// Whether LED sensing is enabled @@ -113,17 +87,6 @@ pub struct AtxLedConfig { pub inverted: bool, } -impl Default for AtxLedConfig { - fn default() -> Self { - Self { - enabled: false, - gpio_chip: String::new(), - gpio_pin: 0, - inverted: false, - } - } -} - impl AtxLedConfig { /// Check if LED sensing is configured pub fn is_configured(&self) -> bool { @@ -132,7 +95,7 @@ impl AtxLedConfig { } /// ATX state information -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AtxState { /// Whether ATX feature is available/enabled pub available: bool, @@ -146,18 +109,6 @@ pub struct AtxState { pub led_supported: bool, } -impl Default for AtxState { - fn default() -> Self { - Self { - available: false, - power_configured: false, - reset_configured: false, - power_status: PowerStatus::Unknown, - led_supported: false, - } - } -} - /// ATX power action request #[derive(Debug, Clone, Deserialize)] pub struct AtxPowerRequest { @@ -179,7 +130,7 @@ pub enum AtxAction { /// Available ATX devices for discovery #[typeshare] -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AtxDevices { /// Available GPIO chips (/dev/gpiochip*) pub gpio_chips: Vec, @@ -187,15 +138,6 @@ pub struct AtxDevices { pub usb_relays: Vec, } -impl Default for AtxDevices { - fn default() -> Self { - Self { - gpio_chips: Vec::new(), - usb_relays: Vec::new(), - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -266,5 +208,6 @@ mod tests { assert!(!state.power_configured); assert!(!state.reset_configured); assert_eq!(state.power_status, PowerStatus::Unknown); + assert!(!state.led_supported); } } diff --git a/src/atx/wol.rs b/src/atx/wol.rs index 958e3a97..9da93cd7 100644 --- a/src/atx/wol.rs +++ b/src/atx/wol.rs @@ -10,7 +10,7 @@ use crate::error::{AppError, Result}; /// WOL magic packet structure: /// - 6 bytes of 0xFF /// - 16 repetitions of the target MAC address (6 bytes each) -/// Total: 6 + 16 * 6 = 102 bytes +/// Total: 6 + 16 * 6 = 102 bytes const MAGIC_PACKET_SIZE: usize = 102; /// Parse MAC address string into bytes @@ -160,8 +160,8 @@ mod tests { let packet = build_magic_packet(&mac); // Check header (6 bytes of 0xFF) - for i in 0..6 { - assert_eq!(packet[i], 0xFF); + for byte in packet.iter().take(6) { + assert_eq!(*byte, 0xFF); } // Check MAC repetitions diff --git a/src/audio/capture.rs b/src/audio/capture.rs index 33fcc673..aef64344 100644 --- a/src/audio/capture.rs +++ b/src/audio/capture.rs @@ -184,14 +184,7 @@ impl AudioCapturer { let log_throttler = self.log_throttler.clone(); let handle = tokio::task::spawn_blocking(move || { - capture_loop( - config, - state, - frame_tx, - stop_flag, - sequence, - log_throttler, - ); + capture_loop(config, state, frame_tx, stop_flag, sequence, log_throttler); }); *self.capture_handle.lock().await = Some(handle); diff --git a/src/audio/controller.rs b/src/audio/controller.rs index ea3621d0..f7ebf82d 100644 --- a/src/audio/controller.rs +++ b/src/audio/controller.rs @@ -39,7 +39,9 @@ impl AudioQuality { } /// Parse from string + #[allow(clippy::should_implement_trait)] pub fn from_str(s: &str) -> Self { + match s.to_lowercase().as_str() { "voice" | "low" => AudioQuality::Voice, "high" | "music" => AudioQuality::High, diff --git a/src/audio/device.rs b/src/audio/device.rs index ed42726c..77536680 100644 --- a/src/audio/device.rs +++ b/src/audio/device.rs @@ -85,9 +85,7 @@ pub fn enumerate_audio_devices_with_current( let mut devices = Vec::new(); // Try to enumerate cards - let cards = match alsa::card::Iter::new() { - i => i, - }; + let cards = alsa::card::Iter::new(); for card_result in cards { let card = match card_result { diff --git a/src/audio/monitor.rs b/src/audio/monitor.rs index d29b747a..6abfb010 100644 --- a/src/audio/monitor.rs +++ b/src/audio/monitor.rs @@ -17,8 +17,10 @@ use crate::utils::LogThrottler; /// Audio health status #[derive(Debug, Clone, PartialEq)] +#[derive(Default)] pub enum AudioHealthStatus { /// Device is healthy and operational + #[default] Healthy, /// Device has an error, attempting recovery Error { @@ -33,11 +35,6 @@ pub enum AudioHealthStatus { Disconnected, } -impl Default for AudioHealthStatus { - fn default() -> Self { - Self::Healthy - } -} /// Audio health monitor configuration #[derive(Debug, Clone)] @@ -166,7 +163,7 @@ impl AudioHealthMonitor { let attempt = self.retry_count.load(Ordering::Relaxed); // Only publish every 5 attempts to avoid event spam - if attempt == 1 || attempt % 5 == 0 { + if attempt == 1 || attempt.is_multiple_of(5) { debug!("Audio reconnecting, attempt {}", attempt); if let Some(ref events) = *self.events.read().await { diff --git a/src/audio/streamer.rs b/src/audio/streamer.rs index 0d843e05..462a3c7c 100644 --- a/src/audio/streamer.rs +++ b/src/audio/streamer.rs @@ -15,8 +15,10 @@ use crate::error::{AppError, Result}; /// Audio stream state #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Default)] pub enum AudioStreamState { /// Stream is stopped + #[default] Stopped, /// Stream is starting up Starting, @@ -26,14 +28,10 @@ pub enum AudioStreamState { Error, } -impl Default for AudioStreamState { - fn default() -> Self { - Self::Stopped - } -} /// Audio streamer configuration #[derive(Debug, Clone)] +#[derive(Default)] pub struct AudioStreamerConfig { /// Audio capture configuration pub capture: AudioConfig, @@ -41,14 +39,6 @@ pub struct AudioStreamerConfig { pub opus: OpusConfig, } -impl Default for AudioStreamerConfig { - fn default() -> Self { - Self { - capture: AudioConfig::default(), - opus: OpusConfig::default(), - } - } -} impl AudioStreamerConfig { /// Create config for a specific device with default quality @@ -290,11 +280,7 @@ impl AudioStreamer { // Encode to Opus let opus_result = { let mut enc_guard = encoder.lock().await; - if let Some(ref mut enc) = *enc_guard { - Some(enc.encode_frame(&audio_frame)) - } else { - None - } + (*enc_guard).as_mut().map(|enc| enc.encode_frame(&audio_frame)) }; match opus_result { diff --git a/src/auth/middleware.rs b/src/auth/middleware.rs index 5bbbd2f0..80f40f3f 100644 --- a/src/auth/middleware.rs +++ b/src/auth/middleware.rs @@ -92,11 +92,7 @@ fn is_public_endpoint(path: &str) -> bool { // Note: paths here are relative to /api since middleware is applied within the nested router matches!( path, - "/" - | "/auth/login" - | "/health" - | "/setup" - | "/setup/init" + "/" | "/auth/login" | "/health" | "/setup" | "/setup/init" ) || path.starts_with("/assets/") || path.starts_with("/static/") || path.ends_with(".js") diff --git a/src/auth/user.rs b/src/auth/user.rs index f731f52f..0854a5ab 100644 --- a/src/auth/user.rs +++ b/src/auth/user.rs @@ -161,13 +161,12 @@ impl UserStore { } let now = Utc::now(); - let result = - sqlx::query("UPDATE users SET username = ?1, updated_at = ?2 WHERE id = ?3") - .bind(new_username) - .bind(now.to_rfc3339()) - .bind(user_id) - .execute(&self.pool) - .await?; + let result = sqlx::query("UPDATE users SET username = ?1, updated_at = ?2 WHERE id = ?3") + .bind(new_username) + .bind(now.to_rfc3339()) + .bind(user_id) + .execute(&self.pool) + .await?; if result.rows_affected() == 0 { return Err(AppError::NotFound("User not found".to_string())); diff --git a/src/config/schema.rs b/src/config/schema.rs index 63abe268..bb5d06a6 100644 --- a/src/config/schema.rs +++ b/src/config/schema.rs @@ -11,6 +11,7 @@ pub use crate::rustdesk::config::RustDeskConfig; #[typeshare] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] +#[derive(Default)] pub struct AppConfig { /// Whether initial setup has been completed pub initialized: bool, @@ -36,23 +37,6 @@ pub struct AppConfig { pub rustdesk: RustDeskConfig, } -impl Default for AppConfig { - fn default() -> Self { - Self { - initialized: false, - auth: AuthConfig::default(), - video: VideoConfig::default(), - hid: HidConfig::default(), - msd: MsdConfig::default(), - atx: AtxConfig::default(), - audio: AudioConfig::default(), - stream: StreamConfig::default(), - web: WebConfig::default(), - extensions: ExtensionsConfig::default(), - rustdesk: RustDeskConfig::default(), - } - } -} /// Authentication configuration #[typeshare] @@ -116,20 +100,17 @@ impl Default for VideoConfig { #[typeshare] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "lowercase")] +#[derive(Default)] pub enum HidBackend { /// USB OTG HID gadget Otg, /// CH9329 serial HID controller Ch9329, /// Disabled + #[default] None, } -impl Default for HidBackend { - fn default() -> Self { - Self::None - } -} /// OTG USB device descriptor configuration #[typeshare] @@ -163,8 +144,10 @@ impl Default for OtgDescriptorConfig { #[typeshare] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "snake_case")] +#[derive(Default)] pub enum OtgHidProfile { /// Full HID device set (keyboard + relative mouse + absolute mouse + consumer control) + #[default] Full, /// Full HID device set without MSD FullNoMsd, @@ -180,11 +163,6 @@ pub enum OtgHidProfile { Custom, } -impl Default for OtgHidProfile { - fn default() -> Self { - Self::Full - } -} /// OTG HID function selection (used when profile is Custom) #[typeshare] @@ -360,6 +338,7 @@ pub use crate::atx::{ActiveLevel, AtxDriverType, AtxKeyConfig, AtxLedConfig}; #[typeshare] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] +#[derive(Default)] pub struct AtxConfig { /// Enable ATX functionality pub enabled: bool, @@ -373,17 +352,6 @@ pub struct AtxConfig { pub wol_interface: String, } -impl Default for AtxConfig { - fn default() -> Self { - Self { - enabled: false, - power: AtxKeyConfig::default(), - reset: AtxKeyConfig::default(), - led: AtxLedConfig::default(), - wol_interface: String::new(), - } - } -} impl AtxConfig { /// Convert to AtxControllerConfig for the controller @@ -427,25 +395,24 @@ impl Default for AudioConfig { #[typeshare] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "lowercase")] +#[derive(Default)] pub enum StreamMode { /// WebRTC with H264/H265 WebRTC, /// MJPEG over HTTP + #[default] Mjpeg, } -impl Default for StreamMode { - fn default() -> Self { - Self::Mjpeg - } -} /// Encoder type #[typeshare] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "lowercase")] +#[derive(Default)] pub enum EncoderType { /// Auto-detect best encoder + #[default] Auto, /// Software encoder (libx264) Software, @@ -463,11 +430,6 @@ pub enum EncoderType { V4l2m2m, } -impl Default for EncoderType { - fn default() -> Self { - Self::Auto - } -} impl EncoderType { /// Convert to EncoderBackend for registry queries diff --git a/src/events/types.rs b/src/events/types.rs index 44a69b2d..ab3ebfd4 100644 --- a/src/events/types.rs +++ b/src/events/types.rs @@ -124,6 +124,7 @@ pub struct ClientStats { /// ``` #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(tag = "event", content = "data")] +#[allow(clippy::large_enum_variant)] pub enum SystemEvent { // ============================================================================ // Video Stream Events diff --git a/src/extensions/types.rs b/src/extensions/types.rs index c6a3a70d..b8c69c77 100644 --- a/src/extensions/types.rs +++ b/src/extensions/types.rs @@ -149,6 +149,7 @@ impl Default for GostcConfig { #[typeshare] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] +#[derive(Default)] pub struct EasytierConfig { /// Enable auto-start pub enabled: bool, @@ -165,17 +166,6 @@ pub struct EasytierConfig { pub virtual_ip: Option, } -impl Default for EasytierConfig { - fn default() -> Self { - Self { - enabled: false, - network_name: String::new(), - network_secret: String::new(), - peer_urls: Vec::new(), - virtual_ip: None, - } - } -} /// Combined extensions configuration #[typeshare] diff --git a/src/hid/backend.rs b/src/hid/backend.rs index 95de431f..dbb8dcbb 100644 --- a/src/hid/backend.rs +++ b/src/hid/backend.rs @@ -14,6 +14,7 @@ fn default_ch9329_baud_rate() -> u32 { /// HID backend type #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "lowercase")] +#[derive(Default)] pub enum HidBackendType { /// USB OTG gadget mode Otg, @@ -26,14 +27,10 @@ pub enum HidBackendType { baud_rate: u32, }, /// No HID backend (disabled) + #[default] None, } -impl Default for HidBackendType { - fn default() -> Self { - Self::None - } -} impl HidBackendType { /// Check if OTG backend is available on this system diff --git a/src/hid/ch9329.rs b/src/hid/ch9329.rs index 0893a49e..85196ce8 100644 --- a/src/hid/ch9329.rs +++ b/src/hid/ch9329.rs @@ -219,8 +219,10 @@ impl From for LedStatus { /// CH9329 work mode #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[repr(u8)] +#[derive(Default)] pub enum WorkMode { /// Mode 0: Standard USB Keyboard + Mouse (default) + #[default] KeyboardMouse = 0x00, /// Mode 1: Standard USB Keyboard only KeyboardOnly = 0x01, @@ -230,17 +232,14 @@ pub enum WorkMode { CustomHid = 0x03, } -impl Default for WorkMode { - fn default() -> Self { - Self::KeyboardMouse - } -} /// CH9329 serial communication mode #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[repr(u8)] +#[derive(Default)] pub enum SerialMode { /// Mode 0: Protocol transmission mode (default) + #[default] Protocol = 0x00, /// Mode 1: ASCII mode Ascii = 0x01, @@ -248,11 +247,6 @@ pub enum SerialMode { Transparent = 0x02, } -impl Default for SerialMode { - fn default() -> Self { - Self::Protocol - } -} /// CH9329 configuration parameters #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/hid/mod.rs b/src/hid/mod.rs index 611bdea8..b73dc916 100644 --- a/src/hid/mod.rs +++ b/src/hid/mod.rs @@ -42,17 +42,17 @@ pub struct HidInfo { pub screen_resolution: Option<(u32, u32)>, } -use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use tokio::sync::RwLock; use tracing::{info, warn}; use crate::error::{AppError, Result}; use crate::otg::OtgService; +use std::time::Duration; use tokio::sync::mpsc; use tokio::sync::Mutex; use tokio::task::JoinHandle; -use std::time::Duration; const HID_EVENT_QUEUE_CAPACITY: usize = 64; const HID_EVENT_SEND_TIMEOUT_MS: u64 = 30; @@ -203,7 +203,10 @@ impl HidController { )); } - if matches!(event.event_type, MouseEventType::Move | MouseEventType::MoveAbs) { + if matches!( + event.event_type, + MouseEventType::Move | MouseEventType::MoveAbs + ) { // Best-effort: drop/merge move events if queue is full self.enqueue_mouse_move(event) } else { @@ -470,13 +473,7 @@ impl HidController { None => break, }; - process_hid_event( - event, - &backend, - &monitor, - &backend_type, - ) - .await; + process_hid_event(event, &backend, &monitor, &backend_type).await; // After each event, flush latest move if pending if pending_move_flag.swap(false, Ordering::AcqRel) { @@ -505,9 +502,9 @@ impl HidController { self.pending_move_flag.store(true, Ordering::Release); Ok(()) } - Err(mpsc::error::TrySendError::Closed(_)) => Err(AppError::BadRequest( - "HID event queue closed".to_string(), - )), + Err(mpsc::error::TrySendError::Closed(_)) => { + Err(AppError::BadRequest("HID event queue closed".to_string())) + } } } @@ -517,9 +514,11 @@ impl HidController { Err(mpsc::error::TrySendError::Full(ev)) => { // For non-move events, wait briefly to avoid dropping critical input let tx = self.hid_tx.clone(); - let send_result = - tokio::time::timeout(Duration::from_millis(HID_EVENT_SEND_TIMEOUT_MS), tx.send(ev)) - .await; + let send_result = tokio::time::timeout( + Duration::from_millis(HID_EVENT_SEND_TIMEOUT_MS), + tx.send(ev), + ) + .await; if send_result.is_ok() { Ok(()) } else { @@ -527,9 +526,9 @@ impl HidController { Ok(()) } } - Err(mpsc::error::TrySendError::Closed(_)) => Err(AppError::BadRequest( - "HID event queue closed".to_string(), - )), + Err(mpsc::error::TrySendError::Closed(_)) => { + Err(AppError::BadRequest("HID event queue closed".to_string())) + } } } } diff --git a/src/hid/monitor.rs b/src/hid/monitor.rs index 0ce84d88..1fc8834f 100644 --- a/src/hid/monitor.rs +++ b/src/hid/monitor.rs @@ -17,8 +17,10 @@ use crate::utils::LogThrottler; /// HID health status #[derive(Debug, Clone, PartialEq)] +#[derive(Default)] pub enum HidHealthStatus { /// Device is healthy and operational + #[default] Healthy, /// Device has an error, attempting recovery Error { @@ -33,11 +35,6 @@ pub enum HidHealthStatus { Disconnected, } -impl Default for HidHealthStatus { - fn default() -> Self { - Self::Healthy - } -} /// HID health monitor configuration #[derive(Debug, Clone)] @@ -196,7 +193,7 @@ impl HidHealthMonitor { let attempt = self.retry_count.load(Ordering::Relaxed); // Only publish every 5 attempts to avoid event spam - if attempt == 1 || attempt % 5 == 0 { + if attempt == 1 || attempt.is_multiple_of(5) { debug!("HID {} reconnecting, attempt {}", backend, attempt); if let Some(ref events) = *self.events.read().await { diff --git a/src/hid/otg.rs b/src/hid/otg.rs index b21917d4..3c04bea0 100644 --- a/src/hid/otg.rs +++ b/src/hid/otg.rs @@ -228,7 +228,7 @@ impl OtgBackend { Ok(false) } Ok(_) => Ok(false), - Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other, e)), + Err(e) => Err(std::io::Error::other(e)), } } @@ -393,21 +393,10 @@ impl OtgBackend { /// Check if all HID device files exist pub fn check_devices_exist(&self) -> bool { - self.keyboard_path - .as_ref() - .map_or(true, |p| p.exists()) - && self - .mouse_rel_path - .as_ref() - .map_or(true, |p| p.exists()) - && self - .mouse_abs_path - .as_ref() - .map_or(true, |p| p.exists()) - && self - .consumer_path - .as_ref() - .map_or(true, |p| p.exists()) + self.keyboard_path.as_ref().is_none_or(|p| p.exists()) + && self.mouse_rel_path.as_ref().is_none_or(|p| p.exists()) + && self.mouse_abs_path.as_ref().is_none_or(|p| p.exists()) + && self.consumer_path.as_ref().is_none_or(|p| p.exists()) } /// Get list of missing device paths @@ -952,9 +941,7 @@ impl HidBackend for OtgBackend { } fn supports_absolute_mouse(&self) -> bool { - self.mouse_abs_path - .as_ref() - .map_or(false, |p| p.exists()) + self.mouse_abs_path.as_ref().is_some_and(|p| p.exists()) } async fn send_consumer(&self, event: ConsumerEvent) -> Result<()> { diff --git a/src/main.rs b/src/main.rs index 43f04907..1b264448 100644 --- a/src/main.rs +++ b/src/main.rs @@ -158,7 +158,11 @@ async fn main() -> anyhow::Result<()> { } let bind_ips = resolve_bind_addresses(&config.web)?; - let scheme = if config.web.https_enabled { "https" } else { "http" }; + let scheme = if config.web.https_enabled { + "https" + } else { + "http" + }; let bind_port = if config.web.https_enabled { config.web.https_port } else { @@ -646,7 +650,7 @@ async fn main() -> anyhow::Result<()> { let server = axum_server::from_tcp_rustls(listener, tls_config.clone())? .serve(app.clone().into_make_service()); - servers.push(async move { server.await }); + servers.push(server); } tokio::select! { diff --git a/src/msd/controller.rs b/src/msd/controller.rs index 5641d3c7..5d38396d 100644 --- a/src/msd/controller.rs +++ b/src/msd/controller.rs @@ -52,10 +52,7 @@ impl MsdController { /// # Parameters /// * `otg_service` - OTG service for gadget management /// * `msd_dir` - Base directory for MSD storage - pub fn new( - otg_service: Arc, - msd_dir: impl Into, - ) -> Self { + pub fn new(otg_service: Arc, msd_dir: impl Into) -> Self { let msd_dir = msd_dir.into(); let images_path = msd_dir.join("images"); let ventoy_dir = msd_dir.join("ventoy"); diff --git a/src/msd/image.rs b/src/msd/image.rs index d08b7a18..8e83425c 100644 --- a/src/msd/image.rs +++ b/src/msd/image.rs @@ -88,7 +88,7 @@ impl ImageManager { .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()) .map(|d| { chrono::DateTime::from_timestamp(d.as_secs() as i64, 0) - .unwrap_or_else(|| Utc::now().into()) + .unwrap_or_else(Utc::now) }) .unwrap_or_else(Utc::now); @@ -400,7 +400,7 @@ impl ImageManager { .headers() .get(reqwest::header::CONTENT_DISPOSITION) .and_then(|v| v.to_str().ok()) - .and_then(|s| extract_filename_from_content_disposition(s)); + .and_then(extract_filename_from_content_disposition); if let Some(name) = from_header { sanitize_filename(&name) diff --git a/src/msd/monitor.rs b/src/msd/monitor.rs index a9f80109..077dadaf 100644 --- a/src/msd/monitor.rs +++ b/src/msd/monitor.rs @@ -16,8 +16,10 @@ use crate::utils::LogThrottler; /// MSD health status #[derive(Debug, Clone, PartialEq)] +#[derive(Default)] pub enum MsdHealthStatus { /// Device is healthy and operational + #[default] Healthy, /// Device has an error Error { @@ -28,11 +30,6 @@ pub enum MsdHealthStatus { }, } -impl Default for MsdHealthStatus { - fn default() -> Self { - Self::Healthy - } -} /// MSD health monitor configuration #[derive(Debug, Clone)] diff --git a/src/msd/types.rs b/src/msd/types.rs index a658db16..5a061c59 100644 --- a/src/msd/types.rs +++ b/src/msd/types.rs @@ -7,8 +7,10 @@ use std::path::PathBuf; /// MSD operating mode #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] +#[derive(Default)] pub enum MsdMode { /// No storage connected + #[default] None, /// Image file mounted (ISO/IMG) Image, @@ -16,11 +18,6 @@ pub enum MsdMode { Drive, } -impl Default for MsdMode { - fn default() -> Self { - Self::None - } -} /// Image file metadata #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/msd/ventoy_drive.rs b/src/msd/ventoy_drive.rs index 8839a6e1..57d8f4a2 100644 --- a/src/msd/ventoy_drive.rs +++ b/src/msd/ventoy_drive.rs @@ -328,8 +328,7 @@ impl VentoyDrive { let image = match VentoyImage::open(&path) { Ok(img) => img, Err(e) => { - let _ = rt.block_on(tx.send(Err(std::io::Error::new( - std::io::ErrorKind::Other, + let _ = rt.block_on(tx.send(Err(std::io::Error::other( e.to_string(), )))); return; @@ -341,8 +340,7 @@ impl VentoyDrive { // Stream the file through the writer if let Err(e) = image.read_file_to_writer(&file_path_owned, &mut chunk_writer) { - let _ = rt.block_on(tx.send(Err(std::io::Error::new( - std::io::ErrorKind::Other, + let _ = rt.block_on(tx.send(Err(std::io::Error::other( e.to_string(), )))); } @@ -543,12 +541,11 @@ mod tests { /// Decompress xz file using system command fn decompress_xz(src: &std::path::Path, dst: &std::path::Path) -> std::io::Result<()> { let output = Command::new("xz") - .args(&["-d", "-k", "-c", src.to_str().unwrap()]) + .args(["-d", "-k", "-c", src.to_str().unwrap()]) .output()?; if !output.status.success() { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, + return Err(std::io::Error::other( format!( "xz decompress failed: {}", String::from_utf8_lossy(&output.stderr) diff --git a/src/otg/manager.rs b/src/otg/manager.rs index 64773d2b..9996238a 100644 --- a/src/otg/manager.rs +++ b/src/otg/manager.rs @@ -422,7 +422,11 @@ impl OtgGadgetManager { if dest.exists() { if let Err(e) = remove_file(&dest) { - warn!("Failed to remove existing config link {}: {}", dest.display(), e); + warn!( + "Failed to remove existing config link {}: {}", + dest.display(), + e + ); continue; } } diff --git a/src/otg/service.rs b/src/otg/service.rs index 2be3c944..e0eeb37a 100644 --- a/src/otg/service.rs +++ b/src/otg/service.rs @@ -36,6 +36,7 @@ const FLAG_MSD: u8 = 0b10; /// HID device paths #[derive(Debug, Clone)] +#[derive(Default)] pub struct HidDevicePaths { pub keyboard: Option, pub mouse_relative: Option, @@ -43,16 +44,6 @@ pub struct HidDevicePaths { pub consumer: Option, } -impl Default for HidDevicePaths { - fn default() -> Self { - Self { - keyboard: None, - mouse_relative: None, - mouse_absolute: None, - consumer: None, - } - } -} impl HidDevicePaths { pub fn existing_paths(&self) -> Vec { @@ -239,14 +230,13 @@ impl OtgService { let requested_functions = self.hid_functions.read().await.clone(); { let state = self.state.read().await; - if state.hid_enabled { - if state.hid_functions.as_ref() == Some(&requested_functions) { + if state.hid_enabled + && state.hid_functions.as_ref() == Some(&requested_functions) { if let Some(ref paths) = state.hid_paths { info!("HID already enabled, returning existing paths"); return Ok(paths.clone()); } } - } } // Recreate gadget with both HID and MSD if needed @@ -671,7 +661,7 @@ mod tests { fn test_service_creation() { let _service = OtgService::new(); // Just test that creation doesn't panic - assert!(!OtgService::is_available() || true); // Depends on environment + let _ = OtgService::is_available(); // Depends on environment } #[tokio::test] diff --git a/src/rustdesk/bytes_codec.rs b/src/rustdesk/bytes_codec.rs index a592f7f5..18f163ca 100644 --- a/src/rustdesk/bytes_codec.rs +++ b/src/rustdesk/bytes_codec.rs @@ -50,7 +50,7 @@ fn decode_header(first_byte: u8, header_bytes: &[u8]) -> (usize, usize) { let head_len = ((first_byte & 0x3) + 1) as usize; let mut n = first_byte as usize; - if head_len > 1 && header_bytes.len() >= 1 { + if head_len > 1 && !header_bytes.is_empty() { n |= (header_bytes[0] as usize) << 8; } if head_len > 2 && header_bytes.len() >= 2 { diff --git a/src/rustdesk/config.rs b/src/rustdesk/config.rs index 3a72e792..c8c64f29 100644 --- a/src/rustdesk/config.rs +++ b/src/rustdesk/config.rs @@ -202,9 +202,11 @@ mod tests { #[test] fn test_rendezvous_addr() { - let mut config = RustDeskConfig::default(); + let mut config = RustDeskConfig { + rendezvous_server: "example.com".to_string(), + ..Default::default() + }; - config.rendezvous_server = "example.com".to_string(); assert_eq!(config.rendezvous_addr(), "example.com:21116"); config.rendezvous_server = "example.com:21116".to_string(); @@ -217,10 +219,12 @@ mod tests { #[test] fn test_relay_addr() { - let mut config = RustDeskConfig::default(); + let mut config = RustDeskConfig { + rendezvous_server: "example.com".to_string(), + ..Default::default() + }; // Rendezvous server configured, relay defaults to same host - config.rendezvous_server = "example.com".to_string(); assert_eq!(config.relay_addr(), Some("example.com:21117".to_string())); // Explicit relay server @@ -238,10 +242,12 @@ mod tests { #[test] fn test_effective_rendezvous_server() { - let mut config = RustDeskConfig::default(); + let mut config = RustDeskConfig { + rendezvous_server: "custom.example.com".to_string(), + ..Default::default() + }; // When user sets a server, use it - config.rendezvous_server = "custom.example.com".to_string(); assert_eq!(config.effective_rendezvous_server(), "custom.example.com"); // When empty, returns empty diff --git a/src/rustdesk/connection.rs b/src/rustdesk/connection.rs index b408ed12..9581bd91 100644 --- a/src/rustdesk/connection.rs +++ b/src/rustdesk/connection.rs @@ -729,7 +729,7 @@ impl Connection { } // Check if client sent supported_decoding with a codec preference - if let Some(ref supported_decoding) = opt.supported_decoding.as_ref() { + if let Some(supported_decoding) = opt.supported_decoding.as_ref() { let prefer = supported_decoding.prefer.value(); debug!("Client codec preference: prefer={}", prefer); @@ -1352,8 +1352,12 @@ impl Connection { debug!("Mouse event: x={}, y={}, mask={}", me.x, me.y, me.mask); // Convert RustDesk mouse event to One-KVM mouse events - let mouse_events = - convert_mouse_event(me, self.screen_width, self.screen_height, self.relative_mouse_active); + let mouse_events = convert_mouse_event( + me, + self.screen_width, + self.screen_height, + self.relative_mouse_active, + ); // Send to HID controller if available if let Some(ref hid) = self.hid { @@ -1616,7 +1620,10 @@ async fn run_video_streaming( ); } if let Err(e) = video_manager.request_keyframe().await { - debug!("Failed to request keyframe for connection {}: {}", conn_id, e); + debug!( + "Failed to request keyframe for connection {}: {}", + conn_id, e + ); } // Inner loop: receives frames from current subscription diff --git a/src/rustdesk/crypto.rs b/src/rustdesk/crypto.rs index 10860402..88b1257f 100644 --- a/src/rustdesk/crypto.rs +++ b/src/rustdesk/crypto.rs @@ -189,7 +189,7 @@ pub fn hash_password_double(password: &str, salt: &str, challenge: &str) -> Vec< // Second hash: SHA256(first_hash + challenge) let mut hasher2 = Sha256::new(); - hasher2.update(&first_hash); + hasher2.update(first_hash); hasher2.update(challenge.as_bytes()); hasher2.finalize().to_vec() } diff --git a/src/rustdesk/frame_adapters.rs b/src/rustdesk/frame_adapters.rs index fbee2c1e..3fa8c8e4 100644 --- a/src/rustdesk/frame_adapters.rs +++ b/src/rustdesk/frame_adapters.rs @@ -127,7 +127,8 @@ impl VideoFrameAdapter { // Inject cached SPS/PPS before IDR when missing if is_keyframe && (!has_sps || !has_pps) { - if let (Some(ref sps), Some(ref pps)) = (self.h264_sps.as_ref(), self.h264_pps.as_ref()) { + if let (Some(sps), Some(pps)) = (self.h264_sps.as_ref(), self.h264_pps.as_ref()) + { let mut out = Vec::with_capacity(8 + sps.len() + pps.len() + data.len()); out.extend_from_slice(&[0, 0, 0, 1]); out.extend_from_slice(sps); diff --git a/src/rustdesk/mod.rs b/src/rustdesk/mod.rs index 5b636497..128337e1 100644 --- a/src/rustdesk/mod.rs +++ b/src/rustdesk/mod.rs @@ -36,8 +36,8 @@ use tracing::{debug, error, info, warn}; use crate::audio::AudioController; use crate::hid::HidController; -use crate::video::stream_manager::VideoStreamManager; use crate::utils::bind_tcp_listener; +use crate::video::stream_manager::VideoStreamManager; use self::config::RustDeskConfig; use self::connection::ConnectionManager; @@ -559,6 +559,7 @@ impl RustDeskService { /// 2. Send RelayResponse with client's socket_addr /// 3. Connect to RELAY server /// 4. Accept connection without waiting for response +#[allow(clippy::too_many_arguments)] async fn handle_relay_request( rendezvous_addr: &str, relay_server: &str, diff --git a/src/rustdesk/rendezvous.rs b/src/rustdesk/rendezvous.rs index d347f81f..87c9ecd4 100644 --- a/src/rustdesk/rendezvous.rs +++ b/src/rustdesk/rendezvous.rs @@ -559,7 +559,7 @@ impl RendezvousMediator { ); let msg = make_punch_hole_sent( - &ph.socket_addr.to_vec(), // Use peer's socket_addr, not ours + &ph.socket_addr, // Use peer's socket_addr, not ours &id, &ph.relay_server, ph.nat_type.enum_value().unwrap_or(NatType::UNKNOWN_NAT), diff --git a/src/state.rs b/src/state.rs index b322f7ed..83d8237f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -64,6 +64,7 @@ pub struct AppState { impl AppState { /// Create new application state + #[allow(clippy::too_many_arguments)] pub fn new( config: ConfigStore, sessions: SessionStore, diff --git a/src/stream/mjpeg_streamer.rs b/src/stream/mjpeg_streamer.rs index d9219123..6fbd7379 100644 --- a/src/stream/mjpeg_streamer.rs +++ b/src/stream/mjpeg_streamer.rs @@ -15,16 +15,16 @@ //! //! Note: Audio WebSocket is handled separately by audio_ws.rs (/api/ws/audio) -use std::io; +use crate::utils::LogThrottler; +use crate::video::v4l2r_capture::V4l2rCaptureStream; use std::collections::HashMap; +use std::io; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; use tokio::sync::{Mutex, RwLock}; use tracing::{error, info, warn}; -use crate::video::v4l2r_capture::V4l2rCaptureStream; -use crate::utils::LogThrottler; use crate::audio::AudioController; use crate::error::{AppError, Result}; @@ -624,7 +624,7 @@ impl MjpegStreamer { validate_counter = validate_counter.wrapping_add(1); if pixel_format.is_compressed() - && validate_counter % JPEG_VALIDATE_INTERVAL == 0 + && validate_counter.is_multiple_of(JPEG_VALIDATE_INTERVAL) && !VideoFrame::is_valid_jpeg_bytes(&owned[..frame_size]) { continue; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 12bf372a..c31db32d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -2,8 +2,8 @@ //! //! This module contains common utilities used across the codebase. -pub mod throttle; pub mod net; +pub mod throttle; -pub use throttle::LogThrottler; pub use net::{bind_tcp_listener, bind_udp_socket}; +pub use throttle::LogThrottler; diff --git a/src/video/capture.rs b/src/video/capture.rs index e2218ac9..464227fd 100644 --- a/src/video/capture.rs +++ b/src/video/capture.rs @@ -2,10 +2,10 @@ //! //! Provides async video capture using memory-mapped buffers. +use bytes::Bytes; use std::collections::HashMap; use std::io; use std::path::{Path, PathBuf}; -use bytes::Bytes; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::{Duration, Instant}; diff --git a/src/video/device.rs b/src/video/device.rs index 543340f6..b35bee8e 100644 --- a/src/video/device.rs +++ b/src/video/device.rs @@ -6,11 +6,11 @@ use std::path::{Path, PathBuf}; use std::sync::mpsc; use std::time::Duration; use tracing::{debug, info, warn}; -use v4l2r::nix::errno::Errno; use v4l2r::bindings::{v4l2_frmivalenum, v4l2_frmsizeenum}; use v4l2r::ioctl::{ self, Capabilities, Capability as V4l2rCapability, FormatIterator, FrmIvalTypes, FrmSizeTypes, }; +use v4l2r::nix::errno::Errno; use v4l2r::{Format as V4l2rFormat, QueueType}; use super::format::{PixelFormat, Resolution}; @@ -96,7 +96,9 @@ impl VideoDevice { .read(true) .write(true) .open(&path) - .map_err(|e| AppError::VideoError(format!("Failed to open device {:?}: {}", path, e)))?; + .map_err(|e| { + AppError::VideoError(format!("Failed to open device {:?}: {}", path, e)) + })?; Ok(Self { path, fd }) } @@ -106,10 +108,9 @@ impl VideoDevice { let path = path.as_ref().to_path_buf(); debug!("Opening video device (read-only): {:?}", path); - let fd = File::options() - .read(true) - .open(&path) - .map_err(|e| AppError::VideoError(format!("Failed to open device {:?}: {}", path, e)))?; + let fd = File::options().read(true).open(&path).map_err(|e| { + AppError::VideoError(format!("Failed to open device {:?}: {}", path, e)) + })?; Ok(Self { path, fd }) } @@ -206,8 +207,9 @@ impl VideoDevice { if let Some(size) = size.size() { match size { FrmSizeTypes::Discrete(d) => { - let fps = - self.enumerate_fps(fourcc, d.width, d.height).unwrap_or_default(); + let fps = self + .enumerate_fps(fourcc, d.width, d.height) + .unwrap_or_default(); resolutions.push(ResolutionInfo::new(d.width, d.height, fps)); } FrmSizeTypes::StepWise(s) => { @@ -225,7 +227,8 @@ impl VideoDevice { let fps = self .enumerate_fps(fourcc, res.width, res.height) .unwrap_or_default(); - resolutions.push(ResolutionInfo::new(res.width, res.height, fps)); + resolutions + .push(ResolutionInfo::new(res.width, res.height, fps)); } } } @@ -265,11 +268,7 @@ impl VideoDevice { let mut index = 0u32; loop { match ioctl::enum_frame_intervals::( - &self.fd, - index, - fourcc, - width, - height, + &self.fd, index, fourcc, width, height, ) { Ok(interval) => { if let Some(interval) = interval.intervals() { @@ -411,7 +410,7 @@ impl VideoDevice { .max() .unwrap_or(0); - priority += (max_resolution / 100000) as u32; + priority += max_resolution / 100000; // Known good drivers get bonus let good_drivers = ["uvcvideo", "tc358743"]; @@ -563,15 +562,7 @@ fn sysfs_maybe_capture(path: &Path) -> bool { } let skip_hints = [ - "codec", - "decoder", - "encoder", - "isp", - "mem2mem", - "m2m", - "vbi", - "radio", - "metadata", + "codec", "decoder", "encoder", "isp", "mem2mem", "m2m", "vbi", "radio", "metadata", "output", ]; if skip_hints.iter().any(|hint| sysfs_name.contains(hint)) && !maybe_capture { diff --git a/src/video/encoder/h264.rs b/src/video/encoder/h264.rs index 65c2512b..f59a27b9 100644 --- a/src/video/encoder/h264.rs +++ b/src/video/encoder/h264.rs @@ -33,6 +33,7 @@ fn init_hwcodec_logging() { /// H.264 encoder type (detected from hwcodec) #[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Default)] pub enum H264EncoderType { /// NVIDIA NVENC Nvenc, @@ -49,6 +50,7 @@ pub enum H264EncoderType { /// Software encoding (libx264/openh264) Software, /// No encoder available + #[default] None, } @@ -67,11 +69,6 @@ impl std::fmt::Display for H264EncoderType { } } -impl Default for H264EncoderType { - fn default() -> Self { - Self::None - } -} /// Map codec name to encoder type fn codec_name_to_type(name: &str) -> H264EncoderType { @@ -94,10 +91,12 @@ fn codec_name_to_type(name: &str) -> H264EncoderType { /// Input pixel format for H264 encoder #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Default)] pub enum H264InputFormat { /// YUV420P (I420) - planar Y, U, V Yuv420p, /// NV12 - Y plane + interleaved UV plane (optimal for VAAPI) + #[default] Nv12, /// NV21 - Y plane + interleaved VU plane Nv21, @@ -113,11 +112,6 @@ pub enum H264InputFormat { Bgr24, } -impl Default for H264InputFormat { - fn default() -> Self { - Self::Nv12 // Default to NV12 for VAAPI compatibility - } -} /// H.264 encoder configuration #[derive(Debug, Clone)] diff --git a/src/video/encoder/h265.rs b/src/video/encoder/h265.rs index 8a89015d..f96c6b62 100644 --- a/src/video/encoder/h265.rs +++ b/src/video/encoder/h265.rs @@ -31,6 +31,7 @@ fn init_hwcodec_logging() { /// H.265 encoder type (detected from hwcodec) #[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Default)] pub enum H265EncoderType { /// NVIDIA NVENC Nvenc, @@ -47,6 +48,7 @@ pub enum H265EncoderType { /// Software encoder (libx265) Software, /// No encoder available + #[default] None, } @@ -65,11 +67,6 @@ impl std::fmt::Display for H265EncoderType { } } -impl Default for H265EncoderType { - fn default() -> Self { - Self::None - } -} impl From for H265EncoderType { fn from(backend: EncoderBackend) -> Self { @@ -87,10 +84,12 @@ impl From for H265EncoderType { /// Input pixel format for H265 encoder #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Default)] pub enum H265InputFormat { /// YUV420P (I420) - planar Y, U, V Yuv420p, /// NV12 - Y plane + interleaved UV plane (optimal for hardware encoders) + #[default] Nv12, /// NV21 - Y plane + interleaved VU plane Nv21, @@ -106,11 +105,6 @@ pub enum H265InputFormat { Bgr24, } -impl Default for H265InputFormat { - fn default() -> Self { - Self::Nv12 // Default to NV12 for hardware encoder compatibility - } -} /// H.265 encoder configuration #[derive(Debug, Clone)] @@ -256,8 +250,6 @@ pub fn detect_best_h265_encoder(width: u32, height: u32) -> (H265EncoderType, Op H265EncoderType::Rkmpp } else if codec.name.contains("v4l2m2m") { H265EncoderType::V4l2M2m - } else if codec.name.contains("libx265") { - H265EncoderType::Software } else { H265EncoderType::Software // Default to software for unknown }; diff --git a/src/video/encoder/registry.rs b/src/video/encoder/registry.rs index 1f9dd1a9..5a9658dc 100644 --- a/src/video/encoder/registry.rs +++ b/src/video/encoder/registry.rs @@ -145,6 +145,7 @@ impl EncoderBackend { } /// Parse from string (case-insensitive) + #[allow(clippy::should_implement_trait)] pub fn from_str(s: &str) -> Option { match s.to_lowercase().as_str() { "vaapi" => Some(EncoderBackend::Vaapi), diff --git a/src/video/encoder/traits.rs b/src/video/encoder/traits.rs index 940ec245..d4b5bd88 100644 --- a/src/video/encoder/traits.rs +++ b/src/video/encoder/traits.rs @@ -15,12 +15,14 @@ use crate::video::format::{PixelFormat, Resolution}; #[typeshare] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "type", content = "value")] +#[derive(Default)] pub enum BitratePreset { /// Speed priority: 1 Mbps, lowest latency, smaller GOP /// Best for: slow networks, remote management, low-bandwidth scenarios Speed, /// Balanced: 4 Mbps, good quality/latency tradeoff /// Best for: typical usage, recommended default + #[default] Balanced, /// Quality priority: 8 Mbps, best visual quality /// Best for: local network, high-bandwidth scenarios, detailed work @@ -74,11 +76,6 @@ impl BitratePreset { } } -impl Default for BitratePreset { - fn default() -> Self { - Self::Balanced - } -} impl std::fmt::Display for BitratePreset { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/src/video/encoder/vp8.rs b/src/video/encoder/vp8.rs index 868af8ee..302fe7ab 100644 --- a/src/video/encoder/vp8.rs +++ b/src/video/encoder/vp8.rs @@ -31,12 +31,14 @@ fn init_hwcodec_logging() { /// VP8 encoder type (detected from hwcodec) #[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Default)] pub enum VP8EncoderType { /// VAAPI (Intel on Linux) Vaapi, /// Software encoder (libvpx) Software, /// No encoder available + #[default] None, } @@ -50,11 +52,6 @@ impl std::fmt::Display for VP8EncoderType { } } -impl Default for VP8EncoderType { - fn default() -> Self { - Self::None - } -} impl From for VP8EncoderType { fn from(backend: EncoderBackend) -> Self { @@ -68,18 +65,15 @@ impl From for VP8EncoderType { /// Input pixel format for VP8 encoder #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Default)] pub enum VP8InputFormat { /// YUV420P (I420) - planar Y, U, V Yuv420p, /// NV12 - Y plane + interleaved UV plane + #[default] Nv12, } -impl Default for VP8InputFormat { - fn default() -> Self { - Self::Nv12 // Default to NV12 for VAAPI compatibility - } -} /// VP8 encoder configuration #[derive(Debug, Clone)] @@ -180,8 +174,6 @@ pub fn detect_best_vp8_encoder(width: u32, height: u32) -> (VP8EncoderType, Opti let encoder_type = if codec.name.contains("vaapi") { VP8EncoderType::Vaapi - } else if codec.name.contains("libvpx") { - VP8EncoderType::Software } else { VP8EncoderType::Software // Default to software for unknown }; diff --git a/src/video/encoder/vp9.rs b/src/video/encoder/vp9.rs index 6995db5d..6ff4c589 100644 --- a/src/video/encoder/vp9.rs +++ b/src/video/encoder/vp9.rs @@ -31,12 +31,14 @@ fn init_hwcodec_logging() { /// VP9 encoder type (detected from hwcodec) #[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Default)] pub enum VP9EncoderType { /// VAAPI (Intel on Linux) Vaapi, /// Software encoder (libvpx-vp9) Software, /// No encoder available + #[default] None, } @@ -50,11 +52,6 @@ impl std::fmt::Display for VP9EncoderType { } } -impl Default for VP9EncoderType { - fn default() -> Self { - Self::None - } -} impl From for VP9EncoderType { fn from(backend: EncoderBackend) -> Self { @@ -68,18 +65,15 @@ impl From for VP9EncoderType { /// Input pixel format for VP9 encoder #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Default)] pub enum VP9InputFormat { /// YUV420P (I420) - planar Y, U, V Yuv420p, /// NV12 - Y plane + interleaved UV plane + #[default] Nv12, } -impl Default for VP9InputFormat { - fn default() -> Self { - Self::Nv12 // Default to NV12 for VAAPI compatibility - } -} /// VP9 encoder configuration #[derive(Debug, Clone)] @@ -180,8 +174,6 @@ pub fn detect_best_vp9_encoder(width: u32, height: u32) -> (VP9EncoderType, Opti let encoder_type = if codec.name.contains("vaapi") { VP9EncoderType::Vaapi - } else if codec.name.contains("libvpx") { - VP9EncoderType::Software } else { VP9EncoderType::Software // Default to software for unknown }; diff --git a/src/video/frame.rs b/src/video/frame.rs index dc8f4c92..9d0b43b0 100644 --- a/src/video/frame.rs +++ b/src/video/frame.rs @@ -81,6 +81,11 @@ impl FrameBuffer { pub fn len(&self) -> usize { self.data.len() } + + /// Check if the frame buffer has no data + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } } impl std::fmt::Debug for FrameBuffer { diff --git a/src/video/shared_video_pipeline.rs b/src/video/shared_video_pipeline.rs index 55fd660f..bfb2acc9 100644 --- a/src/video/shared_video_pipeline.rs +++ b/src/video/shared_video_pipeline.rs @@ -36,9 +36,6 @@ use crate::error::{AppError, Result}; use crate::utils::LogThrottler; use crate::video::convert::{Nv12Converter, PixelConverter}; use crate::video::decoder::MjpegTurboDecoder; -#[cfg(any(target_arch = "aarch64", target_arch = "arm"))] -use hwcodec::ffmpeg_hw::{last_error_message as ffmpeg_hw_last_error, HwMjpegH26xConfig, HwMjpegH26xPipeline}; -use crate::video::v4l2r_capture::V4l2rCaptureStream; use crate::video::encoder::h264::{detect_best_encoder, H264Config, H264Encoder, H264InputFormat}; use crate::video::encoder::h265::{ detect_best_h265_encoder, H265Config, H265Encoder, H265InputFormat, @@ -49,6 +46,11 @@ use crate::video::encoder::vp8::{detect_best_vp8_encoder, VP8Config, VP8Encoder} use crate::video::encoder::vp9::{detect_best_vp9_encoder, VP9Config, VP9Encoder}; use crate::video::format::{PixelFormat, Resolution}; use crate::video::frame::{FrameBuffer, FrameBufferPool, VideoFrame}; +use crate::video::v4l2r_capture::V4l2rCaptureStream; +#[cfg(any(target_arch = "aarch64", target_arch = "arm"))] +use hwcodec::ffmpeg_hw::{ + last_error_message as ffmpeg_hw_last_error, HwMjpegH26xConfig, HwMjpegH26xPipeline, +}; /// Encoded video frame for distribution #[derive(Debug, Clone)] @@ -508,7 +510,10 @@ impl SharedVideoPipeline { #[cfg(any(target_arch = "aarch64", target_arch = "arm"))] if needs_mjpeg_decode && is_rkmpp_encoder - && matches!(config.output_codec, VideoEncoderType::H264 | VideoEncoderType::H265) + && matches!( + config.output_codec, + VideoEncoderType::H264 | VideoEncoderType::H265 + ) { info!( "Initializing FFmpeg HW MJPEG->{} pipeline (no fallback)", @@ -525,7 +530,11 @@ impl SharedVideoPipeline { thread_count: 1, }; let pipeline = HwMjpegH26xPipeline::new(hw_config).map_err(|e| { - let detail = if e.is_empty() { ffmpeg_hw_last_error() } else { e }; + let detail = if e.is_empty() { + ffmpeg_hw_last_error() + } else { + e + }; AppError::VideoError(format!( "FFmpeg HW MJPEG->{} init failed: {}", config.output_codec, detail @@ -899,7 +908,11 @@ impl SharedVideoPipeline { /// Get subscriber count pub fn subscriber_count(&self) -> usize { - self.subscribers.read().iter().filter(|tx| !tx.is_closed()).count() + self.subscribers + .read() + .iter() + .filter(|tx| !tx.is_closed()) + .count() } /// Report that a receiver has lagged behind @@ -948,7 +961,11 @@ impl SharedVideoPipeline { pipeline .reconfigure(bitrate_kbps as i32, gop as i32) .map_err(|e| { - let detail = if e.is_empty() { ffmpeg_hw_last_error() } else { e }; + let detail = if e.is_empty() { + ffmpeg_hw_last_error() + } else { + e + }; AppError::VideoError(format!( "FFmpeg HW reconfigure failed: {}", detail @@ -1364,8 +1381,7 @@ impl SharedVideoPipeline { error!("Capture error: {}", e); } } else { - let counter = - suppressed_capture_errors.entry(key).or_insert(0); + let counter = suppressed_capture_errors.entry(key).or_insert(0); *counter = counter.saturating_add(1); } } @@ -1380,7 +1396,7 @@ impl SharedVideoPipeline { validate_counter = validate_counter.wrapping_add(1); if pixel_format.is_compressed() - && validate_counter % JPEG_VALIDATE_INTERVAL == 0 + && validate_counter.is_multiple_of(JPEG_VALIDATE_INTERVAL) && !VideoFrame::is_valid_jpeg_bytes(&owned[..frame_size]) { continue; @@ -1401,7 +1417,6 @@ impl SharedVideoPipeline { *guard = Some(frame); } let _ = frame_seq_tx.send(sequence); - } pipeline.running_flag.store(false, Ordering::Release); @@ -1466,7 +1481,11 @@ impl SharedVideoPipeline { } let packet = pipeline.encode(raw_frame, pts_ms).map_err(|e| { - let detail = if e.is_empty() { ffmpeg_hw_last_error() } else { e }; + let detail = if e.is_empty() { + ffmpeg_hw_last_error() + } else { + e + }; AppError::VideoError(format!("FFmpeg HW encode failed: {}", detail)) })?; @@ -1486,9 +1505,10 @@ impl SharedVideoPipeline { } let decoded_buf = if input_format.is_compressed() { - let decoder = state.mjpeg_decoder.as_mut().ok_or_else(|| { - AppError::VideoError("MJPEG decoder not initialized".to_string()) - })?; + let decoder = state + .mjpeg_decoder + .as_mut() + .ok_or_else(|| AppError::VideoError("MJPEG decoder not initialized".to_string()))?; let decoded = decoder.decode(raw_frame)?; Some(decoded) } else { @@ -1518,16 +1538,18 @@ impl SharedVideoPipeline { debug!("[Pipeline] Keyframe will be generated for this frame"); } - let encode_result = if needs_yuv420p && state.yuv420p_converter.is_some() { + let encode_result = if needs_yuv420p { // Software encoder with direct input conversion to YUV420P - let conv = state.yuv420p_converter.as_mut().unwrap(); - let yuv420p_data = conv - .convert(raw_frame) - .map_err(|e| AppError::VideoError(format!("YUV420P conversion failed: {}", e)))?; - encoder.encode_raw(yuv420p_data, pts_ms) - } else if state.nv12_converter.is_some() { + if let Some(conv) = state.yuv420p_converter.as_mut() { + let yuv420p_data = conv.convert(raw_frame).map_err(|e| { + AppError::VideoError(format!("YUV420P conversion failed: {}", e)) + })?; + encoder.encode_raw(yuv420p_data, pts_ms) + } else { + encoder.encode_raw(raw_frame, pts_ms) + } + } else if let Some(conv) = state.nv12_converter.as_mut() { // Hardware encoder with input conversion to NV12 - let conv = state.nv12_converter.as_mut().unwrap(); let nv12_data = conv .convert(raw_frame) .map_err(|e| AppError::VideoError(format!("NV12 conversion failed: {}", e)))?; diff --git a/src/video/stream_manager.rs b/src/video/stream_manager.rs index cabe553a..a2734a5f 100644 --- a/src/video/stream_manager.rs +++ b/src/video/stream_manager.rs @@ -718,9 +718,11 @@ impl VideoStreamManager { /// Returns None if video capture cannot be started or pipeline creation fails. pub async fn subscribe_encoded_frames( &self, - ) -> Option>> { + ) -> Option< + tokio::sync::mpsc::Receiver< + std::sync::Arc, + >, + > { // 1. Ensure video capture is initialized (for config discovery) if self.streamer.state().await == StreamerState::Uninitialized { tracing::info!("Initializing video capture for encoded frame subscription"); @@ -756,7 +758,11 @@ impl VideoStreamManager { } // 3. Use WebRtcStreamer to ensure the shared video pipeline is running - match self.webrtc_streamer.ensure_video_pipeline_for_external().await { + match self + .webrtc_streamer + .ensure_video_pipeline_for_external() + .await + { Ok(pipeline) => Some(pipeline.subscribe()), Err(e) => { tracing::error!("Failed to start shared video pipeline: {}", e); diff --git a/src/video/streamer.rs b/src/video/streamer.rs index af2e073f..fdca1d7c 100644 --- a/src/video/streamer.rs +++ b/src/video/streamer.rs @@ -571,11 +571,9 @@ impl Streamer { break; } } - } else { - if zero_since.is_some() { - info!("Clients reconnected, canceling auto-pause"); - zero_since = None; - } + } else if zero_since.is_some() { + info!("Clients reconnected, canceling auto-pause"); + zero_since = None; } } }); @@ -805,7 +803,7 @@ impl Streamer { validate_counter = validate_counter.wrapping_add(1); if pixel_format.is_compressed() - && validate_counter % JPEG_VALIDATE_INTERVAL == 0 + && validate_counter.is_multiple_of(JPEG_VALIDATE_INTERVAL) && !VideoFrame::is_valid_jpeg_bytes(&owned[..frame_size]) { continue; @@ -964,7 +962,7 @@ impl Streamer { *streamer.state.write().await = StreamerState::Recovering; // Publish reconnecting event (every 5 attempts to avoid spam) - if attempt == 1 || attempt % 5 == 0 { + if attempt == 1 || attempt.is_multiple_of(5) { streamer .publish_event(SystemEvent::StreamReconnecting { device: device_path.clone(), diff --git a/src/video/v4l2r_capture.rs b/src/video/v4l2r_capture.rs index cd23f263..0027896c 100644 --- a/src/video/v4l2r_capture.rs +++ b/src/video/v4l2r_capture.rs @@ -10,8 +10,8 @@ use nix::poll::{poll, PollFd, PollFlags, PollTimeout}; use tracing::{debug, warn}; use v4l2r::bindings::{v4l2_requestbuffers, v4l2_streamparm, v4l2_streamparm__bindgen_ty_1}; use v4l2r::ioctl::{ - self, Capabilities, Capability as V4l2rCapability, MemoryConsistency, PlaneMapping, - QBufPlane, QBuffer, QueryBuffer, V4l2Buffer, + self, Capabilities, Capability as V4l2rCapability, MemoryConsistency, PlaneMapping, QBufPlane, + QBuffer, QueryBuffer, V4l2Buffer, }; use v4l2r::memory::{MemoryType, MmapHandle}; use v4l2r::{Format as V4l2rFormat, PixelFormat as V4l2rPixelFormat, QueueType}; @@ -68,24 +68,21 @@ impl V4l2rCaptureStream { )); }; - let mut fmt: V4l2rFormat = ioctl::g_fmt(&fd, queue).map_err(|e| { - AppError::VideoError(format!("Failed to get device format: {}", e)) - })?; + let mut fmt: V4l2rFormat = ioctl::g_fmt(&fd, queue) + .map_err(|e| AppError::VideoError(format!("Failed to get device format: {}", e)))?; fmt.width = resolution.width; fmt.height = resolution.height; fmt.pixelformat = V4l2rPixelFormat::from(&format.to_fourcc()); - let actual_fmt: V4l2rFormat = ioctl::s_fmt(&mut fd, (queue, &fmt)).map_err(|e| { - AppError::VideoError(format!("Failed to set device format: {}", e)) - })?; + let actual_fmt: V4l2rFormat = ioctl::s_fmt(&mut fd, (queue, &fmt)) + .map_err(|e| AppError::VideoError(format!("Failed to set device format: {}", e)))?; let actual_resolution = Resolution::new(actual_fmt.width, actual_fmt.height); let actual_format = PixelFormat::from_v4l2r(actual_fmt.pixelformat).unwrap_or(format); let stride = actual_fmt - .plane_fmt - .get(0) + .plane_fmt.first() .map(|p| p.bytesperline) .unwrap_or_else(|| match actual_format.bytes_per_pixel() { Some(bpp) => actual_resolution.width * bpp as u32, @@ -129,10 +126,7 @@ impl V4l2rCaptureStream { let mut plane_maps = Vec::with_capacity(query.planes.len()); for plane in &query.planes { let mapping = ioctl::mmap(&fd, plane.mem_offset, plane.length).map_err(|e| { - AppError::VideoError(format!( - "Failed to mmap buffer {}: {}", - index, e - )) + AppError::VideoError(format!("Failed to mmap buffer {}: {}", index, e)) })?; plane_maps.push(mapping); } @@ -150,9 +144,8 @@ impl V4l2rCaptureStream { }; stream.queue_all_buffers()?; - ioctl::streamon(&stream.fd, stream.queue).map_err(|e| { - AppError::VideoError(format!("Failed to start capture stream: {}", e)) - })?; + ioctl::streamon(&stream.fd, stream.queue) + .map_err(|e| AppError::VideoError(format!("Failed to start capture stream: {}", e)))?; Ok(stream) } @@ -172,9 +165,8 @@ impl V4l2rCaptureStream { pub fn next_into(&mut self, dst: &mut Vec) -> io::Result { self.wait_ready()?; - let dqbuf: V4l2Buffer = ioctl::dqbuf(&self.fd, self.queue).map_err(|e| { - io::Error::new(io::ErrorKind::Other, format!("dqbuf failed: {}", e)) - })?; + let dqbuf: V4l2Buffer = ioctl::dqbuf(&self.fd, self.queue) + .map_err(|e| io::Error::other(format!("dqbuf failed: {}", e)))?; let index = dqbuf.as_v4l2_buffer().index as usize; let sequence = dqbuf.as_v4l2_buffer().sequence as u64; @@ -211,7 +203,7 @@ impl V4l2rCaptureStream { } self.queue_buffer(index as u32) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; + .map_err(|e| io::Error::other(e.to_string()))?; Ok(CaptureMeta { bytes_used: total, @@ -240,7 +232,7 @@ impl V4l2rCaptureStream { } fn queue_buffer(&mut self, index: u32) -> Result<()> { - let handle = MmapHandle::default(); + let handle = MmapHandle; let planes = self.mappings[index as usize] .iter() .map(|mapping| { diff --git a/src/video/video_session.rs b/src/video/video_session.rs index 7b51f725..07af5f01 100644 --- a/src/video/video_session.rs +++ b/src/video/video_session.rs @@ -326,7 +326,6 @@ impl VideoSessionManager { bitrate_preset: self.config.bitrate_preset, fps: self.config.fps, encoder_backend: self.config.encoder_backend, - ..Default::default() }; // Create new pipeline diff --git a/src/web/handlers/config/apply.rs b/src/web/handlers/config/apply.rs index d7dd88e0..7af5d15a 100644 --- a/src/web/handlers/config/apply.rs +++ b/src/web/handlers/config/apply.rs @@ -191,9 +191,7 @@ pub async fn apply_hid_config( // Low-endpoint UDCs (e.g., musb) cannot handle consumer control endpoints reliably if new_config.backend == HidBackend::Otg { - if let Some(udc) = - crate::otg::configfs::resolve_udc_name(new_config.otg_udc.as_deref()) - { + if let Some(udc) = crate::otg::configfs::resolve_udc_name(new_config.otg_udc.as_deref()) { if crate::otg::configfs::is_low_endpoint_udc(&udc) && new_hid_functions.consumer { tracing::warn!( "UDC {} has low endpoint resources, disabling consumer control", diff --git a/src/web/handlers/extensions.rs b/src/web/handlers/extensions.rs index f91cdb2b..a90c8962 100644 --- a/src/web/handlers/extensions.rs +++ b/src/web/handlers/extensions.rs @@ -86,7 +86,7 @@ pub async fn start_extension( // Start the extension mgr.start(ext_id, &config.extensions) .await - .map_err(|e| AppError::Internal(e))?; + .map_err(AppError::Internal)?; // Return updated status Ok(Json(ExtensionInfo { @@ -108,7 +108,7 @@ pub async fn stop_extension( let mgr = &state.extensions; // Stop the extension - mgr.stop(ext_id).await.map_err(|e| AppError::Internal(e))?; + mgr.stop(ext_id).await.map_err(AppError::Internal)?; // Return updated status Ok(Json(ExtensionInfo { @@ -263,14 +263,16 @@ pub async fn update_gostc_config( if was_enabled && !is_enabled { state.extensions.stop(ExtensionId::Gostc).await.ok(); - } else if !was_enabled && is_enabled && has_key { - if state.extensions.check_available(ExtensionId::Gostc) { - state - .extensions - .start(ExtensionId::Gostc, &new_config.extensions) - .await - .ok(); - } + } else if !was_enabled + && is_enabled + && has_key + && state.extensions.check_available(ExtensionId::Gostc) + { + state + .extensions + .start(ExtensionId::Gostc, &new_config.extensions) + .await + .ok(); } Ok(Json(new_config.extensions.gostc.clone())) @@ -312,14 +314,16 @@ pub async fn update_easytier_config( if was_enabled && !is_enabled { state.extensions.stop(ExtensionId::Easytier).await.ok(); - } else if !was_enabled && is_enabled && has_name { - if state.extensions.check_available(ExtensionId::Easytier) { - state - .extensions - .start(ExtensionId::Easytier, &new_config.extensions) - .await - .ok(); - } + } else if !was_enabled + && is_enabled + && has_name + && state.extensions.check_available(ExtensionId::Easytier) + { + state + .extensions + .start(ExtensionId::Easytier, &new_config.extensions) + .await + .ok(); } Ok(Json(new_config.extensions.easytier.clone())) diff --git a/src/web/handlers/mod.rs b/src/web/handlers/mod.rs index ea0d077f..5a7a16af 100644 --- a/src/web/handlers/mod.rs +++ b/src/web/handlers/mod.rs @@ -205,7 +205,7 @@ fn get_cpu_model() -> String { .count(); Some(format!("{} {}C", std::env::consts::ARCH, cores)) }) - .unwrap_or_else(|| format!("{}", std::env::consts::ARCH)) + .unwrap_or_else(|| std::env::consts::ARCH.to_string()) } /// CPU usage state for calculating usage between samples @@ -686,8 +686,7 @@ pub async fn setup_init( if matches!(new_config.hid.backend, crate::config::HidBackend::Otg) { let mut hid_functions = new_config.hid.effective_otg_functions(); - if let Some(udc) = - crate::otg::configfs::resolve_udc_name(new_config.hid.otg_udc.as_deref()) + if let Some(udc) = crate::otg::configfs::resolve_udc_name(new_config.hid.otg_udc.as_deref()) { if crate::otg::configfs::is_low_endpoint_udc(&udc) && hid_functions.consumer { tracing::warn!( @@ -1842,12 +1841,12 @@ pub async fn mjpeg_stream( break; } // Send last frame again to keep connection alive - if let Some(frame) = handler_clone.current_frame() { - if frame.is_valid_jpeg() { - if tx.send(create_mjpeg_part(frame.data())).await.is_err() { - break; - } - } + let Some(frame) = handler_clone.current_frame() else { + continue; + }; + + if frame.is_valid_jpeg() && tx.send(create_mjpeg_part(frame.data())).await.is_err() { + break; } } } @@ -1866,7 +1865,7 @@ pub async fn mjpeg_stream( yield Ok::(data); // Record FPS after yield - data has been handed to Axum/hyper // This is closer to actual TCP send than recording at tx.send() - handler_for_stream.record_frame_sent(&guard_for_stream.id()); + handler_for_stream.record_frame_sent(guard_for_stream.id()); } }; @@ -2516,7 +2515,7 @@ pub async fn msd_drive_download( let (file_size, mut rx) = drive.read_file_stream(&file_path).await?; // Extract filename for Content-Disposition - let filename = file_path.split('/').last().unwrap_or("download"); + let filename = file_path.split('/').next_back().unwrap_or("download"); // Create a stream from the channel receiver let body_stream = async_stream::stream! { diff --git a/src/web/static_files.rs b/src/web/static_files.rs index 3fb84bbf..44af069b 100644 --- a/src/web/static_files.rs +++ b/src/web/static_files.rs @@ -127,14 +127,14 @@ fn try_serve_file(path: &str) -> Option> { .first_or_octet_stream() .to_string(); - return Some( + Some( Response::builder() .status(StatusCode::OK) .header(header::CONTENT_TYPE, mime) .header(header::CACHE_CONTROL, "public, max-age=86400") .body(Body::from(data)) .unwrap(), - ); + ) } Err(e) => { tracing::debug!( @@ -143,7 +143,7 @@ fn try_serve_file(path: &str) -> Option> { file_path.display(), e ); - return None; + None } } } diff --git a/src/webrtc/config.rs b/src/webrtc/config.rs index 3b5c79c3..3bcf3c1c 100644 --- a/src/webrtc/config.rs +++ b/src/webrtc/config.rs @@ -108,18 +108,15 @@ impl TurnServer { /// Video codec preference #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] +#[derive(Default)] pub enum VideoCodec { + #[default] H264, VP8, VP9, AV1, } -impl Default for VideoCodec { - fn default() -> Self { - Self::H264 - } -} impl std::fmt::Display for VideoCodec { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/src/webrtc/peer.rs b/src/webrtc/peer.rs index 767a0ba3..396b3782 100644 --- a/src/webrtc/peer.rs +++ b/src/webrtc/peer.rs @@ -93,7 +93,6 @@ impl PeerConnection { urls: turn.urls.clone(), username: turn.username.clone(), credential: turn.credential.clone(), - ..Default::default() }); } diff --git a/src/webrtc/rtp.rs b/src/webrtc/rtp.rs index e8dac17b..f576f4e9 100644 --- a/src/webrtc/rtp.rs +++ b/src/webrtc/rtp.rs @@ -330,9 +330,7 @@ impl OpusAudioTrack { stream_id.to_string(), )); - Self { - track, - } + Self { track } } /// Get the underlying WebRTC track @@ -365,13 +363,10 @@ impl OpusAudioTrack { ..Default::default() }; - self.track - .write_sample(&sample) - .await - .map_err(|e| { - error!("Failed to write Opus sample: {}", e); - AppError::WebRtcError(format!("Failed to write audio sample: {}", e)) - }) + self.track.write_sample(&sample).await.map_err(|e| { + error!("Failed to write Opus sample: {}", e); + AppError::WebRtcError(format!("Failed to write audio sample: {}", e)) + }) } } diff --git a/src/webrtc/track.rs b/src/webrtc/track.rs index f9617df2..d3787c63 100644 --- a/src/webrtc/track.rs +++ b/src/webrtc/track.rs @@ -199,7 +199,7 @@ impl VideoTrack { let data = frame.data(); let max_payload_size = 1200; // MTU - headers - let packet_count = (data.len() + max_payload_size - 1) / max_payload_size; + let packet_count = data.len().div_ceil(max_payload_size); let mut bytes_sent = 0u64; for i in 0..packet_count { diff --git a/src/webrtc/universal_session.rs b/src/webrtc/universal_session.rs index b62bc89f..30129954 100644 --- a/src/webrtc/universal_session.rs +++ b/src/webrtc/universal_session.rs @@ -292,7 +292,6 @@ impl UniversalSession { urls: turn.urls.clone(), username: turn.username.clone(), credential: turn.credential.clone(), - ..Default::default() }); } @@ -430,7 +429,9 @@ impl UniversalSession { let candidate = IceCandidate { candidate: candidate_str, sdp_mid: candidate_json.as_ref().and_then(|j| j.sdp_mid.clone()), - sdp_mline_index: candidate_json.as_ref().and_then(|j| j.sdp_mline_index), + sdp_mline_index: candidate_json + .as_ref() + .and_then(|j| j.sdp_mline_index), username_fragment: candidate_json .as_ref() .and_then(|j| j.username_fragment.clone()), @@ -615,20 +616,15 @@ impl UniversalSession { }; // Verify codec matches - let frame_codec = match encoded_frame.codec { - VideoEncoderType::H264 => VideoEncoderType::H264, - VideoEncoderType::H265 => VideoEncoderType::H265, - VideoEncoderType::VP8 => VideoEncoderType::VP8, - VideoEncoderType::VP9 => VideoEncoderType::VP9, - }; + let frame_codec = encoded_frame.codec; if frame_codec != expected_codec { continue; } // Debug log for H265 frames - if expected_codec == VideoEncoderType::H265 { - if encoded_frame.is_keyframe || frames_sent % 30 == 0 { + if expected_codec == VideoEncoderType::H265 + && (encoded_frame.is_keyframe || frames_sent.is_multiple_of(30)) { debug!( "[Session-H265] Received frame #{}: size={}, keyframe={}, seq={}", frames_sent, @@ -637,7 +633,6 @@ impl UniversalSession { encoded_frame.sequence ); } - } // Ensure decoder starts from a keyframe and recover on gaps. let mut gap_detected = false; @@ -768,7 +763,7 @@ impl UniversalSession { // 20ms at 48kHz = 960 samples let samples = 960u32; if let Err(e) = audio_track.write_packet(&opus_frame.data, samples).await { - if packets_sent % 100 == 0 { + if packets_sent.is_multiple_of(100) { debug!("Failed to write audio packet: {}", e); } } else { diff --git a/src/webrtc/video_track.rs b/src/webrtc/video_track.rs index 7ad4c99c..7fe5a8b3 100644 --- a/src/webrtc/video_track.rs +++ b/src/webrtc/video_track.rs @@ -285,7 +285,7 @@ impl UniversalVideoTrack { } /// Get current statistics - + /// /// Write an encoded frame to the track /// /// Handles codec-specific processing: @@ -464,7 +464,6 @@ impl UniversalVideoTrack { if let Err(e) = rtp_track.write_rtp(&packet).await { trace!("H265 write_rtp failed: {}", e); } - } Ok(()) diff --git a/src/webrtc/webrtc_streamer.rs b/src/webrtc/webrtc_streamer.rs index 44ed2b13..8b74e256 100644 --- a/src/webrtc/webrtc_streamer.rs +++ b/src/webrtc/webrtc_streamer.rs @@ -35,8 +35,8 @@ use tokio::sync::RwLock; use tracing::{debug, info, trace, warn}; use crate::audio::{AudioController, OpusFrame}; -use crate::events::EventBus; use crate::error::{AppError, Result}; +use crate::events::EventBus; use crate::hid::HidController; use crate::video::encoder::registry::EncoderBackend; use crate::video::encoder::registry::VideoEncoderType; @@ -270,7 +270,6 @@ impl WebRtcStreamer { bitrate_preset: config.bitrate_preset, fps: config.fps, encoder_backend: config.encoder_backend, - ..Default::default() }; info!("Creating shared video pipeline for {:?}", codec); @@ -311,7 +310,9 @@ impl WebRtcStreamer { } drop(pipeline_guard); - info!("Video pipeline stopped, but keeping capture config for new sessions"); + info!( + "Video pipeline stopped, but keeping capture config for new sessions" + ); } break; } @@ -926,10 +927,7 @@ impl WebRtcStreamer { let pipeline = pipeline_for_callback.clone(); let sid = sid.clone(); tokio::spawn(async move { - info!( - "Requesting keyframe for session {} after reconnect", - sid - ); + info!("Requesting keyframe for session {} after reconnect", sid); pipeline.request_keyframe().await; }); });