Print version number of device 1 on each raw dev.

Only works on HID++ >=2.0 so far. Also solves a race condition where
the wrong response can be sent to a request.
This commit is contained in:
pixl 2020-06-18 01:34:25 -04:00
parent 14d07c220e
commit c21a923ab2
No known key found for this signature in database
GPG Key ID: 1866C148CD593B6E
19 changed files with 518 additions and 123 deletions

View File

@ -17,6 +17,10 @@ add_executable(logid
backend/raw/RawDevice.cpp backend/raw/RawDevice.cpp
backend/hidpp/Device.cpp backend/hidpp/Device.cpp
backend/hidpp/Report.cpp backend/hidpp/Report.cpp
backend/hidpp20/Device.cpp
backend/hidpp20/Error.cpp
backend/hidpp20/Feature.cpp
backend/hidpp20/features/Root.cpp
backend/dj/Report.cpp backend/dj/Report.cpp
util/mutex_queue.h) util/mutex_queue.h)

View File

@ -105,6 +105,9 @@ void DeviceMonitor::addDevice(std::string path)
auto device = std::make_shared<backend::hidpp::Device>(path, hidpp::DeviceIndex::WirelessDevice1); auto device = std::make_shared<backend::hidpp::Device>(path, hidpp::DeviceIndex::WirelessDevice1);
log_printf(DEBUG, "Detected HID++ device at %s", path.c_str()); log_printf(DEBUG, "Detected HID++ device at %s", path.c_str());
auto version = device->version();
log_printf(DEBUG, "HID++ version: %d.%d", std::get<0>(version), std::get<1>(version));
auto eventHandler = std::make_shared<backend::hidpp::EventHandler>(); auto eventHandler = std::make_shared<backend::hidpp::EventHandler>();
eventHandler->condition = [device](backend::hidpp::Report& report)->bool eventHandler->condition = [device](backend::hidpp::Report& report)->bool
{ {

View File

@ -0,0 +1,20 @@
#ifndef LOGID_BACKEND_DJ_DEFS_H
#define LOGID_BACKEND_DJ_DEFS_H
#include <cstdint>
namespace logid {
namespace backend {
namespace dj
{
namespace ReportType
{
enum ReportType : uint8_t
{
Short = 0x20,
Long = 0x21
};
}
}}}
#endif //LOGID_BACKEND_DJ_DEFS_H

View File

@ -1,6 +1,7 @@
#include <assert.h> #include <assert.h>
#include "Device.h" #include "Device.h"
#include "Report.h" #include "Report.h"
#include "../hidpp20/features/Root.h"
using namespace logid::backend; using namespace logid::backend;
using namespace logid::backend::hidpp; using namespace logid::backend::hidpp;
@ -29,12 +30,20 @@ Device::Device(const std::string& path, DeviceIndex index):
if(!supported_reports) if(!supported_reports)
throw InvalidDevice(InvalidDevice::NoHIDPPReport); throw InvalidDevice(InvalidDevice::NoHIDPPReport);
Report versionRequest(Report::Type::Short, index, hidpp20::FeatureID::ROOT,
hidpp20::Root::Ping, LOGID_HIDPP_SOFTWARE_ID);
///TODO: Catch error
auto versionResponse = sendReport(versionRequest);
auto versionResponse_params = versionResponse.paramBegin();
_version = std::make_tuple(versionResponse_params[0], versionResponse_params[1]);
// Pass all HID++ events with device index to this device. // Pass all HID++ events with device index to this device.
RawEventHandler rawEventHandler; RawEventHandler rawEventHandler;
rawEventHandler.condition = [index](std::vector<uint8_t>& report)->bool rawEventHandler.condition = [index](std::vector<uint8_t>& report)->bool
{ {
return (report[Offset::Type] == Report::Short || return (report[Offset::Type] == Report::Type::Short ||
report[Offset::Type] == Report::Long) && (report[Offset::DeviceIndex] == index); report[Offset::Type] == Report::Type::Long) && (report[Offset::DeviceIndex] == index);
}; };
rawEventHandler.callback = [this](std::vector<uint8_t>& report)->void rawEventHandler.callback = [this](std::vector<uint8_t>& report)->void
{ {
@ -69,11 +78,11 @@ Report Device::sendReport(Report& report)
{ {
switch(report.type()) switch(report.type())
{ {
case Report::Short: case Report::Type::Short:
if(!(supported_reports & HIDPP_REPORT_SHORT_SUPPORTED)) if(!(supported_reports & HIDPP_REPORT_SHORT_SUPPORTED))
report.setType(Report::Long); report.setType(Report::Type::Long);
break; break;
case Report::Long: case Report::Type::Long:
/* Report can be truncated, but that isn't a good idea. */ /* Report can be truncated, but that isn't a good idea. */
assert(supported_reports & HIDPP_REPORT_LONG_SUPPORTED); assert(supported_reports & HIDPP_REPORT_LONG_SUPPORTED);
} }

View File

@ -40,6 +40,7 @@ namespace hidpp
std::string devicePath() const { return path; } std::string devicePath() const { return path; }
DeviceIndex deviceIndex() const { return index; } DeviceIndex deviceIndex() const { return index; }
std::tuple<uint8_t, uint8_t> version() const { return _version; }
void listen(); // Runs asynchronously void listen(); // Runs asynchronously
void stopListening(); void stopListening();
@ -56,6 +57,8 @@ namespace hidpp
DeviceIndex index; DeviceIndex index;
uint8_t supported_reports; uint8_t supported_reports;
std::tuple<uint8_t, uint8_t> _version;
std::map<std::string, std::shared_ptr<EventHandler>> event_handlers; std::map<std::string, std::shared_ptr<EventHandler>> event_handlers;
}; };
} } } } } }

View File

@ -96,15 +96,15 @@ const char *Report::InvalidReportLength::what() const noexcept
Report::Report(Report::Type type, DeviceIndex device_index, Report::Report(Report::Type type, DeviceIndex device_index,
uint8_t feature_index, uint8_t function, uint8_t sw_id) uint8_t feature_index, uint8_t function, uint8_t sw_id)
{ {
assert(!(function & functionMask)); assert(function <= functionMask);
assert(!(sw_id & swIdMask)); assert(sw_id <= swIdMask);
switch(type) switch(type)
{ {
case Short: case Type::Short:
_data.resize(HeaderLength + ShortParamLength); _data.resize(HeaderLength + ShortParamLength);
break; break;
case Long: case Type::Long:
_data.resize(HeaderLength + LongParamLength); _data.resize(HeaderLength + LongParamLength);
break; break;
default: default:
@ -123,10 +123,10 @@ Report::Report(const std::vector<uint8_t>& data)
switch(_data[Offset::Type]) switch(_data[Offset::Type])
{ {
case Short: case Type::Short:
_data.resize(HeaderLength + ShortParamLength); _data.resize(HeaderLength + ShortParamLength);
break; break;
case Long: case Type::Long:
_data.resize(HeaderLength + LongParamLength); _data.resize(HeaderLength + LongParamLength);
break; break;
default: default:
@ -138,10 +138,10 @@ void Report::setType(Report::Type type)
{ {
switch(type) switch(type)
{ {
case Short: case Type::Short:
_data.resize(HeaderLength + ShortParamLength); _data.resize(HeaderLength + ShortParamLength);
break; break;
case Long: case Type::Long:
_data.resize(HeaderLength + LongParamLength); _data.resize(HeaderLength + LongParamLength);
break; break;
default: default:
@ -158,3 +158,10 @@ void Report::setParams(const std::vector<uint8_t>& _params)
for(std::size_t i = 0; i < _params.size(); i++) for(std::size_t i = 0; i < _params.size(); i++)
_data[Offset::Parameters + i] = _params[i]; _data[Offset::Parameters + i] = _params[i];
} }
bool Report::isError20(Report::hidpp20_error* error)
{
if(_data[Offset::Type] != Type::Long ||
_data[Offset::Feature] != 0xff)
return false;
}

View File

@ -26,11 +26,7 @@ namespace logid::backend::hidpp
class Report class Report
{ {
public: public:
enum Type: uint8_t typedef ReportType::ReportType Type;
{
Short = 0x10,
Long = 0x11
};
class InvalidReportID: public std::exception class InvalidReportID: public std::exception
{ {
@ -50,19 +46,26 @@ namespace logid::backend::hidpp
static constexpr uint8_t swIdMask = 0x0f; static constexpr uint8_t swIdMask = 0x0f;
static constexpr uint8_t functionMask = 0x0f; static constexpr uint8_t functionMask = 0x0f;
Report(Type type, DeviceIndex device_index, Report(Report::Type type, DeviceIndex device_index,
uint8_t feature_index, uint8_t feature_index,
uint8_t function, uint8_t function,
uint8_t sw_id); uint8_t sw_id);
explicit Report(const std::vector<uint8_t>& data); explicit Report(const std::vector<uint8_t>& data);
Type type() const { return static_cast<Type>(_data[Offset::Type]); }; Report::Type type() const { return static_cast<Report::Type>(_data[Offset::Type]); };
void setType(Report::Type type); void setType(Report::Type type);
std::vector<uint8_t>::const_iterator paramBegin() const { return _data.begin() + Offset::Parameters; } std::vector<uint8_t>::iterator paramBegin() { return _data.begin() + Offset::Parameters; }
std::vector<uint8_t>::const_iterator paramEnd() const { return _data.end(); } std::vector<uint8_t>::iterator paramEnd() { return _data.end(); }
void setParams(const std::vector<uint8_t>& _params); void setParams(const std::vector<uint8_t>& _params);
struct hidpp20_error
{
uint8_t feature_index, function, software_id, error_code;
};
bool isError20(hidpp20_error* error);
logid::backend::hidpp::DeviceIndex deviceIndex() logid::backend::hidpp::DeviceIndex deviceIndex()
{ {
return static_cast<DeviceIndex>(_data[Offset::DeviceIndex]); return static_cast<DeviceIndex>(_data[Offset::DeviceIndex]);

View File

@ -5,6 +5,15 @@
namespace logid::backend::hidpp namespace logid::backend::hidpp
{ {
namespace ReportType
{
enum ReportType : uint8_t
{
Short = 0x10,
Long = 0x11
};
}
enum DeviceIndex: uint8_t enum DeviceIndex: uint8_t
{ {
DefaultDevice = 0, DefaultDevice = 0,

View File

@ -0,0 +1,25 @@
#include <cassert>
#include "Device.h"
#include "../hidpp/defs.h"
using namespace logid::backend::hidpp20;
std::vector<uint8_t> Device::callFunction(uint8_t feature_index,
uint8_t function, std::vector<uint8_t>& params)
{
hidpp::Report::Type type;
assert(params.size() <= hidpp::LongParamLength);
if(params.size() <= hidpp::ShortParamLength)
type = hidpp::Report::Type::Short;
else if(params.size() <= hidpp::LongParamLength)
type = hidpp::Report::Type::Long;
hidpp::Report request(type, deviceIndex(), feature_index, function,
LOGID_HIDPP_SOFTWARE_ID);
std::copy(params.begin(), params.end(), request.paramBegin());
auto response = this->sendReport(request);
return std::vector<uint8_t>(response.paramBegin(), response.paramEnd());
}

View File

@ -0,0 +1,19 @@
#ifndef LOGID_HIDPP20_DEVICE_H
#define LOGID_HIDPP20_DEVICE_H
#include "../hidpp/Device.h"
#include <cstdint>
namespace logid {
namespace backend {
namespace hidpp20 {
class Device : public hidpp::Device
{
public:
std::vector<uint8_t> callFunction(uint8_t feature_index,
uint8_t function,
std::vector<uint8_t>& params);
};
}}}
#endif //LOGID_HIDPP20_DEVICE_H

View File

@ -0,0 +1,46 @@
#include <cassert>
#include <cstring>
#include "Error.h"
using namespace logid::backend::hidpp20;
Error::Error(uint8_t code) : _code (code)
{
assert(_code != NoError);
}
const char* Error::what() const noexcept
{
switch(_code)
{
case NoError:
return "No error";
case Unknown:
return "Unknown";
case InvalidArgument:
return "Invalid argument";
case OutOfRange:
return "Out of range";
case HardwareError:
return "Hardware error";
case LogitechInternal:
return "Logitech internal feature";
case InvalidFeatureIndex:
return "Invalid feature index";
case InvalidFunctionID:
return "Invalid function ID";
case Busy:
return "Busy";
case Unsupported:
return "Unsupported";
case UnknownDevice:
return "Unknown device";
default:
return std::string("Unknown error code " + std::to_string(_code)).c_str();
}
}
uint8_t Error::code() const noexcept
{
return _code;
}

View File

@ -0,0 +1,39 @@
#ifndef LOGID_BACKEND_HIDPP20_ERROR_H
#define LOGID_BACKEND_HIDPP20_ERROR_H
#include <stdexcept>
#include <cstdint>
namespace logid {
namespace backend {
namespace hidpp20 {
static constexpr uint8_t ErrorID = 0xFF;
class Error: public std::exception
{
public:
enum ErrorCode: uint8_t {
NoError = 0,
Unknown = 1,
InvalidArgument = 2,
OutOfRange = 3,
HardwareError = 4,
LogitechInternal = 5,
InvalidFeatureIndex = 6,
InvalidFunctionID = 7,
Busy = 8,
Unsupported = 9,
UnknownDevice = 10
};
Error(uint8_t code);
virtual const char* what() const noexcept;
uint8_t code() const noexcept;
private:
uint8_t _code;
};
}}}
#endif //LOGID_BACKEND_HIDPP20_ERROR_H

View File

@ -0,0 +1,28 @@
#include "Feature.h"
using namespace logid::backend::hidpp20;
const char* Feature::UnsupportedFeature::what() const noexcept
{
return "Unsupported feature";
}
uint16_t Feature::UnsupportedFeature::code() const noexcept
{
return _f_id;
}
std::vector<uint8_t> Feature::callFunction(uint8_t function_id, std::vector<uint8_t>& params)
{
return _device->callFunction(_index, function_id, params);
}
Feature::Feature(Device* dev, uint16_t _id) : _device (dev), _index (0xff)
{
///TODO: Set index
}
Feature::Feature(Device* dev, uint8_t _index) : _device (dev), _index (_index)
{
}

View File

@ -0,0 +1,37 @@
#ifndef LOGID_HIDPP20_FEATURE_H
#define LOGID_HIDPP20_FEATURE_H
#include <cstdint>
#include "Device.h"
namespace logid {
namespace backend {
namespace hidpp20 {
class Feature
{
class UnsupportedFeature : public std::exception
{
public:
explicit UnsupportedFeature(uint16_t ID) : _f_id (ID) {}
virtual const char* what() const noexcept;
uint16_t code() const noexcept;
private:
uint16_t _f_id;
};
public:
static const uint16_t ID;
virtual uint16_t getID() = 0;
protected:
explicit Feature(Device* dev, uint16_t _id);
explicit Feature(Device* dev, uint8_t _index);
std::vector<uint8_t> callFunction(uint8_t function_id,
std::vector<uint8_t>& params);
private:
Device* _device;
uint8_t _index;
};
}}}
#endif //LOGID_HIDPP20_FEATURE_H

View File

@ -6,6 +6,15 @@
namespace logid { namespace logid {
namespace backend { namespace backend {
namespace hidpp20 { namespace hidpp20 {
struct feature_info {
uint16_t feature_id;
bool obsolete;
bool internal;
bool hidden;
};
namespace FeatureID
{
enum FeatureID : uint16_t enum FeatureID : uint16_t
{ {
ROOT = 0x0000, ROOT = 0x0000,
@ -97,6 +106,8 @@ namespace hidpp20 {
EQUALIZER = 0x8310, EQUALIZER = 0x8310,
HEADSET_OUT = 0x8320 HEADSET_OUT = 0x8320
}; };
}
}}} }}}
#endif //LOGID_BACKEND_HIDPP20_FEATUREDEFS #endif //LOGID_BACKEND_HIDPP20_FEATUREDEFS

View File

@ -0,0 +1,25 @@
#include "Root.h"
using namespace logid::backend::hidpp20;
Root::Root(Device* dev) : Feature(dev, ID)
{
}
feature_info Root::getFeature(uint16_t feature_id)
{
feature_info info;
std::vector<uint8_t> params(2);
params[0] = feature_id & 0xff;
params[1] = (feature_id >> 8) & 0xff;
auto response = this->callFunction(Function::Ping, params);
info.feature_id = response[0];
info.hidden = response[1] & FeatureFlag::Hidden;
info.obsolete = response[1] & FeatureFlag::Obsolete;
info.internal = response[1] & FeatureFlag::Internal;
return info;
}

View File

@ -0,0 +1,37 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_ROOT_H
#define LOGID_BACKEND_HIDPP20_FEATURE_ROOT_H
#include "../Feature.h"
#include "../feature_defs.h"
namespace logid {
namespace backend {
namespace hidpp20
{
class Root : public Feature
{
public:
static const uint16_t ID = FeatureID::ROOT;
virtual uint16_t getID() { return ID; }
enum Function : uint8_t
{
GetFeature = 0,
Ping = 1
};
Root(Device* device);
feature_info getFeature (uint16_t feature_id);
void ping();
private:
enum FeatureFlag : uint8_t
{
Obsolete = 1<<7,
Hidden = 1<<6,
Internal = 1<<5
};
};
}}}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_ROOT_H

View File

@ -1,5 +1,7 @@
#include "RawDevice.h" #include "RawDevice.h"
#include "../Error.h" #include "../Error.h"
#include "../hidpp/defs.h"
#include "../dj/defs.h"
#include <string> #include <string>
#include <system_error> #include <system_error>
@ -16,9 +18,16 @@ extern "C"
} }
using namespace logid::backend::raw; using namespace logid::backend::raw;
using namespace logid::backend;
using namespace std::chrono; using namespace std::chrono;
RawDevice::RawDevice(std::string path) : path (path) bool RawDevice::supportedReportID(uint8_t id)
{
return (hidpp::ReportType::Short == id) || (hidpp::ReportType::Long == id) ||
(dj::ReportType::Short == id) || (dj::ReportType::Long == id);
}
RawDevice::RawDevice(std::string path) : path (path), continue_listen (false)
{ {
int ret; int ret;
@ -80,24 +89,81 @@ RawDevice::~RawDevice()
std::vector<uint8_t> RawDevice::sendReport(const std::vector<uint8_t>& report) std::vector<uint8_t> RawDevice::sendReport(const std::vector<uint8_t>& report)
{ {
std::packaged_task<std::vector<uint8_t>()> task( assert(supportedReportID(report[0]));
[=]() {
_sendReport(report);
std::vector<uint8_t> response;
_readReport(response, MAX_DATA_LENGTH);
return response;
});
/* If the listener will stop, handle I/O manually. /* If the listener will stop, handle I/O manually.
* Otherwise, push to queue and wait for result. */ * Otherwise, push to queue and wait for result. */
if(continue_listen) if(continue_listen)
{ {
std::packaged_task<std::vector<uint8_t>()> task( [this, report]() {
return this->_respondToReport(report);
});
auto f = task.get_future(); auto f = task.get_future();
write_queue.push(&task); write_queue.push(&task);
return f.get(); return f.get();
} }
else else
return task.get_future().get(); return _respondToReport(report);
}
std::vector<uint8_t> RawDevice::_respondToReport
(const std::vector<uint8_t>& request)
{
_sendReport(request);
while(true)
{
std::vector<uint8_t> response;
_readReport(response, MAX_DATA_LENGTH);
// All reports have the device index at byte 2
if(response[1] != request[1])
{
std::thread([this](std::vector<uint8_t> report) {
this->handleEvent(report);
}, request).detach();
continue;
}
if(hidpp::ReportType::Short == request[0] ||
hidpp::ReportType::Long == request[0])
{
if(hidpp::ReportType::Short != response[0] &&
hidpp::ReportType::Long != response[0])
{
std::thread([this](std::vector<uint8_t> report) {
this->handleEvent(report);
}, request).detach();
continue;
}
// Error; leave to device to handle
if(response[2] == 0x8f || response[2] == 0xff)
return response;
bool others_match = true;
for(int i = 2; i < 4; i++)
{
if(response[i] != request[i])
others_match = false;
}
if(others_match)
return response;
}
else if(dj::ReportType::Short == request[0] ||
dj::ReportType::Long == request[0])
{
//Error; leave to device ot handle
if(0x7f == response[2])
return response;
else if(response[2] == request[2])
return response;
}
std::thread([this](std::vector<uint8_t> report) {
this->handleEvent(report);
}, request).detach();
}
} }
int RawDevice::_sendReport(const std::vector<uint8_t>& report) int RawDevice::_sendReport(const std::vector<uint8_t>& report)

View File

@ -7,6 +7,7 @@
#include <map> #include <map>
#include <atomic> #include <atomic>
#include <future> #include <future>
#include <set>
#include "../defs.h" #include "../defs.h"
#include "../../util/mutex_queue.h" #include "../../util/mutex_queue.h"
@ -20,12 +21,13 @@ namespace raw
class RawDevice class RawDevice
{ {
public: public:
static bool supportedReportID(uint8_t id);
RawDevice(std::string path); RawDevice(std::string path);
~RawDevice(); ~RawDevice();
std::string hidrawPath() const { return path; } std::string hidrawPath() const { return path; }
std::vector<uint8_t> reportDescriptor() const { return rdesc; } std::vector<uint8_t> reportDescriptor() const { return rdesc; }
/// TODO: Process reports in a queue.
std::vector<uint8_t> sendReport(const std::vector<uint8_t>& report); std::vector<uint8_t> sendReport(const std::vector<uint8_t>& report);
void interruptRead(); void interruptRead();
@ -55,6 +57,8 @@ namespace raw
int _sendReport(const std::vector<uint8_t>& report); int _sendReport(const std::vector<uint8_t>& report);
int _readReport(std::vector<uint8_t>& report, std::size_t maxDataLength); int _readReport(std::vector<uint8_t>& report, std::size_t maxDataLength);
std::vector<uint8_t> _respondToReport(const std::vector<uint8_t>& request);
mutex_queue<std::packaged_task<std::vector<uint8_t>()>*> write_queue; mutex_queue<std::packaged_task<std::vector<uint8_t>()>*> write_queue;
}; };
}}} }}}