clipmenu

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

commit 8388a00ab366dfa0660ed11d6560959a44686def
parent 09e654731efb4645277baa7c3653606c8cf2bd26
Author: Chris Down <chris@chrisdown.name>
Date:   Sun, 22 Apr 2018 10:10:57 +0100

Merge branch 'release/5.2.0'

Diffstat:
MREADME.md | 2++
Aclipdel | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mclipmenu | 36+++++++++++++++++++++++++++---------
Mclipmenud | 12+++++++++---
4 files changed, 117 insertions(+), 12 deletions(-)

diff --git a/README.md b/README.md @@ -21,6 +21,8 @@ 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' +You can remove clips with the `clipdel` utility, see `clipdel --help`. + # How does it work? The code is fairly simple and easy to follow, you may find it easier to read diff --git a/clipdel b/clipdel @@ -0,0 +1,79 @@ +#!/bin/bash + +: "${CM_DIR="${XDG_RUNTIME_DIR-"${TMPDIR-/tmp}"}"}" +CM_REAL_DELETE=0 +[[ $1 == -d ]] && CM_REAL_DELETE=1 + +major_version=5 + +shopt -s nullglob + +cache_dir=$CM_DIR/clipmenu.$major_version.$USER +cache_file_prefix=$cache_dir/line_cache +lock_file=$cache_dir/lock +lock_timeout=2 + +if [[ $1 == --help ]] || [[ $1 == -h ]]; then + cat << 'EOF' +clipdel deletes clipmenu entries matching a regex. By default, just lists what +it would delete, pass -d to do it for real. + +".*" is special, it will just nuke the entire data directory, including the +line caches and all other state. + +Arguments: + + -d Delete for real. + +Environment variables: + +- $CM_DIR: specify the base directory to store the cache dir in (default: $XDG_RUNTIME_DIR, $TMPDIR, or /tmp) +EOF + exit 0 +fi + +line_cache_files=( "$cache_file_prefix"_* ) + +if (( ${#line_cache_files[@]} == 0 )); then + printf '%s\n' "No line cache files found, no clips exist" >&2 + exit 0 # Well, this is a kind of success... +fi + +# https://github.com/koalaman/shellcheck/issues/1141 +# shellcheck disable=SC2124 +raw_pattern=${@: -1} +esc_pattern=${raw_pattern/\#/'\#'} + +exec {lock_fd}> "$lock_file" + +if (( CM_REAL_DELETE )) && [[ "$raw_pattern" == ".*" ]]; then + flock -x -w "$lock_timeout" "$lock_fd" || exit + rm -rf -- "$cache_dir" + exit 0 +else + mapfile -t matches < <( + cat "${line_cache_files[@]}" | cut -d' ' -f2- | sort -u | + sed -n "\\#${esc_pattern}#p" + ) + + if (( CM_REAL_DELETE )); then + flock -x -w "$lock_timeout" "$lock_fd" || exit + + for match in "${matches[@]}"; do + ck=$(cksum <<< "$match") + rm -f -- "$cache_dir/$ck" + done + + for file in "${line_cache_files[@]}"; do + temp=$(mktemp) + cut -d' ' -f2- < "$file" | sed "\\#${esc_pattern}#d" > "$temp" + mv -- "$temp" "$file" + done + + flock -u "$lock_fd" + else + if (( ${#matches[@]} )); then + printf '%s\n' "${matches[@]}" + fi + fi +fi diff --git a/clipmenu b/clipmenu @@ -30,14 +30,28 @@ if [[ "$CM_LAUNCHER" == rofi ]]; then 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 -# one will be ignored. -chosen_line=$( - cat "$cache_file_prefix"_* /dev/null | LC_ALL=C sort -rnk 1 | - cut -d' ' -f2- | awk '!seen[$0]++' | "$CM_LAUNCHER" -l 8 "$@" -) +list_clips() { + cat "$cache_file_prefix"_* /dev/null | LC_ALL=C sort -rnk 1 | cut -d' ' -f2- | awk '!seen[$0]++' +} + +if [[ "$CM_LAUNCHER" == rofi-script ]]; then + if ! (( $# )); then + list_clips + exit + else + # https://github.com/koalaman/shellcheck/issues/1141 + # shellcheck disable=SC2124 + chosen_line="${@: -1}" + fi +else + # 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=$( + list_clips | "$CM_LAUNCHER" -l 8 "$@" + ) +fi [[ $chosen_line ]] || exit 1 @@ -45,7 +59,11 @@ file=$cache_dir/$(cksum <<< "$chosen_line") if ! [[ -f "$file" ]]; then # We didn't find this in cache - printf 'FATAL: %s not in cache\n' "$chosen_line" >&2 + printf 'FATAL: %s not in cache (%s missing)\n' "$chosen_line" "$file" >&2 + printf 'Please report the following debug information:\n\n' >&2 + wc -l "$cache_file_prefix"_* >&2 + grep -nFR "$chosen_line" "$cache_dir" >&2 + stat "$file" >&2 exit 2 fi diff --git a/clipmenud b/clipmenud @@ -181,15 +181,21 @@ while true; do rm -- "${last_filename[$selection]}" fi - last_data[$selection]=$data - last_filename[$selection]=$filename - first_line=$(get_first_line "$data") debug "New clipboard entry on $selection selection: \"$first_line\"" cache_file_output="$(date +%s%N) $first_line" filename="$cache_dir/$(cksum <<< "$first_line")" + + last_data[$selection]=$data + last_filename[$selection]=$filename + + # Recover without restart if we deleted the entire clip dir. + # It's ok that this only applies to the final directory. + # shellcheck disable=SC2174 + mkdir -p -m0700 "$cache_dir" + debug "Writing $data to $filename" printf '%s' "$data" > "$filename"