The open source OpenXR runtime
1// Copyright 2020-2021, N Madsen.
2// Copyright 2020-2022, Collabora, Ltd.
3// SPDX-License-Identifier: BSL-1.0
4/*!
5 * @file
6 * @brief WMR prober code.
7 * @author Nis Madsen <nima_zero_one@protonmail.com>
8 * @author Jakob Bornecrantz <jakob@collabora.com>
9 * @author Nova King <technobaboo@proton.me>
10 * @ingroup drv_wmr
11 */
12
13#include "xrt/xrt_config_drivers.h"
14#include "xrt/xrt_prober.h"
15
16#include "os/os_hid.h"
17
18#include "util/u_misc.h"
19#include "util/u_debug.h"
20#include "util/u_prober.h"
21#include "util/u_logging.h"
22#include "util/u_trace_marker.h"
23
24#include "wmr_interface.h"
25#include "wmr_hmd.h"
26#include "wmr_bt_controller.h"
27#include "wmr_common.h"
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <wchar.h>
32
33#ifdef XRT_BUILD_DRIVER_HANDTRACKING
34#include "../multi_wrapper/multi.h"
35#include "../ht_ctrl_emu/ht_ctrl_emu_interface.h"
36#endif
37
38
39/*
40 *
41 * Functions.
42 *
43 */
44
45static bool
46is_left(const char *product_name, size_t size)
47{
48 return strncmp(product_name, WMR_CONTROLLER_LEFT_PRODUCT_STRING, size) == 0;
49}
50
51static bool
52is_right(const char *product_name, size_t size)
53{
54 return strncmp(product_name, WMR_CONTROLLER_RIGHT_PRODUCT_STRING, size) == 0;
55}
56
57static void
58classify_and_assign_controller(struct xrt_prober *xp,
59 struct xrt_prober_device *xpd,
60 struct wmr_bt_controllers_search_results *ctrls)
61{
62 char buf[256] = {0};
63 int result = xrt_prober_get_string_descriptor(xp, xpd, XRT_PROBER_STRING_PRODUCT, (uint8_t *)buf, sizeof(buf));
64 if (result <= 0) {
65 U_LOG_E("xrt_prober_get_string_descriptor: %i\n\tFailed to get product string!", result);
66 return;
67 }
68
69 if (is_left(buf, sizeof(buf))) {
70 ctrls->left = xpd;
71 } else if (is_right(buf, sizeof(buf))) {
72 ctrls->right = xpd;
73 }
74}
75
76static bool
77check_and_get_interface(struct xrt_prober_device *device,
78 enum u_logging_level log_level,
79 enum wmr_headset_type *out_hmd_type)
80{
81 switch (device->vendor_id) {
82 case HP_VID:
83 U_LOG_IFL_T(log_level, "HP_VID");
84
85 switch (device->product_id) {
86 case REVERB_G1_PID: *out_hmd_type = WMR_HEADSET_REVERB_G1; return true;
87 case REVERB_G2_PID: *out_hmd_type = WMR_HEADSET_REVERB_G2; return true;
88 case REVERB_G2_OMNICEPT_PID: *out_hmd_type = WMR_HEADSET_REVERB_G2; return true;
89 case VR1000_PID: *out_hmd_type = WMR_HEADSET_HP_VR1000; return true;
90 default: U_LOG_IFL_T(log_level, "No matching PID!"); return false;
91 }
92
93 case LENOVO_VID:
94 U_LOG_IFL_T(log_level, "LENOVO_VID");
95
96 switch (device->product_id) {
97 case EXPLORER_PID: *out_hmd_type = WMR_HEADSET_LENOVO_EXPLORER; return true;
98 default: U_LOG_IFL_T(log_level, "No matching PID!"); return false;
99 }
100
101 case SAMSUNG_VID:
102 U_LOG_IFL_T(log_level, "SAMSUNG_VID");
103
104 switch (device->product_id) {
105 case ODYSSEY_PLUS_PID: *out_hmd_type = WMR_HEADSET_SAMSUNG_800ZAA; return true;
106 case ODYSSEY_PID:
107 U_LOG_IFL_W(log_level, "Original Odyssey may not be well-supported - continuing anyway.");
108 *out_hmd_type = WMR_HEADSET_SAMSUNG_XE700X3AI;
109 return true;
110 default: U_LOG_IFL_T(log_level, "No matching PID!"); return false;
111 }
112
113 case QUANTA_VID:
114 U_LOG_IFL_T(log_level, "QUANTA_VID");
115
116 switch (device->product_id) {
117 case MEDION_ERAZER_X1000_PID: *out_hmd_type = WMR_HEADSET_MEDION_ERAZER_X1000; return true;
118 default: U_LOG_IFL_T(log_level, "No matching PID!"); return false;
119 }
120
121 case DELL_VID:
122 U_LOG_IFL_T(log_level, "DELL_VID");
123
124 switch (device->product_id) {
125 case VISOR_PID: *out_hmd_type = WMR_HEADSET_DELL_VISOR; return true;
126 default: U_LOG_IFL_T(log_level, "No matching PID!"); return false;
127 }
128
129 case ACER_VID:
130 U_LOG_IFL_T(log_level, "ACER_VID");
131
132 switch (device->product_id) {
133 case AH100_PID: *out_hmd_type = WMR_HEADSET_ACER_AH100; return true;
134 case AH101_PID: *out_hmd_type = WMR_HEADSET_ACER_AH101; return true;
135 default: U_LOG_IFL_T(log_level, "No matching PID!"); return false;
136 }
137
138 case FUJITSU_VID:
139 U_LOG_IFL_T(log_level, "FUJITSU_VID");
140
141 switch (device->product_id) {
142 case FMVHDS1_PID: *out_hmd_type = WMR_HEADSET_FUJITSU_FMVHDS1; return true;
143 default: U_LOG_IFL_T(log_level, "No matching PID!"); return false;
144 }
145
146 default: return false;
147 }
148}
149
150static bool
151find_companion_device(struct xrt_prober *xp,
152 struct xrt_prober_device **devices,
153 size_t device_count,
154 enum u_logging_level log_level,
155 enum wmr_headset_type *out_hmd_type,
156 struct xrt_prober_device **out_device)
157{
158 struct xrt_prober_device *dev = NULL;
159
160 for (size_t i = 0; i < device_count; i++) {
161 bool match = false;
162
163 if (devices[i]->bus != XRT_BUS_TYPE_USB) {
164 continue;
165 }
166
167 match = check_and_get_interface(devices[i], log_level, out_hmd_type);
168
169 if (!match) {
170 continue;
171 }
172
173 if (dev != NULL) {
174 U_LOG_IFL_W(log_level, "Found multiple control devices, using the last.");
175 }
176 dev = devices[i];
177 }
178
179 if (dev == NULL) {
180 return false;
181 }
182
183 unsigned char m_str[256] = {0};
184 unsigned char p_str[256] = {0};
185 xrt_prober_get_string_descriptor(xp, dev, XRT_PROBER_STRING_MANUFACTURER, m_str, sizeof(m_str));
186 xrt_prober_get_string_descriptor(xp, dev, XRT_PROBER_STRING_PRODUCT, p_str, sizeof(p_str));
187
188 U_LOG_IFL_D(log_level, "Found Hololens Sensors' companion device '%s' '%s' (vid %04X, pid %04X)", p_str, m_str,
189 dev->vendor_id, dev->product_id);
190
191
192 *out_device = dev;
193
194 return dev != NULL;
195}
196
197
198/*
199 *
200 * 'Exported' builder functions.
201 *
202 */
203
204void
205wmr_find_bt_controller_pair(struct xrt_prober *xp,
206 struct xrt_prober_device **devices,
207 size_t device_count,
208 enum u_logging_level log_level,
209 struct wmr_bt_controllers_search_results *out_wbtcsr)
210{
211 // Try to pair controllers of the same type.
212 struct wmr_bt_controllers_search_results odyssey_ctrls = {0};
213 struct wmr_bt_controllers_search_results wmr_ctrls = {0};
214 struct wmr_bt_controllers_search_results reverbg2_ctrls = {0};
215
216 for (size_t i = 0; i < device_count; i++) {
217 struct xrt_prober_device *xpd = devices[i];
218
219 // All controllers have the Microsoft vendor ID.
220 if (xpd->vendor_id != MICROSOFT_VID) {
221 continue;
222 }
223
224 // Only handle Bluetooth connected controllers here.
225 if (xpd->bus != XRT_BUS_TYPE_BLUETOOTH) {
226 continue;
227 }
228
229 if (xpd->product_id == WMR_CONTROLLER_PID) {
230 classify_and_assign_controller(xp, xpd, &wmr_ctrls);
231 } else if (xpd->product_id == ODYSSEY_CONTROLLER_PID) {
232 classify_and_assign_controller(xp, xpd, &odyssey_ctrls);
233 } else if (xpd->product_id == REVERB_G2_CONTROLLER_PID) {
234 classify_and_assign_controller(xp, xpd, &reverbg2_ctrls);
235 }
236 }
237
238 // We have to prefer one type pair, prefer Odyssey.
239 if (odyssey_ctrls.left != NULL && odyssey_ctrls.right != NULL) {
240 *out_wbtcsr = odyssey_ctrls;
241 return;
242 }
243
244 if (reverbg2_ctrls.left != NULL && reverbg2_ctrls.right != NULL) {
245 *out_wbtcsr = reverbg2_ctrls;
246 return;
247 }
248
249 // Other type pair.
250 if (wmr_ctrls.left != NULL && wmr_ctrls.right != NULL) {
251 *out_wbtcsr = wmr_ctrls;
252 return;
253 }
254
255 // Grab any of them.
256 out_wbtcsr->left = reverbg2_ctrls.left != NULL ? reverbg2_ctrls.left
257 : odyssey_ctrls.left != NULL ? odyssey_ctrls.left
258 : wmr_ctrls.left;
259 out_wbtcsr->right = reverbg2_ctrls.right != NULL ? reverbg2_ctrls.right
260 : odyssey_ctrls.right != NULL ? odyssey_ctrls.right
261 : wmr_ctrls.right;
262}
263
264void
265wmr_find_companion_device(struct xrt_prober *xp,
266 struct xrt_prober_device **xpdevs,
267 size_t xpdev_count,
268 enum u_logging_level log_level,
269 struct xrt_prober_device *xpdev_holo,
270 struct wmr_companion_search_results *out_wcsr)
271{
272 struct xrt_prober_device *xpdev_companion = NULL;
273 enum wmr_headset_type type = WMR_HEADSET_GENERIC;
274
275 if (!find_companion_device(xp, xpdevs, xpdev_count, log_level, &type, &xpdev_companion)) {
276 U_LOG_IFL_E(log_level, "Did not find HoloLens Sensors' companion device");
277 return;
278 }
279
280 out_wcsr->xpdev_companion = xpdev_companion;
281 out_wcsr->type = type;
282}
283
284void
285wmr_find_headset(struct xrt_prober *xp,
286 struct xrt_prober_device **xpdevs,
287 size_t xpdev_count,
288 enum u_logging_level log_level,
289 struct wmr_headset_search_results *out_whsr)
290{
291 struct wmr_companion_search_results wcsr = {0};
292 struct xrt_prober_device *xpdev_holo = NULL;
293
294 for (size_t i = 0; i < xpdev_count; i++) {
295 struct xrt_prober_device *xpd = xpdevs[i];
296
297 // Only handle Bluetooth connected controllers here.
298 if (xpd->bus != XRT_BUS_TYPE_USB) {
299 continue;
300 }
301
302 if (xpd->vendor_id != MICROSOFT_VID || xpd->product_id != HOLOLENS_SENSORS_PID) {
303 continue;
304 }
305
306 xpdev_holo = xpd;
307 break;
308 }
309
310 // Did we find any?
311 if (xpdev_holo == NULL) {
312 U_LOG_IFL_D(log_level, "Did not find HoloLens Sensors device, no headset connected?");
313 return; // Didn't find any hololense device, not an error.
314 }
315
316
317 // Find the companion device.
318 wmr_find_companion_device(xp, xpdevs, xpdev_count, log_level, xpdev_holo, &wcsr);
319 if (wcsr.xpdev_companion == NULL) {
320 U_LOG_IFL_E(log_level, "Found a HoloLens device, but not it's companion device");
321 return;
322 }
323
324 // Done now, output.
325 out_whsr->xpdev_holo = xpdev_holo;
326 out_whsr->xpdev_companion = wcsr.xpdev_companion;
327 out_whsr->type = wcsr.type;
328}
329
330
331/*
332 *
333 * 'Exported' create functions.
334 *
335 */
336
337xrt_result_t
338wmr_create_headset(struct xrt_prober *xp,
339 struct xrt_prober_device *xpdev_holo,
340 struct xrt_prober_device *xpdev_companion,
341 enum wmr_headset_type type,
342 enum u_logging_level log_level,
343 struct xrt_device **out_hmd,
344 struct xrt_device **out_left,
345 struct xrt_device **out_right,
346 struct xrt_device **out_ht_left,
347 struct xrt_device **out_ht_right)
348{
349 DRV_TRACE_MARKER();
350
351 U_LOG_IFL_D(log_level, "Creating headset.");
352
353 const int interface_holo = 2;
354 const int interface_companion = 0;
355 int ret;
356
357 struct os_hid_device *hid_holo = NULL;
358 ret = xrt_prober_open_hid_interface(xp, xpdev_holo, interface_holo, &hid_holo);
359 if (ret != 0) {
360 U_LOG_IFL_E(log_level, "Failed to open HoloLens Sensors HID interface");
361 return XRT_ERROR_DEVICE_CREATION_FAILED;
362 }
363
364 struct os_hid_device *hid_companion = NULL;
365 ret = xrt_prober_open_hid_interface(xp, xpdev_companion, interface_companion, &hid_companion);
366 if (ret != 0) {
367 U_LOG_IFL_E(log_level, "Failed to open HoloLens Sensors' companion HID interface.");
368 goto error_holo;
369 }
370
371 struct xrt_device *hmd = NULL;
372 struct xrt_device *ht = NULL;
373 struct xrt_device *two_hands[2] = {NULL, NULL}; // Must initialize, always returned.
374 struct xrt_device *hmd_left_ctrl = NULL, *hmd_right_ctrl = NULL;
375 wmr_hmd_create(type, hid_holo, hid_companion, xpdev_holo, log_level, &hmd, &ht, &hmd_left_ctrl,
376 &hmd_right_ctrl);
377
378 if (hmd == NULL) {
379 U_LOG_IFL_E(log_level, "Failed to create WMR HMD device.");
380 /* No cleanup - the wmr_hmd_create() method cleaned up
381 * the hid devices already */
382 return XRT_ERROR_DEVICE_CREATION_FAILED;
383 }
384
385#ifdef XRT_BUILD_DRIVER_HANDTRACKING
386 if (ht != NULL) { // Create hand-tracked controllers
387 cemu_devices_create(hmd, ht, two_hands);
388 }
389#endif
390
391 *out_hmd = hmd;
392 *out_left = hmd_left_ctrl;
393 *out_right = hmd_right_ctrl;
394
395 *out_ht_left = two_hands[0];
396 *out_ht_right = two_hands[1];
397
398 return XRT_SUCCESS;
399
400error_holo:
401 os_hid_destroy(hid_holo);
402
403 return XRT_ERROR_DEVICE_CREATION_FAILED;
404}
405
406xrt_result_t
407wmr_create_bt_controller(struct xrt_prober *xp,
408 struct xrt_prober_device *xpdev,
409 enum u_logging_level log_level,
410 struct xrt_device **out_xdev)
411{
412 DRV_TRACE_MARKER();
413
414 U_LOG_IFL_D(log_level, "Creating Bluetooth controller.");
415
416 struct os_hid_device *hid_controller = NULL;
417
418 // Only handle Bluetooth connected controllers here.
419 if (xpdev->bus != XRT_BUS_TYPE_BLUETOOTH) {
420 U_LOG_IFL_E(log_level, "Got a non Bluetooth device!");
421 return XRT_ERROR_DEVICE_CREATION_FAILED;
422 }
423
424 char product_name[256] = {0};
425 int ret = xrt_prober_get_string_descriptor( //
426 xp, //
427 xpdev, //
428 XRT_PROBER_STRING_PRODUCT, //
429 (uint8_t *)product_name, //
430 sizeof(product_name)); //
431
432 enum xrt_device_type controller_type = XRT_DEVICE_TYPE_UNKNOWN;
433 const int interface_controller = 0;
434
435 switch (xpdev->product_id) {
436 case WMR_CONTROLLER_PID:
437 case ODYSSEY_CONTROLLER_PID:
438 case REVERB_G2_CONTROLLER_PID:
439 if (is_left(product_name, sizeof(product_name))) {
440 controller_type = XRT_DEVICE_TYPE_LEFT_HAND_CONTROLLER;
441 break;
442 } else if (is_right(product_name, sizeof(product_name))) {
443 controller_type = XRT_DEVICE_TYPE_RIGHT_HAND_CONTROLLER;
444 break;
445 }
446 // else fall through
447 default:
448 U_LOG_IFL_E(log_level,
449 "Unsupported controller device (Bluetooth): vid: 0x%04X, pid: 0x%04X, Product Name: '%s'",
450 xpdev->vendor_id, xpdev->product_id, product_name);
451 return XRT_ERROR_DEVICE_CREATION_FAILED;
452 }
453
454 ret = xrt_prober_open_hid_interface(xp, xpdev, interface_controller, &hid_controller);
455 if (ret != 0) {
456 U_LOG_IFL_E(log_level, "Failed to open WMR Bluetooth controller's HID interface");
457 return XRT_ERROR_DEVICE_CREATION_FAILED;
458 }
459
460 // Takes ownership of the hid_controller, even on failure
461 struct xrt_device *xdev =
462 wmr_bt_controller_create(hid_controller, controller_type, xpdev->vendor_id, xpdev->product_id, log_level);
463 if (xdev == NULL) {
464 U_LOG_IFL_E(log_level, "Failed to create WMR controller (Bluetooth)");
465 return XRT_ERROR_DEVICE_CREATION_FAILED;
466 }
467
468 *out_xdev = xdev;
469
470 return XRT_SUCCESS;
471}