Skip to content

Repository files navigation

iphone_mirror_wsl

License: GPL-3.0 Platform: WSL2 ShellCheck Pre-1.0

Headless AirPlay receiver pipeline for WSL2 + NVIDIA. iPhone screen mirrors into OBS as a webcam-style source with no window on the Windows desktop, so it can't leak into a screen-share or alt-tab preview during a livestream or call.

Built on UxPlay (AirPlay 2 receiver), GStreamer (relay), and OBS Studio (consumer / virtual camera).

Pipeline

iPhone ─AirPlay─▶ UxPlay ─vdmp tap─▶ FIFO ─GStreamer─▶ TCP MPEG-TS ─▶ OBS Media Source ─▶ Virtual Camera ─▶ Zoom/Meet/Teams
                  (fakesink discards
                   decoded frames -
                   no window opens)

Hardware H264 decode in OBS via NVDEC. UxPlay still negotiates a decoder for AirPlay handshake reasons but its output goes nowhere. The raw H264 stream from the iPhone is intercepted before any decode and forwarded to OBS, which does the only meaningful decode in the chain.

A second branch of the GStreamer pipeline decodes the same stream locally and pushes RGBA frames to a WSLg-projected ximagesink window — useful for verifying frame flow without OBS in the loop.

A third branch produces a 10 fps JPEG ring at /tmp/phone-NNNNN.jpg (max 8 files, rotated). Downstream consumers like the agent loop (see agent/) and the tap-tester (agent/tap-tester/) read from there.

Prerequisites

  • Windows 11 with WSL2 (Ubuntu 22.04 or 24.04)

  • NVIDIA GPU + recent driver (RTX 30xx / 40xx / 50xx tested)

  • iPhone on the same Wi-Fi as the host

  • WSL2 mirrored networking. Add to %USERPROFILE%\.wslconfig:

    [wsl2]
    networkingMode=mirrored
    
    [experimental]
    hostAddressLoopback=true

    Then wsl --shutdown from PowerShell.

Install

git clone https://github.com/ellyseum/iphone_mirror_wsl ~/projects/iphone_mirror_wsl
cd ~/projects/iphone_mirror_wsl
./install.sh

install.sh installs apt deps (uxplay, avahi-daemon, gstreamer1.0-{tools,plugins-base,plugins-good,plugins-bad,vaapi}), checks for mirrored networking + an NVIDIA driver, and symlinks ./mirror into ~/.local/bin. Make sure ~/.local/bin is on $PATH.

Usage

mirror start            # bring up UxPlay + relay
mirror status           # check pids and OBS connection info
mirror logs -f          # follow logs (UxPlay's FPS data is here)
mirror test             # open a tester window via WSLg to verify
mirror sdp              # print the SDP path (UDP mode)
mirror stop             # tear it all down
mirror restart          # if iPhone reconnect gets stuck

On the iPhone: Control Center → Screen Mirroring → WSL-Mirror (rename via RECEIVER_NAME in config.env).

OBS setup

  1. Sources → +Media Source
  2. Uncheck "Local File"
  3. Input: tcp://127.0.0.1:5004
  4. Input Format: mpegts
  5. Reconnect Delay: 1
  6. Tick "Restart playback when source becomes active"
  7. OK

Then Start Virtual Camera in OBS → Zoom/Teams/Meet pick "OBS Virtual Camera" in the camera dropdown.

If mirror status reports UDP mode instead, point the Media Source at the SDP file printed by mirror sdp (Local File checkbox on, Input Format mpegts).

Configuration

Edit config.env, then mirror restart:

Variable Default Notes
RECEIVER_NAME WSL-Mirror Name iPhone shows in Screen Mirroring
DISPLAY_RES 3840x2160@60 Higher = iPhone uses higher bitrate
FPS 60 Cap
DECODER nvh264dec avdec_h264 for software fallback
TRANSPORT tcp tcp (reliable) or udp (low-latency, lossy)
RELAY_PORT 5004
RELAY_MCAST 224.1.1.1 UDP mode only — multicast group, or set to a unicast consumer IP

Persistent service

A user-scoped systemd unit ships at systemd/iphone-mirror.service. Install it once:

mkdir -p ~/.config/systemd/user
cp systemd/iphone-mirror.service ~/.config/systemd/user/
systemctl --user enable --now iphone-mirror.service

WSL2 needs systemd = true under [boot] in /etc/wsl.conf for this to work.

Subprojects

  • agent/ — closed-loop agent that reads the mirror's JPEG ring, asks a vision model for the next action, and types/taps back via a Bluetooth HID emulator. Independent of the relay path.
  • agent/tap-tester/ — browser-side tap calibration harness; the iPhone loads a page, the agent issues programmatic taps, the page reports back where the touch landed, and the server logs it.
  • agent/training/ — data-collection pipeline for training a cursor-finder CNN on synthetic samples. Active model training lives in ios_pointer_finder.
  • tester/ — WSLg stream tester + a Python live-overlay that runs the cursor model on every JPEG-ring frame.
  • extras/qvh/ — vendored quicktime_video_hack for USB-side iPhone screen capture. Alternative to AirPlay; not the active path.

Roadmap

  • See CHANGELOG.md for shipped milestones.
  • A separate Electron app (cursor_finder_live) consumes this pipeline and runs the cursor-finder model live in the renderer with overlay UI. That replaces the older iphone_mirror_vcam C# attempt.

Quirks

  • Stale pixels on static UI: H264 inter-frame compression means a bad reference frame can persist on unchanging screen regions until the next IDR keyframe (~2s on iPhone). Real-world content with motion repaints itself; UI screenshots don't. Use TCP transport (default) to avoid the source of corruption.
  • iPhone disconnects after lock: AirPlay sessions die when the phone screen locks. mirror restart and reconnect.
  • First connect after mirror start may take 5–10s: GStreamer's TCP server has to receive enough buffer before OBS sees frames.
  • Mirrored networking is required: default WSL2 NAT mode hides WSL behind a private subnet that mDNS can't bridge cleanly.

License

GPL-3.0. Pulls in UxPlay (GPL-3) at runtime, so the same terms apply downstream. Vendored upstreams keep their own licenses (extras/qvh/LICENSE is MIT). See LICENSE for the full text.

About

Headless AirPlay receiver pipeline for WSL2 + NVIDIA. UxPlay + GStreamer relay so the iPhone screen mirrors into OBS as a webcam without a window on the Windows desktop.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages