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

> Discover how vphone-cli achieves seamless clipboard synchronization between your macOS host and iOS guest. Learn about its vsock-based JSON protocol and daemon interaction.

- Repository: [Lakr/vphone-cli](https://github.com/Lakr233/vphone-cli)
- Tags: internals
- Published: 2026-09-06

---

**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`](https://github.com/Lakr233/vphone-cli/blob/main/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`](https://github.com/Lakr233/vphone-cli/blob/main/sources/vphone-cli/VPhoneControl.swift) handle this transparently:

```swift
// 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:

```swift
// 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:

```swift
// 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:

```swift
// 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`:

```swift
@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`](https://github.com/Lakr233/vphone-cli/blob/main/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`](https://github.com/Lakr233/vphone-cli/blob/main/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.