Add USB condition type

It allows users to check if a given USB device is currently connected
This commit is contained in:
WarmUpTill 2023-07-23 17:14:02 +02:00 committed by WarmUpTill
parent 7d120d8b6a
commit 3ff9a1f270
7 changed files with 867 additions and 0 deletions

View File

@ -703,6 +703,16 @@ AdvSceneSwitcher.condition.folder.condition.folderRemove="A directory was remove
AdvSceneSwitcher.condition.folder.entry="{{conditions}}in{{folder}}{{tooltip}}"
AdvSceneSwitcher.condition.folder.enableFilter="Only evaluate to true, if the changed path matches a patern"
AdvSceneSwitcher.condition.folder.entry.filter="{{filter}}{{regex}}"
AdvSceneSwitcher.condition.usb="USB"
AdvSceneSwitcher.condition.usb.description="A USB device matching the following properties is connected:"
AdvSceneSwitcher.condition.usb.vendorID="Vendor ID:"
AdvSceneSwitcher.condition.usb.productID="Product ID:"
AdvSceneSwitcher.condition.usb.busNumber="Bus Number:"
AdvSceneSwitcher.condition.usb.deviceAddress="Device Address:"
AdvSceneSwitcher.condition.usb.vendorName="Vendor Name:"
AdvSceneSwitcher.condition.usb.productName="Product Name:"
AdvSceneSwitcher.condition.usb.serialNumber="Serial Number:"
AdvSceneSwitcher.condition.noDevicesFoundWarning="No USB devices detected!\nThe plugin might not have the required permissions to check for USB devices."
; Macro Actions
AdvSceneSwitcher.action.scene="Switch scene"
@ -1778,6 +1788,14 @@ AdvSceneSwitcher.tempVar.slideShow.path.description="The filesystem path of the
AdvSceneSwitcher.tempVar.slideShow.fileName="Slide file name"
AdvSceneSwitcher.tempVar.slideShow.fileName.description="The name of file of the slide currently being displayed."
AdvSceneSwitcher.tempVar.usb.vendorID="Vendor ID"
AdvSceneSwitcher.tempVar.usb.productID="Product ID"
AdvSceneSwitcher.tempVar.usb.busNumber="Bus Number"
AdvSceneSwitcher.tempVar.usb.deviceAddress="Device Address"
AdvSceneSwitcher.tempVar.usb.vendorName="Vendor Name"
AdvSceneSwitcher.tempVar.usb.productName="Product Name"
AdvSceneSwitcher.tempVar.usb.serialNumber="Serial Number"
AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene"

View File

@ -31,4 +31,5 @@ install_advss_plugin_dependency(...)
add_plugin(midi)
add_plugin(openvr)
add_plugin(twitch)
add_plugin(usb)
add_plugin(video)

110
plugins/usb/CMakeLists.txt Normal file
View File

@ -0,0 +1,110 @@
cmake_minimum_required(VERSION 3.14)
project(advanced-scene-switcher-usb)
# --- Check libusb requirements ---
get_target_property(ADVSS_SOURCE_DIR advanced-scene-switcher-lib SOURCE_DIR)
set(libusb_DEPS_DIR "${ADVSS_SOURCE_DIR}/deps/libusb")
if(OS_LINUX)
find_package(PkgConfig)
if(NOT PKG_CONFIG_FOUND)
message(WARNING "pkg-config not found!\n" "USB condition will be disabled!")
return()
endif()
pkg_check_modules(libusb OPTIONAL libusb)
find_path(
libusb_HEADER_DIR
NAMES libusb.h
PATHS ${libusb_INCLUDEDIR} /usr/include /usr/local/include
/opt/local/include
PATH_SUFFIXES libusb-1.0)
find_library(
libusb_LINK_LIBRARIES
NAMES usb-1.0 usb
PATHS /usr/lib /usr/local/lib /opt/local/lib)
elseif(OS_MACOS)
find_path(
libusb_HEADER_DIR
NAMES libusb.h
PATHS ${libusb_DEPS_DIR}/libusb)
# The NO_DEFAULT_PATH option is set since we have to use the "universal"
# variant of the library, which combines the x86 and arm variants
find_library(
libusb_LINK_LIBRARIES
NAMES usb-1.0.0 usb-1.0 usb
PATHS ${CMAKE_PREFIX_PATH}
PATH_SUFFIXES lib
NO_DEFAULT_PATH)
find_file(
libusb_RUNTIME_LIBRARY
NAMES libusb-1.0.0.dylib
PATHS ${CMAKE_PREFIX_PATH}
PATH_SUFFIXES lib
NO_DEFAULT_PATH)
elseif(OS_WINDOWS)
find_path(
libusb_HEADER_DIR
NAMES libusb.h
PATHS ${libusb_DEPS_DIR}/libusb)
# Intentionally not using find_library since we have to use the libusb-1.0.lib
# file from within the dll folder instead of the lib folder to avoid linking
# issues due to conflicting LIBCMT versions
find_file(
libusb_LINK_LIBRARIES
NAMES libusb-1.0.lib
PATHS ${CMAKE_PREFIX_PATH}
PATH_SUFFIXES dll)
find_file(
libusb_RUNTIME_LIBRARY
NAMES libusb-1.0.dll
PATHS ${CMAKE_PREFIX_PATH}
PATH_SUFFIXES dll)
endif()
if(NOT libusb_HEADER_DIR)
message(WARNING "libusb headers not found. Make sure libusb is installed. "
"USB condition will be disabled!\n\n"
"libusb sources are available under: ${libusb_DEPS_DIR}")
return()
endif()
if(NOT libusb_LINK_LIBRARIES)
message(WARNING "libusb library not found. Make sure libusb is installed. "
"USB condition will be disabled!\n\n"
"libusb sources are available under: ${libusb_DEPS_DIR}")
return()
endif()
if(NOT OS_LINUX AND NOT libusb_RUNTIME_LIBRARY)
message(
WARNING "libusb runtime library not found. Make sure libusb is installed. "
"USB condition will be disabled!\n\n"
"libusb sources are available under: ${libusb_DEPS_DIR}")
return()
endif()
# --- End of section ---
add_library(${PROJECT_NAME} MODULE)
target_sources(
${PROJECT_NAME} PRIVATE macro-condition-usb.cpp macro-condition-usb.hpp
usb-helpers.cpp usb-helpers.hpp)
setup_advss_plugin(${PROJECT_NAME})
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
target_include_directories(${PROJECT_NAME} PRIVATE ${libusb_HEADER_DIR})
target_link_libraries(${PROJECT_NAME} PRIVATE ${libusb_LINK_LIBRARIES})
install_advss_plugin(${PROJECT_NAME})
if(NOT OS_LINUX)
install_advss_plugin_dependency(TARGET ${PROJECT_NAME} DEPENDENCIES
${libusb_RUNTIME_LIBRARY})
endif()

View File

@ -0,0 +1,381 @@
#include "macro-condition-usb.hpp"
#include "layout-helpers.hpp"
#include "obs.hpp"
namespace advss {
const std::string MacroConditionUSB::id = "usb";
bool MacroConditionUSB::_registered = MacroConditionFactory::Register(
MacroConditionUSB::id,
{MacroConditionUSB::Create, MacroConditionUSBEdit::Create,
"AdvSceneSwitcher.condition.usb"});
bool MacroConditionUSB::DeviceMatchOption::Matches(
const std::string &value) const
{
if (!regex.Enabled()) {
return pattern == value;
}
return regex.Matches(value, pattern);
}
void MacroConditionUSB::DeviceMatchOption::Save(obs_data_t *obj,
const char *name) const
{
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "pattern", pattern.c_str());
regex.Save(data);
obs_data_set_obj(obj, name, data);
}
void MacroConditionUSB::DeviceMatchOption::Load(obs_data_t *obj,
const char *name)
{
OBSDataAutoRelease data = obs_data_get_obj(obj, name);
pattern = obs_data_get_string(data, "pattern");
regex.Load(data);
}
bool MacroConditionUSB::CheckCondition()
{
const auto devs = GetUSBDevices();
if (devs.empty()) {
return false;
}
for (const auto &dev : devs) {
if (_vendorID.Matches(dev.vendorID) &&
_productID.Matches(dev.productID) &&
_busNumber.Matches(dev.busNumber) &&
_deviceAddress.Matches(dev.deviceAddress) &&
_vendorName.Matches(dev.vendorName) &&
_productName.Matches(dev.productName) &&
_serialNumber.Matches(dev.serialNumber)) {
SetTempVarValue("vendorID", dev.vendorID);
SetTempVarValue("productID", dev.productID);
SetTempVarValue("busNumber", dev.busNumber);
SetTempVarValue("deviceAddress", dev.deviceAddress);
SetTempVarValue("vendorName", dev.vendorName);
SetTempVarValue("productName", dev.productName);
SetTempVarValue("serialNumber", dev.serialNumber);
return true;
}
}
return false;
}
bool MacroConditionUSB::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
_vendorID.Save(obj, "vendorID");
_productID.Save(obj, "productID");
_busNumber.Save(obj, "busNumber");
_deviceAddress.Save(obj, "deviceAddress");
_vendorName.Save(obj, "vendorName");
_productName.Save(obj, "productName");
_serialNumber.Save(obj, "serialNumber");
return true;
}
bool MacroConditionUSB::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_vendorID.Load(obj, "vendorID");
_productID.Load(obj, "productID");
_busNumber.Load(obj, "busNumber");
_deviceAddress.Load(obj, "deviceAddress");
_vendorName.Load(obj, "vendorName");
_productName.Load(obj, "productName");
_serialNumber.Load(obj, "serialNumber");
return true;
}
void MacroConditionUSB::SetupTempVars()
{
AddTempvar("vendorName",
obs_module_text("AdvSceneSwitcher.tempVar.usb.vendorName"));
AddTempvar("productName",
obs_module_text("AdvSceneSwitcher.tempVar.usb.productName"));
AddTempvar("vendorID",
obs_module_text("AdvSceneSwitcher.tempVar.usb.vendorID"));
AddTempvar("productID",
obs_module_text("AdvSceneSwitcher.tempVar.usb.productID"));
AddTempvar("busNumber",
obs_module_text("AdvSceneSwitcher.tempVar.usb.busNumber"));
AddTempvar(
"deviceAddress",
obs_module_text("AdvSceneSwitcher.tempVar.usb.deviceAddress"));
AddTempvar(
"serialNumber",
obs_module_text("AdvSceneSwitcher.tempVar.usb.serialNumber"));
}
static void setupDevicePropertySelection(QComboBox *list,
const QSet<QString> &items)
{
list->setEditable(true);
list->setMaxVisibleItems(20);
list->setDuplicatesEnabled(false);
for (const auto &item : items) {
list->addItem(item);
}
list->model()->sort(0);
}
void static populateNewLayoutRow(QGridLayout *layout, int &row,
const char *labelText, QComboBox *property,
RegexConfigWidget *regex)
{
layout->addWidget(new QLabel(obs_module_text(labelText)), row, 0);
auto settingsLayout = new QHBoxLayout();
property->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::MinimumExpanding);
settingsLayout->addWidget(property);
settingsLayout->addWidget(regex);
settingsLayout->setContentsMargins(0, 0, 0, 0);
layout->addLayout(settingsLayout, row, 1);
row++;
}
MacroConditionUSBEdit::MacroConditionUSBEdit(
QWidget *parent, std::shared_ptr<MacroConditionUSB> entryData)
: QWidget(parent),
_vendorID(new QComboBox()),
_productID(new QComboBox()),
_busNumber(new QComboBox()),
_deviceAddress(new QComboBox()),
_vendorName(new QComboBox()),
_productName(new QComboBox()),
_serialNumber(new QComboBox()),
_vendorIDRegex(new RegexConfigWidget()),
_productIDRegex(new RegexConfigWidget()),
_busNumberRegex(new RegexConfigWidget()),
_deviceAddressRegex(new RegexConfigWidget()),
_vendorNameRegex(new RegexConfigWidget()),
_productNameRegex(new RegexConfigWidget()),
_serialNumberRegex(new RegexConfigWidget())
{
QSet<QString> vendorIDs;
QSet<QString> productIDs;
QSet<QString> busNumbers;
QSet<QString> deviceAddresses;
QSet<QString> vendorNames;
QSet<QString> productNames;
QSet<QString> serialNumbers;
const auto devs = GetUSBDevices();
for (const auto &dev : devs) {
vendorIDs.insert(QString::fromStdString(dev.vendorID));
productIDs.insert(QString::fromStdString(dev.productID));
busNumbers.insert(QString::fromStdString(dev.busNumber));
deviceAddresses.insert(
QString::fromStdString(dev.deviceAddress));
vendorNames.insert(QString::fromStdString(dev.vendorName));
productNames.insert(QString::fromStdString(dev.productName));
serialNumbers.insert(QString::fromStdString(dev.serialNumber));
}
setupDevicePropertySelection(_vendorID, vendorIDs);
setupDevicePropertySelection(_productID, productIDs);
setupDevicePropertySelection(_busNumber, busNumbers);
setupDevicePropertySelection(_deviceAddress, deviceAddresses);
setupDevicePropertySelection(_vendorName, vendorNames);
setupDevicePropertySelection(_productName, productNames);
setupDevicePropertySelection(_serialNumber, serialNumbers);
QWidget::connect(_vendorID, &QComboBox::currentTextChanged, this,
&MacroConditionUSBEdit::VendorIDChanged);
QWidget::connect(_productID, &QComboBox::currentTextChanged, this,
&MacroConditionUSBEdit::ProductIDChanged);
QWidget::connect(_busNumber, &QComboBox::currentTextChanged, this,
&MacroConditionUSBEdit::BusNumberChanged);
QWidget::connect(_deviceAddress, &QComboBox::currentTextChanged, this,
&MacroConditionUSBEdit::DeviceAddressChanged);
QWidget::connect(_vendorName, &QComboBox::currentTextChanged, this,
&MacroConditionUSBEdit::VendorNameChanged);
QWidget::connect(_productName, &QComboBox::currentTextChanged, this,
&MacroConditionUSBEdit::ProductNameChanged);
QWidget::connect(_serialNumber, &QComboBox::currentTextChanged, this,
&MacroConditionUSBEdit::SerialNumberChanged);
QWidget::connect(_vendorIDRegex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(VendorIDRegexChanged(const RegexConfig &)));
QWidget::connect(_productIDRegex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(ProductIDRegexChanged(const RegexConfig &)));
QWidget::connect(_busNumberRegex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(BusNumberRegexChanged(const RegexConfig &)));
QWidget::connect(_deviceAddressRegex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(DeviceAddressRegexChanged(const RegexConfig &)));
QWidget::connect(_vendorNameRegex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(VendorNameRegexChanged(const RegexConfig &)));
QWidget::connect(_productNameRegex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(ProductNameRegexChanged(const RegexConfig &)));
QWidget::connect(_serialNumberRegex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(SerialNumberRegexChanged(const RegexConfig &)));
int row = 0;
auto devicePropertyLayout = new QGridLayout;
populateNewLayoutRow(devicePropertyLayout, row,
"AdvSceneSwitcher.condition.usb.vendorName",
_vendorName, _vendorNameRegex);
populateNewLayoutRow(devicePropertyLayout, row,
"AdvSceneSwitcher.condition.usb.productName",
_productName, _productNameRegex);
populateNewLayoutRow(devicePropertyLayout, row,
"AdvSceneSwitcher.condition.usb.vendorID",
_vendorID, _vendorIDRegex);
populateNewLayoutRow(devicePropertyLayout, row,
"AdvSceneSwitcher.condition.usb.productID",
_productID, _productIDRegex);
populateNewLayoutRow(devicePropertyLayout, row,
"AdvSceneSwitcher.condition.usb.busNumber",
_busNumber, _busNumberRegex);
populateNewLayoutRow(devicePropertyLayout, row,
"AdvSceneSwitcher.condition.usb.deviceAddress",
_deviceAddress, _deviceAddressRegex);
populateNewLayoutRow(devicePropertyLayout, row,
"AdvSceneSwitcher.condition.usb.serialNumber",
_serialNumber, _serialNumberRegex);
MinimizeSizeOfColumn(devicePropertyLayout, 0);
devicePropertyLayout->setContentsMargins(0, 0, 0, 0);
auto layout = new QVBoxLayout();
layout->addWidget(new QLabel(
obs_module_text("AdvSceneSwitcher.condition.usb.description")));
layout->addLayout(devicePropertyLayout);
if (devs.empty()) {
layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.noDevicesFoundWarning")));
}
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionUSBEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_vendorID->setCurrentText(
QString::fromStdString(_entryData->_vendorID.pattern));
_productID->setCurrentText(
QString::fromStdString(_entryData->_productID.pattern));
_busNumber->setCurrentText(
QString::fromStdString(_entryData->_busNumber.pattern));
_deviceAddress->setCurrentText(
QString::fromStdString(_entryData->_deviceAddress.pattern));
_vendorName->setCurrentText(
QString::fromStdString(_entryData->_vendorName.pattern));
_productName->setCurrentText(
QString::fromStdString(_entryData->_productName.pattern));
_serialNumber->setCurrentText(
QString::fromStdString(_entryData->_serialNumber.pattern));
_vendorIDRegex->SetRegexConfig(_entryData->_vendorID.regex);
_productIDRegex->SetRegexConfig(_entryData->_productID.regex);
_busNumberRegex->SetRegexConfig(_entryData->_busNumber.regex);
_deviceAddressRegex->SetRegexConfig(_entryData->_deviceAddress.regex);
_vendorNameRegex->SetRegexConfig(_entryData->_vendorName.regex);
_productNameRegex->SetRegexConfig(_entryData->_productName.regex);
_serialNumberRegex->SetRegexConfig(_entryData->_serialNumber.regex);
adjustSize();
updateGeometry();
}
void MacroConditionUSBEdit::VendorIDChanged(const QString &text)
{
GUARD_LOADING_AND_LOCK();
_entryData->_vendorID.pattern = text.toStdString();
}
void MacroConditionUSBEdit::ProductIDChanged(const QString &text)
{
GUARD_LOADING_AND_LOCK();
_entryData->_productID.pattern = text.toStdString();
}
void MacroConditionUSBEdit::BusNumberChanged(const QString &text)
{
GUARD_LOADING_AND_LOCK();
_entryData->_busNumber.pattern = text.toStdString();
}
void MacroConditionUSBEdit::DeviceAddressChanged(const QString &text)
{
GUARD_LOADING_AND_LOCK();
_entryData->_deviceAddress.pattern = text.toStdString();
}
void MacroConditionUSBEdit::VendorNameChanged(const QString &text)
{
GUARD_LOADING_AND_LOCK();
_entryData->_vendorName.pattern = text.toStdString();
}
void MacroConditionUSBEdit::ProductNameChanged(const QString &text)
{
GUARD_LOADING_AND_LOCK();
_entryData->_productName.pattern = text.toStdString();
}
void MacroConditionUSBEdit::SerialNumberChanged(const QString &text)
{
GUARD_LOADING_AND_LOCK();
_entryData->_serialNumber.pattern = text.toStdString();
}
void MacroConditionUSBEdit::VendorIDRegexChanged(const RegexConfig &regex)
{
GUARD_LOADING_AND_LOCK();
_entryData->_vendorID.regex = regex;
}
void MacroConditionUSBEdit::ProductIDRegexChanged(const RegexConfig &regex)
{
GUARD_LOADING_AND_LOCK();
_entryData->_productID.regex = regex;
}
void MacroConditionUSBEdit::BusNumberRegexChanged(const RegexConfig &regex)
{
GUARD_LOADING_AND_LOCK();
_entryData->_busNumber.regex = regex;
}
void MacroConditionUSBEdit::DeviceAddressRegexChanged(const RegexConfig &regex)
{
GUARD_LOADING_AND_LOCK();
_entryData->_deviceAddress.regex = regex;
}
void MacroConditionUSBEdit::VendorNameRegexChanged(const RegexConfig &regex)
{
GUARD_LOADING_AND_LOCK();
_entryData->_vendorName.regex = regex;
}
void MacroConditionUSBEdit::ProductNameRegexChanged(const RegexConfig &regex)
{
GUARD_LOADING_AND_LOCK();
_entryData->_productName.regex = regex;
}
void MacroConditionUSBEdit::SerialNumberRegexChanged(const RegexConfig &regex)
{
GUARD_LOADING_AND_LOCK();
_entryData->_serialNumber.regex = regex;
}
} // namespace advss

View File

@ -0,0 +1,97 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "usb-helpers.hpp"
#include "regex-config.hpp"
#include <QPushButton>
namespace advss {
class MacroConditionUSB : public MacroCondition {
public:
MacroConditionUSB(Macro *m) : MacroCondition(m, true) {}
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetId() const { return id; };
static std::shared_ptr<MacroCondition> Create(Macro *m)
{
return std::make_shared<MacroConditionUSB>(m);
}
struct DeviceMatchOption {
std::string pattern = ".*";
RegexConfig regex = RegexConfig(true);
bool Matches(const std::string &value) const;
void Save(obs_data_t *, const char *) const;
void Load(obs_data_t *, const char *);
};
DeviceMatchOption _vendorID;
DeviceMatchOption _productID;
DeviceMatchOption _busNumber;
DeviceMatchOption _deviceAddress;
DeviceMatchOption _vendorName;
DeviceMatchOption _productName;
DeviceMatchOption _serialNumber;
private:
void SetupTempVars();
static bool _registered;
static const std::string id;
};
class MacroConditionUSBEdit : public QWidget {
Q_OBJECT
public:
MacroConditionUSBEdit(QWidget *parent,
std::shared_ptr<MacroConditionUSB> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionUSBEdit(
parent,
std::dynamic_pointer_cast<MacroConditionUSB>(cond));
}
private slots:
void VendorIDChanged(const QString &text);
void ProductIDChanged(const QString &text);
void BusNumberChanged(const QString &text);
void DeviceAddressChanged(const QString &text);
void VendorNameChanged(const QString &text);
void ProductNameChanged(const QString &text);
void SerialNumberChanged(const QString &text);
void VendorIDRegexChanged(const RegexConfig &regex);
void ProductIDRegexChanged(const RegexConfig &regex);
void BusNumberRegexChanged(const RegexConfig &regex);
void DeviceAddressRegexChanged(const RegexConfig &regex);
void VendorNameRegexChanged(const RegexConfig &regex);
void ProductNameRegexChanged(const RegexConfig &regex);
void SerialNumberRegexChanged(const RegexConfig &regex);
private:
QComboBox *_vendorID;
QComboBox *_productID;
QComboBox *_busNumber;
QComboBox *_deviceAddress;
QComboBox *_vendorName;
QComboBox *_productName;
QComboBox *_serialNumber;
RegexConfigWidget *_vendorIDRegex;
RegexConfigWidget *_productIDRegex;
RegexConfigWidget *_busNumberRegex;
RegexConfigWidget *_deviceAddressRegex;
RegexConfigWidget *_vendorNameRegex;
RegexConfigWidget *_productNameRegex;
RegexConfigWidget *_serialNumberRegex;
std::shared_ptr<MacroConditionUSB> _entryData;
bool _loading = true;
};
} // namespace advss

233
plugins/usb/usb-helpers.cpp Normal file
View File

@ -0,0 +1,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 *ctx,
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

View File

@ -0,0 +1,27 @@
#pragma once
#include <QString>
#include <QStringList>
#include <string>
#include <vector>
namespace advss {
struct USBDeviceInfo {
std::string vendorID;
std::string productID;
std::string busNumber;
std::string deviceAddress;
std::string vendorName;
std::string productName;
std::string serialNumber;
std::string ToString() const;
QString ToQString() const;
bool operator==(const USBDeviceInfo &other);
};
std::vector<USBDeviceInfo> GetUSBDevices();
QStringList GetUSBDevicesStringList();
} // namespace advss