Skip to content

iOS simulator rotation through Simulator.app #1020

Closed as not planned
Closed as not planned

Description

@V3RON

Part of #1013.

Scope

After this PR, a rotation sent on WS /v1/leases/{id}/input turns an iOS simulator's screen, if the one-off check passes. If it fails, the PR delivers only a docs line and a follow-up issue, and rotation stays dropped on iOS. Either way, it settles what 0x07 does on iOS.

In short

serve-sim rotates a simulator with a message to a port that exists only while Simulator.app is attached. So this task starts with one check on one worker. The worker starts Simulator.app for a simulator in Simlock's own device set, then sends a rotation. Only if the picture turns does rotation ship.

flowchart TD
  C{one-off check: Simulator.app rotates a simulator in Simlock's set?}
  C -- yes --> R[0x07 → helper → PurpleWorkspacePort; Simulator.app started on first rotation, left open]
  C -- no --> D[docs line + follow-up issue; 0x07 stays dropped]
Loading
  • one-off check: a single run of e2e/slow-ios-rotation.test.ts on one worker before any rotation code.
  • A rotation is 0x07 {"orientation":"landscape_left"}. The four names are portrait, portrait_upside_down, landscape_left and landscape_right.

If this goes wrong, a person sees a Simulator.app window open on a worker, or a rotation that does nothing.

Technical spec

Facts

Commit e1f2f43.

# Fact Source
F1 serve-sim sends a rotation as a GSEvent to the simulator's PurpleWorkspacePort, found with -[SimDevice lookup:error:]. When the port is missing it prints "Simulator.app must be running." EvanBacon/serve-sim v6d009686:packages/serve-sim/Sources/SimNative/HIDInjector.swift:649-700
F2 The orientation values are portrait 1, upside down 2, landscape right 3, landscape left 4. EvanBacon/serve-sim v6d009686:packages/serve-sim/Sources/SimNative/HIDInjector.swift:631-634
F3 Expo serve-sim maps the four names to those values, and drops an unknown name. expo/expo-device-hub v1357114:packages/serve-sim/packages/serve-sim/src/device-session.ts:190-195, v1357114:packages/serve-sim/packages/serve-sim/src/device-session.ts:1620-1630
F4 Simlock's simulators live in their own device set, named to the capture program by SIMLOCK_IOS_DEVICE_SET. src/drivers/ios/screen-capture.ts:120-124, vendor/screen-capture/Sources/simlock-screen-capture/FrameCapture.swift:425-436
F5 The worker runs as a per-user LaunchAgent in the gui/<uid> launchd domain. src/ports/service-manager.ts:74, src/ports/service-manager.ts:215-221
F6 Nothing in src/ starts Simulator.app today: a search for Simulator.app, open -a Simulator, com.apple.iphonesimulator and CurrentDeviceSet finds nothing. grep -rn over src/ at e1f2f43, 0 hits

Modules touched

Searches: sendOrientation, PurpleWorkspacePort, Simulator.app, open -a, 0x07, and #1019's input mode, in src/, vendor/ and e2e/.

If the check passes:

  • vendor/screen-capture/Sources/simlock-screen-capture/: the input mode takes 0x07 and calls sendOrientation, which is already in the re-vendored HIDInjector.swift (iOS simulator input through the HID injector #1019).
  • src/drivers/ios/input.ts: maps the name, starts Simulator.app on the first rotation, forwards 0x07.
  • src/drivers/ios/AGENTS.md, website/src/content/docs/docs/using-devices/watching-the-screen.mdx.

Either way:

  • e2e/slow-ios-rotation.test.ts (new): the one-off check, kept as the slow-lane test.
sequenceDiagram
  participant M as input module
  participant I as iOS device input
  participant S as Simulator.app
  participant H as helper --input
  M->>I: 0x07 {orientation: landscape_left}
  I->>S: open -a Simulator (first rotation only)
  I->>H: [len][0x07]{orientation: 4}
  H->>H: lookup PurpleWorkspacePort, send GSEvent
  Note over H: port missing
  H-->>I: dropped (logged)
Loading

Contract and event changes

none. Searched src/contract/: 0x07 is #1017's core/input.ts. No event.

Other code on the same state

Searches: Simulator.app, iphonesimulator, PurpleWorkspacePort in src/ and vendor/ (0 hits before this task).

  • A capture of the same simulator: rotation changes the picture's shape. The slow test checks the next frame; how the capture follows a size change is not changed here.
  • agent-device rotating through its own XCUITest runner: whichever runs last wins.
  • A person using Simulator.app on the worker's screen: Simlock opens it once and never closes it.

Failure modes

Call Outcome What happens Limit, counted from
open -a Simulator ok Simulator.app runs; rotations go through 30 s, from the call
open -a Simulator fails or times out this rotation is dropped and logged; the next one tries again 30 s, from the call
helper sendOrientation port missing rotation dropped and logged; socket stays open 5 s, from the write
unknown orientation name — dropped, as serve-sim does (F3) none

A worker restart leaves Simulator.app running, which is harmless. Two sockets of one lease share one device input (#1017), so two starts never race for one lease. Two leases on one worker may both run open.

Rules in play

  • architecture rule 11, "Every wait that crosses a process boundary is bounded": open and the helper write have limits.
  • architecture rule 2, "iOS and Android are encapsulated in their own driver modules": Simulator.app is known only to src/drivers/ios/.
  • testing rule 7, "Prove each claim once, at the cheapest level that can prove it": the port behaviour is only provable on a Mac, so the check is a slow-lane test, and the mapping is a unit test.

Tests

Seam: createIosDeviceInput with a fake launcher and a scripted helper; model on src/drivers/ios/screen-capture.test.ts.

  • A rotation on an iOS simulator turns the screen through Simulator.app, started on first use: each of the four orientations is written to the helper as 1 to 4; Simulator.app not yet running (started once); start fails (dropped).

Done when

  • e2e/slow-ios-rotation.test.ts has run once on one worker, and its result is on this PR (slow lane, iOS simulator).
  • If it passed: the Tests line passes in pnpm test, the same slow test turns a simulator to landscape and the stream's next frame is wider than it is tall, and watching-the-screen.mdx says Simulator.app opens on a worker on the first rotation.
  • If it failed: watching-the-screen.mdx says rotation does nothing on iOS simulators, a follow-up issue records what the check saw, and no rotation code merges.

Out of scope

  • Closing Simulator.app.
  • Rotation on physical iPhones.
  • Rotation through agent-device.

Depends on

Written by an agent.

Activity

  1. added
    task:draftScope written; technical spec or its spec check missing.
    on Oct 26, 2026
  2. V3RON commented on Oct 26, 2026

    @V3RON
    ContributorAuthor

    Closed: rotation left #1013 on both platforms. On Xcode 27 neither Simulator.app (not shipped) nor CoreDevice's vendor channel (cannot see a custom device set; no effect on a non-foldable iPhone) rotates a Simlock simulator. The experiments are recorded in #1024.

    Written by an agent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    task:draftScope written; technical spec or its spec check missing.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions