pub fn detect_audio(sys: &sysinfo::System) -> Option<String> {
#[cfg(target_os = "linux")]
{
let mut server = None;
for process in sys.processes().values() {
let name = process.name().to_string_lossy().to_lowercase();
if name.contains("pipewire") {
server = Some("PipeWire");
break;
} else if name.contains("pulseaudio") {
server = Some("PulseAudio");
}
}
let server_str = server.unwrap_or("ALSA");
let mut devices = Vec::new();
if let Ok(content) = std::fs::read_to_string("/proc/asound/cards") {
devices = parse_asound_cards(&content, "/proc/asound");
}
if !devices.is_empty() {
Some(format!("{} ({})", server_str, devices.join(", ")))
} else {
Some(server_str.to_string())
}
}
#[cfg(target_os = "macos")]
{
let _ = sys;
let devices = crate::macos_ffi::get_audio_device_names();
if !devices.is_empty() {
Some(format!("CoreAudio ({})", devices.join(", ")))
} else {
Some("CoreAudio".to_string())
}
}
#[cfg(target_os = "windows")]
{
let _ = sys;
use crate::win_reg;
const MEDIA_CLASS: &str =
"SYSTEM\\CurrentControlSet\\Control\\Class\\{4d36e96c-e325-11ce-bfc1-08002be10318}";
let mut devices = Vec::new();
for subkey_name in win_reg::enum_reg_subkeys(win_reg::HKEY_LOCAL_MACHINE, MEDIA_CLASS) {
if subkey_name.eq_ignore_ascii_case("Properties") {
continue;
}
let subkey = format!("{}\\{}", MEDIA_CLASS, subkey_name);
if let Some(name) =
win_reg::get_reg_string(win_reg::HKEY_LOCAL_MACHINE, &subkey, "DriverDesc")
{
if let Some(clean_name) = normalize_win_audio_device(name.trim()) {
if !devices.contains(&clean_name) {
devices.push(clean_name);
}
}
}
}
if !devices.is_empty() {
Some(format!("Windows Audio ({})", devices.join(", ")))
} else {
Some("Windows Audio".to_string())
}
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
{
let _ = sys;
None
}
}
#[allow(dead_code)]
pub fn parse_asound_cards(content: &str, asound_dir: &str) -> Vec<String> {
let mut devices = Vec::new();
if let Ok(entries) = std::fs::read_dir(asound_dir) {
for entry in entries.filter_map(|e| e.ok()) {
let path = entry.path();
if path.is_dir() {
let name = entry.file_name().to_string_lossy().to_string();
if name.starts_with("card") {
if let Ok(sub_entries) = std::fs::read_dir(&path) {
for sub_entry in sub_entries.filter_map(|se| se.ok()) {
let sub_path = sub_entry.path();
let sub_name = sub_entry.file_name().to_string_lossy().to_string();
if sub_name.starts_with("codec#") {
if let Ok(codec_content) = std::fs::read_to_string(&sub_path) {
for line in codec_content.lines() {
if let Some(stripped) = line.strip_prefix("Codec: ") {
let codec_name = stripped.trim().to_string();
if !codec_name.is_empty()
&& !devices.contains(&codec_name)
{
devices.push(codec_name);
}
}
}
}
}
}
}
}
}
}
}
if devices.is_empty() {
for line in content.lines() {
if let Some(idx) = line.find("]: ") {
let desc = line[idx + 3..].trim();
let device_name = if let Some(dash_idx) = desc.find(" - ") {
desc[dash_idx + 3..].trim()
} else {
desc
};
if !device_name.is_empty() && !devices.contains(&device_name.to_string()) {
devices.push(device_name.to_string());
}
}
}
}
devices
}
#[allow(dead_code)]
pub fn normalize_win_audio_device(name: &str) -> Option<String> {
let lower = name.to_lowercase();
if lower.is_empty()
|| lower.starts_with("microsoft ")
|| lower.contains("trusted audio")
|| lower.contains("a2dp")
|| lower.contains("render audio")
|| lower.contains("capture audio")
|| lower.contains("uaj ")
|| lower.contains("speaker device")
|| lower.contains("microphone device")
{
return None;
}
if lower.contains("soundwire") {
return Some("AMD SoundWire Audio".to_string());
}
if lower.contains("amd high definition audio") || lower == "amd audio device" {
return Some("AMD High Definition Audio".to_string());
}
if lower.contains("realtek") {
return Some("Realtek High Definition Audio".to_string());
}
if lower.contains("nvidia") {
return Some("NVIDIA High Definition Audio".to_string());
}
if lower.contains("intel") {
return Some("Intel Smart Sound Technology".to_string());
}
if lower.contains("streaming") {
return None;
}
Some(name.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_asound_cards() {
let sample = " 0 [PCH ]: HDA-Intel - HDA Intel PCH\n 1 [NVidia ]: HDA-Intel - HDA NVIDIA HDMI\n 2 [sofhdadsp ]: sof-hda-dsp - sof-hda-dsp\n DellInc.-Inspiron1676302_in_1-0DR8JD\n";
let parsed = parse_asound_cards(sample, "/nonexistent");
assert_eq!(
parsed,
vec![
"HDA Intel PCH".to_string(),
"HDA NVIDIA HDMI".to_string(),
"sof-hda-dsp".to_string()
]
);
}
#[test]
fn test_normalize_win_audio_device_filters_synthetic_and_normalizes() {
assert_eq!(
normalize_win_audio_device("Microsoft Streaming Service Proxy"),
None
);
assert_eq!(
normalize_win_audio_device("Microsoft Bluetooth A2dp Source"),
None
);
assert_eq!(
normalize_win_audio_device("AMD SoundWire Audio Streaming Speaker Device"),
None
);
assert_eq!(
normalize_win_audio_device("AMD SoundWire Audio Streaming Device"),
Some("AMD SoundWire Audio".to_string())
);
assert_eq!(
normalize_win_audio_device("USB Audio Device"),
Some("USB Audio Device".to_string())
);
assert_eq!(
normalize_win_audio_device("AMD High Definition Audio Device"),
Some("AMD High Definition Audio".to_string())
);
}
}