Files
scrcpy/app/src/usb/usb.c
Romain Vimont 1a03206e36 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>
2022-01-27 23:36:18 +01:00

265 lines
7.3 KiB
C

#include "usb.h"
#include <assert.h>
#include "util/log.h"
static inline void
log_libusb_error(enum libusb_error errcode) {
LOGW("libusb error: %s", libusb_strerror(errcode));
}
static char *
read_string(libusb_device_handle *handle, uint8_t desc_index) {
char buffer[128];
int result =
libusb_get_string_descriptor_ascii(handle, desc_index,
(unsigned char *) buffer,
sizeof(buffer));
if (result < 0) {
return NULL;
}
assert((size_t) result <= sizeof(buffer));
// When non-negative, 'result' contains the number of bytes written
char *s = malloc(result + 1);
memcpy(s, buffer, result);
s[result] = '\0';
return s;
}
static bool
accept_device(libusb_device *device, const char *serial,
struct sc_usb_device *out) {
// Do not log any USB error in this function, it is expected that many USB
// devices available on the computer have permission restrictions
struct libusb_device_descriptor desc;
int result = libusb_get_device_descriptor(device, &desc);
if (result < 0 || !desc.iSerialNumber) {
return false;
}
libusb_device_handle *handle;
result = libusb_open(device, &handle);
if (result < 0) {
return false;
}
char *device_serial = read_string(handle, desc.iSerialNumber);
if (!device_serial) {
libusb_close(handle);
return false;
}
if (serial) {
// Filter by serial
bool matches = !strcmp(serial, device_serial);
if (!matches) {
free(device_serial);
libusb_close(handle);
return false;
}
}
out->device = libusb_ref_device(device);
out->serial = device_serial;
out->vid = desc.idVendor;
out->pid = desc.idProduct;
out->manufacturer = read_string(handle, desc.iManufacturer);
out->product = read_string(handle, desc.iProduct);
libusb_close(handle);
return true;
}
void
sc_usb_device_destroy(struct sc_usb_device *usb_device) {
libusb_unref_device(usb_device->device);
free(usb_device->serial);
free(usb_device->manufacturer);
free(usb_device->product);
}
void
sc_usb_device_destroy_all(struct sc_usb_device *usb_devices, size_t count) {
for (size_t i = 0; i < count; ++i) {
sc_usb_device_destroy(&usb_devices[i]);
}
}
ssize_t
sc_usb_find_devices(struct sc_usb *usb, const char *serial,
struct sc_usb_device *devices, size_t len) {
libusb_device **list;
ssize_t count = libusb_get_device_list(usb->context, &list);
if (count < 0) {
log_libusb_error((enum libusb_error) count);
return -1;
}
size_t idx = 0;
for (size_t i = 0; i < (size_t) count && idx < len; ++i) {
libusb_device *device = list[i];
if (accept_device(device, serial, &devices[idx])) {
++idx;
}
}
libusb_free_device_list(list, 1);
return idx;
}
static libusb_device_handle *
sc_usb_open_handle(libusb_device *device) {
libusb_device_handle *handle;
int result = libusb_open(device, &handle);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
return NULL;
}
return handle;
}
bool
sc_usb_init(struct sc_usb *usb) {
usb->handle = NULL;
return libusb_init(&usb->context) == LIBUSB_SUCCESS;
}
void
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,
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);
}
}