update fzfclip
This commit is contained in:
@@ -1,15 +1,14 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# shellcheck disable=SC2016
|
|
||||||
|
|
||||||
# Description:
|
# Description:
|
||||||
# View and manage clipboard history using fzf, with support for
|
# View and manage clipboard history using fzf, with support for
|
||||||
# image preview in compatible terminals.
|
# image preview in compatible terminals.
|
||||||
# Requirements:
|
# Requirements:
|
||||||
|
# - fzf
|
||||||
# - cliphist
|
# - cliphist
|
||||||
# - wl-clipboard
|
# - wl-clipboard
|
||||||
# - fzf
|
# - python with urllib (for URL quoting/unquoting)
|
||||||
# - sixel-query, kgp-query, iterm2-query from this repository
|
# - chafa (optional, for image preview)
|
||||||
# - chafa (for image preview)
|
|
||||||
# - ffmpegthumbnailer (optional, for video thumbnails)
|
# - ffmpegthumbnailer (optional, for video thumbnails)
|
||||||
# Credits:
|
# Credits:
|
||||||
# - Original idea and some code adapted from https://github.com/SHORiN-KiWATA/shorinclip
|
# - Original idea and some code adapted from https://github.com/SHORiN-KiWATA/shorinclip
|
||||||
@@ -38,9 +37,21 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
_cleanup() {
|
||||||
|
if [ -n "${CACHE_DIR:-}" ] && [ -d "$CACHE_DIR" ]; then
|
||||||
|
rm -rf "$CACHE_DIR"
|
||||||
|
fi
|
||||||
|
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
|
||||||
|
|
||||||
CACHE_DIR=$(mktemp -d)
|
CACHE_DIR=$(mktemp -d)
|
||||||
export CACHE_DIR
|
export CACHE_DIR
|
||||||
# trap rm later
|
|
||||||
|
|
||||||
export C_TERTIARY='\x1b[1;35m'
|
export C_TERTIARY='\x1b[1;35m'
|
||||||
export C_PRIMARY='\x1b[1;34m'
|
export C_PRIMARY='\x1b[1;34m'
|
||||||
@@ -49,12 +60,111 @@ export C_RESET='\x1b[0m'
|
|||||||
|
|
||||||
# Check for terminal graphics support and set environment variables accordingly
|
# 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() {
|
_check_kitty_icat() {
|
||||||
# workaround for WezTerm
|
# # workaround for WezTerm
|
||||||
if [ -n "${WEZTERM_EXECUTABLE:-}" ]; then
|
if [ -n "${WEZTERM_EXECUTABLE:-}" ]; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
kgp-query
|
[[ "$SUPPORT_ICAT" -eq 1 ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
_check_sixel() {
|
_check_sixel() {
|
||||||
@@ -65,36 +175,70 @@ _check_sixel() {
|
|||||||
elif [ -n "${TMUX:-}" ]; then
|
elif [ -n "${TMUX:-}" ]; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
sixel-query
|
[[ "$SUPPORT_SIXEL" -eq 1 ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
_check_iterm2() {
|
_check_iterm2() {
|
||||||
iterm2-query
|
[[ "$SUPPORT_ITERM2" -eq 1 ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
ENABLE_ICAT=0
|
|
||||||
ENABLE_SIXEL=0
|
|
||||||
ENABLE_ITERM2=0
|
|
||||||
|
|
||||||
# Priority: KGP > sixel > iterm2
|
# Priority: KGP > sixel > iterm2
|
||||||
|
_check_graphics_support
|
||||||
if _check_kitty_icat; then
|
if _check_kitty_icat; then
|
||||||
export ENABLE_ICAT=1
|
ENABLE_ICAT=1
|
||||||
elif _check_sixel; then
|
elif _check_sixel; then
|
||||||
export ENABLE_SIXEL=1
|
ENABLE_SIXEL=1
|
||||||
elif _check_iterm2; then
|
elif _check_iterm2; then
|
||||||
export ENABLE_ITERM2=1
|
ENABLE_ITERM2=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export ENABLE_ICAT
|
export ENABLE_ICAT
|
||||||
export ENABLE_SIXEL
|
export ENABLE_SIXEL
|
||||||
export ENABLE_ITERM2
|
export ENABLE_ITERM2
|
||||||
|
|
||||||
|
# URL handling (for file:// URLs)
|
||||||
|
|
||||||
|
url_unquote() {
|
||||||
|
if type python3 &>/dev/null; then
|
||||||
|
python3 -c "import sys, urllib.parse; print(urllib.parse.unquote(sys.stdin.read().strip()), end='')"
|
||||||
|
else
|
||||||
|
cat
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
url_quote() {
|
||||||
|
if type python3 &>/dev/null; then
|
||||||
|
python3 -c "import sys, urllib.parse; print(urllib.parse.quote(sys.stdin.read().strip()), end='')"
|
||||||
|
else
|
||||||
|
cat
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
export -f url_unquote
|
||||||
|
export -f url_quote
|
||||||
|
|
||||||
# Preview functions
|
# 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
|
||||||
|
}
|
||||||
|
export -f _clear_preview
|
||||||
|
|
||||||
_preview_image() {
|
_preview_image() {
|
||||||
local file="$1"
|
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
|
if [ "$ENABLE_ICAT" -eq 1 ]; then
|
||||||
printf "\x1b_Ga=d\x1b\\"
|
|
||||||
chafa -f kitty --size="${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}" "$file"
|
chafa -f kitty --size="${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}" "$file"
|
||||||
elif [ "$ENABLE_SIXEL" -eq 1 ]; then
|
elif [ "$ENABLE_SIXEL" -eq 1 ]; then
|
||||||
chafa -f sixels --size="${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}" "$file"
|
chafa -f sixels --size="${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}" "$file"
|
||||||
@@ -108,40 +252,61 @@ export -f _preview_image
|
|||||||
|
|
||||||
_preview_text() {
|
_preview_text() {
|
||||||
local content="$1"
|
local content="$1"
|
||||||
if [ "$ENABLE_ICAT" -eq 1 ]; then
|
printf "%s" "$content" | head -n 100
|
||||||
printf "\x1b_Ga=d\x1b\\"
|
|
||||||
fi
|
|
||||||
echo "$content" | head -n 100
|
|
||||||
}
|
}
|
||||||
export -f _preview_text
|
export -f _preview_text
|
||||||
|
|
||||||
|
_preview_video() {
|
||||||
|
local video_hash thumb_file path
|
||||||
|
path="$1"
|
||||||
|
video_hash=$(echo "$path" | md5sum | cut -d" " -f1)
|
||||||
|
thumb_file="$CACHE_DIR/$video_hash.png"
|
||||||
|
if [ ! -f "$thumb_file" ]; then
|
||||||
|
if type ffmpegthumbnailer &>/dev/null; then
|
||||||
|
ffmpegthumbnailer -i "$path" -o "$thumb_file" -s 480 -t 0 >/dev/null 2>&1
|
||||||
|
else
|
||||||
|
_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"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
export -f _preview_video
|
||||||
|
|
||||||
|
# Kinda buggy right now
|
||||||
|
# _preview_audio() {
|
||||||
|
# local path="$1"
|
||||||
|
# if type mpv &>/dev/null; then
|
||||||
|
# echo "Playing audio: $path" >&2
|
||||||
|
# exec mpv --no-video --keep-open=no --loop-file=no --loop-playlist=no "$path" &>/dev/null
|
||||||
|
# else
|
||||||
|
# _preview_text "Audio: $path"
|
||||||
|
# fi
|
||||||
|
# }
|
||||||
|
# export -f _preview_audio
|
||||||
|
|
||||||
_preview_file() {
|
_preview_file() {
|
||||||
path="$1"
|
path="$1"
|
||||||
path_mime=$(file -b --mime-type "$path")
|
path_mime=$(file -b --mime-type "$path")
|
||||||
if [[ $path_mime =~ image ]]; then
|
if [[ $path_mime =~ image ]]; then
|
||||||
_preview_image "$path"
|
_preview_image "$path"
|
||||||
elif [[ "$path_mime" =~ video ]]; then
|
elif [[ "$path_mime" =~ video ]]; then
|
||||||
video_hash=$(echo "$path" | md5sum | cut -d" " -f1)
|
_preview_video "$path"
|
||||||
thumb_file="$CACHE_DIR/$video_hash.png"
|
# elif [[ "$path_mime" =~ audio ]]; then
|
||||||
if [ ! -f "$thumb_file" ]; then
|
# _preview_audio "$path"
|
||||||
if command -v 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."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if [ -s "$thumb_file" ]; then
|
|
||||||
_preview_image "$thumb_file"
|
|
||||||
else
|
|
||||||
_preview_text "Video: $path (No thumbnail)"
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
_preview_text "$path"
|
_preview_text "$path"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
export -f _preview_file
|
export -f _preview_file
|
||||||
|
|
||||||
|
|
||||||
preview() {
|
preview() {
|
||||||
|
_clear_preview
|
||||||
|
|
||||||
entry="$1"
|
entry="$1"
|
||||||
|
|
||||||
content=$(echo "$entry" | cut -f2-)
|
content=$(echo "$entry" | cut -f2-)
|
||||||
@@ -154,31 +319,31 @@ preview() {
|
|||||||
[ -f "$cache_file" ] || echo "$entry" | cliphist decode >"$cache_file"
|
[ -f "$cache_file" ] || echo "$entry" | cliphist decode >"$cache_file"
|
||||||
_preview_image "$cache_file"
|
_preview_image "$cache_file"
|
||||||
|
|
||||||
elif [ "$mimeType" = "text/html" ] && echo "$content" | grep -q QQ; then
|
|
||||||
qq_img_file=$(echo "$entry" | cliphist decode | grep -oP "^<img src=\"file://\K[^\"]+")
|
|
||||||
#qq_ext="${qq_img_file##*.}"
|
|
||||||
#qq_img_cache_file=$CACHE_DIR/$id.$qq_ext
|
|
||||||
#cp $qq_img_file $qq_img_cache_file
|
|
||||||
if [ -f "$qq_img_file" ]; then
|
|
||||||
_preview_image "$qq_img_file"
|
|
||||||
else
|
|
||||||
_preview_text "$qq_img_file does not exist."
|
|
||||||
fi
|
|
||||||
|
|
||||||
elif path=$(echo "$entry" | cliphist decode) && [[ "$path" == /* ]]; then
|
elif path=$(echo "$entry" | cliphist decode) && [[ "$path" == /* ]]; then
|
||||||
if [ -e "$path" ]; then
|
if [ -e "$path" ]; then
|
||||||
_preview_file "$path"
|
_preview_file "$path"
|
||||||
else
|
else
|
||||||
_preview_text "$path does not exist."
|
_preview_text "$path does not exist."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
elif decoded=$(echo "$entry" | cliphist decode) && [[ "$decoded" == file://* ]]; then
|
elif decoded=$(echo "$entry" | cliphist decode) && [[ "$decoded" == file://* ]]; then
|
||||||
raw_path="${decoded#file://}"
|
paths=()
|
||||||
raw_path=$(echo "$raw_path" | python -c "import sys, urllib.parse; print(urllib.parse.unquote(sys.stdin.read().strip()))")
|
for path in $decoded; do
|
||||||
if [ -e "$raw_path" ]; then
|
raw_path="${path#file://}"
|
||||||
_preview_file "$raw_path"
|
raw_path=$(echo "$raw_path" | url_unquote)
|
||||||
|
paths+=("$raw_path")
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "${#paths[@]}" -eq 1 ] && [ -e "${paths[0]}" ]; then
|
||||||
|
_preview_file "${paths[0]}"
|
||||||
else
|
else
|
||||||
_preview_text "$raw_path does not exist."
|
text="Multiple files:"$'\n'
|
||||||
|
for p in "${paths[@]}"; do
|
||||||
|
text+="$p"$'\n'
|
||||||
|
done
|
||||||
|
_preview_text "$text"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
else
|
else
|
||||||
if [ "$ENABLE_ICAT" = 1 ]; then
|
if [ "$ENABLE_ICAT" = 1 ]; then
|
||||||
printf "\x1b_Ga=d\x1b\\"
|
printf "\x1b_Ga=d\x1b\\"
|
||||||
@@ -194,8 +359,6 @@ format_clip_list() {
|
|||||||
sed -E \
|
sed -E \
|
||||||
-e "s/(\t).*\.(mp4|mkv|webm|avi|mov|flv|wmv)$/\1${C_TERTIARY}[VIDEO]File.\2${C_RESET}/" \
|
-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)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:\/\/.*\.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:\/\/.*\.(png|jpg|jpeg|webp|bmp)$/\1${C_TERTIARY}[IMG]Url.\2${C_RESET}/" \
|
||||||
-e "s/(\t)file:\/\/.*/\1${C_CYAN}[URL]File${C_RESET}/" \
|
-e "s/(\t)file:\/\/.*/\1${C_CYAN}[URL]File${C_RESET}/" \
|
||||||
@@ -220,52 +383,34 @@ copy_selection() {
|
|||||||
local mime
|
local mime
|
||||||
mime=$(echo "$decoded" | file -b --mime-type -)
|
mime=$(echo "$decoded" | file -b --mime-type -)
|
||||||
|
|
||||||
url_encode() {
|
|
||||||
python -c "import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1]))" "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Image
|
# Image
|
||||||
if [[ "$mime" =~ image ]]; then
|
if [[ "$mime" =~ image ]]; then
|
||||||
echo "$decoded" | wl-copy
|
printf "%s" "$decoded" | wl-copy
|
||||||
|
|
||||||
# HTML with QQ image
|
|
||||||
elif [ "$mime" = "text/html" ]; then
|
|
||||||
local qq_src
|
|
||||||
qq_src=$(echo "$decoded" | grep -oP "^<img src=\"file://\K[^\"]+")
|
|
||||||
|
|
||||||
if [ -f "$qq_src" ]; then
|
|
||||||
local encoded_path
|
|
||||||
encoded_path=$(url_encode "$qq_src")
|
|
||||||
|
|
||||||
echo "file://$encoded_path" | wl-copy --type text/uri-list
|
|
||||||
else
|
|
||||||
echo "$decoded" | wl-copy
|
|
||||||
fi
|
|
||||||
|
|
||||||
# URL starting with file://
|
# URL starting with file://
|
||||||
elif [[ "$decoded" == file://* ]]; then
|
elif [[ "$decoded" == file://* ]]; then
|
||||||
echo "$decoded" | wl-copy --type text/uri-list
|
printf "%s" "$decoded" | wl-copy --type text/uri-list
|
||||||
|
|
||||||
# file path
|
# file path
|
||||||
elif [[ "$decoded" == /* ]] && [ -e "$decoded" ]; then
|
elif [[ "$decoded" == /* ]] && [ -e "$decoded" ]; then
|
||||||
local encoded_path
|
local encoded_path
|
||||||
encoded_path=$(url_encode "$decoded")
|
encoded_path=$(echo "$decoded" | url_quote)
|
||||||
echo "file://$encoded_path" | wl-copy --type text/uri-list
|
printf "%s" "file://$encoded_path" | wl-copy --type text/uri-list
|
||||||
|
|
||||||
# Other data, just copy
|
# Other data, just copy
|
||||||
else
|
else
|
||||||
echo "$decoded" | wl-copy
|
printf "%s" "$decoded" | wl-copy
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
export -f copy_selection
|
export -f copy_selection
|
||||||
|
|
||||||
# Reload mechanism
|
# 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"
|
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 &
|
||||||
WATCH_PID=$!
|
WATCH_PID=$!
|
||||||
trap 'rm -rf "$CACHE_DIR"; kill $WATCH_PID 2>/dev/null' EXIT
|
|
||||||
|
|
||||||
# Ensure terminal is large enough
|
# Ensure terminal is large enough
|
||||||
|
|
||||||
@@ -277,9 +422,9 @@ while [[ $(tput cols) -lt 35 || $(tput lines) -lt 25 ]]; do
|
|||||||
[ "$wait_timeout" -eq 0 ] && exit 1
|
[ "$wait_timeout" -eq 0 ] && exit 1
|
||||||
done
|
done
|
||||||
|
|
||||||
cliphist list | format_clip_list | add_num | fzf \
|
$RELOAD_CMD | fzf \
|
||||||
--ansi \
|
--ansi \
|
||||||
--listen "$FZF_PORT" \
|
--listen "$FZF_SOCKET" \
|
||||||
--bind "ctrl-r:reload($RELOAD_CMD)" \
|
--bind "ctrl-r:reload($RELOAD_CMD)" \
|
||||||
--bind "ctrl-x:execute-silent(bash -c 'cliphist delete <<< \"\$1\"' -- {})+reload($RELOAD_CMD)" \
|
--bind "ctrl-x:execute-silent(bash -c 'cliphist delete <<< \"\$1\"' -- {})+reload($RELOAD_CMD)" \
|
||||||
--prompt=" > " \
|
--prompt=" > " \
|
||||||
@@ -292,4 +437,5 @@ cliphist list | format_clip_list | add_num | fzf \
|
|||||||
--delimiter '\t' \
|
--delimiter '\t' \
|
||||||
--preview-window=down:60%,wrap \
|
--preview-window=down:60%,wrap \
|
||||||
--preview "preview {}" \
|
--preview "preview {}" \
|
||||||
--bind "enter:execute-silent(bash -c 'copy_selection \"\$1\"' -- {})+accept"
|
--bind "enter:execute-silent(bash -c 'copy_selection \"\$1\"' -- {})+accept" \
|
||||||
|
|| [ $? -eq 141 ]
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
# Wrapper for fzfclip to ensure only one instance is
|
# Wrapper for fzfclip to ensure only one instance is
|
||||||
# running and to launch it in ghostty.
|
# running and to launch it in ghostty.
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
exec {LOCK_FD}>/tmp/"$(basename "$0")".lock
|
exec {LOCK_FD}>/tmp/"$(basename "$0")".lock
|
||||||
|
|
||||||
flock -n "$LOCK_FD" || {
|
flock -n "$LOCK_FD" || {
|
||||||
@@ -13,4 +11,4 @@ flock -n "$LOCK_FD" || {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
ghostty -e fzfclip "$@"
|
ghostty -e fzfclip "$@" {LOCK_FD}>&-
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ mp4_offset=$((offset - 3))
|
|||||||
tail -c "+$mp4_offset" "$file" > "$tmp_video"
|
tail -c "+$mp4_offset" "$file" > "$tmp_video"
|
||||||
|
|
||||||
play_cmd=()
|
play_cmd=()
|
||||||
if command -v mpv >/dev/null 2>&1; then
|
if type mpv >/dev/null 2>&1; then
|
||||||
play_cmd=(mpv --title="Live Photo View: $file" --keep-open=yes --loop-file=yes --loop-playlist=no --idle=yes)
|
play_cmd=(mpv --title="Live Photo View: $file" --keep-open=yes --loop-file=yes --loop-playlist=no --idle=yes)
|
||||||
elif command -v vlc >/dev/null 2>&1; then
|
elif type vlc >/dev/null 2>&1; then
|
||||||
play_cmd=(vlc --meta-title="Live Photo View: $file")
|
play_cmd=(vlc --meta-title="Live Photo View: $file")
|
||||||
else
|
else
|
||||||
echo "Error: No suitable media player found." >&2
|
echo "Error: No suitable media player found." >&2
|
||||||
|
|||||||
+3
-1
@@ -1,4 +1,4 @@
|
|||||||
de 布局太全能了
|
de 布局太全能了:
|
||||||
|
|
||||||
<img src="https://upload.wikimedia.org/wikipedia/commons/3/3e/KB_Germany_Linux.svg" alt="Deutsche Tastaturbelegung unter Linux"/>
|
<img src="https://upload.wikimedia.org/wikipedia/commons/3/3e/KB_Germany_Linux.svg" alt="Deutsche Tastaturbelegung unter Linux"/>
|
||||||
|
|
||||||
@@ -23,3 +23,5 @@ shift > Y X C V B N M ; : _
|
|||||||
altgr | » « ¢ „ “ ” µ · … –
|
altgr | » « ¢ „ “ ” µ · … –
|
||||||
sh+al ˍ › ‹ © ‚ ‘ ’ º × ÷ —
|
sh+al ˍ › ‹ © ‚ ‘ ’ º × ÷ —
|
||||||
```
|
```
|
||||||
|
|
||||||
|
btw, `^` 死键对数字键(上方一排和小键盘均可)也有效, 作用为打出n次幂, 例如 `^` + `9` -> `⁹`.
|
||||||
|
|||||||
Reference in New Issue
Block a user