Пример #1
0
                public static DeviceInfo FromDescription(InputDeviceDescription description, bool native = false, string layout = null)
                {
                    string product;

                    if (!string.IsNullOrEmpty(description.product) && !string.IsNullOrEmpty(description.manufacturer))
                    {
                        product = $"{description.manufacturer} {description.product}";
                    }
                    else if (!string.IsNullOrEmpty(description.product))
                    {
                        product = description.product;
                    }
                    else
                    {
                        product = description.manufacturer;
                    }

                    if (string.IsNullOrEmpty(layout))
                    {
                        layout = description.deviceClass;
                    }

                    return(new DeviceInfo
                    {
                        layout = layout,
                        @interface = description.interfaceName,
                        product = product,
                        native = native
                    });
                }
Пример #2
0
        private static bool IsIgnoredDevice(InputDeviceDescription description)
        {
            #if UNITY_STANDALONE_WIN
            #endif

            return(false);
        }
Пример #3
0
        internal static string OnFindControlLayoutForDevice(int deviceId, ref InputDeviceDescription description,
                                                            string matchedTemplate, IInputRuntime runtime)
        {
            if (description.interfaceName != "Android" || string.IsNullOrEmpty(description.capabilities))
            {
                return(null);
            }

            ////TODO: these should just be Controller and Sensor; the interface is already Android
            switch (description.deviceClass)
            {
            case "AndroidGameController":
            {
                var caps = AndroidDeviceCapabilities.FromJson(description.capabilities);
                if ((caps.inputSources & AndroidInputSource.Gamepad) == AndroidInputSource.Gamepad)
                {
                    if (caps.motionAxes != null)
                    {
                        if (caps.motionAxes.Contains(AndroidAxis.HatX) &&
                            caps.motionAxes.Contains(AndroidAxis.HatY))
                        {
                            return("AndroidGamepadWithDpadAxes");
                        }
                    }
                    return("AndroidGamepadWithDpadButtons");
                }

                return("AndroidJoystick");
            }

            default:
                return(null);
            }
        }
Пример #4
0
        public static void CreateOrShowExisting(int deviceId, InputDeviceDescription deviceDescription)
        {
            // See if we have an existing window for the device and if so pop it
            // in front.
            if (s_OpenWindows != null)
            {
                for (var i = 0; i < s_OpenWindows.Count; ++i)
                {
                    var existingWindow = s_OpenWindows[i];
                    if (existingWindow.m_DeviceId == deviceId)
                    {
                        existingWindow.Show();
                        existingWindow.Focus();
                        return;
                    }
                }
            }

            // No, so create a new one.
            var window = CreateInstance <HIDDescriptorWindow>();

            window.InitializeWith(deviceId, deviceDescription);
            window.minSize = new Vector2(270, 200);
            window.Show();
            window.titleContent = new GUIContent("HID Descriptor");
        }
Пример #5
0
        internal static string OnFindLayoutForDevice(ref InputDeviceDescription description,
                                                     string matchedLayout, InputDeviceExecuteCommandDelegate executeCommandDelegate)
        {
            // If the device isn't a WebGL device, we're not interested.
            if (string.Compare(description.interfaceName, InterfaceName, StringComparison.InvariantCultureIgnoreCase) != 0)
            {
                return(null);
            }

            // If it was matched by the standard mapping, we don't need to fall back to generating a layout.
            if (!string.IsNullOrEmpty(matchedLayout) && matchedLayout != "Gamepad")
            {
                return(null);
            }

            var deviceMatcher = InputDeviceMatcher.FromDeviceDescription(description);

            var layout = new WebGLLayoutBuilder {
                capabilities = WebGLDeviceCapabilities.FromJson(description.capabilities)
            };

            InputSystem.RegisterLayoutBuilder(() => layout.Build(),
                                              description.product, "Joystick", deviceMatcher);

            return(description.product);
        }
Пример #6
0
        internal static string OnFindLayoutForDevice(int deviceId, ref InputDeviceDescription description, string matchedLayout, IInputRuntime runtime)
        {
            // If the device isn't a XRInput, we're not interested.
            if (description.interfaceName != SDLSupport.kXRInterfaceCurrent)
            {
                return(null);
            }

            // If the description doesn't come with a XR SDK descriptor, we're not
            // interested either.
            if (string.IsNullOrEmpty(description.capabilities))
            {
                return(null);
            }

            // Try to parse the SDL descriptor.
            SDLDeviceDescriptor deviceDescriptor;

            try
            {
                deviceDescriptor = SDLDeviceDescriptor.FromJson(description.capabilities);
            }
            catch (Exception)
            {
                return(null);
            }

            if (deviceDescriptor == null)
            {
                return(null);
            }

            if (string.IsNullOrEmpty(matchedLayout))
            {
                //matchedLayout = "Joystick";
            }

            string layoutName = null;

            if (string.IsNullOrEmpty(description.manufacturer))
            {
                layoutName = string.Format("{0}::{1}", SanitizeName(description.interfaceName),
                                           SanitizeName(description.product));
            }
            else
            {
                layoutName = string.Format("{0}::{1}::{2}", SanitizeName(description.interfaceName), SanitizeName(description.manufacturer), SanitizeName(description.product));
            }

            var layout = new SDLLayoutBuilder {
                descriptor = deviceDescriptor, parentLayout = matchedLayout
            };

            InputSystem.RegisterLayoutBuilder(() => layout.Build(), layoutName, matchedLayout);

            return(layoutName);
        }
Пример #7
0
        private string OnFindLayout(int deviceId, ref InputDeviceDescription description, string matchedLayout,
            IInputRuntime runtime)
        {
            // If there's no matched layout, there's a chance this device will go in
            // the unsupported list. There's no direct notification for that so we
            // pre-emptively trigger a refresh.
            if (string.IsNullOrEmpty(matchedLayout))
                Refresh();

            return null;
        }
Пример #8
0
        internal static string OnFindLayoutForDevice(int deviceId, ref InputDeviceDescription description,
                                                     string matchedTemplate, IInputRuntime runtime)
        {
            if (description.interfaceName != "Android" || string.IsNullOrEmpty(description.capabilities))
            {
                return(null);
            }

            ////TODO: these should just be Controller and Sensor; the interface is already Android
            switch (description.deviceClass)
            {
            case "AndroidGameController":
            {
                var caps = AndroidDeviceCapabilities.FromJson(description.capabilities);

                // Note: Gamepads have both AndroidInputSource.Gamepad and AndroidInputSource.Joystick in input source, while
                //       Joysticks don't have AndroidInputSource.Gamepad in their input source
                if ((caps.inputSources & AndroidInputSource.Gamepad) != AndroidInputSource.Gamepad)
                {
                    return("AndroidJoystick");
                }

                if (caps.motionAxes == null)
                {
                    return("AndroidGamepadWithDpadButtons");
                }

                // Vendor Ids, Product Ids can be found here http://www.linux-usb.org/usb.ids
                const int kVendorMicrosoft = 0x045e;

                if (caps.vendorId == kVendorMicrosoft &&
                    caps.motionAxes != null &&
                    caps.motionAxes.Contains(AndroidAxis.Rx) &&
                    caps.motionAxes.Contains(AndroidAxis.Ry) &&
                    caps.motionAxes.Contains(AndroidAxis.HatX) &&
                    caps.motionAxes.Contains(AndroidAxis.HatY))
                {
                    return("AndroidGamepadXboxController");
                }

                // Fallback to generic gamepads
                if (caps.motionAxes.Contains(AndroidAxis.HatX) &&
                    caps.motionAxes.Contains(AndroidAxis.HatY))
                {
                    return("AndroidGamepadWithDpadAxes");
                }

                return("AndroidGamepadWithDpadButtons");
            }

            default:
                return(null);
            }
        }
        static void SaveDeviceDescription(int deviceId, InputDeviceDescription deviceDescription)
        {
            var hidDescriptor = HID.ReadHIDDeviceDescriptor(ref deviceDescription, (ref InputDeviceCommand command) => InputRuntime.s_Instance.DeviceCommand(deviceId, ref command));
            var json          = EditorJsonUtility.ToJson(hidDescriptor, true);

            var path     = $"Assets/{deviceDescription}.json";
            var fullPath = System.IO.Path.GetFullPath(path).Replace("\\", "/");

            Debug.Log($"Saved ({deviceDescription}) (at {path}:1)\n\n{json}");
            System.IO.File.WriteAllText(path, json);
        }
Пример #10
0
        public void SetDescription()
        {
            var device = InputSystem.AddDevice <Mouse>();
            InputDeviceDescription empty;
            InputDeviceDescription origin = device.description;

            device.SetDescription(empty);
            Assert.That(device.description, Is.EqualTo(empty));
            device.SetDescription(origin);
            Assert.That(device.description, Is.EqualTo(origin));
        }
Пример #11
0
        private string OnFindLayout(ref InputDeviceDescription description, string matchedLayout,
                                    InputDeviceExecuteCommandDelegate executeCommandDelegate)
        {
            // If there's no matched layout, there's a chance this device will go in
            // the unsupported list. There's no direct notification for that so we
            // preemptively trigger a refresh.
            if (string.IsNullOrEmpty(matchedLayout))
            {
                Refresh();
            }

            return(null);
        }
Пример #12
0
        public int ReportNewInputDevice(InputDeviceDescription description, int deviceId = InputDevice.InvalidDeviceId,
                                        ulong userHandle = 0, string userName = null, string userId = null)
        {
            deviceId = ReportNewInputDevice(description.ToJson(), deviceId);

            // If we have user information, automatically set up
            if (userHandle != 0)
            {
                AssociateInputDeviceWithUser(deviceId, userHandle, userName, userId);
            }

            return(deviceId);
        }
Пример #13
0
        public static HIDDeviceDescriptor ReadHIDDeviceDescriptor(InputDevice device, IInputRuntime runtime)
        {
            if (device == null)
                throw new ArgumentNullException("device");

            InputDeviceDescription deviceDescription = device.description;
            if (deviceDescription.interfaceName != kHIDInterface)
                throw new ArgumentException(
                    string.Format("Device '{0}' is not a HID (interface is '{1}')", device,
                        deviceDescription.interfaceName), "device");

            return ReadHIDDeviceDescriptor(device.id, ref deviceDescription, runtime);
        }
Пример #14
0
        internal static string OnFindLayoutForDevice(ref InputDeviceDescription description, string matchedLayout,
                                                     InputDeviceExecuteCommandDelegate executeCommandDelegate)
        {
            if (description.interfaceName != LinuxSupport.kInterfaceName)
            {
                return(null);
            }

            if (string.IsNullOrEmpty(description.capabilities))
            {
                return(null);
            }

            // Try to parse the SDL descriptor.
            SDLDeviceDescriptor deviceDescriptor;

            try
            {
                deviceDescriptor = SDLDeviceDescriptor.FromJson(description.capabilities);
            }
            catch (Exception exception)
            {
                Debug.LogError($"{exception} while trying to parse descriptor for SDL device: {description.capabilities}");
                return(null);
            }

            if (deviceDescriptor == null)
            {
                return(null);
            }

            string layoutName;

            if (string.IsNullOrEmpty(description.manufacturer))
            {
                layoutName = $"{SanitizeName(description.interfaceName)}::{SanitizeName(description.product)}";
            }
            else
            {
                layoutName =
                    $"{SanitizeName(description.interfaceName)}::{SanitizeName(description.manufacturer)}::{SanitizeName(description.product)}";
            }

            var layout = new SDLLayoutBuilder {
                m_Descriptor = deviceDescriptor, m_ParentLayout = matchedLayout
            };

            InputSystem.RegisterLayoutBuilder(() => layout.Build(), layoutName, matchedLayout);

            return(layoutName);
        }
Пример #15
0
 // Get a device object bound with a specified channel.
 // Create a new device if it doesn't exist.
 MidiDevice GetChannelDevice(int channel)
 {
     if (_channels[channel] == null)
     {
         var desc = new InputDeviceDescription {
             interfaceName = "Minis",
             deviceClass   = "MIDI",
             product       = _portName + " Channel " + channel,
             capabilities  = "{\"channel\":" + channel + "}"
         };
         _channels[channel] = (MidiDevice)InputSystem.AddDevice(desc);
     }
     return(_channels[channel]);
 }
Пример #16
0
    public void Devices_CanGetSubTypeOfXInputDevice()
    {
        var capabilities = new XInputController.Capabilities
        {
            subType = XInputController.DeviceSubType.ArcadePad
        };
        var description = new InputDeviceDescription
        {
            interfaceName = "XInput",
            capabilities  = JsonUtility.ToJson(capabilities)
        };

        var device = (XInputController)InputSystem.AddDevice(description);

        Assert.That(device.subType, Is.EqualTo(XInputController.DeviceSubType.ArcadePad));
    }
Пример #17
0
        private void DrawToolbarGUI()
        {
            EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);

            if (GUILayout.Button(Contents.optionsContent, EditorStyles.toolbarDropDown))
            {
                var menu = new GenericMenu();

                menu.AddItem(Contents.addDevicesNotSupportedByProjectContent, InputEditorUserSettings.addDevicesNotSupportedByProject,
                             ToggleAddDevicesNotSupportedByProject);
                menu.AddItem(Contents.diagnosticsModeContent, InputSystem.s_Manager.m_Diagnostics != null,
                             ToggleDiagnosticMode);
                menu.AddItem(Contents.lockInputToGameViewContent, InputEditorUserSettings.lockInputToGameView,
                             ToggleLockInputToGameView);
                menu.AddItem(Contents.touchSimulationContent, InputEditorUserSettings.simulateTouch, ToggleTouchSimulation);

                // Add the inverse of "Copy Device Description" which adds a device with the description from
                // the clipboard to the system. This is most useful for debugging and makes it very easy to
                // have a first pass at device descriptions supplied by users.
                try
                {
                    var copyBuffer = EditorGUIUtility.systemCopyBuffer;
                    if (!string.IsNullOrEmpty(copyBuffer) &&
                        copyBuffer.StartsWith("{") && !InputDeviceDescription.FromJson(copyBuffer).empty)
                    {
                        menu.AddItem(Contents.pasteDeviceDescriptionAsDevice, false, () =>
                        {
                            var description = InputDeviceDescription.FromJson(copyBuffer);
                            InputSystem.AddDevice(description);
                        });
                    }
                }
                catch (ArgumentException)
                {
                    // Catch and ignore exception if buffer doesn't actually contain an InputDeviceDescription
                    // in (proper) JSON format.
                }

                menu.ShowAsContext();
            }

            DrawConnectionGUI();

            GUILayout.FlexibleSpace();
            EditorGUILayout.EndHorizontal();
        }
Пример #18
0
    public void Devices_SupportsXInputDevicesOnPlatform(string product, string manufacturer, string interfaceName, string layoutName)
    {
        var description = new InputDeviceDescription
        {
            interfaceName = interfaceName,
            product       = product,
            manufacturer  = manufacturer
        };

        InputDevice device = null;

        Assert.That(() => device = InputSystem.AddDevice(description), Throws.Nothing);

        Assert.That(InputSystem.GetControls(string.Format("/<{0}>", layoutName)), Has.Exactly(1).SameAs(device));
        Assert.That(device.name, Is.EqualTo(layoutName));
        Assert.That(device.description.manufacturer, Is.EqualTo(manufacturer));
        Assert.That(device.description.interfaceName, Is.EqualTo(interfaceName));
        Assert.That(device.description.product, Is.EqualTo(product));
    }
Пример #19
0
        private void InitializeWith(int deviceId, InputDeviceDescription deviceDescription)
        {
            m_DeviceId          = deviceId;
            m_DeviceDescription = deviceDescription;
            m_Initialized       = true;

            // Set up tree view for HID desctiptor.
            var hidDescriptor = HID.ReadHIDDeviceDescriptor(deviceId, ref m_DeviceDescription, InputRuntime.s_Instance);

            if (m_TreeViewState == null)
            {
                m_TreeViewState = new TreeViewState();
            }
            m_TreeView = new HIDDescriptorTreeView(m_TreeViewState, hidDescriptor);
            m_TreeView.SetExpanded(1, true);

            m_Label = new GUIContent(string.Format("HID Descriptor for '{0} {1}'", deviceDescription.manufacturer,
                                                   deviceDescription.product));
        }
Пример #20
0
        private void InitializeWith(int deviceId, InputDeviceDescription deviceDescription)
        {
            m_DeviceId          = deviceId;
            m_DeviceDescription = deviceDescription;
            m_Initialized       = true;

            // Set up tree view for HID descriptor.
            var hidDescriptor = HID.ReadHIDDeviceDescriptor(ref m_DeviceDescription,
                                                            (ref InputDeviceCommand command) => InputRuntime.s_Instance.DeviceCommand(m_DeviceId, ref command));

            if (m_TreeViewState == null)
            {
                m_TreeViewState = new TreeViewState();
            }
            m_TreeView = new HIDDescriptorTreeView(m_TreeViewState, hidDescriptor);
            m_TreeView.SetExpanded(1, true);

            m_Label = new GUIContent(
                $"HID Descriptor for '{deviceDescription.manufacturer} {deviceDescription.product}'");
        }
Пример #21
0
    public void Devices_DevicesNotAllowedByShouldCreateHIDAreSkipped()
    {
        var hidDescriptor = new HID.HIDDeviceDescriptor
        {
            usage     = 1234,
            usagePage = (HID.UsagePage) 5678,
            // need at least one valid element for the device not to be ignored
            elements = new[]
            {
                new HID.HIDElementDescriptor {
                    usage = (int)HID.GenericDesktop.X, usagePage = HID.UsagePage.GenericDesktop, reportType = HID.HIDReportType.Input, reportId = 1, reportOffsetInBits = 0, reportSizeInBits = 16
                },
            }
        };

        var descriptionJson = new InputDeviceDescription
        {
            interfaceName = HID.kHIDInterface,
            manufacturer  = "TestVendor",
            product       = "TestHID",
            capabilities  = hidDescriptor.ToJson()
        }.ToJson();
        var deviceId = runtime.AllocateDeviceId();

        runtime.ReportNewInputDevice(descriptionJson, deviceId);
        InputSystem.Update();

        Assert.That(InputSystem.devices, Has.Count.EqualTo(0));
        Assert.That(InputSystem.GetDeviceById(deviceId), Is.Null);

        HIDSupport.shouldCreateHID += descriptor =>
                                      descriptor.usagePage == (HID.UsagePage) 5678 && descriptor.usage == 1234
            ? true
            : (bool?)null;

        runtime.ReportNewInputDevice(descriptionJson, deviceId);
        InputSystem.Update();

        Assert.That(InputSystem.devices, Has.Count.EqualTo(1));
        Assert.That(InputSystem.GetDeviceById(deviceId), Is.Not.Null);
    }
Пример #22
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);
        }
Пример #23
0
 public static void SetDescription(this InputDevice device, InputDeviceDescription value)
 {
     fieldInfoDescription.SetValue(device, value);
 }
Пример #24
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);
        }
Пример #25
0
        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);
        }
Пример #26
0
        internal static string OnFindTemplateForDevice(InputDeviceDescription description, string matchedTemplate)
        {
            // If the system found a matching template, there's nothing for us to do.
            if (!string.IsNullOrEmpty(matchedTemplate))
            {
                return(null);
            }

            // If the device isn't a HID, we're not interested.
            if (description.interfaceName != kHIDInterface)
            {
                return(null);
            }

            // We require *some* product name to be supplied.
            if (string.IsNullOrEmpty(description.product))
            {
                return(null);
            }

            // If the description doesn't come with a HID descriptor, we're not
            // interested either.
            if (string.IsNullOrEmpty(description.capabilities))
            {
                return(null);
            }

            // Try to parse the HID descriptor.
            HIDDeviceDescriptor hidDeviceDescriptor;

            try
            {
                hidDeviceDescriptor = HIDDeviceDescriptor.FromJson(description.capabilities);
            }
            catch (Exception exception)
            {
                Debug.Log(string.Format("Could not parse HID descriptor (exception: {0}", exception));
                return(null);
            }

            // 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.DetermineTemplate() != null)
                    {
                        hasUsableElements = true;
                        break;
                    }
                }
            }

            if (!hasUsableElements)
            {
                return(null);
            }

            // Determine base template.
            var baseTemplate = "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)
                 *  baseTemplate = "Joystick";
                 * else if (hidDeviceDescriptor.usage == (int)GenericDesktop.Gamepad)
                 *  baseTemplate = "Gamepad";
                 * else if (hidDeviceDescriptor.usage == (int)GenericDesktop.Mouse)
                 *  baseTemplate = "Mouse";
                 * else if (hidDeviceDescriptor.usage == (int)GenericDesktop.Pointer)
                 *  baseTemplate = "Pointer";
                 * else if (hidDeviceDescriptor.usage == (int)GenericDesktop.Keyboard)
                 *  baseTemplate = "Keyboard";
                 */
            }

            // We don't want the capabilities field in the description to be matched
            // when the input system is looking for matching templates so null it out.
            description.capabilities = null;

            ////TODO: make sure we don't produce name conflicts on the template name

            // Register template constructor that will turn the HID descriptor into an
            // InputTemplate instance.
            var templateName = string.Format("{0}::{1}", kHIDNamespace, description.product);
            var template     = new HIDTemplate {
                descriptor = hidDeviceDescriptor
            };

            InputSystem.RegisterTemplateConstructor(() => template.Build(), templateName, baseTemplate, description);

            return(templateName);
        }
Пример #27
0
        internal static string OnFindLayoutForDevice(ref InputDeviceDescription description, string matchedLayout,
                                                     InputDeviceExecuteCommandDelegate executeCommandDelegate)
        {
            // If the device isn't a XRInput, we're not interested.
            if (description.interfaceName != XRUtilities.InterfaceCurrent && description.interfaceName != XRUtilities.InterfaceV1)
            {
                return(null);
            }

            // If the description doesn't come with a XR SDK descriptor, we're not
            // interested either.
            if (string.IsNullOrEmpty(description.capabilities))
            {
                return(null);
            }

            // Try to parse the XR descriptor.
            XRDeviceDescriptor deviceDescriptor;

            try
            {
                deviceDescriptor = XRDeviceDescriptor.FromJson(description.capabilities);
            }
            catch (Exception)
            {
                return(null);
            }

            if (deviceDescriptor == null)
            {
                return(null);
            }

            if (string.IsNullOrEmpty(matchedLayout))
            {
#if UNITY_2019_3_OR_NEWER
                const InputDeviceCharacteristics controllerCharacteristics = InputDeviceCharacteristics.HeldInHand | InputDeviceCharacteristics.Controller;
                if ((deviceDescriptor.characteristics & InputDeviceCharacteristics.HeadMounted) != 0)
                {
                    matchedLayout = "XRHMD";
                }
                else if ((deviceDescriptor.characteristics & controllerCharacteristics) == controllerCharacteristics)
                {
                    matchedLayout = "XRController";
                }
#else //UNITY_2019_3_OR_NEWER
                if (deviceDescriptor.deviceRole == InputDeviceRole.LeftHanded || deviceDescriptor.deviceRole == InputDeviceRole.RightHanded)
                {
                    matchedLayout = "XRController";
                }
                else if (deviceDescriptor.deviceRole == InputDeviceRole.Generic)
                {
                    matchedLayout = "XRHMD";
                }
#endif //UNITY_2019_3_OR_NEWER
            }

            string layoutName;
            if (string.IsNullOrEmpty(description.manufacturer))
            {
                layoutName = $"{SanitizeName(description.interfaceName)}::{SanitizeName(description.product)}";
            }
            else
            {
                layoutName =
                    $"{SanitizeName(description.interfaceName)}::{SanitizeName(description.manufacturer)}::{SanitizeName(description.product)}";
            }

            var layout = new XRLayoutBuilder {
                descriptor = deviceDescriptor, parentLayout = matchedLayout, interfaceName = description.interfaceName
            };
            InputSystem.RegisterLayoutBuilder(() => layout.Build(), layoutName, matchedLayout);

            return(layoutName);
        }
Пример #28
0
        internal static string OnFindLayoutForDevice(int deviceId, ref InputDeviceDescription description, string matchedLayout, IInputRuntime runtime)
        {
            // If the device isn't a XRInput, we're not interested.
            if (description.interfaceName != XRUtilities.kXRInterfaceCurrent && description.interfaceName != XRUtilities.kXRInterfaceV1)
            {
                return(null);
            }

            // If the description doesn't come with a XR SDK descriptor, we're not
            // interested either.
            if (string.IsNullOrEmpty(description.capabilities))
            {
                return(null);
            }

            // Try to parse the XR descriptor.
            XRDeviceDescriptor deviceDescriptor;

            try
            {
                deviceDescriptor = XRDeviceDescriptor.FromJson(description.capabilities);
            }
            catch (Exception)
            {
                return(null);
            }

            if (deviceDescriptor == null)
            {
                return(null);
            }

            if (string.IsNullOrEmpty(matchedLayout))
            {
                if (deviceDescriptor.deviceRole == DeviceRole.LeftHanded || deviceDescriptor.deviceRole == DeviceRole.RightHanded)
                {
                    matchedLayout = "XRController";
                }
                else if (deviceDescriptor.deviceRole == DeviceRole.Generic)
                {
                    matchedLayout = "XRHMD";
                }
            }

            string layoutName = null;

            if (string.IsNullOrEmpty(description.manufacturer))
            {
                layoutName = string.Format("{0}::{1}", SanitizeName(description.interfaceName),
                                           SanitizeName(description.product));
            }
            else
            {
                layoutName = string.Format("{0}::{1}::{2}", SanitizeName(description.interfaceName), SanitizeName(description.manufacturer), SanitizeName(description.product));
            }

            var layout = new XRLayoutBuilder {
                descriptor = deviceDescriptor, parentLayout = matchedLayout, interfaceName = description.interfaceName
            };

            InputSystem.RegisterLayoutBuilder(() => layout.Build(), layoutName, matchedLayout);

            return(layoutName);
        }
Пример #29
0
    public void Devices_CanCreateGenericHID_FromDeviceWithBinaryReportDescriptor()
    {
        // This is several snippets from the PS4 controller's HID report descriptor
        // pasted together.
        var reportDescriptor = new byte[]
        {
            0x05, 0x01,       // Usage Page (Generic Desktop)
            0x09, 0x05,       // Usage (Gamepad)
            0xA1, 0x01,       // Collection (Application)
            0x85, 0x01,       // Report ID (1)
            0x09, 0x30,       // Usage (X)
            0x09, 0x31,       // Usage (Y)
            0x09, 0x32,       // Usage (Z)
            0x09, 0x35,       // Usage (Rz)
            0x15, 0x00,       // Logical Minimum (0)
            0x26, 0xFF, 0x00, // Logical Maximum (255)
            0x75, 0x08,       // Report Size (8)
            0x95, 0x04,       // Report Count (4)
            0x81, 0x02,       // Input (Data, Var, Abs, NWrp, Lin, Pref, NNul, Bit)
            0x09, 0x39,       // Usage (Hat Switch)
            0x15, 0x00,       // Logical Minimum (0)
            0x25, 0x07,       // Logical Maximum (7)
            0x35, 0x00,       // Physical Maximum (0)
            0x46, 0x3B, 0x01, // Physical Maximum (315)
            0x65, 0x14,       // Unit (Eng Rot: Degree)
            0x75, 0x04,       // Report Size (4)
            0x95, 0x01,       // Report Count (1)
            0x81, 0x42,       // Input (Data, Var, Abs, NWrp, Lin, Pref, Null, Bit)
            0x65, 0x00,       // Unit (None)
            0x05, 0x09,       // Usage Page (Button)
            0x19, 0x01,       // Usage Minimum (Button 1)
            0x29, 0x0E,       // Usage Maximum (Button 14)
            0x15, 0x00,       // Logical Minimum (0)
            0x25, 0x01,       // Logical Maximum (1)
            0x75, 0x01,       // Report Size (1)
            0x95, 0x0E,       // Report Count (14)
            0x81, 0x02,       // Input (Data, Var, Abs, NWrp, Lin, Pref, NNul, Bit)
            0x06, 0x00, 0xFF, // Usage Page (Vendor-Defined 1)
            0x09, 0x21,       // Usage (Vendor-Defined 33)
            0x95, 0x36,       // Report Count (54)
            0x81, 0x02,       // Input (Data, Var, Abs, NWrp, Lin, Pref, NNul, Bit)
            0x85, 0x05,       // Report ID (5)
            0x09, 0x22,       // Usage (Vendor-Defined 34)
            0x95, 0x1F,       // Report Count (31)
            0x91, 0x02,       // Output (Data, Var, Abs, NWrp, Lin, Pref, NNul, NVol, Bit)
            0xC0,             // End Collection
        };

        const int kNumElements = 4 + 1 + 14 + 54 + 31;

        // The HID report descriptor is fetched from the device via an IOCTL.
        var deviceId = runtime.AllocateDeviceId();

        unsafe
        {
            runtime.SetDeviceCommandCallback(deviceId,
                                             (id, commandPtr) =>
            {
                if (commandPtr->type == HID.QueryHIDReportDescriptorSizeDeviceCommandType)
                {
                    return(reportDescriptor.Length);
                }

                if (commandPtr->type == HID.QueryHIDReportDescriptorDeviceCommandType &&
                    commandPtr->payloadSizeInBytes >= reportDescriptor.Length)
                {
                    fixed(byte *ptr = reportDescriptor)
                    {
                        UnsafeUtility.MemCpy(commandPtr->payloadPtr, ptr, reportDescriptor.Length);
                        return(reportDescriptor.Length);
                    }
                }

                return(InputDeviceCommand.kGenericFailure);
            });
        }
        // Report device.
        runtime.ReportNewInputDevice(
            new InputDeviceDescription
        {
            interfaceName = HID.kHIDInterface,
            manufacturer  = "TestVendor",
            product       = "TestHID",
            capabilities  = new HID.HIDDeviceDescriptor
            {
                vendorId  = 0x123,
                productId = 0x234
            }.ToJson()
        }.ToJson(), deviceId);
        InputSystem.Update();

        // Grab device.
        var device = (Joystick)InputSystem.GetDeviceById(deviceId);

        Assert.That(device, Is.Not.Null);
        Assert.That(device, Is.TypeOf <Joystick>());

        InputDeviceDescription deviceDescription = device.description;

        Assert.That(deviceDescription.interfaceName, Is.EqualTo(HID.kHIDInterface));
        HID.HIDDeviceDescriptor hidDescriptor = HID.ReadHIDDeviceDescriptor(device, runtime);
        // Check HID descriptor.
        Assert.That(hidDescriptor.vendorId, Is.EqualTo(0x123));
        Assert.That(hidDescriptor.productId, Is.EqualTo(0x234));
        Assert.That(hidDescriptor.usagePage, Is.EqualTo(HID.UsagePage.GenericDesktop));
        Assert.That(hidDescriptor.usage, Is.EqualTo((int)HID.GenericDesktop.Gamepad));
        Assert.That(hidDescriptor.elements.Length, Is.EqualTo(kNumElements));

        Assert.That(hidDescriptor.elements[0].usagePage, Is.EqualTo(HID.UsagePage.GenericDesktop));
        Assert.That(hidDescriptor.elements[0].usage, Is.EqualTo((int)HID.GenericDesktop.X));
        Assert.That(hidDescriptor.elements[0].reportId, Is.EqualTo(1));
        Assert.That(hidDescriptor.elements[0].reportOffsetInBits, Is.EqualTo(8)); // Descriptor has report ID so that's the first thing in reports.
        Assert.That(hidDescriptor.elements[0].reportSizeInBits, Is.EqualTo(8));
        Assert.That(hidDescriptor.elements[0].logicalMin, Is.EqualTo(0));
        Assert.That(hidDescriptor.elements[0].logicalMax, Is.EqualTo(255));

        Assert.That(hidDescriptor.elements[1].usagePage, Is.EqualTo(HID.UsagePage.GenericDesktop));
        Assert.That(hidDescriptor.elements[1].usage, Is.EqualTo((int)HID.GenericDesktop.Y));
        Assert.That(hidDescriptor.elements[1].reportId, Is.EqualTo(1));
        Assert.That(hidDescriptor.elements[1].reportOffsetInBits, Is.EqualTo(16));
        Assert.That(hidDescriptor.elements[1].reportSizeInBits, Is.EqualTo(8));
        Assert.That(hidDescriptor.elements[1].logicalMin, Is.EqualTo(0));
        Assert.That(hidDescriptor.elements[1].logicalMax, Is.EqualTo(255));

        Assert.That(hidDescriptor.elements[4].hasNullState, Is.True);
        Assert.That(hidDescriptor.elements[4].physicalMax, Is.EqualTo(315));
        Assert.That(hidDescriptor.elements[4].unit, Is.EqualTo(0x14));

        Assert.That(hidDescriptor.elements[5].unit, Is.Zero);

        Assert.That(hidDescriptor.elements[5].reportOffsetInBits, Is.EqualTo(5 * 8 + 4));
        Assert.That(hidDescriptor.elements[5].usagePage, Is.EqualTo(HID.UsagePage.Button));
        Assert.That(hidDescriptor.elements[6].usagePage, Is.EqualTo(HID.UsagePage.Button));
        Assert.That(hidDescriptor.elements[7].usagePage, Is.EqualTo(HID.UsagePage.Button));
        Assert.That(hidDescriptor.elements[5].usage, Is.EqualTo(1));
        Assert.That(hidDescriptor.elements[6].usage, Is.EqualTo(2));
        Assert.That(hidDescriptor.elements[7].usage, Is.EqualTo(3));

        Assert.That(hidDescriptor.collections.Length, Is.EqualTo(1));
        Assert.That(hidDescriptor.collections[0].type, Is.EqualTo(HID.HIDCollectionType.Application));
        Assert.That(hidDescriptor.collections[0].childCount, Is.EqualTo(kNumElements));

        ////TODO: check hat switch
    }
        internal static string OnFindLayoutForDevice(int deviceId, ref InputDeviceDescription description,
                                                     string matchedTemplate, IInputRuntime runtime)
        {
            if (description.interfaceName != "Android" || string.IsNullOrEmpty(description.capabilities))
            {
                return(null);
            }

            ////TODO: these should just be Controller and Sensor; the interface is already Android
            switch (description.deviceClass)
            {
            case "AndroidGameController":
            {
                var caps = AndroidDeviceCapabilities.FromJson(description.capabilities);

                // Note: Gamepads have both AndroidInputSource.Gamepad and AndroidInputSource.Joystick in input source, while
                //       Joysticks don't have AndroidInputSource.Gamepad in their input source
                if ((caps.inputSources & AndroidInputSource.Gamepad) != AndroidInputSource.Gamepad)
                {
                    return("AndroidJoystick");
                }

                // Most of the gamepads:
                // - NVIDIA Controller v01.03/v01.04
                // - ELAN PLAYSTATION(R)3 Controller
                // - My-Power CO.,LTD. PS(R) Controller Adaptor
                // - (Add more)
                // map buttons in the following way:
                //  Left Stick -> AXIS_X(0) / AXIS_Y(1)
                //  Right Stick -> AXIS_Z (11) / AXIS_RZ(14)
                //  Right Thumb -> KEYCODE_BUTTON_THUMBR(107)
                //  Left Thumb -> KEYCODE_BUTTON_THUMBL(106)
                //  L1 (Left shoulder) -> KEYCODE_BUTTON_L1(102)
                //  R1 (Right shoulder) -> KEYCODE_BUTTON_R1(103)
                //  L2 (Left trigger) -> AXIS_BRAKE(23)
                //  R2 (Right trigger) -> AXIS_GAS(22)
                //  X -> KEYCODE_BUTTON_X(99)
                //  Y -> KEYCODE_BUTTON_Y(100)
                //  B -> KEYCODE_BUTTON_B(97)
                //  A -> KEYCODE_BUTTON_A(96)
                //  DPAD -> AXIS_HAT_X(15),AXIS_HAT_Y(16) or KEYCODE_DPAD_LEFT(21), KEYCODE_DPAD_RIGHT(22), KEYCODE_DPAD_UP(19), KEYCODE_DPAD_DOWN(20),

                // Note: On Nvidia Shield Console, L2/R2 additionally invoke key events for AXIS_LTRIGGER, AXIS_RTRIGGER (in addition to AXIS_BRAKE, AXIS_GAS)
                //       If you connect gamepad to a phone for L2/R2 only AXIS_BRAKE/AXIS_GAS come. AXIS_LTRIGGER, AXIS_RTRIGGER are not invoked.
                //       That's why we map triggers only to AXIS_BRAKE/AXIS_GAS


                // Other exotic gamepads have different mappings
                //  Xbox Gamepad (for ex., Microsoft X-Box One pad (Firmware 2015)) mapping (Note mapping: L2/R2/Right Stick)
                //  Left Stick -> AXIS_X(0) / AXIS_Y(1)
                //  Right Stick -> AXIS_RX (12) / AXIS_RY(13)
                //  Right Thumb -> KEYCODE_BUTTON_THUMBR(107)
                //  Left Thumb -> KEYCODE_BUTTON_THUMBL(106)
                //  L1 (Left shoulder) -> KEYCODE_BUTTON_L1(102)
                //  R1 (Right shoulder) -> KEYCODE_BUTTON_R1(103)
                //  L2 (Left trigger) -> AXIS_Z(11)
                //  R2 (Right trigger) -> AXIS_RZ(14)
                //  X -> KEYCODE_BUTTON_X(99)
                //  Y -> KEYCODE_BUTTON_Y(100)
                //  B -> KEYCODE_BUTTON_B(97)
                //  A -> KEYCODE_BUTTON_A(96)
                //  DPAD -> AXIS_HAT_X(15),AXIS_HAT_Y(16)

                //  Sony's Dualshock
                //  Left Stick -> AXIS_X(0) / AXIS_Y(1)
                //  Right Stick -> AXIS_Z(11) / AXIS_RZ(14)
                //  Right Thumb -> KEYCODE_BUTTON_START(108)
                //  Left Thumb -> KEYCODE_BUTTON_SELECT(109)
                //  X -> KEYCODE_BUTTON_A(96),
                //  Y -> KEYCODE_BUTTON_X(99)
                //  B -> KEYCODE_BUTTON_C(98),
                //  A -> KEYCODE_BUTTON_B(97)
                //  L1 -> KEYCODE_BUTTON_Y(100)
                //  R1 -> KEYCODE_BUTTON_Z(101)
                //  L2 -> KEYCODE_BUTTON_L1(102), AXIS_RX(12),
                //  R2 -> KEYCODE_BUTTON_R1(103), AXIS_RY(13),
                //  DPAD -> AXIS_HAT_X(15),AXIS_HAT_Y(16),
                //  Share -> KEYCODE_BUTTON_L2(104)
                //  Options -> KEYCODE_BUTTON_R2(105),
                //  Click on Touchpad -> KEYCODE_BUTTON_THUMBL(106)


                if (caps.motionAxes == null)
                {
                    return("AndroidGamepadWithDpadButtons");
                }

                // Vendor Ids, Product Ids can be found here http://www.linux-usb.org/usb.ids
                const int kVendorMicrosoft = 0x045e;

                const int kVendorSonyCorp     = 0x54c;
                const int kDualShock4CUHZCT1x = 0x05c4;
                const int kDualShock4CUHZCT2x = 0x09cc;


                if (caps.vendorId == kVendorMicrosoft &&
                    caps.motionAxes != null &&
                    caps.motionAxes.Contains(AndroidAxis.Rx) &&
                    caps.motionAxes.Contains(AndroidAxis.Ry) &&
                    caps.motionAxes.Contains(AndroidAxis.HatX) &&
                    caps.motionAxes.Contains(AndroidAxis.HatY))
                {
                    return("AndroidGamepadXboxController");
                }

                if (caps.vendorId == kVendorSonyCorp && (caps.productId == kDualShock4CUHZCT1x || caps.productId == kDualShock4CUHZCT2x))
                {
                    return("AndroidGamepadDualShock");
                }

                // Fallback to generic gamepads
                if (caps.motionAxes.Contains(AndroidAxis.HatX) &&
                    caps.motionAxes.Contains(AndroidAxis.HatY))
                {
                    return("AndroidGamepadWithDpadAxes");
                }

                return("AndroidGamepadWithDpadButtons");
            }

            default:
                return(null);
            }
        }