* Bound clipboard capture reads * Bound the watched clipboard read and drop copies cut off at the deadline In watch mode capture.sh reads the copy from the owner's pipe on stdin, to EOF, with no bound. An owner that stalls without closing its end keeps the callback alive, and wl-paste --watch handles no further clipboard events until it exits. That matches #9443, whose stuck capture.sh had no wl-paste child: --list-types never reads from the owner. Every read now goes through one bounded reader into a temporary file, and the copy is recorded only when the read finished, so a snapshot that stalls halfway no longer records its first half. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Omarchybot <317366263+omarchybot@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
116 lines
3.5 KiB
Bash
Executable File
116 lines
3.5 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Captures the current clipboard as a JSON entry on stdout. In watch mode,
|
|
# wl-paste invokes this with the payload on stdin and the mime as $1. Without
|
|
# arguments, it snapshots the current selection itself.
|
|
|
|
set -o pipefail
|
|
|
|
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy"
|
|
IMAGE_DIR="$STATE_DIR/clipboard-images"
|
|
mkdir -p "$IMAGE_DIR"
|
|
|
|
types=$(timeout 2s wl-paste --list-types 2>/dev/null || true)
|
|
|
|
if [[ ${CLIPBOARD_STATE:-} == "sensitive" ]] || grep -qx 'x-kde-passwordManagerHint' <<<"$types"; then
|
|
exit 0
|
|
fi
|
|
|
|
tmp=
|
|
trap 'rm -f -- "$tmp"' EXIT
|
|
|
|
# The clipboard owner streams the copy and can stall without closing its end, so
|
|
# every read is bounded, and a copy cut off at the deadline is dropped, not kept.
|
|
read_copy() {
|
|
timeout 2s "$@" >"$tmp" 2>/dev/null && [[ -s $tmp ]]
|
|
}
|
|
|
|
emit_image() {
|
|
local mime="$1"
|
|
local ext hash file
|
|
shift
|
|
|
|
ext=${mime#image/}
|
|
[[ $ext == jpeg ]] && ext=jpg
|
|
|
|
tmp=$(mktemp --tmpdir="$IMAGE_DIR" clipboard.XXXXXX) || return 0
|
|
read_copy "$@" || return
|
|
|
|
hash=$(sha256sum "$tmp" | awk '{print $1}')
|
|
file="$IMAGE_DIR/$hash.$ext"
|
|
if [[ -e $file ]]; then
|
|
rm -f "$tmp"
|
|
else
|
|
mv "$tmp" "$file"
|
|
fi
|
|
|
|
jq -cn --arg mime "$mime" --arg path "$file" --arg captured_at "$(date +'%A %H:%M')" \
|
|
'{type:"image", mime:$mime, path:$path, capturedAt:$captured_at}'
|
|
}
|
|
|
|
emit_text() {
|
|
tmp=$(mktemp --tmpdir="$STATE_DIR" clipboard.XXXXXX) || return 0
|
|
read_copy "$@" || return
|
|
|
|
perl -MEncode=decode,FB_CROAK,LEAVE_SRC -MJSON::PP=encode_json -0777 -e '
|
|
my $raw = <STDIN>;
|
|
exit unless length $raw;
|
|
|
|
my $encoding;
|
|
my $heuristic_encoding = 0;
|
|
if ($raw =~ /^(?:\xFF\xFE|\xFE\xFF)/) {
|
|
$encoding = "UTF-16";
|
|
} elsif (length($raw) % 2 == 0 && index($raw, "\0") >= 0) {
|
|
my $units = length($raw) / 2;
|
|
my $nuls = $raw =~ tr/\0/\0/;
|
|
|
|
# Neither byte lane can reach the padding threshold when the entire
|
|
# payload contains fewer NULs than that, so avoid two full string passes.
|
|
if ($nuls * 4 >= $units * 3) {
|
|
my $even_bytes = $raw;
|
|
$even_bytes =~ s/(.)./$1/sg;
|
|
my $even_nuls = $even_bytes =~ tr/\0/\0/;
|
|
undef $even_bytes;
|
|
|
|
my $odd_bytes = $raw;
|
|
$odd_bytes =~ s/.(.)/$1/sg;
|
|
my $odd_nuls = $odd_bytes =~ tr/\0/\0/;
|
|
|
|
# BOM-less UTF-16 is indistinguishable from NUL-separated bytes. Decode
|
|
# only when at least three quarters of the code units have consistent
|
|
# padding and fewer than one quarter have NULs in the opposite byte.
|
|
if ($odd_nuls * 4 >= $units * 3 && $even_nuls * 4 < $units) {
|
|
$encoding = "UTF-16LE";
|
|
$heuristic_encoding = 1;
|
|
} elsif ($even_nuls * 4 >= $units * 3 && $odd_nuls * 4 < $units) {
|
|
$encoding = "UTF-16BE";
|
|
$heuristic_encoding = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
my $text = $encoding ? eval { decode($encoding, $raw, FB_CROAK | LEAVE_SRC) } : undef;
|
|
if ($heuristic_encoding && defined($text) && $text =~ /[\x00-\x08\x0E-\x1A\x1C-\x1F]/) {
|
|
$text = undef;
|
|
}
|
|
$text = decode("UTF-8", $raw) unless defined $text;
|
|
print "{\"type\":\"text\",\"text\":", encode_json($text), "}\n";
|
|
' <"$tmp"
|
|
}
|
|
|
|
case "${1:-}" in
|
|
text) emit_text cat; exit 0 ;;
|
|
image/*) emit_image "$1" cat; exit 0 ;;
|
|
esac
|
|
|
|
for mime in image/png image/jpeg image/webp image/gif image/bmp image/tiff; do
|
|
if grep -qx "$mime" <<<"$types"; then
|
|
emit_image "$mime" wl-paste --type "$mime"
|
|
exit 0
|
|
fi
|
|
done
|
|
|
|
if grep -q '^text/' <<<"$types" || grep -qx 'UTF8_STRING' <<<"$types" || grep -qx 'STRING' <<<"$types"; then
|
|
emit_text wl-paste --type text --no-newline
|
|
fi
|