Skip to content

Commit

Permalink
Detect USB device disconnection
Browse files Browse the repository at this point in the history
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 <#2974>
  • Loading branch information
rom1v committed Jan 27, 2022
1 parent 37987b8 commit 1a03206
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 3 deletions.
4 changes: 3 additions & 1 deletion app/src/scrcpy.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -650,6 +650,7 @@ scrcpy(struct scrcpy_options *options) {
sc_hid_mouse_destroy(&s->mouse_hid);
}
sc_aoa_stop(&s->aoa);
sc_usb_stop(&s->usb);
}
if (acksync) {
sc_acksync_destroy(acksync);
Expand Down Expand Up @@ -686,6 +687,7 @@ scrcpy(struct scrcpy_options *options) {
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);
}
Expand Down
115 changes: 114 additions & 1 deletion app/src/usb/usb.c
Original file line number Diff line number Diff line change
Expand Up @@ -135,17 +135,130 @@ 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;
}

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);
}
}
26 changes: 25 additions & 1 deletion app/src/usb/usb.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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

0 comments on commit 1a03206

Please sign in to comment.