Merge branch 'release/3.0.0'
This commit is contained in:
commit
d938354148
|
@ -8,6 +8,7 @@ before_script:
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- shellcheck -s bash clipmenu clipmenud
|
- shellcheck -s bash clipmenu clipmenud
|
||||||
|
- tests/test-clipmenu
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
|
|
33
README.md
33
README.md
|
@ -1,11 +1,38 @@
|
||||||
clipmenu is a simple clipboard manager using [dmenu][] and [xsel][].
|
clipmenu is a simple clipboard manager using [dmenu][] and [xsel][].
|
||||||
|
|
||||||
To use it, start the `clipmenud` daemon, and then call `clipmenu` to launch
|
# Usage
|
||||||
`dmenu`. Upon choosing an entry, it is copied to the clipboard.
|
|
||||||
|
Start `clipmenud`, then run `clipmenu` to select something to put on the
|
||||||
|
clipboard.
|
||||||
|
|
||||||
|
A systemd user service for starting clipmenud is included at
|
||||||
|
[init/clipmenud.service](https://github.com/cdown/clipmenu/blob/develop/init/clipmenud.service).
|
||||||
|
|
||||||
All args passed to clipmenu are transparently dispatched to dmenu. That is, if
|
All args passed to clipmenu are transparently dispatched to dmenu. That is, if
|
||||||
you usually call dmenu with args to set colours and other properties, you can
|
you usually call dmenu with args to set colours and other properties, you can
|
||||||
invoke clipmenu in exactly the same way to get the same effect.
|
invoke clipmenu in exactly the same way to get the same effect, like so:
|
||||||
|
|
||||||
|
clipmenu -i -fn Terminus:size=8 -nb '#002b36' -nf '#839496' -sb '#073642' -sf '#93a1a1'
|
||||||
|
|
||||||
|
# How does it work?
|
||||||
|
|
||||||
|
The code is fairly simple and easy to follow, you may find it easier to read
|
||||||
|
there, but it basically works like this:
|
||||||
|
|
||||||
|
## clipmenud
|
||||||
|
|
||||||
|
1. `clipmenud` polls the clipboard every 0.5 seconds (or another interval as
|
||||||
|
configured with the `CLIPMENUD_SLEEP` environment variable). Unfortunately
|
||||||
|
there's no interface to subscribe for changes in X11, so we must poll.
|
||||||
|
2. If `clipmenud` detects changes to the clipboard contents, it writes them out
|
||||||
|
to the cache directory.
|
||||||
|
|
||||||
|
## clipmenu
|
||||||
|
|
||||||
|
1. `clipmenu` reads the cache directory to find all available clips.
|
||||||
|
2. `dmenu` is executed to allow the user to select a clip.
|
||||||
|
3. After selection, the clip is put onto the PRIMARY and CLIPBOARD X
|
||||||
|
selections.
|
||||||
|
|
||||||
[dmenu]: http://tools.suckless.org/dmenu/
|
[dmenu]: http://tools.suckless.org/dmenu/
|
||||||
[xsel]: http://www.vergenet.net/~conrad/software/xsel/
|
[xsel]: http://www.vergenet.net/~conrad/software/xsel/
|
||||||
|
|
12
clipmenu
12
clipmenu
|
@ -1,15 +1,17 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
major_version=3
|
||||||
|
|
||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
|
|
||||||
cache_dir=/tmp/clipmenu.$USER
|
cache_dir=/tmp/clipmenu.$major_version.$USER
|
||||||
cache_file=$cache_dir/line_cache
|
cache_file=$cache_dir/line_cache
|
||||||
|
|
||||||
# It's okay to hardcode `-l 8` here as a sensible default without checking
|
# It's okay to hardcode `-l 8` here as a sensible default without checking
|
||||||
# whether `-l` is also in "$@", because the way that dmenu works allows a later
|
# whether `-l` is also in "$@", because the way that dmenu works allows a later
|
||||||
# argument to override an earlier one. That is, if the user passes in `-l`, our
|
# argument to override an earlier one. That is, if the user passes in `-l`, our
|
||||||
# one will be ignored.
|
# one will be ignored.
|
||||||
chosen_line=$(tac "$cache_file" | uniq | dmenu -l 8 "$@")
|
chosen_line=$(tac "$cache_file" | awk '!seen[$0]++' | dmenu -l 8 "$@")
|
||||||
|
|
||||||
[[ $chosen_line ]] || exit 1
|
[[ $chosen_line ]] || exit 1
|
||||||
|
|
||||||
|
@ -22,9 +24,5 @@ if ! [[ -f "$file" ]]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for selection in clipboard primary; do
|
for selection in clipboard primary; do
|
||||||
if type -p xsel >/dev/null 2>&1; then
|
xsel --logfile /dev/null -i --"$selection" < "$file"
|
||||||
xsel --logfile /dev/null -i --"$selection" < "$file"
|
|
||||||
else
|
|
||||||
xclip -sel "$selection" < "$file"
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
|
|
22
clipmenud
22
clipmenud
|
@ -1,5 +1,9 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
major_version=3
|
||||||
|
cache_dir=/tmp/clipmenu.$major_version.$USER/
|
||||||
|
cache_file=$cache_dir/line_cache
|
||||||
|
|
||||||
get_first_line() {
|
get_first_line() {
|
||||||
# Args:
|
# Args:
|
||||||
# - $1, the file or data
|
# - $1, the file or data
|
||||||
|
@ -37,9 +41,6 @@ debug() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
cache_dir=/tmp/clipmenu.$USER/
|
|
||||||
cache_file=$cache_dir/line_cache
|
|
||||||
|
|
||||||
# It's ok that this only applies to the final directory.
|
# It's ok that this only applies to the final directory.
|
||||||
# shellcheck disable=SC2174
|
# shellcheck disable=SC2174
|
||||||
mkdir -p -m0700 "$cache_dir"
|
mkdir -p -m0700 "$cache_dir"
|
||||||
|
@ -48,15 +49,8 @@ declare -A last_data
|
||||||
declare -A last_filename
|
declare -A last_filename
|
||||||
|
|
||||||
while sleep "${CLIPMENUD_SLEEP:-0.5}"; do
|
while sleep "${CLIPMENUD_SLEEP:-0.5}"; do
|
||||||
|
|
||||||
for selection in clipboard primary; do
|
for selection in clipboard primary; do
|
||||||
if type -p xsel >/dev/null 2>&1; then
|
data=$(xsel --logfile /dev/null -o --"$selection"; printf x)
|
||||||
debug 'Using xsel'
|
|
||||||
data=$(xsel --logfile /dev/null -o --"$selection"; printf x)
|
|
||||||
else
|
|
||||||
debug 'Using xclip'
|
|
||||||
data=$(xclip -o -sel "$selection"; printf x)
|
|
||||||
fi
|
|
||||||
|
|
||||||
debug "Data before stripping: $data"
|
debug "Data before stripping: $data"
|
||||||
|
|
||||||
|
@ -109,11 +103,7 @@ while sleep "${CLIPMENUD_SLEEP:-0.5}"; do
|
||||||
# We can't colocate this with the above copying code because
|
# We can't colocate this with the above copying code because
|
||||||
# https://github.com/cdown/clipmenu/issues/34 requires knowing if
|
# https://github.com/cdown/clipmenu/issues/34 requires knowing if
|
||||||
# we would skip first.
|
# we would skip first.
|
||||||
if type -p xsel >/dev/null 2>&1; then
|
xsel --logfile /dev/null -o --"$selection" | xsel -i --"$selection"
|
||||||
xsel --logfile /dev/null -o --"$selection" | xsel -i --"$selection"
|
|
||||||
else
|
|
||||||
xclip -o -sel "$selection" | xclip -i -sel "$selection"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
31
init/clipmenud.service
Normal file
31
init/clipmenud.service
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Clipmenu daemon
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/bin/clipmenud
|
||||||
|
Restart=always
|
||||||
|
RestartSec=0
|
||||||
|
Environment=DISPLAY=:0
|
||||||
|
|
||||||
|
SystemCallFilter=@basic-io @default @io-event @ipc @network-io @process \
|
||||||
|
brk fadvise64 getegid geteuid getgid getgroups getpgrp \
|
||||||
|
getpid getppid getrlimit getuid ioctl mprotect rt_sigaction \
|
||||||
|
rt_sigprocmask setitimer setsid sysinfo umask uname wait4
|
||||||
|
|
||||||
|
# @file-system will handle this once v233 is released, see
|
||||||
|
# http://bit.ly/2l1r8Ah for more details.
|
||||||
|
SystemCallFilter=access chdir close faccessat fcntl fstat getcwd mkdir mmap \
|
||||||
|
munmap open stat statfs unlink
|
||||||
|
|
||||||
|
MemoryDenyWriteExecute=yes
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
ProtectControlGroups=yes
|
||||||
|
ProtectKernelTunables=yes
|
||||||
|
RestrictAddressFamilies=
|
||||||
|
RestrictRealtime=yes
|
||||||
|
|
||||||
|
ProtectSystem=strict
|
||||||
|
ReadWritePaths=/tmp
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
81
tests/test-clipmenu
Executable file
81
tests/test-clipmenu
Executable file
|
@ -0,0 +1,81 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -x
|
||||||
|
set -e
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
major_version=3
|
||||||
|
dir=/tmp/clipmenu.$major_version.$USER
|
||||||
|
cache_file=$dir/line_cache
|
||||||
|
|
||||||
|
if [[ $0 == /* ]]; then
|
||||||
|
location=${0%/*}
|
||||||
|
else
|
||||||
|
location=$PWD/${0#./}
|
||||||
|
location=${location%/*}
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat - "$location/../clipmenu" > /tmp/clipmenu << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
shopt -s expand_aliases
|
||||||
|
|
||||||
|
shim() {
|
||||||
|
printf '%s args:' "$1" >&2
|
||||||
|
printf ' %q' "${@:2}" >&2
|
||||||
|
printf '\n' >&2
|
||||||
|
|
||||||
|
i=0
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
let i++
|
||||||
|
printf '%s line %d stdin: %s\n' "$1" "$i" "$line" >&2
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -v SHIM_STDOUT ]]; then
|
||||||
|
printf '%s\n' "$SHIM_STDOUT"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
alias dmenu='SHIM_STDOUT="Selected text. (2 lines)" shim dmenu'
|
||||||
|
alias xsel='shim xsel'
|
||||||
|
alias xclip='shim xclip'
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod a+x /tmp/clipmenu
|
||||||
|
|
||||||
|
rm -rf "$dir"
|
||||||
|
mkdir -p "$dir"
|
||||||
|
|
||||||
|
cat > "$cache_file" << 'EOF'
|
||||||
|
Selected text. (2 lines)
|
||||||
|
Selected text 2. (2 lines)
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > "$dir/$(cksum <<< 'Selected text. (2 lines)')" << 'EOF'
|
||||||
|
Selected text.
|
||||||
|
Yes, it's selected text.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
### TESTS ###
|
||||||
|
|
||||||
|
output=$(/tmp/clipmenu --foo bar 2>&1)
|
||||||
|
|
||||||
|
temp=$(mktemp)
|
||||||
|
trap 'rm -f -- "$temp"' EXIT
|
||||||
|
|
||||||
|
printf '%s\n' "$output" > "$temp"
|
||||||
|
|
||||||
|
# Arguments are transparently passed to dmenu
|
||||||
|
grep -Fxq 'dmenu args: -l 8 --foo bar' "$temp"
|
||||||
|
|
||||||
|
# Output from cache file should get to dmenu, reversed
|
||||||
|
grep -Fxq 'dmenu line 1 stdin: Selected text 2. (2 lines)' "$temp"
|
||||||
|
grep -Fxq 'dmenu line 2 stdin: Selected text. (2 lines)' "$temp"
|
||||||
|
|
||||||
|
# xsel should copy both to clipboard *and* primary
|
||||||
|
grep -Fxq 'xsel args: --logfile /dev/null -i --clipboard' "$temp"
|
||||||
|
grep -Fxq 'xsel args: --logfile /dev/null -i --primary' "$temp"
|
||||||
|
|
||||||
|
grep -Fxq 'xsel line 1 stdin: Selected text.' "$temp"
|
||||||
|
grep -Fxq "xsel line 2 stdin: Yes, it's selected text." "$temp"
|
|
@ -1,10 +1,12 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
major_version=3
|
||||||
|
|
||||||
msg() {
|
msg() {
|
||||||
printf '>>> %s\n' "$@" >&2
|
printf '>>> %s\n' "$@" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
dir=/tmp/clipmenu.$USER
|
dir=/tmp/clipmenu.$major_version.$USER
|
||||||
cache_file=$dir/line_cache
|
cache_file=$dir/line_cache
|
||||||
|
|
||||||
log=$(mktemp)
|
log=$(mktemp)
|
||||||
|
@ -35,7 +37,6 @@ shopt -s expand_aliases
|
||||||
|
|
||||||
alias dmenu=:
|
alias dmenu=:
|
||||||
alias xsel=:
|
alias xsel=:
|
||||||
alias xclip=:
|
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
|
|
Loading…
Reference in a new issue