How useGuestTouchInjection Selects the Touch Path Based on Guest iOS Version

The useGuestTouchInjection computed property returns true only when the guest iOS major version is less than 26, routing older kernels through native Virtualization framework multitouch while forcing iOS 26+ to use guest-side HID injection via the vphoned daemon.

In the Lakr233/vphone-cli repository, touch input handling depends on a version-aware flag that determines whether macOS processes multitouch events natively or delegates them to a daemon running inside the guest VM. This logic centers on the useGuestTouchInjection property, which inspects the guest's reported iOS version during the handshake phase and switches touch delivery mechanisms accordingly. Understanding this behavior is essential for debugging input issues when virtualizing newer iOS versions that run on the 26.x kernel.

The Version Detection Logic in VPhoneControl

The decision point resides in sources/vphone-cli/VPhoneControl.swift within the useGuestTouchInjection computed property (lines 35‑44). This accessor parses the guestIOSVersion string received during VM initialization and extracts the major version number to apply a hard threshold.

// VPhoneControl.swift – lines 35-44
var useGuestTouchInjection: Bool {
    guard isConnected,
          guestCaps.contains("touch"),
          let major = guestIOSVersion.flatMap({ Int($0.split(separator: ".").first ?? "") })
    else { return false }
    return major < 26          // iOS < 26 → use guest-side injection
}

The property returns true only when three conditions converge:

  • The host maintains an active connection (isConnected).
  • The guest capability set includes the "touch" feature (guestCaps.contains("touch")).
  • The parsed major version integer is strictly less than 26.

If any check fails, the method returns false, signaling the system to use the native Virtualization framework path.

Why iOS 26+ Requires Guest-Side Injection

iOS 18 and later builds run on the 26.x kernel, which alters how the Virtualization framework processes USB HID reports. On these newer kernels, the USB-touchscreen driver extension (dext) receives raw HID reports but does not emit digitizer events that macOS can translate into VZMultiTouchEvent objects.

Consequently, the standard VZMultiTouchEvent dispatched from the host never reaches the iOS UI layer. To circumvent this limitation, vphone-cli falls back to guest-side HID injection performed by the vphoned daemon running inside the VM. Older kernels (iOS < 26) continue to generate digitizer events correctly, allowing the native VZ multitouch path to function without intervention.

Touch Routing in VPhoneVirtualMachineView

The view layer consumes this flag in sources/vphone-cli/VPhoneVirtualMachineView.swift (lines 58‑66) to decide how to forward touch events. When useGuestTouchInjection evaluates to true, the view bypasses the Virtualization framework and sends coordinates directly to the guest daemon.

// VPhoneVirtualMachineView.swift – lines 58-65
if let control, control.useGuestTouchInjection {
    control.sendTouch(phase: phase,
                      x: Double(normalizedPoint.x),
                      y: Double(normalizedPoint.y))
    return true                     // Guest-side injection
}

If the property returns false, the code constructs a VZTouch object and forwards it via Dynamic._VZMultiTouchEvent, leveraging the standard Apple Virtualization APIs. This branching logic ensures compatibility across both legacy and modern iOS bases without requiring manual configuration.

Practical Implementation Examples

When building automation tools or debugging touch input, you can inspect the flag directly from the host side before initiating a gesture sequence:

let control = VPhoneControl(...)
if control.useGuestTouchInjection {
    // Send a single-finger touch through the guest daemon
    control.sendTouch(phase: 0, x: 0.5, y: 0.5)   // down at screen centre
}

Inside a custom input handler, implement the dual-path strategy to support all iOS versions:

// Inside VPhoneVirtualMachineView.handleMouseEvent(...)
let normalized = normalizeCoordinate(localPoint)
if let ctrl = control, ctrl.useGuestTouchInjection {
    ctrl.sendTouch(phase: 1, x: normalized.x, y: normalized.y) // move
} else {
    // Fall back to native VZ touch
    let vzTouch = Dynamic._VZTouch(view: self,
                                   index: 0,
                                   phase: 1,
                                   location: normalized,
                                   swipeAim: nil,
                                   timestamp: timestamp)
    Dynamic(device).sendMultiTouchEvents([vzTouch.asObject!])
}

Summary

  • useGuestTouchInjection inspects the guest iOS major version parsed from guestIOSVersion and returns true only when the version is less than 26.
  • iOS < 26 uses the native Virtualization framework multitouch path via VZMultiTouchEvent.
  • iOS ≥ 26 (including iOS 18 and later) triggers guest-side injection through the vphoned daemon due to missing digitizer events in the 26.x kernel.
  • The logic is implemented in sources/vphone-cli/VPhoneControl.swift (lines 35‑44) and consumed by sources/vphone-cli/VPhoneVirtualMachineView.swift (lines 58‑66).

Frequently Asked Questions

What is the purpose of useGuestTouchInjection in vphone-cli?

The property acts as a version-gated switch that determines whether touch coordinates are injected into the guest VM via the native Apple Virtualization framework or forwarded to an internal HID daemon. It abstracts the complexity of kernel-level behavioral changes between iOS versions.

At which iOS version does the touch path switch from native to guest-side injection?

The switch occurs at iOS major version 26. According to the source code in VPhoneControl.swift, any guest reporting a major version of 26 or higher will trigger useGuestTouchInjection to return false, forcing the system to use the guest-side vphoned injection path.

What files contain the touch injection logic in the vphone-cli repository?

The core detection logic resides in sources/vphone-cli/VPhoneControl.swift (lines 35‑44), while the routing implementation that respects this decision is located in sources/vphone-cli/VPhoneVirtualMachineView.swift (lines 58‑66). These two files handle the complete lifecycle of touch event delivery.

Why does the Virtualization framework fail to deliver touch events on newer iOS versions?

On the 26.x kernel used by iOS 18 and later, the USB-touchscreen driver extension receives raw HID data but no longer emits the digitizer events required by macOS to create VZMultiTouchEvent objects. Without these events, the native multitouch pipeline cannot propagate touches to the guest UI, necessitating the fallback to guest-side HID injection performed by vphoned.

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 →