Profile
Back to NewsBack
GitHub Trending 17 min
Reader Mode
DanielLemky/omarchy-iphone-mirror: On-demand iPhone mirroring and focused input for Omarchy over USB or Wi-Fi.

DanielLemky/omarchy-iphone-mirror: On-demand iPhone mirroring and focused input for Omarchy over USB or Wi-Fi.

15 hours ago

Omarchy iPhone Mirror

An on-demand USB or Wi-Fi mirror application with an optional Omarchy bar plugin.

Early alpha — limited device compatibility. Tested on ARM64 Omarchy with an iPhone 13 running iOS 27. Intel support is not verified.

Install the alpha

Run as your normal user in a terminal, not with sudo:

curl -fsSL https://github.com/daniellemky/omarchy-iphone-mirror/releases/download/v0.1.3/install-online.sh | bash -s -- --release v0.1.3

This installs the early alpha version specified above, not development HEAD or a later release. The guide explains phone trust, Developer Mode, and Wi-Fi pairing. The viewer does not start automatically.

Read the requirements and installation details, including how to inspect the script before running it. Close any running mirror before installation.

Install with an agent

Copy this prompt into your coding agent:

Install and set up iPhone Mirror on this Omarchy machine. Read https://github.com/daniellemky/omarchy-iphone-mirror/blob/master/README.md and install the version specified in its install command, with --skip-phone-setup appended. Do not select a different version or development HEAD. Then read docs/agent-setup.md from the retained release directory printed by the installer, not from another checkout or the current development branch. Follow those instructions and guide me one step at a time. Ask for approval before installing system packages or performing each phone-changing operation. I will enter passcodes and approve prompts on the phone myself. Ask before starting the viewer, then help me test USB and Wi-Fi. Finish by explaining how to open iPhone Mirror from the Omarchy application launcher and close it with Super + W.

The application owns video, input, and connection management. The plugin calls the application's public commands. It does not handle USB. The separate Bluetooth controller plugin is unchanged.

Features

  • Direct HEVC playback in MPV with low-latency settings and a local cursor.
  • The window follows the phone's portrait and landscape orientation. In floating mode, it starts at a portrait size before connection, then follows the phone display ratio, with space for the toolbar. Hyprland handles resizing. After the size stops changing briefly, the mirror adjusts only the other dimension to fit the phone display. This applies to mouse and keyboard resize commands. No resize key bindings are added. In tiled mode, Hyprland controls the window size. Taps are remapped to the rotated picture.
  • Display-size metadata removes small encoder padding before rotation. If the metadata is unavailable or does not match the stream, the picture is not cropped.
  • Taps, drags, vertical wheel scrolling, and focused-window keyboard input over USB or Wi-Fi.
  • Centered Home and Spotlight buttons. Home uses a hardware-button event; Spotlight uses Command+Space. Neither uses a swipe.
  • Input release on focus loss and shutdown. No toggle shortcut is required.
  • One application instance, private runtime files, and structured status.
  • Manual start only: no startup at login or when a USB cable is connected.
  • No saved video or keystrokes. Diagnostic errors exclude input contents.

Requirements

  • Omarchy/Linux with a working systemd user session and Hyprland.
  • Python 3.14 (tested baseline), MPV, usbmuxd, iproute2, and wl-clipboard.
  • A trusted iPhone with Developer Mode enabled.
  • A developer image that provides the CoreDevice display and input services. A previously cached, matching image can be mounted automatically for a USB or paired Wi-Fi launch if none is mounted.
  • Tested phone: iPhone 13, iOS 27.0 build 24A437, developer image 27A5228h.
  • ARM64 Omarchy is the reference test host. Intel/x86-64 USB worked in a manual test. One later Intel test with an iPhone 15 Pro Max on iOS 27.2 had USB and Wi-Fi video and input in an initial session; later sessions on that phone stopped with player-backlog (#13). An earlier Intel iPhone 13 Wi-Fi session still failed with player-backlog. Intel support is not verified.
Compatibility with other models and versions is not yet established. AssistiveTouch and Bluetooth input are not required. If the Bluetooth controller is running, leave it in computer mode while using this application.

Compatibility observations

These are results from specific phones and software combinations, not a complete support matrix or a proven minimum iOS version.

| Host | Phone / iOS | Result | | --- | --- | --- | | ARM64 Omarchy | iPhone 13, iOS 27.0 build 24A437, developer image 27A5228h | USB and Wi-Fi mirroring and input worked in manual tests. | | Intel/x86-64 Omarchy | Known-working iPhone 13, iOS 27.0, developer image 27A5228h | The user confirmed USB was working. Wi-Fi opened but stopped with player-backlog; the failure repeated. Individual input checks have not been recorded separately. Intel support remains unverified. | | Intel/x86-64 Omarchy | iPhone 15 Pro Max, iOS 27.2, developer image 27A5228h | USB worked. Wi-Fi opened and worked in an initial session; later sessions on the same phone stopped with player-backlog (#13). | | Intel/x86-64 Omarchy | Second test phone, iOS 17.1.1 | The current USB tunnel requires iOS 17.4 or later. Wi-Fi connected and the display service was present, but it reported zero supported media features; video startup failed. | | Intel/x86-64 Omarchy | Same second phone after updating to iOS 18.7.10, developer image 27A5228h | The image mounted and the USB tunnel connected. The display service still reported zero supported media features; usable mirroring was not established. |

The second phone's model has not been recorded. These results do not prove that all iOS 17 or 18 devices fail, that iOS 27 is required, or that Intel is the cause. The known-working iOS 27 phone also worked over USB on Intel. A separate Intel test with an iPhone 15 Pro Max on iOS 27.2 had USB and Wi-Fi video and input working in an initial session; later sessions on that phone stopped with player-backlog (#13). That does not establish general Intel or 15 Pro Max support. A mounted image and an advertised display service do not by themselves establish mirroring support.

The Intel machine also has other video and suspend problems. These do not establish the cause of the mirror failure. A diagnostic test showed software HEVC decoding at about one CPU core's capacity with the app's one-thread setting. A temporary hardware-decoding test was inconclusive. Decoder settings remain unchanged; ARM64 remains the release-test reference.

Installation details

Repository: https://github.com/daniellemky/omarchy-iphone-mirror

Inspect the alpha installer

The alpha command above selects an explicit published version. The script verifies the source archive's SHA-256 checksum before running the existing installer. Phone prompts use the terminal, not the pipe. System packages are not installed automatically, and the viewer is not started.

The checksum detects corrupt or mismatched downloads. It is not an independent signature: the script, archive, and checksum are trusted GitHub release assets. To inspect the script before running it, save install-online.sh from the exact URL in the install command above and open it in a text editor. Then run it with Bash, using the same --release argument shown in that command.

For installation without phone setup, append --skip-phone-setup to the alpha command, keeping its --release argument. Without a terminal, this option is required.

There is no stable release yet. Without --release, the bootstrap selects only the latest stable release; it will not silently install an alpha. The version above is marked as a pre-release. Do not use the /releases/latest/download/ install route until a stable release is published.

The downloaded source stays in a unique directory under ${XDG_DATA_HOME:-$HOME/.local/share}/iphone-mirror-releases/. The script prints the paths for repeating setup and uninstalling. It does not overwrite an existing checkout. A failed application install leaves this source available for a manual retry.

Install from a source checkout

Clone or copy this repository, open a terminal in its directory, and close any running mirror. Run as your normal user, not with sudo:

./install.sh

If a dependency is missing, the installer prints an omarchy pkg add command. Run that command yourself, then repeat ./install.sh. Installation needs access to Python packages unless you have configured an offline pip source.

The installer creates:

| Location | Purpose | | --- | --- | | ~/.local/share/iphone-mirror/ | Application and private Python environment | | ~/.local/bin/iphone-mirror | Command client | | ~/.local/share/applications/iphone-mirror.desktop | Application launcher with automatic connection selection | | ~/.config/systemd/user/iphone-mirror.service | On-demand user service |

The installer refuses to replace a running application and does not enable or start the service. In an interactive terminal, it continues directly into guided phone setup. Each trust, image-mount, or Wi-Fi-pairing operation requires separate confirmation. Use ./install.sh --skip-phone-setup to skip the guide, for example during an update. Noninteractive installs also skip it.

XDG data and config overrides are supported. Spaces and single quotes in paths are tested. Paths containing backslashes, double quotes, dollar signs, backticks, percent signs, or line breaks are rejected before installation.

Only the direct pymobiledevice3 requirement is pinned. Transitive dependencies are not yet fully locked.

Prepare the phone

Follow the guide that starts after installation, or run it again later:

./setup-phone.sh

You can instead follow the manual phone setup instructions. Installing the application alone does not complete phone preparation. Setup does not start the viewer, and successful setup commands do not prove display-service compatibility.

Agent-assisted setup

The release includes separate JSON setup commands and a copy-and-paste agent prompt. See agent setup instructions. Phone changes still require individual approval, and passcodes and phone confirmations remain human-only. Real-phone testing of these new commands is still pending.

Commands

iphone-mirror start       # Start, or focus the existing window
iphone-mirror stop        # Release input and stop the session
iphone-mirror restart     # Stop fully, then start
iphone-mirror status      # JSON status; no USB connection is opened
iphone-mirror reload-ui   # Redraw toolbar without restarting capture

The window shows connection status while it starts. If the connection fails or disconnects, the window stays open and shows Retry Connection. Click the button, or press Enter, to try again in the same window. Device selection, each tunnel-opening attempt, and stream startup each have a separate 30-second limit. Wi-Fi discovery and endpoint connection attempts use up to 20 seconds within each tunnel-opening limit. If image preparation is needed over Wi-Fi, startup opens a fresh tunnel after preparation. USB or Wi-Fi image preparation can take up to 90 seconds; opening the window and cleanup can take longer. Retry closes the previous phone session before selecting the connection again.

Closing the viewer also stops the session. If you launch it again while the previous session is still closing, a temporary window shows Closing previous connection... until cleanup finishes. It then opens a new mirror window. Close the temporary window to cancel the launch. Existing window focus uses Hyprland. Logs are available with:

journalctl --user -u iphone-mirror -n 30 --no-pager

The old prototype uses iphone-usb-mirror.service. The new application refuses to run while that prototype service is active, so it cannot create a second stream by accident.

Automatic connection

There is one iPhone Mirror launcher. Each new session uses USB if a matching phone is connected; otherwise it uses Wi-Fi. No connection selector or settings dialog is needed.

Connecting or removing a cable does not change an active session. After a failure or disconnect, use Retry Connection to select the available connection again. You can also close and reopen the viewer. An attached USB phone with a connection error does not silently fall back to Wi-Fi. iphone-mirror start focuses an existing session without changing it.

Explicit CLI options remain available for diagnostics: --connection usb, --connection wifi, and --serial DEVICE_UDID.

Wi-Fi requires an existing CoreDevice pairing record and a local network connection between the computer and phone. On Linux, that record is in ~/.pymobiledevice3/ when that directory already exists, and otherwise under ${XDG_DATA_HOME:-$HOME/.local/share}/pymobiledevice3/. Discovery replaces fixed IP addresses. Wi-Fi startup repeats discovery within a bounded time limit if the phone does not answer the first request. If no phone is found, the viewer reports iPhone not found on Wi-Fi. Check that the phone is on the same local network, then use Retry. This does not guarantee that a sleeping phone will answer. The application does not create new pairing records or change firewall settings. It rejects the iPhone USB-tethering interface in Wi-Fi mode. If several saved phone pairing records exist, use --serial to select one.

Wi-Fi video and input were tested with the USB cable disconnected. The user confirmed good operation. Locked-phone startup and network-loss recovery still need controlled testing.

Update and remove

Keep the checkout for these commands. Obtain the intended source version before updating, then run:

iphone-mirror stop
./install.sh --skip-phone-setup

To remove the application:

./uninstall.sh

Removal stops the mirror and removes only its application, command, service, and launcher files. It keeps UI configuration, phone pairing records, developer images, and unrelated integrations. On Linux, USB pairing files usually remain in /var/lib/lockdown/. Wi-Fi records and downloaded images remain in ~/.pymobiledevice3/ when that directory already exists, and otherwise under ${XDG_DATA_HOME:-$HOME/.local/share}/pymobiledevice3/. The optional Omarchy plugin must be removed separately if you installed it. See phone setup.

Optional Omarchy plugin

Install the application first. Plugin source is in omarchy-plugin/. See its installation instructions.

The plugin provides running/error state and start/focus/stop actions. It adds no key bindings and sends notifications only for errors. Installing the application does not change the bar layout or enable the plugin.

Paste text

With the viewer focused, press Ctrl + V to copy plain text from the computer clipboard to the iPhone clipboard and send the iPhone paste shortcut. Select a text field on the phone first. The phone or target app may require paste permission.

This needs wl-paste from wl-clipboard and the phone's CoreDevice pasteboard service. Text is read only for an explicit paste request, is limited to 1 MiB, and is not logged or saved to files. It is intentionally placed on the phone clipboard. Unicode text and line breaks are preserved. Images and files are not supported. Pending paste work is cancelled when the viewer loses focus; clipboard data already sent to the phone cannot be recalled.

Toolbar appearance without restarting

The toolbar is 68 pixels high, with padded, rounded-square button containers. It sits directly below the displayed phone image and uses the same width. The phone and toolbar stay centered as one group; clicks in the remaining empty space do not activate the buttons. Its height and icon size do not grow with the window. Very small windows reduce them to leave space for the picture.

The toolbar uses the active Omarchy theme's background and foreground colors. The empty area around the phone display uses the same background color as the toolbar. Theme changes update the toolbar automatically, without restarting capture. The palette is read from $XDG_STATE_HOME/omarchy/current/theme/colors.toml (default: ~/.local/state/omarchy/current/theme/colors.toml), with fallback to the Home-based state path if a custom XDG path has no palette. The older XDG and Home-based config paths are also supported. State paths take priority over config paths. Missing or invalid colors use the original dark background and white icons.

Optional file: ~/.config/iphone-mirror/ui.json:

{"button_spacing":64,"icon_size":28}

Run iphone-mirror reload-ui after an edit. Values are bounded and invalid settings use defaults. button_spacing is the requested distance between the icon centers. Small values increase when needed to keep the button containers apart. This changes the toolbar without restarting the phone's media service.

Development

./setup.sh
./run.sh                 # Foreground development; close window or use Ctrl+C to stop
.venv/bin/python -m unittest discover -s tests -v
python3 -m unittest discover -s omarchy-plugin/tests -v

The command client and plugin manage the installed systemd unit, not the foreground development process. The instance lock prevents the two from capturing at the same time. Changes to source files are not deployed until the application is reinstalled.

Tests use mocked device and connection services. The MPV integration test injects synthetic keys into headless MPV. No phone is needed. These tests do not establish real-device compatibility or connection reliability.

Connection and recovery

The application releases input and closes the display connection that started the stream. It then requests media-stream shutdown on a fresh display connection before closing the player, media transport, and tunnel. This request stops all CoreDevice media streams on the phone, not only this application's stream. Do not use another CoreDevice media session at the same time. It cancels only its own tasks. The user service sends the initial stop signal only to the application, so the player is not killed before this cleanup.

The prototype sometimes left the developer display service unresponsive after a restart. Remounting the same developer image restored it in testing. The new shutdown path addresses problems found in the old orchestration, but repeated real-phone restart testing is still required. A failure is reported instead of reconnecting forever. Shutdown uses the corrected stopAll request described in the upstream fix, rather than the pinned library's UUID-based stop method. A connection loss during stop is reported as unconfirmed shutdown, not proof that the phone released its camera. A stopped viewer or an empty media-session list does not by itself prove camera recovery.

Automatic image preparation: On USB or paired Wi-Fi startup, if no developer image is mounted, the viewer attempts one mount of the pinned, locally cached personalized image. An iPhone restart can remove the mounted image; the next launch or Retry can restore it without a USB cable if Wi-Fi pairing and the local network connection are available. It shows Preparing iPhone... while mounting. It copies the cached files to a private temporary directory, checks that the image and trust cache match SHA-384 digests in the pinned-build manifest, and mounts that verified copy. This links the files to the manifest; it is not an independent signature. It does not pair, download an image, or unmount or replace an existing image. Wi-Fi startup uses the saved pairing record. If the display service is available, it uses the same tunnel for mirroring and does not query the image service. If the display service is missing, it checks and prepares the image, then closes the preparation tunnel and opens a fresh connection to discover the display service. A ticket may be requested from Apple if the phone does not already have one. After a phone restart, unlock it and keep its screen on until image preparation finishes. If preparation fails, the viewer identifies the image step and suggests unlocking before Retry. If the mount fails or times out, check its state before using Retry; do not assume it failed. An existing incompatible image still needs an explicit, manual recovery operation.

Known limits

  • Phone calls interrupt mirroring on the tested configuration (USB, ARM64 Omarchy, iPhone 13, iOS 27.0). Video stopped when a call began, even with the phone screen on. The viewer then closed after 15 seconds without media packets. Starting a new session during the call failed. After the call ends, reopen iPhone Mirror; restarting restored live video without a pairing reset or image remount. Other configurations and call types have not been verified.
  • Lower video latency is confirmed by user testing of the prototype, but it has not been measured end to end.
  • International keyboard layouts, IME, multitouch, and horizontal wheel scrolling are not complete. Landscape follow uses SpringBoard orientation when that service is available; if the video buffer itself becomes landscape, the window still follows.
  • An input-service failure disables input but leaves video running. A fresh click attempts reconnection without replaying the click or failed keys.
  • The pinned pymobiledevice3 RTP receiver is reused internally. The application does not start a VNC TCP server. It opens the media transport needed to receive the phone stream; this is not a claim that the application opens no network sockets.
  • The complete locked-phone USB unlock flow still needs controlled validation. Passcodes must remain user-entered and must never be logged.
See development instructions.

License

Original project code is licensed under MIT, copyright 2026 Daniel Lemky. See LICENSE.

The application uses pymobiledevice3, licensed GPL-3.0-or-later. MIT does not remove applicable GPL obligations for a combined distribution. Dependencies keep their own licenses. See third-party notices.

The application uses the library rather than copied upstream source. This is not a claim that the combined application is MIT-only.

Chat with me