#!/usr/bin/env bash # clipboard-check.sh -- diagnose "copy did nothing" on sway/Wayland. # READ-ONLY: lists selection metadata and reads text; never writes the clipboard, # never runs wl-copy, never restarts anything. # ./clipboard-check.sh -> status (what is configured right now) # ./clipboard-check.sh test -> 20s window: copy in Firefox, then tell me who failed set -uo pipefail own_sig() { # identify the owner from its advertised type list local t t=$(wl-paste -l 2>&1); [[ -z "$t" ]] && { echo "(clipboard empty / no owner)"; return; } case "$t" in *x-moz-url-priv*|*text/html*) echo "Firefox (has moz/html types)" ;; *glfw+clipboard*) echo "kitty/GLFW terminal (OSC 52 or copy_on_select)" ;; *image/png*) echo "image (screenshot? wl-copy -b)" ;; *) echo "some other client: $(echo "$t" | tr '\n' ' ')" ;; esac } if [[ "${1:-status}" == test ]]; then echo "BEFORE owner: $(own_sig)" echo echo " >> Copy some text in Firefox NOW (Ctrl+C), then press Enter." read -r _ echo "AFTER owner: $(own_sig)" echo if wl-paste -l >/dev/null 2>&1; then n=$(wl-paste 2>/dev/null | wc -c) echo " text available: $n byte(s)" [[ $n -gt 0 ]] && echo " preview: $(wl-paste 2>/dev/null | head -c 70 | tr '\n' ' ')" echo echo " VERDICT: the compositor HAS the copy -> the loss is on the paste side" echo " (stale PRIMARY for middle-click, or a clipboard-history keybind" echo " that no longer exists since clipman was removed)." else echo " VERDICT: the compositor has NOTHING -> Firefox never took ownership." echo " Usual causes, in order of likelihood:" echo " 1) Firefox lacked keyboard focus at Ctrl+C -- Wayland only" echo " accepts set_selection from the focused surface (a PiP window," echo " a toast, or a workspace switch stealing focus is enough)." echo " 2) The page's own JS intercepted the copy event (sites that" echo " block copying). dom.event.clipboardevents.enabled=true by" echo " default, so page scripts may do this." echo " 3) The offer got cancelled mid-transfer ('selection cancelled'" echo " in sway's log) -- see: journalctl -b | grep 'selection cancelled'" fi exit 0 fi echo "== components ==" printf ' %-22s %s\n' "sway" "$(swaymsg --version 2>&1 | head -1)" printf ' %-22s %s\n' "wl-clipboard" "$(wl-copy --version 2>&1 | head -1)" printf ' %-22s %s\n' "firefox" "$(firefox --version 2>&1 | head -1) [$(pacman -Q firefox 2>/dev/null || echo '?')]" printf ' %-22s %s\n' "cliphist" "$(command -v cliphist || echo 'NOT INSTALLED (history keybinds are dead)')" echo echo "== clipboard managers / watchers (a watcher that dies mid-copy LOSES the copy) ==" m=$(pgrep -af 'wl-paste|cliphist|clipman|copyq|klipper|parcellite|flameshot' 2>/dev/null || true) [[ -n "$m" ]] && echo "$m" | sed 's/^/ RUNNING /' || echo " none running <- no history is being saved, but also nothing can steal a copy" echo echo "== XWayland mixing (X11<->Wayland clipboard skew is a classic cause) ==" if pgrep -x Xwayland >/dev/null 2>&1; then echo " Xwayland IS running -> any X11 app can own the selection out-of-band:" pgrep -a Xwayland | sed 's/^/ /' else echo " no Xwayland -> pure Wayland, that whole failure class is ruled out" fi echo echo "== window shells (a copy from an X11-surfaced Firefox behaves differently) ==" swaymsg -t get_tree 2>/dev/null | python3 -c " import json,sys def walk(n): yield n for k in ('nodes','floating_nodes'): for c in n.get(k) or []: yield from walk(c) for n in walk(json.load(sys.stdin)): if n.get('pid') and n.get('type') in ('con','floating_con'): shell = 'x11' if n.get('window') is not None else 'wayland' wp = n.get('window_properties') or {} ident = str(n.get('app_id') or wp.get('class'))[:24] print(' %-8s %-26s %s' % (shell, ident, (n.get('name') or '')[:38])) " 2>&1 | sort -u | head -12 echo echo "== current selections (read-only) ==" printf ' CLIPBOARD owner : %s\n' "$(own_sig)" printf ' PRIMARY types : %s\n' "$(wl-paste -p -l 2>&1 | tr '\n' ' ' | cut -c1-100)" echo echo "== Firefox clipboard prefs (blank = default) ==" P=$(ls ~/.mozilla/firefox/*/prefs.js 2>/dev/null | head -1) grep -hE 'dom\.event\.clipboardevents|middlepaste|widget\.wayland' "$P" 2>/dev/null | sed 's/^/ /' || true grep -hE 'user_pref' "$(dirname "$P")/user.js" 2>/dev/null | grep -iE 'clipboard|wayland' | sed 's/^/ user.js: /' || true echo echo "== past cancellations sway logged (each one = a copy that vanished) ==" journalctl -b 0 --no-pager 2>/dev/null | grep -ic 'selection cancelled' | sed 's/^/ '\''selection cancelled'\'' events this boot: /' echo echo "Run '$0 test' to A/B an actual Firefox copy."