A game about forced loneliness, made by TACStudios
1using System;
2using UnityEngine.InputSystem.Utilities;
3
4////TODO: add a 'devicePath' property that platforms can use to relay their internal device locators
5//// (but do *not* take it into account when comparing descriptions for disconnected devices)
6
7namespace UnityEngine.InputSystem.Layouts
8{
9 /// <summary>
10 /// Metadata for an input device.
11 /// </summary>
12 /// <remarks>
13 /// Device descriptions are mainly used to determine which <see cref="InputControlLayout"/>
14 /// to create an actual <see cref="InputDevice"/> instance from. Each description is comprised
15 /// of a set of properties that each are individually optional. However, for a description
16 /// to be usable, at least some need to be set. Generally, the minimum viable description
17 /// for a device is one with <see cref="deviceClass"/> filled out.
18 ///
19 /// <example>
20 /// <code>
21 /// // Device description equivalent to a generic gamepad with no
22 /// // further information about the device.
23 /// new InputDeviceDescription
24 /// {
25 /// deviceClass = "Gamepad"
26 /// };
27 /// </code>
28 /// </example>
29 ///
30 /// Device descriptions will usually be supplied by the Unity runtime but can also be manually
31 /// fed into the system using <see cref="InputSystem.AddDevice(InputDeviceDescription)"/>. The
32 /// system will remember each device description it has seen regardless of whether it was
33 /// able to successfully create a device from the description. To query the list of descriptions
34 /// that for whatever reason did not result in a device being created, call <see
35 /// cref="InputSystem.GetUnsupportedDevices()"/>.
36 ///
37 /// Whenever layout registrations in the system are changed (e.g. by calling <see
38 /// cref="InputSystem.RegisterLayout{T}"/> or whenever <see cref="InputSettings.supportedDevices"/>
39 /// is changed, the system will go through the list of unsupported devices itself and figure out
40 /// if there are device descriptions that now it can turn into devices. The same also applies
41 /// in reverse; if, for example, a layout is removed that is currently used a device, the
42 /// device will be removed and its description (if any) will be placed on the list of
43 /// unsupported devices.
44 /// </remarks>
45 /// <seealso cref="InputDevice.description"/>
46 /// <seealso cref="InputDeviceMatcher"/>
47 [Serializable]
48 public struct InputDeviceDescription : IEquatable<InputDeviceDescription>
49 {
50 /// <summary>
51 /// How we talk to the device; usually name of the underlying backend that feeds
52 /// state for the device (e.g. "HID" or "XInput").
53 /// </summary>
54 /// <value>Name of interface through which the device is reported.</value>
55 /// <see cref="InputDeviceMatcher.WithInterface"/>
56 public string interfaceName
57 {
58 get => m_InterfaceName;
59 set => m_InterfaceName = value;
60 }
61
62 /// <summary>
63 /// What the interface thinks the device classifies as.
64 /// </summary>
65 /// <value>Broad classification of device.</value>
66 /// <remarks>
67 /// If there is no layout specifically matching a device description,
68 /// the device class is used as as fallback. If, for example, this field
69 /// is set to "Gamepad", the "Gamepad" layout is used as a fallback.
70 /// </remarks>
71 /// <seealso cref="InputDeviceMatcher.WithDeviceClass"/>
72 public string deviceClass
73 {
74 get => m_DeviceClass;
75 set => m_DeviceClass = value;
76 }
77
78 /// <summary>
79 /// Name of the vendor that produced the device.
80 /// </summary>
81 /// <value>Name of manufacturer.</value>
82 /// <seealso cref="InputDeviceMatcher.WithManufacturer"/>
83 public string manufacturer
84 {
85 get => m_Manufacturer;
86 set => m_Manufacturer = value;
87 }
88
89 /// <summary>
90 /// Name of the product assigned by the vendor to the device.
91 /// </summary>
92 /// <value>Name of product.</value>
93 /// <seealso cref="InputDeviceMatcher.WithProduct"/>
94 public string product
95 {
96 get => m_Product;
97 set => m_Product = value;
98 }
99
100 /// <summary>
101 /// If available, serial number for the device.
102 /// </summary>
103 /// <value>Serial number of device.</value>
104 public string serial
105 {
106 get => m_Serial;
107 set => m_Serial = value;
108 }
109
110 /// <summary>
111 /// Version string of the device and/or driver.
112 /// </summary>
113 /// <value>Version of device and/or driver.</value>
114 /// <seealso cref="InputDeviceMatcher.WithVersion"/>
115 public string version
116 {
117 get => m_Version;
118 set => m_Version = value;
119 }
120
121 /// <summary>
122 /// An optional JSON string listing device-specific capabilities.
123 /// </summary>
124 /// <value>Interface-specific listing of device capabilities.</value>
125 /// <remarks>
126 /// The primary use of this field is to allow custom layout factories
127 /// to create layouts on the fly from in-depth device descriptions delivered
128 /// by external APIs.
129 ///
130 /// In the case of HID, for example, this field contains a JSON representation
131 /// of the HID descriptor (see <see cref="HID.HID.HIDDeviceDescriptor"/>) as
132 /// reported by the device driver. This descriptor contains information about
133 /// all I/O elements on the device which can be used to determine the control
134 /// setup and data format used by the device.
135 /// </remarks>
136 /// <seealso cref="InputDeviceMatcher.WithCapability{T}"/>
137 public string capabilities
138 {
139 get => m_Capabilities;
140 set => m_Capabilities = value;
141 }
142
143 /// <summary>
144 /// Whether any of the properties in the description are set.
145 /// </summary>
146 /// <value>True if any of <see cref="interfaceName"/>, <see cref="deviceClass"/>,
147 /// <see cref="manufacturer"/>, <see cref="product"/>, <see cref="serial"/>,
148 /// <see cref="version"/>, or <see cref="capabilities"/> is not <c>null</c> and
149 /// not empty.</value>
150 public bool empty =>
151 string.IsNullOrEmpty(m_InterfaceName) &&
152 string.IsNullOrEmpty(m_DeviceClass) &&
153 string.IsNullOrEmpty(m_Manufacturer) &&
154 string.IsNullOrEmpty(m_Product) &&
155 string.IsNullOrEmpty(m_Serial) &&
156 string.IsNullOrEmpty(m_Version) &&
157 string.IsNullOrEmpty(m_Capabilities);
158
159 /// <summary>
160 /// Return a string representation of the description useful for
161 /// debugging.
162 /// </summary>
163 /// <returns>A script representation of the description.</returns>
164 public override string ToString()
165 {
166 var haveProduct = !string.IsNullOrEmpty(product);
167 var haveManufacturer = !string.IsNullOrEmpty(manufacturer);
168 var haveInterface = !string.IsNullOrEmpty(interfaceName);
169
170 if (haveProduct && haveManufacturer)
171 {
172 if (haveInterface)
173 return $"{manufacturer} {product} ({interfaceName})";
174
175 return $"{manufacturer} {product}";
176 }
177
178 if (haveProduct)
179 {
180 if (haveInterface)
181 return $"{product} ({interfaceName})";
182
183 return product;
184 }
185
186 if (!string.IsNullOrEmpty(deviceClass))
187 {
188 if (haveInterface)
189 return $"{deviceClass} ({interfaceName})";
190
191 return deviceClass;
192 }
193
194 // For some HIDs on Windows, we don't get a product and manufacturer string even though
195 // the HID is guaranteed to have a product and vendor ID. Resort to printing capabilities
196 // which for HIDs at least include the product and vendor ID.
197 if (!string.IsNullOrEmpty(capabilities))
198 {
199 const int kMaxCapabilitiesLength = 40;
200
201 var caps = capabilities;
202 if (capabilities.Length > kMaxCapabilitiesLength)
203 caps = caps.Substring(0, kMaxCapabilitiesLength) + "...";
204
205 if (haveInterface)
206 return $"{caps} ({interfaceName})";
207
208 return caps;
209 }
210
211 if (haveInterface)
212 return interfaceName;
213
214 return "<Empty Device Description>";
215 }
216
217 /// <summary>
218 /// Compare the description to the given <paramref name="other"/> description.
219 /// </summary>
220 /// <param name="other">Another device description.</param>
221 /// <returns>True if the two descriptions are equivalent.</returns>
222 /// <remarks>
223 /// Two descriptions are equivalent if all their properties are equal
224 /// (ignore case).
225 /// </remarks>
226 public bool Equals(InputDeviceDescription other)
227 {
228 return m_InterfaceName.InvariantEqualsIgnoreCase(other.m_InterfaceName) &&
229 m_DeviceClass.InvariantEqualsIgnoreCase(other.m_DeviceClass) &&
230 m_Manufacturer.InvariantEqualsIgnoreCase(other.m_Manufacturer) &&
231 m_Product.InvariantEqualsIgnoreCase(other.m_Product) &&
232 m_Serial.InvariantEqualsIgnoreCase(other.m_Serial) &&
233 m_Version.InvariantEqualsIgnoreCase(other.m_Version) &&
234 ////REVIEW: this would ideally compare JSON contents not just the raw string
235 m_Capabilities.InvariantEqualsIgnoreCase(other.m_Capabilities);
236 }
237
238 /// <summary>
239 /// Compare the description to the given object.
240 /// </summary>
241 /// <param name="obj">An object.</param>
242 /// <returns>True if <paramref name="obj"/> is an InputDeviceDescription
243 /// equivalent to this one.</returns>
244 /// <seealso cref="Equals(InputDeviceDescription)"/>
245 public override bool Equals(object obj)
246 {
247 if (ReferenceEquals(null, obj))
248 return false;
249 return obj is InputDeviceDescription description && Equals(description);
250 }
251
252 /// <summary>
253 /// Compute a hash code for the device description.
254 /// </summary>
255 /// <returns>A hash code.</returns>
256 public override int GetHashCode()
257 {
258 unchecked
259 {
260 var hashCode = m_InterfaceName != null ? m_InterfaceName.GetHashCode() : 0;
261 hashCode = (hashCode * 397) ^ (m_DeviceClass != null ? m_DeviceClass.GetHashCode() : 0);
262 hashCode = (hashCode * 397) ^ (m_Manufacturer != null ? m_Manufacturer.GetHashCode() : 0);
263 hashCode = (hashCode * 397) ^ (m_Product != null ? m_Product.GetHashCode() : 0);
264 hashCode = (hashCode * 397) ^ (m_Serial != null ? m_Serial.GetHashCode() : 0);
265 hashCode = (hashCode * 397) ^ (m_Version != null ? m_Version.GetHashCode() : 0);
266 hashCode = (hashCode * 397) ^ (m_Capabilities != null ? m_Capabilities.GetHashCode() : 0);
267 return hashCode;
268 }
269 }
270
271 /// <summary>
272 /// Compare the two device descriptions.
273 /// </summary>
274 /// <param name="left">First device description.</param>
275 /// <param name="right">Second device description.</param>
276 /// <returns>True if the two descriptions are equivalent.</returns>
277 /// <seealso cref="Equals(InputDeviceDescription)"/>
278 public static bool operator==(InputDeviceDescription left, InputDeviceDescription right)
279 {
280 return left.Equals(right);
281 }
282
283 /// <summary>
284 /// Compare the two device descriptions for inequality.
285 /// </summary>
286 /// <param name="left">First device description.</param>
287 /// <param name="right">Second device description.</param>
288 /// <returns>True if the two descriptions are not equivalent.</returns>
289 /// <seealso cref="Equals(InputDeviceDescription)"/>
290 public static bool operator!=(InputDeviceDescription left, InputDeviceDescription right)
291 {
292 return !left.Equals(right);
293 }
294
295 /// <summary>
296 /// Return a JSON representation of the device description.
297 /// </summary>
298 /// <returns>A JSON representation of the description.</returns>
299 /// <remarks>
300 /// <example>
301 /// The result can be converted back into an InputDeviceDescription
302 /// using <see cref="FromJson"/>.
303 ///
304 /// <code>
305 /// var description = new InputDeviceDescription
306 /// {
307 /// interfaceName = "HID",
308 /// product = "SomeDevice",
309 /// capabilities = @"
310 /// {
311 /// ""vendorId"" : 0xABA,
312 /// ""productId"" : 0xEFE
313 /// }
314 /// "
315 /// };
316 ///
317 /// Debug.Log(description.ToJson());
318 /// // Prints
319 /// // {
320 /// // "interface" : "HID",
321 /// // "product" : "SomeDevice",
322 /// // "capabilities" : "{ \"vendorId\" : 0xABA, \"productId\" : 0xEFF }"
323 /// // }
324 /// </code>
325 /// </example>
326 /// </remarks>
327 /// <seealso cref="FromJson"/>
328 public string ToJson()
329 {
330 var data = new DeviceDescriptionJson
331 {
332 @interface = interfaceName,
333 type = deviceClass,
334 product = product,
335 manufacturer = manufacturer,
336 serial = serial,
337 version = version,
338 capabilities = capabilities
339 };
340 return JsonUtility.ToJson(data, true);
341 }
342
343 /// <summary>
344 /// Read an InputDeviceDescription from its JSON representation.
345 /// </summary>
346 /// <param name="json">String in JSON format.</param>
347 /// <exception cref="ArgumentNullException"><paramref name="json"/> is <c>null</c>.</exception>
348 /// <returns>The converted </returns>
349 /// <exception cref="ArgumentException">There as a parse error in <paramref name="json"/>.
350 /// </exception>
351 /// <remarks>
352 /// <example>
353 /// <code>
354 /// InputDeviceDescription.FromJson(@"
355 /// {
356 /// ""interface"" : ""HID"",
357 /// ""product"" : ""SomeDevice""
358 /// }
359 /// ");
360 /// </code>
361 /// </example>
362 /// </remarks>
363 /// <seealso cref="ToJson"/>
364 public static InputDeviceDescription FromJson(string json)
365 {
366 if (json == null)
367 throw new ArgumentNullException(nameof(json));
368
369 var data = JsonUtility.FromJson<DeviceDescriptionJson>(json);
370
371 return new InputDeviceDescription
372 {
373 interfaceName = data.@interface,
374 deviceClass = data.type,
375 product = data.product,
376 manufacturer = data.manufacturer,
377 serial = data.serial,
378 version = data.version,
379 capabilities = data.capabilities
380 };
381 }
382
383 internal static bool ComparePropertyToDeviceDescriptor(string propertyName, JsonParser.JsonString propertyValue, string deviceDescriptor)
384 {
385 // We use JsonParser instead of JsonUtility.Parse in order to not allocate GC memory here.
386
387 var json = new JsonParser(deviceDescriptor);
388 if (!json.NavigateToProperty(propertyName))
389 {
390 if (propertyValue.text.isEmpty)
391 return true;
392 return false;
393 }
394
395 return json.CurrentPropertyHasValueEqualTo(propertyValue);
396 }
397
398 [SerializeField] private string m_InterfaceName;
399 [SerializeField] private string m_DeviceClass;
400 [SerializeField] private string m_Manufacturer;
401 [SerializeField] private string m_Product;
402 [SerializeField] private string m_Serial;
403 [SerializeField] private string m_Version;
404 [SerializeField] private string m_Capabilities;
405
406 private struct DeviceDescriptionJson
407 {
408 public string @interface;
409 public string type;
410 public string product;
411 public string serial;
412 public string version;
413 public string manufacturer;
414 public string capabilities;
415 }
416 }
417}