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 <https://github.com/Genymobile/scrcpy/pull/2974>
This commit is contained in:
parent
37987b822e
commit
1a03206e36
3 changed files with 142 additions and 3 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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: <https://stackoverflow.com/a/60119225/1987178>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,26 @@
|
|||
#include <stdbool.h>
|
||||
#include <libusb-1.0/libusb.h>
|
||||
|
||||
#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
|
||||
|
|
Loading…
Reference in a new issue