How vphone-cli Manages Clipboard Synchronization Between Host and Guest VMs

vphone-cli synchronizes clipboard data between macOS host and iOS guest through a bidirectional vsock-based JSON protocol, with the host sending commands via VPhoneControl and the guest daemon vphoned interfacing directly with UIPasteboard.

Clipboard sharing is essential for seamless workflow between a macOS host and an iOS virtual machine. The vphone-cli project implements this through a clean architecture that keeps host-side code free of UIKit dependencies while leveraging the guest's native pasteboard APIs. This guide explains exactly how clipboard synchronization works, with authoritative references to the source implementation in Lakr233/vphone-cli.

The vsock Transport Foundation

All clipboard operations travel over a persistent virtio-socket (vsock) connection on port 1337. This transport layer is managed by VPhoneControl.swift and provides reliable, low-latency communication between host and guest.

The protocol uses a simple framing mechanism: every JSON message is prefixed with a 4-byte big-endian length. The writeMessage and readMessage methods in sources/vphone-cli/VPhoneControl.swift handle this transparently:

// From VPhoneControl.swift#L44-L57
func writeMessage(_ data: Data) throws {
    var length = UInt32(data.count).bigEndian
    let header = Data(bytes: &length, count: 4)
    try socket.write(header + data)
}

func readMessage() async throws -> Data {
    let header = try await socket.read(4)
    let length = UInt32(bigEndian: header.withUnsafeBytes { $0.load(as: UInt32.self) })
    return try await socket.read(Int(length))
}

Host-Side Implementation: VPhoneControl

The Swift class VPhoneControl exposes two async methods for clipboard operations. These methods build JSON payloads and parse responses according to the defined protocol.

Reading Clipboard from Guest

clipboardGet() sends ["t":"clipboard_get"] and expects a JSON response with optional binary PNG data:

// Host-side Swift: retrieve guest clipboard content
Task {
    do {
        let content = try await control.clipboardGet()
        print("Guest clipboard text:", content.text ?? "<none>")
        print("Available types:", content.types ?? [])
        print("Has image:", content.hasImage)
    } catch {
        print("Failed to read clipboard:", error)
    }
}

As implemented in VPhoneControl.swift#L552-L566, this method handles the dual response format: a JSON header followed by raw PNG bytes when an image is present.

Writing Text to Guest Clipboard

clipboardSet(text:) transmits plain text with minimal overhead:

// Set plain text on guest clipboard
Task {
    try await control.clipboardSet(text: "Hello from macOS!")
}

The implementation at VPhoneControl.swift#L63-L68 constructs ["t":"clipboard_set","text":text] and streams it through the vsock connection.

Writing Images to Guest Clipboard

For image data, the protocol separates metadata from binary payload:

// Set image on guest clipboard
let pngData = try Data(contentsOf: URL(fileURLWithPath: "/path/to/image.png"))
Task {
    try await control.clipboardSet(imageData: pngData)
}

The header includes image dimensions; the PNG bytes follow immediately after the JSON frame.

Guest-Side Daemon: vphoned

The iOS guest runs vphoned, an Objective-C daemon that dynamically loads UIKit at runtime. This design avoids static linking of UIKit while enabling full pasteboard access.

Clipboard Handler Registration

In scripts/vphoned/vphoned_clipboard.m#L60-L107, the daemon registers command handlers for clipboard_get and clipboard_set. The UIPasteboard class is resolved via dlopen to ensure the daemon can run in limited environments.

Reading from UIPasteboard

When clipboard_get is received, vphoned:

  1. Retrieves the system UIPasteboard instance
  2. Extracts string, pasteboardTypes, and changeCount
  3. Converts any image to PNG using UIImagePNGRepresentation
  4. Writes JSON header, then streams PNG bytes via vp_write_fully

This implementation at vphoned_clipboard.m#L60-L99 supports both text and image content in a single response.

Writing to UIPasteboard

For clipboard_set commands (vphoned_clipboard.m#L109-L166):

  • Text payload: Calls setString: on the pasteboard
  • Image payload: Reads binary PNG from socket, constructs UIImage, and calls setImage:

The daemon handles both content types transparently, matching the host's expectations.

UI Integration and Capability Detection

The macOS menu bar interface exposes clipboard actions through VPhoneMenuConnect.swift#L42-L50:

@objc func getClipboard() {
    Task {
        do {
            let content = try await control.clipboardGet()
            // Present result to user via alert
        } catch {
            // Handle error in UI
        }
    }
}

Menu availability depends on capability negotiation. During initial handshake, the guest advertises supported features; the "clipboard" capability must be present for these menu items to appear (VPhoneAppDelegate.swift#L174).

Complete Data Flow


┌─────────────────┐     vsock (port 1337)     ┌─────────────────┐
│   macOS Host    │  ───────────────────────► │   iOS Guest     │
│  VPhoneControl  │  ◄────────────────────────│    vphoned      │
│                 │   JSON + binary frames    │                 │
│ ┌─────────────┐ │                           │ ┌─────────────┐ │
│ │ Get/Set UI  │ │                           │ │   Command   │ │
│ │   Menu      │ │                           │ │   Handler   │ │
│ └──────┬──────┘ │                           │ └──────┬──────┘ │
│        │        │                           │        │        │
│        ▼        │                           │        ▼        │
│  Async Methods  │                           │  dlopen/UIKit   │
│ clipboardGet()/ │                           │   UIPasteboard  │
│ clipboardSet()  │                           │  get/set ops    │
└─────────────────┘                           └─────────────────┘

Summary

  • Transport: Persistent vsock on port 1337 with 4-byte length-prefixed JSON framing
  • Host API: VPhoneControl.clipboardGet() and clipboardSet(text:) in VPhoneControl.swift
  • Guest handler: vphoned_clipboard.m dynamically loads UIPasteboard via dlopen
  • Content support: Plain text and PNG images bidirectionally
  • UI gating: Capability handshake determines menu availability

Frequently Asked Questions

What protocol does vphone-cli use for clipboard synchronization?

vphone-cli uses a custom JSON-over-vsock protocol. Each message is framed with a 4-byte big-endian length prefix, followed by UTF-8 JSON. Image data is streamed as raw PNG bytes after the JSON header, not base64-encoded, maximizing efficiency.

Why does the guest daemon use dlopen for UIKit instead of static linking?

The vphoned daemon loads UIPasteboard at runtime via dlopen and NSClassFromString. This minimizes dependencies and allows the daemon to run in constrained environments where full UIKit availability isn't guaranteed until runtime.

Can clipboard synchronization handle rich content beyond text and images?

The protocol is extensible through JSON message fields. Currently vphoned_clipboard.m handles text and PNG images; additional MIME types could be supported by extending the pasteboardTypes handling and corresponding host-side parsing in VPhoneControl.swift.

How does the host know when clipboard operations are available?

During the initial vsock handshake, the guest advertises capabilities as an array of strings. The host checks for the "clipboard" capability (VPhoneAppDelegate.swift#L174) before enabling the "Get Clipboard" and "Set Clipboard Text…" menu items.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →