diff --git a/bin/omarchy-capture-screenrecording b/bin/omarchy-capture-screenrecording index 0c4c5494c95..61db59c1fed 100755 --- a/bin/omarchy-capture-screenrecording +++ b/bin/omarchy-capture-screenrecording @@ -45,6 +45,7 @@ STOP_RECORDING="false" # both files back at the fixed names this comment describes. RUNTIME_DIR="${XDG_RUNTIME_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/omarchy}" RECORDING_FILE="$RUNTIME_DIR/omarchy-screenrecord-filename" +GSR_SOCKET="$RUNTIME_DIR/omarchy-gsr.sock" REGION_FILE="$RUNTIME_DIR/omarchy-screenrecord-region" LOG_FILE=$([[ ${OMARCHY_SCREENRECORD_DEBUG:-false} == "true" ]] && echo "$RUNTIME_DIR/omarchy-screenrecord.log" || echo "/dev/null") @@ -203,7 +204,7 @@ start_screenrecording() { [[ -n $audio_devices ]] && audio_args+=(-a "$audio_devices" -ac aac) echo "===== $(date '+%F %T') args: $* target: $target =====" >>"$LOG_FILE" - gpu-screen-recorder "${capture_args[@]}" -k auto -f 60 -fm cfr -fallback-cpu-encoding yes -o "$filename" "${audio_args[@]}" 2>>"$LOG_FILE" & + gpu-screen-recorder "${capture_args[@]}" -k auto -f 60 -fm cfr -fallback-cpu-encoding yes -ipc "$GSR_SOCKET" -o "$filename" "${audio_args[@]}" 2>>"$LOG_FILE" & local pid=$! while kill -0 $pid 2>/dev/null && [[ ! -f $filename ]]; do @@ -217,22 +218,16 @@ start_screenrecording() { } stop_screenrecording() { - pkill -SIGINT -f "^gpu-screen-recorder" # SIGINT required to save video properly - - # Wait a maximum of 5 seconds to finish before hard killing - local count=0 - while pgrep -f "^gpu-screen-recorder" >/dev/null && ((count < 50)); do - sleep 0.1 - count=$((count + 1)) - done + # gsr-cli stop asks the instance on GSR_SOCKET to save and waits until it has. + local stopped=0 + if gsr-cli -ipc "$GSR_SOCKET" stop >/dev/null 2>&1; then + stopped=1 + fi toggle_screenrecording_indicator cleanup_webcam - if pgrep -f "^gpu-screen-recorder" >/dev/null; then - pkill -9 -f "^gpu-screen-recorder" - omarchy-notification-send -u critical -t 5000 "Screen recording error" "Recording process had to be force-killed. Video may be corrupted." - else + if ((stopped)); then finalize_recording local filename=$(cat "$RECORDING_FILE" 2>/dev/null) echo "$filename" @@ -252,9 +247,12 @@ stop_screenrecording() { sleep 2 rm -f "$preview" ) & - fi - rm -f "$RECORDING_FILE" + rm -f "$RECORDING_FILE" + else + omarchy-notification-send -u critical -t 5000 "Screen recording error" "Could not stop the recording through gsr-cli." + return 1 + fi } toggle_screenrecording_indicator() { @@ -262,7 +260,7 @@ toggle_screenrecording_indicator() { } screenrecording_active() { - pgrep -f "^gpu-screen-recorder" >/dev/null + gsr-cli -ipc "$GSR_SOCKET" status >/dev/null 2>&1 } finalize_recording() { diff --git a/default/omarchy/omarchy-menu.jsonc b/default/omarchy/omarchy-menu.jsonc index 36518d09c4b..184ada12425 100644 --- a/default/omarchy/omarchy-menu.jsonc +++ b/default/omarchy/omarchy-menu.jsonc @@ -57,7 +57,7 @@ "trigger.reminder": {"icon":"󰢌","label":"Reminder","aliases":["reminder"]}, "trigger.capture": {"icon":"","label":"Capture","aliases":["capture","screenshot","screenrecord","screen-record","screenrecording"]}, "trigger.capture.screenshot": {"icon":"","label":"Screenshot","action":"omarchy-capture-screenshot"}, - "trigger.capture.screenrecord.stop": {"icon":"","label":"Stop Screenrecording","when":"pgrep -f '^gpu-screen-recorder'","action":"omarchy-capture-screenrecording --stop-recording"}, + "trigger.capture.screenrecord.stop": {"icon":"","label":"Stop Screenrecording","when":"gsr-cli -ipc \"${XDG_RUNTIME_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/omarchy}/omarchy-gsr.sock\" status","action":"omarchy-capture-screenrecording --stop-recording"}, "trigger.capture.screenrecord": {"icon":"","label":"Screenrecord"}, "trigger.capture.text": {"icon":"󰴑","label":"Text","action":"omarchy-capture-text"}, "trigger.capture.qr": {"icon":"󰐲","label":"QR Code","action":"omarchy-capture-qr"}, diff --git a/shell/plugins/bar/indicators/ScreenRecording.qml b/shell/plugins/bar/indicators/ScreenRecording.qml index 271e1c399c2..271b7316422 100644 --- a/shell/plugins/bar/indicators/ScreenRecording.qml +++ b/shell/plugins/bar/indicators/ScreenRecording.qml @@ -1,4 +1,5 @@ import QtQuick +import Quickshell import Quickshell.Io import qs.Ui @@ -15,7 +16,8 @@ BarIndicator { function refresh() { if (!root.bar || statusProc.running) return - statusProc.command = ["pgrep", "--quiet", "-f", "^gpu-screen-recorder"] + var runtimeDir = Quickshell.env("XDG_RUNTIME_DIR") || (Quickshell.env("XDG_STATE_HOME") || Quickshell.env("HOME") + "/.local/state") + "/omarchy" + statusProc.command = ["gsr-cli", "-ipc", runtimeDir + "/omarchy-gsr.sock", "status"] statusProc.running = true } diff --git a/test/shell.d/screenrecording-ipc-test.sh b/test/shell.d/screenrecording-ipc-test.sh new file mode 100644 index 00000000000..695e30c1816 --- /dev/null +++ b/test/shell.d/screenrecording-ipc-test.sh @@ -0,0 +1,135 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +recording="$ROOT/shell/plugins/bar/indicators/ScreenRecording.qml" +capture="$ROOT/bin/omarchy-capture-screenrecording" +menu="$ROOT/default/omarchy/omarchy-menu.jsonc" +socket='${XDG_RUNTIME_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/omarchy}/omarchy-gsr.sock' + +grep -Fq 'GSR_SOCKET="$RUNTIME_DIR/omarchy-gsr.sock"' "$capture" || + fail "capture helper uses the shared Omarchy gsr socket" +grep -Eq '^ *gpu-screen-recorder .*-ipc "\$GSR_SOCKET"' "$capture" || + fail "gpu-screen-recorder is launched with -ipc on the shared socket" +grep -Fq 'gsr-cli -ipc "$GSR_SOCKET" status' "$capture" || + fail "capture helper gates on gsr-cli status for the shared socket" +grep -Fq 'gsr-cli -ipc "$GSR_SOCKET" stop' "$capture" || + fail "capture helper stops via gsr-cli on the shared socket" +if grep -q 'RECORDING_PID_FILE' "$capture"; then + fail "stop does not keep a pid file beside gsr-cli" +fi +if grep -E 'kill[[:space:]]+(-s[[:space:]]+)?(SIGINT|-INT|-9|-KILL)' "$capture" >/dev/null; then + fail "stop does not kill the recorder; gsr-cli stop is enough" +fi + +grep -Fq 'Quickshell.env("XDG_RUNTIME_DIR")' "$recording" || + fail "indicator builds the gsr socket from XDG_RUNTIME_DIR" +grep -qx 'import Quickshell' "$recording" || + fail "indicator imports Quickshell, which Quickshell.env needs" +grep -Fq '"gsr-cli", "-ipc"' "$recording" || + fail "indicator status uses gsr-cli -ipc" +grep -Fq '"status"' "$recording" || + fail "indicator status asks gsr-cli for status" +grep -Fq '/omarchy-gsr.sock' "$recording" || + fail "indicator uses the shared Omarchy gsr socket" + +grep -Fq "gsr-cli -ipc \\\"$socket\\\" status" "$menu" || + fail "the capture menu uses gsr-cli status on the shared socket" + +for file in "$capture" "$recording" "$menu"; do + if grep -E 'pidof[[:space:]"]+-q[[:space:]"]+gpu-screen-recorder|pgrep[[:space:]"-]+-f[[:space:]"]+\^?gpu-screen-recorder' "$file" >/dev/null; then + fail "screen-recording gate does not use pidof or pgrep -f for gpu-screen-recorder" "$file" + fi +done + +if grep -E 'pkill[[:space:]].*-f[[:space:]"]+\^?gpu-screen-recorder' "$capture" >/dev/null; then + fail "stop does not pkill -f gpu-screen-recorder" +fi +if grep -E 'pgrep[[:space:]].*-f[[:space:]"]+\^?gpu-screen-recorder' "$capture" >/dev/null; then + fail "stop does not pgrep -f gpu-screen-recorder" +fi + +pass "screen recording start/status/stop talk to the Omarchy gsr socket" + +# Run the stop path. A stub that exits 0 for the notification must not hide a +# failed gsr-cli stop, and a successful stop must still print and forget the file. +stub_bin=$(mktemp -d) +runtime=$(mktemp -d) +videos=$(mktemp -d) +trap 'rm -rf "$stub_bin" "$runtime" "$videos"' EXIT + +cat >"$stub_bin/gsr-cli" <<'SH' +#!/bin/bash +printf '%s\n' "$*" >>"${GSR_LOG:?}" +if [[ $1 == -ipc && $3 == status ]]; then + exit 0 +fi +if [[ $1 == -ipc && $3 == stop ]]; then + exit "${GSR_STOP_STATUS:-0}" +fi +exit 1 +SH +cat >"$stub_bin/omarchy-notification-send" <<'SH' +#!/bin/bash +printf '%s\n' "$*" >>"${NOTE_LOG:?}" +exit 0 +SH +cat >"$stub_bin/omarchy-shell" <<'SH' +#!/bin/bash +exit 0 +SH +cat >"$stub_bin/ffprobe" <<'SH' +#!/bin/bash +exit 1 +SH +cat >"$stub_bin/ffmpeg" <<'SH' +#!/bin/bash +exit 1 +SH +cat >"$stub_bin/pkill" <<'SH' +#!/bin/bash +printf '%s\n' "$*" >>"${PKILL_LOG:?}" +exit 0 +SH +chmod +x "$stub_bin"/* + +export PATH="$stub_bin:$PATH" +export GSR_LOG="$videos/gsr.log" +export NOTE_LOG="$videos/note.log" +export PKILL_LOG="$videos/pkill.log" +export XDG_RUNTIME_DIR="$runtime" +export OMARCHY_SCREENRECORD_DIR="$videos" + +clip="$videos/clip.mp4" +printf 'not a video\n' >"$clip" +printf '%s\n' "$clip" >"$runtime/omarchy-screenrecord-filename" + +set +e +GSR_STOP_STATUS=1 "$capture" --stop-recording >/dev/null 2>&1 +failed_status=$? +set -e +[[ $failed_status -ne 0 ]] || + fail "a failed gsr-cli stop does not exit successfully" "status $failed_status" +[[ -f $runtime/omarchy-screenrecord-filename ]] || + fail "a failed gsr-cli stop keeps the recording filename" +grep -Fq 'Could not stop the recording through gsr-cli.' "$NOTE_LOG" || + fail "a failed gsr-cli stop reports the error" "$(cat "$NOTE_LOG")" +pass "a failed gsr-cli stop exits non-zero and keeps the filename" + +: >"$GSR_LOG" +: >"$NOTE_LOG" +printf '%s\n' "$clip" >"$runtime/omarchy-screenrecord-filename" +saved=$("$capture" --stop-recording) +[[ $saved == "$clip" ]] || + fail "a successful stop prints the saved recording" "$saved" +[[ ! -e $runtime/omarchy-screenrecord-filename ]] || + fail "a successful stop removes the recording filename" +grep -Fqx -- "-ipc $runtime/omarchy-gsr.sock stop" "$GSR_LOG" || + fail "a successful stop calls gsr-cli stop on the Omarchy gsr socket" "$(cat "$GSR_LOG")" +pass "a successful gsr-cli stop prints the recording and clears its filename" + +grep -Fq -- '-f WebcamOverlay' "$videos/pkill.log" || + fail "stop cleanup does not call the stubbed pkill" "$(cat "$videos/pkill.log" 2>/dev/null || true)" +pass "stop cleanup calls the stubbed pkill instead of a live WebcamOverlay"