parent
93677b195b
commit
f02c8bc8ca
5 changed files with 189 additions and 0 deletions
@ -0,0 +1,175 @@ |
|||||||
|
// Copyright (C) 2022 Tassilo Horn <tsdh@gnu.org>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by the Free
|
||||||
|
// Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
// more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License along with
|
||||||
|
// this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! The pactl `swayrbar` module.
|
||||||
|
|
||||||
|
use crate::config; |
||||||
|
use crate::module::BarModuleFn; |
||||||
|
use crate::shared::fmt::subst_placeholders; |
||||||
|
use once_cell::sync::Lazy; |
||||||
|
use regex::Regex; |
||||||
|
use std::collections::HashMap; |
||||||
|
use std::process::Command; |
||||||
|
use std::sync::Mutex; |
||||||
|
use swaybar_types as s; |
||||||
|
|
||||||
|
const NAME: &str = "pactl"; |
||||||
|
|
||||||
|
struct State { |
||||||
|
volume: u8, |
||||||
|
muted: bool, |
||||||
|
} |
||||||
|
|
||||||
|
pub static VOLUME_RX: Lazy<Regex> = |
||||||
|
Lazy::new(|| Regex::new(r".?* (\d+)%.*").unwrap()); |
||||||
|
|
||||||
|
fn run_pactl(args: &[&str]) -> String { |
||||||
|
match Command::new("pactl").args(args).output() { |
||||||
|
Ok(output) => String::from_utf8_lossy(&output.stdout).to_string(), |
||||||
|
Err(err) => { |
||||||
|
log::error!("Could not run pactl: {}", err); |
||||||
|
String::new() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn get_volume() -> u8 { |
||||||
|
let output = run_pactl(&["get-sink-volume", "@DEFAULT_SINK@"]); |
||||||
|
VOLUME_RX |
||||||
|
.captures(&output) |
||||||
|
.map(|c| c.get(1).unwrap().as_str().parse::<u8>().unwrap()) |
||||||
|
.unwrap_or(255_u8) |
||||||
|
} |
||||||
|
|
||||||
|
fn get_mute_state() -> bool { |
||||||
|
run_pactl(&["get-sink-mute", "@DEFAULT_SINK@"]).contains("yes") |
||||||
|
} |
||||||
|
|
||||||
|
pub struct BarModulePactl { |
||||||
|
config: config::ModuleConfig, |
||||||
|
state: Mutex<State>, |
||||||
|
} |
||||||
|
|
||||||
|
fn refresh_state(state: &mut State) { |
||||||
|
state.volume = get_volume(); |
||||||
|
state.muted = get_mute_state(); |
||||||
|
} |
||||||
|
|
||||||
|
fn get_text(fmt: &str, html_escape: bool, state: &State) -> String { |
||||||
|
subst_placeholders!(fmt, html_escape, { |
||||||
|
"volume" => { |
||||||
|
state.volume |
||||||
|
}, |
||||||
|
"muted" =>{ |
||||||
|
if state.muted { |
||||||
|
" muted" |
||||||
|
} else { |
||||||
|
"" |
||||||
|
} |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
impl BarModuleFn for BarModulePactl { |
||||||
|
fn create(config: config::ModuleConfig) -> Box<dyn BarModuleFn> |
||||||
|
where |
||||||
|
Self: Sized, |
||||||
|
{ |
||||||
|
Box::new(BarModulePactl { |
||||||
|
config, |
||||||
|
state: Mutex::new(State { |
||||||
|
volume: 255_u8, |
||||||
|
muted: false, |
||||||
|
}), |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
fn default_config(instance: String) -> config::ModuleConfig |
||||||
|
where |
||||||
|
Self: Sized, |
||||||
|
{ |
||||||
|
config::ModuleConfig { |
||||||
|
name: NAME.to_owned(), |
||||||
|
instance, |
||||||
|
format: "🔈 Vol: {volume:{:3}}%{muted}".to_owned(), |
||||||
|
html_escape: Some(true), |
||||||
|
on_click: Some(HashMap::from([ |
||||||
|
("Left".to_owned(), vec!["pavucontrol".to_owned()]), |
||||||
|
( |
||||||
|
"Right".to_owned(), |
||||||
|
vec![ |
||||||
|
"pactl".to_owned(), |
||||||
|
"set-sink-mute".to_owned(), |
||||||
|
"@DEFAULT_SINK@".to_owned(), |
||||||
|
"toggle".to_owned(), |
||||||
|
], |
||||||
|
), |
||||||
|
( |
||||||
|
"WheelUp".to_owned(), |
||||||
|
vec![ |
||||||
|
"pactl".to_owned(), |
||||||
|
"set-sink-volume".to_owned(), |
||||||
|
"@DEFAULT_SINK@".to_owned(), |
||||||
|
"+1%".to_owned(), |
||||||
|
], |
||||||
|
), |
||||||
|
( |
||||||
|
"WheelDown".to_owned(), |
||||||
|
vec![ |
||||||
|
"pactl".to_owned(), |
||||||
|
"set-sink-volume".to_owned(), |
||||||
|
"@DEFAULT_SINK@".to_owned(), |
||||||
|
"-1%".to_owned(), |
||||||
|
], |
||||||
|
), |
||||||
|
])), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn get_config(&self) -> &config::ModuleConfig { |
||||||
|
&self.config |
||||||
|
} |
||||||
|
|
||||||
|
fn build(&self) -> s::Block { |
||||||
|
let mut state = self.state.lock().expect("Could not lock state."); |
||||||
|
refresh_state(&mut state); |
||||||
|
let text = |
||||||
|
get_text(&self.config.format, self.config.is_html_escape(), &state); |
||||||
|
s::Block { |
||||||
|
name: Some(NAME.to_owned()), |
||||||
|
instance: Some(self.config.instance.clone()), |
||||||
|
full_text: text, |
||||||
|
align: Some(s::Align::Left), |
||||||
|
markup: Some(s::Markup::Pango), |
||||||
|
short_text: None, |
||||||
|
color: None, |
||||||
|
background: None, |
||||||
|
border: None, |
||||||
|
border_top: None, |
||||||
|
border_bottom: None, |
||||||
|
border_left: None, |
||||||
|
border_right: None, |
||||||
|
min_width: None, |
||||||
|
urgent: None, |
||||||
|
separator: Some(true), |
||||||
|
separator_block_width: None, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn subst_args<'a>(&'a self, cmd: &'a [String]) -> Option<Vec<String>> { |
||||||
|
let state = self.state.lock().expect("Could not lock state."); |
||||||
|
Some(cmd.iter().map(|arg| get_text(arg, false, &state)).collect()) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue