using System; using UnityEngine.InputSystem.Utilities; ////TODO: add a 'devicePath' property that platforms can use to relay their internal device locators //// (but do *not* take it into account when comparing descriptions for disconnected devices) namespace UnityEngine.InputSystem.Layouts { /// /// Metadata for an input device. /// /// /// Device descriptions are mainly used to determine which /// to create an actual instance from. Each description is comprised /// of a set of properties that each are individually optional. However, for a description /// to be usable, at least some need to be set. Generally, the minimum viable description /// for a device is one with filled out. /// /// /// /// // Device description equivalent to a generic gamepad with no /// // further information about the device. /// new InputDeviceDescription /// { /// deviceClass = "Gamepad" /// }; /// /// /// /// Device descriptions will usually be supplied by the Unity runtime but can also be manually /// fed into the system using . The /// system will remember each device description it has seen regardless of whether it was /// able to successfully create a device from the description. To query the list of descriptions /// that for whatever reason did not result in a device being created, call . /// /// Whenever layout registrations in the system are changed (e.g. by calling or whenever /// is changed, the system will go through the list of unsupported devices itself and figure out /// if there are device descriptions that now it can turn into devices. The same also applies /// in reverse; if, for example, a layout is removed that is currently used a device, the /// device will be removed and its description (if any) will be placed on the list of /// unsupported devices. /// /// /// [Serializable] public struct InputDeviceDescription : IEquatable { /// /// How we talk to the device; usually name of the underlying backend that feeds /// state for the device (e.g. "HID" or "XInput"). /// /// Name of interface through which the device is reported. /// public string interfaceName { get => m_InterfaceName; set => m_InterfaceName = value; } /// /// What the interface thinks the device classifies as. /// /// Broad classification of device. /// /// If there is no layout specifically matching a device description, /// the device class is used as as fallback. If, for example, this field /// is set to "Gamepad", the "Gamepad" layout is used as a fallback. /// /// public string deviceClass { get => m_DeviceClass; set => m_DeviceClass = value; } /// /// Name of the vendor that produced the device. /// /// Name of manufacturer. /// public string manufacturer { get => m_Manufacturer; set => m_Manufacturer = value; } /// /// Name of the product assigned by the vendor to the device. /// /// Name of product. /// public string product { get => m_Product; set => m_Product = value; } /// /// If available, serial number for the device. /// /// Serial number of device. public string serial { get => m_Serial; set => m_Serial = value; } /// /// Version string of the device and/or driver. /// /// Version of device and/or driver. /// public string version { get => m_Version; set => m_Version = value; } /// /// An optional JSON string listing device-specific capabilities. /// /// Interface-specific listing of device capabilities. /// /// The primary use of this field is to allow custom layout factories /// to create layouts on the fly from in-depth device descriptions delivered /// by external APIs. /// /// In the case of HID, for example, this field contains a JSON representation /// of the HID descriptor (see ) as /// reported by the device driver. This descriptor contains information about /// all I/O elements on the device which can be used to determine the control /// setup and data format used by the device. /// /// public string capabilities { get => m_Capabilities; set => m_Capabilities = value; } /// /// Whether any of the properties in the description are set. /// /// True if any of , , /// , , , /// , or is not null and /// not empty.. public bool empty => string.IsNullOrEmpty(m_InterfaceName) && string.IsNullOrEmpty(m_DeviceClass) && string.IsNullOrEmpty(m_Manufacturer) && string.IsNullOrEmpty(m_Product) && string.IsNullOrEmpty(m_Serial) && string.IsNullOrEmpty(m_Version) && string.IsNullOrEmpty(m_Capabilities); /// /// Return a string representation of the description useful for /// debugging. /// /// A script representation of the description. public override string ToString() { var haveProduct = !string.IsNullOrEmpty(product); var haveManufacturer = !string.IsNullOrEmpty(manufacturer); var haveInterface = !string.IsNullOrEmpty(interfaceName); if (haveProduct && haveManufacturer) { if (haveInterface) return $"{manufacturer} {product} ({interfaceName})"; return $"{manufacturer} {product}"; } if (haveProduct) { if (haveInterface) return $"{product} ({interfaceName})"; return product; } if (!string.IsNullOrEmpty(deviceClass)) { if (haveInterface) return $"{deviceClass} ({interfaceName})"; return deviceClass; } // For some HIDs on Windows, we don't get a product and manufacturer string even though // the HID is guaranteed to have a product and vendor ID. Resort to printing capabilities // which for HIDs at least include the product and vendor ID. if (!string.IsNullOrEmpty(capabilities)) { const int kMaxCapabilitiesLength = 40; var caps = capabilities; if (capabilities.Length > kMaxCapabilitiesLength) caps = caps.Substring(0, kMaxCapabilitiesLength) + "..."; if (haveInterface) return $"{caps} ({interfaceName})"; return caps; } if (haveInterface) return interfaceName; return ""; } /// /// Compare the description to the given description. /// /// Another device description. /// True if the two descriptions are equivalent. /// /// Two descriptions are equivalent if all their properties are equal /// (ignore case). /// public bool Equals(InputDeviceDescription other) { return m_InterfaceName.InvariantEqualsIgnoreCase(other.m_InterfaceName) && m_DeviceClass.InvariantEqualsIgnoreCase(other.m_DeviceClass) && m_Manufacturer.InvariantEqualsIgnoreCase(other.m_Manufacturer) && m_Product.InvariantEqualsIgnoreCase(other.m_Product) && m_Serial.InvariantEqualsIgnoreCase(other.m_Serial) && m_Version.InvariantEqualsIgnoreCase(other.m_Version) && ////REVIEW: this would ideally compare JSON contents not just the raw string m_Capabilities.InvariantEqualsIgnoreCase(other.m_Capabilities); } /// /// Compare the description to the given object. /// /// An object. /// True if is an InputDeviceDescription /// equivalent to this one. /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; return obj is InputDeviceDescription description && Equals(description); } /// /// Compute a hash code for the device description. /// /// A hash code. public override int GetHashCode() { unchecked { var hashCode = m_InterfaceName != null ? m_InterfaceName.GetHashCode() : 0; hashCode = (hashCode * 397) ^ (m_DeviceClass != null ? m_DeviceClass.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (m_Manufacturer != null ? m_Manufacturer.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (m_Product != null ? m_Product.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (m_Serial != null ? m_Serial.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (m_Version != null ? m_Version.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (m_Capabilities != null ? m_Capabilities.GetHashCode() : 0); return hashCode; } } /// /// Compare the two device descriptions. /// /// First device description. /// Second device description. /// True if the two descriptions are equivalent. /// public static bool operator==(InputDeviceDescription left, InputDeviceDescription right) { return left.Equals(right); } /// /// Compare the two device descriptions for inequality. /// /// First device description. /// Second device description. /// True if the two descriptions are not equivalent. /// public static bool operator!=(InputDeviceDescription left, InputDeviceDescription right) { return !left.Equals(right); } /// /// Return a JSON representation of the device description. /// /// A JSON representation of the description. /// /// /// The result can be converted back into an InputDeviceDescription /// using . /// /// /// var description = new InputDeviceDescription /// { /// interfaceName = "HID", /// product = "SomeDevice", /// capabilities = @" /// { /// ""vendorId"" : 0xABA, /// ""productId"" : 0xEFE /// } /// " /// }; /// /// Debug.Log(description.ToJson()); /// // Prints /// // { /// // "interface" : "HID", /// // "product" : "SomeDevice", /// // "capabilities" : "{ \"vendorId\" : 0xABA, \"productId\" : 0xEFF }" /// // } /// /// /// /// public string ToJson() { var data = new DeviceDescriptionJson { @interface = interfaceName, type = deviceClass, product = product, manufacturer = manufacturer, serial = serial, version = version, capabilities = capabilities }; return JsonUtility.ToJson(data, true); } /// /// Read an InputDeviceDescription from its JSON representation. /// /// String in JSON format. /// is null. /// The converted /// There as a parse error in . /// /// /// /// /// InputDeviceDescription.FromJson(@" /// { /// ""interface"" : ""HID"", /// ""product"" : ""SomeDevice"" /// } /// "); /// /// /// /// public static InputDeviceDescription FromJson(string json) { if (json == null) throw new ArgumentNullException(nameof(json)); var data = JsonUtility.FromJson(json); return new InputDeviceDescription { interfaceName = data.@interface, deviceClass = data.type, product = data.product, manufacturer = data.manufacturer, serial = data.serial, version = data.version, capabilities = data.capabilities }; } internal static bool ComparePropertyToDeviceDescriptor(string propertyName, string propertyValue, string deviceDescriptor) { // We use JsonParser instead of JsonUtility.Parse in order to not allocate GC memory here. var json = new JsonParser(deviceDescriptor); if (!json.NavigateToProperty(propertyName)) { if (string.IsNullOrEmpty(propertyValue)) return true; return false; } return json.CurrentPropertyHasValueEqualTo(propertyValue); } [SerializeField] private string m_InterfaceName; [SerializeField] private string m_DeviceClass; [SerializeField] private string m_Manufacturer; [SerializeField] private string m_Product; [SerializeField] private string m_Serial; [SerializeField] private string m_Version; [SerializeField] private string m_Capabilities; private struct DeviceDescriptionJson { public string @interface; public string type; public string product; public string serial; public string version; public string manufacturer; public string capabilities; } } }