Skip to content

iPad app v0.1: native viewports for iPad + external 4K (ADR 0008) - #9

Open
rvegajr wants to merge 1 commit into
arch-v2from
ipad-app
Open

rvegajr wants to merge 1 commit into
arch-v2from
ipad-app

Conversation

@rvegajr

@rvegajr rvegajr commented Oct 4, 2026

Copy link
Copy Markdown
Member

Stacked on #8 (architecture), which is stacked on #7 (live-check). Merge them in order; GitHub retargets each to main as the one below it lands.

What you get

  • One window per display. Each iPad window shows one Mac display. Put one window on the iPad screen and one on the external 4K (Stage Manager): they show two different displays at once, independently. A new window picks a display no other window shows.
  • Switching. Ctrl+Option+1…9 picks a display, Ctrl+Option+[ / ] steps through them, and Ctrl+Option+0 or a three-finger tap opens the thumbnail picker. There is also a pill at the top.
  • Input. Mac-correct keys: Cmd stays Cmd (HID codes, host-side key repeat). Trackpad click, right click, drag, and smooth scrolling with phases and momentum; the Mac cursor is drawn on the iPad at your pointer. Touch: tap, long press, drag, two-finger scroll.
  • Pairing. just host-pair <ip> puts a one-time mrdpd://pair link on the Mac's clipboard and shows a QR code. Paste it on the iPad (Universal Clipboard) or scan it; the app confirms before storing.

Setup and limits: docs/ipad.md.

How it's built

Piece What it is
Packages/ViewportKit Shared protocol, framing, geometry, pairing, shortcuts. TLS 1.2 ECDHE-PSK (ChaCha20-Poly1305) transport via Network.framework. Client session with H.264 sample building and momentum.
Sources/HostKit + mrdpd-host Display registry. ScreenCaptureKit at each window's size (NV12, zero copy). VideoToolbox H.264 with the spike-R15 settings. Encode scheduler: the window you use gets 60 fps; extra windows beyond the encoder count drop to 30. CGEvent injection. Cursor monitor, thumbnails, pairing store. The server reloads keys live.
apps/ipad SwiftUI/UIKit (iOS 17+), generated with XcodeGen.

Deviations from ADR 0008, recorded under "As built":

  • TCP + TLS-PSK, one connection per window, instead of QUIC.
  • A random 32-byte key per iPad instead of a 6-digit code. A short code used as the key could be guessed offline.

Tests

  • just test passes:
    • Swift: 80 tests, 6 skipped, 0 failures.
    • ViewportKit: 39 tests.
    • Rust: all green.
    • New loopback end-to-end test with real TLS and real H.264 decoded back to the right colour. It covers display switching, click mapping, thumbnails, resize, two windows on different displays at once, an unpaired key being refused, and a device paired while the host runs.
  • just ipad-sim-check passes. The real app runs in an iPad Pro 13" simulator against a real host (--no-inject --log-input, so nothing is posted to the Mac):
    • a tap arrives as a left click and a long press as a right click;
    • a drag moves the pointer;
    • HID keys arrive with a real Cmd;
    • Ctrl+Option+1 and +2 switch displays, and the switch keys never reach the Mac.
  • Manual simulator run: the app streamed the Mac's live displays.
  • The app also compiles for a real iPad (arm64).

Not done yet

  • Device testing: install with just ipad-device; Xcode creates the NoctuSoft dev cert on first install.
  • On-screen keyboard.
  • An in-app clipboard channel (Universal Clipboard works).
  • Audio.
  • Tiled full-4K mode.
  • A signed menu-bar host.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XASAafkzuqCqwowAZTD1re

Each iPad window is one viewport onto one Mac display; one window on the
iPad screen and one on the external 4K show two displays at once, each
switchable (Ctrl+Option+1..9, [ ], 0 picker, HUD, three-finger tap).

- Packages/ViewportKit: wire format, control/input/video/cursor/thumbnail
  messages, geometry, pairing links, switch shortcuts (ViewportProtocol);
  TLS 1.2 ECDHE-PSK framed transport (ViewportTransport); client session,
  H.264 sample builder, momentum scrolling (ViewportClient). 39 tests.
- HostKit + mrdpd-host: display registry, SCK capture per window at client
  size, VideoToolbox H.264, encode scheduler (focus policy, R15), CGEvent
  injector (HID keymap, side bits, repeat, click counts, scroll phases),
  cursor monitor, thumbnails, pairing store, server with live key reload.
  Loopback E2E: real TLS + real H.264, switching, two windows at once.
- apps/ipad (XcodeGen): one window per display, picker, hardware decode,
  Mac-correct keys, trackpad/touch input, local Mac cursor, paste/QR pairing
  with confirmation. `just ipad-sim-check`: UI test vs a real host.
- just recipes, CI iPad build job, docs/ipad.md, ADR 0008 as built
  (TCP+TLS-PSK instead of QUIC; random per-device key instead of a 6-digit
  code), spec/traceability/milestones/risks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XASAafkzuqCqwowAZTD1re
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant