From 1a03206e36b6065b421b3e7635a48211cdf0ce4a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 24 Jan 2022 20:02:05 +0100 Subject: [PATCH] Detect USB device disconnection The device disconnection is detected when the video socket closes. In order to introduce an OTG mode (HID events) without mirroring (and without server), we must be able to detect USB device disconnection. This feature will only be used in OTG mode. PR #2974 --- app/src/scrcpy.c | 4 +- app/src/usb/usb.c | 115 +++++++++++++++++++++++++++++++++++++++++++++- app/src/usb/usb.h | 26 ++++++++++- 3 files changed, 142 insertions(+), 3 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 1c84b428..5f67f7f8 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -456,7 +456,7 @@ scrcpy(struct scrcpy_options *options) { usb_device->serial, usb_device->vid, usb_device->pid, usb_device->manufacturer, usb_device->product); - ok = sc_usb_connect(&s->usb, usb_device->device); + ok = sc_usb_connect(&s->usb, usb_device->device, NULL, NULL); sc_usb_device_destroy(usb_device); if (!ok) { LOGE("Failed to connect to USB device %s", serial); @@ -650,6 +650,7 @@ end: sc_hid_mouse_destroy(&s->mouse_hid); } sc_aoa_stop(&s->aoa); + sc_usb_stop(&s->usb); } if (acksync) { sc_acksync_destroy(acksync); @@ -686,6 +687,7 @@ end: if (aoa_hid_initialized) { sc_aoa_join(&s->aoa); sc_aoa_destroy(&s->aoa); + sc_usb_join(&s->usb); sc_usb_disconnect(&s->usb); sc_usb_destroy(&s->usb); } diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 8140b674..729df7f0 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -135,13 +135,111 @@ sc_usb_destroy(struct sc_usb *usb) { libusb_exit(usb->context); } +static int +sc_usb_libusb_callback(libusb_context *ctx, libusb_device *device, + libusb_hotplug_event event, void *userdata) { + (void) ctx; + (void) device; + (void) event; + + struct sc_usb *usb = userdata; + + libusb_device *dev = libusb_get_device(usb->handle); + assert(dev); + if (dev != device) { + // Not the connected device + return 0; + } + + assert(usb->cbs && usb->cbs->on_disconnected); + usb->cbs->on_disconnected(usb, usb->cbs_userdata); + + // Do not automatically deregister the callback by returning 1. Instead, + // manually deregister to interrupt libusb_handle_events() from the libusb + // event thread: + return 0; +} + +static int +run_libusb_event_handler(void *data) { + struct sc_usb *usb = data; + while (!atomic_load(&usb->stopped)) { + // Interrupted by events or by libusb_hotplug_deregister_callback() + libusb_handle_events(usb->context); + } + return 0; +} + +static bool +sc_usb_register_callback(struct sc_usb *usb) { + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { + LOGW("libusb does not have hotplug capability"); + return false; + } + + libusb_device *device = libusb_get_device(usb->handle); + assert(device); + + struct libusb_device_descriptor desc; + int result = libusb_get_device_descriptor(device, &desc); + if (result < 0) { + log_libusb_error((enum libusb_error) result); + LOGW("Could not read USB device descriptor"); + return false; + } + + int events = LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT; + int flags = LIBUSB_HOTPLUG_NO_FLAGS; + int vendor_id = desc.idVendor; + int product_id = desc.idProduct; + int dev_class = LIBUSB_HOTPLUG_MATCH_ANY; + result = libusb_hotplug_register_callback(usb->context, events, flags, + vendor_id, product_id, dev_class, + sc_usb_libusb_callback, usb, + &usb->callback_handle); + if (result < 0) { + log_libusb_error((enum libusb_error) result); + LOGW("Could not register USB callback"); + return false; + } + + usb->has_callback_handle = true; + return true; +} + bool -sc_usb_connect(struct sc_usb *usb, libusb_device *device) { +sc_usb_connect(struct sc_usb *usb, libusb_device *device, + const struct sc_usb_callbacks *cbs, void *cbs_userdata) { usb->handle = sc_usb_open_handle(device); if (!usb->handle) { return false; } + usb->has_callback_handle = false; + usb->has_libusb_event_thread = false; + + // If cbs is set, then cbs->on_disconnected must be set + assert(!cbs || cbs->on_disconnected); + usb->cbs = cbs; + usb->cbs_userdata = cbs_userdata; + + if (cbs) { + atomic_init(&usb->stopped, false); + if (sc_usb_register_callback(usb)) { + // Create a thread to process libusb events, so that device + // disconnection could be detected immediately + usb->has_libusb_event_thread = + sc_thread_create(&usb->libusb_event_thread, + run_libusb_event_handler, "scrcpy-usbev", usb); + if (!usb->has_libusb_event_thread) { + LOGW("Libusb event thread handler could not be created, USB " + "device disconnection might not be detected immediately"); + } + } else { + LOGW("Could not register USB device disconnection callback"); + } + } + return true; } @@ -149,3 +247,18 @@ void sc_usb_disconnect(struct sc_usb *usb) { libusb_close(usb->handle); } + +void +sc_usb_stop(struct sc_usb *usb) { + if (usb->has_callback_handle) { + atomic_store(&usb->stopped, true); + libusb_hotplug_deregister_callback(usb->context, usb->callback_handle); + } +} + +void +sc_usb_join(struct sc_usb *usb) { + if (usb->has_libusb_event_thread) { + sc_thread_join(&usb->libusb_event_thread, NULL); + } +} diff --git a/app/src/usb/usb.h b/app/src/usb/usb.h index a271d034..eda7c2f9 100644 --- a/app/src/usb/usb.h +++ b/app/src/usb/usb.h @@ -6,9 +6,26 @@ #include #include +#include "util/thread.h" + struct sc_usb { libusb_context *context; libusb_device_handle *handle; + + const struct sc_usb_callbacks *cbs; + void *cbs_userdata; + + bool has_callback_handle; + libusb_hotplug_callback_handle callback_handle; + + bool has_libusb_event_thread; + sc_thread libusb_event_thread; + + atomic_bool stopped; // only used if cbs != NULL +}; + +struct sc_usb_callbacks { + void (*on_disconnected)(struct sc_usb *usb, void *userdata); }; struct sc_usb_device { @@ -37,9 +54,16 @@ sc_usb_find_devices(struct sc_usb *usb, const char *serial, struct sc_usb_device *devices, size_t len); bool -sc_usb_connect(struct sc_usb *usb, libusb_device *device); +sc_usb_connect(struct sc_usb *usb, libusb_device *device, + const struct sc_usb_callbacks *cbs, void *cbs_userdata); void sc_usb_disconnect(struct sc_usb *usb); +void +sc_usb_stop(struct sc_usb *usb); + +void +sc_usb_join(struct sc_usb *usb); + #endif