mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
fix(audio): suppress ALSA stderr noise during device enumeration
Device watcher (3 s loop) and audio_list_devices both called cpal output_devices(), triggering ALSA to probe unavailable backends (JACK, OSS, dmix) and spam stderr with error messages. Fix: redirect fd 2 to /dev/null for the duration of each enumeration on Unix via libc dup/dup2 + RAII guard. Also add a module-level cache in Settings.tsx so audio_list_devices is only invoked once per app session instead of on every Audio tab activation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Generated
+1
@@ -3564,6 +3564,7 @@ dependencies = [
|
||||
"discord-rich-presence",
|
||||
"futures-util",
|
||||
"id3",
|
||||
"libc",
|
||||
"lofty",
|
||||
"md5",
|
||||
"reqwest 0.12.28",
|
||||
|
||||
@@ -47,6 +47,9 @@ thread-priority = "1"
|
||||
lofty = "0.22"
|
||||
id3 = "1.16.4"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows = { version = "0.58", features = [
|
||||
"Win32_Foundation",
|
||||
|
||||
@@ -2,6 +2,8 @@ use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
|
||||
use std::time::{Duration, Instant};
|
||||
#[cfg(unix)]
|
||||
use libc;
|
||||
|
||||
use ringbuf::{HeapConsumer, HeapProducer, HeapRb};
|
||||
|
||||
@@ -2896,9 +2898,29 @@ pub async fn audio_play_radio(
|
||||
}
|
||||
|
||||
/// Returns the names of all available audio output devices on the current host.
|
||||
/// On Linux, ALSA probes unavailable backends (JACK, OSS, dmix) and prints errors to
|
||||
/// stderr. We suppress fd 2 for the duration of enumeration to keep the terminal clean.
|
||||
#[tauri::command]
|
||||
pub fn audio_list_devices() -> Vec<String> {
|
||||
use rodio::cpal::traits::{DeviceTrait, HostTrait};
|
||||
|
||||
#[cfg(unix)]
|
||||
let _guard = {
|
||||
struct StderrGuard(i32);
|
||||
impl Drop for StderrGuard {
|
||||
fn drop(&mut self) {
|
||||
unsafe { libc::dup2(self.0, 2); libc::close(self.0); }
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
let saved = libc::dup(2);
|
||||
let devnull = libc::open(b"/dev/null\0".as_ptr() as *const libc::c_char, libc::O_WRONLY);
|
||||
libc::dup2(devnull, 2);
|
||||
libc::close(devnull);
|
||||
StderrGuard(saved)
|
||||
}
|
||||
};
|
||||
|
||||
let host = rodio::cpal::default_host();
|
||||
host.output_devices()
|
||||
.map(|iter| iter.filter_map(|d| d.name().ok()).collect())
|
||||
@@ -2975,8 +2997,21 @@ pub fn start_device_watcher(engine: &AudioEngine, app: tauri::AppHandle) {
|
||||
tokio::time::sleep(Duration::from_secs(3)).await;
|
||||
|
||||
// Enumerate all available output devices and the current default.
|
||||
// Suppress stderr on Unix to avoid ALSA probing noise (JACK, OSS, dmix).
|
||||
let (current_default, available) = tauri::async_runtime::spawn_blocking(|| {
|
||||
use rodio::cpal::traits::{DeviceTrait, HostTrait};
|
||||
#[cfg(unix)]
|
||||
let _guard = unsafe {
|
||||
struct StderrGuard(i32);
|
||||
impl Drop for StderrGuard {
|
||||
fn drop(&mut self) { unsafe { libc::dup2(self.0, 2); libc::close(self.0); } }
|
||||
}
|
||||
let saved = libc::dup(2);
|
||||
let devnull = libc::open(b"/dev/null\0".as_ptr() as *const libc::c_char, libc::O_WRONLY);
|
||||
libc::dup2(devnull, 2);
|
||||
libc::close(devnull);
|
||||
StderrGuard(saved)
|
||||
};
|
||||
let host = rodio::cpal::default_host();
|
||||
let default = host.default_output_device().and_then(|d| d.name().ok());
|
||||
let available: Vec<String> = host
|
||||
|
||||
Reference in New Issue
Block a user