The Problem
Streaming a macOS screen to another Apple device normally means either Apple's own AirPlay (which is locked down and not embeddable) or a third-party service that routes traffic through a relay server. MirageKit solves the embeddable, peer-to-peer problem: a macOS host captures windows or a virtual display, encodes HEVC, and pushes frames over UDP directly to a client on iPadOS, visionOS, or macOS — no server in the path.
What This Does
MirageKit is a Swift Package that ships both sides of the stream. The host side (Sources/MirageKit/Public/Host/) captures via ScreenCaptureKit, encodes with HEVCEncoder.swift, and advertises over Bonjour (BonjourAdvertiser.swift). The client side (Sources/MirageKit/Public/Client/) discovers hosts, decodes with HEVCDecoder.swift, and renders through MetalRenderer.swift inside SwiftUI views (MirageStreamView.swift, MirageStreamContentView.swift).
Input is forwarded back: MirageHostInputController.swift and the Public/Input/ types (MirageMouseEvent.swift, MirageKeyEvent.swift) carry mouse, keyboard, and gesture events from client to host. The framework also handles virtual displays (CGVirtualDisplayBridge.swift), remote unlock (UnlockManager.swift), and menu-bar passthrough (MenuBarMonitor.swift).
How It Is Wired
The static import graph resolved 0 internal modules and 0 import edges across 76 files, so the module wiring has not been mapped for this repository yet — the flow below is traced from the README and file structure, not from a resolved call graph.
Execution starts at MirageHostService.start() (Sources/MirageKit/Public/Host/MirageHostService.swift). From there the host advertises _mirage._tcp via Bonjour, accepts TCP control connections, and streams video over UDP with clients registering stream IDs. The client entry point is MirageClientService.connect(to:) (Sources/MirageKit/Public/Client/MirageClientService.swift). The network boundary is crossed in HybridTransport.swift and ConnectionManager.swift (TCP control + UDP video); the capture boundary in WindowCaptureEngine.swift (ScreenCaptureKit); the display boundary in CGVirtualDisplayBridge.swift (virtual display creation).
MirageHostService.swift is the widest blast radius — it is split across nine + extension files (+AppStreaming, +DesktopStreaming, +Input, +VirtualDisplay, etc.), and StreamContext.swift at 1,299 lines carries the session state everything else touches. A change in either ripples across most of the host.
How To Use It
Add it as an SPM dependency, per the README:
.package(url: "https://github.com/EthanLipnik/MirageKit.git", from: "0.0.1"),
Requirements from the README: macOS 26+ for host streaming, iOS 26+ / visionOS 26+ for clients, Swift 6.2+. Host setup is try await hostService.start() with a MirageHostDelegate; client setup is try await clientService.connect(to: host) then requestWindowList(). No environment variables or config files are documented — configuration lives in Swift code (MirageStreamingSettings.swift, MirageAppPreferences.swift). The README notes this is active development with possible breaking changes.
Real-World Use
A Mac app that shares a specific window to an iPad: the host creates a MirageHostService, the client discovers it via Bonjour, requests the window list, and renders the decoded CVPixelBuffer in a MirageStreamView. Input events from the client's SwiftUI view route back through MirageHostInputController to the captured app.
Code Health & Issues
From static analysis (not opinion), 66 findings across 76 Swift files: 11 high, 50 medium, 5 low.
- High — Deep nesting (25 occurrences): max indentation depth 8 in
WindowCaptureEngine.swift,StreamContext.swift,MirageClientService.swift. Fix with early returns/guard clauses. - High — Duplicated code: 35 repeated 6-line blocks across 15 files, including
StreamController.swiftandMirageStreamContentView.swift. Extract shared helpers. - High — Oversized files (7):
StreamContext.swiftat 1,299 lines;MirageClientService.swiftandMirageStreamView.swiftalso flagged. Split by responsibility. - Medium — High branching density (27):
CursorMonitor.swifthas 29 branch points over 66 lines. Consider strategy dispatch. - High — No CI: 76 source files, no workflow builds or tests anything. Add a build+test workflow on push and pull_request.
- Medium — Tests: 1 test file against 76 source files (ratio 0.013). The suite cannot cover this codebase; add tests to the highest fan-in modules first.
The Bottom Line
The architecture is sound — clean separation of capture, encode, transport, and render, with a sensible public API. The risk is engineering discipline: no CI, nearly no tests, and several files too large to hold in one head. Use it if you need embeddable Apple-to-Apple P2P streaming and can absorb the maintenance cost of a pre-1.0 framework.