screenshots-marketing.sh (8011B)
1 #!/bin/bash 2 set -euo pipefail 3 4 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 5 REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" 6 source "${SCRIPT_DIR}/select-simulator.sh" 7 8 PLATFORM="iphone" 9 ADD_DYNAMIC_ISLAND=true 10 while getopts "p:n" opt; do 11 case $opt in 12 p) PLATFORM="$OPTARG" ;; 13 n) ADD_DYNAMIC_ISLAND=false ;; 14 *) ;; 15 esac 16 done 17 shift $((OPTIND - 1)) 18 19 MAJOR="${1:-26}" 20 case "$PLATFORM" in 21 iphone|ipad) ;; 22 *) 23 echo "Unsupported platform '${PLATFORM}'. Use iphone or ipad." >&2 24 exit 1 25 ;; 26 esac 27 28 set_simulator_landscape_left() { 29 # The Device ▸ Orientation ▸ Landscape Left menu path (the extra 30 # `menu bar item "Device"` level is required on the iOS 26 Simulator). 31 # "Landscape Left" is absolute, so clicking it repeatedly is idempotent. 32 osascript \ 33 -e 'tell application "Simulator" to activate' \ 34 -e 'delay 0.3' \ 35 -e 'tell application "System Events" to tell process "Simulator" to click menu item "Landscape Left" of menu "Orientation" of menu item "Orientation" of menu "Device" of menu bar item "Device" of menu bar 1' \ 36 >/dev/null 2>&1 || true 37 } 38 39 set_ipad_landscape() { 40 local udid="$1" 41 42 # Bring the target device's window to the front — the orientation menu acts on 43 # the frontmost device window, so this must run whether or not the Simulator 44 # (or this device) was already booted. 45 open -a Simulator --args -CurrentDeviceUDID "$udid" >/dev/null 2>&1 || true 46 sleep 1 47 48 # Click a few times: right after the window comes forward the orientation menu 49 # item can be briefly disabled, no-oping the click. The command is idempotent, 50 # so extra clicks are harmless once landscape has taken. 51 local attempt 52 for attempt in 1 2 3; do 53 set_simulator_landscape_left 54 sleep 1 55 done 56 } 57 58 if [ "$PLATFORM" = "iphone" ]; then 59 select_simulator "$MAJOR" 60 CANVAS_ARGS=() 61 else 62 MARKETING_IPAD_NAME="Crossmate Marketing iPad" 63 RUNTIME=$(xcrun simctl list runtimes available \ 64 | grep "iOS ${MAJOR}\." \ 65 | sed 's/.*iOS \([0-9.]*\).*/\1/' \ 66 | sort -t. -k1,1n -k2,2n \ 67 | tail -1 || true) 68 DESTINATION_OS=$(xcrun simctl list runtimes available \ 69 | grep "iOS ${MAJOR}\." \ 70 | sed 's/.*(\([0-9.]*\) -.*/\1/' \ 71 | sort -t. -k1,1n -k2,2n -k3,3n \ 72 | tail -1 || true) 73 if [ -z "$RUNTIME" ] || [ -z "$DESTINATION_OS" ]; then 74 echo "No available iOS ${MAJOR}.x simulator runtime found." >&2 75 exit 1 76 fi 77 78 UDID=$(xcrun simctl list devices \ 79 | grep -F "${MARKETING_IPAD_NAME} (" \ 80 | head -1 \ 81 | sed -E 's/.*\(([0-9A-Fa-f-]{36})\).*/\1/' || true) 82 if [ -n "$UDID" ]; then 83 DEVICE="$MARKETING_IPAD_NAME" 84 else 85 DEVICE_TYPE_LINE=$(xcrun simctl list devicetypes \ 86 | grep -F "iPad Pro 13-inch" \ 87 | grep -v "16GB" \ 88 | head -1 \ 89 || true) 90 if [ -z "$DEVICE_TYPE_LINE" ]; then 91 DEVICE_TYPE_LINE=$(xcrun simctl list devicetypes \ 92 | grep -F "iPad" \ 93 | head -1 \ 94 || true) 95 fi 96 DEVICE=$(sed -E 's/ \(com\.apple[^)]*\)$//' <<<"$DEVICE_TYPE_LINE") 97 DEVICE_TYPE_ID=$(sed -E 's/.*\((com\.apple[^)]*)\)$/\1/' <<<"$DEVICE_TYPE_LINE") 98 RUNTIME_ID=$(xcrun simctl list runtimes available \ 99 | grep "iOS ${RUNTIME} (" \ 100 | head -1 \ 101 | sed -E 's/.* - (com\.apple[^ ]*).*/\1/' || true) 102 if [ -z "$DEVICE_TYPE_ID" ] || [ -z "$RUNTIME_ID" ]; then 103 echo "Couldn't resolve a device type / runtime to create '${MARKETING_IPAD_NAME}'." >&2 104 exit 1 105 fi 106 echo "Creating '${MARKETING_IPAD_NAME}' (${DEVICE}, iOS ${RUNTIME})" 107 UDID=$(xcrun simctl create "${MARKETING_IPAD_NAME}" "${DEVICE_TYPE_ID}" "${RUNTIME_ID}") 108 DEVICE="$MARKETING_IPAD_NAME" 109 fi 110 CANVAS_ARGS=(2752 2064) 111 fi 112 113 echo "Using ${DEVICE}, iOS ${RUNTIME}" 114 115 if [ -z "${UDID:-}" ]; then 116 UDID=$(xcrun simctl list devices available "iOS ${RUNTIME}" \ 117 | grep "$DEVICE" \ 118 | head -1 \ 119 | sed 's/.*(\([A-F0-9-]*\)).*/\1/') 120 fi 121 122 xcrun simctl boot "$UDID" 2>/dev/null || true 123 xcrun simctl bootstatus "$UDID" -b 124 125 # Marketing status-bar time/date: 9:41 AM (Apple's canonical time) on 2026-04-09, 126 # the date of this repo's first commit to master. Passing a full ISO 8601 string 127 # to --time sets BOTH the date and the time on devices whose status bar shows a 128 # date (iPad); a bare "9:41" sets only the time. The date comes from the UTC 129 # instant, so convert the wall-clock 9:41 in the local zone first. 130 # NOTE: current Simulator versions apply only the time from this string and fall 131 # back to today's date; the date half may start working if Apple fixes that. 132 MARKETING_EPOCH=$(date -j -f "%Y-%m-%d %H:%M:%S" "2026-04-09 09:41:00" "+%s") 133 MARKETING_TIME=$(date -u -r "$MARKETING_EPOCH" "+%Y-%m-%dT%H:%M:%S.000Z") 134 135 xcrun simctl status_bar "$UDID" override \ 136 --time "$MARKETING_TIME" \ 137 --batteryState discharging \ 138 --batteryLevel 100 \ 139 --wifiBars 3 \ 140 --cellularBars 4 \ 141 --cellularMode active \ 142 --dataNetwork wifi 143 144 echo "Status bar overridden (time/date: ${MARKETING_TIME})" 145 146 # The import scene renders the real (system-appearance) Game List + sheet; the 147 # other scenes force .light in code. Pin the sim to light so all scenes match. 148 xcrun simctl ui "$UDID" appearance light 2>/dev/null || true 149 150 DERIVED_DATA="/tmp/crossmate-marketing-derived" 151 WORK_DIR="/tmp/crossmate-marketing-${PLATFORM}" 152 MARKETING_DIR="${REPO_DIR}/Marketing" 153 BUNDLE_ID="net.inqk.crossmate" 154 155 SCENES=(together solo import replay undo-redo) 156 CAPTIONS=( 157 "Solve Crosswords Together" 158 "Solve Crosswords Solo" 159 "Import Across Lite and XD" 160 "Watch Puzzle Replays" 161 "Undo/Redo Moves" 162 ) 163 164 rm -rf "$DERIVED_DATA" "$WORK_DIR" 165 mkdir -p "$WORK_DIR" "$MARKETING_DIR" 166 167 if [ "$PLATFORM" = "ipad" ]; then 168 set_ipad_landscape "$UDID" 169 fi 170 171 xcodebuild build \ 172 -quiet \ 173 -scheme "Crossmate" \ 174 -project "${REPO_DIR}/Crossmate.xcodeproj" \ 175 -destination "id=${UDID}" \ 176 -derivedDataPath "$DERIVED_DATA" 177 178 APP_PATH="${DERIVED_DATA}/Build/Products/Debug-iphonesimulator/Crossmate.app" 179 if [ ! -d "$APP_PATH" ]; then 180 echo "Built app not found at ${APP_PATH}" >&2 181 exit 1 182 fi 183 184 xcrun simctl install "$UDID" "$APP_PATH" 185 186 for index in "${!SCENES[@]}"; do 187 scene="${SCENES[$index]}" 188 caption="${CAPTIONS[$index]}" 189 raw_path="${WORK_DIR}/${scene}.png" 190 rotated_path="${WORK_DIR}/${scene}-rotated.png" 191 island_path="${WORK_DIR}/${scene}-island.png" 192 framed_path="${WORK_DIR}/${scene}-framed.png" 193 output_path="${MARKETING_DIR}/${PLATFORM}_${index}.png" 194 195 launch_args=( 196 --crossmate-marketing-screenshot 197 --crossmate-marketing-scene "$scene" 198 ) 199 if [ "$scene" = "undo-redo" ]; then 200 launch_args+=(--crossmate-marketing-symbol-keyboard) 201 fi 202 if [ "$scene" = "replay" ]; then 203 launch_args+=(--crossmate-marketing-replay-position 11) 204 fi 205 if [ "$scene" = "import" ]; then 206 # Boots the real Game List (seeded, three-player-max) as the sheet backdrop. 207 launch_args+=(--crossmate-seed-marketing) 208 fi 209 210 echo "" 211 echo "Capturing ${PLATFORM}_${index}.png - ${caption}" 212 xcrun simctl launch --terminate-running-process "$UDID" "$BUNDLE_ID" "${launch_args[@]}" 213 sleep 3 214 xcrun simctl io "$UDID" screenshot --mask ignored "$raw_path" 215 216 frame_input="$raw_path" 217 if [ "$PLATFORM" = "ipad" ]; then 218 sips -r 270 "$raw_path" --out "$rotated_path" >/dev/null 219 frame_input="$rotated_path" 220 fi 221 if [ "$PLATFORM" = "iphone" ] && [ "$ADD_DYNAMIC_ISLAND" = true ]; then 222 swift "${SCRIPT_DIR}/screenshots-iphone-island.swift" "$raw_path" "$island_path" 223 frame_input="$island_path" 224 fi 225 226 shortcuts run "Frame Screenshots" -i "$frame_input" -o "$framed_path" 227 if [ "$PLATFORM" = "ipad" ]; then 228 swift "${SCRIPT_DIR}/screenshots-ios-compose.swift" \ 229 "$framed_path" \ 230 "$output_path" \ 231 "$caption" \ 232 "${CANVAS_ARGS[@]}" 233 else 234 swift "${SCRIPT_DIR}/screenshots-ios-compose.swift" \ 235 "$framed_path" \ 236 "$output_path" \ 237 "$caption" 238 fi 239 ls -la "$output_path" 240 done 241 242 xcrun simctl status_bar "$UDID" clear 2>/dev/null || true 243 rm -rf "$WORK_DIR" 244 245 echo "" 246 echo "Screenshots saved to ${MARKETING_DIR}/" 247 ls -la "${MARKETING_DIR}/${PLATFORM}_"*.png