diff --git a/client/src/main.c b/client/src/main.c index 52f5ed91..988044a8 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -224,6 +224,7 @@ static bool tickTimerFn(void * unused) #define FRAME_TIMING_RECORD_COUNT 1024 #define FRAME_TIMING_PUBLISH_BATCH_SIZE 32 #define FRAME_TIMING_PRESENT_TIMEOUT_NS 500000000ULL +#define USB_REDIR_DISCONNECT_TIMEOUT_US 500000ULL enum FrameTimingReady { @@ -1810,7 +1811,8 @@ int spiceThread(void * arg) #endif }; - bool connected = false; + bool connected = false; + PSStatus status = PS_STATUS_SHUTDOWN; if (!purespice_connect(&config)) { DEBUG_ERROR("Failed to connect to spice server"); @@ -1818,6 +1820,7 @@ int spiceThread(void * arg) goto end; } connected = true; + status = PS_STATUS_RUN; int processTimeout = 100; #if ENABLE_USB_AUDIO @@ -1837,7 +1840,6 @@ int spiceThread(void * arg) DEBUG_WARN("Failed to process USB audio redirection"); #endif - PSStatus status; if ((status = purespice_process(processTimeout)) != PS_STATUS_RUN) { if (status != PS_STATUS_SHUTDOWN) @@ -1856,8 +1858,27 @@ end: lgaSpice_setAvailable(false); #endif #if ENABLE_USB_AUDIO - if (connected && usbRedir && !lgUsbRedir_process(usbRedir)) - DEBUG_WARN("Failed to disconnect USB audio device"); + if (connected && status == PS_STATUS_RUN && usbRedir) + { + if (!lgUsbRedir_process(usbRedir)) + DEBUG_WARN("Failed to disconnect USB audio device"); + else + { + const uint64_t deadline = + microtime() + USB_REDIR_DISCONNECT_TIMEOUT_US; + while (lgUsbRedir_disconnectPending(usbRedir) && + microtime() < deadline) + { + status = purespice_process(10); + if (status != PS_STATUS_RUN) + break; + } + + if (status == PS_STATUS_RUN && + lgUsbRedir_disconnectPending(usbRedir)) + DEBUG_WARN("Timed out disconnecting USB audio device"); + } + } #endif if (connected) purespice_disconnect(); diff --git a/client/src/usbredir.c b/client/src/usbredir.c index 7a677a89..435bd9dd 100644 --- a/client/src/usbredir.c +++ b/client/src/usbredir.c @@ -48,6 +48,7 @@ struct LG_USBRedir atomic_bool desiredPlugged; atomic_bool available; bool plugged; + bool disconnectPending; }; static void setAvailable(LG_USBRedir * usbredir, bool available) @@ -118,6 +119,12 @@ static void helloUSBRedir(void * opaque, setAvailable(usbredir, true); } +static void deviceDisconnectAck(void * opaque) +{ + LG_USBRedir * usbredir = opaque; + usbredir->disconnectPending = false; +} + static void unplugDevice(LG_USBRedir * usbredir) { if (!usbredir->plugged) @@ -131,6 +138,7 @@ static void destroyParser(LG_USBRedir * usbredir) { setAvailable(usbredir, false); unplugDevice(usbredir); + usbredir->disconnectPending = false; if (!usbredir->parser) return; @@ -187,13 +195,16 @@ static bool createParser(LG_USBRedir * usbredir) } usbredir->deviceOps->setup(usbredir->deviceOpaque, usbredir->parser); - usbredir->parser->priv = usbredir; - usbredir->parser->log_func = logUSBRedir; - usbredir->parser->read_func = readUSBRedir; - usbredir->parser->write_func = writeUSBRedir; - usbredir->parser->hello_func = helloUSBRedir; + usbredir->parser->priv = usbredir; + usbredir->parser->log_func = logUSBRedir; + usbredir->parser->read_func = readUSBRedir; + usbredir->parser->write_func = writeUSBRedir; + usbredir->parser->hello_func = helloUSBRedir; + usbredir->parser->device_disconnect_ack_func = deviceDisconnectAck; uint32_t caps[USB_REDIR_CAPS_SIZE] = { 0 }; + usbredirparser_caps_set_cap(caps, + usb_redir_cap_device_disconnect_ack); usbredirparser_caps_set_cap(caps, usb_redir_cap_connect_device_version); usbredirparser_caps_set_cap(caps, @@ -252,12 +263,20 @@ bool lgUsbRedir_available(const LG_USBRedir * usbredir) return atomic_load_explicit(&usbredir->available, memory_order_acquire); } +bool lgUsbRedir_disconnectPending(const LG_USBRedir * usbredir) +{ + return usbredir->disconnectPending; +} + bool lgUsbRedir_process(LG_USBRedir * usbredir) { if (!usbredir->parser || !atomic_load_explicit(&usbredir->available, memory_order_acquire)) return flushUSBRedir(usbredir); + if (usbredir->disconnectPending) + return flushUSBRedir(usbredir); + const bool desired = atomic_load_explicit(&usbredir->desiredPlugged, memory_order_acquire); if (desired != usbredir->plugged) @@ -271,6 +290,8 @@ bool lgUsbRedir_process(LG_USBRedir * usbredir) else { unplugDevice(usbredir); + usbredir->disconnectPending = usbredirparser_peer_has_cap( + usbredir->parser, usb_redir_cap_device_disconnect_ack); usbredirparser_send_device_disconnect(usbredir->parser); } } diff --git a/client/src/usbredir.h b/client/src/usbredir.h index 84d2e9a4..aa67fbb7 100644 --- a/client/src/usbredir.h +++ b/client/src/usbredir.h @@ -57,6 +57,9 @@ void lgUsbRedir_destroy(LG_USBRedir * usbredir); * lgUsbRedir_process on the PureSpice processing thread. */ void lgUsbRedir_setPlugged(LG_USBRedir * usbredir, bool plugged); bool lgUsbRedir_available(const LG_USBRedir * usbredir); +/* True after a device disconnect has been sent and until the guest has + * completed the detach. This must be queried on the processing thread. */ +bool lgUsbRedir_disconnectPending(const LG_USBRedir * usbredir); /* Apply pending device state and flush parser output. This must be called on * the PureSpice processing thread. */