Example #1
0
        // 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 string OnFindLayoutForDevice(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);
            }

            // Read HID descriptor.
            var hidDeviceDescriptor = ReadHIDDeviceDescriptor(deviceId, ref description, runtime);

            // 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.RegisterLayoutBuilder(() => layout.Build(),
                                              layoutName, baseLayout, InputDeviceMatcher.FromDeviceDescription(description));

            return(layoutName);
        }
Example #2
0
        // 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);
        }