1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
#!/bin/sh
# Scan for wlroots and x11 primary selection and clipboard events and synchronize between them
# Requirements:
# clipnotify https://github.com/cdown/clipnotify (xfixes clipboard events)
# wl-clipboard https://github.com/bugaevc/wl-clipboard
# xclip https://github.com/astrand/xclip
# sha256sum coreutils
# multi-TARGET/mimetype negotiation requires a more complicated protocol
# bridge than what xclip and wl-clipboard can be expected to provide.
# To see the scope of the problem for a given selection:
# xclip -o -t TARGETS
# wl-paste --list-types
# Instead, we naively prioritize top-to-bottom from the following list that
# you're welcome to modify to suit your needs:
TYPEPRIORITY="
image/png
image/tiff
text/plain;charset=utf-8
UTF8_STRING
text/plain
STRING
"
set -e
debug() {
prevexit="$?";
prevcmd="$cmd";
cmd="$BASH_COMMAND";
if [ "$prevexit" -eq 0 ]; then
printf "\033[0;32m%i %s\033[0m\n" "$prevexit" "$prevcmd" 1>&2
else
printf "\033[0;31m%i %s\033[0m\n" "$prevexit" "$prevcmd" 1>&2
fi
}
#set -T; trap 'debug' DEBUG # debug, Bash-only
set -x # debug, POSIX
#PS4='+ $(sleep .1)' # debug with aggressive serialization
# "list targets/types" commands
wpt() { wl-paste --primary --list-types; };
wct() { wl-paste --list-types; };
xpt() { xclip -o -selection primary -t TARGETS || true; }; # see xclip note at bottom
xct() { xclip -o -selection clipboard -t TARGETS || true; };
# "paste from" commands
# note: binary data must go through STDIO; ARGV and ENV are both NUL-delimited
wpp() { wl-paste -n --primary -t "$TYPE"; }
wcp() { wl-paste -n -t "$TYPE"; }
xpp() { xclip -o -selection primary -t "$TYPE"; }
xcp() { xclip -o -selection clipboard -t "$TYPE"; }
# "copy into" commands
wpc() { wl-copy --primary -t "$TYPE"; }
wcc() { wl-copy -t "$TYPE"; }
xpc() { xclip -selection primary -t "$TYPE"; }
#xcc() { xclip -selection clipboard -t "$TYPE"; } # why is this broken?
xcc() { true; }
fifo="${XDG_RUNTIME_DIR:-/tmp}/clipsync-$WAYLAND_DISPLAY-$DISPLAY.sock"
mkfifo "$fifo" # unified event stream to prevent multi-process feedback loops
# ensure we kill our whole subprocess group on shutdown
trap "trap - TERM; rm $fifo; kill -- -$$;" INT TERM EXIT # rewrite trap so it doesn't recurse
exec 3<> "$fifo" # open as fd 3
#rm "$fifo" # we don't technically use the link anymore, but it *is* useful as lockfile
# Event generation commands
wl-paste --primary --watch echo wp >&3 & wppid=$!; read -r spam <&3; unset spam
wl-paste --watch echo wc >&3 & wcpid=$!; read -r spam <&3; unset spam
while clipnotify -s primary ; do echo xp; done >&3 & xppid=$!
#while clipnotify -s clipboard; do echo xc; done >&3 & xcpid=$! # why is this broken?
# Event processing
while read -r event <&3; do
case "$event" in
([wx][pc]) # wayland or x11, primary or clipboard
TYPES="$( ${event}t )" &&
if [ -n "$TYPES" ]; then # GIMP X11 GDK likes doing zero-length selections with no targets
for TYPE in $TYPEPRIORITY; do
if [ -z "${TYPES##*$TYPE*}" ]; then # matching type/target?
new="$( ${event}p | sha256sum )" && # new sum
[ x"$new" != x"$old" ] && # different from old sum?
for sink in wp wc xp xc; do
[ "$event" != "$sink" ] && # avoid sinking into the source
${event}p | ${sink}c
done
break; # always break on matched type/target
fi
done;
fi;;
(*) echo "ERROR: INVALID EVENT ON FD 3: $event"; exit 1; ;;
esac
old="$new"
done
# Notes:
# xclip returns 1 on a failure to return TARGETS (such as with -selection clipboard)...
# 000:<:000d: 24: Request(24): ConvertSelection requestor=0x00a00001 selection=0xf9("CLIPBOARD") target=0xf5("TARGETS") property=0x16a("XCLIP_OUT") time=CurrentTime(0x00000000)
# 000:>:000d: Event (generated) SelectionNotify(31) time=CurrentTime(0x00000000) requestor=0x00a00001 selection=0xf9("CLIPBOARD") target=0xf5("TARGETS") property=None(0x0)
# 000:<:000e: 20: Request(16): InternAtom only-if-exists=false(0x00) name='UTF8_STRING'
# 000:>:000e:32: Reply to InternAtom: atom=0x128("UTF8_STRING")
# Error: target TARGETS not available
# ...and also returns 1 on a failure to resolve what TARGETS were returned (such as with Twibright links2 text selections), regardless of how many successful resolutions have been printed...
# 000:<:000c: 24: Request(24): ConvertSelection requestor=0x00c00001 selection=0x1("PRIMARY") target=0xf5("TARGETS") property=0x16a("XCLIP_OUT") time=CurrentTime(0x00000000)
# 000:>:000c: Event (generated) SelectionNotify(31) time=CurrentTime(0x00000000) requestor=0x00a00001 selection=0x1("PRIMARY") target=0xf5("TARGETS") property=0x16a("XCLIP_OUT")
# 000:<:000d: 24: Request(20): GetProperty delete=false(0x00) window=0x00a00001 property=0x16a("XCLIP_OUT") type=any(0x0) long-offset=0x00000000 long-length=0x00000000
# 000:>:000d:32: Reply to GetProperty: type=0x4("ATOM") bytes-after=0x0000000c data=;
# 000:<:000e: 24: Request(20): GetProperty delete=false(0x00) window=0x00a00001 property=0x16a("XCLIP_OUT") type=any(0x0) long-offset=0x00000000 long-length=0x0000000c
# 000:>:000e:44: Reply to GetProperty: type=0x4("ATOM") bytes-after=0x00000000 data=0xf5("TARGETS"),0x128,0x7f5ad23c;
# 000:<:000f: 12: Request(19): DeleteProperty window=0x00a00001 property=0x16a("XCLIP_OUT")
# 000:<:0010: 20: Request(16): InternAtom only-if-exists=true(0x01) name='text/html'
# 000:>:0010: Event PropertyNotify(28) window=0x00a00001 atom=0x16a("XCLIP_OUT") time=0x019bbed6 state=Deleted(0x01)
# 000:>:0010:32: Reply to InternAtom: atom=0x19a("text/html")
# TARGETS
# 000:<:0011: 8: Request(17): GetAtomName atom=0x128(unrecognized atom)
# 000:>:0011:44: Reply to GetAtomName: name='UTF8_STRING'
# UTF8_STRING
# 000:<:0012: 8: Request(17): GetAtomName atom=0x7f5ad23c(unrecognized atom)
# 000:>:0012:Error 5=Atom: major=17, minor=0, bad=0x7f5ad23c, seq=0012
# ...which means we have to blindly try to use its output regardless of exit code.
# Repeatedly using "paste from" on remote X11 servers is pretty horrifying,
# but finding a secure spot to cache NUL-containing data is annoyingly
# system-specific, "od | while read do printf" as a workaround, meanwhile,
# is bafflingly slow. POSIX shell is just not good at this job.
# if further process management is needed:
#kill $wcpid $wppid $xppid $xcpid
#disown $wcpid $wppid $xppid $xcpid
|