CoreHID HidVirtualDevice Returning Nil

I'm trying to follow the guide for creating a virtual device here. My goal was to see the set report print the data (and to eventually create a virtual gamepad and dispatch input reports of button presses).

I am on MacOS v15.0 and I have CoreHID.framework linked (not embedded). However, when using the sample code below, HIDVirtualDevice returns nil.

import Foundation
import CoreHID
// This describes a keyboard device according to the Human Interface Devices standard.
let keyboardDescriptor: Data = Data([0x05, 0x01, 0x09, 0x06, 0xA1, 0x01, 0x05, 0x07, 0x19, 0xE0, 0x29, 0xE7, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x95, 0x05, 0x75, 0x01, 0x91, 0x02, 0x95, 0x01, 0x75, 0x03, 0x91, 0x01, 0x05, 0x07, 0x19, 0x00, 0x2A, 0xFF, 0x00, 0x95, 0x05, 0x75, 0x08, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x81, 0x00, 0x05, 0xFF, 0x09, 0x03, 0x75, 0x08, 0x95, 0x01, 0x81, 0x02, 0xC0])
let properties = HIDVirtualDevice.Properties(descriptor: keyboardDescriptor, vendorID: 1)

let device = HIDVirtualDevice(properties: properties)

final class Delegate : HIDVirtualDeviceDelegate {
    // A handler for system requests to send data to the device.
    func hidVirtualDevice(_ device: HIDVirtualDevice, receivedSetReportRequestOfType type: HIDReportType, id: HIDReportID?, data: Data) throws {
        print("Device received a set report request for report type:\(type) id:\(String(describing: id)) with data:[\(data.map { String(format: "%02x", $0) }.joined(separator: " "))]")
    }


    // A handler for system requests to query data from the device.
    func hidVirtualDevice(_ device: HIDVirtualDevice, receivedGetReportRequestOfType type: HIDReportType, id: HIDReportID?, maxSize: size_t) throws -> Data {
        print("Device received a get report request for report type:\(type) id:\(String(describing: id))")
        assert(maxSize >= 4)
        return (Data([1, 2, 3, 4]))
    }
}


if (device != nil) {
    print("Device is ready")
    await device?.activate(delegate: Delegate())
    try await device?.dispatchInputReport(data: Data([5, 6, 7, 8]), timestamp: SuspendingClock.now)
} else {
    print("Device not created")
}
Answered by DTS Engineer in 805811022

I am on MacOS v15.0 and I have CoreHID.framework linked (not embedded). However, when using the sample code below, HIDVirtualDevice returns nil.

Your code needs to be signed with the "com.apple.developer.hid.virtual.device" entitlement for that API for function. You can apply for that entitlement through the DriverKit entitlement request form.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

I am on MacOS v15.0 and I have CoreHID.framework linked (not embedded). However, when using the sample code below, HIDVirtualDevice returns nil.

Your code needs to be signed with the "com.apple.developer.hid.virtual.device" entitlement for that API for function. You can apply for that entitlement through the DriverKit entitlement request form.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thank you for the response Kevin. Assuming this is the correct route to solving the issue, would it be expected that following the steps here would allow me to test before a response to the request form?

I only want to test on my local computer, and disabling SIP (as well as the other checks), does not change the behavior and nil is still returned.

Accepted Answer

Thank you for the response Kevin. Assuming this is the correct route to solving the issue, would it be expected that following the steps here would allow me to test before a response to the request form?

No. Those directions aren't really about enabling the DEXT to run, they're actually about speeding up the general development "flow". Notably, this section on disabling SIP:

"System Integrity Protection (SIP) in macOS prevents unauthorized code from running on your system. Xcode doesn’t notarize apps or system extensions during the normal development cycle, so disabling SIP bypasses the notarization checks that the system normally performs, and allows you to debug your code more quickly."

The actually issue here is that the system expect DEXTs Developer ID signed DEXT to be notarized (not just signed) but notarizing every DEXT build would enormously slow down the entire build/debug test cycle. Disabling SIP doesn't remove the entitlement check itself, it just removes the notarization requirement.

As an aside, I don't think this is actually necessary for DEXTs anymore, as the "Development Only" entitlement variants both allow the developers to test code without an approved entitlement and (I believe) also bypass the notarization issue. We don't have an "Development Only" variant for this entitlement, but that would be useful and I would encourage you to file an enhancement request asking for us to add one.

I only want to test on my local computer, and disabling SIP (as well as the other checks), does not change the behavior and nil is still returned.

If you want to test without the approved entitlement, this forum post has a rundown of how you can do that. It describes a different entitlement, but the process is exactly the same.

The key point there is that by disabling AMFI, you completely disable the systems ability to validate entitlements. Once AMFI is disable, you can then add the "com.apple.developer.hid.virtual.device" entitlement to your builds entitlement plist and the system will accept it as "valid", since you've specifically disabled the verification process the system uses to validate your usage.

The danger of that should be obvious and I would second Quinn's suggestion that you test this in a VM, NOT on your live machine. I haven't tried it, but I think this API will work fine in a VM.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

AMFI disabling is the solution I'm looking for, thank you

CoreHID HidVirtualDevice Returning Nil
 
 
Q