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

HID Switch to force sending of duplicate data and cleanup of JS API functions #4692

Merged
merged 15 commits into from
Apr 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions src/controllers/hid/hidcontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,18 +197,13 @@ int HidController::close() {
return 0;
}

void HidController::sendReport(const QList<int>& data, unsigned int length, unsigned int reportID) {
Q_UNUSED(length);
QByteArray temp;
temp.reserve(data.size());
for (int datum : data) {
temp.append(datum);
}
m_pHidIoThread->updateCachedOutputReportData(temp, reportID);
}

/// This function is only for class compatibility with the (midi)controller
/// and will not do the same as for MIDI devices,
/// because sending of raw bytes is not a supported HIDAPI feature.
void HidController::sendBytes(const QByteArray& data) {
m_pHidIoThread->updateCachedOutputReportData(data, 0);
// Some HIDAPI backends will fail if the device uses ReportIDs (as practical all DJ controllers),
// because 0 is no valid ReportID for these devices.
m_pHidIoThread->updateCachedOutputReportData(0, data, false);
}

ControllerJSProxy* HidController::jsProxy() {
Expand Down
81 changes: 56 additions & 25 deletions src/controllers/hid/hidcontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@ class HidController final : public Controller {

bool matchMapping(const MappingInfo& mapping) override;

protected:
void sendReport(const QList<int>& data, unsigned int length, unsigned int reportID);

private slots:
int open() override;
int close() override;
Expand All @@ -60,45 +57,79 @@ class HidControllerJSProxy : public ControllerJSProxy {
m_pHidController(m_pController) {
}

Q_INVOKABLE void send(const QList<int>& data, unsigned int length = 0) override {
m_pHidController->send(data, length);
/// @brief Sends HID OutputReport with hard coded ReportID 0 to HID device
/// This function only works with HID devices, which don't use ReportIDs
/// @param dataList Data to send as list of bytes
/// @param length Unused optional argument
Q_INVOKABLE void send(const QList<int>& dataList, unsigned int length = 0) override {
daschuer marked this conversation as resolved.
Show resolved Hide resolved
// This function is only for class compatibility with the (midi)controller
Q_UNUSED(length);
send(dataList, 0, 0);
}

/// @brief Sends HID OutputReport to HID device
/// @param dataList Data to send as list of bytes
/// @param length Unused but mandatory argument
/// @param reportID 1...255 for HID devices that uses ReportIDs - or 0 for devices, which don't use ReportIDs
/// @param resendUnchangedReport If set, the report will also be send, if the data are unchanged since last sending
Q_INVOKABLE void send(const QList<int>& dataList,
daschuer marked this conversation as resolved.
Show resolved Hide resolved
unsigned int length,
quint8 reportID,
bool resendUnchangedReport = false) {
Q_UNUSED(length);
QByteArray dataArray;
dataArray.reserve(dataList.size());
for (int datum : dataList) {
dataArray.append(datum);
}
sendOutputReport(reportID, dataArray, resendUnchangedReport);
}

Q_INVOKABLE void send(const QList<int>& data, unsigned int length, unsigned int reportID) {
m_pHidController->sendReport(data, length, reportID);
/// @brief Sends an OutputReport to HID device
/// @param reportID 1...255 for HID devices that uses ReportIDs - or 0 for devices, which don't use ReportIDs
/// @param dataArray Data to send as byte array (Javascript type Uint8Array)
/// @param resendUnchangedReport If set, the report will also be send, if the data are unchanged since last sending
Q_INVOKABLE void sendOutputReport(quint8 reportID,
const QByteArray& dataArray,
bool resendUnchangedReport = false) {
VERIFY_OR_DEBUG_ASSERT(m_pHidController->m_pHidIoThread) {
return;
}
m_pHidController->m_pHidIoThread->updateCachedOutputReportData(
reportID, dataArray, resendUnchangedReport);
}

// getInputReport receives an input report on request.
// This can be used on startup to initialize the knob positions in Mixxx
// to the physical position of the hardware knobs on the controller.
// The returned data structure for the input reports is the same
// as in the polling functionality (including ReportID in first byte).
// The returned list can be used to call the incomingData
// function of the common-hid-packet-parser.
/// @brief getInputReport receives an InputReport from the HID device on request.
/// @details This can be used on startup to initialize the knob positions in Mixxx
/// to the physical position of the hardware knobs on the controller.
/// This is an optional command in the HID standard - not all devices support it.
/// @param reportID 1...255 for HID devices that uses ReportIDs - or 0 for devices, which don't use
/// @return Returns report data with ReportID byte as prefix
Q_INVOKABLE QByteArray getInputReport(
unsigned int reportID) {
quint8 reportID) {
VERIFY_OR_DEBUG_ASSERT(m_pHidController->m_pHidIoThread) {
return {};
}
return m_pHidController->m_pHidIoThread->getInputReport(reportID);
}

/// @brief Sends a FeatureReport to HID device
/// @param reportID 1...255 for HID devices that uses ReportIDs - or 0 for devices, which don't use
/// @param reportData Data to send as byte array (Javascript type Uint8Array)
Q_INVOKABLE void sendFeatureReport(
const QByteArray& reportData, unsigned int reportID) {
quint8 reportID, const QByteArray& reportData) {
VERIFY_OR_DEBUG_ASSERT(m_pHidController->m_pHidIoThread) {
return;
}
m_pHidController->m_pHidIoThread->sendFeatureReport(reportData, reportID);
m_pHidController->m_pHidIoThread->sendFeatureReport(reportID, reportData);
}
// getFeatureReport receives a feature reports on request.
// HID doesn't support polling feature reports, therefore this is the
// only method to get this information.
// Usually, single bits in a feature report need to be set without
// changing the other bits. The returned list matches the input
// format of sendFeatureReport, allowing it to be read, modified
// and sent it back to the controller.

/// @brief getFeatureReport receives a FeatureReport from the HID device on request.
/// @param reportID 1...255 for HID devices that uses ReportIDs - or 0 for devices, which don't use
/// @return The returned array matches the input format of sendFeatureReport (Javascript type Uint8Array),
/// allowing it to be read, modified and sent it back to the controller.
Q_INVOKABLE QByteArray getFeatureReport(
unsigned int reportID) {
quint8 reportID) {
VERIFY_OR_DEBUG_ASSERT(m_pHidController->m_pHidIoThread) {
return {};
}
Expand Down
8 changes: 5 additions & 3 deletions src/controllers/hid/hidiooutputreport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ constexpr int kMaxHidErrorMessageSize = 512;
} // namespace

HidIoOutputReport::HidIoOutputReport(
const unsigned char& reportId, const unsigned int& reportDataSize)
const quint8& reportId, const unsigned int& reportDataSize)
: m_reportId(reportId),
m_possiblyUnsentDataCached(false),
m_lastCachedDataSize(0) {
Expand All @@ -28,7 +28,8 @@ HidIoOutputReport::HidIoOutputReport(

void HidIoOutputReport::updateCachedData(const QByteArray& data,
const mixxx::hid::DeviceInfo& deviceInfo,
const RuntimeLoggingCategory& logOutput) {
const RuntimeLoggingCategory& logOutput,
bool resendUnchangedReport) {
auto cacheLock = lockMutex(&m_cachedDataMutex);

if (!m_lastCachedDataSize) {
Expand Down Expand Up @@ -65,6 +66,7 @@ void HidIoOutputReport::updateCachedData(const QByteArray& data,
data.constData(),
data.size());
m_possiblyUnsentDataCached = true;
m_resendUnchangedReport = resendUnchangedReport;
}

bool HidIoOutputReport::sendCachedData(QMutex* pHidDeviceAndPollMutex,
Expand All @@ -80,7 +82,7 @@ bool HidIoOutputReport::sendCachedData(QMutex* pHidDeviceAndPollMutex,
return false;
}

if (!m_lastSentData.compare(m_cachedData)) {
if (!(m_resendUnchangedReport || m_lastSentData.compare(m_cachedData))) {
// An HID OutputReport can contain only HID OutputItems.
// HID OutputItems are defined to represent the state of one or more similar controls or LEDs.
// Only HID Feature items may be attributes of other items.
Expand Down
11 changes: 7 additions & 4 deletions src/controllers/hid/hidiooutputreport.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@

class HidIoOutputReport {
public:
HidIoOutputReport(const unsigned char& reportId, const unsigned int& reportDataSize);
HidIoOutputReport(const quint8& reportId, const unsigned int& reportDataSize);

/// Caches new report data, which will later send by the IO thread
void updateCachedData(const QByteArray& data,

const mixxx::hid::DeviceInfo& deviceInfo,
const RuntimeLoggingCategory& logOutput);
const RuntimeLoggingCategory& logOutput,
bool resendUnchangedReport);

/// Sends the OutputReport to the HID device, when changed data are cached.
/// Returns true if a time consuming hid_write operation was executed.
Expand All @@ -22,15 +24,16 @@ class HidIoOutputReport {
const RuntimeLoggingCategory& logOutput);

private:
const unsigned char m_reportId;
const quint8 m_reportId;
QByteArray m_lastSentData;

/// Mutex must be locked when reading/writing m_cachedData
/// or m_possiblyUnsentDataCached
/// or m_possiblyUnsentDataCached, m_resendUnchangedReport
QMutex m_cachedDataMutex;

QByteArray m_cachedData;
bool m_possiblyUnsentDataCached;
bool m_resendUnchangedReport;

/// Due to swapping of the QbyteArrays, we need to store
/// this information independent of the QBytearray size
Expand Down
18 changes: 10 additions & 8 deletions src/controllers/hid/hidiothread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ void HidIoThread::processInputReport(int bytesRead) {
mixxx::Time::elapsed());
}

QByteArray HidIoThread::getInputReport(unsigned int reportID) {
QByteArray HidIoThread::getInputReport(quint8 reportID) {
auto startOfHidGetInputReport = mixxx::Time::elapsed();
auto hidDeviceLock = lockMutex(&m_hidDeviceAndPollMutex);

Expand All @@ -168,10 +168,9 @@ QByteArray HidIoThread::getInputReport(unsigned int reportID) {
}

// Convert array of bytes read in a JavaScript compatible return type, this is returned as deep-copy, for thread safety.
// For compatibility with HidController::processInputReport, the reportID prefix is included added here
QByteArray returnArray = QByteArray(
reinterpret_cast<char*>(m_pPollData[m_pollingBufferIndex]),
bytesRead);
reinterpret_cast<char*>(m_pPollData[m_pollingBufferIndex] + kReportIdSize),
bytesRead - kReportIdSize);

hidDeviceLock.unlock();

Expand All @@ -189,7 +188,9 @@ QByteArray HidIoThread::getInputReport(unsigned int reportID) {
return returnArray;
}

void HidIoThread::updateCachedOutputReportData(const QByteArray& data, unsigned int reportID) {
void HidIoThread::updateCachedOutputReportData(quint8 reportID,
const QByteArray& data,
bool resendUnchangedReport) {
auto mapLock = lockMutex(&m_outputReportMapMutex);
if (m_outputReports.find(reportID) == m_outputReports.end()) {
std::unique_ptr<HidIoOutputReport> pNewOutputReport;
Expand All @@ -205,7 +206,8 @@ void HidIoThread::updateCachedOutputReportData(const QByteArray& data, unsigned

mapLock.unlock();

actualOutputReportIterator->second->updateCachedData(data, m_deviceInfo, m_logOutput);
actualOutputReportIterator->second->updateCachedData(
data, m_deviceInfo, m_logOutput, resendUnchangedReport);
}

bool HidIoThread::sendNextCachedOutputReport() {
Expand Down Expand Up @@ -236,7 +238,7 @@ bool HidIoThread::sendNextCachedOutputReport() {
}

void HidIoThread::sendFeatureReport(
const QByteArray& reportData, unsigned int reportID) {
quint8 reportID, const QByteArray& reportData) {
auto startOfHidSendFeatureReport = mixxx::Time::elapsed();
QByteArray dataArray;
dataArray.reserve(kReportIdSize + reportData.size());
Expand Down Expand Up @@ -271,7 +273,7 @@ void HidIoThread::sendFeatureReport(
}

QByteArray HidIoThread::getFeatureReport(
unsigned int reportID) {
quint8 reportID) {
auto startOfHidGetFeatureReport = mixxx::Time::elapsed();
unsigned char dataRead[kReportIdSize + kBufferSize];
dataRead[0] = reportID;
Expand Down
10 changes: 6 additions & 4 deletions src/controllers/hid/hidiothread.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ class HidIoThread : public QThread {
/// Returns immediately with true if the run loop is stopped.
[[nodiscard]] bool waitUntilRunLoopIsStopped(unsigned int timeoutMillis);

void updateCachedOutputReportData(const QByteArray& reportData, unsigned int reportID);
QByteArray getInputReport(unsigned int reportID);
void sendFeatureReport(const QByteArray& reportData, unsigned int reportID);
QByteArray getFeatureReport(unsigned int reportID);
void updateCachedOutputReportData(quint8 reportID,
const QByteArray& reportData,
bool resendUnchangedReport);
QByteArray getInputReport(quint8 reportID);
void sendFeatureReport(quint8 reportID, const QByteArray& reportData);
QByteArray getFeatureReport(quint8 reportID);

signals:
/// Signals that a HID InputReport received by Interrupt triggered from HID device
Expand Down