diff --git a/bin/omarchy-capture-region-pick b/bin/omarchy-capture-region-pick new file mode 100755 index 00000000..bf9c5526 --- /dev/null +++ b/bin/omarchy-capture-region-pick @@ -0,0 +1,120 @@ +#!/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 + +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])"' +} + +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=$(slurp 2>/dev/null) + ;; +windows) + freeze_screen + SELECTION=$(get_rectangles | slurp -r 2>/dev/null) + ;; +fullscreen) + SELECTION=$(hyprctl monitors -j | jq -r "${JQ_MONITOR_GEO} .[] | select(.focused == true) | format_geo") + ;; +smart | *) + RECTS=$(get_rectangles) + freeze_screen + SELECTION=$(echo "$RECTS" | slurp 2>/dev/null) + + # 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" diff --git a/bin/omarchy-capture-screenrecording b/bin/omarchy-capture-screenrecording index 51a1ea9f..92ea4c3a 100755 --- a/bin/omarchy-capture-screenrecording +++ b/bin/omarchy-capture-screenrecording @@ -102,75 +102,25 @@ default_resolution() { fi } -# Monitor + window rectangles on the focused workspace, in slurp's "X,Y WxH" format. -# Mirrors omarchy-capture-screenshot so the picker UX is identical, including -# swapping monitor dimensions on rotated displays (transform 1/3). -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])"' -} - -# Echoes "monitor:NAME" when the selection matches an entire monitor, otherwise -# "region:WxH+X+Y" with physical-pixel coordinates ready for gpu-screen-recorder. -# Returns non-zero if the user cancelled the picker. +# Echoes "monitor:NAME" when the selection matches an entire monitor (prefer +# -w over a region capture — same kms backend, but no scaling math +# and full native res), otherwise "region:WxH+X+Y". Returns non-zero if the +# user cancelled the picker. select_capture_target() { - local rects=$(get_rectangles) - hyprpicker -r -z >/dev/null 2>&1 & - local picker_pid=$! - sleep .1 - local selection=$(echo "$rects" | slurp 2>/dev/null) - kill $picker_pid 2>/dev/null + local target + target=$(omarchy-capture-region-pick smart --match-monitor) || return 1 - # X and Y can be negative (Hyprland monitor positions in multi-display layouts); - # widths and heights are always positive. - [[ $selection =~ ^(-?[0-9]+),(-?[0-9]+)[[:space:]]([0-9]+)x([0-9]+)$ ]] || return 1 - local sx=${BASH_REMATCH[1]} sy=${BASH_REMATCH[2]} - local sw=${BASH_REMATCH[3]} sh=${BASH_REMATCH[4]} - - # A bare click (area < 20px²) snaps to whichever rectangle the click landed - # inside, so users don't end up with accidental 2px recordings. - if ((sw * sh < 20)); then - while IFS= read -r rect; do - [[ $rect =~ ^(-?[0-9]+),(-?[0-9]+)[[:space:]]([0-9]+)x([0-9]+)$ ]] || continue - local rx=${BASH_REMATCH[1]} ry=${BASH_REMATCH[2]} - local rw=${BASH_REMATCH[3]} rh=${BASH_REMATCH[4]} - if ((sx >= rx && sx < rx + rw && sy >= ry && sy < ry + rh)); then - sx=$rx sy=$ry sw=$rw sh=$rh - break - fi - done <<<"$rects" - fi - - # When the selection exactly matches a monitor, prefer -w over a - # region capture — same kms backend, but no scaling math and full native res. - local monitor=$(hyprctl monitors -j | jq -r --arg geo "${sx},${sy} ${sw}x${sh}" \ - "${JQ_MONITOR_GEO} .[] | select(format_geo == \$geo) | .name" | head -1) - - if [[ -n $monitor ]]; then - echo "monitor:$monitor" + if [[ $target == monitor:* ]]; then + echo "$target" return fi + [[ $target =~ ^(-?[0-9]+),(-?[0-9]+)[[:space:]]([0-9]+)x([0-9]+)$ ]] || return 1 + # gpu-screen-recorder wants region geometry in the compositor's logical # coordinate space — same space slurp returns — so pass the values through # untouched. (gsr scales to physical pixels itself based on the monitor.) - echo "region:${sw}x${sh}+${sx}+${sy}" + echo "region:${BASH_REMATCH[3]}x${BASH_REMATCH[4]}+${BASH_REMATCH[1]}+${BASH_REMATCH[2]}" } start_screenrecording() { diff --git a/bin/omarchy-capture-screenshot b/bin/omarchy-capture-screenshot index 822e66e2..be133a3d 100755 --- a/bin/omarchy-capture-screenshot +++ b/bin/omarchy-capture-screenshot @@ -37,81 +37,15 @@ open_editor() { MODE="${1:-smart}" PROCESSING="${2:-slurp}" -# 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])"' -} - -# Keep hyprpicker alive until after grim captures so the screenshot sees the -# frozen overlay rather than live content shifting during teardown. +# The picker leaves the screen freeze running (PID on its first output line) +# so grim captures the frozen overlay rather than live content shifting +# during teardown. cleanup_freeze() { - [[ -n $PID ]] && kill $PID 2>/dev/null + [[ -n $FREEZE_PID ]] && kill $FREEZE_PID 2>/dev/null } trap cleanup_freeze EXIT -# Select based on mode -case "$MODE" in -region) - hyprpicker -r -z >/dev/null 2>&1 & - PID=$! - sleep .1 - SELECTION=$(slurp 2>/dev/null) - ;; -windows) - hyprpicker -r -z >/dev/null 2>&1 & - PID=$! - sleep .1 - SELECTION=$(get_rectangles | slurp -r 2>/dev/null) - ;; -fullscreen) - SELECTION=$(hyprctl monitors -j | jq -r "${JQ_MONITOR_GEO} .[] | select(.focused == true) | format_geo") - ;; -smart | *) - RECTS=$(get_rectangles) - hyprpicker -r -z >/dev/null 2>&1 & - PID=$! - sleep .1 - SELECTION=$(echo "$RECTS" | slurp 2>/dev/null) - - # If the selection area is L * W < 20, we'll assume you were trying to select whichever - # window or output it was inside of to prevent accidental 2px snapshots - if [[ $SELECTION =~ ^([0-9]+),([0-9]+)[[:space:]]([0-9]+)x([0-9]+)$ ]]; then - if ((${BASH_REMATCH[3]} * ${BASH_REMATCH[4]} < 20)); then - click_x="${BASH_REMATCH[1]}" - click_y="${BASH_REMATCH[2]}" - - while IFS= read -r rect; do - if [[ $rect =~ ^([0-9]+),([0-9]+)[[:space:]]([0-9]+)x([0-9]+) ]]; then - 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 - fi - done <<<"$RECTS" - fi - fi - ;; -esac +{ read -r FREEZE_PID; read -r SELECTION; } < <(omarchy-capture-region-pick "$MODE" --keep-freeze) [[ -z $SELECTION ]] && exit 0