| ... | ... | @@ -87,9 +87,11 @@ export interface ShowcaseCapture { |
| 87 | 87 | } |
| 88 | 88 | |
| 89 | 89 | interface IosCaptureCleanup { |
| 90 | readonly name: string; |
| 90 | 91 | readonly udid: string; |
| 91 | 92 | readonly startedByRunner: boolean; |
| 92 | 93 | readonly createdByRunner: boolean; |
| 94 | readonly restorePortrait: boolean; |
| 93 | 95 | } |
| 94 | 96 | |
| 95 | 97 | interface AndroidCaptureCleanup { |
| ... | ... | @@ -792,6 +794,49 @@ async function normalizeIosSimulator(appearance: ShowcaseAppearance, udid: strin |
| 792 | 794 | ]); |
| 793 | 795 | } |
| 794 | 796 | |
| 797 | async function setIosSimulatorOrientation( |
| 798 | orientation: NonNullable<ShowcaseIosDevice["orientation"]>, |
| 799 | simulator: Pick<SimctlDevice, "name" | "udid">, |
| 800 | ): Promise<void> { |
| 801 | await runCommand("open", ["-a", "Simulator", "--args", "-CurrentDeviceUDID", simulator.udid]); |
| 802 | const menuItem = orientation === "landscape" ? "Landscape Right" : "Portrait"; |
| 803 | await runCommand("osascript", [ |
| 804 | "-e", |
| 805 | "on run argv", |
| 806 | "-e", |
| 807 | "set simulatorName to item 1 of argv", |
| 808 | "-e", |
| 809 | 'tell application "Simulator" to activate', |
| 810 | "-e", |
| 811 | 'tell application "System Events" to tell process "Simulator"', |
| 812 | "-e", |
| 813 | "set simulatorWindows to {}", |
| 814 | "-e", |
| 815 | "repeat 40 times", |
| 816 | "-e", |
| 817 | 'set simulatorWindows to menu items of menu "Window" of menu bar item "Window" of menu bar 1 whose name starts with simulatorName', |
| 818 | "-e", |
| 819 | "if (count of simulatorWindows) is greater than 0 then exit repeat", |
| 820 | "-e", |
| 821 | "delay 0.25", |
| 822 | "-e", |
| 823 | "end repeat", |
| 824 | "-e", |
| 825 | 'if (count of simulatorWindows) is not 1 then error "Expected exactly one Simulator window for " & simulatorName', |
| 826 | "-e", |
| 827 | "click item 1 of simulatorWindows", |
| 828 | "-e", |
| 829 | `click menu item "${menuItem}" of menu "Orientation" of menu item "Orientation" of menu "Device" of menu bar item "Device" of menu bar 1`, |
| 830 | "-e", |
| 831 | "end tell", |
| 832 | "-e", |
| 833 | "delay 1", |
| 834 | "-e", |
| 835 | "end run", |
| 836 | simulator.name, |
| 837 | ]); |
| 838 | } |
| 839 | |
| 795 | 840 | async function iosAppContainer(udid: string): Promise<string> { |
| 796 | 841 | return ( |
| 797 | 842 | await commandOutput("xcrun", ["simctl", "get_app_container", udid, ANDROID_PACKAGE, "data"]) |
| ... | ... | @@ -828,7 +873,13 @@ async function captureIos( |
| 828 | 873 | ): Promise<void> { |
| 829 | 874 | const { simulator, createdByRunner } = await ensureIosSimulator(capture.device); |
| 830 | 875 | const startedByRunner = simulator.state !== "Booted"; |
| 831 | | registerCleanup({ udid: simulator.udid, startedByRunner, createdByRunner }); |
| 876 | registerCleanup({ |
| 877 | name: simulator.name, |
| 878 | udid: simulator.udid, |
| 879 | startedByRunner, |
| 880 | createdByRunner, |
| 881 | restorePortrait: capture.device.orientation === "landscape", |
| 882 | }); |
| 832 | 883 | if (!startedByRunner) { |
| 833 | 884 | // Clear transient SpringBoard state (permission prompts, stale URL-open |
| 834 | 885 | // confirmations, keyboards) without erasing the developer's simulator. |
| ... | ... | @@ -887,6 +938,9 @@ async function captureIos( |
| 887 | 938 | "--showcaseScene", |
| 888 | 939 | firstScene, |
| 889 | 940 | ]); |
| 941 | if (capture.device.orientation === "landscape") { |
| 942 | await setIosSimulatorOrientation("landscape", simulator); |
| 943 | } |
| 890 | 944 | }; |
| 891 | 945 | await NodeFSP.rm(readyPath, { force: true }); |
| 892 | 946 | await NodeFSP.writeFile(scenePath, firstScene); |
| ... | ... | @@ -917,6 +971,9 @@ async function captureIos( |
| 917 | 971 | `${scene}.png`, |
| 918 | 972 | ); |
| 919 | 973 | await runCommand("xcrun", ["simctl", "io", simulator.udid, "screenshot", destination]); |
| 974 | if (capture.device.orientation === "landscape") { |
| 975 | await runCommand("sips", ["--rotate", "90", destination]); |
| 976 | } |
| 920 | 977 | await finalizeCapture(destination, capture.device); |
| 921 | 978 | } |
| 922 | 979 | } |
| ... | ... | @@ -1332,6 +1389,9 @@ async function main(): Promise<void> { |
| 1332 | 1389 | } |
| 1333 | 1390 | } |
| 1334 | 1391 | for (const cleanup of iosCleanups) { |
| 1392 | if (cleanup.restorePortrait) { |
| 1393 | await setIosSimulatorOrientation("portrait", cleanup).catch(() => undefined); |
| 1394 | } |
| 1335 | 1395 | if (cleanup.startedByRunner || cleanup.createdByRunner) { |
| 1336 | 1396 | await runCommand("xcrun", ["simctl", "shutdown", cleanup.udid]).catch(() => undefined); |
| 1337 | 1397 | } |