aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorIgor Soarez <igor@soarez.org>2026-10-20 15:01:18 +0300
committerIgor Soarez <igor@soarez.org>2026-10-20 15:01:18 +0300
commitaa4d6fcd9eac0d0fe051d6307ed6009adefbb2e1 (patch)
treec20523f3753f7c04b34f163135d1419094a9838f /crates
parenteed9c9c1141e118e43b0055f935dd891d89bebd6 (diff)
fix clicks from the menu bar app: find tmux without homebrew on PATH
launchd starts the menu bar app with PATH=/usr/bin:/bin:/usr/sbin:/sbin, so the `dingger focus` it runs for every click (panel rows, menu rows, the jump shortcut) could not find homebrew's tmux and failed with a misleading "no tmux client attached"; the app discarded the error. dingger now finds tmux on PATH or in the usual install locations (DINGGER_TMUX overrides), reports a missing tmux as such, and the app logs failed jumps. also: panel rows own every click inside them (labels took the clicks and counted as window-drag background). e2e step 14 repeats the menu bar row click and dingger focus under launchd's environment; both fail with the previous binaries.
Diffstat (limited to 'crates')
-rw-r--r--crates/dinggerd/src/main.rs2
-rw-r--r--crates/dinggerd/src/tmux.rs36
2 files changed, 29 insertions, 9 deletions
diff --git a/crates/dinggerd/src/main.rs b/crates/dinggerd/src/main.rs
index 285f83a..3707d51 100644
--- a/crates/dinggerd/src/main.rs
+++ b/crates/dinggerd/src/main.rs
@@ -125,7 +125,7 @@ fn main() {
/// by the watcher on its next tick). This is the single code path behind
/// every click: the tmux status line and the menu bar rows.
fn focus(client: Option<String>, pane: Option<String>) -> Result<(), String> {
- let clients = tmux::clients();
+ let clients = tmux::clients()?;
// The named client if it is attached; otherwise the most recently active
// one (a click on the status line makes the clicking client the most
// recent, so this is right even if the name did not expand).
diff --git a/crates/dinggerd/src/tmux.rs b/crates/dinggerd/src/tmux.rs
index 6189c4b..48d9544 100644
--- a/crates/dinggerd/src/tmux.rs
+++ b/crates/dinggerd/src/tmux.rs
@@ -37,8 +37,30 @@ impl Pane {
}
}
+/// The tmux binary. launchd starts dingger, and the menu bar app that runs
+/// `dingger focus`, with PATH=/usr/bin:/bin:/usr/sbin:/sbin, where Homebrew's
+/// tmux is not found: fall back to the usual install locations.
+fn tmux_binary() -> &'static std::path::Path {
+ static TMUX: std::sync::OnceLock<std::path::PathBuf> = std::sync::OnceLock::new();
+ TMUX.get_or_init(|| {
+ if let Some(path) = std::env::var_os("DINGGER_TMUX") {
+ return path.into();
+ }
+ let on_path: Vec<std::path::PathBuf> = std::env::var_os("PATH")
+ .map(|path| std::env::split_paths(&path).map(|dir| dir.join("tmux")).collect())
+ .unwrap_or_default();
+ let known = ["/opt/homebrew/bin/tmux", "/usr/local/bin/tmux", "/usr/bin/tmux"]
+ .map(std::path::PathBuf::from);
+ on_path
+ .into_iter()
+ .chain(known)
+ .find(|candidate| candidate.is_file())
+ .unwrap_or_else(|| "tmux".into())
+ })
+}
+
fn run_tmux(args: &[&str]) -> Result<String, String> {
- let mut command = Command::new("tmux");
+ let mut command = Command::new(tmux_binary());
// -u forces UTF-8 mode: without it, tmux mangles format output in
// non-UTF-8 locales (launchd/cron default to the C locale, turning the
// tab separators in our -F formats into underscores).
@@ -51,7 +73,7 @@ fn run_tmux(args: &[&str]) -> Result<String, String> {
let output = command
.args(args)
.output()
- .map_err(|err| format!("failed to run tmux: {err}"))?;
+ .map_err(|err| format!("failed to run tmux ({}): {err}", tmux_binary().display()))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("tmux {} failed: {}", args.join(" "), stderr.trim()));
@@ -227,14 +249,12 @@ pub fn client_format(client: &str, format: &str) -> Option<String> {
}
/// Attached clients as (name, pid), most recently active first.
-pub fn clients() -> Vec<(String, u32)> {
- let Ok(out) = run_tmux(&[
+pub fn clients() -> Result<Vec<(String, u32)>, String> {
+ let out = run_tmux(&[
"list-clients",
"-F",
"#{client_activity}\t#{client_name}\t#{client_pid}",
- ]) else {
- return Vec::new();
- };
+ ])?;
let mut clients: Vec<(u64, String, u32)> = out
.lines()
.filter_map(|line| {
@@ -246,7 +266,7 @@ pub fn clients() -> Vec<(String, u32)> {
})
.collect();
clients.sort_by_key(|client| std::cmp::Reverse(client.0));
- clients.into_iter().map(|(_, name, pid)| (name, pid)).collect()
+ Ok(clients.into_iter().map(|(_, name, pid)| (name, pid)).collect())
}
/// Show a pane in a client: switch the client to the pane's window and make