Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions packaging/macos/build-app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -517,16 +517,27 @@ build_store() {
# one keychain is an ordinary state, an expiring one beside its replacement,
# and picking whichever `grep` found first is how a package gets signed with
# the wrong one.
#
# One certificate can be reported several times over. `find-identity`
# searches every keychain in the search list, and the signing keychain
# `mac-signing-keychain.sh` makes holds a copy of what the login keychain
# already had, so a Mac set up to sign over ssh lists each identity at least
# twice. That is one identity seen twice and not two identities, and the
# SHA-1 each is listed under is what tells them apart. Counting lines
# refused every machine that had a signing keychain at all.
find_identity() {
matches=$(security find-identity -v 2>/dev/null |
grep "$1: .*(${store_team})" | sed 's/.*"\(.*\)"/\1/')
grep "$1: .*(${store_team})" |
sed 's/^ *[0-9]*) *\([0-9A-F]*\) *"\(.*\)"$/\1 \2/' |
sort -u)
count=$(printf '%s' "$matches" | grep -c . || true)
[ "$count" = 1 ] || {
echo "build-app.sh: expected one \"$1\" identity for team ${store_team}, found ${count}" >&2
[ "$count" = 0 ] || echo "$matches" | sed 's/^/ /' >&2
return 1
}
printf '%s' "$matches"
# The hash was for telling them apart; what signs is the name.
printf '%s' "${matches#* }"
}
app_identity=$(find_identity "Apple Distribution") || exit 1
# Apple's portal calls this Mac Installer Distribution; the certificate calls
Expand Down
44 changes: 40 additions & 4 deletions packaging/macos/screenshot.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,24 @@
# --out shots/xods-02-sheet.png
# ./packaging/macos/screenshot.sh xodp --app dist/'Odox Deck'.app \
# --document corpus/libreoffice/focus.odp --out shots/xodp-01.png
# ./packaging/macos/screenshot.sh xodt --lang de --out shots/de-DE/xodt-01.png
#
# The bundle defaults to `dist/<Product>.app`, which is where `build-app.sh`
# puts it, and the document to the corpus file `packaging/store-listing.md`
# names for that application. Only `--out` has no sensible default.
#
# `--lang` photographs the window in that language. A listing in two languages
# wants a set in each, and a German listing showing an English window is the
# inaccurate metadata guideline 2.3.3 is about. It is passed through
# `open --env` as POTEXT_LANG, which every application in this fleet reads
# before it asks the platform. It has to go through `--env` and cannot be
# exported here: `open` hands the process to launchd, and launchd does not pass
# this shell's environment on.
#
# `--settle` waits longer before the shutter. A deck whose slides carry pictures
# is still decoding them when a document of a few pages has settled, which is
# why `shots.sh` gives xodp more than the other two.
#
# FOUR ACTIONS, IN THE ORDER GIVEN
#
# `--click X,Y` presses a control. `--double X,Y` presses it twice inside the
Expand Down Expand Up @@ -94,6 +107,10 @@ name=""
bundle=""
document=""
out=""
lang=""
# Seconds between the window being sized and the capture. Five is enough for a
# text document and not for a deck; `--settle` is how a caller says so.
settle=5
# 1440x900 is one of the four sizes App Store Connect accepts for macOS, and the
# largest reachable without a Retina display. The other two — 2560x1600 and
# 2880x1800 — need a backing scale of 2, which is why they are not the default.
Expand Down Expand Up @@ -142,6 +159,8 @@ while [ $# -gt 0 ]; do
--double) echo "double ${2:?--double needs X,Y}" >> "$actions"; shift 2 ;;
--type) echo "type ${2?--type needs text}" >> "$actions"; shift 2 ;;
--key) echo "key ${2:?--key needs a name}" >> "$actions"; shift 2 ;;
--lang) lang="${2:?--lang needs a language tag}"; shift 2 ;;
--settle) settle="${2:?--settle needs seconds}"; shift 2 ;;
--width) width="${2:?}"; shift 2 ;;
--height) height="${2:?}"; shift 2 ;;
--x) x="${2:?}"; shift 2 ;;
Expand Down Expand Up @@ -336,8 +355,12 @@ swiftc -O -o "$helper" "$source" || refuse "the helper did not compile"
pkill -f "${bundle}/Contents/MacOS/" 2>/dev/null || true
sleep 1

open -a "$bundle" "$document"
sleep 5
if [ -n "$lang" ]; then
open -a "$bundle" --env "POTEXT_LANG=${lang}" "$document"
else
open -a "$bundle" "$document"
fi
sleep "$settle"

# Found once, by the path of the executable inside this bundle, and used by id
# everywhere below. The header says why not by name.
Expand Down Expand Up @@ -387,8 +410,21 @@ screencapture -x -o -l "$id" "$out"

got_w=$(sips -g pixelWidth "$out" | sed -n 's/.*pixelWidth: *//p')
got_h=$(sips -g pixelHeight "$out" | sed -n 's/.*pixelHeight: *//p')
if [ "$got_w" != "$width" ] || [ "$got_h" != "$height" ]; then
refuse "asked for ${width}x${height} and got ${got_w}x${got_h} — App Store Connect refuses anything but its own sizes"
# The window is sized in points and the capture is written in pixels, so a
# display with a backing scale of 2 returns twice what was asked for. Both are
# right: App Store Connect takes 1280x800, 1440x900, 2560x1600 and 2880x1800,
# and the larger pair is the smaller pair doubled. Comparing the file against
# the number asked for called a correct Retina capture wrong and refused it.
case "${got_w}x${got_h}" in
1280x800|1440x900|2560x1600|2880x1800) ;;
*)
refuse "got ${got_w}x${got_h}, and App Store Connect takes 1280x800, 1440x900, 2560x1600 or 2880x1800 and nothing else"
;;
esac
# Still the window that was asked for, at one scale or the other. A capture
# that is an accepted size but not this one is some other window.
if [ "$got_w" != "$width" ] && [ "$got_w" != "$((width * 2))" ]; then
refuse "asked for ${width}x${height} and got ${got_w}x${got_h}, which is neither that nor that at a backing scale of 2"
fi

echo "${out}: ${got_w}x${got_h}, window ${id} of ${product}"
Expand Down
110 changes: 110 additions & 0 deletions packaging/macos/shots.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/bin/sh
# The Mac App Store screenshots, as recipes rather than as prose.
#
# `screenshot.sh` beside this is the driver and knows nothing about Odox. This
# file is the part that is Odox's — which application, which document, and what
# has to happen in the window before the shutter. It is the counterpart of
# `packaging/windows/shots.ps1`, and the two carry the same table in their own
# platform's spellings.
#
# MACOSX_DEPLOYMENT_TARGET=11.0 cargo build --release
# ./packaging/macos/build-app.sh --all --sign "Apple Development: ..."
# ./packaging/macos/shots.sh # the English set
# ./packaging/macos/shots.sh --lang de # and the German one
# ./packaging/macos/shots.sh --only xodt # one application's frames
#
# Run it in Terminal at the console. Sizing another application's window goes
# through System Events, which is gated on Accessibility permission, and an ssh
# session cannot be granted it.
#
# WHAT IS PHOTOGRAPHED, WHICH IS NOT THE THING THAT SHIPS
#
# A signed development bundle built from the commit being released. The Store
# build cannot be launched on the machine that made it — the kernel refuses its
# entitlements without a profile covering this Mac, and a Store profile covers
# none — so no screenshot can ever be of the exact artefact that gets uploaded.
# Build both from one commit and say which in `packaging/store-listing.md`.
#
# Something is open in every shot: a screenshot of an empty window is what
# guideline 2.3.3 sends back.
#
# Author: David M. Anderson
# Built with AI assistance (Claude, Anthropic)
set -eu

here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
root=$(CDPATH= cd -- "${here}/../.." && pwd)

only=""
lang=""
bundles="${root}/dist"
outdir=""

usage() {
sed -n '2,29p' "$0" | sed 's/^# \{0,1\}//'
exit "${1:-0}"
}

refuse() { echo "shots.sh: $1" >&2; exit 1; }

while [ $# -gt 0 ]; do
case "$1" in
--only) only="${2:?--only needs xodt, xods or xodp}"; shift 2 ;;
--lang) lang="${2:?--lang needs a language tag}"; shift 2 ;;
--bundles) bundles="${2:?--bundles needs a directory}"; shift 2 ;;
--outdir) outdir="${2:?--outdir needs a directory}"; shift 2 ;;
-h|--help) usage 0 ;;
*) echo "shots.sh: unknown argument $1" >&2; usage 2 ;;
esac
done

# The language is a tag and the directory it lands in is a locale, and they are
# not the same string. The Windows lane maps them the same way, in
# `take-shots.ps1`, because a frame's directory is half the name the Store files
# it under and the two lanes have to agree.
case "$lang" in
"") locale="" ;;
en*) locale="en-US" ;;
de*) locale="de-DE" ;;
*) refuse "no locale is known for $lang" ;;
esac

# Where the shots land. Not committed: dist is where every built artefact goes.
[ -n "$outdir" ] || outdir="${root}/dist/screenshots"
[ -z "$locale" ] || outdir="${outdir}/${locale}"

# The three strings each application needs here. `build-app.sh` holds the full
# table and `packaging/windows/shots.ps1` holds the same one in that platform's
# spellings; only these columns are wanted.
#
# A deck whose slides carry pictures is still decoding them when a document of a
# few pages has settled, so xodp waits longer than the driver's default. The
# Windows lane gives it the same eight seconds.
for app in xodt xods xodp; do
[ -z "$only" ] || [ "$only" = "$app" ] || continue
case "$app" in
xodt) product="Odox Text"; document="corpus/libreoffice/text.odt"; settle=5 ;;
xods) product="Odox Grid"; document="corpus/libreoffice/calc.ods"; settle=5 ;;
xodp) product="Odox Deck"; document="corpus/libreoffice/growing-liberty.odp"; settle=8 ;;
esac
bundle="${bundles}/${product}.app"
[ -d "$bundle" ] || refuse "no bundle at ${bundle} — build one with 'packaging/macos/build-app.sh ${app} --sign ...'"

mkdir -p "$outdir"
out="${outdir}/${app}-01-document.png"

# No actions yet. These three read a document and do not edit one, so the
# document on screen is what they do; a frame wanting a pane opened or a
# slide selected takes a coordinate, and a coordinate is read off a frame
# of the same size rather than guessed.
echo "==> ${app}${lang:+ (${lang})}"
"${here}/screenshot.sh" "$app" \
--app "$bundle" \
--document "${root}/${document}" \
--settle "$settle" \
${lang:+--lang "$lang"} \
--out "$out"
done

echo
find "$outdir" -name '*.png' | sort
Loading