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