A previous submap attempt was brittle because the script owned the keyboard state: if it died between enter and exit, the submap stuck. This version lets the compositor own both edges. The Lua config binds plain Return when a layer surface with slurp's "selection" namespace opens and unbinds it when the last one closes, so the bind exists exactly while a pick is on screen. slurp dying for any reason (cancel, crash, pkill, SIGKILL of the picker) fires layer.closed and the bind evaporates — there is no code path where it leaks. Return invokes omarchy-capture-region --take-fullscreen, which flags a marker in XDG_RUNTIME_DIR and dismisses slurp; the picker treats an empty slurp result with a fresh marker as the focused monitor, reusing the existing fullscreen path. Since monitor focus follows the cursor, that is the monitor under the slurp crosshair. The recording flow gets monitor:NAME from the existing match, so Return during a recording pick starts a native full-monitor capture. The signal mode no-ops unless slurp is actually running, and both the signal protocol and the layer-scoped bind lifecycle are verified live: bind present only while slurp is open, real picks resolved by the signal on both the screenshot and recording paths, no leftover binds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
152 lines
4.6 KiB
Bash
Executable File
152 lines
4.6 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# omarchy:summary=Pick a screen region over frozen screen content
|
|
# omarchy:args=[region|windows|smart|fullscreen] [--keep-freeze] [--match-monitor]
|
|
# omarchy:hidden=true
|
|
|
|
# Prints the picked geometry in slurp's "X,Y WxH" format, or exits 1 when the
|
|
# pick is cancelled. Shared by screenshot and screen recording so the picker
|
|
# UX stays identical.
|
|
#
|
|
# region freeform selection
|
|
# windows snap selection to a monitor or window rectangle
|
|
# smart freeform with window/monitor rects hinted; a bare click
|
|
# (area < 20px^2) snaps to the rectangle it landed in
|
|
# fullscreen the focused monitor, no interaction
|
|
#
|
|
# --keep-freeze leave the hyprpicker screen freeze running and print its
|
|
# PID as the first output line (empty when no freeze was
|
|
# started); the caller owns killing it
|
|
# --match-monitor print "monitor:NAME" instead when the picked geometry
|
|
# exactly matches a monitor
|
|
|
|
FULLSCREEN_MARKER="${XDG_RUNTIME_DIR:-/tmp}/omarchy-capture-region-fullscreen"
|
|
|
|
# Pressing Return while slurp is open captures the entire focused monitor:
|
|
# a bind scoped to slurp's layer surface (default/hypr/bindings/utilities.lua)
|
|
# invokes this mode, which flags the intent and dismisses slurp.
|
|
if [[ ${1:-} == "--take-fullscreen" ]]; then
|
|
pgrep -x slurp >/dev/null || exit 0
|
|
touch "$FULLSCREEN_MARKER"
|
|
pkill -x slurp
|
|
exit 0
|
|
fi
|
|
|
|
MODE=smart
|
|
KEEP_FREEZE=false
|
|
MATCH_MONITOR=false
|
|
|
|
for arg in "$@"; do
|
|
case $arg in
|
|
--keep-freeze) KEEP_FREEZE=true ;;
|
|
--match-monitor) MATCH_MONITOR=true ;;
|
|
*) MODE=$arg ;;
|
|
esac
|
|
done
|
|
|
|
# accounting for portrait/transformed displays
|
|
JQ_MONITOR_GEO='
|
|
def format_geo:
|
|
.x as $x | .y as $y |
|
|
(.width / .scale | floor) as $w |
|
|
(.height / .scale | floor) as $h |
|
|
.transform as $t |
|
|
if $t == 1 or $t == 3 then
|
|
"\($x),\($y) \($h)x\($w)"
|
|
else
|
|
"\($x),\($y) \($w)x\($h)"
|
|
end;
|
|
'
|
|
|
|
get_rectangles() {
|
|
local active_workspace=$(hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .activeWorkspace.id')
|
|
hyprctl monitors -j | jq -r --arg ws "$active_workspace" "${JQ_MONITOR_GEO} .[] | select(.activeWorkspace.id == (\$ws | tonumber)) | format_geo"
|
|
hyprctl clients -j | jq -r --arg ws "$active_workspace" '.[] | select(.workspace.id == ($ws | tonumber)) | "\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"'
|
|
}
|
|
|
|
focused_monitor_geo() {
|
|
hyprctl monitors -j | jq -r "${JQ_MONITOR_GEO} .[] | select(.focused == true) | format_geo"
|
|
}
|
|
|
|
# Runs slurp; an empty result with the fullscreen marker present means Return
|
|
# was pressed, so the focused monitor is the selection.
|
|
pick() {
|
|
local selection
|
|
rm -f "$FULLSCREEN_MARKER"
|
|
selection=$(slurp "$@" 2>/dev/null)
|
|
|
|
if [[ -z $selection && -e $FULLSCREEN_MARKER ]]; then
|
|
rm -f "$FULLSCREEN_MARKER"
|
|
selection=$(focused_monitor_geo)
|
|
fi
|
|
|
|
printf '%s' "$selection"
|
|
}
|
|
|
|
FREEZE_PID=""
|
|
freeze_screen() {
|
|
hyprpicker -r -z >/dev/null 2>&1 &
|
|
FREEZE_PID=$!
|
|
sleep .1
|
|
}
|
|
|
|
cleanup_freeze() {
|
|
[[ $KEEP_FREEZE == true ]] && return
|
|
[[ -n $FREEZE_PID ]] && kill $FREEZE_PID 2>/dev/null
|
|
}
|
|
trap cleanup_freeze EXIT
|
|
|
|
case "$MODE" in
|
|
region)
|
|
freeze_screen
|
|
SELECTION=$(pick)
|
|
;;
|
|
windows)
|
|
freeze_screen
|
|
SELECTION=$(get_rectangles | pick -r)
|
|
;;
|
|
fullscreen)
|
|
SELECTION=$(focused_monitor_geo)
|
|
;;
|
|
smart | *)
|
|
RECTS=$(get_rectangles)
|
|
freeze_screen
|
|
SELECTION=$(echo "$RECTS" | pick)
|
|
|
|
# A bare click (area < 20px^2) snaps to whichever rectangle it landed in,
|
|
# so users don't end up with accidental 2px captures. X and Y can be
|
|
# negative (Hyprland monitor positions in multi-display layouts).
|
|
if [[ $SELECTION =~ ^(-?[0-9]+),(-?[0-9]+)[[:space:]]([0-9]+)x([0-9]+)$ ]] && ((BASH_REMATCH[3] * BASH_REMATCH[4] < 20)); then
|
|
click_x=${BASH_REMATCH[1]}
|
|
click_y=${BASH_REMATCH[2]}
|
|
|
|
while IFS= read -r rect; do
|
|
[[ $rect =~ ^(-?[0-9]+),(-?[0-9]+)[[:space:]]([0-9]+)x([0-9]+)$ ]] || continue
|
|
rect_x=${BASH_REMATCH[1]}
|
|
rect_y=${BASH_REMATCH[2]}
|
|
rect_width=${BASH_REMATCH[3]}
|
|
rect_height=${BASH_REMATCH[4]}
|
|
|
|
if ((click_x >= rect_x && click_x < rect_x + rect_width && click_y >= rect_y && click_y < rect_y + rect_height)); then
|
|
SELECTION="${rect_x},${rect_y} ${rect_width}x${rect_height}"
|
|
break
|
|
fi
|
|
done <<<"$RECTS"
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
[[ $KEEP_FREEZE == true ]] && echo "$FREEZE_PID"
|
|
|
|
[[ -n $SELECTION ]] || exit 1
|
|
|
|
if [[ $MATCH_MONITOR == true ]]; then
|
|
monitor=$(hyprctl monitors -j | jq -r --arg geo "$SELECTION" "${JQ_MONITOR_GEO} .[] | select(format_geo == \$geo) | .name" | head -1)
|
|
if [[ -n $monitor ]]; then
|
|
echo "monitor:$monitor"
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
echo "$SELECTION"
|