Rasagar/Library/PackageCache/com.unity.inputsystem/InputSystem/Devices/InputDeviceDescription.cs

418 lines
16 KiB
C#
Raw Normal View History

2024-08-26 13:07:20 -07:00
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
{
/// <summary>
/// Metadata for an input device.
/// </summary>
/// <remarks>
/// Device descriptions are mainly used to determine which <see cref="InputControlLayout"/>
/// to create an actual <see cref="InputDevice"/> 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 <see cref="deviceClass"/> filled out.
///
/// <example>
/// <code>
/// // Device description equivalent to a generic gamepad with no
/// // further information about the device.
/// new InputDeviceDescription
/// {
/// deviceClass = "Gamepad"
/// };
/// </code>
/// </example>
///
/// Device descriptions will usually be supplied by the Unity runtime but can also be manually
/// fed into the system using <see cref="InputSystem.AddDevice(InputDeviceDescription)"/>. 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 <see
/// cref="InputSystem.GetUnsupportedDevices()"/>.
///
/// Whenever layout registrations in the system are changed (e.g. by calling <see
/// cref="InputSystem.RegisterLayout{T}"/> or whenever <see cref="InputSettings.supportedDevices"/>
/// 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.
/// </remarks>
/// <seealso cref="InputDevice.description"/>
/// <seealso cref="InputDeviceMatcher"/>
[Serializable]
public struct InputDeviceDescription : IEquatable<InputDeviceDescription>
{
/// <summary>
/// How we talk to the device; usually name of the underlying backend that feeds
/// state for the device (e.g. "HID" or "XInput").
/// </summary>
/// <value>Name of interface through which the device is reported.</value>
/// <see cref="InputDeviceMatcher.WithInterface"/>
public string interfaceName
{
get => m_InterfaceName;
set => m_InterfaceName = value;
}
/// <summary>
/// What the interface thinks the device classifies as.
/// </summary>
/// <value>Broad classification of device.</value>
/// <remarks>
/// 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.
/// </remarks>
/// <seealso cref="InputDeviceMatcher.WithDeviceClass"/>
public string deviceClass
{
get => m_DeviceClass;
set => m_DeviceClass = value;
}
/// <summary>
/// Name of the vendor that produced the device.
/// </summary>
/// <value>Name of manufacturer.</value>
/// <seealso cref="InputDeviceMatcher.WithManufacturer"/>
public string manufacturer
{
get => m_Manufacturer;
set => m_Manufacturer = value;
}
/// <summary>
/// Name of the product assigned by the vendor to the device.
/// </summary>
/// <value>Name of product.</value>
/// <seealso cref="InputDeviceMatcher.WithProduct"/>
public string product
{
get => m_Product;
set => m_Product = value;
}
/// <summary>
/// If available, serial number for the device.
/// </summary>
/// <value>Serial number of device.</value>
public string serial
{
get => m_Serial;
set => m_Serial = value;
}
/// <summary>
/// Version string of the device and/or driver.
/// </summary>
/// <value>Version of device and/or driver.</value>
/// <seealso cref="InputDeviceMatcher.WithVersion"/>
public string version
{
get => m_Version;
set => m_Version = value;
}
/// <summary>
/// An optional JSON string listing device-specific capabilities.
/// </summary>
/// <value>Interface-specific listing of device capabilities.</value>
/// <remarks>
/// 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 <see cref="HID.HID.HIDDeviceDescriptor"/>) 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.
/// </remarks>
/// <seealso cref="InputDeviceMatcher.WithCapability{T}"/>
public string capabilities
{
get => m_Capabilities;
set => m_Capabilities = value;
}
/// <summary>
/// Whether any of the properties in the description are set.
/// </summary>
/// <value>True if any of <see cref="interfaceName"/>, <see cref="deviceClass"/>,
/// <see cref="manufacturer"/>, <see cref="product"/>, <see cref="serial"/>,
/// <see cref="version"/>, or <see cref="capabilities"/> is not <c>null</c> and
/// not empty.</value>
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);
/// <summary>
/// Return a string representation of the description useful for
/// debugging.
/// </summary>
/// <returns>A script representation of the description.</returns>
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 "<Empty Device Description>";
}
/// <summary>
/// Compare the description to the given <paramref name="other"/> description.
/// </summary>
/// <param name="other">Another device description.</param>
/// <returns>True if the two descriptions are equivalent.</returns>
/// <remarks>
/// Two descriptions are equivalent if all their properties are equal
/// (ignore case).
/// </remarks>
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);
}
/// <summary>
/// Compare the description to the given object.
/// </summary>
/// <param name="obj">An object.</param>
/// <returns>True if <paramref name="obj"/> is an InputDeviceDescription
/// equivalent to this one.</returns>
/// <seealso cref="Equals(InputDeviceDescription)"/>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
return false;
return obj is InputDeviceDescription description && Equals(description);
}
/// <summary>
/// Compute a hash code for the device description.
/// </summary>
/// <returns>A hash code.</returns>
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;
}
}
/// <summary>
/// Compare the two device descriptions.
/// </summary>
/// <param name="left">First device description.</param>
/// <param name="right">Second device description.</param>
/// <returns>True if the two descriptions are equivalent.</returns>
/// <seealso cref="Equals(InputDeviceDescription)"/>
public static bool operator==(InputDeviceDescription left, InputDeviceDescription right)
{
return left.Equals(right);
}
/// <summary>
/// Compare the two device descriptions for inequality.
/// </summary>
/// <param name="left">First device description.</param>
/// <param name="right">Second device description.</param>
/// <returns>True if the two descriptions are not equivalent.</returns>
/// <seealso cref="Equals(InputDeviceDescription)"/>
public static bool operator!=(InputDeviceDescription left, InputDeviceDescription right)
{
return !left.Equals(right);
}
/// <summary>
/// Return a JSON representation of the device description.
/// </summary>
/// <returns>A JSON representation of the description.</returns>
/// <remarks>
/// <example>
/// The result can be converted back into an InputDeviceDescription
/// using <see cref="FromJson"/>.
///
/// <code>
/// 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 }"
/// // }
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="FromJson"/>
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);
}
/// <summary>
/// Read an InputDeviceDescription from its JSON representation.
/// </summary>
/// <param name="json">String in JSON format.</param>
/// <exception cref="ArgumentNullException"><paramref name="json"/> is <c>null</c>.</exception>
/// <returns>The converted </returns>
/// <exception cref="ArgumentException">There as a parse error in <paramref name="json"/>.
/// </exception>
/// <remarks>
/// <example>
/// <code>
/// InputDeviceDescription.FromJson(@"
/// {
/// ""interface"" : ""HID"",
/// ""product"" : ""SomeDevice""
/// }
/// ");
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="ToJson"/>
public static InputDeviceDescription FromJson(string json)
{
if (json == null)
throw new ArgumentNullException(nameof(json));
var data = JsonUtility.FromJson<DeviceDescriptionJson>(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;
}
}
}