From 4ca7ef8f7673e2a8922305db825ccbc1810ddb8c Mon Sep 17 00:00:00 2001 From: Uyanide Date: Wed, 11 Feb 2026 08:36:34 +0100 Subject: [PATCH] update fzfclip --- config/scripts/.local/scripts/fzfclip | 154 ++++++++++++++++++++++---- scripts | 1 + 2 files changed, 132 insertions(+), 23 deletions(-) create mode 120000 scripts diff --git a/config/scripts/.local/scripts/fzfclip b/config/scripts/.local/scripts/fzfclip index 76549d3..5648640 100755 --- a/config/scripts/.local/scripts/fzfclip +++ b/config/scripts/.local/scripts/fzfclip @@ -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="󰅍 > " \ diff --git a/scripts b/scripts new file mode 120000 index 0000000..736fee1 --- /dev/null +++ b/scripts @@ -0,0 +1 @@ +config/scripts/.local/scripts \ No newline at end of file