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
Luca Matei Pintilie <luca@lucamatei.com> writes:
Hi Luca,
> Hi, big fan of swayrbar!
Thanks!
> 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
> [...]
> 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.
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.
> - Is "cmd" a good name? Kinda vague but I'm a bit out of ideas.
Yes, it's ok. Maybe "shell-cmd" is a bit more explanatory but I have no
strong opinion here.
> - What config value should be used here to input the command?
I think that "format" isn't bad. It executes the command in format and
displays its output. It has to be documented, though.
And the question is how to cope with shell commands with arguments,
e.g., "my-command {arg1} '{arg2} {arg3}'" which would usually be
specified as ["cmd", "{arg1}", "'{arg2} {arg3}'"]...
I guess that should be run with
Command::new("sh")
.arg("-c")
.arg(command)
.output()
so that the interpretation is deferred to sh.
> - What are some sane defaults?
I don't think there can be any for this module.
> - 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?
Wouldn't ~/.config/swayrbar/ work as well?
And could't you defer that business to sh by using
$HOME/.config/swayrbar/ and passing that to sh -c as shown above? Then
any environment variable would work.
We should also think about what other placeholders could be useful for
passing to shell commands. I think at least the {pid} of the currently
focused window could be useful.
> - License header? Who should it credit?
You wrote it, so credit yourself.
Bye,
Tassilo