commit ef3102c5e9c8c927afc0c463f0bbe372a75a815b
parent 3ab7a6e2e7b4ebb18a432e7adee6a64bbbc825f9
Author: Chris Down <chris@chrisdown.name>
Date: Wed, 25 Oct 2017 01:58:03 +0100
Merge branch 'release/4.0.0'
Diffstat:
6 files changed, 80 insertions(+), 44 deletions(-)
diff --git a/README.md b/README.md
@@ -29,6 +29,11 @@ there, but it basically works like this:
1. `clipmenud` polls the clipboard every 0.5 seconds (or another interval as
configured with the `CM_SLEEP` environment variable). Unfortunately there's
no interface to subscribe for changes in X11, so we must poll.
+
+ Instead of polling, you can bind your copy key binding to also issue
+ `CM_ONESHOT=1 clipmenud`. However, there's no generic way to do this, since
+ any keys or mouse buttons could be bound to do this action in a number of
+ ways.
2. If `clipmenud` detects changes to the clipboard contents, it writes them out
to the cache directory.
diff --git a/clipmenu b/clipmenu
@@ -1,21 +1,17 @@
#!/bin/bash
-major_version=3
+: "${CM_LAUNCHER=dmenu}"
+: "${CM_DIR="${XDG_RUNTIME_DIR-"${TMPDIR-/tmp}"}"}"
+
+major_version=4
shopt -s nullglob
-cache_dir=/tmp/clipmenu.$major_version.$USER
+cache_dir=$CM_DIR/clipmenu.$major_version.$USER
cache_file=$cache_dir/line_cache
-: "${CM_LAUNCHER=dmenu}"
-
-if [[ "$CM_LAUNCHER" == rofi ]]; then
- # rofi supports dmenu-like arguments through the -dmenu flag
- set -- -dmenu "$@"
-fi
-
-if [[ $1 == --help ]]; then
- cat << EOF
+if [[ $1 == --help ]] || [[ $1 == -h ]]; then
+ cat << 'EOF'
clipmenu is a simple clipboard manager using dmenu and xsel. Launch this
when you want to select a clip.
@@ -23,11 +19,17 @@ All arguments are passed through to dmenu itself.
Environment variables:
-- \$CM_LAUNCHER: specify a dmenu-compatible launcher (default: dmenu)
+- $CM_LAUNCHER: specify a dmenu-compatible launcher (default: dmenu)
+- $CM_DIR: specify the base directory to store the cache dir in (default: $XDG_RUNTIME_DIR, $TMPDIR, or /tmp)
EOF
exit 0
fi
+if [[ "$CM_LAUNCHER" == rofi ]]; then
+ # rofi supports dmenu-like arguments through the -dmenu flag
+ set -- -dmenu "$@"
+fi
+
# 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
diff --git a/clipmenud b/clipmenud
@@ -1,14 +1,16 @@
#!/bin/bash
-major_version=3
-cache_dir=/tmp/clipmenu.$major_version.$USER/
-cache_file=$cache_dir/line_cache
-lock_file=$cache_dir/lock
-lock_timeout=2
-
: "${CM_ONESHOT=0}"
: "${CM_OWN_CLIPBOARD=1}"
: "${CM_DEBUG=0}"
+: "${CM_DIR="${XDG_RUNTIME_DIR-"${TMPDIR-/tmp}"}"}"
+: "${CM_MAX_CLIPS=1000}"
+
+major_version=4
+cache_dir=$CM_DIR/clipmenu.$major_version.$USER/
+cache_file=$cache_dir/line_cache
+lock_file=$cache_dir/lock
+lock_timeout=2
_xsel() {
timeout 1 xsel --logfile /dev/stderr "$@"
@@ -51,16 +53,32 @@ debug() {
fi
}
+if [[ $1 == --help ]] || [[ $1 == -h ]]; then
+ cat << 'EOF'
+clipmenud is the daemon that collects and caches what's on the clipboard.
+when you want to select a clip.
+
+Environment variables:
+
+- $CM_ONESHOT: run once immediately, do not loop (default: 0)
+- $CM_DEBUG: turn on debugging output (default: 0)
+- $CM_OWN_CLIPBOARD: take ownership of the clipboard (default: 1)
+- $CM_MAX_CLIPS: maximum number of clips to store, 0 for inf (default: 1000)
+- $CM_DIR: specify the base directory to store the cache dir in (default: $XDG_RUNTIME_DIR, $TMPDIR, or /tmp)
+EOF
+ exit 0
+fi
+
+
# It's ok that this only applies to the final directory.
# shellcheck disable=SC2174
mkdir -p -m0700 "$cache_dir"
declare -A last_data
-declare -A last_filename
exec {lock_fd}> "$lock_file"
-while sleep "${CM_SLEEP:-0.5}"; do
+while (( CM_ONESHOT )) || sleep "${CM_SLEEP:-0.5}"; do
if ! flock -x -w "$lock_timeout" "$lock_fd"; then
if (( CM_ONESHOT )); then
printf 'ERROR: %s\n' 'Timed out waiting for lock' >&2
@@ -94,29 +112,24 @@ while sleep "${CM_SLEEP:-0.5}"; do
continue
fi
- # If we were in the middle of doing a selection when the previous poll
- # ran, then we may have got a partial clip.
- possible_partial=${last_data[$selection]}
- if [[ $possible_partial && $data == "$possible_partial"* ]]; then
- debug "$possible_partial is a possible partial of $data"
- debug "Removing ${last_filename[$selection]}"
- rm -- "${last_filename[$selection]}"
- fi
-
last_data[$selection]=$data
- last_filename[$selection]=$filename
first_line=$(get_first_line "$data")
- printf 'New clipboard entry on %s selection: "%s"\n' \
- "$selection" "$first_line"
+ debug "New clipboard entry on $selection selection: \"$first_line\""
- filename="$cache_dir/$(cksum <<< "$first_line")"
- debug "Writing $data to $filename"
- printf '%s' "$data" > "$filename"
+ # Without checking ${last_data[any]}, we often double write since both
+ # selections get the same content
+ if [[ ${last_data[any]} != "$data" ]]; then
+ filename="$cache_dir/$(cksum <<< "$first_line")"
+ debug "Writing $data to $filename"
+ printf '%s' "$data" > "$filename"
- debug "Writing $first_line to $cache_file"
- printf '%s\n' "$first_line" >> "$cache_file"
+ debug "Writing $first_line to $cache_file"
+ printf '%s\n' "$first_line" >> "$cache_file"
+ fi
+
+ last_data[any]=$data
if (( CM_OWN_CLIPBOARD )) && [[ $selection != primary ]]; then
# Take ownership of the clipboard, in case the original application
@@ -132,6 +145,21 @@ while sleep "${CM_SLEEP:-0.5}"; do
# we would skip first.
_xsel -o --"$selection" | _xsel -i --"$selection"
fi
+
+ if (( CM_MAX_CLIPS )); then
+ mapfile -t to_remove < <(
+ head -n -"$CM_MAX_CLIPS" "$cache_file" |
+ while read -r line; do cksum <<< "$line"; done
+ )
+ num_to_remove="${#to_remove[@]}"
+ if (( num_to_remove )); then
+ debug "Removing $num_to_remove old clips"
+ rm -- "${to_remove[@]/#/"$cache_dir/"}"
+ trunc_tmp=$(mktemp)
+ tail -n "$CM_MAX_CLIPS" "$cache_file" | uniq > "$trunc_tmp"
+ mv -- "$trunc_tmp" "$cache_file"
+ fi
+ fi
done
flock -u "$lock_fd"
diff --git a/init/clipmenud.service b/init/clipmenud.service
@@ -14,8 +14,5 @@ ProtectKernelTunables=yes
RestrictAddressFamilies=
RestrictRealtime=yes
-ProtectSystem=strict
-ReadWritePaths=/tmp
-
[Install]
WantedBy=default.target
diff --git a/tests/test-clipmenu b/tests/test-clipmenu
@@ -4,8 +4,10 @@ set -x
set -e
set -o pipefail
-major_version=3
-dir=/tmp/clipmenu.$major_version.$USER
+: "${CM_DIR="${XDG_RUNTIME_DIR-"${TMPDIR-/tmp}"}"}"
+
+major_version=4
+dir=$CM_DIR/clipmenu.$major_version.$USER
cache_file=$dir/line_cache
if [[ $0 == /* ]]; then
diff --git a/tests/test-perf b/tests/test-perf
@@ -1,12 +1,14 @@
#!/bin/bash
-major_version=3
+major_version=4
msg() {
printf '>>> %s\n' "$@" >&2
}
-dir=/tmp/clipmenu.$major_version.$USER
+: "${CM_DIR="${XDG_RUNTIME_DIR-"${TMPDIR-/tmp}"}"}"
+
+dir=$CM_DIR/clipmenu.$major_version.$USER
cache_file=$dir/line_cache
log=$(mktemp)