diff --git a/src/logid/CMakeLists.txt b/src/logid/CMakeLists.txt index 1bafd4a..e1c0924 100644 --- a/src/logid/CMakeLists.txt +++ b/src/logid/CMakeLists.txt @@ -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}) diff --git a/src/logid/DeviceMonitor.cpp b/src/logid/DeviceMonitor.cpp index 790df62..e69bda4 100644 --- a/src/logid/DeviceMonitor.cpp +++ b/src/logid/DeviceMonitor.cpp @@ -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) { diff --git a/src/logid/DeviceMonitor.h b/src/logid/DeviceMonitor.h index c288f0c..159970e 100644 --- a/src/logid/DeviceMonitor.h +++ b/src/logid/DeviceMonitor.h @@ -38,6 +38,7 @@ namespace logid private: std::mutex devices_mutex; std::map> devices; + backend::hidpp::EventHandler eventHandler; }; extern DeviceMonitor* finder; diff --git a/src/logid/backend/Error.cpp b/src/logid/backend/Error.cpp new file mode 100644 index 0000000..c224f76 --- /dev/null +++ b/src/logid/backend/Error.cpp @@ -0,0 +1,6 @@ +#include "Error.h" + +const char *logid::backend::TimeoutError::what() noexcept +{ + return "Device timed out"; +} diff --git a/src/logid/backend/Error.h b/src/logid/backend/Error.h new file mode 100644 index 0000000..20865cf --- /dev/null +++ b/src/logid/backend/Error.h @@ -0,0 +1,16 @@ +#ifndef LOGID_BACKEND_ERROR_H +#define LOGID_BACKEND_ERROR_H + +#include + +namespace logid { +namespace backend { +class TimeoutError: public std::exception +{ +public: + TimeoutError() = default; + virtual const char* what() noexcept; +}; +}} + +#endif //LOGID_BACKEND_ERROR_H \ No newline at end of file diff --git a/src/logid/backend/defs.h b/src/logid/backend/defs.h new file mode 100644 index 0000000..8ef5af1 --- /dev/null +++ b/src/logid/backend/defs.h @@ -0,0 +1,15 @@ +#ifndef LOGID_BACKEND_DEFS_H +#define LOGID_BACKEND_DEFS_H + +#include + +namespace logid::backend +{ + struct RawEventHandler + { + std::function& )> condition; + std::function& )> callback; + }; +} + +#endif //LOGID_BACKEND_DEFS_H \ No newline at end of file diff --git a/src/logid/backend/hidpp/Device.cpp b/src/logid/backend/hidpp/Device.cpp index 9b517bb..38a2764 100644 --- a/src/logid/backend/hidpp/Device.cpp +++ b/src/logid/backend/hidpp/Device.cpp @@ -1,3 +1,4 @@ +#include #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(path)), path (path), index (index) { supported_reports = getSupportedReports(raw_device->reportDescriptor()); if(!supported_reports) throw InvalidDevice(InvalidDevice::NoHIDPPReport); -} \ No newline at end of file + + // Pass all HID++ events with device index to this device. + RawEventHandler rawEventHandler; + rawEventHandler.condition = [index](std::vector& report)->bool + { + return (report[Offset::Type] == Report::Short || + report[Offset::Type] == Report::Long) && (report[Offset::DeviceIndex] == index); + }; + rawEventHandler.callback = [this](std::vector& 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(); +} diff --git a/src/logid/backend/hidpp/Device.h b/src/logid/backend/hidpp/Device.h index 70e0956..fcaf732 100644 --- a/src/logid/backend/hidpp/Device.h +++ b/src/logid/backend/hidpp/Device.h @@ -3,22 +3,21 @@ #include #include +#include +#include #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 condition; + std::function 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 raw_device; + std::shared_ptr raw_device; std::string path; DeviceIndex index; uint8_t supported_reports; + + std::map event_handlers; }; -} +} } } #endif //LOGID_HIDPP_DEVICE_H \ No newline at end of file diff --git a/src/logid/backend/hidpp/Report.cpp b/src/logid/backend/hidpp/Report.cpp index c9b27b4..0666db7 100644 --- a/src/logid/backend/hidpp/Report.cpp +++ b/src/logid/backend/hidpp/Report.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "Report.h" using namespace logid::backend::hidpp; @@ -80,4 +81,80 @@ uint8_t hidpp::getSupportedReports(std::vector&& rdesc) ret |= HIDPP_REPORT_LONG_SUPPORTED; return ret; -} \ No newline at end of file +} + +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& 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& _params) +{ + assert(_params.size() <= _data.size()-HeaderLength); + + for(std::size_t i = 0; i < _params.size(); i++) + _data[Offset::Parameters + i] = _params[i]; +} diff --git a/src/logid/backend/hidpp/Report.h b/src/logid/backend/hidpp/Report.h index d69c4b6..90e10b7 100644 --- a/src/logid/backend/hidpp/Report.h +++ b/src/logid/backend/hidpp/Report.h @@ -3,9 +3,7 @@ #include #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&& 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 data); + Report(Type type, DeviceIndex device_index, + uint8_t feature_index, + uint8_t function, + uint8_t sw_id); + explicit Report(const std::vector& data); - Type type() const; + Type type() const { return static_cast(_data[Offset::Type]); }; void setType(Report::Type type); - logid::backend::hidpp::DeviceIndex deviceIndex(); + std::vector::const_iterator paramBegin() const { return _data.begin() + Offset::Parameters; } + std::vector::const_iterator paramEnd() const { return _data.end(); } + void setParams(const std::vector& _params); + + logid::backend::hidpp::DeviceIndex deviceIndex() + { + return static_cast(_data[Offset::DeviceIndex]); + } std::vector rawReport () const { return _data; } - private: static constexpr std::size_t HeaderLength = 4; std::vector _data; diff --git a/src/logid/backend/hidpp/defs.h b/src/logid/backend/hidpp/defs.h new file mode 100644 index 0000000..5539587 --- /dev/null +++ b/src/logid/backend/hidpp/defs.h @@ -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 \ No newline at end of file diff --git a/src/logid/backend/raw/RawDevice.cpp b/src/logid/backend/raw/RawDevice.cpp index 22513ae..e334d5b 100644 --- a/src/logid/backend/raw/RawDevice.cpp +++ b/src/logid/backend/raw/RawDevice.cpp @@ -1,9 +1,11 @@ #include "RawDevice.h" +#include "../Error.h" #include #include -#include +#include +#define MAX_DATA_LENGTH 32 extern "C" { @@ -76,29 +78,43 @@ RawDevice::~RawDevice() } } -void RawDevice::sendReport(std::vector report) +std::vector RawDevice::sendReport(const std::vector& report) { - _sendReport(std::move(report)); + std::packaged_task()> task( + [=]() { + _sendReport(report); + std::vector 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 RawDevice::readReport(std::size_t maxDataLength) -{ - return _readReport(maxDataLength); -} - -void RawDevice::_sendReport(std::vector report) +int RawDevice::_sendReport(const std::vector& report) { std::lock_guard 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 RawDevice::_readReport(std::size_t maxDataLength) +int RawDevice::_readReport(std::vector& report, std::size_t maxDataLength) { std::lock_guard lock(dev_io); int ret; - std::vector report(maxDataLength); + report.resize(maxDataLength); timeval timeout = { duration_cast(HIDPP_IO_TIMEOUT).count(), duration_cast(HIDPP_IO_TIMEOUT).count() }; @@ -133,7 +149,10 @@ std::vector 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 lock(dev_io); -} \ No newline at end of file +} + +void RawDevice::listen() +{ + std::lock_guard lock(listening); + + continue_listen = true; + while(continue_listen) + { + while(!write_queue.empty()) + { + auto task = write_queue.front(); + (*task)(); + write_queue.pop(); + } + std::vector report; + _readReport(report, MAX_DATA_LENGTH); + std::thread([this](std::vector 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 &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; +} diff --git a/src/logid/backend/raw/RawDevice.h b/src/logid/backend/raw/RawDevice.h index c6c88fc..e7baba6 100644 --- a/src/logid/backend/raw/RawDevice.h +++ b/src/logid/backend/raw/RawDevice.h @@ -4,10 +4,18 @@ #include #include #include +#include +#include +#include + +#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 reportDescriptor() const { return rdesc; } /// TODO: Process reports in a queue. - void sendReport(std::vector report); - std::vector readReport(std::size_t maxDataLength); - + std::vector sendReport(const std::vector& 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 rdesc; + std::atomic continue_listen; + + std::map event_handlers; + void handleEvent(std::vector& report); + /* These will only be used internally and processed with a queue */ - void _sendReport(std::vector report); - std::vector _readReport(std::size_t maxDataLength); + int _sendReport(const std::vector& report); + int _readReport(std::vector& report, std::size_t maxDataLength); + + mutex_queue()>*> write_queue; }; -} +}}} #endif //LOGID_BACKEND_RAWDEVICE_H \ No newline at end of file diff --git a/src/logid/util/mutex_queue.h b/src/logid/util/mutex_queue.h new file mode 100644 index 0000000..153b1a5 --- /dev/null +++ b/src/logid/util/mutex_queue.h @@ -0,0 +1,37 @@ +#ifndef MUTEX_QUEUE_H +#define MUTEX_QUEUE_H + +#include +#include + +template +class mutex_queue +{ +public: + mutex_queue() = default; + bool empty() + { + std::lock_guard lock(_mutex); + return _queue.empty(); + } + data& front() + { + std::lock_guard lock(_mutex); + return _queue.front(); + } + void push(const data& _data) + { + std::lock_guard lock(_mutex); + _queue.push(_data); + } + void pop() + { + std::lock_guard lock(_mutex); + _queue.pop(); + } +private: + std::queue _queue; + std::mutex _mutex; +}; + +#endif //MUTEX_QUEUE_H \ No newline at end of file