From 67feeb51a2c3e4fb579c129b15784c65d57c2607 Mon Sep 17 00:00:00 2001 From: Josh Rahm Date: Wed, 29 Oct 2025 16:42:17 -0600 Subject: Add quick-clip and bind it to c. Quick-clip makes it easy to keep text snippets around for quick copy-paste. --- extras/HOME/.local/bin/quick-clip.sh | 196 +++++++++++++++++++++++++++++++++++ src/Main.hs | 2 + src/Rahm/Desktop/Keys.hs | 14 +-- 3 files changed, 201 insertions(+), 11 deletions(-) create mode 100755 extras/HOME/.local/bin/quick-clip.sh diff --git a/extras/HOME/.local/bin/quick-clip.sh b/extras/HOME/.local/bin/quick-clip.sh new file mode 100755 index 0000000..49dfe48 --- /dev/null +++ b/extras/HOME/.local/bin/quick-clip.sh @@ -0,0 +1,196 @@ +#!/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" diff --git a/src/Main.hs b/src/Main.hs index 4158c6a..09810e2 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -122,6 +122,8 @@ main = do className =? "Tilda" --> doFloat, className =? "yakuake" --> doFloat, className =? "MPlayer" --> doFloat, + className =? "zenity" --> doCenterFloat, + className =? "Yad" --> doCenterFloat, className =? "Xfce4-notifyd" --> doIgnore, className =? "popup-terminal" --> doShift "*" <> updatePopupTerminalHook, className =? "spotify" --> doShift "s", diff --git a/src/Rahm/Desktop/Keys.hs b/src/Rahm/Desktop/Keys.hs index 9ca862a..8ec3aeb 100644 --- a/src/Rahm/Desktop/Keys.hs +++ b/src/Rahm/Desktop/Keys.hs @@ -395,7 +395,7 @@ bindings = do bind xK_b $ do justMod $ spawnX "bluetooth-select.sh" - bind xK_u $ do + bind xK_i $ do justMod $ spawnX "notes-select.sh" bind xK_c $ do @@ -631,10 +631,6 @@ bindings = do flip whenJust toggleWindowInSelection =<< withWindowSet (return . W.peek) bind xK_m $ do - rawMask - mod2Mask - (logs Info "Testing Mod2Mask" :: X ()) - justMod $ doc "Mark the windows described by the window set with a given character.\n\n\t\ @@ -925,12 +921,8 @@ bindings = do -- Explode bind xK_c $ do noMod -|- justMod $ - doc "Toggle explode on the workspace" $ do - sendMessage - ( toggleExplodeM - movePopupToCurrentWorkspace - movePopupToHiddenWorkspace - ) + doc "Run Quick-clip" $ + spawnX "quick-clip.sh" bindOtherKeys $ \(_, _, s) -> logs Info "Unhandled key pressed: %s" s -- cgit