109 lines
4.7 KiB
Bash
Executable File
109 lines
4.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Capture an area, active monitor, or all monitors and edit in Satty.
|
|
set -euo pipefail
|
|
umask 077
|
|
|
|
fail() {
|
|
echo "ferese-screenshot: $*" >&2
|
|
if command -v notify-send >/dev/null; then
|
|
notify-send --app-name=Ferese --urgency=critical 'Screenshot failed' "$*" || true
|
|
fi
|
|
exit 1
|
|
}
|
|
|
|
mode=area
|
|
case ${1:-} in
|
|
'') ;;
|
|
--full) mode=full; shift ;;
|
|
--all) mode=all; shift ;;
|
|
--help|-h)
|
|
echo 'Usage: ferese-screenshot [--full | --all]'
|
|
echo 'Select an area; --full captures the active monitor; --all captures all monitors.'
|
|
exit 0
|
|
;;
|
|
*) fail 'Usage: ferese-screenshot [--full | --all]' ;;
|
|
esac
|
|
(($# == 0)) || fail 'Usage: ferese-screenshot [--full | --all]'
|
|
|
|
for tool in feresectl satty wl-copy flock python3; do
|
|
command -v "$tool" >/dev/null || fail "Required command not found: $tool"
|
|
done
|
|
: "${XDG_RUNTIME_DIR:?A Wayland login session is required}"
|
|
exec 9>"$XDG_RUNTIME_DIR/ferese-screenshot.lock"
|
|
flock -n 9 || exit 0
|
|
|
|
capture_args=()
|
|
if [[ $mode == area ]]; then
|
|
command -v slurp >/dev/null || fail 'Required command not found: slurp'
|
|
# Escape cancels without creating a file or changing the clipboard.
|
|
geometry=$(slurp) || exit 0
|
|
[[ -n $geometry ]] || exit 0
|
|
capture_args=(-g "$geometry")
|
|
elif [[ $mode == full ]]; then
|
|
# Resolve the active monitor before Satty can change keyboard focus.
|
|
# Never fall back to an all-monitor capture when IPC is unavailable.
|
|
# This is the only mode that needs the output list; the editor is sized
|
|
# from the captured image instead.
|
|
geometry=$(feresectl -j outputs | python3 -c '
|
|
import json, sys
|
|
outputs = [output for output in json.load(sys.stdin) if output.get("enabled")]
|
|
focused = [output for output in outputs if output.get("focused")]
|
|
selected = focused if len(focused) == 1 else outputs if len(outputs) == 1 else []
|
|
if len(selected) != 1 or any(selected[0].get(k) is None for k in ("x", "y", "width", "height")):
|
|
sys.exit(1)
|
|
print("%d,%d %dx%d" % (selected[0]["x"], selected[0]["y"], selected[0]["width"], selected[0]["height"]))
|
|
') || fail 'Could not determine the active monitor. Use area selection or --all.'
|
|
capture_args=(-g "$geometry")
|
|
fi
|
|
pictures=$(xdg-user-dir PICTURES 2>/dev/null) || pictures="$HOME/Pictures"
|
|
directory=${pictures:-$HOME/Pictures}/Screenshots
|
|
mkdir -p -- "$directory" || fail 'Could not create the screenshot directory.'
|
|
file=$(mktemp "$directory/Screenshot-$(date +%Y-%m-%d_%H-%M-%S)-XXXXXX.png") || fail 'Could not create the screenshot file.'
|
|
input=''
|
|
capture_error=''
|
|
trap '[[ -z $input ]] || rm -f -- "$input"; [[ -z $capture_error ]] || rm -f -- "$capture_error"; [[ -s $file ]] || rm -f -- "$file"' EXIT
|
|
input=$(mktemp "$XDG_RUNTIME_DIR/ferese-screenshot-XXXXXX.png") || fail 'Could not create the temporary capture.'
|
|
capture_error=$(mktemp "$XDG_RUNTIME_DIR/ferese-screenshot-error-XXXXXX") || fail 'Could not create the capture log.'
|
|
if ! feresectl screenshot "${capture_args[@]}" >"$input" 2>"$capture_error"; then
|
|
detail=$(<"$capture_error")
|
|
fail "Could not capture the screen. ${detail:-The compositor returned no error details.}"
|
|
fi
|
|
# Size the editor to the image that was actually captured, so a small area
|
|
# selection does not open a huge window and a multi-monitor --all capture is not
|
|
# sized from whichever output happened to be focused. Satty treats this as a
|
|
# default size, and Ferese floats the window at whatever it commits.
|
|
editor_size=$(python3 -c '
|
|
import sys
|
|
|
|
header = open(sys.argv[1], "rb").read(24)
|
|
if header[:8] != b"\x89PNG\r\n\x1a\n" or header[12:16] != b"IHDR":
|
|
sys.exit(1)
|
|
width = int.from_bytes(header[16:20], "big")
|
|
height = int.from_bytes(header[20:24], "big")
|
|
if width <= 0 or height <= 0:
|
|
sys.exit(1)
|
|
|
|
MAX_W, MAX_H = 1500, 920
|
|
MIN_W, MIN_H = 480, 360
|
|
factor = min(MAX_W / width, MAX_H / height, 2.0)
|
|
target_w = max(MIN_W, round(width * factor))
|
|
target_h = max(MIN_H, round(height * factor))
|
|
# The minimums can push past the maximums for extreme aspect ratios.
|
|
shrink = min(MAX_W / target_w, MAX_H / target_h, 1.0)
|
|
print("%dx%d" % (max(1, round(target_w * shrink)), max(1, round(target_h * shrink))))
|
|
' "$input") || editor_size=1000x700
|
|
# Enter saves the annotated image and copies it; Escape discards the capture.
|
|
# Keep the parent lock through editing. Close it only in Satty so clipboard
|
|
# helpers cannot inherit the lock and keep later screenshots blocked.
|
|
satty --filename "$input" --output-filename "$file" \
|
|
--app-id dev.ferese.Screenshot \
|
|
--resize "$editor_size" \
|
|
--no-window-decoration \
|
|
--copy-command 'wl-copy --type image/png' \
|
|
--save-after-copy \
|
|
--actions-on-enter save-to-file,save-to-clipboard,exit \
|
|
--actions-on-escape exit 9>&- || fail 'Could not open the screenshot in Satty.'
|
|
if [[ -s $file ]]; then
|
|
printf '%s\n' "$file"
|
|
fi
|