Skip to content

Commit

Permalink
Extract HID DeviceInfo from HidController/HidEnumerator
Browse files Browse the repository at this point in the history
  • Loading branch information
uklotzde committed Nov 13, 2020
1 parent 14401da commit 3c58f6b
Show file tree
Hide file tree
Showing 8 changed files with 393 additions and 262 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions build/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']

Expand Down
222 changes: 45 additions & 177 deletions src/controllers/hid/hidcontroller.cpp
Original file line number Diff line number Diff line change
@@ -1,86 +1,24 @@
/**
* @file hidcontroller.cpp
* @author Sean M. Pappalardo [email protected]
* @date Sun May 1 2011
* @brief HID controller backend
*
*/

#include <wchar.h>
#include <string.h>

#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)
.arg(hid_serial.right(4)));
} else {
setDeviceName(
QString("%1 %2_%3").arg(hid_product)
.arg(hid_serial.right(4))
.arg(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);
Expand All @@ -91,8 +29,6 @@ HidController::~HidController() {
if (isOpen()) {
close();
}
delete [] hid_path;
delete [] hid_serial_raw;
}

QString HidController::presetExtension() {
Expand All @@ -114,76 +50,11 @@ void HidController::visit(const HidControllerPreset* preset) {
bool HidController::matchPreset(const PresetInfo& preset) {
const QList<ProductInfo>& 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() {
Expand All @@ -193,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;
}

Expand Down Expand Up @@ -325,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 << ")");
}
}
Expand All @@ -354,33 +236,19 @@ void HidController::sendFeatureReport(
reinterpret_cast<const unsigned char*>(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);
}
46 changes: 8 additions & 38 deletions src/controllers/hid/hidcontroller.h
Original file line number Diff line number Diff line change
@@ -1,36 +1,24 @@
/**
* @file hidcontroller.h
* @author Sean M. Pappalardo [email protected]
* @date Sun May 1 2011
* @brief HID controller backend
*/

#ifndef HIDCONTROLLER_H
#define HIDCONTROLLER_H

#include <hidapi.h>

#include <QAtomicInt>
#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;

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;
Expand All @@ -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<int> data, unsigned int length, unsigned int reportID);

Expand All @@ -73,22 +59,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;

Expand Down Expand Up @@ -125,5 +97,3 @@ class HidControllerJSProxy : public ControllerJSProxy {
private:
HidController* m_pHidController;
};

#endif
Loading

0 comments on commit 3c58f6b

Please sign in to comment.