127 lines
4.2 KiB
Rust
127 lines
4.2 KiB
Rust
#[cfg(not(coverage))]
|
|
fn audio_usb_auto_recover_enabled() -> bool {
|
|
std::env::var("LESAVKA_AUDIO_AUTO_RECOVER_USB")
|
|
.map(|raw| {
|
|
!matches!(
|
|
raw.trim().to_ascii_lowercase().as_str(),
|
|
"0" | "false" | "no" | "off"
|
|
)
|
|
})
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
#[cfg(not(coverage))]
|
|
fn audio_usb_recover_after() -> u32 {
|
|
std::env::var("LESAVKA_AUDIO_AUTO_RECOVER_AFTER")
|
|
.ok()
|
|
.and_then(|raw| raw.parse::<u32>().ok())
|
|
.filter(|value| *value > 0)
|
|
.unwrap_or(3)
|
|
}
|
|
|
|
#[cfg(not(coverage))]
|
|
fn audio_usb_recover_cooldown() -> Duration {
|
|
let millis = std::env::var("LESAVKA_AUDIO_AUTO_RECOVER_COOLDOWN_MS")
|
|
.ok()
|
|
.and_then(|raw| raw.parse::<u64>().ok())
|
|
.unwrap_or(60_000);
|
|
Duration::from_millis(millis)
|
|
}
|
|
|
|
#[cfg(not(coverage))]
|
|
fn is_recoverable_remote_audio_error(message: &str) -> bool {
|
|
message.contains("remote speaker capture produced no audio samples")
|
|
|| message.contains("remote speaker capture stalled")
|
|
|| message.contains("remote speaker capture cadence is too low")
|
|
}
|
|
|
|
#[cfg(not(coverage))]
|
|
#[derive(Debug, Default)]
|
|
struct AudioFailureLogLimiter {
|
|
last_warn_at: Option<Instant>,
|
|
suppressed_repeats: u64,
|
|
last_message: String,
|
|
}
|
|
|
|
#[cfg(not(coverage))]
|
|
/// Rate-limit repeated remote audio failures so operators see state changes, not log floods.
|
|
impl AudioFailureLogLimiter {
|
|
/// Emit the first failure promptly, then aggregate identical repeats.
|
|
fn record(&mut self, context: &'static str, message: &str) {
|
|
let same_message = self.last_message == message;
|
|
let should_warn = !same_message
|
|
|| self
|
|
.last_warn_at
|
|
.map(|last| last.elapsed() >= AUDIO_FAILURE_WARN_INTERVAL)
|
|
.unwrap_or(true);
|
|
|
|
if should_warn {
|
|
tracing::warn!(
|
|
context,
|
|
suppressed_repeats = self.suppressed_repeats,
|
|
"❌🔊 audio stream unhealthy: {message}"
|
|
);
|
|
self.last_warn_at = Some(Instant::now());
|
|
self.suppressed_repeats = 0;
|
|
self.last_message.clear();
|
|
self.last_message.push_str(message);
|
|
} else {
|
|
self.suppressed_repeats = self.suppressed_repeats.saturating_add(1);
|
|
tracing::debug!(
|
|
context,
|
|
suppressed_repeats = self.suppressed_repeats,
|
|
"audio stream repeated unhealthy state suppressed from WARN noise: {message}"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(not(coverage))]
|
|
const AUDIO_FAILURE_WARN_INTERVAL: Duration = Duration::from_secs(30);
|
|
|
|
pub(crate) fn keyboard_stream_report(
|
|
report: Result<KeyboardReport, BroadcastStreamRecvError>,
|
|
remote_capture_enabled: bool,
|
|
remote_capture_was_enabled: &mut bool,
|
|
) -> Option<KeyboardReport> {
|
|
if !remote_capture_enabled {
|
|
let emit_reset = *remote_capture_was_enabled;
|
|
*remote_capture_was_enabled = false;
|
|
return emit_reset.then_some(KeyboardReport { data: vec![0; 8] });
|
|
}
|
|
*remote_capture_was_enabled = true;
|
|
match report {
|
|
Ok(report) => Some(report),
|
|
Err(BroadcastStreamRecvError::Lagged(skipped)) => {
|
|
warn!(
|
|
skipped,
|
|
"⌨️ live keyboard stream lagged; sending a clean reset report before continuing"
|
|
);
|
|
Some(KeyboardReport { data: vec![0; 8] })
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn mouse_stream_report(
|
|
report: Result<MouseReport, BroadcastStreamRecvError>,
|
|
remote_capture_enabled: bool,
|
|
remote_capture_was_enabled: &mut bool,
|
|
) -> Option<MouseReport> {
|
|
if !remote_capture_enabled {
|
|
let emit_reset = *remote_capture_was_enabled;
|
|
*remote_capture_was_enabled = false;
|
|
return emit_reset.then_some(MouseReport { data: vec![0; 4] });
|
|
}
|
|
*remote_capture_was_enabled = true;
|
|
match report {
|
|
Ok(report) => Some(report),
|
|
Err(BroadcastStreamRecvError::Lagged(skipped)) => {
|
|
warn!(
|
|
skipped,
|
|
"🖱️ live mouse stream lagged; sending a neutral report before continuing"
|
|
);
|
|
Some(MouseReport { data: vec![0; 4] })
|
|
}
|
|
}
|
|
}
|