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 }); }
private static bool IsIgnoredDevice(InputDeviceDescription description) { #if UNITY_STANDALONE_WIN #endif return(false); }
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); } }
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"); }
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); }
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); }
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 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); }
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)); }
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); }
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); }
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); }
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); }
// 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]); }
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)); }
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(); }
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)); }
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)); }
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}'"); }
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); }
// 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); }
public static void SetDescription(this InputDevice device, InputDeviceDescription value) { fieldInfoDescription.SetValue(device, value); }
// 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); }
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); }
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); }
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 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); } }