Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 43 additions & 4 deletions shell/plugins/bar/indicators/ScreenRecording.qml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,23 @@ BarIndicator {
activeTooltipText: "Stop recording"
inactiveTooltipText: "Screen Recording"

// pgrep is fast; if the Process never leaves running (onExited lost after an
// abrupt recorder death, handler throw, etc.), refresh() would bail forever
// and the icon stay stuck on the last known state. Cap each probe and keep
// polling so a killed gpu-screen-recorder is noticed without a shell restart.
function refresh() {
if (!root.bar || statusProc.running) return
if (!root.bar)
return

if (statusProc.running) {
if (!probeWatchdog.running)
probeWatchdog.restart()
return
}

statusProc.command = ["pgrep", "--quiet", "-f", "^gpu-screen-recorder"]
statusProc.running = true
probeWatchdog.restart()
}

onBarChanged: refresh()
Expand All @@ -25,17 +38,43 @@ BarIndicator {
Connections {
target: root.indicatorHost
ignoreUnknownSignals: true
function onRefreshRequested() { root.refresh() }
function onRefreshRequested() {
root.refresh()
}
}

Process {
id: statusProc
onExited: function(exitCode) {
onExited: function (exitCode) {
probeWatchdog.stop()
root.recording = exitCode === 0
}
}

onPressed: function() {
Timer {
id: probeWatchdog
interval: 2000
repeat: false
onTriggered: {
if (!statusProc.running)
return
// Drop the stuck Process handle so the next poll can start a fresh pgrep.
statusProc.running = false
root.refresh()
}
}

Timer {
id: pollTimer
// Abnormal kills skip omarchy-capture-screenrecording's indicator toggle;
// poll so the bar catches up without waiting for another IPC refresh.
interval: 2000
running: true
repeat: true
onTriggered: root.refresh()
}

onPressed: function () {
if (root.bar) {
root.bar.run(root.recording ? "omarchy-capture-screenrecording --stop-recording" : "omarchy-menu toggle trigger.capture.screenrecord")
}
Expand Down
33 changes: 33 additions & 0 deletions test/shell.d/screenrecording-indicator-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash

set -euo pipefail

# ScreenRecording.qml must not freeze on the last known state when a pgrep
# Process never leaves running, and must notice an abnormally killed recorder
# without waiting for omarchy.indicators refresh.

source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"

indicator="$ROOT/shell/plugins/bar/indicators/ScreenRecording.qml"
[[ -f $indicator ]] || fail "ScreenRecording indicator is present"

grep -q 'probeWatchdog' "$indicator" ||
fail "indicator watches for a stuck status Process"
grep -q 'statusProc.running = false' "$indicator" ||
fail "stuck-probe path clears Process.running so refresh can run again"
grep -q 'pollTimer' "$indicator" ||
fail "indicator polls recording state on a timer"
grep -Eq 'interval:[[:space:]]*2000' "$indicator" ||
fail "probe/poll intervals stay short enough to recover without a shell restart"

# refresh must not permanently no-op solely because running is true without a
# way to clear it.
if ! grep -A20 'function refresh' "$indicator" | grep -q 'probeWatchdog'; then
fail "refresh arms the stuck-probe watchdog when a probe is already running"
fi

grep -q 'onExited' "$indicator" || fail "indicator still updates recording from pgrep exit code"
grep -q 'omarchy-capture-screenrecording --stop-recording' "$indicator" ||
fail "active click still stops the recorder"

pass "ScreenRecording indicator recovers from stuck probes and polls after crashes"