87 lines
2.0 KiB
Rust
87 lines
2.0 KiB
Rust
use std::collections::BTreeMap;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{
|
|
output::{self, SinkList},
|
|
util::CommandExt,
|
|
};
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct Sink {
|
|
pub state: String,
|
|
pub name: String,
|
|
pub description: String,
|
|
pub mute: bool,
|
|
|
|
/// Comma-separated list
|
|
pub channel_map: String,
|
|
|
|
/// Volume values per channel
|
|
pub volume: BTreeMap<String, Volume>,
|
|
|
|
/// Arbitrary stringly-typed properties
|
|
pub properties: BTreeMap<String, String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct Volume {
|
|
/// Volume value in range 0..=65536
|
|
value: u32,
|
|
value_percent: String,
|
|
db: String,
|
|
}
|
|
|
|
pub fn get_sinks() -> eyre::Result<output::SinkList> {
|
|
let default_sink = pactl_get_default_sink_name()?;
|
|
let sinks = pactl_get_sinks()?;
|
|
|
|
let mut output = SinkList::default();
|
|
|
|
for sink in sinks {
|
|
let default = sink.name == default_sink;
|
|
|
|
let sink = output::Sink {
|
|
name: sink.name,
|
|
pretty_name: sink.description,
|
|
muted: sink.mute,
|
|
default,
|
|
volume: sink
|
|
.volume
|
|
.first_key_value()
|
|
.map(|(_key, volume)| u64::from(volume.value) * 100 / u64::from(u16::MAX))
|
|
.map(|volume| volume as u8)
|
|
.unwrap_or(0),
|
|
};
|
|
|
|
if default {
|
|
output.default = Some(sink.clone());
|
|
}
|
|
|
|
output.all.push(sink);
|
|
}
|
|
|
|
output.all.sort_by_key(|s| s.pretty_name.clone());
|
|
|
|
Ok(output)
|
|
}
|
|
|
|
fn pactl_get_sinks() -> eyre::Result<Vec<Sink>> {
|
|
std::process::Command::new("pactl")
|
|
.args(["--format", "json"])
|
|
.args(["list", "sinks"])
|
|
.just_exec_json()
|
|
}
|
|
|
|
fn pactl_get_default_sink_name() -> eyre::Result<String> {
|
|
let mut default_sink = std::process::Command::new("pactl")
|
|
.arg("get-default-sink")
|
|
.just_exec()?;
|
|
|
|
while default_sink.ends_with(char::is_whitespace) {
|
|
default_sink.pop();
|
|
}
|
|
|
|
Ok(default_sink)
|
|
}
|