Пример #1
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);
            }
        }
Пример #2
0
        internal static string OnFindLayoutForDevice(int deviceId, ref InputDeviceDescription description,
                                                     string matchedLayout, IInputRuntime runtime)
        {
            // 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);
        }
Пример #3
0
        public static unsafe long DeviceCommand<TCommand>(this IInputRuntime runtime, int deviceId, ref TCommand command)
            where TCommand : struct, IInputDeviceCommandInfo
        {
            if (runtime == null)
                throw new ArgumentNullException(nameof(runtime));

            return runtime.DeviceCommand(deviceId, (InputDeviceCommand*)UnsafeUtility.AddressOf(ref command));
        }
Пример #4
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);
            }
        }
Пример #5
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;
        }
Пример #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
        internal static string OnFindLayoutForDevice(int deviceId, ref InputDeviceDescription description, string matchedLayout, IInputRuntime runtime)
        {
            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);
        }
Пример #8
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);
        }
 public static unsafe long DeviceCommand <TCommand>(this IInputRuntime runtime, int deviceId, ref TCommand command)
     where TCommand : struct, IInputDeviceCommandInfo
 {
     return(runtime.DeviceCommand(deviceId, (InputDeviceCommand *)UnsafeUtility.AddressOf(ref command)));
 }
        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);
            }
        }
Пример #11
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);
        }
Пример #12
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);
        }
Пример #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
        // 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);
        }