Add PipeWire device names via pactl and fix badge margins
This commit is contained in:
1 parent
78e3af691f
commit
b6b019e659
5 files changed
+208
-18
No files matched your search
@@ -347,3 +347,104 @@ pub fn get_audio_output_status(
|
||||
is_playing,
|
||||
})
|
||||
}
|
||||
|
||||
/// PipeWire/PulseAudio sink information
|
||||
#[derive(serde::Serialize, Clone)]
|
||||
pub struct PipewireSink {
|
||||
/// Internal name (e.g., "alsa_output.usb-XXX")
|
||||
pub name: String,
|
||||
/// User-friendly description (e.g., "AB13X Headset Adapter Analog Stereo")
|
||||
pub description: String,
|
||||
/// Current volume percentage (0-100)
|
||||
pub volume: Option<u32>,
|
||||
/// Whether this is the default sink
|
||||
pub is_default: bool,
|
||||
}
|
||||
|
||||
/// Get PipeWire/PulseAudio sink information with friendly names
|
||||
#[tauri::command]
|
||||
pub fn get_pipewire_sinks() -> Result<Vec<PipewireSink>, String> {
|
||||
log::info!("Command: get_pipewire_sinks");
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
// Get default sink name first
|
||||
let default_sink = Command::new("pactl")
|
||||
.args(["get-default-sink"])
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|o| {
|
||||
if o.status.success() {
|
||||
String::from_utf8(o.stdout).ok().map(|s| s.trim().to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
// Get all sinks
|
||||
let output = Command::new("pactl")
|
||||
.args(["list", "sinks"])
|
||||
.output()
|
||||
.map_err(|e| format!("Failed to run pactl: {}. Is PulseAudio/PipeWire installed?", e))?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err("pactl command failed".to_string());
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let mut sinks = Vec::new();
|
||||
|
||||
// Parse pactl output
|
||||
let mut current_name: Option<String> = None;
|
||||
let mut current_description: Option<String> = None;
|
||||
let mut current_volume: Option<u32> = None;
|
||||
|
||||
for line in stdout.lines() {
|
||||
let line = line.trim();
|
||||
|
||||
// New sink starts with "Sink #"
|
||||
if line.starts_with("Sink #") {
|
||||
// Save previous sink if complete
|
||||
if let (Some(name), Some(desc)) = (current_name.take(), current_description.take()) {
|
||||
let is_default = default_sink.as_ref().map(|d| d == &name).unwrap_or(false);
|
||||
sinks.push(PipewireSink {
|
||||
name,
|
||||
description: desc,
|
||||
volume: current_volume.take(),
|
||||
is_default,
|
||||
});
|
||||
}
|
||||
current_volume = None;
|
||||
} else if line.starts_with("Name:") {
|
||||
current_name = Some(line.trim_start_matches("Name:").trim().to_string());
|
||||
} else if line.starts_with("Description:") {
|
||||
current_description = Some(line.trim_start_matches("Description:").trim().to_string());
|
||||
} else if line.starts_with("Volume:") {
|
||||
// Parse volume like "Volume: front-left: 65536 / 100% / 0.00 dB"
|
||||
if let Some(percent_pos) = line.find('%') {
|
||||
// Find the number before %
|
||||
let before_percent = &line[..percent_pos];
|
||||
if let Some(slash_pos) = before_percent.rfind('/') {
|
||||
let vol_str = before_percent[slash_pos + 1..].trim();
|
||||
if let Ok(vol) = vol_str.parse::<u32>() {
|
||||
current_volume = Some(vol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't forget the last sink
|
||||
if let (Some(name), Some(desc)) = (current_name, current_description) {
|
||||
let is_default = default_sink.as_ref().map(|d| d == &name).unwrap_or(false);
|
||||
sinks.push(PipewireSink {
|
||||
name,
|
||||
description: desc,
|
||||
volume: current_volume,
|
||||
is_default,
|
||||
});
|
||||
}
|
||||
|
||||
log::info!("Found {} PipeWire sinks", sinks.len());
|
||||
Ok(sinks)
|
||||
}
|
||||
@@ -235,6 +235,7 @@ pub fn run() {
|
||||
commands::set_media_metadata,
|
||||
commands::get_audio_devices,
|
||||
commands::get_audio_output_status,
|
||||
commands::get_pipewire_sinks,
|
||||
// Queue commands
|
||||
commands::add_to_queue,
|
||||
commands::add_to_queue_next,
|
||||
|
||||
Reference in new issue
Block a user