~tsdh/public-inbox

swayrbar: Add pipewire (wpctl) module v1 PROPOSED

bitraid: 1
 Add pipewire (wpctl) module

 3 files changed, 205 insertions(+), 0 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~tsdh/public-inbox/patches/55265/mbox | git am -3
Learn more about email & git

[PATCH swayrbar] Add pipewire (wpctl) module Export this patch

---
 swayrbar/src/bar.rs          |   1 +
 swayrbar/src/module.rs       |   1 +
 swayrbar/src/module/wpctl.rs | 203 +++++++++++++++++++++++++++++++++++
 3 files changed, 205 insertions(+)
 create mode 100644 swayrbar/src/module/wpctl.rs

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..f3c7241
--- /dev/null
+++ b/swayrbar/src/module/wpctl.rs
@@ -0,0 +1,203 @@
// Copyright (C) 2022-2023  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 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
bitraid <bitraid@protonmail.ch> writes:

Hi, thanks a lot!

the code looks fine and I'm happy to accept your contribution.  Some
minor nitpicks below.