public static unsafe HIDDeviceDescriptor ReadHIDDeviceDescriptor(int deviceId, ref InputDeviceDescription deviceDescription, IInputRuntime runtime) { if (deviceDescription.interfaceName != kHIDInterface) { throw new ArgumentException( string.Format("Device '{0}' is not a HID", deviceDescription)); } // See if we have to request a HID descriptor from the device. // We support having the descriptor directly as a JSON string in the `capabilities` // field of the device description. var needToRequestDescriptor = true; var hidDeviceDescriptor = new HIDDeviceDescriptor(); if (!string.IsNullOrEmpty(deviceDescription.capabilities)) { try { hidDeviceDescriptor = HIDDeviceDescriptor.FromJson(deviceDescription.capabilities); // If there's elements in the descriptor, we're good with the descriptor. If there aren't, // we go and ask the device for a full descriptor. if (hidDeviceDescriptor.elements != null && hidDeviceDescriptor.elements.Length > 0) { needToRequestDescriptor = false; } } catch (Exception exception) { Debug.LogError(string.Format("Could not parse HID descriptor of device '{0}'", deviceDescription)); Debug.LogException(exception); } } ////REVIEW: we *could* switch to a single path here that supports *only* parsed descriptors but it'd //// mean having to switch *every* platform supporting HID to the hack we currently have to do //// on Windows // Request descriptor, if necessary. if (needToRequestDescriptor) { // If the device has no assigned ID yet, we can't perform IOCTLs on the // device so no way to get a report descriptor. if (deviceId == kInvalidDeviceId) { return(new HIDDeviceDescriptor()); } // Try to get the size of the HID descriptor from the device. var sizeOfDescriptorCommand = new InputDeviceCommand(QueryHIDReportDescriptorSizeDeviceCommandType); var sizeOfDescriptorInBytes = runtime.DeviceCommand(deviceId, ref sizeOfDescriptorCommand); if (sizeOfDescriptorInBytes > 0) { // Now try to fetch the HID descriptor. using (var buffer = InputDeviceCommand.AllocateNative(QueryHIDReportDescriptorDeviceCommandType, (int)sizeOfDescriptorInBytes)) { var commandPtr = (InputDeviceCommand *)NativeArrayUnsafeUtility.GetUnsafePtr(buffer); if (runtime.DeviceCommand(deviceId, ref *commandPtr) != sizeOfDescriptorInBytes) { return(new HIDDeviceDescriptor()); } // Try to parse the HID report descriptor. if (!HIDParser.ParseReportDescriptor((byte *)commandPtr->payloadPtr, (int)sizeOfDescriptorInBytes, ref hidDeviceDescriptor)) { return(new HIDDeviceDescriptor()); } } // Update the descriptor on the device with the information we got. deviceDescription.capabilities = hidDeviceDescriptor.ToJson(); } else { // The device may not support binary descriptors but may support parsed descriptors so // try the IOCTL for parsed descriptors next. // // This path exists pretty much only for the sake of Windows where it is not possible to get // unparsed/binary descriptors from the device (and where getting element offsets is only possible // with some dirty hacks we're performing in the native runtime). const int kMaxDescriptorBufferSize = 2 * 1024 * 1024; ////TODO: switch to larger buffer based on return code if request fails using (var buffer = InputDeviceCommand.AllocateNative(QueryHIDParsedReportDescriptorDeviceCommandType, kMaxDescriptorBufferSize)) { var commandPtr = (InputDeviceCommand *)NativeArrayUnsafeUtility.GetUnsafePtr(buffer); var utf8Length = runtime.DeviceCommand(deviceId, ref *commandPtr); if (utf8Length < 0) { return(new HIDDeviceDescriptor()); } // Turn UTF-8 buffer into string. ////TODO: is there a way to not have to copy here? var utf8 = new byte[utf8Length]; fixed(byte *utf8Ptr = utf8) { UnsafeUtility.MemCpy(utf8Ptr, commandPtr->payloadPtr, utf8Length); } var descriptorJson = Encoding.UTF8.GetString(utf8, 0, (int)utf8Length); // Try to parse the HID report descriptor. try { hidDeviceDescriptor = HIDDeviceDescriptor.FromJson(descriptorJson); } catch (Exception exception) { Debug.LogError(string.Format("Could not parse HID descriptor of device '{0}'", deviceDescription)); Debug.LogException(exception); return(new HIDDeviceDescriptor()); } // Update the descriptor on the device with the information we got. deviceDescription.capabilities = descriptorJson; } } } return(hidDeviceDescriptor); }
// This is the workhorse for figuring out fallback options for HIDs attached to the system. // If the system cannot find a more specific layout for a given HID, this method will try // to produce a layout builder on the fly based on the HID descriptor received from // the device. internal static unsafe string OnFindControlLayoutForDevice(int deviceId, ref InputDeviceDescription description, string matchedLayout, IInputRuntime runtime) { // If the system found a matching layout, there's nothing for us to do. if (!string.IsNullOrEmpty(matchedLayout)) { return(null); } // If the device isn't a HID, we're not interested. if (description.interfaceName != kHIDInterface) { return(null); } // See if we have to request a HID descriptor from the device. // We support having the descriptor directly as a JSON string in the `capabilities` // field of the device description. var needToRequestDescriptor = true; var hidDeviceDescriptor = new HIDDeviceDescriptor(); if (!string.IsNullOrEmpty(description.capabilities)) { try { hidDeviceDescriptor = HIDDeviceDescriptor.FromJson(description.capabilities); // If there's elements in the descriptor, we're good with the descriptor. If there aren't, // we go and ask the device for a full descriptor. if (hidDeviceDescriptor.elements != null && hidDeviceDescriptor.elements.Length > 0) { needToRequestDescriptor = false; } } catch (Exception exception) { Debug.Log(string.Format("Could not parse HID descriptor (exception: {0})", exception)); } } ////REVIEW: we *could* switch to a single path here that supports *only* parsed descriptors but it'd //// mean having to switch *every* platform supporting HID to the hack we currently have to do //// on Windows // Request descriptor, if necessary. if (needToRequestDescriptor) { // If the device has no assigned ID yet, we can't perform IOCTLs on the // device so no way to get a report descriptor. if (deviceId == kInvalidDeviceId) { return(null); } // Try to get the size of the HID descriptor from the device. var sizeOfDescriptorCommand = new InputDeviceCommand(QueryHIDReportDescriptorSizeDeviceCommandType); var sizeOfDescriptorInBytes = runtime.DeviceCommand(deviceId, ref sizeOfDescriptorCommand); if (sizeOfDescriptorInBytes > 0) { // Now try to fetch the HID descriptor. using (var buffer = InputDeviceCommand.AllocateNative(QueryHIDReportDescriptorDeviceCommandType, (int)sizeOfDescriptorInBytes)) { var commandPtr = (InputDeviceCommand *)NativeArrayUnsafeUtility.GetUnsafePtr(buffer); if (runtime.DeviceCommand(deviceId, ref *commandPtr) != sizeOfDescriptorInBytes) { return(null); } // Try to parse the HID report descriptor. if (!HIDParser.ParseReportDescriptor((byte *)commandPtr->payloadPtr, (int)sizeOfDescriptorInBytes, ref hidDeviceDescriptor)) { return(null); } } // Update the descriptor on the device with the information we got. description.capabilities = hidDeviceDescriptor.ToJson(); } else { // The device may not support binary descriptors but may support parsed descriptors so // try the IOCTL for parsed descriptors next. // // This path exists pretty much only for the sake of Windows where it is not possible to get // unparsed/binary descriptors from the device (and where getting element offsets is only possible // with some dirty hacks we're performing in the native runtime). const int kMaxDescriptorBufferSize = 2 * 1024 * 1024; ////TODO: switch to larger buffer based on return code if request fails using (var buffer = InputDeviceCommand.AllocateNative(QueryHIDParsedReportDescriptorDeviceCommandType, kMaxDescriptorBufferSize)) { var commandPtr = (InputDeviceCommand *)NativeArrayUnsafeUtility.GetUnsafePtr(buffer); var utf8Length = runtime.DeviceCommand(deviceId, ref *commandPtr); if (utf8Length < 0) { return(null); } // Turn UTF-8 buffer into string. ////TODO: is there a way to not have to copy here? var utf8 = new byte[utf8Length]; fixed(byte *utf8Ptr = utf8) { UnsafeUtility.MemCpy(utf8Ptr, commandPtr->payloadPtr, utf8Length); } var descriptorJson = Encoding.UTF8.GetString(utf8, 0, (int)utf8Length); // Try to parse the HID report descriptor. try { hidDeviceDescriptor = HIDDeviceDescriptor.FromJson(descriptorJson); } catch (Exception exception) { Debug.Log(string.Format("Could not parse HID descriptor JSON returned from runtime (exception: {0})", exception)); return(null); } // Update the descriptor on the device with the information we got. description.capabilities = descriptorJson; } } } // Determine if there's any usable elements on the device. var hasUsableElements = false; if (hidDeviceDescriptor.elements != null) { foreach (var element in hidDeviceDescriptor.elements) { if (element.DetermineLayout() != null) { hasUsableElements = true; break; } } } // If not, there's nothing we can do with the device. if (!hasUsableElements) { return(null); } // Determine base layout. var baseLayout = "HID"; if (hidDeviceDescriptor.usagePage == UsagePage.GenericDesktop) { /* * ////TODO: there's some work to be done to make the HID *actually* compatible with these devices * if (hidDeviceDescriptor.usage == (int)GenericDesktop.Joystick) * baseLayout = "Joystick"; * else if (hidDeviceDescriptor.usage == (int)GenericDesktop.Gamepad) * baseLayout = "Gamepad"; * else if (hidDeviceDescriptor.usage == (int)GenericDesktop.Mouse) * baseLayout = "Mouse"; * else if (hidDeviceDescriptor.usage == (int)GenericDesktop.Pointer) * baseLayout = "Pointer"; * else if (hidDeviceDescriptor.usage == (int)GenericDesktop.Keyboard) * baseLayout = "Keyboard"; */ } ////TODO: match HID layouts by vendor and product ID ////REVIEW: this probably works fine for most products out there but I'm not sure it works reliably for all cases // Come up with a unique template name. HIDs are required to have product and vendor IDs. // We go with the string versions if we have them and with the numeric versions if we don't. string layoutName; if (!string.IsNullOrEmpty(description.product) && !string.IsNullOrEmpty(description.manufacturer)) { layoutName = string.Format("{0}::{1} {2}", kHIDNamespace, description.manufacturer, description.product); } else { // Sanity check to make sure we really have the data we expect. if (hidDeviceDescriptor.vendorId == 0) { return(null); } layoutName = string.Format("{0}::{1:X}-{2:X}", kHIDNamespace, hidDeviceDescriptor.vendorId, hidDeviceDescriptor.productId); } // Register layout builder that will turn the HID descriptor into an // InputControlLayout instance. var layout = new HIDLayoutBuilder { hidDescriptor = hidDeviceDescriptor }; InputSystem.RegisterControlLayoutBuilder(() => layout.Build(), layoutName, baseLayout, InputDeviceMatcher.FromDeviceDescription(description)); return(layoutName); }