Fix clipboard watcher lifecycle
This commit is contained in:
@@ -19,7 +19,8 @@ Item {
|
||||
|
||||
property string historyPath: Quickshell.env("HOME") + "/.local/state/omarchy/clipboard-history.json"
|
||||
property string captureScript: root.omarchyPath + "/shell/plugins/clipboard/capture.sh"
|
||||
property string watchCommand: "script=$1\ntrap 'kill $(jobs -p) 2>/dev/null' EXIT\nOMARCHY_CLIPBOARD_WATCH_MIME=text wl-paste --type text --watch \"$script\" 2>/dev/null &\nOMARCHY_CLIPBOARD_WATCH_MIME=image/png wl-paste --type image/png --watch \"$script\" 2>/dev/null &\nwait"
|
||||
property string initScript: root.omarchyPath + "/shell/plugins/clipboard/init.sh"
|
||||
property string watchScript: root.omarchyPath + "/shell/plugins/clipboard/watch.sh"
|
||||
// Shares the [menu] surface tokens — themes that style the menu also
|
||||
// style the clipboard. Selected-row colors composed in the
|
||||
// singleton so consumers drop them straight into Rectangle bindings.
|
||||
@@ -261,11 +262,11 @@ Item {
|
||||
|
||||
Process {
|
||||
id: initProc
|
||||
command: ["bash", "-c", "mkdir -p ~/.local/state/omarchy\nscript=$1\nfor pid in $(pgrep -x wl-paste || true); do\n cmdline=$(tr '\\0' ' ' <\"/proc/$pid/cmdline\" 2>/dev/null || true)\n if [[ $cmdline == *\"wl-paste --type text --watch $script \"* || $cmdline == *\"wl-paste --type \"*\" --watch $script \"* || $cmdline == *\"/shell/plugins/clipboard/capture.sh\"* ]]; then\n kill \"$pid\" 2>/dev/null || true\n fi\ndone", "clipboard-init", root.captureScript]
|
||||
command: [root.initScript, root.captureScript]
|
||||
onExited: {
|
||||
currentProc.command = [root.captureScript]
|
||||
currentProc.running = true
|
||||
watchProc.command = ["bash", "-c", root.watchCommand, "clipboard-watch", root.captureScript]
|
||||
watchProc.command = [root.watchScript, root.captureScript]
|
||||
watchProc.running = true
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
|
||||
current_script=${1:-}
|
||||
[[ -n $current_script ]] || exit 0
|
||||
|
||||
is_clipboard_capture() {
|
||||
local path=$1
|
||||
|
||||
[[ $path == */shell/plugins/clipboard/capture.sh ]]
|
||||
}
|
||||
|
||||
watched_script_for_pid() {
|
||||
local pid=$1
|
||||
local -a args=()
|
||||
local i
|
||||
|
||||
[[ -r /proc/$pid/cmdline ]] || return 1
|
||||
mapfile -d '' -t args <"/proc/$pid/cmdline" || return 1
|
||||
((${#args[@]} > 0)) || return 1
|
||||
|
||||
for ((i = 0; i < ${#args[@]}; i++)); do
|
||||
if [[ ${args[i]} == "--watch" ]]; then
|
||||
printf '%s\n' "${args[i + 1]:-}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
for pid in $(pgrep -x wl-paste 2>/dev/null || true); do
|
||||
watched_script=$(watched_script_for_pid "$pid" || true)
|
||||
[[ -n $watched_script ]] || continue
|
||||
is_clipboard_capture "$watched_script" || continue
|
||||
|
||||
if [[ $watched_script == $current_script || ! -e $watched_script ]]; then
|
||||
kill "$pid" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -o pipefail
|
||||
|
||||
capture_script=${1:-}
|
||||
[[ -n $capture_script && -x $capture_script ]] || exit 1
|
||||
|
||||
owner_pid=$PPID
|
||||
watchdog_pid=""
|
||||
|
||||
cleanup() {
|
||||
local pid
|
||||
|
||||
if [[ -n $watchdog_pid ]]; then
|
||||
kill "$watchdog_pid" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
for pid in $(jobs -p); do
|
||||
[[ $pid == $watchdog_pid ]] && continue
|
||||
kill "$pid" 2>/dev/null || true
|
||||
done
|
||||
|
||||
wait 2>/dev/null || true
|
||||
}
|
||||
|
||||
stop() {
|
||||
cleanup
|
||||
exit 0
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
trap stop HUP INT TERM
|
||||
|
||||
(
|
||||
while kill -0 "$owner_pid" 2>/dev/null; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
kill -TERM "$$" 2>/dev/null || true
|
||||
) &
|
||||
watchdog_pid=$!
|
||||
|
||||
OMARCHY_CLIPBOARD_WATCH_MIME=text wl-paste --type text --watch "$capture_script" 2>/dev/null &
|
||||
OMARCHY_CLIPBOARD_WATCH_MIME=image/png wl-paste --type image/png --watch "$capture_script" 2>/dev/null &
|
||||
|
||||
wait
|
||||
@@ -165,7 +165,42 @@ assert(
|
||||
JS
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$TMPDIR"' EXIT
|
||||
PIDS_TO_KILL=()
|
||||
|
||||
cleanup() {
|
||||
local pid
|
||||
|
||||
for pid in "${PIDS_TO_KILL[@]}"; do
|
||||
kill "$pid" 2>/dev/null || true
|
||||
wait "$pid" 2>/dev/null || true
|
||||
done
|
||||
|
||||
rm -rf "$TMPDIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
process_gone() {
|
||||
local pid=$1
|
||||
local stat
|
||||
|
||||
for _ in {1..40}; do
|
||||
stat=$(ps -o stat= -p "$pid" 2>/dev/null || true)
|
||||
if [[ -z $stat || $stat == Z* ]]; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
process_alive() {
|
||||
local pid=$1
|
||||
local stat
|
||||
|
||||
stat=$(ps -o stat= -p "$pid" 2>/dev/null || true)
|
||||
[[ -n $stat && $stat != Z* ]]
|
||||
}
|
||||
|
||||
mkdir -p "$TMPDIR/bin" "$TMPDIR/home/.local/state/omarchy"
|
||||
|
||||
@@ -206,26 +241,112 @@ SH
|
||||
|
||||
chmod +x "$TMPDIR/bin/wl-copy" "$TMPDIR/bin/wl-paste" "$TMPDIR/bin/wtype" "$TMPDIR/bin/omarchy-launch-browser" "$TMPDIR/bin/omarchy-launch-editor" "$TMPDIR/bin/tensaku-edit"
|
||||
|
||||
capture_output=$(XDG_RUNTIME_DIR="$TMPDIR" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh")
|
||||
capture_output=$(XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh")
|
||||
[[ $capture_output == '{"type":"text","text":"terminal copy"}' ]] || fail "clipboard capture records normal text events"
|
||||
pass "clipboard capture records normal text events"
|
||||
|
||||
capture_output=$(printf 'closing app copy' | OMARCHY_CLIPBOARD_WATCH_MIME=text WL_PASTE_TEXT="stale read" XDG_RUNTIME_DIR="$TMPDIR" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh")
|
||||
capture_output=$(printf 'closing app copy' | OMARCHY_CLIPBOARD_WATCH_MIME=text WL_PASTE_TEXT="stale read" XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh")
|
||||
[[ $capture_output == '{"type":"text","text":"closing app copy"}' ]] || fail "clipboard capture records watched text from stdin"
|
||||
pass "clipboard capture records watched text from stdin"
|
||||
|
||||
capture_output=$(printf 'secret' | CLIPBOARD_STATE=sensitive OMARCHY_CLIPBOARD_WATCH_MIME=text XDG_RUNTIME_DIR="$TMPDIR" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh")
|
||||
capture_output=$(printf 'png-data' | OMARCHY_CLIPBOARD_WATCH_MIME=image/png XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh")
|
||||
image_path=$(jq -r '.path' <<<"$capture_output")
|
||||
jq -e '.type == "image" and .mime == "image/png" and (.capturedAt | type == "string")' <<<"$capture_output" >/dev/null || fail "clipboard capture records watched png images"
|
||||
[[ -s $image_path && $(<"$image_path") == "png-data" ]] || fail "clipboard capture stores watched png image data"
|
||||
pass "clipboard capture records watched png images"
|
||||
|
||||
capture_output=$(printf 'secret' | CLIPBOARD_STATE=sensitive OMARCHY_CLIPBOARD_WATCH_MIME=text XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh")
|
||||
[[ -z $capture_output ]] || fail "clipboard capture ignores sensitive watched text"
|
||||
pass "clipboard capture ignores sensitive watched text"
|
||||
|
||||
capture_output=$(CLIPBOARD_STATE=sensitive XDG_RUNTIME_DIR="$TMPDIR" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh")
|
||||
capture_output=$(CLIPBOARD_STATE=sensitive XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh")
|
||||
[[ -z $capture_output ]] || fail "clipboard capture ignores sensitive clipboard events"
|
||||
pass "clipboard capture ignores sensitive clipboard events"
|
||||
|
||||
capture_output=$(WL_PASTE_TYPES="text/plain\nx-kde-passwordManagerHint\n" XDG_RUNTIME_DIR="$TMPDIR" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh")
|
||||
capture_output=$(WL_PASTE_TYPES="text/plain\nx-kde-passwordManagerHint\n" XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh")
|
||||
[[ -z $capture_output ]] || fail "clipboard capture ignores password manager hint"
|
||||
pass "clipboard capture ignores password manager hint"
|
||||
|
||||
cat >"$TMPDIR/bin/wl-paste" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s\t%s\n' "$BASHPID" "$*" >>"${WL_PASTE_LOG:-/dev/null}"
|
||||
sleep_pid=""
|
||||
|
||||
cleanup() {
|
||||
[[ -n $sleep_pid ]] && kill "$sleep_pid" 2>/dev/null || true
|
||||
exit 0
|
||||
}
|
||||
trap cleanup HUP INT TERM
|
||||
|
||||
while true; do
|
||||
sleep 10 &
|
||||
sleep_pid=$!
|
||||
wait "$sleep_pid"
|
||||
done
|
||||
SH
|
||||
chmod +x "$TMPDIR/bin/wl-paste"
|
||||
|
||||
clipboard_lifecycle_dir="$TMPDIR/clipboard-lifecycle"
|
||||
current_script="$clipboard_lifecycle_dir/current/shell/plugins/clipboard/capture.sh"
|
||||
other_script="$clipboard_lifecycle_dir/other/shell/plugins/clipboard/capture.sh"
|
||||
missing_script="$clipboard_lifecycle_dir/missing/shell/plugins/clipboard/capture.sh"
|
||||
watch_log="$clipboard_lifecycle_dir/wl-paste.log"
|
||||
mkdir -p "$(dirname "$current_script")" "$(dirname "$other_script")"
|
||||
cp "$ROOT/shell/plugins/clipboard/capture.sh" "$current_script"
|
||||
cp "$ROOT/shell/plugins/clipboard/capture.sh" "$other_script"
|
||||
chmod +x "$current_script" "$other_script"
|
||||
|
||||
WL_PASTE_LOG="$watch_log" PATH="$TMPDIR/bin:$PATH" wl-paste --type text --watch "$current_script" &
|
||||
current_pid=$!
|
||||
PIDS_TO_KILL+=("$current_pid")
|
||||
WL_PASTE_LOG="$watch_log" PATH="$TMPDIR/bin:$PATH" wl-paste --type text --watch "$other_script" &
|
||||
other_pid=$!
|
||||
PIDS_TO_KILL+=("$other_pid")
|
||||
WL_PASTE_LOG="$watch_log" PATH="$TMPDIR/bin:$PATH" wl-paste --type text --watch "$missing_script" &
|
||||
missing_pid=$!
|
||||
PIDS_TO_KILL+=("$missing_pid")
|
||||
|
||||
sleep 0.2
|
||||
PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/init.sh" "$current_script"
|
||||
process_gone "$current_pid" || fail "clipboard init kills watchers for the current capture script"
|
||||
process_gone "$missing_pid" || fail "clipboard init kills stale temp-root watchers"
|
||||
process_alive "$other_pid" || fail "clipboard init preserves watchers for another existing capture script"
|
||||
kill "$other_pid" 2>/dev/null || true
|
||||
wait "$other_pid" 2>/dev/null || true
|
||||
pass "clipboard init cleans current and stale watchers without killing other live roots"
|
||||
|
||||
watch_owner="$clipboard_lifecycle_dir/watch-owner.sh"
|
||||
watch_pid_file="$clipboard_lifecycle_dir/watch.pid"
|
||||
: >"$watch_log"
|
||||
cat >"$watch_owner" <<SH
|
||||
#!/bin/bash
|
||||
WL_PASTE_LOG="$watch_log" PATH="$TMPDIR/bin:\$PATH" "$ROOT/shell/plugins/clipboard/watch.sh" "$current_script" &
|
||||
printf '%s\n' "\$!" >"$watch_pid_file"
|
||||
wait
|
||||
SH
|
||||
chmod +x "$watch_owner"
|
||||
|
||||
"$watch_owner" &
|
||||
owner_pid=$!
|
||||
PIDS_TO_KILL+=("$owner_pid")
|
||||
|
||||
for _ in {1..40}; do
|
||||
[[ -s $watch_pid_file && $(wc -l <"$watch_log") -ge 2 ]] && break
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
watch_pid=$(<"$watch_pid_file")
|
||||
mapfile -t watch_child_pids < <(awk '{print $1}' "$watch_log")
|
||||
[[ -n $watch_pid && ${#watch_child_pids[@]} -ge 2 ]] || fail "clipboard watch helper starts text and image watchers"
|
||||
PIDS_TO_KILL+=("$watch_pid" "${watch_child_pids[@]}")
|
||||
|
||||
kill "$owner_pid" 2>/dev/null || true
|
||||
process_gone "$watch_pid" || fail "clipboard watch helper exits when its owner exits"
|
||||
for child_pid in "${watch_child_pids[@]}"; do
|
||||
process_gone "$child_pid" || fail "clipboard watch helper stops child wl-paste watchers"
|
||||
done
|
||||
pass "clipboard watch helper cleans up when its owner exits"
|
||||
|
||||
jq -n --arg text "$(printf 'large block line 1\nlarge block line 2\n')" '[{type:"text", text:"ignored"}, {type:"text", text:$text}]' >"$TMPDIR/home/.local/state/omarchy/clipboard-history.json"
|
||||
|
||||
WL_COPY_OUT="$TMPDIR/copied" WTYPE_OUT="$TMPDIR/wtype" HOME="$TMPDIR/home" PATH="$TMPDIR/bin:$PATH" \
|
||||
|
||||
Reference in New Issue
Block a user