Reverse Engineering Astropad Workbench: How LIQUID Protocol Works Under the Hood
Astropad Workbench - Reverse Engineering Research
Table of Contents
- Product Overview
- Binary Analysis
- Internal Architecture
- LIQUID Protocol Deep Dive
- Networking & Connectivity
- Screen Capture Pipeline
- Video Encoding Pipeline
- Input Handling
- State Synchronization
- Security Model
- Server Infrastructure
- Open Source Alternatives
- Building a Clone - Technical Approach
- Reddit Community Insights
Product Overview
Astropad Workbench (v1.1.0, build 799) is a remote desktop app for Mac, iPad, and iPhone. Built for the "AI era" - monitoring AI agents, remote workflows, and creative work.
Pricing
| Plan | Monthly | Annual | Limits | |------|---------|--------|--------| | Free | $0 | $0 | 20 min/day | | Unlimited | $10/mo | $50/yr | Unlimited |
System Requirements
- Mac (host): macOS 15+ (Apple Silicon strongly preferred; Intel limited to baseline LIQUID codec)
- iPhone/iPad (client): iOS 26+ / iPadOS 26+
- No Windows support
Key Features
- LIQUID proprietary streaming engine (shared with Luna Display & Astropad Studio)
- Retina-grade "perceptually lossless" streaming
- AES-256 end-to-end encryption
- Global relay network (11 regions), no port-forwarding needed
- Apple Pencil support with pressure sensitivity
- Voice input/dictation
- Multi-display consolidation into single virtual screen
- Mini-map navigation
Binary Analysis
Build Environment (from Info.plist)
Build Date: 2026-04-07T16:40:03Z
Xcode: 26.11 (17B100)
SDK: macOS 26.1 (25B74)
Architecture: Universal (x86_64 + arm64)
Bundle ID: com.astropad.workbench
Project Name: Stargate (build 799)
Principal Class: Astropad_Workbench.StargateApplication
Update Feed: https://downloads.astropad.com/workbench/mac/sparkle.xml
Linked Frameworks (key ones)
| Framework | Purpose | |-----------|---------| | ScreenCaptureKit | Screen capture (SCStream, SCContentFilter) | | VideoToolbox | Hardware H.264/HEVC encoding/decoding | | Metal | GPU rendering, tile processing | | CoreGraphics | CGEvent input injection | | Network.framework | QUIC/UDP networking | | CoreAudio / AVFAudio | Audio capture & streaming | | IOSurface | Zero-copy GPU buffer sharing | | CoreImage | Image processing | | IOKit | Hardware interaction | | Speech | Speech recognition for dictation | | Vision | Computer vision (likely for UI detection) | | Security | TLS/certificate management | | CoreLocation | Location services | | Sparkle | Auto-update framework | | Sentry | Crash reporting |
Third-Party Frameworks
- Sparkle 2.7.0 - Auto-updates via
https://downloads.astropad.com/workbench/mac/sparkle.xml - Sentry - Crash reporting & diagnostics
Internal Architecture
Project Codename: Stargate
The internal project name is "Stargate" - visible in build paths, class names, and the CI runner path:
/Users/admin/actions-runner/_work/Stargate/Stargate/
Language Stack
- Swift (macOS app layer, SwiftUI UI)
- Rust (LIQUID engine core, networking, codec, image processing)
- Objective-C (bridging layer, especially for ScreenCaptureKit FFI)
Rust Core: liquid-next
The LIQUID engine is a Rust workspace located at AstroDeps/liquid-next/ containing these crates:
LIQUID Engine Crates (from binary symbols)
| Crate | Purpose |
|-------|---------|
| liquid_codec | Tile-based encoding/decoding, H.264/HEVC support, video stats |
| liquid_net | Networking layer: QUIC, STUN/TURN, encrypted UDP, routing |
| liquid_screencap | Screen capture via ScreenCaptureKit (macOS backend) |
| liquid_input | Input handling: cursor monitoring, window tracking (macOS) |
| liquid_image_processing | wgpu-based GPU image processing: diffing, scaling pipelines |
| liquid_manager | Session management: client/server loops, screen selection |
| liquid_rate_control | Adaptive bitrate: integrated & Kalman filter approaches |
| liquid_statesync / liquid_statesync_core | State sync between peers via FFI to Swift |
| liquid_reporter | Diagnostics & telemetry |
| liquid_selection / liquid_selection_types | Content selection (windows, displays) |
| liquid_metrics | Performance metrics collection |
| liquid_web_server | Local web server (endpoints for configuration) |
Private Astropad Crates (from crates.astropadcloud.com)
| Crate | Version | Purpose |
|-------|---------|---------|
| astro-auth | 0.14.1 | JWT-based auth (HS256/ES256/EdDSA), shared sessions, token refresh |
| astro-key-store | 0.9.4 | Peer identity, certificate management, keychain storage |
| astro-peer-identity | 0.14.0 | Peer identification & authorization |
| astro-identity | 0.10.1 | User identity management |
| astro-virtual-display | 0.1.0 | Virtual display creation/management (CGVirtualDisplay private API) |
| astro-diagnostics | 0.3.1 | Diagnostic reporting, tracing |
| astro-reporting | 0.4.1 | Azure-hosted reporting endpoint |
| astro-sysinfo | 0.10.7 | System information collection |
| astro-remote | 0.10.2 | Remote connection management |
| astro-monitor | 8.1.0 | Display monitoring |
| astro-edid | 2.1.0 | EDID display metadata |
| astro-hermes-uploader | 0.1.0 | Hermes analytics upload |
| kv_db | 0.2.0 | Key-value database |
| scoped-thread-pool | 1.0.4 | Thread pool management |
Additional Rust Components
| Component | Purpose |
|-----------|---------|
| apple_api/src/sc_kit/ | Rust FFI bindings for ScreenCaptureKit |
| astro-clipboard/ | Cross-platform clipboard sync |
| astro-sync-quinn/ | QUIC-based synchronization (client + server) |
| segmented_payload/ | Payload segmentation/aggregation |
| tokio_utils/ | Async utilities (debouncing) |
Microservices Architecture (from binary paths)
The app uses an internal microservice/actor pattern:
| Service | Location | Purpose |
|---------|----------|---------|
| video_encoder | services/video_encoder/ | Screen capture → tile encoding → H.264/HEVC |
| video_decoder | services/video_decoder/ | H.264/HEVC decoding on client |
| video_network | services/video_network/ | Video frame transport |
| event_network | services/event_network/ | Event stream transport |
| event_renderer | services/event_renderer/ | Event rendering on client |
| event_sender | services/event_sender/ | Event dispatch |
| clipboard_sync | services/clipboard_sync/ | Bidirectional clipboard sync (local/remote/rx/files) |
| statesync | services/statesync/ | State synchronization protocol |
| multiplayer_input_host | services/multiplayer_input_host/ | Input injection on host (cursor, keyboard) |
| multiplayer_input_client | services/multiplayer_input_client/ | Input capture on client |
| multiplayer_input_protocol | services/multiplayer_input_protocol/ | Input protocol (cursor info, service errors) |
| virtual_display_client | services/virtual_display_client/ | Virtual display management (client-side) |
| virtual_display_server | services/virtual_display_server/ | Virtual display management (server-side) |
Swift App Layer (from debug paths)
Stargate/Stargate/
├── API/
│ └── BenchAPIClient.swift # API client for bench.astropad.com
├── AppDelegate.swift # Main app delegate
├── AppDelegate+ClientRequestedContent.swift
├── AppDelegate+DecoderWindowController.swift
├── AppDelegate+Diagnostic.swift
├── AppDelegate+MeetingDetector.swift
├── AppDelegate+SleepTracking.swift
├── AppState/
│ └── ConnectedPeer.swift
├── AX/
│ └── NSRunningApplication+AX.swift # Accessibility API integration
├── ChromeWindows/
│ ├── ClientChromeWindow.swift
│ ├── Views/ClientToolbarView.swift
│ └── WorkbenchChromeWindow.swift
├── Client/
│ ├── Decoder.swift # Video decoder
│ ├── Decoder+EventProcessing.swift
│ ├── Decoder+H26x.swift # H.264/H.265 specific
│ ├── Decoder+LiquidClient.swift # LIQUID client integration
│ ├── Decoder+OutputSize.swift
│ ├── Decoder+SharedContentState.swift
│ ├── Decoder+StateSync.swift
│ ├── DecoderProcessor.swift
│ ├── DecoderWindowController.swift
│ ├── DecoderWindowController+FullScreen.swift
│ ├── H264x.swift # H.264 handling
│ ├── ClientMultiplayerState.swift
│ └── SystemKeyEventTap.swift # System-wide key event interception
├── LIQEvent+Extensions.swift
├── LiquidRuntime.swift # Rust↔Swift bridge
├── Onboarding/
│ ├── OnboardingPermissions.swift
│ ├── OnboardingUI.swift
│ ├── OnboardingWindow.swift
│ └── Permissions/
│ ├── AccessibilityPermission.swift
│ └── PermissionHelper.swift
├── SelectionUI/
│ ├── ContentSelectorController.swift
│ └── DropAreasWindow.swift
├── Server/
│ ├── InputOSInjection/
│ │ └── InputOSInjector.swift # CGEvent-based input injection
│ └── SharedContent/
│ ├── FloatingControlsWindow.swift
│ ├── MultiPlayerViewController.swift
│ ├── SharedContentController.swift
│ └── SharedContentTracker.swift
├── Utils/
│ ├── ConnectionRequestManager.swift
│ ├── Image+Util.swift
│ ├── ImageLoader.swift
│ ├── NetworkObserver.swift
│ └── SleepPrevention.swift
└── Workbench/
├── AvailableContent.swift
└── Diagnostic/
└── ReportGeneratedView.swift
MeetingDetection/
├── ContactSelectorView.swift
├── DetectedMeeting.swift
└── WebBrowserTracker.swift
Swift System Classes (from Obj-C metadata)
StargateApplication # NSApplication subclass
StargateState # Main app state (observable)
├── DebugSystem # Debug overlay & tools
├── InputSystem # Input event handling
├── ClientViewSystem # Client view management
├── ClipboardSyncSystem # Clipboard synchronization
├── MeetingContactsRepo # Meeting detection contacts
├── VirtualDisplaySystem # Virtual display lifecycle
├── CapturedContentSystem # Captured screen content
├── CaptureSettingsSystem # Capture configuration
└── CapturableContentSystem # Available content enumeration
StargateDefaults # UserDefaults wrapper
LIQUID Protocol Deep Dive
Architecture
LIQUID is a tile-based rendering protocol — NOT a traditional video codec:
- Tile Division: Screen divided into independent tiles (typically 64x64 or 128x128 pixels)
- Change Detection: Only changed tiles are processed and transmitted (via wgpu diffing pipeline)
- Independent Tiles: No inter-frame prediction (no P/B-frames for tile data)
- GPU-Accelerated Diffing: Uses wgpu compute shaders for pixel diffing (
all_pixels_diffing,checkboard_diffing)
Dual Codec System
From binary analysis, LIQUID supports two parallel encoding modes:
TileCodecOnly - Pure tile-based (LIQUID native, lowest latency)
H26xCodecOnly - H.264/HEVC full-frame (for motion-heavy content)
The system dynamically switches between these based on content:
- Static/drawing content → Tile codec (raw pixel tiles via UDP)
- Full-screen video/motion → H.264/HEVC via VideoToolbox
- Both can run simultaneously for different screen regions
Key Internal Types (from binary strings)
// Codec types
enum CodecMode { TileCodecOnly, H26xCodecOnly }
// Frame handling
struct H264EncodedFrame { frame_id, segment_num, total_segments, tile_info }
struct H264EncodedFrameSegment { segment_num, total_segments, tile_info_dead_bit_count, tile_info, encoded_frame_data }
// Tile system
struct TileInfo { ... } // Tile metadata with dead bit count
struct TileBuffer { ... } // Tile pixel data
// Encoder config
struct EncoderConfig {
target_size: TargetSize,
max_available_size: Size,
scale: f64,
drop_frames_when_busy: bool,
load_style: LoadStyle, // NetworkDriven or Static
scheduler: TileScheduler,
starting_frame_id: u64,
debug_overlay_config: DebugOverlayConfig,
}
// Rate control
struct DebugOverlayConfig { flags: DebugOverlayFlags }
Encoding Pipeline (from module paths)
ScreenCaptureKit (SCStream)
→ liquid_screencap::capture::capture_macos::sc_kit::stream
→ IOSurface buffer
→ liquid_image_processing::wgpu::diffing_pipeline (GPU tile diff)
→ liquid_image_processing::wgpu::scaling_pipeline (resolution adapt)
→ liquid_codec::encoder::tile_scheduler (tile selection)
→ liquid_codec::encoder::compressor (tile compression)
→ liquid_codec::video_encoder::apple (H.264/HEVC via VideoToolbox)
→ liquid_codec::render_proxy::frame_aggregator (frame assembly)
→ segmented_payload (packet segmentation)
→ liquid_net::runtime::datagram (UDP send)
Latency
- Local network: ≤ 16ms end-to-end (claimed by Astropad blog)
- The tile approach inherently has lower latency than full-frame encoding since only changed regions are sent
Networking & Connectivity
Transport Protocol: QUIC (via Quinn)
The binary contains quinn-proto 0.11.9 - a Rust QUIC implementation. This is the PRIMARY transport:
QUIC is used for:
- Reliable stream transport (control, state sync, clipboard)
- Unreliable datagrams (video tiles, audio)
- Built-in encryption (TLS 1.3)
- Connection migration
- 0-RTT reconnection
Signaling: MQTT (via mqtts://)
Peer discovery and signaling uses MQTT over TLS:
Production: mqtts://signal.astropad.com:1883
Cloud: mqtts://signal.astropadcloud.com
NAT Traversal
- STUN: Used for public IP discovery (
STUN Error:strings present) - TURN: Relay fallback when direct connection fails (
TURN Error:strings present) - QUIC/TURN task: Combined QUIC-over-TURN for relay scenarios
Connection Types (from binary)
ListenerQUIC - QUIC server listener
ListenerUSB - USB direct connection
RestoreLastConnection - Reconnect to last peer
ClientQR - QR code pairing
ClientManual - Manual IP entry
ClientUSB - USB client connection
ClientDiscovery - Automatic peer discovery
Network Quality Monitoring
liquid_net::runtime::network_quality // Real-time quality assessment
liquid_net::runtime::datagram::pulse // Heartbeat/keepalive
liquid_net::throttle // Bandwidth throttling
liquid_rate_control::integrated // Integrated rate control
liquid_rate_control::kalman // Kalman filter for rate estimation
Encrypted UDP
Beyond QUIC's built-in TLS, there's an additional layer:
liquid_net::sockets::encrypted_udp::encryption_keys // Custom UDP encryption
Message Channels (from router)
Message - Generic messages
Fragment - Fragmented large messages
Analytics - Telemetry data
StateSyncEvent - State synchronization events
Video - Video frame data
Connectivity - Connection state updates
Diagnostics - Debug information
AppSpecific - Custom app messages
StateSyncStargateClient - Stargate-specific client state
StateSyncStargateServer - Stargate-specific server state
MultiplayerInputCoordination - Input event coordination
ClipboardSync - Clipboard data sync
Screen Capture Pipeline
macOS Backend
liquid_screencap::capture::capture_macos::sc_kit # ScreenCaptureKit wrapper
liquid_screencap::capture::capture_macos::backend # Capture orchestration
liquid_screencap::capture::capture_macos::display_stream # Display stream management
liquid_screencap::capture::capture_macos::img_processing # Post-capture processing
liquid_screencap::io_surface # IOSurface buffer handling
ScreenCaptureKit Integration
- Uses
SCStreamfor screen capture SCContentFilterfor window/display selection- Custom
stream::delegateandstream::outputfor receiving frames filters::windows_wrappersfor window-level captureshareable_content.rsfor enumerating capturable content
Image Processing (GPU via wgpu)
liquid_image_processing::wgpu::diffing_pipeline # GPU-accelerated change detection
- all_pixels_diffing # Full pixel comparison
- checkboard_diffing # Checkerboard pattern diffing (optimization)
liquid_image_processing::wgpu::scaling_pipeline # Resolution scaling
liquid_image_processing::wgpu::resources::mapped_buffer # GPU↔CPU buffer mapping
liquid_image_processing::pool # Buffer pool management
Video Encoding Pipeline
H.264/HEVC via VideoToolbox
liquid_codec::video_encoder::apple // Apple VideoToolbox encoder
liquid_codec::video_encoder::data_limits // Bitrate control
// Metrics tracked:
codec.h26x_bytes // H.264/HEVC byte count
codec.tile_bytes // Raw tile byte count
codec.keyframes.regular // Regular keyframes
codec.keyframes.recovery // Recovery keyframes (error recovery)
Tile Encoding
liquid_codec::tiles::tile_processor // Individual tile compression
liquid_codec::tiles::compressed_tile // Compressed tile format
liquid_codec::tiles::tilebuffer // Tile buffer management
liquid_codec::encoder::tile_scheduler // Decides which tiles to encode
liquid_codec::encoder::compressor // Tile compression engine
liquid_codec::encoder::encoder_inner // Core encoding logic
Frame Assembly
liquid_codec::render_proxy::frame_aggregator // Assembles tiles into frames
liquid_codec::render_proxy::fps // FPS tracking
liquid_codec::frame::frame_grid // Tile grid layout
// Segmentation for network transport
segmented_payload::segmentation // Break frames into network packets
segmented_payload::aggregation // Reassemble on receiver
segmented_payload::segment // Individual segment handling
Decoding (Client Side)
Decoder.swift // Main decoder
Decoder+H26x.swift // H.264/HEVC decoding via VTDecompressionSession
Decoder+LiquidClient.swift // LIQUID tile reassembly
DecoderProcessor.swift // Frame processing pipeline
H264x.swift // H.264 specific utilities
Input Handling
Server-Side (Host Mac)
InputOSInjector.swift // CGEvent-based input injection
liquid_input::os::mac // macOS input module
liquid_input::os::mac::cursor_monitor // Cursor position tracking
liquid_input::os::mac::multiplayer_state // Multi-user input state
liquid_input::os::mac::window_tracking // Window focus tracking
liquid_input::os::mac::unsupported_events // Handling unsupported input
liquid_input::types::raw_type_conversions // Input type normalization
Client-Side (iPad/iPhone)
multiplayer_input_client/ // Client input capture
multiplayer_input_protocol/ // Wire format for input events
- current_cursor // Cursor position messages
- cursor_info // Extended cursor metadata (per-platform)
- service_error // Error handling
Input Method: CGEvent (confirmed from linked frameworks)
- Uses
ApplicationServices.frameworkforCGEventPost,CGWarpMouseCursorPosition SystemKeyEventTap.swiftfor intercepting system key events viaCGEvent.tapCreate- Requires Accessibility permission
Apple Pencil
- Apple Pencil acts as "precise cursor controller" (from App Store listing)
- Pressure sensitivity transmitted via the multiplayer input protocol
kCGTabletEventPointPressure(0.0-1.0) injected on host via CGEvent
State Synchronization
StateSync System
A sophisticated bidirectional state sync between client and server:
liquid_statesync_core::ffi::statesync // Core state sync FFI
- capturable_content // What can be captured
- client_view // Client viewport state
- clipboard_sync // Clipboard state
- debug // Debug state
- input // Input state
- meeting_contacts_repo // Meeting contacts
- virtual_display // Virtual display config
liquid_statesync_core::store // State store (persistent)
liquid_statesync_core::store::flusher // Periodic state flush
liquid_statesync_core::observe::notifier // Change notifications
liquid_statesync_core::ffi::subscriber // State change subscribers
Network Protocol
services/statesync/
├── lib.rs // Service entry point
├── network_message.rs // Wire format for state sync messages
└── protocol.rs // State sync protocol definition
Security Model
Encryption
- QUIC TLS 1.3 for transport encryption (via quinn-proto)
- AES-256-GCM for additional data encryption (OpenSSL-based, from string analysis)
- CHACHA20_POLY1305 also available as cipher option
- Encrypted UDP layer for datagram encryption
Authentication
- JWT tokens with multiple algorithm support (HS256, HS384, HS512, ES256, ES384, RS256, EdDSA)
- Shared sessions with claims:
exp,iss,sub,owner_id,peer_id - Peer authorization:
Authorize,AuthorizeTemporary,Deny,DenyPersistent - Certificate-based peer identity via
astro-key-store - Keychain integration for secure key storage
Peer Identity Flow
1. Generate certificate & key pair (astro-key-store)
2. Store in macOS Keychain
3. Exchange peer identities via MQTT signaling
4. Verify certificates during QUIC handshake
5. Authorize/deny peers based on stored trust
6. Detect MITM: "Peer is authorized with a different hash, did they partially reset or MITM?"
Server Infrastructure
API Endpoints
| URL | Purpose |
|-----|---------|
| https://bench.astropad.com | Production API |
| https://bench.astropad.com/api/v1 | REST API v1 |
| https://sbench.astropad.com/api/v1 | Staging API |
| mqtts://signal.astropad.com:1883 | MQTT signaling (production) |
| mqtts://signal.astropadcloud.com | MQTT signaling (cloud) |
| https://hermes.astropad.com | Analytics/telemetry |
| https://astropad-44f7fcc4d74ab.flex.countly.com | Countly analytics |
| https://astropadwest-reporting-64356.azurewebsites.net/api/reports | Azure reporting |
| https://downloads.astropad.com/workbench/mac/sparkle.xml | Update feed |
Environment Flavors (from binary)
Apollo - Internal (shared code name with Luna Display?)
Bench - Workbench production
Beta - Beta testing
Dev - Development
LocalDev - Local development
PR - Pull request testing
Staging - Staging environment
Custom - Custom deployment
Build Infrastructure
- GitHub Actions runner at
/Users/admin/actions-runner/_work/Stargate/Stargate/ - Private Cargo registry at
crates.astropadcloud.com - Xcode 26.11 (Xcode 17B100)
Open Source Alternatives
For Building Something Similar
| Project | Approach | macOS Host | iOS Client | Latency | Stars | |---------|----------|------------|------------|---------|-------| | Lumen (Sunshine fork) | ScreenCaptureKit + VideoToolbox + virtual displays | Yes (Apple Silicon) | Moonlight iOS | ~1ms encode | 112+ | | Sunshine | Cross-platform game streaming host | Yes (limited) | Moonlight iOS | <30ms | 11.8k | | RustDesk | Rust-based remote desktop | Yes | Yes | 30-60ms | 73k | | Deskreen | WebRTC browser-based | Yes | No (Safari limitations) | 100-200ms | 17k |
Key Takeaway from Reddit
Lumen (github.com/trollzem/Lumen) is the most promising open-source base:
- Fork of Sunshine specifically for Apple Silicon
- Already uses ScreenCaptureKit + VideoToolbox (same APIs as Astropad)
- System audio via ScreenCaptureKit (no BlackHole needed)
- Virtual display via CGVirtualDisplay (same private API as Astropad)
- 1ms encode latency on M4 Mac Mini
- Missing: Apple Pencil support, tile-based encoding, adaptive codec switching
Building a Clone - Technical Approach
Phase 1: Core Screen Streaming (MVP)
1. Screen Capture
// Use ScreenCaptureKit (same as Astropad)
let content = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: true)
let filter = SCContentFilter(display: content.displays.first!, excludingApplications: [], exceptingWindows: [])
let config = SCStreamConfiguration()
config.width = 1920
config.height = 1080
config.minimumFrameInterval = CMTime(value: 1, timescale: 60)
config.showsCursor = true
config.capturesAudio = true
config.sampleRate = 48000
let stream = SCStream(filter: filter, configuration: config, delegate: self)
try await stream.startCapture()
2. Video Encoding
// Hardware H.264/HEVC via VideoToolbox
VTCompressionSessionCreate(nil, width, height, kCMVideoCodecType_HEVC, nil, nil, nil, callback, nil, &session)
VTSessionSetProperty(session, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue)
VTSessionSetProperty(session, kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse)
// Encode CVPixelBuffer from ScreenCaptureKit
VTCompressionSessionEncodeFrame(session, pixelBuffer, pts, duration, nil, nil, nil)
3. Input Injection
// CGEvent for mouse/keyboard injection
let mouseEvent = CGEvent(mouseEventSource: nil, mouseEventType: .mouseMoved, mouseCursorPosition: point, mouseButton: .left)
mouseEvent?.post(tap: .cghidEventTap)
// Tablet pressure via CGEvent
let tabletEvent = CGEvent(mouseEventSource: nil, mouseEventType: .mouseMoved, mouseCursorPosition: point, mouseButton: .left)
tabletEvent?.setDoubleValueField(.tabletEventPointPressure, value: pressure)
tabletEvent?.post(tap: .cghidEventTap)
4. Networking (QUIC)
Use Quinn (Rust) or Network.framework (Swift) for QUIC transport:
- Reliable streams for control, state sync, clipboard
- Unreliable datagrams for video frames and audio
- Built-in TLS 1.3 encryption
Phase 2: LIQUID-like Tile Engine
GPU Tile Diffing
// Metal compute shader for tile change detection
kernel void detectChangedTiles(
texture2d<float, access::read> previousFrame [[texture(0)]],
texture2d<float, access::read> currentFrame [[texture(1)]],
device uint* changedTiles [[buffer(0)]],
uint2 gid [[thread_position_in_grid]]
) {
// Compare tile regions, mark changed tiles
float4 prev = previousFrame.read(gid);
float4 curr = currentFrame.read(gid);
if (distance(prev, curr) > threshold) {
atomicOr(&changedTiles[tileIndex], 1);
}
}
Adaptive Codec Selection
if (changed_tile_ratio > 0.7) {
// Lots of motion → switch to H.264/HEVC full-frame
useH26xCodec()
} else {
// Mostly static → tile-based streaming
sendChangedTilesOnly()
}
Phase 3: NAT Traversal & Global Connectivity
Architecture
1. MQTT signaling server (peer discovery & SDP-like exchange)
2. STUN servers (public IP discovery)
3. TURN/relay servers (fallback for restrictive NATs)
4. QUIC transport (multiplexed streams + datagrams)
Services to Use
| Component | Option 1 | Option 2 | |-----------|----------|----------| | Signaling | Self-hosted MQTT (Mosquitto) | AWS IoT Core | | STUN | coturn (free) | Google STUN (stun.l.google.com:19302) | | TURN relay | coturn cluster on AWS/GCP | Cloudflare Realtime | | QUIC | Quinn (Rust) | Network.framework (Swift) |
Phase 4: Apple Pencil & Advanced Features
Apple Pencil Pressure
// On iOS client:
func pencilInteraction(_ interaction: UIPencilInteraction, didUpdate data: UIPencilInteractionData) {
let pressure = data.pressure // 0.0 - 1.0
let altitude = data.altitudeAngle
let azimuth = data.azimuthAngle
// Send to host via QUIC
}
// On macOS host:
let event = CGEvent(mouseEventSource: nil, ...)
event?.setDoubleValueField(.tabletEventPointPressure, value: pressure)
event?.post(tap: .cghidEventTap)
Virtual Display
// Astropad uses private CGVirtualDisplay API (same as Lumen)
// Alternative: BetterDisplay open-source approach
Clipboard Sync
// macOS: NSPasteboard observation
// iOS: UIPasteboard monitoring
// Sync via QUIC reliable stream with UTType serialization
Reddit Community Insights
Key Findings from Reddit
- EasyCanvas is repeatedly recommended as a cheaper Astropad Studio alternative ($12 one-time vs $80/yr)
- Jump Desktop supports Apple Pencil pressure sensitivity for remote desktop
- Lumen (Sunshine fork for macOS) achieves 1ms encode latency with VideoToolbox
- Parsec is widely used for Mac remote desktop but lacks 4:4:4 on free tier
- Splashtop (v3.4.2+) supports stylus pressure events
- CGEventTap usage can cause App Store rejection under guideline 2.4.5 (important for distribution)
- Disabling AWDL (
sudo ifconfig awdl0 down) fixes audio/video jitter when streaming from Mac - Community sentiment: Astropad is technically excellent but overpriced
Competitive Landscape
| Product | Price | Pencil Pressure | Latency | Platform | |---------|-------|----------------|---------|----------| | Astropad Workbench | $50/yr | Yes | ~16ms local | Mac→iPad/iPhone | | Astropad Studio | $80/yr or $150 lifetime | Yes | ~16ms local | Mac/Win→iPad | | EasyCanvas | $12 one-time | Partial (hover added 2025) | Low | Mac/Win→iPad | | Jump Desktop | $18 one-time | Yes | Medium | Cross-platform | | Parsec | Free/$8/mo | No | ~15ms | Cross-platform | | Splashtop | $5/mo+ | Yes (v3.4.2+) | Medium | Cross-platform | | Luna Display | $130 hardware | Yes | ~16ms (hardware) | Mac→iPad | | Duet Display | $4/mo+ | Yes | Medium | Cross-platform |
Next Steps for Reverse Engineering
Immediate
- Network traffic analysis: Use Wireshark/mitmproxy to capture MQTT signaling and QUIC streams
- Binary decompilation: Use Hopper/Ghidra on the arm64 slice for deeper code analysis
- Runtime analysis: Use
fridaordtraceto trace API calls during a live session - class-dump: Extract Objective-C headers from the Swift/ObjC binary
Deeper Analysis
- LIQUID wire format: Capture and analyze UDP datagrams to understand tile encoding format
- MQTT protocol: Subscribe to signaling topics to understand peer discovery
- QUIC streams: Map stream IDs to service channels
- Rate control: Understand the Kalman filter-based adaptive bitrate algorithm
Building a Prototype
- Start with Lumen as a base (already has ScreenCaptureKit + VideoToolbox + virtual displays)
- Add tile-based diffing using Metal compute shaders
- Replace Sunshine's protocol with QUIC (Quinn)
- Add Apple Pencil pressure/tilt injection via CGEvent tablet events
- Build MQTT signaling for peer discovery
- Deploy coturn for STUN/TURN relay
Research compiled: April 9, 2026 Binary analyzed: Astropad Workbench 1.1.0 (799), built 2026-04-07