#!/usr/bin/env bash
# set -euo pipefail
# --- Config -----------------------------------------------------------------
: "${XDG_CONFIG_HOME:=${HOME}/.config}"
SNIPPETS_DIR="${XDG_CONFIG_HOME}/quick-clip"
SNIPPETS_FILE="${SNIPPETS_DIR}/snippets.tsv"
PROMPT="Quick Clip"
SHOW_NOTIFICATIONS="yes" # set to "no" to disable notify-send
ROFI_CMD=(rofi -markup-rows -dmenu -i -p "${PROMPT}" -lines 30 -theme-str '* {theme-color: #ffffdd;}' \
-kb-custom-1 'Alt+d' \
-kb-custom-2 'Alt+r'
)
# --- Helpers ----------------------------------------------------------------
copy() {
# Copy stdin to clipboard using best available tool.
if [ -n "${WAYLAND_DISPLAY:-}" ] && command -v wl-copy >/dev/null 2>&1; then
wl-copy --primary && wl-copy # copy to both clipboards on Wayland
elif command -v xclip >/dev/null 2>&1; then
xclip -selection clipboard
printf "%s" "$CLIPBUF" | xclip -selection primary || true
elif command -v xsel >/dev/null 2>&1; then
xsel --clipboard --input
printf "%s" "$CLIPBUF" | xsel --primary --input || true
else
echo "quick-clip: no clipboard tool found (need wl-copy, xclip, or xsel)" >&2
exit 1
fi
}
notify() {
if [ "${SHOW_NOTIFICATIONS}" = "yes" ] && command -v notify-send >/dev/null 2>&1; then
notify-send "${PROMPT}" "$1"
fi
}
ensure_files() {
mkdir -p "${SNIPPETS_DIR}"
if [ ! -f "${SNIPPETS_FILE}" ]; then
cat >"${SNIPPETS_FILE}" <<'TPL'
# Example snippets (TSV):
# Label Value
Email josh@example.com
GitHub https://github.com/Josh
# Single-field lines copy the whole line:
+1 (555) 867-5309
TPL
fi
}
open_editor() {
local editor
editor="${EDITOR:-}"
if [ -z "$editor" ]; then
if command -v vim >/dev/null 2>&1; then editor=vim
elif command -v nano >/dev/null 2>&1; then editor=nano
elif command -v vi >/dev/null 2>&1; then editor=vi
else
printf "quick-clip: set $EDITOR or install nano/vim.\n" >&2
exit 1
fi
fi
alacritty --class "floating-terminal" -e "$editor" "${SNIPPETS_FILE}"
}
add_snippet_via_rofi() {
local label value
label=$(printf '' | rofi -dmenu -i -p "new label" -lines 0 || true)
[ -z "$label" ] && return 0
value=$(printf '' | rofi -dmenu -i -p "new value" -lines 0 || true)
[ -z "$value" ] && return 0
printf '%s\t%s\n' "$label" "$value" >>"${SNIPPETS_FILE}"
notify "Added snippet: $label"
}
add_snippet() {
if ! command -v zenity >/dev/null 2>&1; then
echo "quick-clip: zenity not found (install zenity or change add flow)" >&2
notify "zenity not found"
return 1
fi
local label value
label=$(zenity --entry --title="$PROMPT" --text="New snippet label:" 2>/dev/null) || return 0
value=$(zenity --entry --title="$PROMPT" --text="Value for '$label':" 2>/dev/null) || return 0
[ -z "$value" ] && return 0
if [ -z "$label" ] ; then
printf "%s" "$value"
else
printf '%s\t%s\n' "$label" "$value" >>"${SNIPPETS_FILE}"
fi
notify "Added snippet: $label"
}
add_snippet_via_yad() {
if ! command -v yad >/dev/null 2>&1; then
echo "quick-clip: yad not found (install yad or change add flow)" >&2
notify "yad not found"
return 1
fi
local out label value
out=$(yad --form \
--title="$PROMPT" \
--center --on-top \
--window-icon="accessories-text-editor" \
--field="Label" \
--field="Value" \
--borders=10 \
--separator=$'🔨' 2>/dev/null) || return 0
IFS=$'🔨' read -r label value <<<"${out%🔨}"
echo "label: $label, value: $value"
if [ -z "$label" ]; then
printf '%s\n' "$value" >>"${SNIPPETS_FILE}"
notify "Added snippet: $value"
else
printf '%s\t%s\n' "$label" "$value" >>"${SNIPPETS_FILE}"
notify "Added snippet: $label"
fi
}
# Given a chosen line, determine the text to copy.
extract_value() {
# If line contains a TAB, take field 2; else copy entire line.
awk -F "\t" 'NF>=2 {print $2; exit} NF==1 {print $0; exit}'
}
# --- Main -------------------------------------------------------------------
ensure_files
SPEC_ADD="α Add new snippet"
SPEC_EDIT="ε Edit snippets"
# Build menu: actions then snippets (skip comments/blank lines)
mapfile -t MENU < <(
printf "Snippets\t\n"
awk 'BEGIN{FS="\t"} /^[[:space:]]*#/ {next} NF==0 {next} {print $0}' "${SNIPPETS_FILE}"
for f in "$HOME/.local/lib/quick-clip"/*; do
if [ -f "$f" ] && [ -x "$f" ]; then
"$f"
fi
done
printf "\nActions\t\n"
printf '%s\n' "$SPEC_ADD" "$SPEC_EDIT"
)
# Show menu; allow custom input
SELECTION=$(printf '%s\n' "${MENU[@]}" | "${ROFI_CMD[@]}")
ec="$?"
echo "EC: $ec"
if [[ "$ec" -eq 10 ]] ; then # Delete the entry
tfile=/tmp/$(mktemp 'XXXX.tmp')
grep -Fxv "$SELECTION" "$SNIPPETS_FILE" > "$tfile"
if [[ "$?" -ne 0 ]] ; then
notify "Could not delete"
else
mv "$tfile" "$SNIPPETS_FILE"
notify "Deleted Snippet: $SELECTION"
fi
exit 0
fi
if [[ "$ec" -eq 11 ]] ; then # Run the entry
val=$(extract_value <<< "$SELECTION")
sh -c "$val"
exit 0
fi
# If empty (Esc), quit
[ -z "${SELECTION}" ] && exit 0
case "${SELECTION}" in
$SPEC_ADD)
add_snippet_via_yad
exit 0
;;
$SPEC_EDIT)
open_editor
exit 0
;;
*)
;;
esac
# If the selection equals a menu entry that came from the file and contains a TAB,
# copy field 2; otherwise, treat it as free text.
CLIPBUF=$(printf '%s' "${SELECTION}" | extract_value)
printf '%s' "${CLIPBUF}" | copy
notify "Copied to clipboard"