One of my most used tools is a tiny script called scrt. It takes a screenshot and puts it in clipboard. That’s it. I use it all-the-dang-time. When I moved from AwesomeWM to
Hyprland, it stopped working.
The old version was just a small wrapper around scrot and xclip.
scrot -s '/tmp/scrt_temp_%Y%m%d%H%M%S.png' \
-e 'xclip -selection clipboard -t image/png -i "$f" && rm "$f"'
Under X11, scrot -s had exactly the behaviour I wanted:
- Click a window and it captures the whole window.
- Drag a rectangle and it captures that region.
- Copy the result to the clipboard.
That is the whole workflow. No save dialog, no image editor, no naming files, no remembering where screenshots went. I can then just paste the screenshot into slack or (dog forbid) an email.
TL;DR: Wayland made the old script stop being the right script. The replacement
is an overengineered vibecoded combo of grim, slurp, wl-copy, and a bit of hyprctl/jq to get the old
click-a-window behaviour back.
The Script
The script lives in my dotfiles here: bin/bin/scrt.
#!/bin/bash
# scrt - Take a screenshot selection and copy it to the clipboard
set -euo pipefail
if [ "${XDG_SESSION_TYPE:-}" = "wayland" ] || [ -n "${WAYLAND_DISPLAY:-}" ]; then
for cmd in grim hyprctl jq slurp wl-copy; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "Error: '$cmd' is not installed."
exit 1
fi
done
selection_file="$(mktemp -t scrt-selection.XXXXXX)"
trap 'rm -f "$selection_file"' EXIT
while true; do
workspace_id="$(hyprctl activeworkspace -j | jq -r '.id')"
windows="$(hyprctl clients -j | jq -r --argjson workspace_id "$workspace_id" '
.[]
| select(.mapped and .workspace.id == $workspace_id)
| "\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1]) \(.title)"
')"
refresh_selection=false
slurp > "$selection_file" <<< "$windows" &
slurp_pid="$!"
while kill -0 "$slurp_pid" >/dev/null 2>&1; do
current_workspace_id="$(hyprctl activeworkspace -j | jq -r '.id')"
if [ "$workspace_id" != "$current_workspace_id" ]; then
refresh_selection=true
kill "$slurp_pid" >/dev/null 2>&1 || true
wait "$slurp_pid" >/dev/null 2>&1 || true
break
fi
sleep 0.1
done
if [ "$refresh_selection" = true ]; then
continue
fi
wait "$slurp_pid" || exit 1
geometry="$(cat "$selection_file")"
[ -n "$geometry" ] && break
done
grim -g "$geometry" - | wl-copy --type image/png
else
for cmd in scrot xclip; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "Error: '$cmd' is not installed."
exit 1
fi
done
scrot -s '/tmp/scrt_temp_%Y%m%d%H%M%S.png' -e 'xclip -selection clipboard -t image/png -i "$f" && rm "$f"'
fi
echo "Screenshot copied to clipboard!"
How It Works
On Wayland, the script uses hyprctl clients -j to get the windows on the
current workspace. jq turns that JSON into rectangle lines that slurp
understands, so I can click a window to capture the whole thing or drag a custom
region.
slurp only knows about the rectangles it was given when it started. To handle
workspace changes, the script watches hyprctl activeworkspace -j while slurp
is running. If the workspace changes, it kills slurp and starts it again with
the new workspace’s windows. Once a selection is made, grim captures that
geometry and pipes the PNG into wl-copy. I’m pretty sure my AwesomeWM version just exited when changing tags, so this is a nice improvement.
Bonus: on X11, it falls back to the old scrot and xclip version.
The End
Long live scrt.