Monitor all HID++ reports on wireless device 1

Again, many things were done in this commit such as implementing an
I/O queue, a mutex_queue, and implementing the hidpp::Report class.

I'm expecting commits to be like this until I can get a clean
codebase for the backend.
This commit is contained in:
pixl 2020-06-17 02:43:53 -04:00
parent 1de722b935
commit 6b895b3015
No known key found for this signature in database
GPG Key ID: 1866C148CD593B6E
14 changed files with 442 additions and 55 deletions

View File

@ -12,11 +12,13 @@ add_executable(logid
logid.cpp logid.cpp
util.cpp util.cpp
DeviceMonitor.cpp DeviceMonitor.cpp
backend/Error.cpp
backend/raw/DeviceMonitor.cpp backend/raw/DeviceMonitor.cpp
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/dj/Report.cpp) backend/dj/Report.cpp
util/mutex_queue.h)
set_target_properties(logid PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set_target_properties(logid PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})

View File

@ -102,9 +102,29 @@ void DeviceMonitor::stopAndDeleteDevice (const std::string &path, HIDPP::DeviceI
void DeviceMonitor::addDevice(std::string path) void DeviceMonitor::addDevice(std::string path)
{ {
try { try {
backend::hidpp::Device device(path, hidpp::DeviceIndex::DefaultDevice); backend::hidpp::Device 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());
backend::hidpp::EventHandler eventHandler;
eventHandler.condition = [device](backend::hidpp::Report& report)->bool
{
return true;
};
eventHandler.callback = [device](backend::hidpp::Report& report)->void
{
log_printf(DEBUG, "Event on %s:%d", device.devicePath().c_str(),
device.deviceIndex());
for(auto& i : report.rawReport())
printf("%02x ", i);
printf("\n");
};
device.addEventHandler("MONITOR_ALL", eventHandler);
std::thread([](backend::hidpp::Device device) { device.listen(); }, device).detach();
/* This is a temporary solution to avoid std::bad_function_call */
while(true) {}
} }
catch(backend::hidpp::Device::InvalidDevice &e) catch(backend::hidpp::Device::InvalidDevice &e)
{ {

View File

@ -38,6 +38,7 @@ namespace logid
private: private:
std::mutex devices_mutex; std::mutex devices_mutex;
std::map<std::string, std::map<backend::hidpp::DeviceIndex, ConnectedDevice>> devices; std::map<std::string, std::map<backend::hidpp::DeviceIndex, ConnectedDevice>> devices;
backend::hidpp::EventHandler eventHandler;
}; };
extern DeviceMonitor* finder; extern DeviceMonitor* finder;

View File

@ -0,0 +1,6 @@
#include "Error.h"
const char *logid::backend::TimeoutError::what() noexcept
{
return "Device timed out";
}

16
src/logid/backend/Error.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef LOGID_BACKEND_ERROR_H
#define LOGID_BACKEND_ERROR_H
#include <stdexcept>
namespace logid {
namespace backend {
class TimeoutError: public std::exception
{
public:
TimeoutError() = default;
virtual const char* what() noexcept;
};
}}
#endif //LOGID_BACKEND_ERROR_H

15
src/logid/backend/defs.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef LOGID_BACKEND_DEFS_H
#define LOGID_BACKEND_DEFS_H
#include <functional>
namespace logid::backend
{
struct RawEventHandler
{
std::function<bool(std::vector<uint8_t>& )> condition;
std::function<void(std::vector<uint8_t>& )> callback;
};
}
#endif //LOGID_BACKEND_DEFS_H

View File

@ -1,3 +1,4 @@
#include <assert.h>
#include "Device.h" #include "Device.h"
#include "Report.h" #include "Report.h"
@ -21,10 +22,67 @@ Device::InvalidDevice::Reason Device::InvalidDevice::code() const noexcept
} }
/// TODO: Initialize a single RawDevice for each path. /// TODO: Initialize a single RawDevice for each path.
Device::Device(std::string path, DeviceIndex index): Device::Device(const std::string& path, DeviceIndex index):
raw_device (std::make_shared<raw::RawDevice>(path)), path (path), index (index) raw_device (std::make_shared<raw::RawDevice>(path)), path (path), index (index)
{ {
supported_reports = getSupportedReports(raw_device->reportDescriptor()); supported_reports = getSupportedReports(raw_device->reportDescriptor());
if(!supported_reports) if(!supported_reports)
throw InvalidDevice(InvalidDevice::NoHIDPPReport); throw InvalidDevice(InvalidDevice::NoHIDPPReport);
// Pass all HID++ events with device index to this device.
RawEventHandler rawEventHandler;
rawEventHandler.condition = [index](std::vector<uint8_t>& report)->bool
{
return (report[Offset::Type] == Report::Short ||
report[Offset::Type] == Report::Long) && (report[Offset::DeviceIndex] == index);
};
rawEventHandler.callback = [this](std::vector<uint8_t>& report)->void
{
Report _report(report);
this->handleEvent(_report);
};
raw_device->addEventHandler("DEV_" + std::to_string(index), rawEventHandler);
}
void Device::addEventHandler(const std::string& nickname, EventHandler& handler)
{
auto it = event_handlers.find(nickname);
assert(it == event_handlers.end());
event_handlers.emplace(nickname, handler);
}
void Device::removeEventHandler(const std::string& nickname)
{
event_handlers.erase(nickname);
}
void Device::handleEvent(Report& report)
{
for(auto& handler : event_handlers)
if(handler.second.condition(report))
handler.second.callback(report);
}
Report Device::sendReport(Report& report)
{
switch(report.type())
{
case Report::Short:
if(!(supported_reports & HIDPP_REPORT_SHORT_SUPPORTED))
report.setType(Report::Long);
break;
case Report::Long:
/* Report can be truncated, but that isn't a good idea. */
assert(supported_reports & HIDPP_REPORT_LONG_SUPPORTED);
}
auto raw_response = raw_device->sendReport(report.rawReport());
return Report(raw_response);
}
void Device::listen()
{
raw_device->listen();
} }

View File

@ -3,22 +3,21 @@
#include <string> #include <string>
#include <memory> #include <memory>
#include <functional>
#include <map>
#include "../raw/RawDevice.h" #include "../raw/RawDevice.h"
#include "Report.h"
#include "defs.h"
namespace logid::backend::hidpp namespace logid {
namespace backend {
namespace hidpp
{ {
enum DeviceIndex: uint8_t struct EventHandler
{ {
DefaultDevice = 0, std::function<bool(Report&)> condition;
WirelessDevice1 = 1, std::function<void(Report&)> callback;
WirelessDevice2 = 2,
WirelessDevice3 = 3,
WirelessDevice4 = 4,
WirelessDevice5 = 5,
WirelessDevice6 = 6,
CordedDevice = 0xff
}; };
class Device class Device
{ {
public: public:
@ -35,17 +34,30 @@ namespace logid::backend::hidpp
virtual Reason code() const noexcept; virtual Reason code() const noexcept;
private: private:
Reason _reason; Reason _reason;
}; };
Device(std::string path, DeviceIndex index);
Device(const std::string& path, DeviceIndex index);
std::string devicePath() const { return path; } std::string devicePath() const { return path; }
DeviceIndex deviceIndex() const { return index; } DeviceIndex deviceIndex() const { return index; }
void listen(); // Runs asynchronously
void stopListening();
void addEventHandler(const std::string& nickname, EventHandler& handler);
void removeEventHandler(const std::string& nickname);
Report sendReport(Report& report);
void handleEvent(Report& report);
private: private:
std::shared_ptr<logid::backend::raw::RawDevice> raw_device; std::shared_ptr<raw::RawDevice> raw_device;
std::string path; std::string path;
DeviceIndex index; DeviceIndex index;
uint8_t supported_reports; uint8_t supported_reports;
std::map<std::string, EventHandler> event_handlers;
}; };
} } } }
#endif //LOGID_HIDPP_DEVICE_H #endif //LOGID_HIDPP_DEVICE_H

View File

@ -1,5 +1,6 @@
#include <array> #include <array>
#include <algorithm> #include <algorithm>
#include <cassert>
#include "Report.h" #include "Report.h"
using namespace logid::backend::hidpp; using namespace logid::backend::hidpp;
@ -81,3 +82,79 @@ uint8_t hidpp::getSupportedReports(std::vector<uint8_t>&& rdesc)
return ret; return ret;
} }
const char *Report::InvalidReportID::what() const noexcept
{
return "Invalid report ID";
}
const char *Report::InvalidReportLength::what() const noexcept
{
return "Invalid report length";
}
Report::Report(Report::Type type, DeviceIndex device_index,
uint8_t feature_index, uint8_t function, uint8_t sw_id)
{
assert(!(function & functionMask));
assert(!(sw_id & swIdMask));
switch(type)
{
case Short:
_data.resize(HeaderLength + ShortParamLength);
break;
case Long:
_data.resize(HeaderLength + LongParamLength);
break;
default:
throw InvalidReportID();
}
_data[Offset::Type] = type;
_data[Offset::DeviceIndex] = device_index;
_data[Offset::Feature] = feature_index;
_data[Offset::Function] = (function & functionMask) << 4 | (sw_id & swIdMask);
}
Report::Report(const std::vector<uint8_t>& data)
{
_data = data;
switch(_data[Offset::Type])
{
case Short:
_data.resize(HeaderLength + ShortParamLength);
break;
case Long:
_data.resize(HeaderLength + LongParamLength);
break;
default:
throw InvalidReportID();
}
}
void Report::setType(Report::Type type)
{
switch(type)
{
case Short:
_data.resize(HeaderLength + ShortParamLength);
break;
case Long:
_data.resize(HeaderLength + LongParamLength);
break;
default:
throw InvalidReportID();
}
_data[Offset::Type] = type;
}
void Report::setParams(const std::vector<uint8_t>& _params)
{
assert(_params.size() <= _data.size()-HeaderLength);
for(std::size_t i = 0; i < _params.size(); i++)
_data[Offset::Parameters + i] = _params[i];
}

View File

@ -3,9 +3,7 @@
#include <cstdint> #include <cstdint>
#include "../raw/RawDevice.h" #include "../raw/RawDevice.h"
#include "Device.h" #include "defs.h"
#define LOGID_HIDPP_SW_ID 0x0f
/* Some devices only support a subset of these reports */ /* Some devices only support a subset of these reports */
#define HIDPP_REPORT_SHORT_SUPPORTED 1U #define HIDPP_REPORT_SHORT_SUPPORTED 1U
@ -15,6 +13,16 @@
namespace logid::backend::hidpp namespace logid::backend::hidpp
{ {
uint8_t getSupportedReports(std::vector<uint8_t>&& rdesc); uint8_t getSupportedReports(std::vector<uint8_t>&& rdesc);
namespace Offset
{
static constexpr uint8_t Type = 0;
static constexpr uint8_t DeviceIndex = 1;
static constexpr uint8_t Feature = 2;
static constexpr uint8_t Function = 3;
static constexpr uint8_t Parameters = 4;
}
class Report class Report
{ {
public: public:
@ -24,30 +32,43 @@ namespace logid::backend::hidpp
Long = 0x11 Long = 0x11
}; };
class InvalidReportID: std::exception class InvalidReportID: public std::exception
{ {
InvalidReportID(); public:
InvalidReportID() = default;
virtual const char* what() const noexcept; virtual const char* what() const noexcept;
}; };
class InvalidReportLength: std::exception class InvalidReportLength: public std::exception
{ {
InvalidReportLength(); public:
InvalidReportLength() = default;;
virtual const char* what() const noexcept; virtual const char* what() const noexcept;
}; };
static constexpr std::size_t MaxDataLength = 32; static constexpr std::size_t MaxDataLength = 20;
static constexpr uint8_t swIdMask = 0x0f;
static constexpr uint8_t functionMask = 0x0f;
Report(uint8_t report_id, const uint8_t* data, std::size_t length); Report(Type type, DeviceIndex device_index,
Report(std::vector<uint8_t> data); uint8_t feature_index,
uint8_t function,
uint8_t sw_id);
explicit Report(const std::vector<uint8_t>& data);
Type type() const; Type type() const { return static_cast<Type>(_data[Offset::Type]); };
void setType(Report::Type type); void setType(Report::Type type);
logid::backend::hidpp::DeviceIndex deviceIndex(); std::vector<uint8_t>::const_iterator paramBegin() const { return _data.begin() + Offset::Parameters; }
std::vector<uint8_t>::const_iterator paramEnd() const { return _data.end(); }
void setParams(const std::vector<uint8_t>& _params);
logid::backend::hidpp::DeviceIndex deviceIndex()
{
return static_cast<DeviceIndex>(_data[Offset::DeviceIndex]);
}
std::vector<uint8_t> rawReport () const { return _data; } std::vector<uint8_t> rawReport () const { return _data; }
private: private:
static constexpr std::size_t HeaderLength = 4; static constexpr std::size_t HeaderLength = 4;
std::vector<uint8_t> _data; std::vector<uint8_t> _data;

View File

@ -0,0 +1,24 @@
#ifndef LOGID_HIDPP_DEFS_H
#define LOGID_HIDPP_DEFS_H
#define LOGID_HIDPP_SOFTWARE_ID 1
namespace logid::backend::hidpp
{
enum DeviceIndex: uint8_t
{
DefaultDevice = 0,
WirelessDevice1 = 1,
WirelessDevice2 = 2,
WirelessDevice3 = 3,
WirelessDevice4 = 4,
WirelessDevice5 = 5,
WirelessDevice6 = 6,
CordedDevice = 0xff
};
static constexpr std::size_t ShortParamLength = 3;
static constexpr std::size_t LongParamLength = 16;
}
#endif //LOGID_HIDPP_DEFS_H

View File

@ -1,9 +1,11 @@
#include "RawDevice.h" #include "RawDevice.h"
#include "../Error.h"
#include <string> #include <string>
#include <system_error> #include <system_error>
#include <utility> #include <cassert>
#define MAX_DATA_LENGTH 32
extern "C" extern "C"
{ {
@ -76,29 +78,43 @@ RawDevice::~RawDevice()
} }
} }
void RawDevice::sendReport(std::vector<uint8_t> report) std::vector<uint8_t> RawDevice::sendReport(const std::vector<uint8_t>& report)
{ {
_sendReport(std::move(report)); std::packaged_task<std::vector<uint8_t>()> task(
[=]() {
_sendReport(report);
std::vector<uint8_t> response;
_readReport(response, MAX_DATA_LENGTH);
return response;
});
/* If the listener will stop, handle I/O manually.
* Otherwise, push to queue and wait for result. */
if(continue_listen)
{
auto f = task.get_future();
write_queue.push(&task);
return f.get();
}
else
return task.get_future().get();
} }
std::vector<uint8_t> RawDevice::readReport(std::size_t maxDataLength) int RawDevice::_sendReport(const std::vector<uint8_t>& report)
{
return _readReport(maxDataLength);
}
void RawDevice::_sendReport(std::vector<uint8_t> report)
{ {
std::lock_guard<std::mutex> lock(dev_io); std::lock_guard<std::mutex> lock(dev_io);
int ret = ::write(fd, report.data(), report.size()); int ret = ::write(fd, report.data(), report.size());
if(ret == -1) if(ret == -1)
throw std::system_error(errno, std::system_category(), "_sendReport write failed"); throw std::system_error(errno, std::system_category(), "_sendReport write failed");
return ret;
} }
std::vector<uint8_t> RawDevice::_readReport(std::size_t maxDataLength) int RawDevice::_readReport(std::vector<uint8_t>& report, std::size_t maxDataLength)
{ {
std::lock_guard<std::mutex> lock(dev_io); std::lock_guard<std::mutex> lock(dev_io);
int ret; int ret;
std::vector<uint8_t> report(maxDataLength); report.resize(maxDataLength);
timeval timeout = { duration_cast<milliseconds>(HIDPP_IO_TIMEOUT).count(), timeval timeout = { duration_cast<milliseconds>(HIDPP_IO_TIMEOUT).count(),
duration_cast<microseconds>(HIDPP_IO_TIMEOUT).count() }; duration_cast<microseconds>(HIDPP_IO_TIMEOUT).count() };
@ -133,7 +149,10 @@ std::vector<uint8_t> RawDevice::_readReport(std::size_t maxDataLength)
throw std::system_error(errno, std::system_category(), "_readReport read pipe failed"); throw std::system_error(errno, std::system_category(), "_readReport read pipe failed");
} }
return report; if(0 == ret)
throw backend::TimeoutError();
return ret;
} }
void RawDevice::interruptRead() void RawDevice::interruptRead()
@ -145,3 +164,61 @@ void RawDevice::interruptRead()
// Ensure I/O has halted // Ensure I/O has halted
std::lock_guard<std::mutex> lock(dev_io); std::lock_guard<std::mutex> lock(dev_io);
} }
void RawDevice::listen()
{
std::lock_guard<std::mutex> lock(listening);
continue_listen = true;
while(continue_listen)
{
while(!write_queue.empty())
{
auto task = write_queue.front();
(*task)();
write_queue.pop();
}
std::vector<uint8_t> report;
_readReport(report, MAX_DATA_LENGTH);
std::thread([this](std::vector<uint8_t> report) {
this->handleEvent(report);
}, report).detach();
}
continue_listen = false;
}
void RawDevice::stopListener()
{
continue_listen = false;
interruptRead();
}
void RawDevice::addEventHandler(const std::string &nickname, RawEventHandler &handler)
{
auto it = event_handlers.find(nickname);
assert(it == event_handlers.end());
event_handlers.emplace(nickname, handler);
}
void RawDevice::removeEventHandler(const std::string &nickname)
{
event_handlers.erase(nickname);
}
void RawDevice::handleEvent(std::vector<uint8_t> &report)
{
for(auto& handler : event_handlers)
if(handler.second.condition(report))
handler.second.callback(report);
}
bool RawDevice::isListening()
{
bool ret = listening.try_lock();
if(ret)
listening.unlock();
return ret;
}

View File

@ -4,10 +4,18 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <mutex> #include <mutex>
#include <map>
#include <atomic>
#include <future>
#include "../defs.h"
#include "../../util/mutex_queue.h"
#define HIDPP_IO_TIMEOUT std::chrono::seconds(2) #define HIDPP_IO_TIMEOUT std::chrono::seconds(2)
namespace logid::backend::raw namespace logid {
namespace backend {
namespace raw
{ {
class RawDevice class RawDevice
{ {
@ -18,12 +26,18 @@ namespace logid::backend::raw
std::vector<uint8_t> reportDescriptor() const { return rdesc; } std::vector<uint8_t> reportDescriptor() const { return rdesc; }
/// TODO: Process reports in a queue. /// TODO: Process reports in a queue.
void sendReport(std::vector<uint8_t> report); std::vector<uint8_t> sendReport(const std::vector<uint8_t>& report);
std::vector<uint8_t> readReport(std::size_t maxDataLength);
void interruptRead(); void interruptRead();
void listen();
void stopListener();
bool isListening();
void addEventHandler(const std::string& nickname, RawEventHandler& handler);
void removeEventHandler(const std::string& nickname);
private: private:
std::mutex dev_io; std::mutex dev_io, listening;
std::string path; std::string path;
int fd; int fd;
int dev_pipe[2]; int dev_pipe[2];
@ -32,10 +46,17 @@ namespace logid::backend::raw
std::string name; std::string name;
std::vector<uint8_t> rdesc; std::vector<uint8_t> rdesc;
std::atomic<bool> continue_listen;
std::map<std::string, backend::RawEventHandler> event_handlers;
void handleEvent(std::vector<uint8_t>& report);
/* These will only be used internally and processed with a queue */ /* These will only be used internally and processed with a queue */
void _sendReport(std::vector<uint8_t> report); int _sendReport(const std::vector<uint8_t>& report);
std::vector<uint8_t> _readReport(std::size_t maxDataLength); int _readReport(std::vector<uint8_t>& report, std::size_t maxDataLength);
mutex_queue<std::packaged_task<std::vector<uint8_t>()>*> write_queue;
}; };
} }}}
#endif //LOGID_BACKEND_RAWDEVICE_H #endif //LOGID_BACKEND_RAWDEVICE_H

View File

@ -0,0 +1,37 @@
#ifndef MUTEX_QUEUE_H
#define MUTEX_QUEUE_H
#include <queue>
#include <mutex>
template<typename data>
class mutex_queue
{
public:
mutex_queue<data>() = default;
bool empty()
{
std::lock_guard<std::mutex> lock(_mutex);
return _queue.empty();
}
data& front()
{
std::lock_guard<std::mutex> lock(_mutex);
return _queue.front();
}
void push(const data& _data)
{
std::lock_guard<std::mutex> lock(_mutex);
_queue.push(_data);
}
void pop()
{
std::lock_guard<std::mutex> lock(_mutex);
_queue.pop();
}
private:
std::queue<data> _queue;
std::mutex _mutex;
};
#endif //MUTEX_QUEUE_H