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
util.cpp
DeviceMonitor.cpp
backend/Error.cpp
backend/raw/DeviceMonitor.cpp
backend/raw/RawDevice.cpp
backend/hidpp/Device.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})

View File

@ -102,9 +102,29 @@ void DeviceMonitor::stopAndDeleteDevice (const std::string &path, HIDPP::DeviceI
void DeviceMonitor::addDevice(std::string path)
{
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());
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)
{

View File

@ -38,6 +38,7 @@ namespace logid
private:
std::mutex devices_mutex;
std::map<std::string, std::map<backend::hidpp::DeviceIndex, ConnectedDevice>> devices;
backend::hidpp::EventHandler eventHandler;
};
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 "Report.h"
@ -21,10 +22,67 @@ Device::InvalidDevice::Reason Device::InvalidDevice::code() const noexcept
}
/// 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)
{
supported_reports = getSupportedReports(raw_device->reportDescriptor());
if(!supported_reports)
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 <memory>
#include <functional>
#include <map>
#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,
WirelessDevice1 = 1,
WirelessDevice2 = 2,
WirelessDevice3 = 3,
WirelessDevice4 = 4,
WirelessDevice5 = 5,
WirelessDevice6 = 6,
CordedDevice = 0xff
std::function<bool(Report&)> condition;
std::function<void(Report&)> callback;
};
class Device
{
public:
@ -35,17 +34,30 @@ namespace logid::backend::hidpp
virtual Reason code() const noexcept;
private:
Reason _reason;
};
Device(std::string path, DeviceIndex index);
Device(const std::string& path, DeviceIndex index);
std::string devicePath() const { return path; }
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:
std::shared_ptr<logid::backend::raw::RawDevice> raw_device;
std::shared_ptr<raw::RawDevice> raw_device;
std::string path;
DeviceIndex index;
uint8_t supported_reports;
std::map<std::string, EventHandler> event_handlers;
};
}
} } }
#endif //LOGID_HIDPP_DEVICE_H

View File

@ -1,5 +1,6 @@
#include <array>
#include <algorithm>
#include <cassert>
#include "Report.h"
using namespace logid::backend::hidpp;
@ -80,4 +81,80 @@ uint8_t hidpp::getSupportedReports(std::vector<uint8_t>&& rdesc)
ret |= HIDPP_REPORT_LONG_SUPPORTED;
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 "../raw/RawDevice.h"
#include "Device.h"
#define LOGID_HIDPP_SW_ID 0x0f
#include "defs.h"
/* Some devices only support a subset of these reports */
#define HIDPP_REPORT_SHORT_SUPPORTED 1U
@ -15,6 +13,16 @@
namespace logid::backend::hidpp
{
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
{
public:
@ -24,30 +32,43 @@ namespace logid::backend::hidpp
Long = 0x11
};
class InvalidReportID: std::exception
class InvalidReportID: public std::exception
{
InvalidReportID();
public:
InvalidReportID() = default;
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;
};
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(std::vector<uint8_t> data);
Report(Type type, DeviceIndex device_index,
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);
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; }
private:
static constexpr std::size_t HeaderLength = 4;
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 "../Error.h"
#include <string>
#include <system_error>
#include <utility>
#include <cassert>
#define MAX_DATA_LENGTH 32
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)
{
return _readReport(maxDataLength);
}
void RawDevice::_sendReport(std::vector<uint8_t> report)
int RawDevice::_sendReport(const std::vector<uint8_t>& report)
{
std::lock_guard<std::mutex> lock(dev_io);
int ret = ::write(fd, report.data(), report.size());
if(ret == -1)
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);
int ret;
std::vector<uint8_t> report(maxDataLength);
report.resize(maxDataLength);
timeval timeout = { duration_cast<milliseconds>(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");
}
return report;
if(0 == ret)
throw backend::TimeoutError();
return ret;
}
void RawDevice::interruptRead()
@ -144,4 +163,62 @@ void RawDevice::interruptRead()
// Ensure I/O has halted
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 <vector>
#include <mutex>
#include <map>
#include <atomic>
#include <future>
#include "../defs.h"
#include "../../util/mutex_queue.h"
#define HIDPP_IO_TIMEOUT std::chrono::seconds(2)
namespace logid::backend::raw
namespace logid {
namespace backend {
namespace raw
{
class RawDevice
{
@ -18,12 +26,18 @@ namespace logid::backend::raw
std::vector<uint8_t> reportDescriptor() const { return rdesc; }
/// TODO: Process reports in a queue.
void sendReport(std::vector<uint8_t> report);
std::vector<uint8_t> readReport(std::size_t maxDataLength);
std::vector<uint8_t> sendReport(const std::vector<uint8_t>& report);
void interruptRead();
void listen();
void stopListener();
bool isListening();
void addEventHandler(const std::string& nickname, RawEventHandler& handler);
void removeEventHandler(const std::string& nickname);
private:
std::mutex dev_io;
std::mutex dev_io, listening;
std::string path;
int fd;
int dev_pipe[2];
@ -32,10 +46,17 @@ namespace logid::backend::raw
std::string name;
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 */
void _sendReport(std::vector<uint8_t> report);
std::vector<uint8_t> _readReport(std::size_t maxDataLength);
int _sendReport(const std::vector<uint8_t>& report);
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

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