diff --git a/CMakeLists.txt b/CMakeLists.txt index 28221d25688..b814c8f4d79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2381,6 +2381,7 @@ cmake_dependent_option(HIDAPI_STATIC "Link HIDAPI library statically" OFF "HIDAP if(HID) target_sources(mixxx-lib PRIVATE src/controllers/hid/hidcontroller.cpp + src/controllers/hid/hiddevice.cpp src/controllers/hid/hidenumerator.cpp src/controllers/hid/hidcontrollerpreset.cpp src/controllers/hid/hidcontrollerpresetfilehandler.cpp diff --git a/build/features.py b/build/features.py index 2dea3ff0034..0a050e997a5 100644 --- a/build/features.py +++ b/build/features.py @@ -101,6 +101,7 @@ def configure(self, build, conf): def sources(self, build): sources = ['src/controllers/hid/hidcontroller.cpp', 'src/controllers/hid/hidcontrollerpreset.cpp', + 'src/controllers/hid/hiddevice.cpp', 'src/controllers/hid/hidenumerator.cpp', 'src/controllers/hid/hidcontrollerpresetfilehandler.cpp'] diff --git a/src/controllers/hid/hidcontroller.cpp b/src/controllers/hid/hidcontroller.cpp index af9003cd498..d050dd90ee9 100644 --- a/src/controllers/hid/hidcontroller.cpp +++ b/src/controllers/hid/hidcontroller.cpp @@ -1,85 +1,24 @@ -/** - * @file hidcontroller.cpp - * @author Sean M. Pappalardo spappalardo@mixxx.org - * @date Sun May 1 2011 - * @brief HID controller backend - * - */ - -#include -#include - -#include "util/path.h" // for PATH_MAX on Windows #include "controllers/hid/hidcontroller.h" -#include "controllers/defs_controllers.h" -#include "util/trace.h" + #include "controllers/controllerdebug.h" +#include "controllers/defs_controllers.h" +#include "controllers/hid/hidcontrollerpresetfilehandler.h" +#include "util/string.h" #include "util/time.h" - -ControllerJSProxy* HidController::jsProxy() { - return new HidControllerJSProxy(this); -} +#include "util/trace.h" namespace { constexpr int kReportIdSize = 1; constexpr int kMaxHidErrorMessageSize = 512; } // namespace -HidController::HidController(const hid_device_info& deviceInfo) - : Controller(), +HidController::HidController( + mixxx::hid::DeviceInfo&& deviceInfo) + : m_deviceInfo(std::move(deviceInfo)), m_pHidDevice(nullptr), m_iPollingBufferIndex(0) { - // Copy required variables from deviceInfo, which will be freed after - // this class is initialized by caller. - hid_vendor_id = deviceInfo.vendor_id; - hid_product_id = deviceInfo.product_id; - hid_interface_number = deviceInfo.interface_number; - if (hid_interface_number == -1) { - // OS/X and windows don't use interface numbers, but usage_page/usage - hid_usage_page = deviceInfo.usage_page; - hid_usage = deviceInfo.usage; - } else { - // Linux hidapi does not set value for usage_page or usage and uses - // interface number to identify subdevices - hid_usage_page = 0; - hid_usage = 0; - } - - // Don't trust path to be null terminated. - hid_path = new char[PATH_MAX + 1]; - strncpy(hid_path, deviceInfo.path, PATH_MAX); - hid_path[PATH_MAX] = 0; - - hid_serial_raw = NULL; - if (deviceInfo.serial_number != NULL) { - size_t serial_max_length = 512; - hid_serial_raw = new wchar_t[serial_max_length+1]; - wcsncpy(hid_serial_raw, deviceInfo.serial_number, serial_max_length); - hid_serial_raw[serial_max_length] = 0; - } - - hid_serial = safeDecodeWideString(deviceInfo.serial_number, 512); - hid_manufacturer = safeDecodeWideString(deviceInfo.manufacturer_string, 512); - hid_product = safeDecodeWideString(deviceInfo.product_string, 512); - - guessDeviceCategory(); - - // Set the Unique Identifier to the serial_number - m_sUID = hid_serial; - - //Note: We include the last 4 digits of the serial number and the - // interface number to allow the user (and Mixxx!) to keep track of - // which is which - if (hid_interface_number < 0) { - setDeviceName( - QString("%1 %2").arg(hid_product, hid_serial.right(4))); - } else { - setDeviceName(QString("%1 %2_%3") - .arg(hid_product, - hid_serial.right(4), - QString::number(hid_interface_number))); - m_sUID.append(QString::number(hid_interface_number)); - } + setDeviceCategory(mixxx::hid::DeviceCategory::guessFromDeviceInfo(m_deviceInfo)); + setDeviceName(m_deviceInfo.formatName()); // All HID devices are full-duplex setInputDevice(true); @@ -90,8 +29,6 @@ HidController::~HidController() { if (isOpen()) { close(); } - delete [] hid_path; - delete [] hid_serial_raw; } QString HidController::presetExtension() { @@ -113,76 +50,11 @@ void HidController::visit(const HidControllerPreset* preset) { bool HidController::matchPreset(const PresetInfo& preset) { const QList& products = preset.getProducts(); for (const auto& product : products) { - if (matchProductInfo(product)) + if (m_deviceInfo.matchProductInfo(product)) { return true; - } - return false; -} - -bool HidController::matchProductInfo(const ProductInfo& product) { - int value; - bool ok; - // Product and vendor match is always required - value = product.vendor_id.toInt(&ok,16); - if (!ok || hid_vendor_id!=value) return false; - value = product.product_id.toInt(&ok,16); - if (!ok || hid_product_id!=value) return false; - - // Optionally check against interface_number / usage_page && usage - if (hid_interface_number!=-1) { - value = product.interface_number.toInt(&ok,16); - if (!ok || hid_interface_number!=value) return false; - } else { - value = product.usage_page.toInt(&ok,16); - if (!ok || hid_usage_page!=value) return false; - - value = product.usage.toInt(&ok,16); - if (!ok || hid_usage!=value) return false; - } - // Match found - return true; -} - -void HidController::guessDeviceCategory() { - // This should be done somehow else, I know. But at least we get started with - // the idea of mapping this information - QString info; - if (hid_interface_number==-1) { - if (hid_usage_page==0x1) { - switch (hid_usage) { - case 0x2: info = tr("Generic HID Mouse"); break; - case 0x4: info = tr("Generic HID Joystick"); break; - case 0x5: info = tr("Generic HID Gamepad"); break; - case 0x6: info = tr("Generic HID Keyboard"); break; - case 0x8: info = tr("Generic HID Multiaxis Controller"); break; - default: info = tr("Unknown HID Desktop Device") + - QStringLiteral(" 0x") + QString::number(hid_usage_page, 16) + - QStringLiteral("/0x") + QString::number(hid_usage, 16); - break; - } - } else if (hid_vendor_id==0x5ac) { - // Apple laptop special HID devices - if (hid_product_id==0x8242) { - info = tr("HID Infrared Control"); - } else { - info = tr("Unknown Apple HID Device") + - QStringLiteral(" 0x") + QString::number(hid_usage_page, 16) + - QStringLiteral("/0x") + QString::number(hid_usage, 16); - } - } else { - // Fill in the usage page and usage fields for debugging info - info = tr("HID Unknown Device") + - QStringLiteral(" 0x") + QString::number(hid_usage_page, 16) + - QStringLiteral("/0x") + QString::number(hid_usage, 16); } - } else { - // Guess linux device types somehow as well. Or maybe just fill in the - // interface number? - info = tr("HID Interface Number") + - QStringLiteral(" 0x") + QString::number(hid_usage_page, 16) + - QStringLiteral("/0x") + QString::number(hid_usage, 16); } - setDeviceCategory(info); + return false; } int HidController::open() { @@ -192,29 +64,36 @@ int HidController::open() { } // Open device by path - controllerDebug("Opening HID device" << getName() << "by HID path" << hid_path); + controllerDebug("Opening HID device" << getName() << "by HID path" + << m_deviceInfo.pathRaw()); - m_pHidDevice = hid_open_path(hid_path); + m_pHidDevice = hid_open_path(m_deviceInfo.pathRaw()); // If that fails, try to open device with vendor/product/serial # - if (m_pHidDevice == NULL) { + if (!m_pHidDevice) { controllerDebug("Failed. Trying to open with make, model & serial no:" - << hid_vendor_id << hid_product_id << hid_serial); - m_pHidDevice = hid_open(hid_vendor_id, hid_product_id, hid_serial_raw); + << m_deviceInfo.vendorId() << m_deviceInfo.productId() + << m_deviceInfo.serialNumber()); + m_pHidDevice = hid_open( + m_deviceInfo.vendorId(), + m_deviceInfo.productId(), + m_deviceInfo.serialNumberRaw()); } // If it does fail, try without serial number WARNING: This will only open // one of multiple identical devices - if (m_pHidDevice == NULL) { + if (!m_pHidDevice) { qWarning() << "Unable to open specific HID device" << getName() << "Trying now with just make and model." << "(This may only open the first of multiple identical devices.)"; - m_pHidDevice = hid_open(hid_vendor_id, hid_product_id, NULL); + m_pHidDevice = hid_open(m_deviceInfo.vendorId(), + m_deviceInfo.productId(), + nullptr); } // If that fails, we give up! - if (m_pHidDevice == NULL) { - qWarning() << "Unable to open HID device" << getName(); + if (!m_pHidDevice) { + qWarning() << "Unable to open HID device" << getName(); return -1; } @@ -324,15 +203,19 @@ void HidController::sendBytesReport(QByteArray data, unsigned int reportID) { if (result == -1) { if (ControllerDebug::enabled()) { qWarning() << "Unable to send data to" << getName() - << "serial #" << hid_serial << ":" - << safeDecodeWideString(hid_error(m_pHidDevice), kMaxHidErrorMessageSize); + << "serial #" << m_deviceInfo.serialNumber() << ":" + << mixxx::convertWCStringToQString( + hid_error(m_pHidDevice), + kMaxHidErrorMessageSize); } else { qWarning() << "Unable to send data to" << getName() << ":" - << safeDecodeWideString(hid_error(m_pHidDevice), kMaxHidErrorMessageSize); + << mixxx::convertWCStringToQString( + hid_error(m_pHidDevice), + kMaxHidErrorMessageSize); } } else { controllerDebug(result << "bytes sent to" << getName() - << "serial #" << hid_serial + << "serial #" << m_deviceInfo.serialNumber() << "(including report ID of" << reportID << ")"); } } @@ -353,33 +236,19 @@ void HidController::sendFeatureReport( reinterpret_cast(dataArray.constData()), dataArray.size()); if (result == -1) { - qWarning() << "sendFeatureReport is unable to send data to" << getName() - << "serial #" << hid_serial << ":" - << safeDecodeWideString(hid_error(m_pHidDevice), kMaxHidErrorMessageSize); + qWarning() << "sendFeatureReport is unable to send data to" + << getName() << "serial #" << m_deviceInfo.serialNumber() + << ":" + << mixxx::convertWCStringToQString( + hid_error(m_pHidDevice), + kMaxHidErrorMessageSize); } else { controllerDebug(result << "bytes sent by sendFeatureReport to" << getName() - << "serial #" << hid_serial + << "serial #" << m_deviceInfo.serialNumber() << "(including report ID of" << reportID << ")"); } } -//static -QString HidController::safeDecodeWideString(const wchar_t* pStr, size_t max_length) { - if (pStr == NULL) { - return QString(); - } - // find a terminating 0 or take all chars - int size = 0; - while ((size < (int)max_length) && (pStr[size] != 0)) { - ++size; - } - // inlining QString::fromWCharArray() - // We cannot use Qts wchar_t functions, since they may work or not - // depending on the '/Zc:wchar_t-' build flag in the Qt configs - // on Windows build - if (sizeof(wchar_t) == sizeof(QChar)) { - return QString::fromUtf16((const ushort *)pStr, size); - } else { - return QString::fromUcs4((uint *)pStr, size); - } +ControllerJSProxy* HidController::jsProxy() { + return new HidControllerJSProxy(this); } diff --git a/src/controllers/hid/hidcontroller.h b/src/controllers/hid/hidcontroller.h index 0faa94f4345..bfb928078dd 100644 --- a/src/controllers/hid/hidcontroller.h +++ b/src/controllers/hid/hidcontroller.h @@ -1,26 +1,15 @@ -/** - * @file hidcontroller.h - * @author Sean M. Pappalardo spappalardo@mixxx.org - * @date Sun May 1 2011 - * @brief HID controller backend - */ - -#ifndef HIDCONTROLLER_H -#define HIDCONTROLLER_H - -#include - -#include +#pragma once #include "controllers/controller.h" #include "controllers/hid/hidcontrollerpreset.h" -#include "controllers/hid/hidcontrollerpresetfilehandler.h" +#include "controllers/hid/hiddevice.h" #include "util/duration.h" class HidController final : public Controller { Q_OBJECT public: - HidController(const hid_device_info& deviceInfo); + explicit HidController( + mixxx::hid::DeviceInfo&& deviceInfo); ~HidController() override; ControllerJSProxy* jsProxy() override; @@ -28,9 +17,8 @@ class HidController final : public Controller { QString presetExtension() override; ControllerPresetPointer getPreset() const override { - HidControllerPreset* pClone = new HidControllerPreset(); - *pClone = m_preset; - return ControllerPresetPointer(pClone); + return ControllerPresetPointer( + new HidControllerPreset(m_preset)); } void visit(const MidiControllerPreset* preset) override; @@ -48,8 +36,6 @@ class HidController final : public Controller { bool matchPreset(const PresetInfo& preset) override; - static QString safeDecodeWideString(const wchar_t* pStr, size_t max_length); - protected: void sendReport(QList data, unsigned int length, unsigned int reportID); @@ -74,22 +60,8 @@ class HidController final : public Controller { return &m_preset; } - bool matchProductInfo(const ProductInfo& product); - void guessDeviceCategory(); - - // Local copies of things we need from hid_device_info - int hid_interface_number; - unsigned short hid_vendor_id; - unsigned short hid_product_id; - unsigned short hid_usage_page; - unsigned short hid_usage; - char* hid_path; - wchar_t* hid_serial_raw; - QString hid_serial; - QString hid_manufacturer; - QString hid_product; - - QString m_sUID; + const mixxx::hid::DeviceInfo m_deviceInfo; + hid_device* m_pHidDevice; HidControllerPreset m_preset; @@ -126,5 +98,3 @@ class HidControllerJSProxy : public ControllerJSProxy { private: HidController* m_pHidController; }; - -#endif diff --git a/src/controllers/hid/hiddevice.cpp b/src/controllers/hid/hiddevice.cpp new file mode 100644 index 00000000000..bbc8f2a1613 --- /dev/null +++ b/src/controllers/hid/hiddevice.cpp @@ -0,0 +1,205 @@ +#include "controllers/hid/hiddevice.h" + +#include + +#include "controllers/controllerpresetinfo.h" +#include "util/path.h" // for PATH_MAX on Windows +#include "util/string.h" + +namespace { + +constexpr unsigned short kGenericDesktopUsagePage = 0x01; + +constexpr unsigned short kGenericDesktopPointerUsage = 0x01; +constexpr unsigned short kGenericDesktopMouseUsage = 0x02; +constexpr unsigned short kGenericDesktopJoystickUsage = 0x04; +constexpr unsigned short kGenericDesktopGamePadUsage = 0x05; +constexpr unsigned short kGenericDesktopKeyboardUsage = 0x06; +constexpr unsigned short kGenericDesktopKeypadUsage = 0x07; +constexpr unsigned short kGenericDesktopMultiaxisControllerUsage = 0x08; + +constexpr unsigned short kAppleInfraredControlProductId = 0x8242; + +constexpr std::size_t kDeviceInfoStringMaxLength = 512; + +} // namespace + +namespace mixxx { + +namespace hid { + +DeviceInfo::DeviceInfo( + const hid_device_info& device_info) + : vendor_id(device_info.vendor_id), + product_id(device_info.product_id), + release_number(device_info.release_number), + usage_page(device_info.usage_page), + usage(device_info.usage), + interface_number(device_info.interface_number), + m_pathRaw(device_info.path, mixxx::strnlen(device_info.path, PATH_MAX)), + m_serialNumberRaw(device_info.serial_number, + mixxx::wcsnlen(device_info.serial_number, + kDeviceInfoStringMaxLength)), + m_manufacturerString(mixxx::convertWCStringToQString( + device_info.manufacturer_string, + mixxx::wcsnlen(device_info.manufacturer_string, + kDeviceInfoStringMaxLength))), + m_productString(mixxx::convertWCStringToQString(device_info.product_string, + mixxx::wcsnlen(device_info.product_string, + kDeviceInfoStringMaxLength))), + m_serialNumber(mixxx::convertWCStringToQString( + m_serialNumberRaw.data(), m_serialNumberRaw.size())) { +} + +QString DeviceInfo::formatInterface() const { + if (interface_number < 0) { + return QString(); + } + return QChar('#') + QString::number(interface_number); +} + +QString DeviceInfo::formatVID() const { + return QStringLiteral("%1").arg(vendor_id, 4, 16, QLatin1Char('0')); +} + +QString DeviceInfo::formatPID() const { + return QStringLiteral("%1").arg(product_id, 4, 16, QLatin1Char('0')); +} + +QString DeviceInfo::formatReleaseNumber() const { + return QString::number(releaseNumberBCD(), 16); +} + +QString DeviceInfo::formatUsage() const { + if (usage_page == 0 && usage == 0) { + DEBUG_ASSERT(!formatInterface().isEmpty()); + return QString(); + } + return QStringLiteral("%1").arg(usage_page, 4, 16, QLatin1Char('0')) + + QLatin1Char(':') + + QStringLiteral("%1").arg(usage, 4, 16, QLatin1Char('0')); +} + +QString DeviceInfo::formatName() const { + // We include the last 4 digits of the serial number and the + // interface number to allow the user (and Mixxx!) to keep + // track of which is which + const auto serialSuffix = serialNumber().right(4); + if (interface_number >= 0) { + return productString() + + QChar(' ') + + serialSuffix + + QChar('_') + + QString::number(interface_number); + } else { + return productString() + + QChar(' ') + + serialSuffix; + } +} + +QDebug operator<<(QDebug dbg, const DeviceInfo& deviceInfo) { + QStringList parts; + parts.reserve(8); + // "VID:PID vReleaseNumber" + parts.append(deviceInfo.formatVID() + + QLatin1Char(':') + + deviceInfo.formatPID() + + QLatin1String(" r") + + deviceInfo.formatReleaseNumber()); + const QString usage = deviceInfo.formatUsage(); + if (!usage.isEmpty()) { + parts.append(QStringLiteral("Usage: ") + usage); + } + const QString interface = deviceInfo.formatInterface(); + if (!interface.isEmpty()) { + parts.append(QStringLiteral("Interface: ") + interface); + } + if (!deviceInfo.manufacturerString().isEmpty()) { + parts.append(QStringLiteral("Manufacturer: ") + deviceInfo.manufacturerString()); + } + if (!deviceInfo.productString().isEmpty()) { + parts.append(QStringLiteral("Product: ") + deviceInfo.productString()); + } + if (!deviceInfo.serialNumber().isEmpty()) { + parts.append(QStringLiteral("S/N: ") + deviceInfo.serialNumber()); + } + const auto dbgState = QDebugStateSaver(dbg); + return dbg.nospace().noquote() + << QStringLiteral("{ ") + << parts.join(QStringLiteral(" | ")) + << QStringLiteral(" }"); +} + +QString DeviceCategory::guessFromDeviceInfoImpl( + const DeviceInfo& deviceInfo) const { + // This should be done somehow else, I know. But at least we get started with + // the idea of mapping this information + const QString interface = deviceInfo.formatInterface(); + if (!interface.isEmpty()) { + // TODO: Guess linux device types somehow as well + // or maybe just fill in the interface number? + return tr("HID Interface %1: ").arg(interface) + deviceInfo.formatUsage(); + } + if (deviceInfo.usage_page == kGenericDesktopUsagePage) { + switch (deviceInfo.usage) { + case kGenericDesktopPointerUsage: + return tr("Generic HID Pointer"); + case kGenericDesktopMouseUsage: + return tr("Generic HID Mouse"); + case kGenericDesktopJoystickUsage: + return tr("Generic HID Joystick"); + case kGenericDesktopGamePadUsage: + return tr("Generic HID Game Pad"); + case kGenericDesktopKeyboardUsage: + return tr("Generic HID Keyboard"); + case kGenericDesktopKeypadUsage: + return tr("Generic HID Keypad"); + case kGenericDesktopMultiaxisControllerUsage: + return tr("Generic HID Multi-axis Controller"); + default: + return tr("Unknown HID Desktop Device: ") + deviceInfo.formatUsage(); + } + } else if (deviceInfo.vendor_id == kAppleVendorId) { + // Apple laptop special HID devices + if (deviceInfo.product_id == kAppleInfraredControlProductId) { + return tr("Apple HID Infrared Control"); + } else { + return tr("Unknown Apple HID Device: ") + deviceInfo.formatUsage(); + } + } else { + return tr("Unknown HID Device: ") + deviceInfo.formatUsage(); + } +} + +bool DeviceInfo::matchProductInfo( + const ProductInfo& product) const { + bool ok; + // Product and vendor match is always required + if (vendor_id != product.vendor_id.toInt(&ok, 16) || !ok) { + return false; + } + if (product_id != product.product_id.toInt(&ok, 16) || !ok) { + return false; + } + + // Optionally check against interface_number / usage_page && usage + if (interface_number >= 0) { + if (interface_number != product.interface_number.toInt(&ok, 16) || !ok) { + return false; + } + } else { + if (usage_page != product.usage_page.toInt(&ok, 16) || !ok) { + return false; + } + if (usage != product.usage.toInt(&ok, 16) || !ok) { + return false; + } + } + // Match found + return true; +} + +} // namespace hid + +} // namespace mixxx diff --git a/src/controllers/hid/hiddevice.h b/src/controllers/hid/hiddevice.h new file mode 100644 index 00000000000..28d1539c347 --- /dev/null +++ b/src/controllers/hid/hiddevice.h @@ -0,0 +1,119 @@ +#pragma once + +#include + +#include +#include + +struct ProductInfo; + +namespace mixxx { + +namespace hid { + +constexpr unsigned short kAppleVendorId = 0x5ac; + +/// Detached copy of `struct hid_device_info`. +/// +/// Stores a detached copy of hid_device_info and its contents. +/// +/// All instances of hid_device_info are returned by the HIDAPI +/// library as a linked list. The memory of both the members +/// of this list as well as their contents are managed by the +/// library and freed immediately after iterating through this +/// list. +/// +/// Includes some basic validations and implicit conversion to +/// QString if needed. +class DeviceInfo final { + public: + explicit DeviceInfo( + const hid_device_info& device_info); + + // The VID. + unsigned short vendorId() const { + return vendor_id; + } + // The PID. + unsigned short productId() const { + return product_id; + } + /// The release number as a binary-coded decimal (BCD). + unsigned short releaseNumberBCD() const { + return release_number; + } + + /// The raw path, needed for subsequent HIDAPI requests. + const char* pathRaw() const { + return m_pathRaw.c_str(); + } + /// The raw serial number, needed for subsequent HIDAPI requests. + const wchar_t* serialNumberRaw() const { + return m_serialNumberRaw.c_str(); + } + + const QString& manufacturerString() const { + return m_manufacturerString; + } + const QString& productString() const { + return m_productString; + } + const QString& serialNumber() const { + return m_serialNumber; + } + + bool isValid() const { + return !productString().isNull() && !serialNumber().isNull(); + } + + QString formatVID() const; + QString formatPID() const; + QString formatReleaseNumber() const; + QString formatInterface() const; + QString formatUsage() const; + QString formatName() const; + + bool matchProductInfo( + const ProductInfo& productInfo) const; + + private: + friend class DeviceCategory; + friend QDebug operator<<( + QDebug dbg, + const DeviceInfo& deviceInfo); + + // members from hid_device_info + unsigned short vendor_id; + unsigned short product_id; + unsigned short release_number; + unsigned short usage_page; + unsigned short usage; + int interface_number; + + std::string m_pathRaw; + std::wstring m_serialNumberRaw; + + QString m_manufacturerString; + QString m_productString; + QString m_serialNumber; +}; + +class DeviceCategory final : public QObject { + // QObject needed for i18n device category + Q_OBJECT + public: + static QString guessFromDeviceInfo( + const DeviceInfo& deviceInfo) { + return DeviceCategory().guessFromDeviceInfoImpl(deviceInfo); + } + + private: + QString guessFromDeviceInfoImpl( + const DeviceInfo& deviceInfo) const; + + DeviceCategory() = default; +}; + +} // namespace hid + +} // namespace mixxx diff --git a/src/controllers/hid/hidenumerator.cpp b/src/controllers/hid/hidenumerator.cpp index be4a150b890..1863b712af2 100644 --- a/src/controllers/hid/hidenumerator.cpp +++ b/src/controllers/hid/hidenumerator.cpp @@ -1,10 +1,3 @@ -/** -* @file hidenumerator.cpp -* @author Sean Pappalardo spappalardo@mixxx.org -* @date Sat Apr 30 2011 -* @brief This class handles discovery and enumeration of DJ controllers that use the USB-HID protocol -*/ - #include "controllers/hid/hidenumerator.h" #include @@ -64,54 +57,32 @@ HidEnumerator::~HidEnumerator() { } QList HidEnumerator::queryDevices() { - qDebug() << "Scanning HID devices:"; - - struct hid_device_info *devs, *cur_dev; - devs = hid_enumerate(0x0, 0x0); + qInfo() << "Scanning USB HID devices"; - for (cur_dev = devs; cur_dev; cur_dev = cur_dev->next) { - if (!recognizeDevice(*cur_dev)) { - // OS/X and windows use usage_page/usage not interface_number - qDebug() - << "Skipping" - << HidController::safeDecodeWideString( - cur_dev->manufacturer_string, 512) - << HidController::safeDecodeWideString( - cur_dev->product_string, 512) - << QString("r%1").arg(cur_dev->release_number) << "S/N" - << HidController::safeDecodeWideString( - cur_dev->serial_number, 512) - << (cur_dev->interface_number == -1 - ? QString("Usage Page %1 Usage %2") - .arg(QString::number( - cur_dev->usage_page), - QString::number( - cur_dev->usage)) - : QString("Interface %1") - .arg(cur_dev->interface_number)); + hid_device_info* device_info_list = hid_enumerate(0x0, 0x0); + for (const auto* device_info = device_info_list; + device_info; + device_info = device_info->next) { + auto deviceInfo = mixxx::hid::DeviceInfo(*device_info); + if (!recognizeDevice(*device_info)) { + qInfo() + << "Excluding USB HID device" + << deviceInfo; continue; } + qInfo() << "Found USB HID device:" + << deviceInfo; - // OS/X and windows use usage_page/usage not interface_number - qDebug() << "Found" - << HidController::safeDecodeWideString(cur_dev->manufacturer_string, 512) - << HidController::safeDecodeWideString(cur_dev->product_string, 512) - << QString("r%1").arg(cur_dev->release_number) - << "S/N" << HidController::safeDecodeWideString(cur_dev->serial_number, 512) - << (cur_dev->interface_number == -1 ? QString("Usage Page %1 Usage %2").arg( - QString::number(cur_dev->usage_page), - QString::number(cur_dev->usage)) : - QString("Interface %1").arg(cur_dev->interface_number)); - - if (!cur_dev->serial_number && !cur_dev->product_string) { - qWarning() << "USB permissions problem (or device error.) Your account needs write access to USB HID controllers."; + if (!deviceInfo.isValid()) { + qWarning() << "USB permissions problem or device error." + << "Your account needs write access to USB HID controllers."; continue; } - HidController* currentDevice = new HidController(*cur_dev); - m_devices.push_back(currentDevice); + HidController* newDevice = new HidController(std::move(deviceInfo)); + m_devices.push_back(newDevice); } - hid_free_enumeration(devs); + hid_free_enumeration(device_info_list); return m_devices; } diff --git a/src/library/basetrackcache.h b/src/library/basetrackcache.h index 64b346312ed..95a57b689d6 100644 --- a/src/library/basetrackcache.h +++ b/src/library/basetrackcache.h @@ -122,7 +122,7 @@ class BaseTrackCache : public QObject { const std::unique_ptr m_pQueryParser; - const StringCollator m_collator; + const mixxx::StringCollator m_collator; QStringList m_searchColumns; QVector m_searchColumnIndices; diff --git a/src/util/db/dbconnection.cpp b/src/util/db/dbconnection.cpp index 850c47c55aa..bd0d58d5021 100644 --- a/src/util/db/dbconnection.cpp +++ b/src/util/db/dbconnection.cpp @@ -180,7 +180,7 @@ const QChar kSqlLikeEscapeDefault = '\0'; int sqliteStringCompareUTF16(void* pArg, int len1, const void* data1, int len2, const void* data2) { - StringCollator* pCollator = static_cast(pArg); + const auto* pCollator = static_cast(pArg); // Construct a QString without copy QString string1 = QString::fromRawData(static_cast(data1), len1 / sizeof(QChar)); @@ -233,7 +233,7 @@ void sqliteLike(sqlite3_context *context, #endif // __SQLITE3__ -bool initDatabase(QSqlDatabase database, StringCollator* pCollator) { +bool initDatabase(QSqlDatabase database, mixxx::StringCollator* pCollator) { DEBUG_ASSERT(database.isOpen()); #ifdef __SQLITE3__ QVariant v = database.driver()->handle(); diff --git a/src/util/db/dbconnection.h b/src/util/db/dbconnection.h index 76b6af89287..dfe91dbf860 100644 --- a/src/util/db/dbconnection.h +++ b/src/util/db/dbconnection.h @@ -64,7 +64,7 @@ class DbConnection final { DbConnection(const DbConnection&&) = delete; QSqlDatabase m_sqlDatabase; - StringCollator m_collator; + mixxx::StringCollator m_collator; }; } // namespace mixxx diff --git a/src/util/string.h b/src/util/string.h index 264b006c8b1..57f2b256614 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -1,16 +1,21 @@ -#ifndef MIXXX_STRING_H -#define MIXXX_STRING_H +#pragma once -#include #include +#include #include #include +#include +#include + +#include "util/assert.h" + +namespace mixxx { // The default comparison of strings for sorting. class StringCollator { public: explicit StringCollator(QLocale locale = QLocale()) - : m_collator(std::move(locale)) { + : m_collator(std::move(locale)) { m_collator.setCaseSensitivity(Qt::CaseInsensitive); } @@ -26,4 +31,58 @@ class StringCollator { QCollator m_collator; }; -#endif // MIXXX_STRING_H +/// A nullptr-safe variant of the corresponding standard C function. +/// +/// Treats nullptr like an empty string and returns 0. +inline std::size_t strnlen( + const char* str, + std::size_t len) { + if (str == nullptr) { + return 0; + } + // Invoke the global function + return ::strnlen(str, len); +} + +/// A nullptr-safe variant of the corresponding standard C function. +/// +/// Treats nullptr like an empty string and returns 0. +inline std::size_t wcsnlen( + const wchar_t* wcs, + std::size_t len) { + if (wcs == nullptr) { + return 0; + } + // Invoke the global function + return ::wcsnlen(wcs, len); +} + +/// Convert a wide-character C string to QString. +/// +/// We cannot use Qts wchar_t functions, since they may work or not +/// depending on the '/Zc:wchar_t-' build flag in the Qt configs +/// on Windows build. +/// +/// See also: QString::fromWCharArray() +inline QString convertWCStringToQString( + const wchar_t* wcs, + std::size_t len) { + if (!wcs) { + DEBUG_ASSERT(len == 0); + return QString(); + } + DEBUG_ASSERT(wcsnlen(wcs, len) == len); + const auto ilen = static_cast(len); + DEBUG_ASSERT(ilen >= 0); // unsigned -> signed + switch (sizeof(wchar_t)) { + case sizeof(ushort): + return QString::fromUtf16(reinterpret_cast(wcs), ilen); + case sizeof(uint): + return QString::fromUcs4(reinterpret_cast(wcs), ilen); + default: + DEBUG_ASSERT(!"unsupported character type"); + return QString(); + } +} + +} // namespace mixxx