This tmux alert system didn't work as nicely as I imagined
This commit is contained in:
@@ -81,7 +81,6 @@ GROUP_DESCRIPTIONS[sudo]="Sudo configuration helpers"
|
|||||||
GROUP_DESCRIPTIONS[system]="System status, reboot, shutdown, logout, and lock"
|
GROUP_DESCRIPTIONS[system]="System status, reboot, shutdown, logout, and lock"
|
||||||
GROUP_DESCRIPTIONS[tailscale]="Tailscale helpers"
|
GROUP_DESCRIPTIONS[tailscale]="Tailscale helpers"
|
||||||
GROUP_DESCRIPTIONS[theme]="Theme management"
|
GROUP_DESCRIPTIONS[theme]="Theme management"
|
||||||
GROUP_DESCRIPTIONS[tmux]="Tmux session helpers"
|
|
||||||
GROUP_DESCRIPTIONS[toggle]="Toggle Omarchy features"
|
GROUP_DESCRIPTIONS[toggle]="Toggle Omarchy features"
|
||||||
GROUP_DESCRIPTIONS[transcode]="Image and video transcoding"
|
GROUP_DESCRIPTIONS[transcode]="Image and video transcoding"
|
||||||
GROUP_DESCRIPTIONS[tui]="Terminal UI launchers"
|
GROUP_DESCRIPTIONS[tui]="Terminal UI launchers"
|
||||||
|
|||||||
@@ -418,7 +418,6 @@ prioritize_entries() {
|
|||||||
if (match(line, /Toggle workspace gaps/)) prio = 39
|
if (match(line, /Toggle workspace gaps/)) prio = 39
|
||||||
if (match(line, /Toggle nightlight/)) prio = 40
|
if (match(line, /Toggle nightlight/)) prio = 40
|
||||||
if (match(line, /Toggle locking/)) prio = 41
|
if (match(line, /Toggle locking/)) prio = 41
|
||||||
if (match(line, /Jump to waiting Tmux pane/)) prio = 42
|
|
||||||
if (match(line, /group/)) prio = 94
|
if (match(line, /group/)) prio = 94
|
||||||
if (match(line, /Scroll active workspace/)) prio = 95
|
if (match(line, /Scroll active workspace/)) prio = 95
|
||||||
if (match(line, /Cycle to/)) prio = 96
|
if (match(line, /Cycle to/)) prio = 96
|
||||||
|
|||||||
+3
-2
@@ -40,8 +40,9 @@ fi
|
|||||||
[[ -n ${OMARCHY_PATH:-} ]] || fail "OMARCHY_PATH is not set"
|
[[ -n ${OMARCHY_PATH:-} ]] || fail "OMARCHY_PATH is not set"
|
||||||
[[ -f $OMARCHY_PATH/shell/shell.qml ]] || fail "omarchy-shell config not found: $OMARCHY_PATH/shell/shell.qml"
|
[[ -f $OMARCHY_PATH/shell/shell.qml ]] || fail "omarchy-shell config not found: $OMARCHY_PATH/shell/shell.qml"
|
||||||
|
|
||||||
# qs matches instances by display, and tmux run-shell strips WAYLAND_DISPLAY
|
# qs matches instances by display, and a caller from outside the session (an
|
||||||
# from hooks, so recover it from the compositor socket when it is missing.
|
# ssh or TTY omarchy-restart-shell, and the migrations it runs for) has none,
|
||||||
|
# so recover it from the compositor socket.
|
||||||
if [[ -z ${WAYLAND_DISPLAY:-} ]]; then
|
if [[ -z ${WAYLAND_DISPLAY:-} ]]; then
|
||||||
socket=$(ls -t "${XDG_RUNTIME_DIR:-/run/user/$UID}"/wayland-[0-9]* 2>/dev/null | grep -v '\.lock$' | head -n1)
|
socket=$(ls -t "${XDG_RUNTIME_DIR:-/run/user/$UID}"/wayland-[0-9]* 2>/dev/null | grep -v '\.lock$' | head -n1)
|
||||||
[[ -n $socket ]] && export WAYLAND_DISPLAY=${socket##*/}
|
[[ -n $socket ]] && export WAYLAND_DISPLAY=${socket##*/}
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# omarchy:summary=Show or jump to tmux windows waiting for attention
|
|
||||||
# omarchy:args=<show [--json]|focus>
|
|
||||||
# omarchy:examples=omarchy tmux alert show | omarchy tmux alert focus
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Session names cannot contain colons, and window names can, so they always
|
|
||||||
# come last when parsed.
|
|
||||||
WINDOW_FORMAT='#{window_id}:#{window_bell_flag}#{window_activity_flag}#{window_silence_flag}:#{window_activity}:#{@omarchy_unfocused_activity}:#{session_name}:#{window_index}:#{window_name}'
|
|
||||||
CLIENT_FORMAT='#{client_flags}:#{window_id}'
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
echo "Usage: omarchy-tmux-alert <show [--json]|focus>" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Include tmux alert flags plus output newer than the last focus transition for
|
|
||||||
# selected windows whose attached terminals are all unfocused.
|
|
||||||
alerted_windows() {
|
|
||||||
local client_flags window_id flags activity seen_activity session index name
|
|
||||||
local waiting
|
|
||||||
local -A attached_windows=()
|
|
||||||
local -A focused_windows=()
|
|
||||||
|
|
||||||
while IFS=':' read -r client_flags window_id; do
|
|
||||||
[[ -n $window_id ]] || continue
|
|
||||||
attached_windows[$window_id]=1
|
|
||||||
if [[ ,$client_flags, == *",focused,"* ]]; then
|
|
||||||
focused_windows[$window_id]=1
|
|
||||||
fi
|
|
||||||
done < <(tmux list-clients -F "$CLIENT_FORMAT" 2>/dev/null)
|
|
||||||
|
|
||||||
while IFS=':' read -r window_id flags activity seen_activity session index name; do
|
|
||||||
waiting=0
|
|
||||||
[[ $flags == *1* ]] && waiting=1
|
|
||||||
|
|
||||||
if [[ ${attached_windows[$window_id]:-} == "1" &&
|
|
||||||
${focused_windows[$window_id]:-} != "1" &&
|
|
||||||
$activity =~ ^[0-9]+$ &&
|
|
||||||
$seen_activity =~ ^[0-9]+$ ]] && ((activity > seen_activity)); then
|
|
||||||
waiting=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
((waiting)) && printf '%s:%s:%s\n' "$session" "$index" "$name"
|
|
||||||
done < <(tmux list-windows -a -F "$WINDOW_FORMAT" 2>/dev/null)
|
|
||||||
}
|
|
||||||
|
|
||||||
show() {
|
|
||||||
local windows=() window session index name description=""
|
|
||||||
|
|
||||||
readarray -t windows < <(alerted_windows)
|
|
||||||
|
|
||||||
for window in "${windows[@]}"; do
|
|
||||||
IFS=':' read -r session index name <<<"$window"
|
|
||||||
[[ -n $description ]] && description+=", "
|
|
||||||
description+="$name ($session:$index)"
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ ${1:-} == "--json" ]]; then
|
|
||||||
jq -nc --argjson count "${#windows[@]}" --arg tooltip "$description" '{count: $count, tooltip: $tooltip}'
|
|
||||||
elif [[ -n $description ]]; then
|
|
||||||
echo "$description"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
focus_terminal_window() {
|
|
||||||
local pid=$1 address="" window_pid window_address
|
|
||||||
local -A windows=()
|
|
||||||
|
|
||||||
while read -r window_pid window_address; do
|
|
||||||
windows[$window_pid]=$window_address
|
|
||||||
done < <(hyprctl clients -j | jq -r '.[] | "\(.pid) \(.address)"')
|
|
||||||
|
|
||||||
# The tmux client runs somewhere below the terminal that owns the window.
|
|
||||||
while [[ -n $pid ]] && (( pid > 1 )); do
|
|
||||||
if [[ -n ${windows[$pid]:-} ]]; then
|
|
||||||
address=${windows[$pid]}
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
pid=$(awk '/^PPid:/ { print $2 }' "/proc/$pid/status" 2>/dev/null)
|
|
||||||
done
|
|
||||||
|
|
||||||
[[ -n $address ]] || return 1
|
|
||||||
|
|
||||||
hyprctl dispatch "hl.dsp.focus({ window = \"address:$address\" })" >/dev/null 2>&1 ||
|
|
||||||
hyprctl dispatch focuswindow "address:$address" >/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
most_recent_client() {
|
|
||||||
tmux list-clients "$@" -F '#{client_activity}:#{client_tty}:#{client_pid}' 2>/dev/null | sort -rn | head -n1
|
|
||||||
}
|
|
||||||
|
|
||||||
track() {
|
|
||||||
local window=${1:-} activity=${2:-}
|
|
||||||
|
|
||||||
[[ $window =~ ^@[0-9]+$ && $activity =~ ^[0-9]+$ ]] || return 0
|
|
||||||
tmux set-option -wq -t "$window" @omarchy_unfocused_activity "$activity" 2>/dev/null || return 0
|
|
||||||
omarchy-shell -q omarchy.indicators refresh
|
|
||||||
}
|
|
||||||
|
|
||||||
focus() {
|
|
||||||
local target session index client tty pid
|
|
||||||
|
|
||||||
target=$(alerted_windows | head -n1)
|
|
||||||
[[ -n $target ]] || return 0
|
|
||||||
|
|
||||||
IFS=':' read -r session index _ <<<"$target"
|
|
||||||
|
|
||||||
client=$(most_recent_client -t "$session")
|
|
||||||
[[ -n $client ]] || client=$(most_recent_client)
|
|
||||||
|
|
||||||
if [[ -z $client ]]; then
|
|
||||||
tmux select-window -t "$session:$index"
|
|
||||||
exec omarchy-launch-terminal tmux attach -t "$session"
|
|
||||||
fi
|
|
||||||
|
|
||||||
IFS=':' read -r _ tty pid <<<"$client"
|
|
||||||
tmux switch-client -c "$tty" -t "$session:$index"
|
|
||||||
omarchy-shell -q omarchy.indicators refresh
|
|
||||||
focus_terminal_window "$pid"
|
|
||||||
}
|
|
||||||
|
|
||||||
case "${1:-}" in
|
|
||||||
show)
|
|
||||||
shift
|
|
||||||
show "$@"
|
|
||||||
;;
|
|
||||||
focus)
|
|
||||||
focus
|
|
||||||
;;
|
|
||||||
track)
|
|
||||||
shift
|
|
||||||
track "$@"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
usage
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
@@ -81,15 +81,6 @@ set -ag terminal-features "xterm-kitty:extkeys"
|
|||||||
set -as terminal-features ",*:clipboard"
|
set -as terminal-features ",*:clipboard"
|
||||||
set -sg escape-time 10
|
set -sg escape-time 10
|
||||||
|
|
||||||
# Alerts
|
|
||||||
set-hook -g alert-bell 'run-shell -b "omarchy-shell -q omarchy.indicators refresh"'
|
|
||||||
set-hook -g alert-activity 'run-shell -b "omarchy-shell -q omarchy.indicators refresh"'
|
|
||||||
set-hook -g alert-silence 'run-shell -b "omarchy-shell -q omarchy.indicators refresh"'
|
|
||||||
set-hook -g after-select-window 'run-shell -b "omarchy-tmux-alert track #{window_id} #{window_activity}"'
|
|
||||||
set-hook -g client-session-changed 'run-shell -b "omarchy-tmux-alert track #{window_id} #{window_activity}"'
|
|
||||||
set-hook -g client-focus-out[100] 'run-shell -b "omarchy-tmux-alert track #{window_id} #{window_activity}"'
|
|
||||||
set-hook -g client-focus-in[100] 'run-shell -b "omarchy-tmux-alert track #{window_id} #{window_activity}"'
|
|
||||||
|
|
||||||
# Status bar
|
# Status bar
|
||||||
set -g status-position top
|
set -g status-position top
|
||||||
set -g status-interval 5
|
set -g status-interval 5
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ o.bind("SUPER + ESCAPE", "System menu", "omarchy-menu toggle system")
|
|||||||
o.bind("XF86PowerOff", "Power menu", "omarchy-menu toggle system", { locked = true })
|
o.bind("XF86PowerOff", "Power menu", "omarchy-menu toggle system", { locked = true })
|
||||||
o.bind("SUPER + K", "Show key bindings", "omarchy-menu-keybindings")
|
o.bind("SUPER + K", "Show key bindings", "omarchy-menu-keybindings")
|
||||||
o.bind("SUPER + ALT + K", "Show Tmux key bindings", "omarchy-menu-tmux-keybindings")
|
o.bind("SUPER + ALT + K", "Show Tmux key bindings", "omarchy-menu-tmux-keybindings")
|
||||||
o.bind("SUPER + CTRL + J", "Jump to waiting Tmux pane", "omarchy-tmux-alert focus")
|
|
||||||
o.bind("XF86Calculator", "Calculator", "gnome-calculator")
|
o.bind("XF86Calculator", "Calculator", "gnome-calculator")
|
||||||
|
|
||||||
o.bind_toggle("SUPER + SHIFT + SPACE", "Toggle top bar", "bar")
|
o.bind_toggle("SUPER + SHIFT + SPACE", "Toggle top bar", "bar")
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
echo "Add tmux hooks that surface waiting windows in the bar"
|
|
||||||
|
|
||||||
tmux_config="$HOME/.config/tmux/tmux.conf"
|
|
||||||
|
|
||||||
if [[ -f $tmux_config ]] && ! grep -q 'alert-bell' "$tmux_config"; then
|
|
||||||
cat >>"$tmux_config" <<'EOF'
|
|
||||||
|
|
||||||
# Alerts
|
|
||||||
set-hook -g alert-bell 'run-shell -b "omarchy-shell -q omarchy.indicators refresh"'
|
|
||||||
set-hook -g alert-activity 'run-shell -b "omarchy-shell -q omarchy.indicators refresh"'
|
|
||||||
set-hook -g alert-silence 'run-shell -b "omarchy-shell -q omarchy.indicators refresh"'
|
|
||||||
set-hook -g after-select-window 'run-shell -b "omarchy-shell -q omarchy.indicators refresh"'
|
|
||||||
set-hook -g client-session-changed 'run-shell -b "omarchy-shell -q omarchy.indicators refresh"'
|
|
||||||
EOF
|
|
||||||
|
|
||||||
omarchy-restart-tmux
|
|
||||||
fi
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
echo "Track tmux output while its terminal is unfocused"
|
|
||||||
|
|
||||||
tmux_config="$HOME/.config/tmux/tmux.conf"
|
|
||||||
|
|
||||||
if [[ -f $tmux_config ]] && ! grep -q 'client-focus-out\[100\].*omarchy-tmux-alert track' "$tmux_config"; then
|
|
||||||
sed -i \
|
|
||||||
-e 's|^set-hook -g after-select-window .*|set-hook -g after-select-window '"'"'run-shell -b "omarchy-tmux-alert track #{window_id} #{window_activity}"'"'"'|' \
|
|
||||||
-e 's|^set-hook -g client-session-changed .*|set-hook -g client-session-changed '"'"'run-shell -b "omarchy-tmux-alert track #{window_id} #{window_activity}"'"'"'|' \
|
|
||||||
"$tmux_config"
|
|
||||||
|
|
||||||
cat >>"$tmux_config" <<'EOF'
|
|
||||||
set-hook -g client-focus-out[100] 'run-shell -b "omarchy-tmux-alert track #{window_id} #{window_activity}"'
|
|
||||||
set-hook -g client-focus-in[100] 'run-shell -b "omarchy-tmux-alert track #{window_id} #{window_activity}"'
|
|
||||||
EOF
|
|
||||||
|
|
||||||
omarchy-restart-tmux
|
|
||||||
fi
|
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
echo "Remove the tmux alert hooks and its bar indicator"
|
||||||
|
|
||||||
|
tmux_config="$HOME/.config/tmux/tmux.conf"
|
||||||
|
|
||||||
|
# Drop only the hooks Omarchy installed, wherever the migrations that wrote
|
||||||
|
# them left them, and only the blank run around what goes: every other line
|
||||||
|
# comes through untouched.
|
||||||
|
if [[ -f $tmux_config ]]; then
|
||||||
|
tmp=$(mktemp)
|
||||||
|
|
||||||
|
if awk '
|
||||||
|
function flush(count, i) { for (i = 0; i < count; i++) print "" }
|
||||||
|
|
||||||
|
function keep(line) {
|
||||||
|
if (dropped) { if (printed && blanks) print "" }
|
||||||
|
else flush(blanks)
|
||||||
|
|
||||||
|
blanks = 0
|
||||||
|
dropped = 0
|
||||||
|
printed = 1
|
||||||
|
print line
|
||||||
|
}
|
||||||
|
|
||||||
|
# The heading only belongs to the block when a removed hook follows it.
|
||||||
|
/^[[:space:]]*# Alerts$/ && header == "" { header = $0; next }
|
||||||
|
|
||||||
|
/^[[:space:]]*set-hook -g alert-(bell|activity|silence) .*omarchy\.indicators refresh/ ||
|
||||||
|
/^[[:space:]]*set-hook -g (after-select-window|client-session-changed|client-focus-(in|out))(\[[0-9]+\])? .*(omarchy-tmux-alert track|omarchy\.indicators refresh)/ {
|
||||||
|
header = ""
|
||||||
|
dropped = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
/^[[:space:]]*$/ {
|
||||||
|
if (header != "") { keep(header); header = "" }
|
||||||
|
blanks++
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
if (header != "") { keep(header); header = "" }
|
||||||
|
keep($0)
|
||||||
|
}
|
||||||
|
|
||||||
|
END {
|
||||||
|
if (header != "") keep(header)
|
||||||
|
if (!dropped) flush(blanks)
|
||||||
|
}
|
||||||
|
' "$tmux_config" >"$tmp"; then
|
||||||
|
if ! cmp -s "$tmp" "$tmux_config"; then
|
||||||
|
# Write through the path instead of replacing it, so a tmux.conf
|
||||||
|
# symlinked out of a dotfiles repo keeps pointing where it pointed.
|
||||||
|
cat "$tmp" >"$tmux_config"
|
||||||
|
omarchy-restart-tmux
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Could not rewrite $tmux_config; remove the tmux alert hooks by hand."
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$tmp"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A running server keeps its hooks as options, so sourcing a config that no
|
||||||
|
# longer sets them leaves every one of them firing at the deleted command.
|
||||||
|
# Unset by the exact name and index tmux reports, which leaves hooks the user
|
||||||
|
# added at other indexes alone.
|
||||||
|
if tmux has-session 2>/dev/null; then
|
||||||
|
while read -r hook; do
|
||||||
|
[[ -n $hook ]] || continue
|
||||||
|
tmux set-hook -gu "$hook" 2>/dev/null || true
|
||||||
|
done < <(tmux show-hooks -g 2>/dev/null |
|
||||||
|
awk '$0 ~ /omarchy-tmux-alert|omarchy\.indicators refresh/ { print $1 }')
|
||||||
|
|
||||||
|
# The removed track subcommand stamped this on every window it saw.
|
||||||
|
while read -r window; do
|
||||||
|
[[ -n $window ]] || continue
|
||||||
|
tmux set-option -wqu -t "$window" @omarchy_unfocused_activity 2>/dev/null || true
|
||||||
|
done < <(tmux list-windows -a -F '#{window_id}' 2>/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only a hand-picked indicator list names TmuxAlert; the default list lives in
|
||||||
|
# the widget. An emptied list would read as "show them all", so a widget that
|
||||||
|
# has nothing left to show goes with it.
|
||||||
|
config_file="$HOME/.config/omarchy/shell.json"
|
||||||
|
|
||||||
|
if [[ -s $config_file ]] && grep -q 'TmuxAlert' "$config_file"; then
|
||||||
|
tmp=$(mktemp)
|
||||||
|
|
||||||
|
if jq '
|
||||||
|
def entry_id:
|
||||||
|
if type == "string" then . elif type == "object" then (.id // "") else "" end;
|
||||||
|
|
||||||
|
def without_tmux_alert: map(select(entry_id != "TmuxAlert"));
|
||||||
|
|
||||||
|
def stripped($key):
|
||||||
|
if (.[$key] | type) == "array" then .[$key] |= without_tmux_alert else . end;
|
||||||
|
|
||||||
|
# The list the widget actually reads: "indicators" is the older spelling of
|
||||||
|
# "items", and either one empty means "show the default set".
|
||||||
|
def picked:
|
||||||
|
if (.items | type) == "array" and (.items | length) > 0 then .items
|
||||||
|
elif (.indicators | type) == "array" and (.indicators | length) > 0 then .indicators
|
||||||
|
else null end;
|
||||||
|
|
||||||
|
def cleaned:
|
||||||
|
if type == "object" and .id == "omarchy.indicators" then stripped("items") | stripped("indicators") else . end;
|
||||||
|
|
||||||
|
def emptied:
|
||||||
|
type == "object" and .id == "omarchy.indicators" and
|
||||||
|
picked != null and (cleaned | picked) == null;
|
||||||
|
|
||||||
|
walk(if type == "array" then map(select(emptied | not)) | map(cleaned) else . end)
|
||||||
|
' "$config_file" >"$tmp"; then
|
||||||
|
cat "$tmp" >"$config_file"
|
||||||
|
else
|
||||||
|
echo "Could not rewrite $config_file; remove the TmuxAlert indicator by hand."
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$tmp"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Nothing to restart from a TTY or over ssh, and that is no reason to stop the
|
||||||
|
# rest of the queue: hand it to the post-update restart instead.
|
||||||
|
omarchy-restart-shell >/dev/null 2>&1 || omarchy-state set restart-shell-required
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
import qs.Ui
|
|
||||||
|
|
||||||
BarIndicator {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
readonly property var tmuxService: bar?.shell?.firstPartyServiceFor("omarchy.tmux")
|
|
||||||
|
|
||||||
active: tmuxService ? tmuxService.waiting : false
|
|
||||||
activeText: ""
|
|
||||||
inactiveText: ""
|
|
||||||
activeTooltipText: tmuxService ? tmuxService.tooltip : ""
|
|
||||||
inactiveTooltipText: "No Terminal Waiting"
|
|
||||||
|
|
||||||
// The service owns the probe and its polling; the tmux hooks still reach it
|
|
||||||
// through the indicator host's refresh broadcast, which coalesces into a
|
|
||||||
// single run no matter how many bars relay it.
|
|
||||||
function refresh() {
|
|
||||||
if (root.tmuxService) root.tmuxService.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
SequentialAnimation {
|
|
||||||
running: root.active
|
|
||||||
loops: Animation.Infinite
|
|
||||||
|
|
||||||
PauseAnimation { duration: 2740 }
|
|
||||||
NumberAnimation { target: root; property: "textRotation"; to: -10; duration: 50; easing.type: Easing.OutQuad }
|
|
||||||
NumberAnimation { target: root; property: "textRotation"; to: 10; duration: 70; easing.type: Easing.InOutQuad }
|
|
||||||
NumberAnimation { target: root; property: "textRotation"; to: -7; duration: 55; easing.type: Easing.InOutQuad }
|
|
||||||
NumberAnimation { target: root; property: "textRotation"; to: 7; duration: 45; easing.type: Easing.InOutQuad }
|
|
||||||
NumberAnimation { target: root; property: "textRotation"; to: 0; duration: 40; easing.type: Easing.OutQuad }
|
|
||||||
|
|
||||||
onStopped: root.textRotation = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: root.indicatorHost
|
|
||||||
ignoreUnknownSignals: true
|
|
||||||
function onRefreshRequested() { root.refresh() }
|
|
||||||
}
|
|
||||||
|
|
||||||
onPressed: function() {
|
|
||||||
Quickshell.execDetached(["omarchy-tmux-alert", "focus"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -26,11 +26,6 @@
|
|||||||
"placeholderText": "Search indicators...",
|
"placeholderText": "Search indicators...",
|
||||||
"emptyText": "No indicators",
|
"emptyText": "No indicators",
|
||||||
"options": [
|
"options": [
|
||||||
{
|
|
||||||
"value": "TmuxAlert",
|
|
||||||
"label": "Tmux alert",
|
|
||||||
"description": "Tmux windows waiting for attention"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"value": "Dictation",
|
"value": "Dictation",
|
||||||
"label": "Dictation",
|
"label": "Dictation",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ BarWidget {
|
|||||||
id: root
|
id: root
|
||||||
moduleName: "omarchy.indicators"
|
moduleName: "omarchy.indicators"
|
||||||
|
|
||||||
readonly property var defaultIndicatorEntries: [ "TmuxAlert", "Dictation", "ScreenRecording", "Reminder", "NightLight", "Dnd", "StayAwake" ]
|
readonly property var defaultIndicatorEntries: [ "Dictation", "ScreenRecording", "Reminder", "NightLight", "Dnd", "StayAwake" ]
|
||||||
readonly property var indicatorEntries: indicatorEntriesFromSettings(settings)
|
readonly property var indicatorEntries: indicatorEntriesFromSettings(settings)
|
||||||
property var activeIndicatorIds: []
|
property var activeIndicatorIds: []
|
||||||
property var indicatorActiveStates: ({})
|
property var indicatorActiveStates: ({})
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import Quickshell.Io
|
|
||||||
import "TmuxModel.js" as TmuxModel
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
// Injected by omarchy-shell (the first-party service loader).
|
|
||||||
property var shell: null
|
|
||||||
|
|
||||||
property int waitingCount: 0
|
|
||||||
property string tooltip: ""
|
|
||||||
property bool refreshPending: false
|
|
||||||
|
|
||||||
readonly property bool waiting: waitingCount > 0
|
|
||||||
|
|
||||||
// A bar surface exists per monitor and each one instantiates the indicator
|
|
||||||
// twice (active and inactive block), so the probe lives here instead: one
|
|
||||||
// process for the whole shell, and every indicator reads the same answer.
|
|
||||||
function refresh() {
|
|
||||||
if (statusProc.running) refreshPending = true
|
|
||||||
else statusProc.running = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// tmux hooks cover the flag transitions, but output in a selected window
|
|
||||||
// whose terminal sits on another workspace never fires one, and neither does
|
|
||||||
// a window or session killed while it was still flagged.
|
|
||||||
Timer {
|
|
||||||
interval: 3000
|
|
||||||
running: true
|
|
||||||
repeat: true
|
|
||||||
onTriggered: root.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: statusProc
|
|
||||||
command: ["omarchy-tmux-alert", "show", "--json"]
|
|
||||||
stdout: StdioCollector {
|
|
||||||
waitForEnd: true
|
|
||||||
onStreamFinished: {
|
|
||||||
var waiting = TmuxModel.waitingFromOutput(text)
|
|
||||||
root.waitingCount = waiting.count
|
|
||||||
root.tooltip = waiting.tooltip
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onExited: function(exitCode) {
|
|
||||||
if (exitCode !== 0) {
|
|
||||||
root.waitingCount = 0
|
|
||||||
root.tooltip = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (root.refreshPending) {
|
|
||||||
root.refreshPending = false
|
|
||||||
root.refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: refresh()
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// Shape of `omarchy-tmux-alert show --json`. The command emits one JSON object
|
|
||||||
// on the last line; anything tmux or a shell profile printed before it is
|
|
||||||
// ignored so a noisy environment cannot blank the indicator.
|
|
||||||
function waitingFromOutput(output) {
|
|
||||||
var text = String(output === undefined || output === null ? "" : output).trim()
|
|
||||||
if (!text) return { count: 0, tooltip: "" }
|
|
||||||
|
|
||||||
var lines = text.split("\n")
|
|
||||||
var data
|
|
||||||
try {
|
|
||||||
data = JSON.parse(lines[lines.length - 1])
|
|
||||||
} catch (e) {
|
|
||||||
return { count: 0, tooltip: "" }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data || typeof data !== "object") return { count: 0, tooltip: "" }
|
|
||||||
|
|
||||||
var count = Number(data.count)
|
|
||||||
return {
|
|
||||||
count: isFinite(count) && count > 0 ? Math.floor(count) : 0,
|
|
||||||
tooltip: data.tooltip === undefined || data.tooltip === null ? "" : String(data.tooltip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof module !== "undefined") {
|
|
||||||
module.exports = {
|
|
||||||
waitingFromOutput: waitingFromOutput
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"schemaVersion": 1,
|
|
||||||
"id": "omarchy.tmux",
|
|
||||||
"name": "Tmux",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"author": "Omarchy",
|
|
||||||
"description": "Owns the set of tmux windows waiting for attention for the bar indicator.",
|
|
||||||
"kinds": [
|
|
||||||
"service"
|
|
||||||
],
|
|
||||||
"entryPoints": {
|
|
||||||
"service": "Service.qml"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,8 @@ export PATH="$test_dir/bin:$PATH"
|
|||||||
export OMARCHY_PATH="$ROOT"
|
export OMARCHY_PATH="$ROOT"
|
||||||
export XDG_RUNTIME_DIR="$test_dir/run"
|
export XDG_RUNTIME_DIR="$test_dir/run"
|
||||||
|
|
||||||
# tmux hooks run without WAYLAND_DISPLAY, and qs matches instances by display.
|
# Callers from a stripped environment have no WAYLAND_DISPLAY, and qs matches
|
||||||
|
# instances by display.
|
||||||
output=$(env -u WAYLAND_DISPLAY "$ROOT/bin/omarchy-shell" omarchy.indicators refresh)
|
output=$(env -u WAYLAND_DISPLAY "$ROOT/bin/omarchy-shell" omarchy.indicators refresh)
|
||||||
[[ $output == "display=[wayland-1]" ]] || fail "shell ipc recovers a missing display" "$output"
|
[[ $output == "display=[wayland-1]" ]] || fail "shell ipc recovers a missing display" "$output"
|
||||||
pass "shell ipc recovers a missing display"
|
pass "shell ipc recovers a missing display"
|
||||||
|
|||||||
@@ -0,0 +1,246 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source "$(dirname "$0")/base-test.sh"
|
||||||
|
|
||||||
|
migration="$ROOT/migrations/1785189600.sh"
|
||||||
|
test_dir=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$test_dir"' EXIT
|
||||||
|
|
||||||
|
mkdir -p "$test_dir/bin"
|
||||||
|
|
||||||
|
# Everything the migration reaches for is stubbed: a real tmux here would talk
|
||||||
|
# to the developer's own server.
|
||||||
|
cat >"$test_dir/bin/tmux" <<'STUB'
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
printf '%s\n' "$*" >>"$TMUX_CALLS"
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
has-session) exit "${TMUX_SERVER_MISSING:-0}" ;;
|
||||||
|
show-hooks)
|
||||||
|
cat <<'HOOKS'
|
||||||
|
after-select-window[0] run-shell -b "omarchy-tmux-alert track #{window_id} #{window_activity}"
|
||||||
|
alert-activity
|
||||||
|
alert-bell[0] run-shell -b "omarchy-shell -q omarchy.indicators refresh"
|
||||||
|
client-focus-in[100] run-shell -b "omarchy-tmux-alert track #{window_id} #{window_activity}"
|
||||||
|
client-focus-out[42] display-message "user hook"
|
||||||
|
session-created
|
||||||
|
HOOKS
|
||||||
|
;;
|
||||||
|
list-windows) printf '@0\n@3\n' ;;
|
||||||
|
esac
|
||||||
|
STUB
|
||||||
|
|
||||||
|
cat >"$test_dir/bin/omarchy-restart-tmux" <<'STUB'
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo restart >>"$TMUX_RESTARTS"
|
||||||
|
STUB
|
||||||
|
|
||||||
|
cat >"$test_dir/bin/omarchy-restart-shell" <<'STUB'
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo restart >>"$SHELL_RESTARTS"
|
||||||
|
exit "${SHELL_RESTART_STATUS:-0}"
|
||||||
|
STUB
|
||||||
|
|
||||||
|
cat >"$test_dir/bin/omarchy-state" <<'STUB'
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
printf '%s\n' "$*" >>"$STATE_CALLS"
|
||||||
|
STUB
|
||||||
|
|
||||||
|
chmod +x "$test_dir/bin/"*
|
||||||
|
|
||||||
|
export TMUX_CALLS="$test_dir/tmux-calls"
|
||||||
|
export TMUX_RESTARTS="$test_dir/tmux-restarts"
|
||||||
|
export SHELL_RESTARTS="$test_dir/shell-restarts"
|
||||||
|
export STATE_CALLS="$test_dir/state-calls"
|
||||||
|
|
||||||
|
home="$test_dir/home"
|
||||||
|
tmux_config="$home/.config/tmux/tmux.conf"
|
||||||
|
shell_config="$home/.config/omarchy/shell.json"
|
||||||
|
|
||||||
|
run_migration() {
|
||||||
|
: >"$TMUX_CALLS"
|
||||||
|
: >"$TMUX_RESTARTS"
|
||||||
|
: >"$SHELL_RESTARTS"
|
||||||
|
: >"$STATE_CALLS"
|
||||||
|
|
||||||
|
HOME="$home" PATH="$test_dir/bin:$PATH" bash -euo pipefail "$migration" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_home() {
|
||||||
|
rm -rf "$home"
|
||||||
|
mkdir -p "$home/.config/tmux" "$home/.config/omarchy"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- tmux config
|
||||||
|
|
||||||
|
# The shipped config with the alert block put back is exactly what an installed
|
||||||
|
# machine has, so cleaning it has to land on the shipped config byte for byte.
|
||||||
|
reset_home
|
||||||
|
awk '
|
||||||
|
/^# Status bar$/ {
|
||||||
|
print "# Alerts"
|
||||||
|
print "set-hook -g alert-bell '\''run-shell -b \"omarchy-shell -q omarchy.indicators refresh\"'\''"
|
||||||
|
print "set-hook -g alert-activity '\''run-shell -b \"omarchy-shell -q omarchy.indicators refresh\"'\''"
|
||||||
|
print "set-hook -g alert-silence '\''run-shell -b \"omarchy-shell -q omarchy.indicators refresh\"'\''"
|
||||||
|
print "set-hook -g after-select-window '\''run-shell -b \"omarchy-tmux-alert track #{window_id} #{window_activity}\"'\''"
|
||||||
|
print "set-hook -g client-session-changed '\''run-shell -b \"omarchy-tmux-alert track #{window_id} #{window_activity}\"'\''"
|
||||||
|
print "set-hook -g client-focus-out[100] '\''run-shell -b \"omarchy-tmux-alert track #{window_id} #{window_activity}\"'\''"
|
||||||
|
print "set-hook -g client-focus-in[100] '\''run-shell -b \"omarchy-tmux-alert track #{window_id} #{window_activity}\"'\''"
|
||||||
|
print ""
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
' "$ROOT/config/tmux/tmux.conf" >"$tmux_config"
|
||||||
|
|
||||||
|
run_migration
|
||||||
|
|
||||||
|
diff -u "$ROOT/config/tmux/tmux.conf" "$tmux_config" >/dev/null ||
|
||||||
|
fail "alert removal restores the shipped tmux config" "$(diff -u "$ROOT/config/tmux/tmux.conf" "$tmux_config")"
|
||||||
|
pass "alert removal restores the shipped tmux config"
|
||||||
|
|
||||||
|
(($(wc -l <"$TMUX_RESTARTS") == 1)) || fail "alert removal reloads tmux once"
|
||||||
|
pass "alert removal reloads tmux once"
|
||||||
|
|
||||||
|
before=$(sha256sum "$tmux_config")
|
||||||
|
run_migration
|
||||||
|
[[ $before == $(sha256sum "$tmux_config") ]] || fail "alert removal is idempotent"
|
||||||
|
[[ ! -s $TMUX_RESTARTS ]] || fail "idempotent alert removal does not reload tmux"
|
||||||
|
pass "alert removal is idempotent"
|
||||||
|
|
||||||
|
# The hooks the removed migrations appended sit at the end of the file, and a
|
||||||
|
# user's own hooks and blank lines have to survive next to them.
|
||||||
|
reset_home
|
||||||
|
cat >"$tmux_config" <<'EOF'
|
||||||
|
set -g mouse on
|
||||||
|
|
||||||
|
|
||||||
|
# Custom
|
||||||
|
set-hook -g alert-bell 'display-message "mine"'
|
||||||
|
set-hook -g client-focus-out[42] 'display-message "custom focus hook"'
|
||||||
|
|
||||||
|
# Alerts
|
||||||
|
set-hook -g alert-bell 'run-shell -b "omarchy-shell -q omarchy.indicators refresh"'
|
||||||
|
set-hook -g alert-activity 'run-shell -b "omarchy-shell -q omarchy.indicators refresh"'
|
||||||
|
set-hook -g after-select-window 'run-shell -b "omarchy-shell -q omarchy.indicators refresh"'
|
||||||
|
set-hook -g client-session-changed 'run-shell -b "omarchy-tmux-alert track #{window_id} #{window_activity}"'
|
||||||
|
set-hook -g client-focus-out[100] 'run-shell -b "omarchy-tmux-alert track #{window_id} #{window_activity}"'
|
||||||
|
EOF
|
||||||
|
|
||||||
|
run_migration
|
||||||
|
|
||||||
|
expected=$(printf '%s\n' 'set -g mouse on' '' '' '# Custom' "set-hook -g alert-bell 'display-message \"mine\"'" "set-hook -g client-focus-out[42] 'display-message \"custom focus hook\"'")
|
||||||
|
[[ $(cat "$tmux_config") == "$expected" ]] ||
|
||||||
|
fail "alert removal drops an appended block and keeps the user's lines" "$(cat -A "$tmux_config")"
|
||||||
|
pass "alert removal drops an appended block and keeps the user's lines"
|
||||||
|
|
||||||
|
# An older config kept the refresh spelling of these two hooks; an indented one
|
||||||
|
# is still the hook Omarchy wrote.
|
||||||
|
grep -q 'after-select-window\|client-session-changed' "$tmux_config" &&
|
||||||
|
fail "alert removal drops the older and indented hook spellings"
|
||||||
|
pass "alert removal drops the older and indented hook spellings"
|
||||||
|
|
||||||
|
# A config that never had the feature is not a config to rewrite.
|
||||||
|
reset_home
|
||||||
|
printf '%s\n' 'set -g mouse on' '' '' 'set -g status-position top' '' >"$tmux_config"
|
||||||
|
before=$(sha256sum "$tmux_config")
|
||||||
|
run_migration
|
||||||
|
[[ $before == $(sha256sum "$tmux_config") ]] || fail "alert removal leaves an unrelated config alone" "$(cat -A "$tmux_config")"
|
||||||
|
[[ ! -s $TMUX_RESTARTS ]] || fail "alert removal does not reload tmux for an unrelated config"
|
||||||
|
pass "alert removal leaves an unrelated config alone"
|
||||||
|
|
||||||
|
# Dotfile setups symlink the config; the link has to survive the rewrite.
|
||||||
|
reset_home
|
||||||
|
mkdir -p "$home/dotfiles"
|
||||||
|
cat >"$home/dotfiles/tmux.conf" <<'EOF'
|
||||||
|
set -g mouse on
|
||||||
|
|
||||||
|
# Alerts
|
||||||
|
set-hook -g alert-bell 'run-shell -b "omarchy-shell -q omarchy.indicators refresh"'
|
||||||
|
EOF
|
||||||
|
ln -sf "$home/dotfiles/tmux.conf" "$tmux_config"
|
||||||
|
|
||||||
|
run_migration
|
||||||
|
|
||||||
|
[[ -L $tmux_config ]] || fail "alert removal writes through a symlinked config"
|
||||||
|
[[ $(readlink "$tmux_config") == "$home/dotfiles/tmux.conf" ]] || fail "alert removal keeps the symlink target"
|
||||||
|
grep -q 'alert-bell' "$home/dotfiles/tmux.conf" && fail "alert removal cleans the symlink target"
|
||||||
|
pass "alert removal writes through a symlinked config"
|
||||||
|
|
||||||
|
# --------------------------------------------------------------- live server
|
||||||
|
|
||||||
|
reset_home
|
||||||
|
run_migration
|
||||||
|
|
||||||
|
grep -Fxq 'set-hook -gu after-select-window[0]' "$TMUX_CALLS" || fail "alert removal unsets live hooks by index" "$(cat "$TMUX_CALLS")"
|
||||||
|
grep -Fxq 'set-hook -gu alert-bell[0]' "$TMUX_CALLS" || fail "alert removal unsets live alert hooks"
|
||||||
|
grep -Fxq 'set-hook -gu client-focus-in[100]' "$TMUX_CALLS" || fail "alert removal unsets live focus hooks"
|
||||||
|
grep -q 'set-hook -gu client-focus-out\[42\]' "$TMUX_CALLS" && fail "alert removal keeps a user's hook at another index"
|
||||||
|
pass "alert removal unsets the live hooks it installed"
|
||||||
|
|
||||||
|
grep -Fxq 'set-option -wqu -t @0 @omarchy_unfocused_activity' "$TMUX_CALLS" || fail "alert removal clears tracked window options"
|
||||||
|
grep -Fxq 'set-option -wqu -t @3 @omarchy_unfocused_activity' "$TMUX_CALLS" || fail "alert removal clears every tracked window"
|
||||||
|
pass "alert removal clears the window options it stamped"
|
||||||
|
|
||||||
|
TMUX_SERVER_MISSING=1 run_migration
|
||||||
|
grep -q 'set-hook' "$TMUX_CALLS" && fail "alert removal skips a server that is not running" "$(cat "$TMUX_CALLS")"
|
||||||
|
pass "alert removal skips a server that is not running"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- shell.json
|
||||||
|
|
||||||
|
reset_home
|
||||||
|
cat >"$shell_config" <<'EOF'
|
||||||
|
{
|
||||||
|
"bar": {
|
||||||
|
"layout": {
|
||||||
|
"left": [
|
||||||
|
{ "id": "omarchy.indicators", "items": ["TmuxAlert", "Dnd", { "id": "TmuxAlert" }, { "id": "NightLight" }] }
|
||||||
|
],
|
||||||
|
"center": [
|
||||||
|
{ "id": "omarchy.indicators", "items": ["TmuxAlert"] },
|
||||||
|
{ "id": "omarchy.clock" }
|
||||||
|
],
|
||||||
|
"right": [
|
||||||
|
{ "id": "omarchy.indicators", "indicators": ["TmuxAlert", "Dnd"] },
|
||||||
|
{ "id": "omarchy.indicators", "items": [] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
run_migration
|
||||||
|
|
||||||
|
grep -q 'TmuxAlert' "$shell_config" && fail "alert removal strips TmuxAlert from shell.json" "$(cat "$shell_config")"
|
||||||
|
pass "alert removal strips TmuxAlert from shell.json"
|
||||||
|
|
||||||
|
[[ $(jq -c '.bar.layout.left[0].items' "$shell_config") == '["Dnd",{"id":"NightLight"}]' ]] ||
|
||||||
|
fail "alert removal keeps the other picked indicators" "$(jq -c '.bar.layout.left[0].items' "$shell_config")"
|
||||||
|
pass "alert removal keeps the other picked indicators"
|
||||||
|
|
||||||
|
[[ $(jq -c '[.bar.layout.center[].id]' "$shell_config") == '["omarchy.clock"]' ]] ||
|
||||||
|
fail "alert removal drops a widget left with nothing to show" "$(jq -c '.bar.layout.center' "$shell_config")"
|
||||||
|
pass "alert removal drops a widget left with nothing to show"
|
||||||
|
|
||||||
|
[[ $(jq -c '.bar.layout.right[0].indicators' "$shell_config") == '["Dnd"]' ]] ||
|
||||||
|
fail "alert removal handles the older indicators key" "$(jq -c '.bar.layout.right[0]' "$shell_config")"
|
||||||
|
pass "alert removal handles the older indicators key"
|
||||||
|
|
||||||
|
[[ $(jq -c '.bar.layout.right[1]' "$shell_config") == '{"id":"omarchy.indicators","items":[]}' ]] ||
|
||||||
|
fail "alert removal leaves an already-empty list alone" "$(jq -c '.bar.layout.right[1]' "$shell_config")"
|
||||||
|
pass "alert removal leaves an already-empty list alone"
|
||||||
|
|
||||||
|
(($(wc -l <"$SHELL_RESTARTS") == 1)) || fail "alert removal restarts the shell"
|
||||||
|
[[ ! -s $STATE_CALLS ]] || fail "a restarted shell needs no deferred restart" "$(cat "$STATE_CALLS")"
|
||||||
|
pass "alert removal restarts the shell"
|
||||||
|
|
||||||
|
# Migrations run from a TTY or over ssh have no shell to restart, and stopping
|
||||||
|
# there would strand every migration queued behind this one.
|
||||||
|
reset_home
|
||||||
|
SHELL_RESTART_STATUS=1 run_migration
|
||||||
|
grep -Fxq 'set restart-shell-required' "$STATE_CALLS" || fail "an unavailable shell defers its restart" "$(cat "$STATE_CALLS")"
|
||||||
|
pass "an unavailable shell defers its restart"
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
source "$(dirname "$0")/base-test.sh"
|
|
||||||
|
|
||||||
require_command jq
|
|
||||||
|
|
||||||
run_node_test <<'JS'
|
|
||||||
const tmux = requireFromRoot('shell/plugins/services/tmux/TmuxModel.js')
|
|
||||||
|
|
||||||
assertEqual(tmux.waitingFromOutput('{"count":2,"tooltip":"claude (Work:2)"}').count, 2, 'tmux model reads the waiting count')
|
|
||||||
assertEqual(tmux.waitingFromOutput('{"count":2,"tooltip":"claude (Work:2)"}').tooltip, 'claude (Work:2)', 'tmux model reads the tooltip')
|
|
||||||
assertEqual(tmux.waitingFromOutput('shell noise\n{"count":1,"tooltip":"editor (Work:1)"}').count, 1, 'tmux model ignores output before the json line')
|
|
||||||
assertEqual(tmux.waitingFromOutput('').count, 0, 'tmux model treats empty output as nothing waiting')
|
|
||||||
assertEqual(tmux.waitingFromOutput('not json').count, 0, 'tmux model treats unparseable output as nothing waiting')
|
|
||||||
assertEqual(tmux.waitingFromOutput('not json').tooltip, '', 'tmux model blanks the tooltip on unparseable output')
|
|
||||||
assertEqual(tmux.waitingFromOutput('{"count":-3}').count, 0, 'tmux model clamps a negative count')
|
|
||||||
assertEqual(tmux.waitingFromOutput('{"tooltip":"editor (Work:1)"}').count, 0, 'tmux model defaults a missing count')
|
|
||||||
JS
|
|
||||||
|
|
||||||
test_dir=$(mktemp -d)
|
|
||||||
trap 'rm -rf "$test_dir"' EXIT
|
|
||||||
|
|
||||||
cat >"$test_dir/tmux" <<'STUB'
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
if [[ $1 == "list-windows" ]]; then
|
|
||||||
cat "$TMUX_STUB_WINDOWS"
|
|
||||||
elif [[ $1 == "list-clients" ]]; then
|
|
||||||
cat "$TMUX_STUB_CLIENTS"
|
|
||||||
elif [[ $1 == "set-option" ]]; then
|
|
||||||
printf '%s\n' "$*" >>"$TMUX_STUB_CALLS"
|
|
||||||
fi
|
|
||||||
STUB
|
|
||||||
chmod +x "$test_dir/tmux"
|
|
||||||
|
|
||||||
cat >"$test_dir/omarchy-shell" <<'STUB'
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
printf '%s\n' "$*" >>"$TMUX_STUB_CALLS"
|
|
||||||
STUB
|
|
||||||
chmod +x "$test_dir/omarchy-shell"
|
|
||||||
|
|
||||||
export TMUX_STUB_WINDOWS="$test_dir/windows"
|
|
||||||
export TMUX_STUB_CLIENTS="$test_dir/clients"
|
|
||||||
export TMUX_STUB_CALLS="$test_dir/calls"
|
|
||||||
export PATH="$test_dir:$PATH"
|
|
||||||
|
|
||||||
cat >"$TMUX_STUB_WINDOWS" <<'WINDOWS'
|
|
||||||
@1:000:100::Work:1:editor
|
|
||||||
@2:100:110::Work:2:claude
|
|
||||||
@3:001:120::Side|Gig:1:server: still going
|
|
||||||
WINDOWS
|
|
||||||
: >"$TMUX_STUB_CLIENTS"
|
|
||||||
|
|
||||||
output=$("$ROOT/bin/omarchy-tmux-alert" show --json)
|
|
||||||
expected='{"count":2,"tooltip":"claude (Work:2), server: still going (Side|Gig:1)"}'
|
|
||||||
[[ $output == "$expected" ]] || fail "tmux alert reports alerted windows" "expected: $expected"$'\n'"actual: $output"
|
|
||||||
pass "tmux alert reports alerted windows"
|
|
||||||
|
|
||||||
output=$("$ROOT/bin/omarchy-tmux-alert" show)
|
|
||||||
[[ $output == "claude (Work:2), server: still going (Side|Gig:1)" ]] || fail "tmux alert describes alerted windows" "$output"
|
|
||||||
pass "tmux alert describes alerted windows"
|
|
||||||
|
|
||||||
cat >"$TMUX_STUB_WINDOWS" <<'WINDOWS'
|
|
||||||
@1:000:200:100:Work:1:editor
|
|
||||||
WINDOWS
|
|
||||||
echo 'attached,UTF-8:@1' >"$TMUX_STUB_CLIENTS"
|
|
||||||
|
|
||||||
output=$("$ROOT/bin/omarchy-tmux-alert" show --json)
|
|
||||||
expected='{"count":1,"tooltip":"editor (Work:1)"}'
|
|
||||||
[[ $output == "$expected" ]] || fail "tmux alert reports output in an unfocused active window" "expected: $expected"$'\n'"actual: $output"
|
|
||||||
pass "tmux alert reports output in an unfocused active window"
|
|
||||||
|
|
||||||
echo 'attached,focused,UTF-8:@1' >"$TMUX_STUB_CLIENTS"
|
|
||||||
output=$("$ROOT/bin/omarchy-tmux-alert" show --json)
|
|
||||||
[[ $output == '{"count":0,"tooltip":""}' ]] || fail "tmux alert ignores output in a focused active window" "$output"
|
|
||||||
pass "tmux alert ignores output in a focused active window"
|
|
||||||
|
|
||||||
cat >"$TMUX_STUB_CLIENTS" <<'CLIENTS'
|
|
||||||
attached,UTF-8:@1
|
|
||||||
attached,focused,UTF-8:@1
|
|
||||||
CLIENTS
|
|
||||||
output=$("$ROOT/bin/omarchy-tmux-alert" show --json)
|
|
||||||
[[ $output == '{"count":0,"tooltip":""}' ]] || fail "tmux alert ignores a window visible in another focused client" "$output"
|
|
||||||
pass "tmux alert ignores a window visible in another focused client"
|
|
||||||
|
|
||||||
echo 'attached,UTF-8:@1' >"$TMUX_STUB_CLIENTS"
|
|
||||||
cat >"$TMUX_STUB_WINDOWS" <<'WINDOWS'
|
|
||||||
@1:000:200:200:Work:1:editor
|
|
||||||
WINDOWS
|
|
||||||
|
|
||||||
output=$("$ROOT/bin/omarchy-tmux-alert" show --json)
|
|
||||||
[[ $output == '{"count":0,"tooltip":""}' ]] || fail "tmux alert reports no alerted windows" "$output"
|
|
||||||
pass "tmux alert reports no alerted windows"
|
|
||||||
|
|
||||||
[[ -z $("$ROOT/bin/omarchy-tmux-alert" show) ]] || fail "tmux alert stays quiet without alerts"
|
|
||||||
pass "tmux alert stays quiet without alerts"
|
|
||||||
|
|
||||||
: >"$TMUX_STUB_WINDOWS"
|
|
||||||
: >"$TMUX_STUB_CLIENTS"
|
|
||||||
output=$("$ROOT/bin/omarchy-tmux-alert" show --json)
|
|
||||||
[[ $output == '{"count":0,"tooltip":""}' ]] || fail "tmux alert handles a missing tmux server" "$output"
|
|
||||||
pass "tmux alert handles a missing tmux server"
|
|
||||||
|
|
||||||
: >"$TMUX_STUB_CALLS"
|
|
||||||
"$ROOT/bin/omarchy-tmux-alert" track @3 345
|
|
||||||
expected=$'set-option -wq -t @3 @omarchy_unfocused_activity 345\n-q omarchy.indicators refresh'
|
|
||||||
output=$(cat "$TMUX_STUB_CALLS")
|
|
||||||
[[ $output == "$expected" ]] || fail "tmux alert records the activity watermark and refreshes indicators" "expected: $expected"$'\n'"actual: $output"
|
|
||||||
pass "tmux alert records the activity watermark and refreshes indicators"
|
|
||||||
|
|
||||||
: >"$TMUX_STUB_CALLS"
|
|
||||||
"$ROOT/bin/omarchy-tmux-alert" track invalid nope
|
|
||||||
[[ ! -s $TMUX_STUB_CALLS ]] || fail "tmux alert ignores invalid tracking arguments" "$(cat "$TMUX_STUB_CALLS")"
|
|
||||||
pass "tmux alert ignores invalid tracking arguments"
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
source "$(dirname "$0")/base-test.sh"
|
|
||||||
|
|
||||||
migration="$ROOT/migrations/1784955584.sh"
|
|
||||||
test_dir=$(mktemp -d)
|
|
||||||
trap 'rm -rf "$test_dir"' EXIT
|
|
||||||
|
|
||||||
mkdir -p "$test_dir/home/.config/tmux" "$test_dir/bin"
|
|
||||||
|
|
||||||
cat >"$test_dir/home/.config/tmux/tmux.conf" <<'EOF'
|
|
||||||
# Alerts
|
|
||||||
set-hook -g alert-bell 'run-shell -b "omarchy-shell -q omarchy.indicators refresh"'
|
|
||||||
set-hook -g after-select-window 'run-shell -b "omarchy-shell -q omarchy.indicators refresh"'
|
|
||||||
set-hook -g client-session-changed 'run-shell -b "omarchy-shell -q omarchy.indicators refresh"'
|
|
||||||
set-hook -g client-focus-out[42] 'display-message "custom focus hook"'
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat >"$test_dir/bin/omarchy-restart-tmux" <<'EOF'
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo restart >>"$TMUX_MIGRATION_RESTART_LOG"
|
|
||||||
EOF
|
|
||||||
chmod +x "$test_dir/bin/omarchy-restart-tmux"
|
|
||||||
|
|
||||||
export TMUX_MIGRATION_RESTART_LOG="$test_dir/restarts"
|
|
||||||
tmux_config="$test_dir/home/.config/tmux/tmux.conf"
|
|
||||||
|
|
||||||
HOME="$test_dir/home" PATH="$test_dir/bin:$PATH" bash -euo pipefail "$migration" >/dev/null
|
|
||||||
|
|
||||||
grep -Fq 'after-select-window '"'"'run-shell -b "omarchy-tmux-alert track #{window_id} #{window_activity}"'"'" "$tmux_config" ||
|
|
||||||
fail "tmux activity migration tracks newly selected windows"
|
|
||||||
grep -Fq 'client-session-changed '"'"'run-shell -b "omarchy-tmux-alert track #{window_id} #{window_activity}"'"'" "$tmux_config" ||
|
|
||||||
fail "tmux activity migration tracks session changes"
|
|
||||||
grep -Fq 'client-focus-out[100] '"'"'run-shell -b "omarchy-tmux-alert track #{window_id} #{window_activity}"'"'" "$tmux_config" ||
|
|
||||||
fail "tmux activity migration tracks terminal focus loss"
|
|
||||||
grep -Fq 'client-focus-in[100] '"'"'run-shell -b "omarchy-tmux-alert track #{window_id} #{window_activity}"'"'" "$tmux_config" ||
|
|
||||||
fail "tmux activity migration tracks terminal focus gain"
|
|
||||||
grep -Fq 'client-focus-out[42] '"'"'display-message "custom focus hook"'"'" "$tmux_config" ||
|
|
||||||
fail "tmux activity migration preserves custom indexed focus hooks"
|
|
||||||
restart_count=$(wc -l <"$TMUX_MIGRATION_RESTART_LOG")
|
|
||||||
((restart_count == 1)) || fail "tmux activity migration reloads tmux once"
|
|
||||||
pass "tmux activity migration installs focus-aware hooks"
|
|
||||||
|
|
||||||
before=$(sha256sum "$tmux_config")
|
|
||||||
HOME="$test_dir/home" PATH="$test_dir/bin:$PATH" bash -euo pipefail "$migration" >/dev/null
|
|
||||||
after=$(sha256sum "$tmux_config")
|
|
||||||
|
|
||||||
[[ $before == "$after" ]] || fail "tmux activity migration is idempotent"
|
|
||||||
restart_count=$(wc -l <"$TMUX_MIGRATION_RESTART_LOG")
|
|
||||||
((restart_count == 1)) || fail "idempotent tmux activity migration does not reload tmux"
|
|
||||||
pass "tmux activity migration is idempotent"
|
|
||||||
Reference in New Issue
Block a user