bitraid: 1 Add pipewire (wpctl) module 4 files changed, 231 insertions(+), 5 deletions(-)
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~tsdh/public-inbox/patches/55280/mbox | git am -3Learn more about email & git
--- README.md | 30 +++++- swayrbar/src/bar.rs | 1 + swayrbar/src/module.rs | 1 + swayrbar/src/module/wpctl.rs | 204 +++++++++++++++++++++++++++++++++++ 4 files changed, 231 insertions(+), 5 deletions(-) create mode 100644 swayrbar/src/module/wpctl.rs diff --git a/README.md b/README.md index 1321bc2..1dc6e84 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ These commands change the layout of the current workspace. between a tabbed and tiled layout, i.e., it calls `shuffle-tile-workspace` if it is currently tabbed, and calls `shuffle-tile-workspace` if it is currently tiled. - + #### Scripting commands * `get-windows-as-json` returns a JSON containing all windows, possibly with @@ -265,7 +265,7 @@ These commands change the layout of the current workspace. Swayr supports most of the criteria querys defined by Sway, see section `CRITERIA` in `man sway(5)`. Right now, these are: * `app_id=<regex | __focused__>` -* `class=<regex | __focused__>` +* `class=<regex | __focused__>` * `instance=<regex | __focused__>` * `title=<regex | __focused__>` * `workspace=<regex | __focused__ | __visible__ >` @@ -276,7 +276,7 @@ Swayr supports most of the criteria querys defined by Sway, see section * `floating` * `tiling` * `app_name=<regex | __focused__>` (not in sway!) - + The last criterion `app_name` is matched against the application's name which can either be `app_id`, `window_properties.class`, or `window_properties.instance` (whatever is filled). @@ -717,9 +717,12 @@ Right now, there are the following modules: 4. The `date` module can show, you guess it, the current date and time! 5. The `pactl` module can show the current volume percentage and muted state. Clicks can increase/decrease the volume or toggle the mute state. -6. The `nmcli` module uses NetworkManager's `nmcli` command line tool to show +6. The `wpctl` module can show the current volume percentage and muted state. + Clicks can increase/decrease the volume or toggle the mute state. It + requires PipeWire. +7. The `nmcli` module uses NetworkManager's `nmcli` command line tool to show the currently connected wifi and its signal strength. -7. The `iwctl` module the `iwctl` command line tool to show the currently +8. The `iwctl` module the `iwctl` command line tool to show the currently connected wifi and its signal strength. @@ -892,6 +895,23 @@ By default, it has the following click bindings: * `WheelUp` and `WheelDown` increase/decrease the volume of the default sink. +#### The `wpctl` module + +The `wpctl` module requires the pipewire command line tool of the same name +to be installed. It supports the following placeholders: +* `{volume}` is the current volume percentage of the default sink. +* `{muted}` is the string `" muted"` if the default sink is currently muted, + otherwise it is the empty string. +* `{volume_source}` is the current volume percentage of the default source. +* `{muted_source}` is the string `" muted"` if the default source is currently + muted, otherwise it is the empty string. + +By default, it has the following click bindings: +* `Left` executes `foot watch wpctl status` (monitor PipeWire objects). +* `Right` toggles the default sink's mute state. +* `WheelUp` and `WheelDown` increase/decrease the volume of the default sink. + + #### The `nmcli` module The `nmcli` module requires NetworkManager and the `nmcli` command line tool. diff --git a/swayrbar/src/bar.rs b/swayrbar/src/bar.rs index 8321c3b..b62053b 100644 --- a/swayrbar/src/bar.rs +++ b/swayrbar/src/bar.rs @@ -97,6 +97,7 @@ fn create_modules(config: config::Config) -> Vec<Box<dyn BarModuleFn>> { "battery" => module::battery::create(mc), "date" => module::date::create(mc), "pactl" => module::pactl::create(mc), + "wpctl" => module::wpctl::create(mc), "nmcli" => module::wifi::create(module::wifi::WifiTool::Nmcli, mc), "iwctl" => module::wifi::create(module::wifi::WifiTool::Iwctl, mc), "cmd" => module::cmd::create(mc), diff --git a/swayrbar/src/module.rs b/swayrbar/src/module.rs index fe2d39f..c3e3ba4 100644 --- a/swayrbar/src/module.rs +++ b/swayrbar/src/module.rs @@ -23,6 +23,7 @@ pub mod battery; pub mod cmd; pub mod date; pub mod pactl; +pub mod wpctl; pub mod sysinfo; pub mod wifi; pub mod window; diff --git a/swayrbar/src/module/wpctl.rs b/swayrbar/src/module/wpctl.rs new file mode 100644 index 0000000..be9f4f4 --- /dev/null +++ b/swayrbar/src/module/wpctl.rs @@ -0,0 +1,204 @@ +// Copyright (C) 2024 Tassilo Horn <tsdh@gnu.org> +// Copyright (C) 2024 bitraid <bitraid@protonmail.ch> +// +// 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 wpctl `swayrbar` module. + +use crate::config; +use crate::module::{BarModuleFn, RefreshReason}; +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 = "wpctl"; + +struct State { + volume: u8, + muted: bool, + volume_source: u8, + muted_source: bool, + cached_text: String, +} + +pub static VOLUME_RX: Lazy<Regex> = + Lazy::new(|| Regex::new(r".* (?<num>\d+)\.(?<frac>\d{2}).*").unwrap()); + +fn run_wpctl(args: &[&str]) -> String { + match Command::new("wpctl").args(args).output() { + Ok(output) => String::from_utf8_lossy(&output.stdout).to_string(), + Err(err) => { + log::error!("Could not run wpctl: {err}"); + String::new() + } + } +} + +fn get_volume(device: &str) -> (u8, bool) { + let output = run_wpctl(&["get-volume", device]); + let mut volume = String::new(); + VOLUME_RX + .captures(&output) + .unwrap() + .expand("$num$frac", &mut volume); + (volume.parse::<u8>().unwrap_or(255_u8), output.contains("[MUTED]")) +} + +pub struct BarModuleWpctl { + config: config::ModuleConfig, + state: Mutex<State>, +} + +fn refresh_state(state: &mut State, fmt_str: &str, html_escape: bool) { + (state.volume, state.muted) = get_volume("@DEFAULT_AUDIO_SINK@"); + (state.volume_source, state.muted_source) = get_volume("@DEFAULT_AUDIO_SOURCE@"); + state.cached_text = subst_placeholders(fmt_str, html_escape, state); +} + +fn subst_placeholders(fmt: &str, html_escape: bool, state: &State) -> String { + subst_placeholders!(fmt, html_escape, { + "volume" => { + state.volume + }, + "muted" =>{ + if state.muted { + " muted" + } else { + "" + } + }, + "volume_source" => { + state.volume_source + }, + "muted_source" =>{ + if state.muted_source { + " muted" + } else { + "" + } + }, + }) +} + +pub fn create(config: config::ModuleConfig) -> Box<dyn BarModuleFn> { + Box::new(BarModuleWpctl { + config, + state: Mutex::new(State { + volume: 255_u8, + muted: false, + volume_source: 255_u8, + muted_source: false, + cached_text: String::new(), + }), + }) +} + +impl BarModuleFn for BarModuleWpctl { + 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!["foot".to_owned(), "watch".to_owned(), + "wpctl".to_owned(), "status".to_owned()]), + ( + "Right".to_owned(), + vec![ + "wpctl".to_owned(), + "set-mute".to_owned(), + "@DEFAULT_AUDIO_SINK@".to_owned(), + "toggle".to_owned(), + ], + ), + ( + "WheelUp".to_owned(), + vec![ + "wpctl".to_owned(), + "set-volume".to_owned(), + "@DEFAULT_AUDIO_SINK@".to_owned(), + "1%+".to_owned(), + ], + ), + ( + "WheelDown".to_owned(), + vec![ + "wpctl".to_owned(), + "set-volume".to_owned(), + "@DEFAULT_AUDIO_SINK@".to_owned(), + "1%-".to_owned(), + ], + ), + ])), + } + } + + fn get_config(&self) -> &config::ModuleConfig { + &self.config + } + + fn build(&self, reason: &RefreshReason) -> s::Block { + let mut state = self.state.lock().expect("Could not lock state."); + + if match reason { + RefreshReason::TimerEvent => true, + RefreshReason::ClickEvent { name, instance } => { + name == &self.config.name && instance == &self.config.instance + } + _ => false, + } { + refresh_state( + &mut state, + &self.config.format, + self.config.is_html_escape(), + ); + } + + s::Block { + name: Some(NAME.to_owned()), + instance: Some(self.config.instance.clone()), + full_text: state.cached_text.to_owned(), + 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_cmd_args<'a>(&'a self, cmd: &'a [String]) -> Vec<String> { + let state = self.state.lock().expect("Could not lock state."); + cmd.iter() + .map(|arg| subst_placeholders(arg, false, &state)) + .collect() + } +} -- 2.46.1
Applied, pushed, and released as swayrbar-0.4.1! Thanks a lot! Tassilo