commit 0011a2c3b981157f7decc2eac2c3db4adc04f77d
parent bcbe7b144598db4a103f14e8408c4b7327d6d5e1
Author: Chris Down <chris@chrisdown.name>
Date: Wed, 3 Jun 2020 22:09:03 +0100
Merge branch 'release/6.1.0'
Diffstat:
8 files changed, 157 insertions(+), 26 deletions(-)
diff --git a/.travis.yml b/.travis.yml
@@ -3,7 +3,7 @@ language: bash
dist: xenial
script:
- - shellcheck -s bash clipmenu clipmenud clipdel clipfsck
+ - shellcheck -s bash clipmenu clipmenud clipdel clipfsck clipctl
- tests/test-clipmenu
matrix:
diff --git a/Makefile b/Makefile
@@ -0,0 +1,14 @@
+# `dmenu` is not a hard dependency, but you need it unless
+# you plan to set CM_LAUNCHER to another value like `rofi`
+REQUIRED_BINS := xsel clipnotify
+$(foreach bin,$(REQUIRED_BINS),\
+ $(if $(shell command -v $(bin) 2> /dev/null),$(info Found `$(bin)`),$(error Missing Dep. Please install `$(bin)`)))
+
+.PHONY: install
+
+install:
+ install -D -m755 clipmenu /usr/bin/clipmenu
+ install -D -m755 clipmenud /usr/bin/clipmenud
+ install -D -m755 clipdel /usr/bin/clipdel
+ install -D -m755 clipctl /usr/bin/clipctl
+ install -D -m644 init/clipmenud.service /usr/lib/systemd/user/clipmenud.service
diff --git a/README.md b/README.md
@@ -10,13 +10,10 @@ clipmenu is a simple clipboard manager using [dmenu][] (or [rofi][] with
# Usage
Start `clipmenud`, then run `clipmenu` to select something to put on the
-clipboard.
+clipboard. For systemd users, a user service called `clipmenud` is packaged as
+part of the project.
-A systemd user service for starting clipmenud is included at
-[init/clipmenud.service](https://github.com/cdown/clipmenu/blob/develop/init/clipmenud.service).
-You can then start clipmenud like this:
-
- systemctl --user start clipmenud
+You may wish to bind a shortcut in your window manager to launch `clipmenu`.
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
@@ -24,25 +21,53 @@ 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'
-If you prefer to collect clips on demand rather than running clipmenud as a
-daemon, you can bind a key to the following command for one-off collection:
-
- CM_ONESHOT=1 clipmenud
-
For a full list of environment variables that clipmenud can take, please see
`clipmenud --help`.
+# Features
+
+The behavior of `clipmenud` can be customized through environment variables.
+Despite being only <300 lines, clipmenu has many useful features, including:
+
+* Customising the maximum number of clips stored (default 1000)
+* Disabling clip collection temporarily with `clipctl disable`, reenabling with
+ `clipctl enable`
+* Not storing clipboard changes from certain applications, like password
+ managers
+* Taking direct ownership of the clipboard
+* ...and much more.
+
+Check `clipmenud --help` to view all possible environment variables and what
+they do. If you manage `clipmenud` with `systemd`, you can override the
+defaults by using `systemctl --user edit clipmenud` to generate an override
+file.
+
+# Supported launchers
+
+Any dmenu-compliant application will work, but here are `CM_LAUNCHER`
+configurations that are known to work:
+
+- `dmenu` (the default)
+- `fzf`
+- `rofi`
+- `rofi-script`, for [rofi's script
+ mode](https://github.com/davatorium/rofi-scripts/tree/master/mode-scripts)
+
# Installation
Several distributions, including Arch and Nix, provide clipmenu as an official
package called `clipmenu`.
-If your distribution doesn't provide a package, you can run the scripts
-standalone (or better yet, package them!).
+## Manual installation
+
+If your distribution doesn't provide a package, you can manually install using
+`make install` (or better yet, create a package for your distribution!). You
+will need `xsel` and `clipnotify` installed, and also `dmenu` unless you plan
+to use a different launcher.
# How does it work?
-clipmenud is less than 200 lines, and clipmenu is less than 100, so hopefully
+clipmenud is less than 300 lines, and clipmenu is less than 100, so hopefully
it should be fairly self-explanatory. However, at the most basic level:
## clipmenud
diff --git a/clipctl b/clipctl
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+
+if [[ -z $1 ]] || [[ $1 == --help ]] || [[ $1 == -h ]]; then
+ cat << 'EOF'
+clipctl provides controls for the clipmenud daemon.
+
+You can temporarily disable clip collection without stopping clipmenud entirely
+by running "clipctl disable". You can then reenable with "clipctl enable".
+EOF
+ exit 0
+fi
+
+_CLIPMENUD_PID=$(pgrep -u "$(id -u)" -nf 'clipmenud$')
+
+if [[ -z "$_CLIPMENUD_PID" ]]; then
+ echo "clipmenud is not running"
+ exit 2
+fi
+
+case $1 in
+ enable) kill -USR2 "$_CLIPMENUD_PID" ;;
+ disable) kill -USR1 "$_CLIPMENUD_PID" ;;
+ *)
+ printf 'Unknown command: %s\n' "$1"
+ exit 1
+ ;;
+esac
diff --git a/clipdel b/clipdel
@@ -19,7 +19,8 @@ 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.
+it would delete, pass -d to do it for real. If no pattern is passed as an argument,
+it will try to read one from standard input.
".*" is special, it will just nuke the entire data directory, including the
line caches and all other state.
@@ -40,9 +41,12 @@ if ! [[ -f $cache_file ]]; then
exit 0 # Well, this is a kind of success...
fi
-# https://github.com/koalaman/shellcheck/issues/1141
-# shellcheck disable=SC2124
-raw_pattern=$1
+if [[ -n $1 ]]; then
+ raw_pattern=$1
+elif ! [[ -t 0 ]]; then
+ IFS= read -r raw_pattern
+fi
+
esc_pattern=${raw_pattern//\#/'\#'}
# We use 2 separate sed commands so "esc_pattern" matches only the 'clip' text
diff --git a/clipmenu b/clipmenu
@@ -23,10 +23,18 @@ Environment variables:
- $CM_DIR: specify the base directory to store the cache dir in (default: $XDG_RUNTIME_DIR, $TMPDIR, or /tmp)
- $CM_HISTLENGTH: specify the number of lines to show in dmenu/rofi (default: 8)
- $CM_LAUNCHER: specify a dmenu-compatible launcher (default: dmenu)
+- $CM_OUTPUT_CLIP: if set, output clip selection to stdout
EOF
exit 0
fi
+
+# Blacklist of non-dmenu launchers
+launcher_args=(-l "${CM_HISTLENGTH}")
+if [[ "$CM_LAUNCHER" == fzf ]]; then
+ launcher_args=()
+fi
+
# rofi supports dmenu-like arguments through the -dmenu flag
[[ "$CM_LAUNCHER" == rofi ]] && set -- -dmenu "$@"
@@ -42,7 +50,7 @@ if [[ "$CM_LAUNCHER" == rofi-script ]]; then
exit
fi
else
- chosen_line=$(list_clips | "$CM_LAUNCHER" -l "${CM_HISTLENGTH}" "$@")
+ chosen_line=$(list_clips | "$CM_LAUNCHER" "${launcher_args[@]}" "$@")
fi
[[ $chosen_line ]] || exit 1
@@ -52,3 +60,7 @@ file=$cache_dir/$(cksum <<< "$chosen_line")
for selection in clipboard primary; do
xsel --logfile /dev/null -i --"$selection" < "$file"
done
+
+if (( CM_OUTPUT_CLIP )); then
+ cat "$file"
+fi
diff --git a/clipmenud b/clipmenud
@@ -3,13 +3,13 @@
: "${CM_ONESHOT=0}"
: "${CM_OWN_CLIPBOARD=0}"
: "${CM_DEBUG=0}"
-: "${CM_DIR="${XDG_RUNTIME_DIR-"${TMPDIR-/tmp}"}"}"
+: "${CM_DIR:="${XDG_RUNTIME_DIR-"${TMPDIR-/tmp}"}"}"
-: "${CM_MAX_CLIPS=1000}"
+: "${CM_MAX_CLIPS:=1000}"
# Buffer to batch to avoid calling too much. Only used if CM_MAX_CLIPS >0.
CM_MAX_CLIPS_THRESH=$(( CM_MAX_CLIPS + 10 ))
-: "${CM_SELECTIONS=clipboard primary}"
+: "${CM_SELECTIONS:=clipboard primary}"
read -r -a selections <<< "$CM_SELECTIONS"
major_version=6
@@ -55,9 +55,34 @@ get_first_line() {
debug() { (( CM_DEBUG )) && printf '%s\n' "$@" >&2; }
+sig_disable() {
+ info "Received disable signal, suspending clipboard capture"
+ _CM_DISABLED=1
+ _CM_FIRST_DISABLE=1
+ [[ -v _CM_CLIPNOTIFY_PID ]] && kill "$_CM_CLIPNOTIFY_PID"
+}
+
+sig_enable() {
+ if ! (( _CM_DISABLED )); then
+ info "Received enable signal but we're not disabled, so doing nothing"
+ return
+ fi
+
+ # Still store the last data so we don't end up eventually putting it in the
+ # clipboard if it wasn't changed
+ for selection in "${selections[@]}"; do
+ data=$(_xsel -o --"$selection"; printf x)
+ last_data_sel[$selection]=${data%x}
+ done
+
+ info "Received enable signal, resuming clipboard capture"
+ _CM_DISABLED=0
+}
+
if [[ $1 == --help ]] || [[ $1 == -h ]]; then
cat << 'EOF'
-clipmenud collects and caches what's on the clipboard.
+clipmenud collects and caches what's on the clipboard. You can manage its
+operation with clipctl.
Environment variables:
@@ -92,8 +117,29 @@ fi
exec {lock_fd}> "$lock_file"
+trap sig_disable USR1
+trap sig_enable USR2
+
+# Kill all background processes on exit
+trap 'trap - TERM; kill -- -$$' INT TERM EXIT
+
while true; do
- (( CM_ONESHOT )) || clipnotify
+ if ! (( CM_ONESHOT )); then
+ # Make sure we're interruptible for the sig_{en,dis}able traps
+ clipnotify &
+ _CM_CLIPNOTIFY_PID="$!"
+ wait "$_CM_CLIPNOTIFY_PID"
+ fi
+
+ if (( _CM_DISABLED )); then
+ # The first one will just be from interrupting `wait`, so don't print
+ if (( _CM_FIRST_DISABLE )); then
+ unset _CM_FIRST_DISABLE
+ else
+ info "Got a clipboard notification, but we are disabled, skipping"
+ fi
+ continue
+ fi
if [[ $CM_IGNORE_WINDOW ]] && (( has_xdotool )); then
windowname="$(xdotool getactivewindow getwindowname)"
@@ -149,7 +195,8 @@ while true; do
fi
done
- if (( CM_MAX_CLIPS )) && (( "$(wc -l < "$cache_file")" > CM_MAX_CLIPS_THRESH )); then
+ # The cache file may not exist if this is the first run and data is skipped
+ if (( CM_MAX_CLIPS )) && [[ -f "$cache_file" ]] && (( "$(wc -l < "$cache_file")" > CM_MAX_CLIPS_THRESH )); then
info "Trimming clip cache to CM_MAX_CLIPS ($CM_MAX_CLIPS)"
trunc_tmp=$(mktemp)
tail -n "$CM_MAX_CLIPS" "$cache_file" | uniq > "$trunc_tmp"
diff --git a/tests/test-clipmenu b/tests/test-clipmenu
@@ -71,6 +71,8 @@ EOF
temp=$(mktemp)
+trap 'cat "$temp"' EXIT
+
/tmp/clipmenu --foo bar > "$temp" 2>&1
# Arguments are transparently passed to dmenu