1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
|
#include "usb-helpers.hpp"
#include "log-helper.hpp"
#include "plugin-state-helpers.hpp"
#include <chrono>
#include <libusb.h>
#include <mutex>
#define LOG_PREFIX "[usb] "
namespace advss {
static bool setup();
static bool setupDone = setup();
static bool hotplugsAreSupported = false;
static std::mutex mutex;
static bool setup()
{
AddPluginInitStep([]() {
libusb_init(NULL);
hotplugsAreSupported =
libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG);
});
AddPluginCleanupStep([]() { libusb_exit(NULL); });
return true;
}
static void logLibusbError(int value, const char *msg)
{
if (value >= LIBUSB_SUCCESS) {
return;
}
vblog(LOG_WARNING, LOG_PREFIX "%s: %s", msg, libusb_strerror(value));
}
static USBDeviceInfo
getDeviceInfo(libusb_device *device, libusb_device_handle *handle,
const struct libusb_device_descriptor &descriptor)
{
char vendor_name[256] = {};
char product_name[256] = {};
char serial_number[256] = {};
int ret = libusb_get_string_descriptor_ascii(handle,
descriptor.iManufacturer,
(uint8_t *)vendor_name,
sizeof(vendor_name));
logLibusbError(ret, "Failed to query vendor name");
ret = libusb_get_string_descriptor_ascii(handle, descriptor.iProduct,
(uint8_t *)product_name,
sizeof(product_name));
logLibusbError(ret, "Failed to query product name");
ret = libusb_get_string_descriptor_ascii(handle,
descriptor.iSerialNumber,
(uint8_t *)serial_number,
sizeof(serial_number));
logLibusbError(ret, "Failed to query serial number");
const USBDeviceInfo deviceInfo = {
std::to_string(descriptor.idVendor),
std::to_string(descriptor.idProduct),
std::to_string(libusb_get_bus_number(device)),
std::to_string(libusb_get_device_address(device)),
vendor_name,
product_name,
serial_number};
return deviceInfo;
}
static std::vector<USBDeviceInfo> pollUSBDevices()
{
libusb_device **devices;
ssize_t count = libusb_get_device_list(NULL, &devices);
if (count < 0) {
logLibusbError(count, "Failed to query device list");
return {};
}
std::vector<USBDeviceInfo> result;
for (int i = 0; i < count; i++) {
libusb_device *device = devices[i];
struct libusb_device_descriptor descriptor;
int ret = libusb_get_device_descriptor(device, &descriptor);
if (ret != LIBUSB_SUCCESS) {
logLibusbError(ret, "Error getting device descriptor");
continue;
}
libusb_device_handle *handle;
ret = libusb_open(device, &handle);
if (ret != LIBUSB_SUCCESS) {
logLibusbError(ret, "Error opening device");
continue;
}
result.emplace_back(getDeviceInfo(device, handle, descriptor));
libusb_close(handle);
}
libusb_free_device_list(devices, 1);
return result;
}
static int hotplugCallback(struct libusb_context *,
struct libusb_device *device,
libusb_hotplug_event event, void *user_data)
{
auto devices = static_cast<std::vector<USBDeviceInfo> *>(user_data);
static libusb_device_handle *handle = nullptr;
struct libusb_device_descriptor descriptor;
int ret = libusb_get_device_descriptor(device, &descriptor);
logLibusbError(ret, "Error getting device descriptor");
ret = libusb_open(device, &handle);
if (ret != LIBUSB_SUCCESS) {
logLibusbError(ret, "Error opening device");
return 0;
}
const auto deviceInfo = getDeviceInfo(device, handle, descriptor);
std::lock_guard<std::mutex> lock(mutex);
if (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED == event) {
devices->emplace_back(deviceInfo);
} else if (LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT == event) {
devices->erase(std::find(devices->begin(), devices->end(),
deviceInfo));
}
libusb_close(handle);
return 0;
}
static std::vector<USBDeviceInfo> getHotplugBasedDeviceList()
{
static std::vector<USBDeviceInfo> devices;
static bool hotplugSetupDone = false;
if (!hotplugSetupDone) {
hotplugSetupDone = true;
devices = pollUSBDevices();
int ret = libusb_hotplug_register_callback(
nullptr,
LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED |
LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
0, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY,
LIBUSB_HOTPLUG_MATCH_ANY, hotplugCallback, &devices,
nullptr);
if (ret != LIBUSB_SUCCESS) {
hotplugsAreSupported = false;
} else {
blog(LOG_WARNING, LOG_PREFIX "hotplug supported!");
}
}
std::lock_guard<std::mutex> lock(mutex);
return devices;
}
static std::vector<USBDeviceInfo> getPollingBasedDeviceList()
{
// Polling can be very expensive
// Perform this operation at most once every 10 seconds
static const int timeout = 10;
static std::vector<USBDeviceInfo> deviceList = {};
static std::chrono::high_resolution_clock::time_point lastUpdate = {};
std::lock_guard<std::mutex> lock(mutex);
auto now = std::chrono::high_resolution_clock::now();
if (std::chrono::duration_cast<std::chrono::seconds>(now - lastUpdate)
.count() >= timeout) {
deviceList = pollUSBDevices();
lastUpdate = now;
}
return deviceList;
}
std::vector<USBDeviceInfo> GetUSBDevices()
{
// Hotplug events do not seem to be firing consistently during testing
// on Linux so for now only rely on polling based functionality instead
return getPollingBasedDeviceList();
/*
if (hotplugsAreSupported) {
return getHotplugBasedDeviceList();
} else {
return getPollingBasedDeviceList();
}
*/
}
QStringList GetUSBDevicesStringList()
{
QStringList result;
const auto devices = GetUSBDevices();
for (const auto &device : devices) {
result << device.ToQString();
}
return result;
}
std::string USBDeviceInfo::ToString() const
{
return "Vendor ID: " + vendorID + "\nProduct ID: " + productID +
"\nBus Number:" + busNumber +
"\nDevice Address:" + deviceAddress +
"\nVendor Name:" + vendorName + "\nProduct Name:" + productName +
"\nSerial Number:" + serialNumber;
}
QString USBDeviceInfo::ToQString() const
{
return QString::fromStdString(ToString());
}
bool USBDeviceInfo::operator==(const USBDeviceInfo &other)
{
return vendorID == other.vendorID && productID == other.productID &&
busNumber == other.busNumber &&
deviceAddress == other.deviceAddress &&
vendorName == other.vendorName &&
productName == other.productName &&
serialNumber == other.serialNumber;
}
} // namespace advss
|