From 31b57f4f8c3d5e6473dfe7810acdf339acf6db84 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Sun, 4 Oct 2026 23:36:56 +0200 Subject: [PATCH] feat: screenshots answer with a thumbnail notification All four Print binds go through hypr/scripts/shot.sh. Every capture lands on the clipboard and posts a notification showing the picture, with Open, Save (for clipboard captures, which wait in the runtime dir) and Edit when satty is installed. Cancelling the region picker does nothing. Notification cards now load images passed as a path hint: Quickshell routes those through the icon provider, which returned a placeholder. --- hypr/lua/binds/capture.lua | 11 +++-- hypr/scripts/shot.sh | 73 +++++++++++++++++++++++++++++ quickshell/components/NotifCard.qml | 9 +++- 3 files changed, 88 insertions(+), 5 deletions(-) create mode 100755 hypr/scripts/shot.sh diff --git a/hypr/lua/binds/capture.lua b/hypr/lua/binds/capture.lua index 9561b4a..29cfa09 100644 --- a/hypr/lua/binds/capture.lua +++ b/hypr/lua/binds/capture.lua @@ -5,10 +5,13 @@ local scripts = os.getenv("HOME") .. "/.config/hypr/scripts/" -- ------------------------------------------------------------- screenshots -- PRINT selects a region, CTRL grabs the focused window, SHIFT saves to disk -- instead of the clipboard. -hl.bind("PRINT", hl.dsp.exec_cmd('grim -g "$(slurp)" - | wl-copy')) -hl.bind("SHIFT + PRINT", hl.dsp.exec_cmd('grim -g "$(slurp)" ~/Pictures/Screenshots/$(date +\'%Y-%m-%d_%H-%M-%S\').png')) -hl.bind("CTRL + PRINT", hl.dsp.exec_cmd(scripts .. "screenshot_window.sh copy")) -hl.bind("CTRL + SHIFT + PRINT", hl.dsp.exec_cmd(scripts .. "screenshot_window.sh save")) +-- All four go through scripts/shot.sh, which also posts a notification with +-- the capture as its thumbnail (Open, Edit with satty, Save). +local shot = scripts .. "shot.sh " +hl.bind("PRINT", hl.dsp.exec_cmd(shot .. "region copy"), { description = "screenshot an area to the clipboard" }) +hl.bind("SHIFT + PRINT", hl.dsp.exec_cmd(shot .. "region save"), { description = "screenshot an area to a file" }) +hl.bind("CTRL + PRINT", hl.dsp.exec_cmd(shot .. "window copy"), { description = "screenshot the window to the clipboard" }) +hl.bind("CTRL + SHIFT + PRINT", hl.dsp.exec_cmd(shot .. "window save"), { description = "screenshot the window to a file" }) -- ------------------------------------------------------- replay and camera -- On release, so the shortcut is not caught mid-chord. diff --git a/hypr/scripts/shot.sh b/hypr/scripts/shot.sh new file mode 100755 index 0000000..22846ef --- /dev/null +++ b/hypr/scripts/shot.sh @@ -0,0 +1,73 @@ +#!/bin/sh +# Screenshots with a receipt: every capture lands on the clipboard and comes +# back as a notification with its own thumbnail, so a keypress that used to +# vanish silently now shows what it took. +# +# shot.sh region copy pick an area, copy it +# shot.sh region save pick an area, save it to ~/Pictures/Screenshots +# shot.sh window copy the focused window, copy it +# shot.sh window save the focused window, save it +# +# A copy is kept in $XDG_RUNTIME_DIR only so the notification has a picture +# to show; its Save action moves it into ~/Pictures/Screenshots. Edit opens +# satty when it is installed. Cancelling the region picker does nothing. + +set -eu + +target=${1:-region} +mode=${2:-copy} + +dir="$HOME/Pictures/Screenshots" +stamp=$(date +'%Y-%m-%d_%H-%M-%S') + +case "$target" in + region) + geometry=$(slurp) || exit 0 + name="$stamp.png" + ;; + window) + info=$(hyprctl activewindow -j) + geometry=$(printf '%s' "$info" | jq -r 'if .at then "\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])" else empty end') + if [ -z "$geometry" ]; then + notify-send -a Screenshot "Screenshot" "No window is focused." + exit 1 + fi + app=$(printf '%s' "$info" | jq -r '.class // "unknown"' | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_-]/_/g') + name="${app}_$stamp.png" + ;; + *) + echo "usage: shot.sh region|window copy|save" >&2 + exit 2 + ;; +esac + +if [ "$mode" = save ]; then + mkdir -p "$dir" + file="$dir/$name" +else + file="${XDG_RUNTIME_DIR:-/tmp}/shot-$name" +fi + +grim -g "$geometry" "$file" +wl-copy --type image/png < "$file" + +title=$([ "$mode" = save ] && echo "Screenshot saved" || echo "Screenshot copied") +body=$([ "$mode" = save ] && echo "${file#"$HOME"/}" || echo "On the clipboard") + +set -- --action=open=Open +command -v satty >/dev/null 2>&1 && set -- "$@" --action=edit=Edit +[ "$mode" = copy ] && set -- "$@" --action=save=Save + +# notify-send waits for the notification to close and prints the action +# that was chosen, so run it in the background and act on the answer. +( + choice=$(notify-send -a Screenshot -i "$file" -h "string:image-path:$file" \ + "$@" "$title" "$body") || exit 0 + case "$choice" in + open) xdg-open "$file" ;; + edit) satty --filename "$file" --output-filename "$dir/edited_$name" --copy-command wl-copy ;; + save) mkdir -p "$dir" && mv "$file" "$dir/$name" \ + && notify-send -a Screenshot -i "$dir/$name" -h "string:image-path:$dir/$name" \ + "Screenshot saved" "${dir#"$HOME"/}/$name" ;; + esac +) >/dev/null 2>&1 & diff --git a/quickshell/components/NotifCard.qml b/quickshell/components/NotifCard.qml index 4bf06a7..f92ad8c 100644 --- a/quickshell/components/NotifCard.qml +++ b/quickshell/components/NotifCard.qml @@ -185,7 +185,14 @@ Item { id: inline x: 1 y: 1 - source: root.notif?.image || "" + // A path given as an image hint (notify-send -h image-path:…) + // comes back routed through the icon provider, which treats + // it as an icon name and returns a placeholder. Load the file. + source: { + const img = root.notif?.image || ""; + if (img.startsWith("image://icon//")) return "file://" + img.slice("image://icon/".length); + return img.startsWith("/") ? "file://" + img : img; + } sourceSize.height: 90 fillMode: Image.PreserveAspectFit asynchronous: true