mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-03-21 17:34:57 -05:00
Add USB condition type
It allows users to check if a given USB device is currently connected
This commit is contained in:
parent
7d120d8b6a
commit
3ff9a1f270
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
110
plugins/usb/CMakeLists.txt
Normal 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()
|
||||
381
plugins/usb/macro-condition-usb.cpp
Normal file
381
plugins/usb/macro-condition-usb.cpp
Normal 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 ®ex)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_vendorID.regex = regex;
|
||||
}
|
||||
|
||||
void MacroConditionUSBEdit::ProductIDRegexChanged(const RegexConfig ®ex)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_productID.regex = regex;
|
||||
}
|
||||
|
||||
void MacroConditionUSBEdit::BusNumberRegexChanged(const RegexConfig ®ex)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_busNumber.regex = regex;
|
||||
}
|
||||
|
||||
void MacroConditionUSBEdit::DeviceAddressRegexChanged(const RegexConfig ®ex)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_deviceAddress.regex = regex;
|
||||
}
|
||||
|
||||
void MacroConditionUSBEdit::VendorNameRegexChanged(const RegexConfig ®ex)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_vendorName.regex = regex;
|
||||
}
|
||||
|
||||
void MacroConditionUSBEdit::ProductNameRegexChanged(const RegexConfig ®ex)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_productName.regex = regex;
|
||||
}
|
||||
|
||||
void MacroConditionUSBEdit::SerialNumberRegexChanged(const RegexConfig ®ex)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_serialNumber.regex = regex;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
97
plugins/usb/macro-condition-usb.hpp
Normal file
97
plugins/usb/macro-condition-usb.hpp
Normal 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 ®ex);
|
||||
void ProductIDRegexChanged(const RegexConfig ®ex);
|
||||
void BusNumberRegexChanged(const RegexConfig ®ex);
|
||||
void DeviceAddressRegexChanged(const RegexConfig ®ex);
|
||||
void VendorNameRegexChanged(const RegexConfig ®ex);
|
||||
void ProductNameRegexChanged(const RegexConfig ®ex);
|
||||
void SerialNumberRegexChanged(const RegexConfig ®ex);
|
||||
|
||||
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
233
plugins/usb/usb-helpers.cpp
Normal 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
|
||||
27
plugins/usb/usb-helpers.hpp
Normal file
27
plugins/usb/usb-helpers.hpp
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user