From 76304f846d2cede18e3ff0767facab5d3430135b Mon Sep 17 00:00:00 2001 From: m-va Date: Mon, 17 Aug 2026 19:22:27 +0900 Subject: [PATCH] driver_vive: fix infinite loop when closing HIDAPI devices survive_vive_close() waits for every device to raise request_close, but the HIDAPI backend only ever sets that flag from HAPIReceiver() when hid_read() fails. survive_close_usb_device() closes the HID handles without setting it, so the wait loop never terminates. The libusb backend does not hit this because libusb_handle_events() drives the transfer-completion callbacks that raise the flag, and that call is compiled out under HIDAPI. Set the flag when the device is closed. This also lets survive_close() reach config_save(), so lighthouse calibration is persisted again instead of being lost on every run. Tested on Windows 11 x64 (MSVC, USE_HIDAPI=ON) with two Vive Trackers (2018) over USB and SteamVR 2.0 base stations: survive_simple_close() goes from never returning to completing immediately, and config.json gets its lighthouse0/1 entries so calibration survives a restart. Fixes #312 --- src/driver_vive.hidapi.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/driver_vive.hidapi.h b/src/driver_vive.hidapi.h index 5d3d4a0d..c1c17363 100644 --- a/src/driver_vive.hidapi.h +++ b/src/driver_vive.hidapi.h @@ -189,6 +189,20 @@ static inline void survive_close_usb_device(struct SurviveUSBInfo *usbInfo) { } free(usbInfo->handle); + usbInfo->handle = 0; + + /* Unlike the libusb backend, HIDAPI has no transfer-completion callback to + raise request_close; it is otherwise only set from HAPIReceiver() when + hid_read() fails. Without setting it here the wait loop in + survive_vive_close() never terminates, so config_save() further down in + survive_close() is never reached and lighthouse calibration is lost. + Freeing the handle above is safe because survive_usb_handle_close() is a + no-op in this backend. */ + for (size_t j = 0; j < usbInfo->interface_cnt; j++) { + usbInfo->interfaces[j].shutdown = 1; + usbInfo->interfaces[j].assoc_obj = 0; + } + usbInfo->request_close = true; #ifndef HID_NONBLOCKING for (int j = 0; j < MAX_INTERFACES_PER_DEVICE; j++) { OGJoinThread(sv->udev[i].interfaces->servicethread);