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); } }
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); }
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)); }
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); } }
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; }
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); }
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); }
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); } }
// 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); }
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); }
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)); }
// 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); }