diff --git a/bin/omarchy-capture-screenrecording b/bin/omarchy-capture-screenrecording index 49ffa04e..4db875d0 100755 --- a/bin/omarchy-capture-screenrecording +++ b/bin/omarchy-capture-screenrecording @@ -35,6 +35,7 @@ RESOLUTION="" FULLSCREEN="false" STOP_RECORDING="false" RECORDING_FILE="/tmp/omarchy-screenrecord-filename" +REGION_FILE="${XDG_RUNTIME_DIR:-/tmp}/omarchy-screenrecord-region" LOG_FILE=$([[ ${OMARCHY_SCREENRECORD_DEBUG:-false} == "true" ]] && echo "/tmp/omarchy-screenrecord.log" || echo "/dev/null") for arg in "$@"; do @@ -89,11 +90,26 @@ start_webcam_overlay() { --title="WebcamOverlay" --wayland-app-id="WebcamOverlay-$WEBCAM_SIZE" \ --no-border --no-audio --no-osc --osd-level=0 \ --really-quiet &>/dev/null & - sleep 1 + + # The move has to settle before gpu-screen-recorder starts, or the camera is + # recorded sliding into its corner. Waiting for the map is what the blind + # second was partly guessing at, so the remainder is trimmed to hold the + # pre-capture delay where it was: starting later costs the first words spoken. + local waited=0 + while ((waited < 40)) && ! hyprctl clients -j | jq -e 'any(.[]; .title == "WebcamOverlay")' >/dev/null 2>&1; do + sleep 0.05 + ((waited++)) + done + + [[ ${1:-} == region:* ]] && echo "${1#region:}" >"$REGION_FILE" + omarchy-capture-webcam-resize "$WEBCAM_SIZE" + + sleep 0.6 } cleanup_webcam() { pkill -f "WebcamOverlay" 2>/dev/null + rm -f "$REGION_FILE" } default_resolution() { @@ -155,7 +171,7 @@ start_screenrecording() { esac fi - [[ $WEBCAM == "true" ]] && start_webcam_overlay + [[ $WEBCAM == "true" ]] && start_webcam_overlay "$target" local filename="$OUTPUT_DIR/screenrecording-$(date +'%Y-%m-%d_%H-%M-%S').mp4" local audio_devices="" diff --git a/bin/omarchy-capture-webcam-resize b/bin/omarchy-capture-webcam-resize index ebe9840f..cde61e08 100755 --- a/bin/omarchy-capture-webcam-resize +++ b/bin/omarchy-capture-webcam-resize @@ -8,6 +8,7 @@ set -euo pipefail readonly MARGIN=40 +readonly REGION_FILE="${XDG_RUNTIME_DIR:-/tmp}/omarchy-screenrecord-region" usage() { echo "Usage: omarchy-capture-webcam-resize " >&2 @@ -57,13 +58,39 @@ read -r monitor_x monitor_y monitor_width monitor_height < <( [[ $monitor_x =~ ^-?[0-9]+$ && $monitor_y =~ ^-?[0-9]+$ && $monitor_width =~ ^[0-9]+$ && $monitor_height =~ ^[0-9]+$ ]] || exit 0 -# Scale the 8:9 portrait presets from monitor height so they occupy the same +# Anchor to the recorded region when there is one, so a window picked on a wide +# display keeps the camera in its own corner. Full-monitor captures, the portal +# backend, and resizes outside a recording publish none and fall back here. +anchor_x=$monitor_x +anchor_y=$monitor_y +anchor_width=$monitor_width +anchor_height=$monitor_height + +if [[ -f $REGION_FILE ]] && region=$(<"$REGION_FILE"); then + if [[ $region =~ ^([0-9]+)x([0-9]+)\+(-?[0-9]+)\+(-?[0-9]+)$ ]]; then + anchor_width=${BASH_REMATCH[1]} + anchor_height=${BASH_REMATCH[2]} + anchor_x=${BASH_REMATCH[3]} + anchor_y=${BASH_REMATCH[4]} + fi +fi + +# A tall, narrow region can't fit presets scaled from its own height, so cap the +# height they scale from to what the width allows — the large preset is the +# widest at 3/10 of it. Scaling the ladder as a whole leaves small, medium and +# large distinct sizes for smaller and larger to step between. +scale_height=$anchor_height +available_width=$((anchor_width - 2 * MARGIN)) +((available_width > 0 && scale_height * 3 / 10 > available_width)) && + scale_height=$((available_width * 10 / 3)) + +# Scale the 8:9 portrait presets from that height so they occupy the same # proportion of a 1080p, HiDPI, ultrawide, or 6K recording. -small_height=$(((monitor_height * 9 + 25) / 50)) +small_height=$(((scale_height * 9 + 25) / 50)) small_width=$(((small_height * 8 + 4) / 9)) -medium_height=$(((monitor_height + 2) / 4)) +medium_height=$(((scale_height + 2) / 4)) medium_width=$(((medium_height * 8 + 4) / 9)) -large_height=$(((monitor_height * 27 + 40) / 80)) +large_height=$(((scale_height * 27 + 40) / 80)) large_width=$(((large_height * 8 + 4) / 9)) target_width=$current_width @@ -107,11 +134,11 @@ larger) ;; esac -target_x=$((monitor_x + monitor_width - target_width - MARGIN)) -target_y=$((monitor_y + monitor_height - target_height - MARGIN)) +target_x=$((anchor_x + anchor_width - target_width - MARGIN)) +target_y=$((anchor_y + anchor_height - target_height - MARGIN)) -((target_x < monitor_x + MARGIN)) && target_x=$((monitor_x + MARGIN)) -((target_y < monitor_y + MARGIN)) && target_y=$((monitor_y + MARGIN)) +((target_x < anchor_x + MARGIN)) && target_x=$((anchor_x + MARGIN)) +((target_y < anchor_y + MARGIN)) && target_y=$((anchor_y + MARGIN)) window="address:$address" hypr_dispatch \ diff --git a/test/shell.d/screenrecording-test.sh b/test/shell.d/screenrecording-test.sh index 238dc28b..cb8ca7e1 100644 --- a/test/shell.d/screenrecording-test.sh +++ b/test/shell.d/screenrecording-test.sh @@ -45,6 +45,8 @@ SH chmod +x "$stub_bin"/* export PATH="$stub_bin:$ROOT/bin:$PATH" +# The resize helper anchors to a region file here, so keep it out of the real one +export XDG_RUNTIME_DIR="$tmp_dir" export OMARCHY_TEST_MENU_ARGS="$tmp_dir/menu-args" export OMARCHY_TEST_RECORDER_ARGS="$tmp_dir/recorder-args" export OMARCHY_TEST_NOTIFICATION_ARGS="$tmp_dir/notification-args" @@ -153,6 +155,59 @@ if [[ -s $OMARCHY_TEST_HYPRCTL_ARGS ]]; then fi pass "webcam resize ignores other windows" +region_file="$XDG_RUNTIME_DIR/omarchy-screenrecord-region" + +: >"$OMARCHY_TEST_HYPRCTL_ARGS" +echo "800x600+100+100" >"$region_file" +"$ROOT/bin/omarchy-capture-webcam-resize" reset + +printf '%s\n' \ + 'dispatch hl.dsp.window.resize({ window = "address:0xabc", x = 133, y = 150 })' \ + 'dispatch hl.dsp.window.move({ window = "address:0xabc", x = 727, y = 510 })' >"$expected_hyprctl_args" + +if ! cmp -s "$OMARCHY_TEST_HYPRCTL_ARGS" "$expected_hyprctl_args"; then + fail "webcam anchors to the recorded region" "$(diff -u "$expected_hyprctl_args" "$OMARCHY_TEST_HYPRCTL_ARGS")" +fi +pass "webcam anchors to the recorded region" + +printf '%s\n' \ + 'dispatch hl.dsp.window.resize({ window = "address:0xabc", x = 178, y = 200 })' \ + 'dispatch hl.dsp.window.move({ window = "address:0xabc", x = 2342, y = 460 })' >"$expected_hyprctl_args" + +for region in "not-a-region" ""; do + : >"$OMARCHY_TEST_HYPRCTL_ARGS" + printf '%s' "$region" >"$region_file" + "$ROOT/bin/omarchy-capture-webcam-resize" reset + + if ! cmp -s "$OMARCHY_TEST_HYPRCTL_ARGS" "$expected_hyprctl_args"; then + fail "webcam falls back to the monitor for an unusable region" "$(diff -u "$expected_hyprctl_args" "$OMARCHY_TEST_HYPRCTL_ARGS")" + fi +done +pass "webcam falls back to the monitor for an unusable region" + +# A region too narrow for presets scaled from its height shrinks the whole +# ladder, so the three sizes stay distinct and each one fits inside the margins +: >"$OMARCHY_TEST_HYPRCTL_ARGS" +echo "200x1200+0+0" >"$region_file" +for size in small medium large; do + "$ROOT/bin/omarchy-capture-webcam-resize" "$size" +done + +printf '%s\n' \ + 'dispatch hl.dsp.window.resize({ window = "address:0xabc", x = 64, y = 72 })' \ + 'dispatch hl.dsp.window.move({ window = "address:0xabc", x = 96, y = 1088 })' \ + 'dispatch hl.dsp.window.resize({ window = "address:0xabc", x = 89, y = 100 })' \ + 'dispatch hl.dsp.window.move({ window = "address:0xabc", x = 71, y = 1060 })' \ + 'dispatch hl.dsp.window.resize({ window = "address:0xabc", x = 120, y = 135 })' \ + 'dispatch hl.dsp.window.move({ window = "address:0xabc", x = 40, y = 1025 })' >"$expected_hyprctl_args" + +if ! cmp -s "$OMARCHY_TEST_HYPRCTL_ARGS" "$expected_hyprctl_args"; then + fail "webcam sizes stay distinct and inside a narrow region" "$(diff -u "$expected_hyprctl_args" "$OMARCHY_TEST_HYPRCTL_ARGS")" +fi +pass "webcam sizes stay distinct and inside a narrow region" + +rm -f "$region_file" + grep -F 'o.bind("SUPER + ALT + code:34", "Make webcam overlay smaller", "omarchy-capture-webcam-resize smaller")' \ "$ROOT/default/hypr/bindings/utilities.lua" >/dev/null || fail "webcam smaller hotkey is configured" grep -F 'o.bind("SUPER + ALT + code:35", "Make webcam overlay larger", "omarchy-capture-webcam-resize larger")' \