Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract HID DeviceInfo from HidController/HidEnumerator #3308

Merged
merged 12 commits into from
Nov 17, 2020
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 spappalardo@mixxx.org
* @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 spappalardo@mixxx.org
* @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