206 lines
5.1 KiB
Bash
Executable File
206 lines
5.1 KiB
Bash
Executable File
#!/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 <on|off> enable/disable shclip on startup
|
|
-f <on|off> enable/disable filtering
|
|
-s <on|off> enable/disable syncing clipboard to primary
|
|
-p <on|off> enable/disable caching kde passwords
|
|
-c <num> set max clips to <num> clips
|
|
-m <num> set memory limit to <num> 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
|