From e7a15a5d5012281e926e683deca16a97cadf4c89 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Wed, 10 May 2017 10:05:16 +0100 Subject: [PATCH 01/13] Do not sleep initially in oneshot mode --- clipmenud | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clipmenud b/clipmenud index ad35888..97480a4 100755 --- a/clipmenud +++ b/clipmenud @@ -60,7 +60,7 @@ 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 From f184c37994a592bea9932e4a9ce981f39a4517dc Mon Sep 17 00:00:00 2001 From: Chris Down Date: Tue, 30 May 2017 08:55:27 +0100 Subject: [PATCH 02/13] Add a note about using CM_ONESHOT, per #47 --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 2b94ea0..d3f96db 100644 --- 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. From caa009b222e6b859c86de1b44409f9a013f0ae10 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Tue, 30 May 2017 08:58:19 +0100 Subject: [PATCH 03/13] Remove partial clip dedupe support The application that was particularly bad at this (Chrome) now no longer does this, and this is basically a hack that has lasted too long. Also closes #41 and closes #42 since the questionable "feature" is going away. --- clipmenud | 9 --------- 1 file changed, 9 deletions(-) diff --git a/clipmenud b/clipmenud index 97480a4..8c54f2d 100755 --- a/clipmenud +++ b/clipmenud @@ -94,15 +94,6 @@ while (( CM_ONESHOT )) || 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 From e5508e84c546f03480e171e1e2edc5855aa3ed52 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Tue, 30 May 2017 09:02:58 +0100 Subject: [PATCH 04/13] systemd service: Don't restrict to only /tmp People might set $TMPDIR and this doesn't buy us much. --- init/clipmenud.service | 3 --- 1 file changed, 3 deletions(-) diff --git a/init/clipmenud.service b/init/clipmenud.service index 3912619..1e67258 100644 --- 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 From f0d09c76921a3b06fd309e74a1fc87f83346b23c Mon Sep 17 00:00:00 2001 From: Chris Down Date: Tue, 30 May 2017 09:03:35 +0100 Subject: [PATCH 05/13] Use $TMPDIR as base for cache directory Closes #38. --- clipmenu | 6 ++++-- clipmenud | 13 +++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/clipmenu b/clipmenu index 2f65084..607e7a9 100755 --- a/clipmenu +++ b/clipmenu @@ -1,13 +1,15 @@ #!/bin/bash +: "${CM_LAUNCHER=dmenu}" +: "${TMPDIR=/tmp}" + major_version=3 shopt -s nullglob -cache_dir=/tmp/clipmenu.$major_version.$USER +cache_dir=$TMPDIR/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 diff --git a/clipmenud b/clipmenud index 8c54f2d..8574cd5 100755 --- a/clipmenud +++ b/clipmenud @@ -1,14 +1,15 @@ #!/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}" +: "${TMPDIR=/tmp}" + +major_version=3 +cache_dir=$TMPDIR/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 "$@" From 60611584ecb72b0e05f773b9823e60153f30b21e Mon Sep 17 00:00:00 2001 From: Chris Down Date: Tue, 30 May 2017 09:06:04 +0100 Subject: [PATCH 06/13] Remove unused last_filename --- clipmenud | 2 -- 1 file changed, 2 deletions(-) diff --git a/clipmenud b/clipmenud index 8574cd5..dd6a1df 100755 --- a/clipmenud +++ b/clipmenud @@ -57,7 +57,6 @@ debug() { mkdir -p -m0700 "$cache_dir" declare -A last_data -declare -A last_filename exec {lock_fd}> "$lock_file" @@ -96,7 +95,6 @@ while (( CM_ONESHOT )) || sleep "${CM_SLEEP:-0.5}"; do fi last_data[$selection]=$data - last_filename[$selection]=$filename first_line=$(get_first_line "$data") From 2cd2287612a541260e4a6973045479b354a4febf Mon Sep 17 00:00:00 2001 From: Chris Down Date: Wed, 31 May 2017 20:01:56 +0100 Subject: [PATCH 07/13] Add more documentation on -h/--help for clipmenu{,d} --- clipmenu | 18 +++++++++--------- clipmenud | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/clipmenu b/clipmenu index 607e7a9..9c67c8c 100755 --- a/clipmenu +++ b/clipmenu @@ -10,14 +10,8 @@ shopt -s nullglob cache_dir=$TMPDIR/clipmenu.$major_version.$USER cache_file=$cache_dir/line_cache - -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. @@ -25,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) +- $TMPDIR: specify the base directory to store the cache dir in (default: /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 index dd6a1df..42dd2fc 100755 --- a/clipmenud +++ b/clipmenud @@ -52,6 +52,22 @@ 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) +- $TMPDIR: specify the base directory to store the cache dir in (default: /tmp) +EOF + exit 0 +fi + + # It's ok that this only applies to the final directory. # shellcheck disable=SC2174 mkdir -p -m0700 "$cache_dir" From 8bb4ff39998b027b7f9a5c1e6ca9afe66c14ee03 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Tue, 24 Oct 2017 17:03:02 +0200 Subject: [PATCH 08/13] Gate new clipboard entry output on CM_DEBUG Closes #49. --- clipmenud | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clipmenud b/clipmenud index 42dd2fc..22c100c 100755 --- a/clipmenud +++ b/clipmenud @@ -114,8 +114,7 @@ while (( CM_ONESHOT )) || sleep "${CM_SLEEP:-0.5}"; do 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" From e6c312b6394150384330c86a7c225030f96c1126 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Tue, 24 Oct 2017 17:30:32 +0200 Subject: [PATCH 09/13] Avoid double writing if both selections are the same --- clipmenud | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/clipmenud b/clipmenud index 22c100c..8fb3c00 100755 --- a/clipmenud +++ b/clipmenud @@ -116,12 +116,18 @@ while (( CM_ONESHOT )) || sleep "${CM_SLEEP:-0.5}"; do 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 From 4a0bd8f1f48f7a4262c02c71cb2a658a3fb7084d Mon Sep 17 00:00:00 2001 From: Chris Down Date: Tue, 24 Oct 2017 17:14:06 +0200 Subject: [PATCH 10/13] Only keep $CM_MAX_CLIPS newest clips Closes #50. --- clipmenud | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/clipmenud b/clipmenud index 8fb3c00..2fc2fde 100755 --- a/clipmenud +++ b/clipmenud @@ -4,6 +4,7 @@ : "${CM_OWN_CLIPBOARD=1}" : "${CM_DEBUG=0}" : "${TMPDIR=/tmp}" +: "${CM_MAX_CLIPS=1000}" major_version=3 cache_dir=$TMPDIR/clipmenu.$major_version.$USER/ @@ -62,6 +63,7 @@ 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) - $TMPDIR: specify the base directory to store the cache dir in (default: /tmp) EOF exit 0 @@ -143,6 +145,19 @@ while (( CM_ONESHOT )) || sleep "${CM_SLEEP:-0.5}"; do # we would skip first. _xsel -o --"$selection" | _xsel -i --"$selection" fi + + 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 done flock -u "$lock_fd" From 6855d24e3a3393c124e18a4dacc5d007eb463a3a Mon Sep 17 00:00:00 2001 From: Chris Down Date: Tue, 24 Oct 2017 19:14:09 +0200 Subject: [PATCH 11/13] Prefer $XDG_RUNTIME_DIR for storage Closes #51. --- clipmenu | 6 +++--- clipmenud | 6 +++--- tests/test-clipmenu | 4 +++- tests/test-perf | 4 +++- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/clipmenu b/clipmenu index 9c67c8c..51058a6 100755 --- a/clipmenu +++ b/clipmenu @@ -1,13 +1,13 @@ #!/bin/bash : "${CM_LAUNCHER=dmenu}" -: "${TMPDIR=/tmp}" +: "${CM_DIR="${XDG_RUNTIME_DIR-"${TMPDIR-/tmp}"}"}" major_version=3 shopt -s nullglob -cache_dir=$TMPDIR/clipmenu.$major_version.$USER +cache_dir=$CM_DIR/clipmenu.$major_version.$USER cache_file=$cache_dir/line_cache if [[ $1 == --help ]] || [[ $1 == -h ]]; then @@ -20,7 +20,7 @@ All arguments are passed through to dmenu itself. Environment variables: - $CM_LAUNCHER: specify a dmenu-compatible launcher (default: dmenu) -- $TMPDIR: specify the base directory to store the cache dir in (default: /tmp) +- $CM_DIR: specify the base directory to store the cache dir in (default: $XDG_RUNTIME_DIR, $TMPDIR, or /tmp) EOF exit 0 fi diff --git a/clipmenud b/clipmenud index 2fc2fde..3997819 100755 --- a/clipmenud +++ b/clipmenud @@ -3,11 +3,11 @@ : "${CM_ONESHOT=0}" : "${CM_OWN_CLIPBOARD=1}" : "${CM_DEBUG=0}" -: "${TMPDIR=/tmp}" +: "${CM_DIR="${XDG_RUNTIME_DIR-"${TMPDIR-/tmp}"}"}" : "${CM_MAX_CLIPS=1000}" major_version=3 -cache_dir=$TMPDIR/clipmenu.$major_version.$USER/ +cache_dir=$CM_DIR/clipmenu.$major_version.$USER/ cache_file=$cache_dir/line_cache lock_file=$cache_dir/lock lock_timeout=2 @@ -64,7 +64,7 @@ Environment variables: - $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) -- $TMPDIR: specify the base directory to store the cache dir in (default: /tmp) +- $CM_DIR: specify the base directory to store the cache dir in (default: $XDG_RUNTIME_DIR, $TMPDIR, or /tmp) EOF exit 0 fi diff --git a/tests/test-clipmenu b/tests/test-clipmenu index 43af5d5..c7e89eb 100755 --- a/tests/test-clipmenu +++ b/tests/test-clipmenu @@ -4,8 +4,10 @@ set -x set -e set -o pipefail +: "${CM_DIR="${XDG_RUNTIME_DIR-"${TMPDIR-/tmp}"}"}" + major_version=3 -dir=/tmp/clipmenu.$major_version.$USER +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 index a393de6..bb2a5c2 100755 --- a/tests/test-perf +++ b/tests/test-perf @@ -6,7 +6,9 @@ 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) From 48b1ebf9fbe20153165ace0f3d290bb1d8a7637a Mon Sep 17 00:00:00 2001 From: Chris Down Date: Tue, 24 Oct 2017 22:53:54 +0200 Subject: [PATCH 12/13] If CM_MAX_CLIPS is 0, don't truncate --- clipmenud | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/clipmenud b/clipmenud index 3997819..ba2ef3a 100755 --- a/clipmenud +++ b/clipmenud @@ -146,17 +146,19 @@ while (( CM_ONESHOT )) || sleep "${CM_SLEEP:-0.5}"; do _xsel -o --"$selection" | _xsel -i --"$selection" fi - 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" + 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 From f34bd68797ec54b5a1b3b7a94699db980b5eca88 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Wed, 25 Oct 2017 01:57:35 +0100 Subject: [PATCH 13/13] Bump major_version to 4 --- clipmenu | 2 +- clipmenud | 2 +- tests/test-clipmenu | 2 +- tests/test-perf | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clipmenu b/clipmenu index 51058a6..b526f17 100755 --- a/clipmenu +++ b/clipmenu @@ -3,7 +3,7 @@ : "${CM_LAUNCHER=dmenu}" : "${CM_DIR="${XDG_RUNTIME_DIR-"${TMPDIR-/tmp}"}"}" -major_version=3 +major_version=4 shopt -s nullglob diff --git a/clipmenud b/clipmenud index ba2ef3a..5b407a7 100755 --- a/clipmenud +++ b/clipmenud @@ -6,7 +6,7 @@ : "${CM_DIR="${XDG_RUNTIME_DIR-"${TMPDIR-/tmp}"}"}" : "${CM_MAX_CLIPS=1000}" -major_version=3 +major_version=4 cache_dir=$CM_DIR/clipmenu.$major_version.$USER/ cache_file=$cache_dir/line_cache lock_file=$cache_dir/lock diff --git a/tests/test-clipmenu b/tests/test-clipmenu index c7e89eb..3916c36 100755 --- a/tests/test-clipmenu +++ b/tests/test-clipmenu @@ -6,7 +6,7 @@ set -o pipefail : "${CM_DIR="${XDG_RUNTIME_DIR-"${TMPDIR-/tmp}"}"}" -major_version=3 +major_version=4 dir=$CM_DIR/clipmenu.$major_version.$USER cache_file=$dir/line_cache diff --git a/tests/test-perf b/tests/test-perf index bb2a5c2..9a7a767 100755 --- a/tests/test-perf +++ b/tests/test-perf @@ -1,6 +1,6 @@ #!/bin/bash -major_version=3 +major_version=4 msg() { printf '>>> %s\n' "$@" >&2