tmux only raises an activity flag for windows that are not currently
selected, so a long-running command in the window you left selected --
the common case, since you switch away by moving your Hyprland focus
elsewhere rather than by selecting another tmux window -- finished
silently and the indicator never lit.
Windows now also count as waiting when they are selected in an attached
client, every client showing them is unfocused, and their window_activity
is newer than an @omarchy_unfocused_activity watermark. The watermark is
stamped by a new `track` subcommand wired to client-focus-in/out and the
existing select-window hooks, so it records where attention last was.
The hooks pass #{window_id} and #{window_activity} as arguments, which
tmux expands when the hook fires; run-shell -b would otherwise let output
arriving during the handoff be swallowed by the new watermark. The focus
hooks take index 100 to leave a user's own bindings alone.
That state has no hook of its own, so it needs polling to be noticed.
The probe therefore moves out of the indicator and into a service plugin,
alongside nightlight and battery. A bar surface exists per monitor and
each one instantiates every indicator twice, once per block, so a timer
on the indicator meant a shell-out per instance per tick -- four probe
processes every three seconds on a two-monitor machine, forever. One
service polls for the whole shell instead.
Sharing the state also fixes what per-instance polling would have papered
over: each indicator used to own its own count, so a timer-driven update
only refreshed the bar it ran on and left the other monitors stale.
Refreshes still arrive over the existing indicator broadcast, which now
coalesces into a single run no matter how many bars relay it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018CC9kSQv8ZEogaxDoeKBzL
61 lines
1.6 KiB
QML
61 lines
1.6 KiB
QML
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()
|
|
}
|