{"id":51538865,"url":"https://github.com/peetzweg/opendisplay","last_synced_at":"2026-07-09T11:30:44.463Z","repository":{"id":363782699,"uuid":"1264883757","full_name":"peetzweg/opendisplay","owner":"peetzweg","description":"Free, open-source Sidecar/Duet alternative — use your iPhone or iPad as a true second monitor for your Mac over USB or WiFi. Low latency H.264, Retina HiDPI, touch input.","archived":false,"fork":false,"pushed_at":"2026-07-07T15:39:43.000Z","size":10616,"stargazers_count":231,"open_issues_count":43,"forks_count":21,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-07T16:16:13.552Z","etag":null,"topics":["duet-display","external-display","ipad","iphone","macos","screen-extension","screencapturekit","second-monitor","sidecar","swift","videotoolbox","virtual-display"],"latest_commit_sha":null,"homepage":"https://opendisplay.app","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/peetzweg.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-10T09:08:00.000Z","updated_at":"2026-07-07T16:08:36.000Z","dependencies_parsed_at":"2026-06-29T22:02:38.334Z","dependency_job_id":null,"html_url":"https://github.com/peetzweg/opendisplay","commit_stats":null,"previous_names":["peetzweg/opensidecar","peetzweg/opendisplay"],"tags_count":27,"template":false,"template_full_name":null,"purl":"pkg:github/peetzweg/opendisplay","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peetzweg%2Fopendisplay","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peetzweg%2Fopendisplay/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peetzweg%2Fopendisplay/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peetzweg%2Fopendisplay/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/peetzweg","download_url":"https://codeload.github.com/peetzweg/opendisplay/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peetzweg%2Fopendisplay/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35298222,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-09T02:00:07.329Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["duet-display","external-display","ipad","iphone","macos","screen-extension","screencapturekit","second-monitor","sidecar","swift","videotoolbox","virtual-display"],"created_at":"2026-07-09T11:30:43.799Z","updated_at":"2026-07-09T11:30:44.456Z","avatar_url":"https://github.com/peetzweg.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"public/logo.png\" width=\"128\" alt=\"OpenDisplay app icon\" /\u003e\n\n# OpenDisplay\n\n**Turn your spare Apple devices into second monitors for your Mac — free, open source, no subscription.**\n\niPhone and iPad today, spare MacBooks on the roadmap. A self-hosted\nalternative to Apple Sidecar, Duet Display, and Luna Display: true extended\ndisplay (not just mirroring), Retina-sharp, over USB or WiFi, with touch and\nscroll input.\n\n[Website](https://peetzweg.github.io/opendisplay/) · [Quick start](#quick-start) · [How it works](#how-it-works) · [FAQ](#faq) · [Contributing](#contributing)\n\n\u003c/div\u003e\n\n---\n\n## Why OpenDisplay exists\n\nTurning an iPhone or iPad into an external display for a Mac is a solved\nproblem — but every existing option has a catch:\n\n- **Apple Sidecar** is free but requires both devices on the *same Apple ID*,\n  doesn't support iPhones at all, and only works on supported hardware pairs.\n- **Duet Display** moved to a subscription.\n- **Luna Display** requires a hardware dongle.\n\nOpenDisplay is the missing option: a **free, open-source, no-account,\nno-dongle** way to use the iOS device you already own as a true second\ndisplay. If you were about to write your own — don't! Contribute here\ninstead; the hard parts (virtual display creation, low-latency H.264\npipeline, USB transport, input injection) are already working.\n\n## Features\n\n- 🖥️ **True display extension** — macOS treats the device as a real second\n  monitor (drag windows to it, arrange it in System Settings), not a mirror.\n  Mirroring is also available as a mode.\n- 🔌 **USB-wired for lowest latency** — streams over the Lightning/USB-C\n  cable via macOS's built-in `usbmuxd`; plug in and go, no network, no\n  WiFi jitter, no helper tools.\n- 📶 **WiFi with zero config** — the iPhone advertises itself via Bonjour;\n  pick it from a dropdown on the Mac.\n- 🔍 **Retina / HiDPI** — the virtual display matches the device panel\n  pixel-for-pixel (@2x), so text is sharp.\n- 👆 **Touch input built in** — your iPhone becomes a touchscreen for macOS:\n  **tap to click**, **drag to drag**, and **two-finger scroll** that feels\n  like a trackpad. (Apple Pencil support is on the roadmap.)\n- 🔄 **Portrait or landscape** — rotate the device and the virtual display\n  rebuilds itself as a vertical monitor at native resolution.\n- ⚡ **Low-latency pipeline** — hardware H.264 encode (VideoToolbox,\n  real-time mode, no B-frames), TCP_NODELAY, frame-drop backpressure with\n  keyframe recovery, decode-and-render via `AVSampleBufferDisplayLayer`.\n- 🔒 **Self-hosted \u0026 private** — your screen never touches anyone's server.\n  Two small apps, one TCP connection, that's it.\n\n## How it works\n\n```\nMAC (sender)                                      iPHONE / iPAD (receiver)\nCGVirtualDisplay  ← macOS believes a monitor is attached\n   → ScreenCaptureKit (capture the virtual display)\n   → VideoToolbox H.264 (hardware, real-time)\n   → TCP  [4-byte length][Annex B frame]  ═══════→  NWListener :9000\n                                                      → AVSampleBufferDisplayLayer\n   ← JSON control messages (hello, touch, scroll) ═══\n   → CGEvent injection (click / drag / scroll)\n```\n\nThe **phone listens and the Mac connects** — that ordering is what makes the\nexact same code work over USB (via the `usbmuxd` daemon built into every\nmacOS install) and WiFi. The phone\nannounces its native panel size; the Mac creates a `CGVirtualDisplay` at\nexactly half that in points (@2x HiDPI) and streams the pixels back.\n\n`CGVirtualDisplay` is a **private CoreGraphics API** (the same one used by\nBetterDisplay and DeskPad) — which is precisely why this project can't ship\non the App Store and lives on GitHub instead.\n\n## Install\n\nYou need **two apps**: a Mac app (captures and sends) and an iOS app\n(receives and displays).\n\n### Prebuilt downloads (Mac)\n\nGrab `OpenDisplay.dmg` from the\n[latest release](https://github.com/peetzweg/opendisplay/releases/latest).\nThe app is signed with a Developer ID certificate and notarized by Apple, so it\nopens with a plain double-click on macOS 14+ — no Gatekeeper warning. Open the\n`.dmg` and drag the app to Applications.\n\n### iPhone app\n\n- **TestFlight** (recommended): join the public beta at\n  [testflight.apple.com/join/3NYaY11c](https://testflight.apple.com/join/3NYaY11c).\n- **Build from source**: open the project in Xcode, select your free Apple ID\n  under Signing, hit Run. Takes ~2 minutes.\n\n## Quick start (from source)\n\n### Prerequisites\n\n```sh\nbrew install xcodegen   # project generation\n```\n\nXcode 15+ and a free or paid Apple developer account (to sideload the iOS\napp onto your device).\n\n### Build\n\n```sh\ngit clone https://github.com/peetzweg/opendisplay.git\ncd opendisplay\necho \"DEVELOPMENT_TEAM=YOURTEAMID\" \u003e .env   # your Apple team ID, for signing\n./generate.sh                               # runs xcodegen with your .env\nxcodebuild -project OpenSidecar.xcodeproj -scheme OpenSidecarMac \\\n  -configuration Debug -derivedDataPath build build\nxcodebuild -project OpenSidecar.xcodeproj -scheme OpenSidecariOS \\\n  -configuration Debug -destination 'generic/platform=iOS' \\\n  -derivedDataPath build -allowProvisioningUpdates build\n```\n\n(Or open `OpenSidecar.xcodeproj` in Xcode and hit Run on each target. Your\nteam ID is shown at [developer.apple.com/account](https://developer.apple.com/account)\nunder Membership, or just pick your team in Xcode's Signing pane.)\n\n### Run (USB — recommended)\n\n1. Install + open **OpenDisplay** on the iPhone (it listens on port 9000).\n2. On the Mac, run `./run.sh` (or just open the app) — it talks to macOS's\n   built-in `usbmuxd` directly and auto-connects over the cable. No tunnel\n   tools needed.\n3. Grant **Screen Recording** (for capture) and **Accessibility** (for touch)\n   when macOS asks — one time each.\n4. Drag a window onto your new display. Done.\n\n### Run (WiFi)\n\nOpen the iPhone app, then pick **\"iPhone (WiFi)\"** from the Connection menu\nin the Mac app. Discovery is automatic via Bonjour. USB has lower latency;\nWiFi has no cable.\n\n### Permissions checklist\n\nmacOS and iOS gate several things this app needs — most prompt on first use,\nbut some **fail silently** if denied or missed. The Mac app shows a live\npermission status panel; the iPhone app has a settings screen (shake the\nphone, or tap Settings \u0026 Help when idle).\n\n| Where | Permission | Needed for | If missing |\n|---|---|---|---|\n| Mac | Screen Recording | capturing the display | black screen on the phone |\n| Mac | Accessibility | touch/scroll input | taps do nothing |\n| Mac | **Local Network** | WiFi discovery | no device in the Connection menu |\n| iPhone | **Local Network** | WiFi discovery | Mac can't find the phone |\n\nAll live under **Privacy \u0026 Security** in System Settings (Mac) / Settings\n(iPhone). The Local Network ones are only needed for WiFi mode — USB works\nwithout them. If the prompt never appeared, toggle the entry manually or\nforce-quit and reopen the app.\n\n## FAQ\n\n**Why do I see the purple screen-recording indicator in the menu bar?**\nThat's a macOS privacy indicator shown for *any* app that captures the\nscreen — Duet, Luna, OBS, and Zoom trigger it too. Apple Sidecar doesn't,\nonly because it's implemented inside the OS rather than on public capture\nAPIs. It cannot (and shouldn't) be hidden by an app; it's how macOS tells\nyou a capture is running.\n\n**The Mac app doesn't show my iPhone in the Connection menu (WiFi).**\nBoth sides need **Local Network** permission, and both fail *silently*\nwithout it: check Privacy \u0026 Security → Local Network on the Mac **and** on\nthe iPhone, make sure both are on the same WiFi network, and keep the\niPhone app open in the foreground. USB mode is unaffected.\n\n**Does it support iPad?** The receiver app is universal (iPhone + iPad);\niPad is the same codebase. iPad-specific polish (Pencil, pressure) is on the\nroadmap.\n\n**Why H.264 and not HEVC/AV1?** Hardware H.264 encode/decode is universally\nfast and the latency is excellent. HEVC is a planned option for better\nquality-per-bit.\n\n**Is my screen content sent anywhere?** No. One direct TCP connection\nbetween your Mac and your device, over your cable or your LAN. No servers,\nno accounts, no analytics. Full details — including what the apps store\nlocally and the current WiFi-encryption caveat — on the\n[privacy page](https://peetzweg.github.io/opendisplay/privacy.html).\n\n**What's the license? Can I fork it or use it commercially?**\n[GPL-3.0](LICENSE). Use, study, and adapt it freely — commercially too. If\nyou distribute a modified version it must stay open source under the same\nlicense with the original attribution intact, so improvements flow back\ninstead of into closed forks. (Releases up to v0.4.x were MIT-licensed and\nremain available under those terms.)\n\n**Will it break on a macOS update?** Possibly — `CGVirtualDisplay` is\nprivate API. The same risk applies to every virtual-display product.\nThe capture/streaming pipeline itself uses only public APIs.\n\n**Audio?** Out of scope for now.\n\n## Comparison\n\n| | OpenDisplay | Apple Sidecar | Duet Display | Luna Display |\n|---|---|---|---|---|\n| Price | **Free, open source** | Free | Subscription | $$$ + dongle |\n| iPhone as display | ✅ | ❌ (iPad only) | ✅ | ✅ |\n| Different Apple IDs | ✅ | ❌ | ✅ | ✅ |\n| Wired (USB) | ✅ | ✅ | ✅ | ❌ |\n| True extension | ✅ | ✅ | ✅ | ✅ |\n| Touch input | ✅ | ✅ | ✅ | ✅ |\n| Self-hosted / auditable | ✅ | — | ❌ | ❌ |\n\n## Roadmap\n\nTracked as [roadmap issues](https://github.com/peetzweg/opendisplay/issues?q=is%3Aissue+is%3Aopen+label%3Aroadmap) — pick one up if you'd like to contribute!\n\n**Connectivity \u0026 distribution**\n- [#16](https://github.com/peetzweg/opendisplay/issues/16) Encrypted WiFi transport with pairing code\n- [ ] App Store release of the iOS app + notarized Mac downloads\n\n**Input**\n- [#4](https://github.com/peetzweg/opendisplay/issues/4) Apple Pencil with pressure and tilt\n- [#5](https://github.com/peetzweg/opendisplay/issues/5) Right-click and multi-touch gestures\n- [#6](https://github.com/peetzweg/opendisplay/issues/6) Hardware keyboard passthrough\n- [#7](https://github.com/peetzweg/opendisplay/issues/7) On-screen modifier key sidebar\n\n**Display \u0026 media**\n- [#9](https://github.com/peetzweg/opendisplay/issues/9) Resolution \u0026 quality settings\n- [#10](https://github.com/peetzweg/opendisplay/issues/10) HEVC encoding\n- [#12](https://github.com/peetzweg/opendisplay/issues/12) Audio forwarding\n- [#17](https://github.com/peetzweg/opendisplay/issues/17) macOS receiver — use another Mac as a display\n\n**Experience**\n- [#11](https://github.com/peetzweg/opendisplay/issues/11) Menu bar app mode with auto-connect\n- [#13](https://github.com/peetzweg/opendisplay/issues/13) Battery \u0026 lifecycle awareness\n\n**Exploratory**\n- [#14](https://github.com/peetzweg/opendisplay/issues/14) Remote access beyond the local network\n- [#15](https://github.com/peetzweg/opendisplay/issues/15) Additional client platforms\n\nDone: prebuilt releases, built-in USB connectivity (no helper tools), WiFi via Bonjour, portrait mode, touch + two-finger scroll, performance overlay, iPad support, multiple devices at once ([#8](https://github.com/peetzweg/opendisplay/issues/8) — every connected device becomes its own extended display).\n\n## Auto-update (macOS app)\n\nThe macOS app updates itself with [Sparkle](https://sparkle-project.org) —\nan open-source framework, **not** a hosted service. Update checks hit only\nour own infrastructure:\n\n- The app reads an **appcast** feed hosted on the landing-page site:\n  `https://opendisplay.app/appcast.xml` (`SUFeedURL` in `project.yml`).\n- The release workflow (`.github/workflows/release.yml`, `build-mac` job)\n  runs Sparkle's `generate_appcast` against the notarized `OpenDisplay.dmg`,\n  signs it with the EdDSA key, commits the result to `public/appcast.xml`,\n  and dispatches the Pages deploy — so the published feed points download\n  links at the GitHub Release assets.\n- Sparkle verifies both the EdDSA signature and Apple's notarization before\n  installing. The app checks automatically in the background\n  (`SUEnableAutomaticChecks`) and offers a manual **\"Check for Updates…\"**\n  button next to **Quit** in the menu-bar window.\n\n### Maintainer prerequisites (before auto-update goes live)\n\nAuto-update is **scaffolded but inert** until the signing keys are in place.\nThe private signing key is **never** committed — it lives only as a CI\nsecret. To switch it on:\n\n1. **Generate the key pair.** Run Sparkle's `generate_keys` once (it ships\n   in the Sparkle SPM artifact bundle and in the release tarball at\n   `bin/generate_keys`). It prints a **public** key and stores the\n   **private** key in your login keychain.\n2. **Public key →** paste it into `SUPublicEDKey` in `project.yml` (replace\n   the `REPLACE_WITH_SUPUBLICEDKEY_FROM_generate_keys` placeholder), then\n   re-run `xcodegen generate` and commit.\n3. **Private key →** add it as the `SPARKLE_PRIVATE_KEY` GitHub Actions\n   secret (export it with `generate_keys -x private_key.pem` if needed). The\n   appcast step in `release.yml` no-ops gracefully while this secret is\n   absent, so releases keep working until you're ready.\n4. The **first appcast publishes on the next release** after both keys are\n   set. Confirm `https://opendisplay.app/appcast.xml` resolves, then **test\n   the full update flow on a real signed/notarized build** (check → download\n   → verify → relaunch) — this can't be validated in CI.\n\n## Contributing\n\nIssues and PRs are very welcome — especially for the roadmap items above.\nThe codebase is intentionally small: ~4 Swift files per platform, with\n[Sparkle](https://sparkle-project.org) (SPM) as the macOS app's only\nruntime dependency, for auto-update. The [How it works](#how-it-works)\nsection above is the architecture doc; see `Mac/CGVirtualDisplayPrivate.h`\nfor the private API surface.\n\nReleases are automated with\n[release-please](https://github.com/googleapis/release-please): use\n[Conventional Commits](https://www.conventionalcommits.org) (`feat:`,\n`fix:`, `docs:`, …) and a release PR with a generated changelog appears\nautomatically — merging it tags the release and attaches prebuilt\nartifacts.\n\n## License\n\n[GPL-3.0](LICENSE) — Copyright (c) 2026 Philip Poloczek.\n\nFree to use, study, and adapt. If you distribute a modified version it\nmust remain open source under the same license, with the original\nattribution intact — improvements flow back to everyone instead of into\nclosed forks. (Versions up to v0.4.x were MIT-licensed; those releases\nremain available under MIT.)\n\n---\n\n*Keywords: iPhone second monitor Mac, iPad external display, free Sidecar\nalternative, Duet Display alternative, open source screen extension macOS,\nuse iPhone as extra screen, virtual display Mac, USB second display.*\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeetzweg%2Fopendisplay","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpeetzweg%2Fopendisplay","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeetzweg%2Fopendisplay/lists"}