~tsdh/public-inbox

swayr: swayrbar: add cmd module v1 PROPOSED

Luca Matei Pintilie: 1
 swayrbar: add cmd module

 3 files changed, 131 insertions(+), 0 deletions(-)
#1272034 arch.yml success
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/53781/mbox | git am -3
Learn more about email & git

[PATCH swayr] swayrbar: add cmd module Export this patch

Signed-off-by: Luca Matei Pintilie <luca@lucamatei.com>
---
 Hi, big fan of swayrbar! I've been using it for a couple of months now
 and one feature of Waybar I've been missing has been the "Idle
 Inhibitor" module
 
 https://github.com/Alexays/Waybar/wiki/Module:-Idle-Inhibitor
 
 As such the easiest way for me to implement this in swayrbar has been to
 run an external script that runs another program to idle inhibit.

 In particular I've set it up with the following config and scripts to
 run wayland-idle-inhibitor

 https://github.com/stwa/wayland-idle-inhibitor

 config.toml
 ```toml
 [[modules]]
 name = "cmd"
 instance = "0"
 format = "{HOME}/.config/swayrbar/status-idle"
 html_escape = false
 
 [modules.on_click]
 Left = ["{HOME}/.config/swayrbar/toggle-idle"]
 ```

 status-idle
 ```sh
 #!/usr/bin/env sh
 pid_file="/tmp/wayland-idle-inhibitor-$(id --user).pid"
 pid=$(cat "${pid_file}")
 shif [ -n "${pid}" ] && kill -0 "${pid}"
 then
 	echo -n "Awake..."
 else
 	echo -n "I'll go to sleep whenever"
 fi
 ```

 ```sh
 #!/usr/bin/env sh
 pid_file="/tmp/wayland-idle-inhibitor-$(id --user).pid"
 pid=$(cat "${pid_file}")
 if [ -n "${pid}" ]
 then
 	kill -- "${pid}"
 	rm "${pid_file}"
 else
 	nohup "$HOME/.config/swayrbar/wayland-idle-inhibitor.py" > /dev/null 2>&1 & echo $! > "${pid_file}"
 fi
 ```
 
 I'm sending this patch mostly to start a discussion. There are a couple
 of open questions I have, and I feel like it's easier to have a PoC to
 discuss.
 
 - Is this even the right approach to implementing what I want?
   Essentially custom code execution.
 - Is "cmd" a good name? Kinda vague but I'm a bit out of ideas
 - What config value should be used here to input the command?
 - What are some sane defaults?
 - No other module does environment variable substitution in strings
   (on_click/format), but personally I would like to have these scripts
   in $HOME/.config/swayrbar/, but not want to hard code my $HOME. Is
   this a desirable feature, or should it be removed?
 - License header? Who should it credit?
 Thank you for taking the time to look over this patch and I hope to
 hear back from you!

 swayrbar/src/bar.rs        |   1 +
 swayrbar/src/module.rs     |   1 +
 swayrbar/src/module/cmd.rs | 129 +++++++++++++++++++++++++++++++++++++
 3 files changed, 131 insertions(+)
 create mode 100644 swayrbar/src/module/cmd.rs

diff --git a/swayrbar/src/bar.rs b/swayrbar/src/bar.rs
index 7f2c0c5..a25ec0f 100644
--- a/swayrbar/src/bar.rs
+++ b/swayrbar/src/bar.rs
@@ -99,6 +99,7 @@ fn create_modules(config: config::Config) -> Vec<Box<dyn BarModuleFn>> {
            "pactl" => module::pactl::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),
            unknown => {
                log::warn!("Unknown module name '{unknown}'.  Ignoring...");
                continue;
diff --git a/swayrbar/src/module.rs b/swayrbar/src/module.rs
index d050658..979eed5 100644
--- a/swayrbar/src/module.rs
+++ b/swayrbar/src/module.rs
@@ -25,6 +25,7 @@ pub mod pactl;
pub mod sysinfo;
pub mod wifi;
pub mod window;
pub mod cmd;

#[derive(Debug)]
pub enum RefreshReason {
diff --git a/swayrbar/src/module/cmd.rs b/swayrbar/src/module/cmd.rs
new file mode 100644
index 0000000..acd40ca
--- /dev/null
+++ b/swayrbar/src/module/cmd.rs
@@ -0,0 +1,129 @@
// Copyright (C) 2024 Luca Matei Pintilie <luca@lucamatei.com>
//
// 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 cmd `swayrbar` module.

use crate::config;
use crate::module::{BarModuleFn, RefreshReason};
use crate::shared::fmt::subst_placeholders;
use std::process::Command;
use std::string::String;
use std::sync::Mutex;
use swaybar_types as s;

const NAME: &str = "cmd";

struct State {
    cached_home: String,
    cached_text: String,
}

pub struct BarModuleCmd {
    config: config::ModuleConfig,
    state: Mutex<State>,
}

fn refresh_state(program: &str) -> String {
    match Command::new(program).output() {
        Ok(output) => String::from_utf8_lossy(&output.stdout).to_string(),
        Err(err) => {
            log::error!("Could not run command: {err}");
            String::new()
        }
    }
}

fn subst_placeholders(fmt: &str, html_escape: bool, state: &State) -> String {
    subst_placeholders!(fmt, html_escape, {
        "HOME" => {
            state.cached_home.to_owned()
        },
    })
}

pub fn create(config: config::ModuleConfig) -> Box<dyn BarModuleFn> {
    Box::new(BarModuleCmd {
        config,
        state: Mutex::new(State {
            cached_home: std::env::var("HOME").unwrap(),
            cached_text: String::new(),
        }),
    })
}

impl BarModuleFn for BarModuleCmd {
    fn default_config(instance: String) -> config::ModuleConfig
    where
        Self: Sized,
    {
        config::ModuleConfig {
            name: NAME.to_owned(),
            instance,
            format: "/usr/bin/echo".to_owned(),
            html_escape: Some(true),
            on_click: None,
        }
    }

    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,
        } {
            // FIXME: config.format is wrong here, but it's the best we have
            state.cached_text = refresh_state(&subst_placeholders(
                &self.config.format,
                self.config.html_escape.unwrap_or(false),
                &state,
            ));
        }

        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.45.2
swayr/patches/arch.yml: SUCCESS in 1m18s

[swayrbar: add cmd module][0] from [Luca Matei Pintilie][1]

[0]: https://lists.sr.ht/~tsdh/public-inbox/patches/53781
[1]: mailto:luca@lucamatei.com

✓ #1272034 SUCCESS swayr/patches/arch.yml https://builds.sr.ht/~tsdh/job/1272034
Luca Matei Pintilie <luca@lucamatei.com> writes:

Hi Luca,
I think a generic shell command module is nice in any case.

If there were a standard idle inhibitor interface you could operate
using sway IPC, then a specialized module would also be nice but I have
no knowledge about that.  I've never needed idle inhibitors until now.
Yes, it's ok.  Maybe "shell-cmd" is a bit more explanatory but I have no
strong opinion here.