#!/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"