Skip to content

Commit

Permalink
Add VeryLong report type and support for modern HID++ collections
Browse files Browse the repository at this point in the history
Dispatcher stores in ReportInfo which reports are present on the device.
  • Loading branch information
cvuchener committed Jul 2, 2021
1 parent 0d2c524 commit 4931f4d
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 182 deletions.
95 changes: 95 additions & 0 deletions src/libhidpp/hidpp/Dispatcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

#include "Dispatcher.h"

#include <misc/Log.h>

using namespace HIDPP;

const char *Dispatcher::NoHIDPPReportException::what () const noexcept
Expand Down Expand Up @@ -58,3 +60,96 @@ void Dispatcher::processEvent (const Report &report)
it = _listeners.erase (it);
}
}

static bool hasReport(const HID::ReportCollection &collection, HID::ReportID::Type type, uint8_t id, HID::Usage usage, unsigned int count)
{
using namespace HID;
auto it = collection.reports.find (ReportID {type, id});
if (it == collection.reports.end ())
return false;
const auto &fields = it->second;
if (fields.size () != 1)
return false;
const auto &field = fields.front ();
if (auto usages = std::get_if<std::vector<Usage>> (&field.usages)) {
return field.flags.Data () && field.flags.Array () &&
field.count == count && field.size == 8 &&
usages->size () == 1 && usages->front () == usage;
}
else
return false;
}

void Dispatcher::checkReportDescriptor (const HID::ReportDescriptor &rdesc)
{
enum {
Unknown,
Legacy,
Modern,
} scheme = Unknown;
_report_info.flags = 0;
uint8_t expected_reports = 0;
for (const auto &collection: rdesc.collections) {
auto collection_usage_msb = static_cast<uint8_t> (collection.usage.usage >> 8);
auto collection_usage_lsb = static_cast<uint8_t> (collection.usage.usage);
if (collection.usage.usage_page == 0xFF43) { // Modern scheme usage page
if (scheme == Unknown) {
scheme = Modern;
expected_reports = collection_usage_msb;
}
else if (scheme != Modern) {
Log::warning () << "Ignoring HID++ collection with unexpected usage page." << std::endl;
continue;
}
else if (expected_reports != collection_usage_msb) {
Log::warning () << "Ignoring HID++ collection with mismatched usage report flags." << std::endl;
continue;
}
}
else if (collection.usage.usage_page == 0xFF00) { // Legacy scheme usage page
if (scheme == Unknown)
scheme = Legacy;
else if (scheme != Legacy) {
Log::warning () << "Ignoring HID++ collection with unexpected usage page." << std::endl;
continue;
}
else if (collection_usage_msb != 0) {
Log::warning () << "Ignoring HID++ collection with invalid usage." << std::endl;
continue;
}
}
else // Not a HID++ collection
continue;
for (auto [type, flag]: {
std::make_tuple (Report::Short, ReportInfo::HasShortReport),
std::make_tuple (Report::Long, ReportInfo::HasLongReport),
std::make_tuple (Report::VeryLong, ReportInfo::HasVeryLongReport) }) {
if (collection_usage_lsb == flag) {
auto usage = HID::Usage (collection.usage.usage_page, flag);
bool has_input = hasReport (
collection,
HID::ReportID::Type::Input,
static_cast<uint8_t> (type),
usage,
Report::reportLength (type)-1);
if (!has_input)
Log::warning () << "Missing input report for report " << type << std::endl;
bool has_output = hasReport (
collection,
HID::ReportID::Type::Output,
static_cast<uint8_t> (type),
usage,
Report::reportLength(type)-1);
if (!has_output)
Log::warning () << "Missing output report for report " << type << std::endl;
if (has_input && has_output)
_report_info.flags |= flag;
}
}
}
if (scheme == Modern && expected_reports != _report_info.flags)
Log::warning () << "Expected HID++ reports were not found." << std::endl;
if (_report_info.flags == 0)
throw Dispatcher::NoHIDPPReportException ();
}

21 changes: 21 additions & 0 deletions src/libhidpp/hidpp/Dispatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,32 @@ class Dispatcher
*/
virtual void unregisterEventHandler (listener_iterator it);

struct ReportInfo {
enum Flags { // flags are also the usage for collections and reports
HasShortReport = 1<<0,
HasLongReport = 1<<1,
HasVeryLongReport = 1<<2,
};
int flags;

bool hasReport (Report::Type type) const noexcept {
switch (type) {
case Report::Short: return flags & HasShortReport;
case Report::Long: return flags & HasLongReport;
case Report::VeryLong: return flags & HasVeryLongReport;
default: return false;
}
}
};
ReportInfo reportInfo () const noexcept { return _report_info; }

protected:
void processEvent (const Report &);
void checkReportDescriptor (const HID::ReportDescriptor &report_desc);

private:
listener_container _listeners;
ReportInfo _report_info;
};

}
Expand Down
6 changes: 2 additions & 4 deletions src/libhidpp/hidpp/DispatcherThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ DispatcherThread::DispatcherThread (const char *path):
_dev (path),
_stopped (false)
{
const HID::ReportDescriptor &rdesc = _dev.getReportDescriptor ();
if (!checkReportDescriptor (rdesc))
throw Dispatcher::NoHIDPPReportException ();
checkReportDescriptor (_dev.getReportDescriptor ());
}

DispatcherThread::~DispatcherThread ()
Expand Down Expand Up @@ -151,7 +149,7 @@ void DispatcherThread::run ()
{
while (!_stopped) {
try {
std::vector<uint8_t> raw_report (Report::MaxDataLength+1);
std::vector<uint8_t> raw_report (MaxReportLength);
if (0 != _dev.readReport (raw_report))
processReport (std::move (raw_report));
}
Expand Down
164 changes: 29 additions & 135 deletions src/libhidpp/hidpp/Report.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,68 +25,6 @@

using namespace HIDPP;

static bool hasReport(const HID::ReportCollection &collection, HID::ReportID::Type type, uint8_t id, HID::Usage usage, unsigned int count)
{
using namespace HID;
auto it = collection.reports.find (ReportID {type, id});
if (it == collection.reports.end ())
return false;
const auto &fields = it->second;
if (fields.size () != 1)
return false;
const auto &field = fields.front ();
if (auto usages = std::get_if<std::vector<Usage>> (&field.usages)) {
return field.flags.Data () && field.flags.Array () &&
field.count == count && field.size == 8 &&
usages->size () == 1 && usages->front () == usage;
}
else
return false;
}

bool HIDPP::checkReportDescriptor (const HID::ReportDescriptor &rdesc)
{
static constexpr auto ShortReportUsage = HID::Usage (0xFF000001);
static constexpr auto LongReportUsage = HID::Usage (0xFF000002);
static constexpr unsigned int ShortReportCount = 6;
static constexpr unsigned int LongReportCount = 19;
bool has_short_input = false;
bool has_long_input = false;
bool has_short_output = false;
bool has_long_output = false;
for (const auto &collection: rdesc.collections) {
if (collection.usage == ShortReportUsage) {
has_short_input = hasReport (
collection,
HID::ReportID::Type::Input,
Report::Short,
ShortReportUsage,
ShortReportCount);
has_short_output = hasReport (
collection,
HID::ReportID::Type::Output,
Report::Short,
ShortReportUsage,
ShortReportCount);
}
else if (collection.usage == LongReportUsage) {
has_long_input = hasReport (
collection,
HID::ReportID::Type::Input,
Report::Long,
LongReportUsage,
LongReportCount);
has_long_output = hasReport (
collection,
HID::ReportID::Type::Output,
Report::Long,
LongReportUsage,
LongReportCount);
}
}
return has_short_input && has_long_input && has_short_output && has_long_output;
}

Report::InvalidReportID::InvalidReportID ()
{
}
Expand Down Expand Up @@ -114,38 +52,24 @@ static constexpr unsigned int Address = 3;
static constexpr unsigned int Parameters = 4;
}

Report::Report (uint8_t type, const uint8_t *data, std::size_t length)
Report::Report (uint8_t report_id, const uint8_t *data, std::size_t length)
{
switch (static_cast<Type> (type)) {
case Short:
_data.resize (HeaderLength+ShortParamLength);
break;
case Long:
_data.resize (HeaderLength+LongParamLength);
break;
default:

auto expected_len = reportLength (static_cast<Type> (report_id));
if (expected_len == 0)
throw InvalidReportID ();
}
if (length != _data.size ()-1)
_data.resize (expected_len);
if (length != expected_len-1)
throw InvalidReportLength ();

_data[Offset::Type] = type;
_data[Offset::Type] = report_id;
std::copy_n (data, length, &_data[1]);
}

Report::Report (std::vector<uint8_t> &&data)
{
std::size_t expected_len;
switch (static_cast<Type> (data[0])) {
case Short:
expected_len = HeaderLength+ShortParamLength;
break;
case Long:
expected_len = HeaderLength+LongParamLength;
break;
default:
auto expected_len = reportLength (static_cast<Type> (data[0]));
if (expected_len == 0)
throw InvalidReportID ();
}
if (data.size () != expected_len)
throw InvalidReportLength ();
_data = std::move (data);
Expand All @@ -156,14 +80,7 @@ Report::Report (Type type,
uint8_t sub_id,
uint8_t address)
{
switch (type) {
case Short:
_data.resize (HeaderLength+ShortParamLength, 0);
break;
case Long:
_data.resize (HeaderLength+LongParamLength, 0);
break;
}
_data.resize (reportLength (type), 0);
_data[Offset::Type] = type;
_data[Offset::DeviceIndex] = device_index;
_data[Offset::SubID] = sub_id;
Expand All @@ -176,18 +93,16 @@ Report::Report (HIDPP::DeviceIndex device_index,
std::vector<uint8_t>::const_iterator param_begin,
std::vector<uint8_t>::const_iterator param_end)
{
switch (std::distance (param_begin, param_end)) {
case ShortParamLength:
_data.resize (HeaderLength+ShortParamLength);
_data[Offset::Type] = Short;
break;
case LongParamLength:
_data.resize (HeaderLength+LongParamLength);
_data[Offset::Type] = Long;
break;
default:
throw InvalidReportLength ();
std::size_t param_len = std::distance (param_begin, param_end);
for (auto type: { Short, Long, VeryLong }) {
if (param_len == parameterLength (type)) {
_data.resize (reportLength (type));
_data[Offset::Type] = type;
break;
}
}
if (_data.empty ())
throw InvalidReportLength ();
_data[Offset::DeviceIndex] = device_index;
_data[Offset::SubID] = sub_id;
_data[Offset::Address] = address;
Expand All @@ -200,14 +115,7 @@ Report::Report (Type type,
unsigned int function,
unsigned int sw_id)
{
switch (type) {
case Short:
_data.resize (HeaderLength+ShortParamLength, 0);
break;
case Long:
_data.resize (HeaderLength+LongParamLength, 0);
break;
}
_data.resize (reportLength (type), 0);
_data[Offset::Type] = type;
_data[Offset::DeviceIndex] = device_index;
_data[Offset::SubID] = feature_index;
Expand All @@ -221,18 +129,16 @@ Report::Report (DeviceIndex device_index,
std::vector<uint8_t>::const_iterator param_begin,
std::vector<uint8_t>::const_iterator param_end)
{
switch (std::distance (param_begin, param_end)) {
case ShortParamLength:
_data.resize (HeaderLength+ShortParamLength);
_data[Offset::Type] = Short;
break;
case LongParamLength:
_data.resize (HeaderLength+LongParamLength);
_data[Offset::Type] = Long;
break;
default:
throw InvalidReportLength ();
std::size_t param_len = std::distance (param_begin, param_end);
for (auto type: { Short, Long, VeryLong }) {
if (param_len == parameterLength (type)) {
_data.resize (reportLength (type));
_data[Offset::Type] = type;
break;
}
}
if (_data.empty ())
throw InvalidReportLength ();
_data[Offset::DeviceIndex] = device_index;
_data[Offset::SubID] = feature_index;
_data[Offset::Address] = (function & 0x0f) << 4 | (sw_id & 0x0f);
Expand Down Expand Up @@ -304,18 +210,6 @@ std::size_t Report::parameterLength () const
return parameterLength (static_cast<Type> (_data[Offset::Type]));
}

std::size_t Report::parameterLength (Type type)
{
switch (type) {
case Short:
return ShortParamLength;
case Long:
return LongParamLength;
default:
throw std::logic_error ("Invalid type");
}
}

std::vector<uint8_t>::iterator Report::parameterBegin ()
{
return _data.begin () + Offset::Parameters;
Expand Down
Loading

0 comments on commit 4931f4d

Please sign in to comment.