commit d938354148163f549dcdd92759832d686d276d88
parent 09c32b025ea3e4e3d7b0d949035b4f104f7e10e8
Author: Chris Down <chris@chrisdown.name>
Date: Fri, 17 Feb 2017 12:07:05 -0500
Merge branch 'release/3.0.0'
Diffstat:
8 files changed, 245 insertions(+), 116 deletions(-)
diff --git a/.travis.yml b/.travis.yml
@@ -8,6 +8,7 @@ before_script:
script:
- shellcheck -s bash clipmenu clipmenud
+ - tests/test-clipmenu
matrix:
fast_finish: true
diff --git a/README.md b/README.md
@@ -1,11 +1,38 @@
clipmenu is a simple clipboard manager using [dmenu][] and [xsel][].
-To use it, start the `clipmenud` daemon, and then call `clipmenu` to launch
-`dmenu`. Upon choosing an entry, it is copied to the clipboard.
+# Usage
+
+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
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/
[xsel]: http://www.vergenet.net/~conrad/software/xsel/
diff --git a/clipmenu b/clipmenu
@@ -1,15 +1,17 @@
#!/bin/bash
+major_version=3
+
shopt -s nullglob
-cache_dir=/tmp/clipmenu.$USER
+cache_dir=/tmp/clipmenu.$major_version.$USER
cache_file=$cache_dir/line_cache
# 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
# argument to override an earlier one. That is, if the user passes in `-l`, our
# 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
@@ -22,9 +24,5 @@ if ! [[ -f "$file" ]]; then
fi
for selection in clipboard primary; do
- if type -p xsel >/dev/null 2>&1; then
- xsel --logfile /dev/null -i --"$selection" < "$file"
- else
- xclip -sel "$selection" < "$file"
- fi
+ xsel --logfile /dev/null -i --"$selection" < "$file"
done
diff --git a/clipmenud b/clipmenud
@@ -1,5 +1,9 @@
#!/bin/bash
+major_version=3
+cache_dir=/tmp/clipmenu.$major_version.$USER/
+cache_file=$cache_dir/line_cache
+
get_first_line() {
# Args:
# - $1, the file or data
@@ -37,9 +41,6 @@ debug() {
fi
}
-cache_dir=/tmp/clipmenu.$USER/
-cache_file=$cache_dir/line_cache
-
# It's ok that this only applies to the final directory.
# shellcheck disable=SC2174
mkdir -p -m0700 "$cache_dir"
@@ -48,15 +49,8 @@ declare -A last_data
declare -A last_filename
while sleep "${CLIPMENUD_SLEEP:-0.5}"; do
-
for selection in clipboard primary; do
- if type -p xsel >/dev/null 2>&1; then
- debug 'Using xsel'
- data=$(xsel --logfile /dev/null -o --"$selection"; printf x)
- else
- debug 'Using xclip'
- data=$(xclip -o -sel "$selection"; printf x)
- fi
+ data=$(xsel --logfile /dev/null -o --"$selection"; printf x)
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
# https://github.com/cdown/clipmenu/issues/34 requires knowing if
# we would skip first.
- if type -p xsel >/dev/null 2>&1; then
- xsel --logfile /dev/null -o --"$selection" | xsel -i --"$selection"
- else
- xclip -o -sel "$selection" | xclip -i -sel "$selection"
- fi
+ xsel --logfile /dev/null -o --"$selection" | xsel -i --"$selection"
fi
done
done
diff --git a/init/clipmenud.service b/init/clipmenud.service
@@ -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
diff --git a/test/test-perf b/test/test-perf
@@ -1,90 +0,0 @@
-#!/bin/bash
-
-msg() {
- printf '>>> %s\n' "$@" >&2
-}
-
-dir=/tmp/clipmenu.$USER
-cache_file=$dir/line_cache
-
-log=$(mktemp)
-tim=$(mktemp)
-clipmenu_shim=$(mktemp)
-num_files=1500
-
-trap 'rm -f -- "$log" "$tim" "$clipmenu_shim"' EXIT
-
-if [[ $0 == /* ]]; then
- location=${0%/*}
-else
- location=$PWD/${0#./}
- location=${location%/*}
-fi
-
-msg 'Setting up edited clipmenu'
-
-cat - "$location/../clipmenu" > /tmp/clipmenu << EOF
-#!/bin/bash
-
-exec 3>&2 2> >(tee "$log" |
- sed -u 's/^.*$/now/' |
- date -f - +%s.%N > "$tim")
-set -x
-
-shopt -s expand_aliases
-
-alias dmenu=:
-alias xsel=:
-alias xclip=:
-
-EOF
-
-chmod a+x /tmp/clipmenu
-
-if ! (( NO_RECREATE )); then
- rm -rf "$dir"
- mkdir -p "$dir"
-
- msg "Writing $num_files clipboard files"
-
- for (( i = 0; i <= num_files; i++ )); do
- (( i % 100 )) || printf '%s... ' "$i"
-
- line_len=$(( (RANDOM % 10000) + 1 ))
- num_lines=$(( (RANDOM % 10) + 1 ))
- data=$(
- tr -dc 'a-zA-Z0-9' < /dev/urandom |
- fold -w "$line_len" |
- head -"$num_lines"
- )
- read -r first_line_raw <<< "$data"
- printf -v first_line '%s (%s lines)\n' "$first_line_raw" "$num_lines"
- printf '%s' "$first_line" >> "$cache_file"
- fn=$dir/$(cksum <<< "$first_line")
- printf '%s' "$data" > "$fn"
- done
-
- printf 'done\n'
-else
- msg 'Not nuking/creating new clipmenu files'
-fi
-
-msg 'Running modified clipmenu'
-
-time /tmp/clipmenu
-
-(( TIME_ONLY )) && exit 0
-
-msg 'Displaying perf data'
-
-# modified from http://stackoverflow.com/a/20855353/945780
-paste <(
- while read -r tim ;do
- [ -z "$last" ] && last=${tim//.} && first=${tim//.}
- crt=000000000$((${tim//.}-10#0$last))
- ctot=000000000$((${tim//.}-10#0$first))
- printf "%12.9f %12.9f\n" ${crt:0:${#crt}-9}.${crt:${#crt}-9} \
- ${ctot:0:${#ctot}-9}.${ctot:${#ctot}-9}
- last=${tim//.}
- done < "$tim"
-) "$log" | less
diff --git a/tests/test-clipmenu b/tests/test-clipmenu
@@ -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"
diff --git a/tests/test-perf b/tests/test-perf
@@ -0,0 +1,91 @@
+#!/bin/bash
+
+major_version=3
+
+msg() {
+ printf '>>> %s\n' "$@" >&2
+}
+
+dir=/tmp/clipmenu.$major_version.$USER
+cache_file=$dir/line_cache
+
+log=$(mktemp)
+tim=$(mktemp)
+clipmenu_shim=$(mktemp)
+num_files=1500
+
+trap 'rm -f -- "$log" "$tim" "$clipmenu_shim"' EXIT
+
+if [[ $0 == /* ]]; then
+ location=${0%/*}
+else
+ location=$PWD/${0#./}
+ location=${location%/*}
+fi
+
+msg 'Setting up edited clipmenu'
+
+cat - "$location/../clipmenu" > /tmp/clipmenu << EOF
+#!/bin/bash
+
+exec 3>&2 2> >(tee "$log" |
+ sed -u 's/^.*$/now/' |
+ date -f - +%s.%N > "$tim")
+set -x
+
+shopt -s expand_aliases
+
+alias dmenu=:
+alias xsel=:
+
+EOF
+
+chmod a+x /tmp/clipmenu
+
+if ! (( NO_RECREATE )); then
+ rm -rf "$dir"
+ mkdir -p "$dir"
+
+ msg "Writing $num_files clipboard files"
+
+ for (( i = 0; i <= num_files; i++ )); do
+ (( i % 100 )) || printf '%s... ' "$i"
+
+ line_len=$(( (RANDOM % 10000) + 1 ))
+ num_lines=$(( (RANDOM % 10) + 1 ))
+ data=$(
+ tr -dc 'a-zA-Z0-9' < /dev/urandom |
+ fold -w "$line_len" |
+ head -"$num_lines"
+ )
+ read -r first_line_raw <<< "$data"
+ printf -v first_line '%s (%s lines)\n' "$first_line_raw" "$num_lines"
+ printf '%s' "$first_line" >> "$cache_file"
+ fn=$dir/$(cksum <<< "$first_line")
+ printf '%s' "$data" > "$fn"
+ done
+
+ printf 'done\n'
+else
+ msg 'Not nuking/creating new clipmenu files'
+fi
+
+msg 'Running modified clipmenu'
+
+time /tmp/clipmenu
+
+(( TIME_ONLY )) && exit 0
+
+msg 'Displaying perf data'
+
+# modified from http://stackoverflow.com/a/20855353/945780
+paste <(
+ while read -r tim ;do
+ [ -z "$last" ] && last=${tim//.} && first=${tim//.}
+ crt=000000000$((${tim//.}-10#0$last))
+ ctot=000000000$((${tim//.}-10#0$first))
+ printf "%12.9f %12.9f\n" ${crt:0:${#crt}-9}.${crt:${#crt}-9} \
+ ${ctot:0:${#ctot}-9}.${ctot:${#ctot}-9}
+ last=${tim//.}
+ done < "$tim"
+) "$log" | less