rofi/scripts/rofi-clip 4.2 K raw
1
#!/bin/sh
2
# Requires: rofi, xclip, rofi-clipd running; xdotool for the paste step
3
# Clipboard history picker. Runs as a rofi script mode so each row can carry
4
# its entry's hash out of band — the visible text is a one-line preview, but
5
# what gets copied is the full original, newlines and all.
6
#   Enter          paste the entry into the window rofi was opened over
7
#   Shift+Delete   forget that entry
8
# The entry goes to the clipboard either way, so a manual Ctrl+V still works
9
# when the synthetic keystroke does not suit the app underneath.
10
# Called with no arguments it launches the rofi that then calls it back, so
11
# the keybinding stays a single path.
12
set -u
13
14
DIR="${XDG_CACHE_HOME:-$HOME/.cache}/rofi-clipboard"
15
STORE="$DIR/store"
16
INDEX="$DIR/index"
17
18
if [ -z "${ROFI_RETV:-}" ]; then
19
    exec rofi -show clipboard -modes "clipboard:$0"
20
fi
21
22
# Row options ride behind a NUL, with 0x1f between key and value. NUL cannot
23
# survive a shell variable, so it has to come from the format string itself.
24
US=$(printf '\037')
25
26
list() {
27
    if [ ! -s "$INDEX" ]; then
28
        printf '\0message%sClipboard history is empty — is rofi-clipd running?\n' "$US"
29
        return
30
    fi
31
    printf '\0prompt%sclipboard\n' "$US"
32
    # keep-selection stops the cursor jumping back to the top after a delete.
33
    printf '\0keep-selection%strue\n' "$US"
34
    while IFS='	' read -r hash preview; do
35
        printf '%s\0info%s%s\n' "$preview" "$US" "$hash"
36
    done < "$INDEX"
37
}
38
39
# Type the paste keystroke into whatever the window manager focuses once rofi
40
# is gone. It cannot be sent from here directly: rofi holds a keyboard grab
41
# for as long as it is up, and XTEST events go to the grab holder, so this
42
# runs detached and waits the grab out. Its stdout goes to /dev/null so it
43
# cannot hold that pipe open either — see the note on xclip below.
44
paste_into_focus() {
45
    if ! command -v xdotool >/dev/null 2>&1; then
46
        notify-send "Clipboard" "Copied — install xdotool to paste automatically." \
47
            >/dev/null 2>&1
48
        return 0
49
    fi
50
    (
51
        # Up to three seconds of waiting for focus to land somewhere that is
52
        # not rofi; if it never does, paste nowhere rather than into rofi.
53
        target=""
54
        i=0
55
        while [ "$i" -lt 60 ]; do
56
            class=$(xdotool getactivewindow getwindowclassname 2>/dev/null) || class=""
57
            case $class in
58
                ""|[Rr]ofi) ;;
59
                *) target=$class; break ;;
60
            esac
61
            sleep 0.05
62
            i=$((i + 1))
63
        done
64
        [ -n "$target" ] || exit 0
65
66
        # Terminals keep Ctrl+V for the readline binding and put paste behind
67
        # Shift; everything else takes the plain one.
68
        case $target in
69
            org.wezfurlong.wezterm|Alacritty|com.mitchellh.ghostty|kitty|foot|Foot)
70
                key=ctrl+shift+v ;;
71
            *)  key=ctrl+v ;;
72
        esac
73
74
        # Let the newly focused window settle before typing at it.
75
        sleep 0.05
76
        xdotool key --clearmodifiers "$key"
77
    ) >/dev/null 2>&1 &
78
}
79
80
case "$ROFI_RETV" in
81
    0) list ;;
82
    1)
83
        hash=${ROFI_INFO:-}
84
        [ -n "$hash" ] && [ -f "$STORE/$hash" ] || exit 0
85
        # xclip forks and holds the selection until something else claims it,
86
        # so it has to outlive this script — which it does on its own. Its
87
        # stdout has to go, though: it inherits the pipe rofi reads this script
88
        # on, and rofi waits for EOF there before closing and dropping its
89
        # keyboard grab. That EOF never comes while xclip holds the pipe open,
90
        # and a hung rofi still holding the grab looks like a frozen desktop.
91
        xclip -selection clipboard -i "$STORE/$hash" >/dev/null 2>&1
92
        paste_into_focus
93
        ;;
94
    2)
95
        # Typed text with no match: copy what was typed, it is the one thing
96
        # the user could have meant.
97
        if [ -n "${1:-}" ]; then
98
            printf '%s' "$1" | xclip -selection clipboard -i >/dev/null 2>&1
99
            paste_into_focus
100
        fi
101
        ;;
102
    3)
103
        hash=${ROFI_INFO:-}
104
        if [ -n "$hash" ]; then
105
            grep -v "^$hash	" "$INDEX" > "$INDEX.$$" 2>/dev/null
106
            mv "$INDEX.$$" "$INDEX"
107
            rm -f "$STORE/$hash"
108
        fi
109
        list
110
        ;;
111
esac