i3status_rs/blocks/sound/
alsa.rs1use std::cmp::{max, min};
2use std::process::Stdio;
3use tokio::process::{ChildStdout, Command};
4
5use super::super::prelude::*;
6use super::SoundDevice;
7
8pub(super) struct Device {
9 name: String,
10 device: String,
11 natural_mapping: bool,
12 volume: u32,
13 muted: bool,
14 monitor: ChildStdout,
15}
16
17impl Device {
18 pub(super) fn new(name: String, device: String, natural_mapping: bool) -> Result<Self> {
19 Ok(Device {
20 name,
21 device,
22 natural_mapping,
23 volume: 0,
24 muted: false,
25 monitor: Command::new("alsactl")
26 .arg("monitor")
27 .stdout(Stdio::piped())
28 .spawn()
29 .error("Failed to start alsactl monitor")?
30 .stdout
31 .error("Failed to pipe alsactl monitor output")?,
32 })
33 }
34}
35
36#[async_trait::async_trait]
37impl SoundDevice for Device {
38 fn volume(&self) -> u32 {
39 self.volume
40 }
41
42 fn muted(&self) -> bool {
43 self.muted
44 }
45
46 fn output_name(&self) -> String {
47 self.name.clone()
48 }
49
50 fn output_description(&self) -> Option<String> {
51 None
53 }
54
55 fn active_port(&self) -> Option<String> {
56 None
57 }
58
59 fn form_factor(&self) -> Option<&str> {
60 None
61 }
62
63 async fn get_info(&mut self) -> Result<()> {
64 let mut args = Vec::new();
65 if self.natural_mapping {
66 args.push("-M");
67 };
68 args.extend(["-D", &self.device, "get", &self.name]);
69
70 let output: String = Command::new("amixer")
71 .args(&args)
72 .output()
73 .await
74 .map(|o| std::str::from_utf8(&o.stdout).unwrap().trim().into())
75 .error("could not run amixer to get sound info")?;
76
77 let last_line = &output.lines().last().error("could not get sound info")?;
78
79 const FILTER: &[char] = &['[', ']', '%'];
80 let mut last = last_line
81 .split_whitespace()
82 .filter(|x| x.starts_with('[') && !x.contains("dB"))
83 .map(|s| s.trim_matches(FILTER));
84
85 self.volume = last
86 .next()
87 .error("could not get volume")?
88 .parse::<u32>()
89 .error("could not parse volume to u32")?;
90
91 self.muted = last.next().map(|muted| muted == "off").unwrap_or(false);
92
93 Ok(())
94 }
95
96 async fn set_volume(&mut self, step: i32, max_vol: Option<u32>) -> Result<()> {
97 let new_vol = max(0, self.volume as i32 + step) as u32;
98 let capped_volume = if let Some(vol_cap) = max_vol {
99 min(new_vol, vol_cap)
100 } else {
101 new_vol
102 };
103 let mut args = Vec::new();
104 if self.natural_mapping {
105 args.push("-M");
106 };
107 let vol_str = format!("{capped_volume}%");
108 args.extend(["-D", &self.device, "set", &self.name, &vol_str]);
109
110 Command::new("amixer")
111 .args(&args)
112 .output()
113 .await
114 .error("failed to set volume")?;
115
116 self.volume = capped_volume;
117
118 Ok(())
119 }
120
121 async fn toggle(&mut self) -> Result<()> {
122 let mut args = Vec::new();
123 if self.natural_mapping {
124 args.push("-M");
125 };
126 args.extend(["-D", &self.device, "set", &self.name, "toggle"]);
127
128 Command::new("amixer")
129 .args(&args)
130 .output()
131 .await
132 .error("failed to toggle mute")?;
133
134 self.muted = !self.muted;
135
136 Ok(())
137 }
138
139 async fn wait_for_update(&mut self) -> Result<()> {
140 let mut buf = [0u8; 1024];
141 self.monitor
142 .read(&mut buf)
143 .await
144 .error("Failed to read stdbuf output")?;
145 Ok(())
146 }
147}