#!/bin/sh # shclip, by yosh # https://git.unix.dog/yosh/shclip set -eu TAB=" " BN="${0##*/}" # config file if exists SHCLIP_CONFIG_DIR="${SHCLIP_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/shclip}" . "$SHCLIP_CONFIG_DIR/config" SHCLIP_MAX_CLIPS="${SHCLIP_MAX_CLIPS:-25}" SHCLIP_MEMORY_LIMIT="${SHCLIP_MEMORY_LIMIT:-4100}" # memory limit in kb SHCLIP_ENABLED="${SHCLIP_ENABLED-1}" SHCLIP_FILTER_ENABLED="${SHCLIP_FILTER_ENABLED-}" SHCLIP_SYNC_CLIPBOARD="${SHCLIP_SYNC_CLIPBOARD-1}" SHCLIP_CACHE_PASSWORDS="${SHCLIP_CACHE_PASSWORDS-}" SHCLIP_TEMP_DIR="${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/shclip-$USER-$DISPLAY" errecho() { printf '%s\n' "$*" >&2; } exists() { command -v "$@" >/dev/null 2>&1; } notify() { errecho "$*" exists notify-send && notify-send "$*" : } fail() { notify "error: $BN: $*" exit 1 } cleanup() { trap 'exit' INT HUP QUIT TERM EXIT set +e rmdir "$SHCLIP_TEMP_DIR/lock" kill "$clipnotify_pid" exit } toggle() { if [ -z "$SHCLIP_ENABLED" ]; then SHCLIP_ENABLED=1 notify "shclip enabled!" else SHCLIP_ENABLED="" notify "shclip disabled!" fi kill "$clipnotify_pid" } filter_toggle() { if [ -z "$SHCLIP_FILTER_ENABLED" ]; then SHCLIP_FILTER_ENABLED=1 notify "shclip filter enabled!" else SHCLIP_FILTER_ENABLED="" notify "shclip filter disabled!" fi kill "$clipnotify_pid" } # setup process setup() { mkdir -p -m 700 "$SHCLIP_TEMP_DIR" # test the temp/lock directory if mkdir -m 700 "$SHCLIP_TEMP_DIR/lock"; then errecho "successfully acquired lock" else fail "cannot acquire lock. is another instance running? if not, delete the directory at $SHCLIP_TEMP_DIR/lock" fi trap 'cleanup' INT HUP QUIT TERM EXIT trap 'toggle' USR1 trap 'filter_toggle' USR2 BUFFER="$SHCLIP_TEMP_DIR/buffer" ORDER="$SHCLIP_TEMP_DIR/order" PID="$SHCLIP_TEMP_DIR/pid" printf '%s' $$ > "$PID" # create ORDER file if [ ! -f "$ORDER" ]; then digits=${#SHCLIP_MAX_CLIPS} i=1 while [ "$i" -le "$SHCLIP_MAX_CLIPS" ]; do printf "%0${digits}d.clip\n" "$i" i="$((i + 1))" done > "$ORDER" fi } # reclip top of the stack reclip() { n="$(head -n 1 "$ORDER")" mime="$(file -bi "$SHCLIP_TEMP_DIR/$n")" xclip -sel clipboard -t "${mime%%;*}" "$SHCLIP_TEMP_DIR/$n" } updateclip() { # some good variables to have firstind="$(head -n 1 "$ORDER")" # top of clip stack lastind="$(tail -n 1 "$ORDER")" # bottom of stack targets="$(xclip -sel clipboard -t TARGETS -o)" || { reclip && return 0; } # targets of clip, will also return if clip doesn't exist # this covers if we've copied a file in a GUI file manager # probably don't want that in your history case "$targets" in *gnome-copied-files*) return 0 ;; *image/png*) xclip -sel clipboard -t image/png -o > "$BUFFER" ;; *x-kde-passwordManagerHint*) if [ -n "$SHCLIP_CACHE_PASSWORDS" ]; then xclip -sel clipboard -t x-kde-passwordManagerHint -o > "$BUFFER" else return 0 fi ;; *) xclip -sel clipboard -o > "$BUFFER" ;; esac # check if clip exceeds memory limit sz="$(du -k "$BUFFER")" if [ "${sz%%${TAB}*}" -gt "$SHCLIP_MEMORY_LIMIT" ]; then notify 'clip exceeds memory threshold! not caching...' rm -f "$BUFFER" return 0 fi # buffer mime matching mime="$(file -bi "$BUFFER")" case "${mime%%;}" in inode/x-empty*) # empty clip reclip return 0 ;; text*) # apply text filters [ -n "$SHCLIP_FILTER_ENABLED" ] && [ -f "$SHCLIP_CONFIG_DIR/filter.sh" ] && \ sh "$SHCLIP_CONFIG_DIR/filter.sh" < "$BUFFER" | diff "$BUFFER" - | patch "$BUFFER" xclip -sel clipboard "$BUFFER" [ -n "$SHCLIP_SYNC_CLIPBOARD" ] && xclip -sel primary "$BUFFER" ;; *) ;; esac # top of stack & buffer are identical, don't cache if cmp "$BUFFER" "$SHCLIP_TEMP_DIR/$firstind" >/dev/null 2>&1; then errecho 'duplicate clip! not caching...' rm -f "$BUFFER" return 0 fi mv -f "$BUFFER" "$SHCLIP_TEMP_DIR/$lastind" # edit order file in place ed -s "$ORDER" <<-EOF ${SHCLIP_MAX_CLIPS}m0 wq EOF } [ -z "${DISPLAY:-}" ] && fail 'Cannot find $DISPLAY. Is your X server running?' # parse options while getopts :hn:f:s:p:c:m OPT; do case $OPT in h) cat >&2 <<-EOF please refer to README for general usage: https://git.unix.dog/yosh/shclip options that exist (overrides config file): -h show this help -n enable/disable shclip on startup -f enable/disable filtering -s enable/disable syncing clipboard to primary -p enable/disable caching kde passwords -c set max clips to clips -m set memory limit to kibibytes EOF ;; n) [ "$OPTARG" = "on" ] && SHCLIP_ENABLED=1 || SHCLIP_ENABLED="" ;; f) [ "$OPTARG" = "on" ] && SHCLIP_FILTER_ENABLED=1 || SHCLIP_FILTER_ENABLED="" ;; s) [ "$OPTARG" = "on" ] && SHCLIP_SYNC_CLIPBOARD=1 || SHCLIP_SYNC_CLIPBOARD="" ;; p) [ "$OPTARG" = "on" ] && SHCLIP_CACHE_PASSWORDS=1 || SHCLIP_CACHE_PASSWORDS="" ;; c) SHCLIP_MAX_CLIPS=$OPTARG ;; m) SHCLIP_MEMORY_LIMIT=$OPTARG ;; *) fail "$BN: unknown option: -$OPTARG" ;; esac done setup while if [ -z "$SHCLIP_ENABLED" ]; then : else updateclip fi do set +e clipnotify -s clipboard & clipnotify_pid=$! wait set -e done