From 9d5a241f1dbe10c96257a56b62dfd9d3767a68f1 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Fri, 6 Jan 2017 17:50:09 +0000 Subject: [PATCH 01/12] Document behaviour in README --- README.md | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c1b13b4..00c5463 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,35 @@ clipmenu is a simple clipboard manager using [dmenu][] and [xsel][]. -To use it, start the `clipmenud` daemon, and then call `clipmenu` to launch -`dmenu`. Upon choosing an entry, it is copied to the clipboard. +# Usage + +Start `clipmenud`, then run `clipmenu` to select something to put on the +clipboard. 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 -invoke clipmenu in exactly the same way to get the same effect. +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 + +# How does it work? + +The code is fairly simple and easy to follow, you may find it easier to read +there, but it basically works like this: + +## clipmenud + +1. `clipmenud` polls the clipboard every 0.5 seconds (or another interval as + configured with the `CLIPMENUD_SLEEP` environment variable. Unfortunately + there's no interface to subscribe for changes in X11, so we must poll. +2. If `clipmenud` detects changes to the clipboard contents, it writes them out + to the cache directory. + +## clipmenu + +1. `clipmenu` reads the cache directory to find all available clips. +2. `dmenu` is executed to allow the user to select a clip. +3. After selection, the clip is put onto the PRIMARY and CLIPBOARD X + selections. [dmenu]: http://tools.suckless.org/dmenu/ [xsel]: http://www.vergenet.net/~conrad/software/xsel/ From 8b4a047768c7cfe95b23475f57fe113ddfe81e2e Mon Sep 17 00:00:00 2001 From: Chris Down Date: Sat, 7 Jan 2017 14:30:55 +0000 Subject: [PATCH 02/12] Use awk instead of uniq to only keep latest, even if not adjacent --- clipmenu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clipmenu b/clipmenu index 05ad197..11e12c2 100755 --- a/clipmenu +++ b/clipmenu @@ -9,7 +9,7 @@ cache_file=$cache_dir/line_cache # 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" | uniq | dmenu -l 8 "$@") +chosen_line=$(tac "$cache_file" | awk '!seen[$0]++' | dmenu -l 8 "$@") [[ $chosen_line ]] || exit 1 From 533cdc871e91605a6262c5fb0c13b7f6a0162850 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Wed, 11 Jan 2017 11:28:51 +0000 Subject: [PATCH 03/12] Fix parens in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 00c5463..58e48a8 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ there, but it basically works like this: ## clipmenud 1. `clipmenud` polls the clipboard every 0.5 seconds (or another interval as - configured with the `CLIPMENUD_SLEEP` environment variable. Unfortunately + configured with the `CLIPMENUD_SLEEP` environment variable). Unfortunately there's no interface to subscribe for changes in X11, so we must poll. 2. If `clipmenud` detects changes to the clipboard contents, it writes them out to the cache directory. From 8c1054ca73b05f54859f95e3ccb90d66e3325342 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Wed, 11 Jan 2017 11:33:14 +0000 Subject: [PATCH 04/12] Put vars above functions --- clipmenud | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clipmenud b/clipmenud index 879f18c..fc593a8 100755 --- a/clipmenud +++ b/clipmenud @@ -1,5 +1,8 @@ #!/bin/bash +cache_dir=/tmp/clipmenu.$USER/ +cache_file=$cache_dir/line_cache + get_first_line() { # Args: # - $1, the file or data @@ -37,9 +40,6 @@ debug() { fi } -cache_dir=/tmp/clipmenu.$USER/ -cache_file=$cache_dir/line_cache - # It's ok that this only applies to the final directory. # shellcheck disable=SC2174 mkdir -p -m0700 "$cache_dir" From f7b0ea1c185e5ca9a237f43103a29c1fc50c604d Mon Sep 17 00:00:00 2001 From: Chris Down Date: Wed, 11 Jan 2017 11:35:09 +0000 Subject: [PATCH 05/12] Add version to $cache_dir --- clipmenud | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/clipmenud b/clipmenud index fc593a8..a978a3d 100755 --- a/clipmenud +++ b/clipmenud @@ -2,6 +2,8 @@ cache_dir=/tmp/clipmenu.$USER/ cache_file=$cache_dir/line_cache +version=2.0.0 +version_file=$cache_dir/version get_first_line() { # Args: @@ -44,11 +46,14 @@ debug() { # shellcheck disable=SC2174 mkdir -p -m0700 "$cache_dir" +# We currently don't do anything with this, but in future we can use this +# version for the fabled clipmenu-fsck, or other migration logic. +printf '%s\n' "$version" > "$version_file" + declare -A last_data declare -A last_filename while sleep "${CLIPMENUD_SLEEP:-0.5}"; do - for selection in clipboard primary; do if type -p xsel >/dev/null 2>&1; then debug 'Using xsel' From e8b4d83e2e0633755e42663bfb022036df90d763 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Thu, 12 Jan 2017 08:34:41 +0000 Subject: [PATCH 06/12] readme: Escape colour codes --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 58e48a8..4d2dc9b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ 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 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 + clipmenu -i -fn Terminus:size=8 -nb '#002b36' -nf '#839496' -sb '#073642' -sf '#93a1a1' # How does it work? From e85c7691e2b47a9f8dd916666f9966c561fb6c99 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Sun, 15 Jan 2017 09:48:04 +0000 Subject: [PATCH 07/12] tests: Add clipmenu test --- .travis.yml | 1 + tests/test-clipmenu | 76 +++++++++++++++++++++++++++++++++++++++ {test => tests}/test-perf | 0 3 files changed, 77 insertions(+) create mode 100755 tests/test-clipmenu rename {test => tests}/test-perf (100%) diff --git a/.travis.yml b/.travis.yml index ec0f110..6b793dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ before_script: script: - shellcheck -s bash clipmenu clipmenud + - tests/test-clipmenu matrix: fast_finish: true diff --git a/tests/test-clipmenu b/tests/test-clipmenu new file mode 100755 index 0000000..fcc4a0b --- /dev/null +++ b/tests/test-clipmenu @@ -0,0 +1,76 @@ +#!/bin/bash + +set -e +set -o pipefail + +dir=/tmp/clipmenu.$USER +cache_file=$dir/line_cache + +if [[ $0 == /* ]]; then + location=${0%/*} +else + location=$PWD/${0#./} + location=${location%/*} +fi + +cat - "$location/../clipmenu" > /tmp/clipmenu << 'EOF' +#!/bin/bash + +shopt -s expand_aliases + +shim() { + printf '%s args:' "$1" >&2 + printf ' %q' "${@:2}" >&2 + printf '\n' >&2 + + i=0 + + while IFS= read -r line; do + let i++ + printf '%s line %d stdin: %s\n' "$1" "$i" "$line" >&2 + done + + if [[ -v SHIM_STDOUT ]]; then + printf '%s\n' "$SHIM_STDOUT" + fi +} + +alias dmenu='SHIM_STDOUT="Selected text. (2 lines)" shim dmenu' +alias xsel='shim xsel' +alias xclip='shim xclip' +EOF + +chmod a+x /tmp/clipmenu + +rm -rf "$dir" +mkdir -p "$dir" + +cat > "$cache_file" << 'EOF' +Selected text. (2 lines) +Selected text 2. (2 lines) +EOF + +cat > "$dir/$(cksum <<< 'Selected text. (2 lines)')" << 'EOF' +Selected text. +Yes, it's selected text. +EOF + +### TESTS ### + +output=$(/tmp/clipmenu --foo bar 2>&1) + +printf '%s\n' "$output" > /tmp/qq + +# Arguments are transparently passed to dmenu +grep -Fxq 'dmenu args: -l 8 --foo bar' <<< "$output" + +# Output from cache file should get to dmenu, reversed +grep -Fxq 'dmenu line 1 stdin: Selected text 2. (2 lines)' <<< "$output" +grep -Fxq 'dmenu line 2 stdin: Selected text. (2 lines)' <<< "$output" + +# xsel should copy both to clipboard *and* primary +grep -Fxq 'xsel args: --logfile /dev/null -i --clipboard' <<< "$output" +grep -Fxq 'xsel args: --logfile /dev/null -i --primary' <<< "$output" + +grep -Fxq 'xsel line 1 stdin: Selected text.' <<< "$output" +grep -Fxq "xsel line 2 stdin: Yes, it's selected text." <<< "$output" diff --git a/test/test-perf b/tests/test-perf similarity index 100% rename from test/test-perf rename to tests/test-perf From 662e31d618870e77a371a422d5d623dcd42b1e55 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Fri, 17 Feb 2017 11:45:03 -0500 Subject: [PATCH 08/12] Add systemd unit Closes #39, #40. --- README.md | 3 +++ init/clipmenud.service | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 init/clipmenud.service diff --git a/README.md b/README.md index 4d2dc9b..360eb74 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,9 @@ clipmenu is a simple clipboard manager using [dmenu][] and [xsel][]. Start `clipmenud`, then run `clipmenu` to select something to put on the clipboard. +A systemd user service for starting clipmenud is included at +[init/clipmenud.service](https://github.com/cdown/clipmenu/blob/develop/init/clipmenud.service). + 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 invoke clipmenu in exactly the same way to get the same effect, like so: diff --git a/init/clipmenud.service b/init/clipmenud.service new file mode 100644 index 0000000..029ad0c --- /dev/null +++ b/init/clipmenud.service @@ -0,0 +1,31 @@ +[Unit] +Description=Clipmenu daemon + +[Service] +ExecStart=/usr/bin/clipmenud +Restart=always +RestartSec=0 +Environment=DISPLAY=:0 + +SystemCallFilter=@basic-io @default @io-event @ipc @network-io @process \ + brk fadvise64 getegid geteuid getgid getgroups getpgrp \ + getpid getppid getrlimit getuid ioctl mprotect rt_sigaction \ + rt_sigprocmask setitimer setsid sysinfo umask uname wait4 + +# @file-system will handle this once v233 is released, see +# http://bit.ly/2l1r8Ah for more details. +SystemCallFilter=access chdir close faccessat fcntl fstat getcwd mkdir mmap \ + munmap open stat statfs unlink + +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +ProtectControlGroups=yes +ProtectKernelTunables=yes +RestrictAddressFamilies= +RestrictRealtime=yes + +ProtectSystem=strict +ReadWritePaths=/tmp + +[Install] +WantedBy=default.target From 683f57484fb32f8e810b59e3a96a8d361d255e95 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Fri, 17 Feb 2017 11:50:02 -0500 Subject: [PATCH 09/12] Remove xclip support Pretty much everyone runs with xsel, so xclip support is becoming more and more questionable. Just have people use xsel. --- clipmenu | 6 +----- clipmenud | 14 ++------------ tests/test-perf | 1 - 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/clipmenu b/clipmenu index 11e12c2..ce93af6 100755 --- a/clipmenu +++ b/clipmenu @@ -22,9 +22,5 @@ if ! [[ -f "$file" ]]; then fi for selection in clipboard primary; do - if type -p xsel >/dev/null 2>&1; then - xsel --logfile /dev/null -i --"$selection" < "$file" - else - xclip -sel "$selection" < "$file" - fi + xsel --logfile /dev/null -i --"$selection" < "$file" done diff --git a/clipmenud b/clipmenud index a978a3d..c044665 100755 --- a/clipmenud +++ b/clipmenud @@ -55,13 +55,7 @@ declare -A last_filename while sleep "${CLIPMENUD_SLEEP:-0.5}"; do for selection in clipboard primary; do - if type -p xsel >/dev/null 2>&1; then - debug 'Using xsel' - data=$(xsel --logfile /dev/null -o --"$selection"; printf x) - else - debug 'Using xclip' - data=$(xclip -o -sel "$selection"; printf x) - fi + data=$(xsel --logfile /dev/null -o --"$selection"; printf x) debug "Data before stripping: $data" @@ -114,11 +108,7 @@ while sleep "${CLIPMENUD_SLEEP:-0.5}"; 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. - if type -p xsel >/dev/null 2>&1; then - xsel --logfile /dev/null -o --"$selection" | xsel -i --"$selection" - else - xclip -o -sel "$selection" | xclip -i -sel "$selection" - fi + xsel --logfile /dev/null -o --"$selection" | xsel -i --"$selection" fi done done diff --git a/tests/test-perf b/tests/test-perf index fca39ac..bc75d5a 100755 --- a/tests/test-perf +++ b/tests/test-perf @@ -35,7 +35,6 @@ shopt -s expand_aliases alias dmenu=: alias xsel=: -alias xclip=: EOF From 2acece7dce6f6f2dd1e722c40b932f9358abd8e1 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Fri, 17 Feb 2017 11:55:48 -0500 Subject: [PATCH 10/12] Put $major_version in cache_dir to avoid upgrade incompatibility --- clipmenu | 4 +++- clipmenud | 3 ++- tests/test-clipmenu | 20 +++++++++++--------- tests/test-perf | 4 +++- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/clipmenu b/clipmenu index ce93af6..5b5f873 100755 --- a/clipmenu +++ b/clipmenu @@ -1,8 +1,10 @@ #!/bin/bash +major_version=3 + shopt -s nullglob -cache_dir=/tmp/clipmenu.$USER +cache_dir=/tmp/clipmenu.$major_version.$USER cache_file=$cache_dir/line_cache # It's okay to hardcode `-l 8` here as a sensible default without checking diff --git a/clipmenud b/clipmenud index c044665..c2afb8d 100755 --- a/clipmenud +++ b/clipmenud @@ -1,6 +1,7 @@ #!/bin/bash -cache_dir=/tmp/clipmenu.$USER/ +major_version=3 +cache_dir=/tmp/clipmenu.$major_version.$USER/ cache_file=$cache_dir/line_cache version=2.0.0 version_file=$cache_dir/version diff --git a/tests/test-clipmenu b/tests/test-clipmenu index fcc4a0b..708b377 100755 --- a/tests/test-clipmenu +++ b/tests/test-clipmenu @@ -3,7 +3,8 @@ set -e set -o pipefail -dir=/tmp/clipmenu.$USER +major_version=3 +dir=/tmp/clipmenu.$major_version.$USER cache_file=$dir/line_cache if [[ $0 == /* ]]; then @@ -59,18 +60,19 @@ EOF output=$(/tmp/clipmenu --foo bar 2>&1) -printf '%s\n' "$output" > /tmp/qq +temp=$(mktemp) +printf '%s\n' "$output" > "$temp" # Arguments are transparently passed to dmenu -grep -Fxq 'dmenu args: -l 8 --foo bar' <<< "$output" +grep -Fxq 'dmenu args: -l 8 --foo bar' "$temp" # Output from cache file should get to dmenu, reversed -grep -Fxq 'dmenu line 1 stdin: Selected text 2. (2 lines)' <<< "$output" -grep -Fxq 'dmenu line 2 stdin: Selected text. (2 lines)' <<< "$output" +grep -Fxq 'dmenu line 1 stdin: Selected text 2. (2 lines)' "$temp" +grep -Fxq 'dmenu line 2 stdin: Selected text. (2 lines)' "$temp" # xsel should copy both to clipboard *and* primary -grep -Fxq 'xsel args: --logfile /dev/null -i --clipboard' <<< "$output" -grep -Fxq 'xsel args: --logfile /dev/null -i --primary' <<< "$output" +grep -Fxq 'xsel args: --logfile /dev/null -i --clipboard' "$temp" +grep -Fxq 'xsel args: --logfile /dev/null -i --primary' "$temp" -grep -Fxq 'xsel line 1 stdin: Selected text.' <<< "$output" -grep -Fxq "xsel line 2 stdin: Yes, it's selected text." <<< "$output" +grep -Fxq 'xsel line 1 stdin: Selected text.' "$temp" +grep -Fxq "xsel line 2 stdin: Yes, it's selected text." "$temp" diff --git a/tests/test-perf b/tests/test-perf index bc75d5a..a393de6 100755 --- a/tests/test-perf +++ b/tests/test-perf @@ -1,10 +1,12 @@ #!/bin/bash +major_version=3 + msg() { printf '>>> %s\n' "$@" >&2 } -dir=/tmp/clipmenu.$USER +dir=/tmp/clipmenu.$major_version.$USER cache_file=$dir/line_cache log=$(mktemp) From 00e221559e104b3e7dcfeef1c9deb7b0c65114ca Mon Sep 17 00:00:00 2001 From: Chris Down Date: Fri, 17 Feb 2017 12:02:02 -0500 Subject: [PATCH 11/12] Set -x in test-clipmenu --- tests/test-clipmenu | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test-clipmenu b/tests/test-clipmenu index 708b377..3da51aa 100755 --- a/tests/test-clipmenu +++ b/tests/test-clipmenu @@ -1,5 +1,6 @@ #!/bin/bash +set -x set -e set -o pipefail @@ -61,6 +62,8 @@ EOF output=$(/tmp/clipmenu --foo bar 2>&1) temp=$(mktemp) +trap 'rm -f -- "$temp"' EXIT + printf '%s\n' "$output" > "$temp" # Arguments are transparently passed to dmenu From 9a5174123e00e1a37404586d4a582d7a3beec8fb Mon Sep 17 00:00:00 2001 From: Chris Down Date: Fri, 17 Feb 2017 12:06:10 -0500 Subject: [PATCH 12/12] Remove old version file, we now use versioned dirs --- clipmenud | 6 ------ 1 file changed, 6 deletions(-) diff --git a/clipmenud b/clipmenud index c2afb8d..8eca3a3 100755 --- a/clipmenud +++ b/clipmenud @@ -3,8 +3,6 @@ major_version=3 cache_dir=/tmp/clipmenu.$major_version.$USER/ cache_file=$cache_dir/line_cache -version=2.0.0 -version_file=$cache_dir/version get_first_line() { # Args: @@ -47,10 +45,6 @@ debug() { # shellcheck disable=SC2174 mkdir -p -m0700 "$cache_dir" -# We currently don't do anything with this, but in future we can use this -# version for the fabled clipmenu-fsck, or other migration logic. -printf '%s\n' "$version" > "$version_file" - declare -A last_data declare -A last_filename