# How vphone-cli Handles HID Injection in the Guest Daemon: A Deep Dive into macOS-to-iOS Event Routing

> Explore how vphone-cli handles HID injection, routing macOS events to iOS via JSON commands and dynamic IOKit binding in the guest daemon. Learn the technical details.

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

---

**vphone-cli handles HID injection by sending JSON-encoded commands from the host over vsock to the guest vphoned daemon, which dynamically binds Apple's private IOKit HID APIs at runtime to synthesize keyboard and touch events.**

The `vphone-cli` project provides a robust mechanism for injecting human interface device events into iOS virtual machines running on Apple Silicon Macs. Unlike the standard Virtualization.framework USB multitouch path—which fails on iOS 18 base kernels—this approach uses a custom vsock-based protocol and guest-side daemon to deliver events directly through the iOS HID subsystem. This article examines how vphone-cli performs HID injection in the guest daemon, tracing the complete flow from host Swift code to guest Objective-C implementation.

## Host-Side Message Preparation

The injection process begins in [`sources/vphone-cli/VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/sources/vphone-cli/VPhoneControl.swift), where the host constructs structured HID requests.

### Keyboard and Button Events

The `VPhoneControl` class exposes `sendHID(page:usage:down:)` for raw HID access and a convenience wrapper `sendHIDPress(page:usage:)` for single keypresses:

```swift
// VPhoneControl.swift
func sendHIDPress(page: UInt32, usage: UInt32) {
    sendHID(page: page, usage: usage, down: nil)   // "down" omitted → press-tap
}

```

This serializes a JSON payload containing protocol version, request type `"hid"`, unique request ID, HID page, usage code, and optional `down` boolean.

### Touch Events

Digitizer injection uses `sendTouch(phase:x:y:)` with normalized coordinates (0.0–1.0) and phase constants (0=down, 1=move, 3=up):

```swift
control.sendTouch(phase: 0, x: 0.5, y: 0.5)   // touch down at screen center
control.sendTouch(phase: 3, x: 0.5, y: 0.5)   // touch up

```

## vsock Transport Protocol

Events traverse the VM boundary via a length-prefixed JSON protocol implemented in `scripts/vphoned/vphoned_protocol.m`. The `vp_read_message` and `vp_write_message` functions handle complete message framing, ensuring atomic delivery of variable-length commands.

## Guest Daemon Architecture: vphoned_hid.m

The core HID injection logic resides in `scripts/vphoned/vphoned_hid.m`. This module dynamically loads Apple's private IOKit framework to avoid compile-time linkage against unpublished APIs.

### Dynamic IOKit Symbol Resolution

The `vp_hid_load()` function implements lazy binding on first use:

```c
static int vp_hid_load(void) {
    // dlopen IOKit.framework and resolve:
    // - IOHIDEventCreateKeyboardEvent
    // - IOHIDEventCreateDigitizerEvent
    // - IOHIDEventCreateDigitizerFingerEvent
    // - IOHIDEventSystemClientDispatchEvent
}

```

If digitizer symbols are unavailable, touch injection degrades gracefully by returning early from touch functions.

### Serial Event Dispatch Queue

All HID operations execute on a dedicated serial queue created during initialization:

```c
gHIDQueue = dispatch_queue_create("com.vphone.hid", 
    DISPATCH_QUEUE_SERIAL);
dispatch_set_qos_class_fallback(gHIDQueue, QOS_CLASS_USER_INTERACTIVE);

```

The **QOS_CLASS_USER_INTERACTIVE** quality of service level guarantees ordered, low-latency event delivery critical for responsive input.

## Keyboard Event Injection Flow

When `vphoned` receives a `"hid"` message, it dispatches to `vp_hid_key` (stateful press/release) or `vp_hid_press` (automatic tap):

```c
// vphoned_hid.m
void vp_hid_press(uint32_t page, uint32_t usage) {
    IOHIDEventRef down = pKeyboard(kCFAllocatorDefault,
                                   mach_absolute_time(),
                                   page, usage, 1, 0);  // key down
    send_hid_event(down);
    usleep(100000);  // 100ms hold duration
    IOHIDEventRef up = pKeyboard(kCFAllocatorDefault,
                                 mach_absolute_time(),
                                 page, usage, 0, 0);    // key up
    send_hid_event(up);
}

```

The `send_hid_event` helper sets a fixed sender ID (`0x8000000817319372`) and posts to `IOHIDEventSystemClientDispatchEvent` on `gHIDQueue`.

## Touch Event Injection: Digitizer Construction

Touch events require building a composite event structure. The `vp_hid_touch` function translates phase codes into digitizer operations:

```c
// vphoned_hid.m
void vp_hid_touch(int phase, double x, double y) {
    switch (phase) {
        case 0: // down
            dispatch_digitizer(x, y, 1, 1,
                VP_DIG_TOUCH | VP_DIG_IDENTITY);
            break;
        case 1: // move
            dispatch_digitizer(x, y, 1, 1, VP_DIG_POSITION);
            break;
        case 3: // up
        default:
            dispatch_digitizer(x, y, 0, 0,
                VP_DIG_TOUCH | VP_DIG_IDENTITY);
            break;
    }
}

```

The `dispatch_digitizer` function constructs a hierarchy of events:

1. **Parent digitizer event** via `IOHIDEventCreateDigitizerEvent`
2. **Finger sub-event** via `IOHIDEventCreateDigitizerFingerEvent`
3. **Display integration flag** applied via `IOHIDEventSetIntegerValue` with key `kIOHIDEventFieldDigitizerIsDisplayIntegrated`
4. **Append finger to parent** and dispatch combined structure

This multi-level construction mirrors physical touchscreen hardware reporting, ensuring iOS recognizes the input as genuine display touch data.

## Capability Negotiation at Connection

The host determines injection availability through an initial handshake. In `VPhoneControl.performHandshake`, the guest advertises capabilities via JSON:

```json
{"caps":["hid","touch"],"iosVersion":"18.1"}

```

The host evaluates `guestCaps.contains("touch")` and `guestIOSVersion` to set `useGuestTouchInjection`, falling back to alternative input methods when the daemon lacks support.

## Key Implementation Files

| File Path | Role in HID Injection |
|-----------|----------------------|
| [`sources/vphone-cli/VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/sources/vphone-cli/VPhoneControl.swift) | Host vsock client, JSON message construction, handshake management |
| `scripts/vphoned/vphoned_hid.m` | Guest daemon HID implementation, dynamic IOKit binding, event creation and dispatch |
| `scripts/vphoned/vphoned_protocol.m` | Length-prefixed JSON protocol for reliable host-guest communication |
| [`sources/vphone-cli/VPhoneMenuKeys.swift`](https://github.com/Lakr233/vphone-cli/blob/main/sources/vphone-cli/VPhoneMenuKeys.swift) | UI translation layer mapping system actions (Home, Power) to HID codes |
| [`sources/vphone-cli/VPhoneKeyHelper.swift`](https://github.com/Lakr233/vphone-cli/blob/main/sources/vphone-cli/VPhoneKeyHelper.swift) | macOS key event to HID usage mapping for keyboard forwarding |

## Summary

- **vphone-cli** solves iOS VM input limitations by implementing guest-side HID injection through the private IOKit framework
- **Dynamic symbol loading** at runtime avoids linking against unpublished APIs while maintaining forward compatibility
- **Structured JSON protocol** over vsock provides reliable, ordered message delivery between host and guest
- **Dedicated serial queue** with interactive QoS class ensures low-latency, deterministic event timing
- **Composite digitizer events** faithfully replicate physical touchscreen hardware for seamless iOS integration
- **Capability negotiation** enables graceful degradation when guest features are unavailable

## Frequently Asked Questions

### How does vphone-cli differ from standard Virtualization.framework touch input?

Standard Virtualization.framework relies on VZUSBKeyboard and VZUSBMultiTouch configuration, which Apple disabled for iOS 18 base kernels. vphone-cli bypasses this limitation by connecting directly to the iOS HID event system via private IOKit APIs inside the guest, making touch injection possible where native USB multitouch fails.

### Why use dynamic loading instead of linking against IOKit directly?

Apple's HID event creation functions are private APIs not exposed in public headers. Dynamic loading via `dlopen` and `dlsym` allows vphone-cli's guest daemon to compile without unauthorized framework linkages, and to degrade gracefully—silently disabling touch features if symbols are absent—rather than crashing at launch on newer or modified iOS versions.

### What is the purpose of the fixed sender ID in HID events?

The sender ID `0x8000000817319372` identifies the virtual input source to the iOS HID system, distinguishing injected events from physical hardware. Consistent sender assignment enables iOS to track input provenance and maintain proper event attribution across the window server and application layers.

### Can vphone-cli inject multi-touch gestures?

The current implementation supports single-finger touch events through the `vp_hid_touch` interface. The `dispatch_digitizer` architecture accommodates extension—multiple finger sub-events can be appended to the parent digitizer event—but the host protocol and convenience methods in `VPhoneControl` currently expose only single-point control.