clipmenu

Simple clipboard management using dmenu
git clone https://git.sinitax.com/cdown/clipmenu
Log | Files | Refs | README | LICENSE | sfeed.txt

commit 44c0fb3ad26dea8cc818d8a57aa414aecf22128d
parent fd665a298a1ace0637b5a6f3c4cd017c03c51216
Author: Chris Down <chris@chrisdown.name>
Date:   Tue, 20 Feb 2018 11:38:34 +0000

Merge branch 'release/5.0.0'

Diffstat:
Mclipmenu | 9++++++---
Mclipmenud | 68+++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mtests/test-clipmenu | 9++++-----
Mtests/test-perf | 6+++---
4 files changed, 70 insertions(+), 22 deletions(-)

diff --git a/clipmenu b/clipmenu @@ -3,12 +3,12 @@ : "${CM_LAUNCHER=dmenu}" : "${CM_DIR="${XDG_RUNTIME_DIR-"${TMPDIR-/tmp}"}"}" -major_version=4 +major_version=5 shopt -s nullglob cache_dir=$CM_DIR/clipmenu.$major_version.$USER -cache_file=$cache_dir/line_cache +cache_file_prefix=$cache_dir/line_cache if [[ $1 == --help ]] || [[ $1 == -h ]]; then cat << 'EOF' @@ -34,7 +34,10 @@ fi # 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" | awk '!seen[$0]++' | "$CM_LAUNCHER" -l 8 "$@") +chosen_line=$( + cat "$cache_file_prefix"_* /dev/null | sort -rnk 1 | cut -d' ' -f2- | + awk '!seen[$0]++' | "$CM_LAUNCHER" -l 8 "$@" +) [[ $chosen_line ]] || exit 1 diff --git a/clipmenud b/clipmenud @@ -6,13 +6,22 @@ : "${CM_DIR="${XDG_RUNTIME_DIR-"${TMPDIR-/tmp}"}"}" : "${CM_MAX_CLIPS=1000}" -major_version=4 +# Shellcheck is mistaken here, this is used later as lowercase. +# shellcheck disable=SC2153 +: "${CM_SELECTIONS=clipboard primary}" + +major_version=5 cache_dir=$CM_DIR/clipmenu.$major_version.$USER/ -cache_file=$cache_dir/line_cache +cache_file_prefix=$cache_dir/line_cache lock_file=$cache_dir/lock lock_timeout=2 has_clipnotify=0 +# This comes from the environment, so we rely on word splitting. +# shellcheck disable=SC2206 +cm_selections=( $CM_SELECTIONS ) + + xsel_log=/dev/null for file in /proc/self/fd/2 /dev/stderr; do [[ -f "$file" ]] || continue @@ -64,6 +73,17 @@ debug() { fi } +element_in() { + local item element + item="$1" + for element in "${@:2}"; do + if [[ "$item" == "$element" ]]; then + return 0 + fi + done + return 1 +} + if [[ $1 == --help ]] || [[ $1 == -h ]]; then cat << 'EOF' clipmenud is the daemon that collects and caches what's on the clipboard. @@ -76,6 +96,7 @@ Environment variables: - $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) +- $CM_SELECTIONS: space separated list of the selections to manage (default: "clipboard primary") EOF exit 0 fi @@ -86,6 +107,8 @@ fi mkdir -p -m0700 "$cache_dir" declare -A last_data +declare -A last_filename +declare -A last_cache_file_output command -v clipnotify >/dev/null 2>&1 && has_clipnotify=1 @@ -96,13 +119,16 @@ fi exec {lock_fd}> "$lock_file" +sleep_cmd=(sleep "${CM_SLEEP:-0.5}") + while true; do if ! (( CM_ONESHOT )); then if (( has_clipnotify )); then - clipnotify + # Fall back to polling if clipnotify fails + clipnotify || "${sleep_cmd[@]}" else # Use old polling method - sleep "${CM_SLEEP:-0.5}" + "${sleep_cmd[@]}" fi fi @@ -117,7 +143,8 @@ while true; do fi fi - for selection in clipboard primary; do + for selection in "${cm_selections[@]}"; do + cache_file=${cache_file_prefix}_$selection data=$(_xsel -o --"$selection"; printf x) debug "Data before stripping: $data" @@ -139,7 +166,23 @@ while true; 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"* ]] || + [[ $possible_partial && $data == *"$possible_partial" ]]; then + debug "$possible_partial is a possible partial of $data" + debug "Removing ${last_filename[$selection]}" + + previous_size=$(wc -c <<< "${last_cache_file_output[$selection]}") + truncate -s -"$previous_size" "$cache_file" + + rm -- "${last_filename[$selection]}" + fi + last_data[$selection]=$data + last_filename[$selection]=$filename first_line=$(get_first_line "$data") @@ -147,18 +190,21 @@ while true; do # Without checking ${last_data[any]}, we often double write since both # selections get the same content + cache_file_output="$(date +%s) $first_line" 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 $cache_file_output to $cache_file" + printf '%s\n' "$cache_file_output" >> "$cache_file" fi last_data[any]=$data + last_cache_file_output[$selection]=$cache_file_output - if (( CM_OWN_CLIPBOARD )) && [[ $selection != primary ]]; then + if (( CM_OWN_CLIPBOARD )) && [[ $selection != primary ]] && + element_in clipboard "${cm_selections[@]}"; then # Take ownership of the clipboard, in case the original application # is unable to serve the clipboard request (due to being suspended, # etc). @@ -170,13 +216,13 @@ while true; 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. - _xsel -o --"$selection" | _xsel -i --"$selection" + _xsel -o --clipboard | _xsel -i --clipboard fi - if (( CM_MAX_CLIPS )); then + if (( CM_MAX_CLIPS )) && [[ -f $cache_file ]]; then mapfile -t to_remove < <( head -n -"$CM_MAX_CLIPS" "$cache_file" | - while read -r line; do cksum <<< "$line"; done + while read -r line; do cksum <<< "${line#* }"; done ) num_to_remove="${#to_remove[@]}" if (( num_to_remove )); then diff --git a/tests/test-clipmenu b/tests/test-clipmenu @@ -6,9 +6,9 @@ set -o pipefail : "${CM_DIR="${XDG_RUNTIME_DIR-"${TMPDIR-/tmp}"}"}" -major_version=4 +major_version=5 dir=$CM_DIR/clipmenu.$major_version.$USER -cache_file=$dir/line_cache +cache_file=$dir/line_cache_primary if [[ $0 == /* ]]; then location=${0%/*} @@ -58,8 +58,8 @@ rm -rf "$dir" mkdir -p "$dir" cat > "$cache_file" << 'EOF' -Selected text. (2 lines) -Selected text 2. (2 lines) +1234 Selected text. (2 lines) +1235 Selected text 2. (2 lines) EOF cat > "$dir/$(cksum <<< 'Selected text. (2 lines)')" << 'EOF' @@ -70,7 +70,6 @@ EOF ### TESTS ### temp=$(mktemp) -trap 'rm -f -- "$temp"' EXIT /tmp/clipmenu --foo bar > "$temp" 2>&1 diff --git a/tests/test-perf b/tests/test-perf @@ -1,6 +1,6 @@ #!/bin/bash -major_version=4 +major_version=5 msg() { printf '>>> %s\n' "$@" >&2 @@ -9,7 +9,7 @@ msg() { : "${CM_DIR="${XDG_RUNTIME_DIR-"${TMPDIR-/tmp}"}"}" dir=$CM_DIR/clipmenu.$major_version.$USER -cache_file=$dir/line_cache +cache_file=$dir/line_cache_primary log=$(mktemp) tim=$(mktemp) @@ -62,7 +62,7 @@ if ! (( NO_RECREATE )); then ) 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" + printf '%d %s' "$i" "$first_line" >> "$cache_file" fn=$dir/$(cksum <<< "$first_line") printf '%s' "$data" > "$fn" done