Skip to content

Connect an iPhone ​

Physical iPhone support is not reliable yet. Read the open problems first.

First-Time USB Preparation ​

  1. Connect the iPhone to your Mac over USB, unlock the phone, and accept Trust This Computer.
  2. On the iPhone, enable Developer Mode and turn on UI Automation in Developer Settings. Complete all on-device prompts manually.
  3. Ensure developer services are mounted before capturing screens or injecting input. If compatible Apple-signed Developer Disk Image files already exist on your Mac under /Library/Developer/DeveloperDiskImages/iOS_DDI/Restore, run:
sh
node bin/prepare-device.mjs YOUR_DEVICE_ID

(If exactly one device is connected, YOUR_DEVICE_ID can be omitted.) This helper checks device services and mounts the local image when needed; it does not download Apple files, alter security settings, or reboot the device.

Post-Pairing Wi-Fi Operation ​

Once paired over USB, the Mac and iPhone can communicate over the same local network for wireless discovery and supported operations while the phone stays unlocked and reachable:

  • Verified wireless operations: Prior hardware testing (with USB disconnected) verified screenshots, accessibility field readback, standard key input, backward/forward deletion, Unicode paste, and verified paste followed by Return. (Note: Those checks were not re-run as a full hardware pass after later input changes.)
  • Native app launch: Physical launch_app and open_settings without a page use the paired device’s RemoteXPC AppService directly. An explicit bundle ID skips application discovery; name lookup uses the advertised streaming app list. Arguments, environment, language, and locale are preserved. A positive process ID must match the observed foreground before success is reported. Foreground verification makes at most three observation attempts with a 2.5-second attempt limit and an 8-second total observation deadline; child-process cleanup completes before returning. Only observation is retried, never launch. If launch may have happened but cannot be confirmed, the receipt preserves known facts and stops even a permissive batch.
  • Application discovery: Physical list_apps uses the paired device’s advertised streaming app list, including for shared name resolution. System filtering retains the application-path distinction. Container-access eligibility is not supplied by this list and is not inferred.
  • Wireless limitations: Application install/uninstall use paired InstallationProxy and verify the resulting app inventory. If an installation is interrupted, its private recovery record remains under the state directory at install/operations/<UUID>.json, identifying the exact possible device staging path; it is never automatically retried or deleted while its status is uncertain. Remaining process/container management and Settings deep links still use Apple devicectl, which may fail to discover a wirelessly paired device (CoreDeviceError 1000) or reject a launch (4016). Native input and app launch do not prove those separate management paths work.

Open problems ​

The earlier measurements below come from a single iPhone running iOS 27.3 over Wi-Fi. Treat them as a test log from that setup, not a guarantee for other apps or devices.

  • With the old connection handling, tapping a specific search field in a chat app dropped 2 of 70 touches. Every dropped touch still returned sent: true. Keeping the connection open until the post-touch wait finished dropped 0 of 70 touches, and the final build dropped 0 of 24. These numbers only apply to that single search field.
  • Via MCP in the same app, tap_text took effect 12 of 12 times. Batches that launched the app and called tap_text opened the conversation 14 of 14 times.
  • Via MCP, fill on that search field worked 6 of 6 times and took about 15 seconds. If fill cannot verify the entered text, it now returns an error and stops the batch, matching Simulator behavior. tap_text took about 8 seconds.
  • Two earlier tap_text calls on a conversation row returned sent: true but had no effect. We have not reproduced this failure, so it may have a different cause. Why the device drops touches is unknown.
  • Touches sent too soon after app launch may have no effect. At 1.5 seconds after launch, 0 of 3 taps opened the conversation. At 2.8 seconds or later, 12 of 12 did. Via MCP, the pre-tap screen reads have so far delayed the tap past this window.
  • Tapping a target next to a bottom floating bar had no effect 4 of 4 times in the Settings app. This is not fixed. Those taps now return screen_changed: false.
  • We only measured taps. drag and pinch use the same connection path and were not measured. The latest update to how screen_changed compares screens has not been tested with touches on a physical phone.
  • In one landscape game, taps on button coordinates had no effect, while portrait taps in the same session worked. The cause is unknown.

How input reaches the device ​

  • Input goes through a standalone Rust program in native/bridge/, built on the pinned idevice public protocol interfaces.
  • sent=true only means the touch or key reports were written to the connection. The device does not acknowledge these reports, so a dropped report produces no error. Check the screen through accessibility or a screenshot.
  • Keyboard input registers a virtual keyboard over UniversalHID. Unicode text goes through the clipboard and Command-V, and the bridge never taps Allow Paste.