update fzfclip
This commit is contained in:
@@ -1,15 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck disable=SC2016
|
||||
|
||||
# Description:
|
||||
# View and manage clipboard history using fzf, with support for
|
||||
# image preview in compatible terminals.
|
||||
# Requirements:
|
||||
# - fzf
|
||||
# - cliphist
|
||||
# - wl-clipboard
|
||||
# - fzf
|
||||
# - sixel-query, kgp-query, iterm2-query from this repository
|
||||
# - chafa (for image preview)
|
||||
# - python with urllib (for URL unquoting)
|
||||
# - chafa (optional, for image preview)
|
||||
# - ffmpegthumbnailer (optional, for video thumbnails)
|
||||
# Credits:
|
||||
# - Original idea and some code adapted from https://github.com/SHORiN-KiWATA/shorinclip
|
||||
@@ -45,6 +44,9 @@ _cleanup() {
|
||||
if [ -n "${WATCH_PID:-}" ]; then
|
||||
kill "$WATCH_PID" 2>/dev/null || true
|
||||
fi
|
||||
if [ -n "${FZF_SOCKET:-}" ] && [ -S "$FZF_SOCKET" ]; then
|
||||
rm -f "$FZF_SOCKET"
|
||||
fi
|
||||
}
|
||||
trap _cleanup EXIT
|
||||
|
||||
@@ -58,12 +60,111 @@ export C_RESET='\x1b[0m'
|
||||
|
||||
# Check for terminal graphics support and set environment variables accordingly
|
||||
|
||||
graphics-query() {
|
||||
# Port of [graphics-query](https://github.com/Uyanide/dotfiles/blob/main/config/scripts/.local/scripts/graphics-query)
|
||||
|
||||
# Ensure in a interactive terminal
|
||||
[ ! -t 0 ] && return
|
||||
|
||||
(
|
||||
# Construct query
|
||||
KGP_QUERY_ID=$RANDOM
|
||||
KGP_QUERY_CODE=$(printf "\033_Gi=%d,s=1,v=1,a=q,t=d,f=24;AAAA\033\\" "$KGP_QUERY_ID")
|
||||
ITERM2_QUERY_CODE=$(printf "\033]1337;ReportCellSize\a")
|
||||
KGP_EXPECTED_RESPONSE=$(printf "\033_Gi=%d;OK\033\\" "$KGP_QUERY_ID")
|
||||
ITERM2_EXPECTED_RESPONSE=$(printf "\033]1337;") # followed by "ReportCellSize=...", but only the prefix is enough
|
||||
FENCE_CODE=$(printf "\033[c")
|
||||
|
||||
# Set terminal to raw mode with timeout
|
||||
stty_orig=$(stty -g)
|
||||
trap 'stty "$stty_orig"' EXIT
|
||||
stty -echo -icanon min 1 time 0
|
||||
|
||||
printf "%s%s%s" "$ITERM2_QUERY_CODE" "$KGP_QUERY_CODE" "$FENCE_CODE" > /dev/tty
|
||||
|
||||
support_kgp=0
|
||||
support_iterm2=0
|
||||
support_sixel=0
|
||||
|
||||
response=""
|
||||
while true; do
|
||||
IFS= read -r -N 1 -t 0.3 char || {
|
||||
[ -z "$char" ] && break
|
||||
}
|
||||
|
||||
response+="$char"
|
||||
|
||||
if [[ "$response" == *"$KGP_EXPECTED_RESPONSE"* ]]; then
|
||||
support_kgp=1
|
||||
fi
|
||||
|
||||
if [[ "$response" == *"$ITERM2_EXPECTED_RESPONSE"* ]]; then
|
||||
support_iterm2=1
|
||||
fi
|
||||
|
||||
if [[ "$response" == *$'\033['*'c' ]]; then
|
||||
break
|
||||
fi
|
||||
|
||||
if [ ${#response} -gt 1024 ]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$response" =~ $'\x1b'\[\?([0-9;]*)c ]]; then
|
||||
params="${BASH_REMATCH[1]}"
|
||||
|
||||
IFS=';' read -ra codes <<< "$params"
|
||||
|
||||
for code in "${codes[@]}"; do
|
||||
if [[ "$code" == "4" ]]; then
|
||||
support_sixel=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$support_kgp" -eq 1 ]; then
|
||||
echo "kitty"
|
||||
fi
|
||||
|
||||
if [ "$support_iterm2" -eq 1 ]; then
|
||||
echo "iterm"
|
||||
fi
|
||||
|
||||
if [ "$support_sixel" -eq 1 ]; then
|
||||
echo "sixels"
|
||||
fi
|
||||
)
|
||||
}
|
||||
|
||||
SUPPORT_ICAT=0
|
||||
SUPPORT_SIXEL=0
|
||||
SUPPORT_ITERM2=0
|
||||
|
||||
ENABLE_ICAT=0
|
||||
ENABLE_SIXEL=0
|
||||
ENABLE_ITERM2=0
|
||||
|
||||
_check_graphics_support() {
|
||||
# type graphics-query &>/dev/null || return
|
||||
local result
|
||||
result=$(graphics-query)
|
||||
if [[ "$result" == *"kitty"* ]]; then
|
||||
SUPPORT_ICAT=1
|
||||
elif [[ "$result" == *"sixels"* ]]; then
|
||||
SUPPORT_SIXEL=1
|
||||
elif [[ "$result" == *"iterm"* ]]; then
|
||||
SUPPORT_ITERM2=1
|
||||
fi
|
||||
}
|
||||
|
||||
_check_kitty_icat() {
|
||||
# workaround for WezTerm
|
||||
# # workaround for WezTerm
|
||||
if [ -n "${WEZTERM_EXECUTABLE:-}" ]; then
|
||||
return 1
|
||||
fi
|
||||
kgp-query
|
||||
[[ "$SUPPORT_ICAT" -eq 1 ]]
|
||||
}
|
||||
|
||||
_check_sixel() {
|
||||
@@ -74,24 +175,21 @@ _check_sixel() {
|
||||
elif [ -n "${TMUX:-}" ]; then
|
||||
return 1
|
||||
fi
|
||||
sixel-query
|
||||
[[ "$SUPPORT_SIXEL" -eq 1 ]]
|
||||
}
|
||||
|
||||
_check_iterm2() {
|
||||
iterm2-query
|
||||
[[ "$SUPPORT_ITERM2" -eq 1 ]]
|
||||
}
|
||||
|
||||
ENABLE_ICAT=0
|
||||
ENABLE_SIXEL=0
|
||||
ENABLE_ITERM2=0
|
||||
|
||||
# Priority: KGP > sixel > iterm2
|
||||
_check_graphics_support
|
||||
if _check_kitty_icat; then
|
||||
export ENABLE_ICAT=1
|
||||
ENABLE_ICAT=1
|
||||
elif _check_sixel; then
|
||||
export ENABLE_SIXEL=1
|
||||
ENABLE_SIXEL=1
|
||||
elif _check_iterm2; then
|
||||
export ENABLE_ITERM2=1
|
||||
ENABLE_ITERM2=1
|
||||
fi
|
||||
|
||||
export ENABLE_ICAT
|
||||
@@ -101,6 +199,9 @@ export ENABLE_ITERM2
|
||||
# Preview functions
|
||||
|
||||
_clear_preview() {
|
||||
# chafa --clear will simply send '\x1b[H\x1b[2J' which may not work
|
||||
# for images displayed with KGP, so we send the specific clear sequence
|
||||
# manually in this case.
|
||||
if [ "$ENABLE_ICAT" -eq 1 ]; then
|
||||
printf "\x1b_Ga=d\x1b\\"
|
||||
fi
|
||||
@@ -109,6 +210,14 @@ export -f _clear_preview
|
||||
|
||||
_preview_image() {
|
||||
local file="$1"
|
||||
if ! type chafa >/dev/null 2>&1; then
|
||||
text="Preview not available (chafa not found)."$'\n'
|
||||
text+="Image: $file"
|
||||
_preview_text "$text"
|
||||
return
|
||||
fi
|
||||
# Though chafa is able to detect which output format to use based on the terminal capabilities,
|
||||
# it is yet not always reliable, so we force it based on our checks.
|
||||
if [ "$ENABLE_ICAT" -eq 1 ]; then
|
||||
chafa -f kitty --size="${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}" "$file"
|
||||
elif [ "$ENABLE_SIXEL" -eq 1 ]; then
|
||||
@@ -136,13 +245,13 @@ _preview_video() {
|
||||
if type ffmpegthumbnailer &>/dev/null; then
|
||||
ffmpegthumbnailer -i "$path" -o "$thumb_file" -s 480 -t 0 >/dev/null 2>&1
|
||||
else
|
||||
_preview_text "ffmpegthumbnailer not installed, cannot generate thumbnail for video."
|
||||
_preview_text "Thumbnail not available (ffmpegthumbnailer not found)."$'\n'
|
||||
fi
|
||||
fi
|
||||
if [ -s "$thumb_file" ]; then
|
||||
_preview_image "$thumb_file"
|
||||
else
|
||||
_preview_text "Video: $path (No thumbnail)"
|
||||
_preview_text "Video: $path"
|
||||
fi
|
||||
}
|
||||
export -f _preview_video
|
||||
@@ -230,8 +339,6 @@ format_clip_list() {
|
||||
sed -E \
|
||||
-e "s/(\t).*\.(mp4|mkv|webm|avi|mov|flv|wmv)$/\1${C_TERTIARY}[VIDEO]File.\2${C_RESET}/" \
|
||||
-e "s/(\t)file:\/\/.*\.(mp4|mkv|webm|avi|mov|flv|wmv)$/\1${C_TERTIARY}[VIDEO]Url.\2${C_RESET}/" \
|
||||
-e "s/(\t).*src=\"file:\/\/.*[qQ][qQ].*/\1${C_PRIMARY}[IMG_HTML]QQ${C_RESET}/" \
|
||||
-e "s/(\t)file:\/\/.*xwechat.*temp.*/\1${C_PRIMARY}[IMG]WeChat${C_RESET}/" \
|
||||
-e "s/(\t)file:\/\/.*\.gif$/\1${C_PRIMARY}[IMG]Url.gif${C_RESET}/" \
|
||||
-e "s/(\t)file:\/\/.*\.(png|jpg|jpeg|webp|bmp)$/\1${C_TERTIARY}[IMG]Url.\2${C_RESET}/" \
|
||||
-e "s/(\t)file:\/\/.*/\1${C_CYAN}[URL]File${C_RESET}/" \
|
||||
@@ -283,9 +390,10 @@ export -f copy_selection
|
||||
|
||||
# Reload mechanism
|
||||
|
||||
FZF_PORT=$(shuf -i 10000-60000 -n 1)
|
||||
RUNTIME_DIR="${XDG_RUNTIME_DIR:-/tmp}"
|
||||
FZF_SOCKET=$(mktemp -u "$RUNTIME_DIR/fzfclip.XXXXXX.sock")
|
||||
RELOAD_CMD="cliphist list | format_clip_list | add_num"
|
||||
wl-paste --watch bash -c "curl -s -X POST -d 'reload($RELOAD_CMD)' http://localhost:$FZF_PORT" >/dev/null 2>&1 &
|
||||
wl-paste --watch bash -c "curl -s --unix-socket '$FZF_SOCKET' -X POST -d 'reload($RELOAD_CMD)' http://localhost" >/dev/null 2>&1 &
|
||||
WATCH_PID=$!
|
||||
|
||||
# Ensure terminal is large enough
|
||||
@@ -298,9 +406,9 @@ while [[ $(tput cols) -lt 35 || $(tput lines) -lt 25 ]]; do
|
||||
[ "$wait_timeout" -eq 0 ] && exit 1
|
||||
done
|
||||
|
||||
cliphist list | format_clip_list | add_num | fzf \
|
||||
$RELOAD_CMD | fzf \
|
||||
--ansi \
|
||||
--listen "$FZF_PORT" \
|
||||
--listen "$FZF_SOCKET" \
|
||||
--bind "ctrl-r:reload($RELOAD_CMD)" \
|
||||
--bind "ctrl-x:execute-silent(bash -c 'cliphist delete <<< \"\$1\"' -- {})+reload($RELOAD_CMD)" \
|
||||
--prompt=" > " \
|
||||
|
||||
Reference in New Issue
Block a user