Free open-source iOS 12 iPad/iPhone second monitor for Mac. OpenDisplay-compatible client over USB/Lightning & Wi-Fi. Sidecar/Duet alternative for old devices.
22
stars
14
commits
Swift
primary language
Jul 29, 2026
updated
Built and running for real on an iPad Air (Model A1475, iOS 12.5.8), both over a Lightning cable and over Wi-Fi. H.264 video + touch + two-finger scroll + a real mouse cursor all work reliably.
No cable required: the Mac app auto-discovers the iPad over Bonjour on the same Wi-Fi network and connects directly. It prefers USB when a cable is plugged in (lower, steadier latency) and falls back to Wi-Fi automatically otherwise — you don't have to choose a mode, it just works either way.
Use an old iPad on iOS 12 / 12.5.x as a free second display for Mac (Apple Silicon or Intel, modern macOS). Open-source alternative to Sidecar, Duet Display, and Luna Display for devices too old to run OpenDisplay's own client (which needs iPadOS 17+). Works over USB/Lightning and Wi-Fi.
| This project | Sidecar | Duet Display | Luna Display | OpenDisplay (official client) | |
|---|---|---|---|---|---|
| Cost | Free | Free | Free tier + paid | Paid (hardware dongle) | Free |
| Min iOS/iPadOS | 12.0 | 13+ (needs macOS Catalina+ on the Mac side) | 12+ | 12+ | 17+ |
| Open source | iOS client: yes (MIT). Mac app: yes (GPL-3.0, upstream) | No | No | No | Yes (GPL-3.0) |
| Connection | USB/Lightning or Wi-Fi | USB or Wi-Fi | USB or Wi-Fi | USB or Wi-Fi (dongle) | USB or Wi-Fi |
| Needs a companion Mac app you build yourself | Yes | No | No | No | Yes |
Skip the paid apps (Duet Display, Luna Display...). Instead:
CGVirtualDisplay), capturing it (ScreenCaptureKit), hardware-encoding
H.264 (VideoToolbox), and transporting it either over Wi-Fi (discovered
via Bonjour) or by talking directly to macOS's usbmuxd over
Lightning/USB-C — no extra tool like iproxy needed.iOS/ folder in this repo
is a purpose-built iOS 12 client: a clean-room reimplementation of
OpenDisplay's network protocol (read from their public source), so it
can talk to the unmodified Mac app above without touching it.Since the Mac side only needs an unmodified build, the only thing you're actually "writing" yourself is the iPad app — much lighter than reimplementing the whole pipeline from scratch.
ipad12-second-screen/
README.md
LICENSE <- MIT, covers iOS/ (code written for this project)
iOS/ <- iOS 12 client, original code, MIT
LegacyPadDisplay.xcodeproj <- committed and ready to open, no build step
project.yml <- xcodegen config (source of truth if you edit the project;
re-run `xcodegen generate` after changing it)
App/
AppDelegate.swift
ReceiverViewController.swift <- fullscreen video view + touch/scroll capture + cursor sprite
VideoReceiver.swift <- core: TCP listener, frame deframing, H.264 decode, control message send/recv
LaunchScreen.storyboard
Assets.xcassets/
Mac/ <- git submodule, upstream OpenDisplay, UNMODIFIED, GPL-3.0
Mac/ is a git submodule pointing straight at the upstream
peetzweg/opendisplay repo — nothing copied or edited, so it's clear this
is someone else's code (their GPL-3.0 license stays intact), and it's easy
to git submodule update --remote when they cut a new release.
Minimum requirement: iOS 12.0 (iOS/project.yml → deploymentTarget),
tested for real on iOS 12.5.8.
Yes, no code changes needed. TARGETED_DEVICE_FAMILY is already set to
"1,2" (iPhone + iPad), and VideoReceiver.swift already reports
"device": "iPhone" vs "iPad" based on UIDevice.current.userInterfaceIdiom
in its hello message — this mirrors upstream OpenDisplay itself, which
also targets both (Mac/project.yml's local-network usage description
literally says "connects to your iPad or iPhone"). The Mac side sizes the
virtual display from whatever pixel dimensions the device reports in
hello, so it isn't hardcoded to iPad proportions either.
Build/install is the same flow as the iPad steps below — just pick the iPhone as the destination device in Xcode. If it's also stuck on an old iOS version, the same DeviceSupport caveat further down applies. The one real downside: an iPhone screen is a lot smaller, so it's a much less useful second display in practice than an iPad.
git clone --recurse-submodules https://github.com/cuongpham1/ipad-iphone-second-monitor-ios12-free.git
cd ipad-iphone-second-monitor-ios12-free
If you already cloned without --recurse-submodules:
git submodule update --init --recursive
Two options:
OpenDisplay.dmg from
the latest release
— signed and notarized by the author. Open it and drag
OpenDisplay.app into /Applications first — don't run it straight
out of the mounted .dmg. Running it from the disk image works for a
first test, but the volume unmounts on reboot and takes the app with it,
and macOS ties the Screen Recording/Accessibility grants to that specific
app path — moving the app afterward means re-granting both permissions
again.Mac/ in Xcode and run it — see
OpenDisplay's own build instructions
for details.Either way, on first launch macOS will ask for Screen Recording and Accessibility permissions (System Settings → Privacy & Security) — grant both, fully quit the app (Cmd+Q) and relaunch it (flipping the toggle isn't enough; the app needs a restart to actually pick up the new permissions), then run it again.
iOS/ in this repo)open iOS/LegacyPadDisplay.xcodeproj
In Xcode:
The app should show a fullscreen black screen with "Listening on :9000" at the bottom — meaning it's waiting for the Mac to connect.
Recent Xcode versions don't ship iOS DeviceSupport for old iOS 12
builds anymore. You need to add the matching support folder to
~/Library/Developer/Xcode/iOS DeviceSupport/. This repo was actually
brought up using the 12.5 support bundle from
apptim/iPhoneOSDeviceSupport:
curl -L -o /tmp/12.5.zip https://raw.githubusercontent.com/apptim/iPhoneOSDeviceSupport/master/12.5.zip
unzip -o /tmp/12.5.zip -d /tmp/ds125
cp -R "/tmp/ds125/12.5" ~/Library/Developer/Xcode/iOS\ DeviceSupport/
It doesn't need to match the exact build number (e.g. 12.5.8/16H88 worked fine with a plain "12.5" folder) — a close minor version is enough. Other community repos like filsv/iOSDeviceSupport work the same way if you need a different version. Copy the folder into place, fully quit Xcode, reconnect the iPad, and reopen.
This is an Apple limitation, not something this project can fix. After 7 days, plug the cable back in, open Xcode, and hit Run again to reinstall. To avoid repeating this, you'd need a paid Apple Developer Program membership ($99/year) — signs for a full year.
usbmuxd just like a normal OpenDisplay
device (same id/port 9000 as the original client)._opensidecar._tcp over Bonjour and connects
directly to the iPad's port 9000. If the iPad asks for Local Network
permission the first time, allow it — Wi-Fi discovery needs it.ensureListening() reconnects automatically on
foreground return, which briefly flashes black while the Mac redials.updateRequired/incompatible-version message —
a newer OpenDisplay Mac build may have changed the protocol.
VideoReceiver.swift intentionally omits the pv (protocol version)
field, so it's always treated as "protocol 1", which every current
OpenDisplay Mac release documents as backward-compatible.Works: video, touch to click/drag, two-finger scroll, a real mouse
cursor (position + shape, synced over its own control message, smooth),
reconnect on foreground return (the listener/connection can die
silently while backgrounded since no background networking mode is
declared — ensureListening() forces a clean reconnect every time the app
comes back, at the cost of a brief flicker).
Missing: an external keyboard, automatic rotation to match the virtual display, latency measurement.
iOS/ (iOS 12 client): MIT, see LICENSE.Mac/: submodule pointing at peetzweg/opendisplay, GPL-3.0, copyright
held by its original authors — unmodified, not vendored into this repo.Keywords: iPad iOS 12 second monitor Mac, old iPad external display, Sidecar alternative iOS 12, Duet Display free alternative, Luna Display free alternative, OpenDisplay iOS 12 client, Lightning USB second screen, legacy iPad second monitor, LegacyPadDisplay.
14 commits
Swift
100.0%
Free open-source iOS 12 iPad/iPhone second monitor for Mac. OpenDisplay-compatible client over USB/Lightning & Wi-Fi. Sidecar/Duet alternative for old devices.
22
stars
14
commits
Swift
primary language
Jul 29, 2026
updated
Built and running for real on an iPad Air (Model A1475, iOS 12.5.8), both over a Lightning cable and over Wi-Fi. H.264 video + touch + two-finger scroll + a real mouse cursor all work reliably.
No cable required: the Mac app auto-discovers the iPad over Bonjour on the same Wi-Fi network and connects directly. It prefers USB when a cable is plugged in (lower, steadier latency) and falls back to Wi-Fi automatically otherwise — you don't have to choose a mode, it just works either way.
Use an old iPad on iOS 12 / 12.5.x as a free second display for Mac (Apple Silicon or Intel, modern macOS). Open-source alternative to Sidecar, Duet Display, and Luna Display for devices too old to run OpenDisplay's own client (which needs iPadOS 17+). Works over USB/Lightning and Wi-Fi.
| This project | Sidecar | Duet Display | Luna Display | OpenDisplay (official client) | |
|---|---|---|---|---|---|
| Cost | Free | Free | Free tier + paid | Paid (hardware dongle) | Free |
| Min iOS/iPadOS | 12.0 | 13+ (needs macOS Catalina+ on the Mac side) | 12+ | 12+ | 17+ |
| Open source | iOS client: yes (MIT). Mac app: yes (GPL-3.0, upstream) | No | No | No | Yes (GPL-3.0) |
| Connection | USB/Lightning or Wi-Fi | USB or Wi-Fi | USB or Wi-Fi | USB or Wi-Fi (dongle) | USB or Wi-Fi |
| Needs a companion Mac app you build yourself | Yes | No | No | No | Yes |
Skip the paid apps (Duet Display, Luna Display...). Instead:
CGVirtualDisplay), capturing it (ScreenCaptureKit), hardware-encoding
H.264 (VideoToolbox), and transporting it either over Wi-Fi (discovered
via Bonjour) or by talking directly to macOS's usbmuxd over
Lightning/USB-C — no extra tool like iproxy needed.iOS/ folder in this repo
is a purpose-built iOS 12 client: a clean-room reimplementation of
OpenDisplay's network protocol (read from their public source), so it
can talk to the unmodified Mac app above without touching it.Since the Mac side only needs an unmodified build, the only thing you're actually "writing" yourself is the iPad app — much lighter than reimplementing the whole pipeline from scratch.
ipad12-second-screen/
README.md
LICENSE <- MIT, covers iOS/ (code written for this project)
iOS/ <- iOS 12 client, original code, MIT
LegacyPadDisplay.xcodeproj <- committed and ready to open, no build step
project.yml <- xcodegen config (source of truth if you edit the project;
re-run `xcodegen generate` after changing it)
App/
AppDelegate.swift
ReceiverViewController.swift <- fullscreen video view + touch/scroll capture + cursor sprite
VideoReceiver.swift <- core: TCP listener, frame deframing, H.264 decode, control message send/recv
LaunchScreen.storyboard
Assets.xcassets/
Mac/ <- git submodule, upstream OpenDisplay, UNMODIFIED, GPL-3.0
Mac/ is a git submodule pointing straight at the upstream
peetzweg/opendisplay repo — nothing copied or edited, so it's clear this
is someone else's code (their GPL-3.0 license stays intact), and it's easy
to git submodule update --remote when they cut a new release.
Minimum requirement: iOS 12.0 (iOS/project.yml → deploymentTarget),
tested for real on iOS 12.5.8.
Yes, no code changes needed. TARGETED_DEVICE_FAMILY is already set to
"1,2" (iPhone + iPad), and VideoReceiver.swift already reports
"device": "iPhone" vs "iPad" based on UIDevice.current.userInterfaceIdiom
in its hello message — this mirrors upstream OpenDisplay itself, which
also targets both (Mac/project.yml's local-network usage description
literally says "connects to your iPad or iPhone"). The Mac side sizes the
virtual display from whatever pixel dimensions the device reports in
hello, so it isn't hardcoded to iPad proportions either.
Build/install is the same flow as the iPad steps below — just pick the iPhone as the destination device in Xcode. If it's also stuck on an old iOS version, the same DeviceSupport caveat further down applies. The one real downside: an iPhone screen is a lot smaller, so it's a much less useful second display in practice than an iPad.
git clone --recurse-submodules https://github.com/cuongpham1/ipad-iphone-second-monitor-ios12-free.git
cd ipad-iphone-second-monitor-ios12-free
If you already cloned without --recurse-submodules:
git submodule update --init --recursive
Two options:
OpenDisplay.dmg from
the latest release
— signed and notarized by the author. Open it and drag
OpenDisplay.app into /Applications first — don't run it straight
out of the mounted .dmg. Running it from the disk image works for a
first test, but the volume unmounts on reboot and takes the app with it,
and macOS ties the Screen Recording/Accessibility grants to that specific
app path — moving the app afterward means re-granting both permissions
again.Mac/ in Xcode and run it — see
OpenDisplay's own build instructions
for details.Either way, on first launch macOS will ask for Screen Recording and Accessibility permissions (System Settings → Privacy & Security) — grant both, fully quit the app (Cmd+Q) and relaunch it (flipping the toggle isn't enough; the app needs a restart to actually pick up the new permissions), then run it again.
iOS/ in this repo)open iOS/LegacyPadDisplay.xcodeproj
In Xcode:
The app should show a fullscreen black screen with "Listening on :9000" at the bottom — meaning it's waiting for the Mac to connect.
Recent Xcode versions don't ship iOS DeviceSupport for old iOS 12
builds anymore. You need to add the matching support folder to
~/Library/Developer/Xcode/iOS DeviceSupport/. This repo was actually
brought up using the 12.5 support bundle from
apptim/iPhoneOSDeviceSupport:
curl -L -o /tmp/12.5.zip https://raw.githubusercontent.com/apptim/iPhoneOSDeviceSupport/master/12.5.zip
unzip -o /tmp/12.5.zip -d /tmp/ds125
cp -R "/tmp/ds125/12.5" ~/Library/Developer/Xcode/iOS\ DeviceSupport/
It doesn't need to match the exact build number (e.g. 12.5.8/16H88 worked fine with a plain "12.5" folder) — a close minor version is enough. Other community repos like filsv/iOSDeviceSupport work the same way if you need a different version. Copy the folder into place, fully quit Xcode, reconnect the iPad, and reopen.
This is an Apple limitation, not something this project can fix. After 7 days, plug the cable back in, open Xcode, and hit Run again to reinstall. To avoid repeating this, you'd need a paid Apple Developer Program membership ($99/year) — signs for a full year.
usbmuxd just like a normal OpenDisplay
device (same id/port 9000 as the original client)._opensidecar._tcp over Bonjour and connects
directly to the iPad's port 9000. If the iPad asks for Local Network
permission the first time, allow it — Wi-Fi discovery needs it.ensureListening() reconnects automatically on
foreground return, which briefly flashes black while the Mac redials.updateRequired/incompatible-version message —
a newer OpenDisplay Mac build may have changed the protocol.
VideoReceiver.swift intentionally omits the pv (protocol version)
field, so it's always treated as "protocol 1", which every current
OpenDisplay Mac release documents as backward-compatible.Works: video, touch to click/drag, two-finger scroll, a real mouse
cursor (position + shape, synced over its own control message, smooth),
reconnect on foreground return (the listener/connection can die
silently while backgrounded since no background networking mode is
declared — ensureListening() forces a clean reconnect every time the app
comes back, at the cost of a brief flicker).
Missing: an external keyboard, automatic rotation to match the virtual display, latency measurement.
iOS/ (iOS 12 client): MIT, see LICENSE.Mac/: submodule pointing at peetzweg/opendisplay, GPL-3.0, copyright
held by its original authors — unmodified, not vendored into this repo.Keywords: iPad iOS 12 second monitor Mac, old iPad external display, Sidecar alternative iOS 12, Duet Display free alternative, Luna Display free alternative, OpenDisplay iOS 12 client, Lightning USB second screen, legacy iPad second monitor, LegacyPadDisplay.
14 commits
Swift
100.0%