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