From 7862c85d719229a68d891ac421db06d4dff6d904 Mon Sep 17 00:00:00 2001 From: pixl Date: Fri, 28 Jan 2022 15:48:00 -0500 Subject: [PATCH] Use epoll for I/O multiplexing --- src/logid/CMakeLists.txt | 1 + src/logid/Device.cpp | 26 +- src/logid/DeviceManager.cpp | 7 +- src/logid/Receiver.cpp | 7 +- src/logid/backend/Error.cpp | 5 + src/logid/backend/Error.h | 7 + src/logid/backend/dj/Receiver.cpp | 92 ++--- src/logid/backend/dj/Receiver.h | 11 +- src/logid/backend/dj/ReceiverMonitor.cpp | 78 ++-- src/logid/backend/dj/ReceiverMonitor.h | 9 +- src/logid/backend/dj/Report.cpp | 4 +- src/logid/backend/dj/Report.h | 10 +- src/logid/backend/hidpp/Device.cpp | 226 +++++------ src/logid/backend/hidpp/Device.h | 36 +- src/logid/backend/hidpp/Report.cpp | 8 +- src/logid/backend/hidpp/Report.h | 12 +- src/logid/backend/hidpp/defs.h | 2 +- src/logid/backend/hidpp10/Device.cpp | 92 ++++- src/logid/backend/hidpp10/Device.h | 21 +- src/logid/backend/hidpp20/Device.cpp | 128 ++++++- src/logid/backend/hidpp20/Device.h | 31 +- src/logid/backend/raw/DeviceMonitor.cpp | 208 +++++----- src/logid/backend/raw/DeviceMonitor.h | 26 +- src/logid/backend/raw/IOMonitor.cpp | 200 ++++++++++ src/logid/backend/raw/IOMonitor.h | 69 ++++ src/logid/backend/raw/RawDevice.cpp | 459 +++++------------------ src/logid/backend/raw/RawDevice.h | 85 ++--- src/logid/backend/raw/defs.h | 10 +- src/logid/logid.cpp | 29 +- 29 files changed, 1056 insertions(+), 843 deletions(-) create mode 100644 src/logid/backend/raw/IOMonitor.cpp create mode 100644 src/logid/backend/raw/IOMonitor.h diff --git a/src/logid/CMakeLists.txt b/src/logid/CMakeLists.txt index 63bac6b..aca7539 100644 --- a/src/logid/CMakeLists.txt +++ b/src/logid/CMakeLists.txt @@ -41,6 +41,7 @@ add_executable(logid backend/Error.cpp backend/raw/DeviceMonitor.cpp backend/raw/RawDevice.cpp + backend/raw/IOMonitor.cpp backend/dj/Receiver.cpp backend/dj/ReceiverMonitor.cpp backend/dj/Error.cpp diff --git a/src/logid/Device.cpp b/src/logid/Device.cpp index bbc176e..dc06158 100644 --- a/src/logid/Device.cpp +++ b/src/logid/Device.cpp @@ -17,6 +17,7 @@ */ #include +#include #include "util/log.h" #include "features/DPI.h" #include "Device.h" @@ -97,7 +98,7 @@ std::shared_ptr Device::make( Device::Device(std::string path, backend::hidpp::DeviceIndex index, std::shared_ptr manager) : - _hidpp20 (path, index, + _hidpp20 (path, index, manager, manager->config()->io_timeout.value_or(defaults::io_timeout)), _path (std::move(path)), _index (index), _config (_getConfig(manager, _hidpp20.name())), @@ -113,21 +114,24 @@ Device::Device(std::string path, backend::hidpp::DeviceIndex index, Device::Device(std::shared_ptr raw_device, hidpp::DeviceIndex index, std::shared_ptr manager) : - _hidpp20(raw_device, index), - _path (raw_device->hidrawPath()), _index (index), - _config (_getConfig(manager, _hidpp20.name())), - _receiver (nullptr), - _manager (manager), - _nickname (manager), - _ipc_node (manager->devicesNode()->make_child(_nickname)), - _awake (ipcgull::property_readable, true) + _hidpp20(std::move(raw_device), index, + manager->config()->io_timeout.value_or(defaults::io_timeout)), + _path (raw_device->rawPath()), _index (index), + _config (_getConfig(manager, _hidpp20.name())), + _receiver (nullptr), + _manager (manager), + _nickname (manager), + _ipc_node (manager->devicesNode()->make_child(_nickname)), + _awake (ipcgull::property_readable, true) { _init(); } Device::Device(Receiver* receiver, hidpp::DeviceIndex index, std::shared_ptr manager) : - _hidpp20 (receiver->rawReceiver(), index), + _hidpp20 (receiver->rawReceiver(), index, + manager->config()->io_timeout.value_or( + defaults::io_timeout)), _path (receiver->path()), _index (index), _config (_getConfig(manager, _hidpp20.name())), _receiver (receiver), @@ -162,8 +166,6 @@ void Device::_init() feature.second->configure(); feature.second->listen(); } - - _hidpp20.listen(); } std::string Device::name() diff --git a/src/logid/DeviceManager.cpp b/src/logid/DeviceManager.cpp index 7ae827d..0b03527 100644 --- a/src/logid/DeviceManager.cpp +++ b/src/logid/DeviceManager.cpp @@ -55,6 +55,7 @@ DeviceManager::DeviceManager(std::shared_ptr config, _device_node->add_server(_server); _receiver_node->add_server(_server); _root_node->add_server(_server); + ready(); } std::shared_ptr DeviceManager::make( @@ -94,8 +95,7 @@ void DeviceManager::addDevice(std::string path) // Check if device is ignored before continuing { - raw::RawDevice raw_dev( - path,config()->io_timeout.value_or(defaults::io_timeout)); + raw::RawDevice raw_dev(path, _self.lock()); if(config()->ignore.has_value() && config()->ignore.value().contains(raw_dev.productId())) { logPrintf(DEBUG, "%s: Device 0x%04x ignored.", @@ -106,7 +106,7 @@ void DeviceManager::addDevice(std::string path) try { hidpp::Device device( - path, hidpp::DefaultDevice, + path, hidpp::DefaultDevice, _self.lock(), config()->io_timeout.value_or(defaults::io_timeout)); isReceiver = device.version() == std::make_tuple(1, 0); } catch(hidpp10::Error &e) { @@ -126,7 +126,6 @@ void DeviceManager::addDevice(std::string path) if(isReceiver) { logPrintf(INFO, "Detected receiver at %s", path.c_str()); auto receiver = Receiver::make(path, _self.lock()); - receiver->run(); std::lock_guard lock(_map_lock); _receivers.emplace(path, receiver); _ipc_receivers->receiverAdded(receiver); diff --git a/src/logid/Receiver.cpp b/src/logid/Receiver.cpp index 7ea7f5c..ce4004c 100644 --- a/src/logid/Receiver.cpp +++ b/src/logid/Receiver.cpp @@ -64,13 +64,14 @@ std::shared_ptr Receiver::make( Receiver::Receiver(const std::string& path, const std::shared_ptr& manager) : - dj::ReceiverMonitor(path, + dj::ReceiverMonitor(path, manager, manager->config()->io_timeout.value_or( defaults::io_timeout)), _path (path), _manager (manager), _nickname (manager), _ipc_node (manager->receiversNode()->make_child(_nickname)), _ipc_interface (_ipc_node->make_interface(this)) { + ready(); } const Receiver::DeviceList& Receiver::devices() const { @@ -118,7 +119,9 @@ void Receiver::addDevice(hidpp::DeviceConnectionEvent event) if(!event.linkEstablished) return; - hidpp::Device hidpp_device(receiver(), event); + hidpp::Device hidpp_device( + receiver(), event, + manager->config()->io_timeout.value_or(defaults::io_timeout)); auto version = hidpp_device.version(); diff --git a/src/logid/backend/Error.cpp b/src/logid/backend/Error.cpp index f15fc0e..bb4d811 100644 --- a/src/logid/backend/Error.cpp +++ b/src/logid/backend/Error.cpp @@ -22,3 +22,8 @@ const char *logid::backend::TimeoutError::what() const noexcept { return "Device timed out"; } + +const char *logid::backend::InvalidDevice::what() const noexcept +{ + return "Device has become invalidated"; +} diff --git a/src/logid/backend/Error.h b/src/logid/backend/Error.h index d281a3b..9c41214 100644 --- a/src/logid/backend/Error.h +++ b/src/logid/backend/Error.h @@ -29,6 +29,13 @@ public: TimeoutError() = default; const char* what() const noexcept override; }; + +class InvalidDevice : public std::exception +{ +public: + InvalidDevice() = default; + const char* what() const noexcept override; +}; }} #endif //LOGID_BACKEND_ERROR_H \ No newline at end of file diff --git a/src/logid/backend/dj/Receiver.cpp b/src/logid/backend/dj/Receiver.cpp index a97ece1..fc19374 100644 --- a/src/logid/backend/dj/Receiver.cpp +++ b/src/logid/backend/dj/Receiver.cpp @@ -43,13 +43,44 @@ InvalidReceiver::Reason InvalidReceiver::code() const noexcept return _reason; } -Receiver::Receiver(std::string path, double io_timeout) : - _raw_device (std::make_shared( - std::move(path), io_timeout)), - _hidpp10_device (_raw_device, hidpp::DefaultDevice) +Receiver::Receiver(std::string path, + const std::shared_ptr& monitor, + double timeout) : + _raw_device (std::make_shared(std::move(path), monitor)), + _hidpp10_device (_raw_device, hidpp::DefaultDevice, timeout) { if(!supportsDjReports(_raw_device->reportDescriptor())) throw InvalidReceiver(InvalidReceiver::NoDJReports); + + // Pass all HID++ events on DefaultDevice to handleHidppEvent + _raw_hidpp_handler = _raw_device->addEventHandler({ + [](const std::vector& report)->bool { + return (report[hidpp::Offset::Type] == hidpp::Report::Type::Short || + report[hidpp::Offset::Type] == hidpp::Report::Type::Long); + }, + [this](const std::vector& report)->void { + hidpp::Report _report(report); + this->_handleHidppEvent(_report); + } + }); + + // Pass all DJ events with device index to handleDjEvent + _raw_dj_handler = _raw_device->addEventHandler({ + [](const std::vector& report)->bool { + return (report[Offset::Type] == Report::Type::Short || + report[Offset::Type] == Report::Type::Long); + }, + [this](const std::vector& report)->void { + Report _report(report); + this->_handleDjEvent(_report); + } + }); +} + +Receiver::~Receiver() +{ + _raw_device->removeEventHandler(_raw_dj_handler); + _raw_device->removeEventHandler(_raw_hidpp_handler); } void Receiver::enumerateDj() @@ -312,7 +343,7 @@ void Receiver::_sendDjRequest(hidpp::DeviceIndex index, uint8_t function, std::copy(params.begin(), params.end(), request.paramBegin()); - _raw_device->sendReportNoResponse(request.rawData()); + _raw_device->sendReport(request.rawData()); } Receiver::ConnectionStatusEvent Receiver::connectionStatusEvent(Report& report) @@ -324,57 +355,6 @@ Receiver::ConnectionStatusEvent Receiver::connectionStatusEvent(Report& report) return event; } -void Receiver::listen() -{ - if(!_raw_device->isListening()) - _raw_device->listenAsync(); - - if(_raw_device->eventHandlers().find("RECV_HIDPP") == - _raw_device->eventHandlers().end()) { - // Pass all HID++ events on DefaultDevice to handleHidppEvent - std::shared_ptr hidpp_handler = - std::make_shared(); - hidpp_handler->condition = [](std::vector& report)->bool - { - return (report[hidpp::Offset::Type] == hidpp::Report::Type::Short || - report[hidpp::Offset::Type] == hidpp::Report::Type::Long); - }; - hidpp_handler->callback = [this](std::vector& report) - ->void { - hidpp::Report _report(report); - this->_handleHidppEvent(_report); - }; - _raw_device->addEventHandler("RECV_HIDPP", hidpp_handler); - } - - if(_raw_device->eventHandlers().find("RECV_DJ") == - _raw_device->eventHandlers().end()) { - // Pass all DJ events with device index to handleDjEvent - std::shared_ptr dj_handler = - std::make_shared(); - dj_handler->condition = [](std::vector& report)->bool - { - return (report[Offset::Type] == Report::Type::Short || - report[Offset::Type] == Report::Type::Long); - }; - dj_handler->callback = [this](std::vector& report)->void - { - Report _report(report); - this->_handleDjEvent(_report); - }; - _raw_device->addEventHandler("RECV_DJ", dj_handler); - } -} - -void Receiver::stopListening() -{ - _raw_device->removeEventHandler("RECV_HIDPP"); - _raw_device->removeEventHandler("RECV_DJ"); - - if(_raw_device->eventHandlers().empty()) - _raw_device->stopListener(); -} - std::shared_ptr Receiver::rawDevice() const { return _raw_device; diff --git a/src/logid/backend/dj/Receiver.h b/src/logid/backend/dj/Receiver.h index 7152918..37c0262 100644 --- a/src/logid/backend/dj/Receiver.h +++ b/src/logid/backend/dj/Receiver.h @@ -52,7 +52,10 @@ namespace dj class Receiver final { public: - Receiver(std::string path, double io_timeout); + Receiver(std::string path, + const std::shared_ptr& monitor, + double timeout); + ~Receiver(); enum DjEvents : uint8_t { @@ -161,9 +164,6 @@ namespace dj static hidpp::DeviceConnectionEvent deviceConnectionEvent( const hidpp::Report& report); - void listen(); - void stopListening(); - void addDjEventHandler(const std::string& nickname, const std::shared_ptr& handler); void removeDjEventHandler(const std::string& nickname); @@ -184,6 +184,9 @@ namespace dj void _handleDjEvent(dj::Report& report); void _handleHidppEvent(hidpp::Report& report); + raw::RawDevice::EvHandlerId _raw_hidpp_handler; + raw::RawDevice::EvHandlerId _raw_dj_handler; + std::map> _dj_event_handlers; std::map> diff --git a/src/logid/backend/dj/ReceiverMonitor.cpp b/src/logid/backend/dj/ReceiverMonitor.cpp index 55e527d..96feea6 100644 --- a/src/logid/backend/dj/ReceiverMonitor.cpp +++ b/src/logid/backend/dj/ReceiverMonitor.cpp @@ -25,8 +25,11 @@ using namespace logid::backend::dj; -ReceiverMonitor::ReceiverMonitor(std::string path, double io_timeout) : - _receiver (std::make_shared(std::move(path), io_timeout)) +ReceiverMonitor::ReceiverMonitor(std::string path, + std::shared_ptr monitor, + double timeout): + _receiver (std::make_shared( + std::move(path), std::move(monitor), timeout)) { assert(_receiver->hidppEventHandlers().find("RECVMON") == _receiver->hidppEventHandlers().end()); @@ -42,13 +45,11 @@ ReceiverMonitor::ReceiverMonitor(std::string path, double io_timeout) : ReceiverMonitor::~ReceiverMonitor() { - this->stop(); + _receiver->removeHidppEventHandler("RECVMON"); } -void ReceiverMonitor::run() +void ReceiverMonitor::ready() { - _receiver->listen(); - if(_receiver->hidppEventHandlers().find("RECVMON") == _receiver->hidppEventHandlers().end()) { std::shared_ptr event_handler = @@ -62,8 +63,8 @@ void ReceiverMonitor::run() /* Running in a new thread prevents deadlocks since the * receiver may be enumerating. */ - std::async([this, report, - path=this->_receiver->rawDevice()->hidrawPath()]() { + spawn_task([this, report, + path= this->_receiver->rawDevice()->rawPath()]() { if (report.subId() == Receiver::DeviceConnection) { try { this->addDevice(this->_receiver->deviceConnectionEvent @@ -93,13 +94,6 @@ void ReceiverMonitor::run() enumerate(); } -void ReceiverMonitor::stop() -{ - _receiver->removeHidppEventHandler("RECVMON"); - - _receiver->stopListening(); -} - void ReceiverMonitor::enumerate() { _receiver->enumerateHidpp(); @@ -107,36 +101,34 @@ void ReceiverMonitor::enumerate() void ReceiverMonitor::waitForDevice(hidpp::DeviceIndex index) { - std::string nickname = "WAIT_DEV_" + std::to_string(index); - auto handler = std::make_shared(); - handler->condition = [index](std::vector& report)->bool { - return report[Offset::DeviceIndex] == index; - }; + auto handler_id = std::make_shared(); - handler->callback = [this, index, nickname](std::vector& report) { - (void)report; // Suppress unused warning + *handler_id = _receiver->rawDevice()->addEventHandler({ + [index](const std::vector& report)->bool { + return report[Offset::DeviceIndex] == index; + }, + [this, index, handler_id]( + [[maybe_unused]] const std::vector& report) { + hidpp::DeviceConnectionEvent event{}; + event.withPayload = false; + event.linkEstablished = true; + event.index = index; + event.fromTimeoutCheck = true; - hidpp::DeviceConnectionEvent event{}; - event.withPayload = false; - event.linkEstablished = true; - event.index = index; - event.fromTimeoutCheck = true; - - spawn_task( - [this, event, nickname]() { - try { - _receiver->rawDevice()->removeEventHandler(nickname); - this->addDevice(event); - } catch(std::exception& e) { - logPrintf(ERROR, "Failed to add device %d to receiver " - "on %s: %s", event.index, - _receiver->rawDevice()->hidrawPath().c_str(), - e.what()); - } - }); - }; - - _receiver->rawDevice()->addEventHandler(nickname, handler); + spawn_task([this, event, handler_id]() { + assert(handler_id); + try { + _receiver->rawDevice()->removeEventHandler(*handler_id); + addDevice(event); + } catch(std::exception& e) { + logPrintf(ERROR, "Failed to add device %d to receiver " + "on %s: %s", event.index, + _receiver->rawDevice()->rawPath().c_str(), + e.what()); + } + }); + } + }); } std::shared_ptr ReceiverMonitor::receiver() const diff --git a/src/logid/backend/dj/ReceiverMonitor.h b/src/logid/backend/dj/ReceiverMonitor.h index 33517b4..343d590 100644 --- a/src/logid/backend/dj/ReceiverMonitor.h +++ b/src/logid/backend/dj/ReceiverMonitor.h @@ -33,14 +33,13 @@ namespace dj { public: ReceiverMonitor(std::string path, - double io_timeout); + std::shared_ptr monitor, + double timeout); virtual ~ReceiverMonitor(); void enumerate(); - void run(); - void stop(); - protected: + void ready(); virtual void addDevice(hidpp::DeviceConnectionEvent event) = 0; virtual void removeDevice(hidpp::DeviceIndex index) = 0; @@ -52,7 +51,7 @@ namespace dj void _unpair(); - std::shared_ptr receiver() const; + [[nodiscard]] std::shared_ptr receiver() const; private: std::shared_ptr _receiver; }; diff --git a/src/logid/backend/dj/Report.cpp b/src/logid/backend/dj/Report.cpp index dff4040..f2abac6 100644 --- a/src/logid/backend/dj/Report.cpp +++ b/src/logid/backend/dj/Report.cpp @@ -66,7 +66,7 @@ static const std::array DJReportDesc2 = { 0xC0 // End Collection }; -bool dj::supportsDjReports(std::vector&& rdesc) +bool dj::supportsDjReports(const std::vector& rdesc) { auto it = std::search(rdesc.begin(), rdesc.end(), DJReportDesc.begin(), DJReportDesc.end()); @@ -76,7 +76,7 @@ bool dj::supportsDjReports(std::vector&& rdesc) return it != rdesc.end(); } -Report::Report(std::vector& data) : _data (data) +Report::Report(const std::vector& data) : _data (data) { switch(data[Offset::Type]) { case ReportType::Short: diff --git a/src/logid/backend/dj/Report.h b/src/logid/backend/dj/Report.h index 76439a5..0a89d1d 100644 --- a/src/logid/backend/dj/Report.h +++ b/src/logid/backend/dj/Report.h @@ -24,9 +24,7 @@ #include "defs.h" #include "../hidpp/defs.h" -namespace logid { -namespace backend { -namespace dj +namespace logid::backend::dj { namespace Offset { @@ -36,13 +34,13 @@ namespace dj static constexpr uint8_t Parameters = 3; } - bool supportsDjReports(std::vector&& rdesc); + bool supportsDjReports(const std::vector& rdesc); class Report { public: typedef ReportType::ReportType Type; - explicit Report(std::vector& data); + explicit Report(const std::vector& data); Report(Type type, hidpp::DeviceIndex index, uint8_t feature); Type type() const; @@ -53,6 +51,6 @@ namespace dj private: std::vector _data; }; -}}} +} #endif //LOGID_BACKEND_DJ_REPORT_H diff --git a/src/logid/backend/hidpp/Device.cpp b/src/logid/backend/hidpp/Device.cpp index 7bc521d..1a18a1d 100644 --- a/src/logid/backend/hidpp/Device.cpp +++ b/src/logid/backend/hidpp/Device.cpp @@ -24,11 +24,14 @@ #include "../hidpp20/features/DeviceName.h" #include "../hidpp20/Error.h" #include "../hidpp10/Error.h" +#include "../Error.h" #include "../dj/Receiver.h" using namespace logid::backend; using namespace logid::backend::hidpp; +using namespace std::chrono; + const char* Device::InvalidDevice::what() const noexcept { switch(_reason) { @@ -49,24 +52,31 @@ Device::InvalidDevice::Reason Device::InvalidDevice::code() const noexcept } Device::Device(const std::string& path, DeviceIndex index, - double io_timeout): - _raw_device (std::make_shared(path, io_timeout)), + std::shared_ptr monitor, double timeout): + io_timeout (duration_cast( + duration(timeout))), + _raw_device (std::make_shared(path, std::move(monitor))), _receiver (nullptr), _path (path), _index (index) { _init(); } -Device::Device(std::shared_ptr raw_device, DeviceIndex index) : - _raw_device (std::move(raw_device)), _receiver (nullptr), - _path (_raw_device->hidrawPath()), _index (index) +Device::Device(std::shared_ptr raw_device, DeviceIndex index, + double timeout) : + io_timeout (duration_cast( + duration(timeout))), + _raw_device (std::move(raw_device)), _receiver (nullptr), + _path (_raw_device->rawPath()), _index (index) { _init(); } Device::Device(std::shared_ptr receiver, - hidpp::DeviceConnectionEvent event) : - _raw_device (receiver->rawDevice()), _receiver (receiver), - _path (receiver->rawDevice()->hidrawPath()), _index (event.index) + hidpp::DeviceConnectionEvent event, double timeout) : + io_timeout (duration_cast( + duration(timeout))), + _raw_device (receiver->rawDevice()), _receiver (receiver), + _path (receiver->rawDevice()->rawPath()), _index (event.index) { // Device will throw an error soon, just do it now if(!event.linkEstablished) @@ -80,8 +90,11 @@ Device::Device(std::shared_ptr receiver, } Device::Device(std::shared_ptr receiver, - DeviceIndex index) : _raw_device (receiver->rawDevice()), - _receiver (receiver), _path (receiver->rawDevice()->hidrawPath()), + DeviceIndex index, double timeout) : + io_timeout (duration_cast( + duration(timeout))), + _raw_device (receiver->rawDevice()), + _receiver (receiver), _path (receiver->rawDevice()->rawPath()), _index (index) { _pid = receiver->getPairingInfo(_index).pid; @@ -105,53 +118,66 @@ std::tuple Device::version() const void Device::_init() { - _listening = false; - _supported_reports = getSupportedReports(_raw_device->reportDescriptor()); - if(!_supported_reports) + supported_reports = getSupportedReports(_raw_device->reportDescriptor()); + if(!supported_reports) throw InvalidDevice(InvalidDevice::NoHIDPPReport); + _raw_handler = _raw_device->addEventHandler({ + [index=this->_index](const std::vector& report)->bool { + return (report[Offset::Type] == Report::Type::Short || + report[Offset::Type] == Report::Type::Long) && + (report[Offset::DeviceIndex] == index); + }, [this](const std::vector& report)->void { + Report _report(report); + this->handleEvent(_report); + } }); + try { - hidpp20::EssentialRoot root(this); - _version = root.getVersion(); - } catch(hidpp10::Error &e) { - // Valid HID++ 1.0 devices should send an InvalidSubID error - if(e.code() != hidpp10::Error::InvalidSubID) - throw; + try { + hidpp20::EssentialRoot root(this); + _version = root.getVersion(); + } catch(hidpp10::Error &e) { + // Valid HID++ 1.0 devices should send an InvalidSubID error + if(e.code() != hidpp10::Error::InvalidSubID) + throw; - // HID++ 2.0 is not supported, assume HID++ 1.0 - _version = std::make_tuple(1, 0); - } + // HID++ 2.0 is not supported, assume HID++ 1.0 + _version = std::make_tuple(1, 0); + } - if(!_receiver) { - _pid = _raw_device->productId(); - if(std::get<0>(_version) >= 2) { - try { - hidpp20::EssentialDeviceName deviceName(this); - _name = deviceName.getName(); - } catch(hidpp20::UnsupportedFeature &e) { + if(!_receiver) { + _pid = _raw_device->productId(); + if(std::get<0>(_version) >= 2) { + try { + hidpp20::EssentialDeviceName deviceName(this); + _name = deviceName.getName(); + } catch(hidpp20::UnsupportedFeature &e) { + _name = _raw_device->name(); + } + } else { _name = _raw_device->name(); } } else { - _name = _raw_device->name(); - } - } else { - if(std::get<0>(_version) >= 2) { - try { - hidpp20::EssentialDeviceName deviceName(this); - _name = deviceName.getName(); - } catch(hidpp20::UnsupportedFeature &e) { + if(std::get<0>(_version) >= 2) { + try { + hidpp20::EssentialDeviceName deviceName(this); + _name = deviceName.getName(); + } catch(hidpp20::UnsupportedFeature &e) { + _name = _receiver->getDeviceName(_index); + } + } else { _name = _receiver->getDeviceName(_index); } - } else { - _name = _receiver->getDeviceName(_index); } + } catch(std::exception& e) { + _raw_device->removeEventHandler(_raw_handler); + throw; } } Device::~Device() { - if(_listening) - _raw_device->removeEventHandler("DEV_" + std::to_string(_index)); + _raw_device->removeEventHandler(_raw_handler); } void Device::addEventHandler(const std::string& nickname, @@ -175,53 +201,76 @@ const std::map>& void Device::handleEvent(Report& report) { + if(responseReport(report)) + return; + for(auto& handler : _event_handlers) if(handler.second->condition(report)) handler.second->callback(report); } -Report Device::sendReport(Report& report) +Report Device::sendReport(const Report &report) { - switch(report.type()) + std::lock_guard lock(_send_lock); { - case Report::Type::Short: - if(!(_supported_reports & HIDPP_REPORT_SHORT_SUPPORTED)) - report.setType(Report::Type::Long); - break; - case Report::Type::Long: - /* Report can be truncated, but that isn't a good idea. */ - assert(_supported_reports & HIDPP_REPORT_LONG_SUPPORTED); + std::lock_guard sl(_slot_lock); + _report_slot = {}; } + sendReportNoResponse(report); + std::unique_lock wait(_resp_wait_lock); + bool valid = _resp_cv.wait_for( + wait, io_timeout, [this](){ + std::lock_guard sl(_slot_lock); + return _report_slot.has_value(); + }); - auto raw_response = _raw_device->sendReport(report.rawReport()); + if(!valid) + throw TimeoutError(); - Report response(raw_response); - - Report::Hidpp10Error hidpp10_error{}; - if(response.isError10(&hidpp10_error)) - throw hidpp10::Error(hidpp10_error.error_code); - - Report::Hidpp20Error hidpp20_error{}; - if(response.isError20(&hidpp20_error)) - throw hidpp20::Error(hidpp20_error.error_code); - - return response; + { + Report::Hidpp10Error error{}; + if(report.isError10(&error)) + throw hidpp10::Error(error.error_code); + } + { + Report::Hidpp20Error error{}; + if(report.isError20(&error)) + throw hidpp20::Error(error.error_code); + } + return _report_slot.value(); } -void Device::sendReportNoResponse(Report &report) +bool Device::responseReport(const Report &report) +{ + if(_send_lock.try_lock()) { + _send_lock.unlock(); + return false; + } + + std::lock_guard lock(_slot_lock); + _report_slot = report; + _resp_cv.notify_all(); + return true; +} + +void Device::sendReportNoResponse(Report report) +{ + reportFixup(report); + _raw_device->sendReport(report.rawReport()); +} + +void Device::reportFixup(Report& report) { switch(report.type()) { - case Report::Type::Short: - if(!(_supported_reports & HIDPP_REPORT_SHORT_SUPPORTED)) - report.setType(Report::Type::Long); - break; - case Report::Type::Long: - /* Report can be truncated, but that isn't a good idea. */ - assert(_supported_reports & HIDPP_REPORT_LONG_SUPPORTED); + case Report::Type::Short: + if(!(supported_reports & HIDPP_REPORT_SHORT_SUPPORTED)) + report.setType(Report::Type::Long); + break; + case Report::Type::Long: + /* Report can be truncated, but that isn't a good idea. */ + assert(supported_reports & HIDPP_REPORT_LONG_SUPPORTED); } - - _raw_device->sendReportNoResponse(report.rawReport()); } std::string Device::name() const @@ -233,36 +282,3 @@ uint16_t Device::pid() const { return _pid; } - -void Device::listen() -{ - if(!_raw_device->isListening()) - _raw_device->listenAsync(); - - // Pass all HID++ events with device index to this device. - auto handler = std::make_shared(); - handler->condition = [index=this->_index](std::vector& report) - ->bool { - return (report[Offset::Type] == Report::Type::Short || - report[Offset::Type] == Report::Type::Long) && - (report[Offset::DeviceIndex] == index); - }; - handler->callback = [this](std::vector& report)->void { - Report _report(report); - this->handleEvent(_report); - }; - - _raw_device->addEventHandler("DEV_" + std::to_string(_index), handler); - _listening = true; -} - -void Device::stopListening() -{ - if(_listening) - _raw_device->removeEventHandler("DEV_" + std::to_string(_index)); - - _listening = false; - - if(!_raw_device->eventHandlers().empty()) - _raw_device->stopListener(); -} \ No newline at end of file diff --git a/src/logid/backend/hidpp/Device.h b/src/logid/backend/hidpp/Device.h index 9c36b85..534b7a7 100644 --- a/src/logid/backend/hidpp/Device.h +++ b/src/logid/backend/hidpp/Device.h @@ -19,6 +19,7 @@ #ifndef LOGID_BACKEND_HIDPP_DEVICE_H #define LOGID_BACKEND_HIDPP_DEVICE_H +#include #include #include #include @@ -62,14 +63,14 @@ namespace hidpp }; Device(const std::string& path, DeviceIndex index, - double io_timeout); - Device(std::shared_ptr raw_device, - DeviceIndex index); + std::shared_ptr monitor, double timeout); + Device(std::shared_ptr raw_device, DeviceIndex index, + double timeout); Device(std::shared_ptr receiver, - hidpp::DeviceConnectionEvent event); + hidpp::DeviceConnectionEvent event, double timeout); Device(std::shared_ptr receiver, - DeviceIndex index); - ~Device(); + DeviceIndex index, double timeout); + virtual ~Device(); std::string devicePath() const; DeviceIndex deviceIndex() const; @@ -78,33 +79,42 @@ namespace hidpp std::string name() const; uint16_t pid() const; - void listen(); // Runs asynchronously - void stopListening(); - void addEventHandler(const std::string& nickname, const std::shared_ptr& handler); void removeEventHandler(const std::string& nickname); const std::map>& eventHandlers(); - Report sendReport(Report& report); - void sendReportNoResponse(Report& report); + virtual Report sendReport(const Report& report); + void sendReportNoResponse(Report report); void handleEvent(Report& report); + protected: + // Returns whether the report is a response + virtual bool responseReport(const Report& report); + + void reportFixup(Report& report); + + const std::chrono::milliseconds io_timeout; + uint8_t supported_reports; private: void _init(); std::shared_ptr _raw_device; + raw::RawDevice::EvHandlerId _raw_handler; std::shared_ptr _receiver; std::string _path; DeviceIndex _index; - uint8_t _supported_reports; std::tuple _version; uint16_t _pid; std::string _name; - std::atomic _listening; + std::mutex _send_lock; + std::mutex _resp_wait_lock; + std::condition_variable _resp_cv; + std::mutex _slot_lock; + std::optional _report_slot; std::map> _event_handlers; }; diff --git a/src/logid/backend/hidpp/Report.cpp b/src/logid/backend/hidpp/Report.cpp index c5683a7..209847f 100644 --- a/src/logid/backend/hidpp/Report.cpp +++ b/src/logid/backend/hidpp/Report.cpp @@ -84,7 +84,7 @@ static const std::array LongReportDesc2 = { 0xC0 // End Collection }; -uint8_t hidpp::getSupportedReports(std::vector&& rdesc) +uint8_t hidpp::getSupportedReports(const std::vector& rdesc) { uint8_t ret = 0; @@ -248,7 +248,7 @@ uint8_t Report::swId() const void Report::setSwId(uint8_t sub_id) { - _data[Offset::Function] &= 0x0f; + _data[Offset::Function] &= 0xf0; _data[Offset::Function] |= sub_id & 0x0f; } @@ -290,7 +290,7 @@ void Report::setParams(const std::vector& _params) _data[Offset::Parameters + i] = _params[i]; } -bool Report::isError10(Report::Hidpp10Error *error) +bool Report::isError10(Report::Hidpp10Error *error) const { assert(error != nullptr); @@ -305,7 +305,7 @@ bool Report::isError10(Report::Hidpp10Error *error) return true; } -bool Report::isError20(Report::Hidpp20Error* error) +bool Report::isError20(Report::Hidpp20Error* error) const { assert(error != nullptr); diff --git a/src/logid/backend/hidpp/Report.h b/src/logid/backend/hidpp/Report.h index e406a6d..41b4839 100644 --- a/src/logid/backend/hidpp/Report.h +++ b/src/logid/backend/hidpp/Report.h @@ -32,7 +32,7 @@ namespace logid { namespace backend { namespace hidpp { - uint8_t getSupportedReports(std::vector&& rdesc); + uint8_t getSupportedReports(const std::vector& rdesc); namespace Offset { @@ -78,8 +78,8 @@ namespace hidpp Report::Type type() const; void setType(Report::Type type); - logid::backend::hidpp::DeviceIndex deviceIndex() const; - void setDeviceIndex(hidpp::DeviceIndex index); + DeviceIndex deviceIndex() const; + void setDeviceIndex(DeviceIndex index); uint8_t feature() const; void setFeature(uint8_t feature); @@ -106,15 +106,15 @@ namespace hidpp { uint8_t sub_id, address, error_code; }; - bool isError10(Hidpp10Error* error); + bool isError10(Hidpp10Error* error) const; struct Hidpp20Error { uint8_t feature_index, function, software_id, error_code; }; - bool isError20(Hidpp20Error* error); + bool isError20(Hidpp20Error* error) const; - std::vector rawReport () const { return _data; } + std::vector rawReport() const { return _data; } static constexpr std::size_t HeaderLength = 4; private: diff --git a/src/logid/backend/hidpp/defs.h b/src/logid/backend/hidpp/defs.h index 3a85470..96a8bf6 100644 --- a/src/logid/backend/hidpp/defs.h +++ b/src/logid/backend/hidpp/defs.h @@ -19,7 +19,7 @@ #ifndef LOGID_BACKEND_HIDPP_DEFS_H #define LOGID_BACKEND_HIDPP_DEFS_H -#define LOGID_HIDPP_SOFTWARE_ID 0 +#define LOGID_HIDPP_SOFTWARE_ID 1 #include diff --git a/src/logid/backend/hidpp10/Device.cpp b/src/logid/backend/hidpp10/Device.cpp index 65cf538..2596669 100644 --- a/src/logid/backend/hidpp10/Device.cpp +++ b/src/logid/backend/hidpp10/Device.cpp @@ -20,29 +20,107 @@ #include #include "Device.h" #include "defs.h" +#include "../Error.h" using namespace logid::backend; using namespace logid::backend::hidpp10; -Device::Device(const std::string &path, hidpp::DeviceIndex index, - double io_timeout) : - hidpp::Device(path, index, io_timeout) +Device::Device(const std::string &path, + hidpp::DeviceIndex index, + std::shared_ptr monitor, double timeout) : + hidpp::Device(path, index, std::move(monitor), timeout) { assert(version() == std::make_tuple(1, 0)); } Device::Device(std::shared_ptr raw_dev, - hidpp::DeviceIndex index) : hidpp::Device(std::move(raw_dev), index) + hidpp::DeviceIndex index, + double timeout) : hidpp::Device(std::move(raw_dev), index, timeout) { assert(version() == std::make_tuple(1, 0)); } -Device::Device(std::shared_ptr receiver, hidpp::DeviceIndex index) - : hidpp::Device(receiver, index) +Device::Device(std::shared_ptr receiver, + hidpp::DeviceIndex index, + double timeout) + : hidpp::Device(std::move(receiver), index, timeout) { assert(version() == std::make_tuple(1, 0)); } +hidpp::Report Device::sendReport(const hidpp::Report& report) +{ + decltype(_responses)::iterator response_slot; + while(true) { + { + std::lock_guard lock(_response_lock); + response_slot = _responses.find(report.subId()); + if(response_slot == _responses.end()) { + response_slot = _responses.emplace( + report.subId(), std::optional()).first; + break; + } + } + std::unique_lock lock(_response_wait_lock); + _response_cv.wait(lock, [this, sub_id=report.subId()](){ + std::lock_guard lock(_response_lock); + return _responses.find(sub_id) != _responses.end(); + }); + } + + sendReportNoResponse(report); + std::unique_lock wait(_response_wait_lock); + bool valid = _response_cv.wait_for(wait, io_timeout, + [this, &response_slot]() { + std::lock_guard lock(_response_lock); + return response_slot->second.has_value(); + }); + + if(!valid) { + std::lock_guard lock(_response_lock); + _responses.erase(response_slot); + throw TimeoutError(); + } + + std::lock_guard lock(_response_lock); + assert(response_slot->second.has_value()); + auto response = response_slot->second.value(); + _responses.erase(response_slot); + if(std::holds_alternative(response)) + return std::get(response); + else // if(std::holds_alternative(response)) + throw Error(std::get(response)); +} + +bool Device::responseReport(const hidpp::Report& report) +{ + std::lock_guard lock(_response_lock); + uint8_t sub_id; + + bool is_error = false; + hidpp::Report::Hidpp10Error hidpp10_error {}; + if(report.isError10(&hidpp10_error)) { + sub_id = hidpp10_error.sub_id; + is_error = true; + } else { + sub_id = report.subId(); + } + + auto response_slot = _responses.find(sub_id); + if(response_slot == _responses.end()) + return false; + + if(is_error) { + response_slot->second = static_cast( + hidpp10_error.error_code); + } else { + response_slot->second = report; + } + + _response_cv.notify_all(); + return true; +} + std::vector Device::getRegister(uint8_t address, const std::vector& params, hidpp::Report::Type type) { @@ -76,7 +154,7 @@ std::vector Device::accessRegister(uint8_t sub_id, uint8_t address, std::copy(params.begin(), params.end(), request.paramBegin()); auto response = sendReport(request); - return std::vector(response.paramBegin(), response.paramEnd()); + return {response.paramBegin(), response.paramEnd()}; } diff --git a/src/logid/backend/hidpp10/Device.h b/src/logid/backend/hidpp10/Device.h index be98dd0..659abbb 100644 --- a/src/logid/backend/hidpp10/Device.h +++ b/src/logid/backend/hidpp10/Device.h @@ -19,7 +19,11 @@ #ifndef LOGID_BACKEND_HIDPP10_DEVICE_H #define LOGID_BACKEND_HIDPP10_DEVICE_H +#include +#include + #include "../hidpp/Device.h" +#include "Error.h" namespace logid { namespace backend { @@ -29,18 +33,29 @@ namespace hidpp10 { public: Device(const std::string& path, hidpp::DeviceIndex index, - double io_timeout); + std::shared_ptr monitor, double timeout); Device(std::shared_ptr raw_dev, - hidpp::DeviceIndex index); + hidpp::DeviceIndex index, double timeout); Device(std::shared_ptr receiver, - hidpp::DeviceIndex index); + hidpp::DeviceIndex index, double timeout); + + hidpp::Report sendReport(const hidpp::Report& report) final; std::vector getRegister(uint8_t address, const std::vector& params, hidpp::Report::Type type); std::vector setRegister(uint8_t address, const std::vector& params, hidpp::Report::Type type); + protected: + bool responseReport(const hidpp::Report& report) final; private: + std::mutex _response_lock; + std::mutex _response_wait_lock; + std::condition_variable _response_cv; + + typedef std::variant Response; + std::map> _responses; + std::vector accessRegister(uint8_t sub_id, uint8_t address, const std::vector& params); }; diff --git a/src/logid/backend/hidpp20/Device.cpp b/src/logid/backend/hidpp20/Device.cpp index a3ac6a3..78cce96 100644 --- a/src/logid/backend/hidpp20/Device.cpp +++ b/src/logid/backend/hidpp20/Device.cpp @@ -20,26 +20,44 @@ #include "Device.h" #include "../hidpp/defs.h" +#include "../Error.h" +#include "../dj/Receiver.h" +using namespace logid::backend; using namespace logid::backend::hidpp20; -Device::Device(std::string path, hidpp::DeviceIndex index, double io_timeout) : - hidpp::Device(path, index, io_timeout) +Device::Device(const std::string& path, hidpp::DeviceIndex index, + std::shared_ptr monitor, double timeout) : + hidpp::Device(path, index, + std::move(monitor), timeout) { - assert(std::get<0>(version()) >= 2); + // TODO: Fix version check + if(std::get<0>(version()) < 2) + throw std::runtime_error("Invalid HID++ version"); } Device::Device(std::shared_ptr raw_device, - hidpp::DeviceIndex index) - : hidpp::Device(raw_device, index) + hidpp::DeviceIndex index, double timeout) : + hidpp::Device(std::move(raw_device), index, timeout) { - assert(std::get<0>(version()) >= 2); + if(std::get<0>(version()) < 2) + throw std::runtime_error("Invalid HID++ version"); } -Device::Device(std::shared_ptr receiver, hidpp::DeviceIndex index) - : hidpp::Device(receiver, index) +Device::Device(std::shared_ptr receiver, + hidpp::DeviceConnectionEvent event, double timeout) : + hidpp::Device(std::move(receiver), event, timeout) { - assert(std::get<0>(version()) >= 2); + if(std::get<0>(version()) < 2) + throw std::runtime_error("Invalid HID++ version"); +} + +Device::Device(std::shared_ptr receiver, + hidpp::DeviceIndex index, double timeout) + : hidpp::Device(std::move(receiver), index, timeout) +{ + if(std::get<0>(version()) < 2) + throw std::runtime_error("Invalid HID++ version"); } std::vector Device::callFunction(uint8_t feature_index, @@ -81,4 +99,94 @@ void Device::callFunctionNoResponse(uint8_t feature_index, uint8_t function, std::copy(params.begin(), params.end(), request.paramBegin()); this->sendReportNoResponse(request); -} \ No newline at end of file +} + +hidpp::Report Device::sendReport(const hidpp::Report& report) +{ + decltype(_responses)::iterator response_slot; + + while(true) { + { + std::lock_guard lock(_response_lock); + if(_responses.empty()) { + response_slot = _responses.emplace( + 2, std::optional()).first; + break; + } else if(_responses.size() < response_slots) { + uint8_t i = 0; + for(auto& x : _responses) { + if(x.first != i + 1) { + ++i; + break; + } + i = x.first; + } + assert(_responses.count(i) == 0); + + response_slot = _responses.emplace( + i, std::optional()).first; + break; + } + } + + std::unique_lock lock(_response_wait_lock); + _response_cv.wait(lock, [this, sub_id=report.subId()](){ + std::lock_guard lock(_response_lock); + return _responses.size() < response_slots; + }); + } + + { + std::lock_guard lock(_response_lock); + hidpp::Report mod_report {report}; + mod_report.setSwId(response_slot->first); + sendReportNoResponse(std::move(mod_report)); + } + + std::unique_lock wait(_response_wait_lock); + bool valid = _response_cv.wait_for(wait, io_timeout, + [this, &response_slot]() { + std::lock_guard lock(_response_lock); + return response_slot->second.has_value(); + }); + + if(!valid) { + std::lock_guard lock(_response_lock); + _responses.erase(response_slot); + throw TimeoutError(); + } + + std::lock_guard lock(_response_lock); + assert(response_slot->second.has_value()); + auto response = response_slot->second.value(); + _responses.erase(response_slot); + if(std::holds_alternative(response)) + return std::get(response); + else // if(std::holds_alternative(response)) + throw Error(std::get(response)); +} + +bool Device::responseReport(const hidpp::Report& report) +{ + std::lock_guard lock(_response_lock); + uint8_t sw_id = report.swId(); + + bool is_error = false; + hidpp::Report::Hidpp20Error hidpp20_error {}; + if(report.isError20(&hidpp20_error)) + is_error = true; + + auto response_slot = _responses.find(sw_id); + if(response_slot == _responses.end()) + return false; + + if(is_error) { + response_slot->second = static_cast( + hidpp20_error.error_code); + } else { + response_slot->second = report; + } + + _response_cv.notify_all(); + return true; +} diff --git a/src/logid/backend/hidpp20/Device.h b/src/logid/backend/hidpp20/Device.h index 64f80c3..0813cd5 100644 --- a/src/logid/backend/hidpp20/Device.h +++ b/src/logid/backend/hidpp20/Device.h @@ -19,8 +19,12 @@ #ifndef LOGID_BACKEND_HIDPP20_DEVICE_H #define LOGID_BACKEND_HIDPP20_DEVICE_H -#include "../hidpp/Device.h" #include +#include +#include + +#include "../hidpp/Device.h" +#include "Error.h" namespace logid { namespace backend { @@ -28,11 +32,14 @@ namespace hidpp20 { class Device : public hidpp::Device { public: - Device(std::string path, hidpp::DeviceIndex index, - double io_timeout); - Device(std::shared_ptr raw_device, hidpp::DeviceIndex index); - Device(std::shared_ptr receiver, hidpp::DeviceIndex - index); + Device(const std::string& path, hidpp::DeviceIndex index, + std::shared_ptr monitor, double timeout); + Device(std::shared_ptr raw_device, + hidpp::DeviceIndex index, double timeout); + Device(std::shared_ptr receiver, + hidpp::DeviceConnectionEvent event, double timeout); + Device(std::shared_ptr receiver, + hidpp::DeviceIndex index, double timeout); std::vector callFunction(uint8_t feature_index, uint8_t function, @@ -41,6 +48,18 @@ namespace hidpp20 { void callFunctionNoResponse(uint8_t feature_index, uint8_t function, std::vector& params); + + hidpp::Report sendReport(const hidpp::Report& report) final; + protected: + bool responseReport(const hidpp::Report& report) final; + private: + std::mutex _response_lock; + std::mutex _response_wait_lock; + std::condition_variable _response_cv; + + static constexpr int response_slots = 14; + typedef std::variant Response; + std::map> _responses; }; }}} diff --git a/src/logid/backend/raw/DeviceMonitor.cpp b/src/logid/backend/raw/DeviceMonitor.cpp index 1553e61..2a1756f 100644 --- a/src/logid/backend/raw/DeviceMonitor.cpp +++ b/src/logid/backend/raw/DeviceMonitor.cpp @@ -22,7 +22,6 @@ #include "RawDevice.h" #include "../hidpp/Device.h" -#include #include extern "C" @@ -34,117 +33,85 @@ extern "C" using namespace logid; using namespace logid::backend::raw; -DeviceMonitor::DeviceMonitor() +DeviceMonitor::DeviceMonitor() : _io_monitor (std::make_shared()), + _ready (false) { - if(-1 == pipe(_pipe)) - throw std::system_error(errno, std::system_category(), - "pipe creation failed"); - + int ret; _udev_context = udev_new(); if(!_udev_context) throw std::runtime_error("udev_new failed"); + + _udev_monitor = udev_monitor_new_from_netlink(_udev_context, + "udev"); + if(!_udev_monitor) { + if(_udev_context) + udev_unref(_udev_context); + throw std::runtime_error("udev_monitor_new_from_netlink failed"); + } + + ret = udev_monitor_filter_add_match_subsystem_devtype( + _udev_monitor, "hidraw", nullptr); + if(0 != ret) { + if(_udev_monitor) + udev_monitor_unref(_udev_monitor); + if(_udev_context) + udev_unref(_udev_context); + throw std::system_error( + -ret, std::system_category(), + "udev_monitor_filter_add_match_subsystem_devtype"); + } + + ret = udev_monitor_enable_receiving(_udev_monitor); + if(0 != ret) { + if(_udev_monitor) + udev_monitor_unref(_udev_monitor); + if(_udev_context) + udev_unref(_udev_context); + throw std::system_error(-ret, std::system_category(), + "udev_monitor_enable_receiving"); + } + + _fd = udev_monitor_get_fd(_udev_monitor); } DeviceMonitor::~DeviceMonitor() { - this->stop(); + if(_ready) + _io_monitor->remove(_fd); - udev_unref(_udev_context); - - for(int i : _pipe) - close(i); + if(_udev_monitor) + udev_monitor_unref(_udev_monitor); + if(_udev_context) + udev_unref(_udev_context); } -void DeviceMonitor::run() +void DeviceMonitor::ready() { - int ret; - std::lock_guard lock(_running); + if(_ready) + return; + _ready = true; - struct udev_monitor* monitor = udev_monitor_new_from_netlink(_udev_context, - "udev"); - if(!monitor) - throw std::runtime_error("udev_monitor_new_from_netlink failed"); + _io_monitor->add(_fd, { + [this]() { + struct udev_device *device = udev_monitor_receive_device( + _udev_monitor); + std::string action = udev_device_get_action(device); + std::string devnode = udev_device_get_devnode(device); - ret = udev_monitor_filter_add_match_subsystem_devtype(monitor, "hidraw", - nullptr); - if (0 != ret) - throw std::system_error (-ret, std::system_category(), - "udev_monitor_filter_add_match_subsystem_devtype"); + if (action == "add") + spawn_task([this, devnode]() { _addHandler(devnode); } ); + else if (action == "remove") + spawn_task([this, devnode]() { _removeHandler(devnode); } ); - ret = udev_monitor_enable_receiving(monitor); - if(0 != ret) - throw std::system_error(-ret, std::system_category(), - "udev_moniotr_enable_receiving"); - - this->enumerate(); - - int fd = udev_monitor_get_fd(monitor); - - _run_monitor = true; - while (_run_monitor) { - fd_set fds; - FD_ZERO(&fds); - FD_SET(_pipe[0], &fds); - FD_SET(fd, &fds); - - if (-1 == select (std::max (_pipe[0], fd)+1, &fds, nullptr, - nullptr, nullptr)) { - if (errno == EINTR) - continue; - throw std::system_error (errno, std::system_category(), - "udev_monitor select"); - } - - if (FD_ISSET(fd, &fds)) { - struct udev_device *device = udev_monitor_receive_device(monitor); - std::string action = udev_device_get_action(device); - std::string devnode = udev_device_get_devnode(device); - - if (action == "add") - spawn_task( - [this, name=devnode]() { - try { - // Wait for device to initialise - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - auto supported_reports = backend::hidpp::getSupportedReports( - RawDevice::getReportDescriptor(name)); - if(supported_reports) - this->addDevice(name); - else - logPrintf(DEBUG, "Unsupported device %s ignored", - name.c_str()); - } catch(std::exception& e) { - logPrintf(WARN, "Error adding device %s: %s", - name.c_str(), e.what()); - } - }); - else if (action == "remove") - spawn_task( - [this, name=devnode]() { - try { - this->removeDevice(name); - } catch(std::exception& e) { - logPrintf(WARN, "Error removing device %s: %s", - name.c_str(), e.what()); - } - }); - - udev_device_unref (device); - } - if (FD_ISSET(_pipe[0], &fds)) { - char c; - if (-1 == read(_pipe[0], &c, sizeof (char))) - throw std::system_error (errno, std::system_category(), - "read pipe"); - break; - } - } -} - -void DeviceMonitor::stop() -{ - _run_monitor = false; - std::lock_guard lock(_running); + udev_device_unref(device); + }, + []() { + throw std::runtime_error("udev hangup"); + }, + []() { + throw std::runtime_error("udev error"); + } + }); } void DeviceMonitor::enumerate() @@ -174,22 +141,39 @@ void DeviceMonitor::enumerate() std::string devnode = udev_device_get_devnode(device); udev_device_unref(device); - spawn_task( - [this, name=devnode]() { - try { - auto supported_reports = backend::hidpp::getSupportedReports( - RawDevice::getReportDescriptor(name)); - if(supported_reports) - this->addDevice(name); - else - logPrintf(DEBUG, "Unsupported device %s ignored", - name.c_str()); - } catch(std::exception& e) { - logPrintf(WARN, "Error adding device %s: %s", - name.c_str(), e.what()); - } - }); + spawn_task([this, devnode]() { _addHandler(devnode); } ); } udev_enumerate_unref(udev_enum); } + +void DeviceMonitor::_addHandler(const std::string& device) +{ + try { + auto supported_reports = backend::hidpp::getSupportedReports( + RawDevice::getReportDescriptor(device)); + if(supported_reports) + this->addDevice(device); + else + logPrintf(DEBUG, "Unsupported device %s ignored", + device.c_str()); + } catch(std::exception& e) { + logPrintf(WARN, "Error adding device %s: %s", + device.c_str(), e.what()); + } +} + +void DeviceMonitor::_removeHandler(const std::string& device) +{ + try { + this->removeDevice(device); + } catch(std::exception& e) { + logPrintf(WARN, "Error removing device %s: %s", + device.c_str(), e.what()); + } +} + +std::shared_ptr DeviceMonitor::ioMonitor() const +{ + return _io_monitor; +} diff --git a/src/logid/backend/raw/DeviceMonitor.h b/src/logid/backend/raw/DeviceMonitor.h index 9b7fc8e..80abf00 100644 --- a/src/logid/backend/raw/DeviceMonitor.h +++ b/src/logid/backend/raw/DeviceMonitor.h @@ -23,28 +23,40 @@ #include #include #include +#include "IOMonitor.h" -struct udev; +extern "C" +{ + struct udev; + struct udev_monitor; +} namespace logid::backend::raw { class DeviceMonitor { public: + virtual ~DeviceMonitor(); + void enumerate(); - void run(); - void stop(); + [[nodiscard]] std::shared_ptr ioMonitor() const; protected: DeviceMonitor(); - virtual ~DeviceMonitor(); + // This should be run once the derived class is ready + void ready(); virtual void addDevice(std::string device) = 0; virtual void removeDevice(std::string device) = 0; private: + void _addHandler(const std::string& device); + void _removeHandler(const std::string& device); + + std::shared_ptr _io_monitor; + struct udev* _udev_context; - int _pipe[2]; - std::atomic _run_monitor; - std::mutex _running; + struct udev_monitor* _udev_monitor; + int _fd; + bool _ready; }; } diff --git a/src/logid/backend/raw/IOMonitor.cpp b/src/logid/backend/raw/IOMonitor.cpp new file mode 100644 index 0000000..f92e6b0 --- /dev/null +++ b/src/logid/backend/raw/IOMonitor.cpp @@ -0,0 +1,200 @@ +/* + * Copyright 2022 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include +#include "IOMonitor.h" + +extern "C" +{ +#include +#include +#include +} + +using namespace logid::backend::raw; + +IOHandler::IOHandler(std::function r, + std::function hup, + std::function err) : + read (std::move(r)), + hangup (std::move(hup)), + error (std::move(err)) +{ +} + +IOMonitor::IOMonitor() : _epoll_fd (epoll_create1(0)), + _event_fd(eventfd(0, EFD_NONBLOCK)) +{ + if(_epoll_fd < 0) { + if(_event_fd >= 0) + close(_event_fd); + throw std::runtime_error("failed to create epoll fd"); + } + + if(_event_fd < 0) { + if(_epoll_fd >= 0) + close(_epoll_fd); + throw std::runtime_error("failed to create event fd"); + } + + struct epoll_event event{}; + event.events = EPOLLIN; + event.data.fd = _event_fd; + + if(::epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, _event_fd, &event)) { + throw std::system_error(errno, std::generic_category()); + } + + _fds.emplace(std::piecewise_construct,std::forward_as_tuple(_event_fd), + std::forward_as_tuple([]() { },[]() { + throw std::runtime_error("eventfd hangup"); + },[]() { + throw std::runtime_error("eventfd error"); + })); + + std::thread([this](){ + _listen(); + }).detach(); +} + +IOMonitor::~IOMonitor() noexcept +{ + std::lock_guard ctl_lock(_ctl_lock); + _stop(); + + if(_event_fd >= 0) + close(_event_fd); + + if(_epoll_fd >= 0) + close(_epoll_fd); +} + +void IOMonitor::_listen() +{ + std::unique_lock run_lock(_run_lock, std::try_to_lock); + if(!run_lock.owns_lock()) + throw std::runtime_error("IOMonitor already listening"); + std::vector events; + + _is_running = true; + + while(_is_running) { + { + std::unique_lock lock(_interrupt_lock); + _interrupt_cv.wait(lock, [this](){ + return !(bool)_interrupting; + }); + + if(!_is_running) + break; + } + + std::lock_guard io_lock(_io_lock); + if(events.size() != _fds.size()) + events.resize(_fds.size()); + int eventc = ::epoll_wait(_epoll_fd, events.data(), events.size(), -1); + for(int i = 0; i < eventc; ++i) { + const auto& handler = _fds.at(events[i].data.fd); + if(events[i].events & EPOLLIN) + handler.read(); + if(events[i].events & EPOLLHUP) + handler.hangup(); + if(events[i].events & EPOLLERR) + handler.error(); + } + } +} + +void IOMonitor::_stop() noexcept +{ + _interrupt(); + _is_running = false; + _continue(); + std::lock_guard run_lock(_run_lock); +} + +bool IOMonitor::_running() const +{ + std::unique_lock run_lock(_run_lock, std::try_to_lock); + return !run_lock.owns_lock(); +} + +void IOMonitor::add(int fd, IOHandler handler) +{ + std::lock_guard lock(_ctl_lock); + _interrupt(); + + struct epoll_event event{}; + event.events = EPOLLIN | EPOLLHUP | EPOLLERR; + event.data.fd = fd; + + // TODO: EPOLL_CTL_MOD + if(_fds.contains(fd)) { + _continue(); + throw std::runtime_error("duplicate io fd"); + } + + if(::epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, fd, &event)) { + _continue(); + throw std::system_error(errno, std::generic_category()); + } + _fds.emplace(fd, std::move(handler)); + + _continue(); +} + +void IOMonitor::remove(int fd) noexcept +{ + std::lock_guard lock(_ctl_lock); + _interrupt(); + std::lock_guard io_lock(_io_lock); + + ::epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, fd, nullptr); + _fds.erase(fd); + + _continue(); +} + +void IOMonitor::_interrupt() noexcept +{ + std::unique_lock run_lock(_run_lock, std::try_to_lock); + + _interrupting = true; + + uint64_t counter = 1; + ssize_t ret = ::write(_event_fd, &counter, sizeof(counter)); + assert(ret == sizeof(counter)); + + // Wait for the IO monitor to _stop + std::lock_guard io_lock(_io_lock); + +} + +void IOMonitor::_continue() noexcept +{ + std::unique_lock run_lock(_run_lock, std::try_to_lock); + + uint64_t counter; + ssize_t ret = ::read(_event_fd, &counter, sizeof(counter)); + + assert(ret != -1); + + if(counter == 1) { + _interrupting = false; + _interrupt_cv.notify_all(); + } +} diff --git a/src/logid/backend/raw/IOMonitor.h b/src/logid/backend/raw/IOMonitor.h new file mode 100644 index 0000000..eae487c --- /dev/null +++ b/src/logid/backend/raw/IOMonitor.h @@ -0,0 +1,69 @@ +/* + * Copyright 2022 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_BACKEND_RAW_IOMONITOR_H +#define LOGID_BACKEND_RAW_IOMONITOR_H + +#include +#include +#include +#include +#include + +namespace logid::backend::raw { + struct IOHandler { + std::function read; + std::function hangup; + std::function error; + + IOHandler(std::function r, + std::function hup, + std::function err); + }; + + class IOMonitor + { + public: + IOMonitor(); + ~IOMonitor() noexcept; + + void add(int fd, IOHandler handler); + void remove(int fd) noexcept; + private: + void _listen(); // This is a blocking call + void _stop() noexcept; + + bool _running() const; + + void _interrupt() noexcept; + void _continue() noexcept; + + std::map _fds; + std::mutex _io_lock, _ctl_lock; + mutable std::mutex _run_lock; + std::atomic_bool _is_running; + + std::atomic_bool _interrupting; + std::mutex _interrupt_lock; + std::condition_variable _interrupt_cv; + + const int _epoll_fd; + const int _event_fd; + }; +} + +#endif //LOGID_BACKEND_RAW_IOMONITOR_H diff --git a/src/logid/backend/raw/RawDevice.cpp b/src/logid/backend/raw/RawDevice.cpp index 0c86eb6..77fecc2 100644 --- a/src/logid/backend/raw/RawDevice.cpp +++ b/src/logid/backend/raw/RawDevice.cpp @@ -17,19 +17,15 @@ */ #include "RawDevice.h" +#include "DeviceMonitor.h" +#include "IOMonitor.h" #include "../Error.h" -#include "../hidpp/defs.h" -#include "../dj/defs.h" #include "../../util/log.h" -#include "../hidpp/Report.h" -#include "../../util/thread.h" #include #include #include -#define MAX_DATA_LENGTH 32 - extern "C" { #include @@ -43,101 +39,88 @@ using namespace logid::backend::raw; using namespace logid::backend; using namespace std::chrono; -bool RawDevice::supportedReport(uint8_t id, uint8_t length) +int get_fd(const std::string& path) { - switch(id) { - case hidpp::ReportType::Short: - return length == (hidpp::ShortParamLength + - hidpp::Report::HeaderLength); - case hidpp::ReportType::Long: - return length == (hidpp::LongParamLength + - hidpp::Report::HeaderLength); - case dj::ReportType::Short: - return length == (dj::ShortParamLength + dj::HeaderLength); - case dj::ReportType::Long: - return length == (dj::LongParamLength + dj::HeaderLength); - default: - return false; - } -} - -RawDevice::RawDevice(std::string path, double io_timeout) : - _path (std::move(path)), - _continue_listen (false), _continue_respond (false), - _io_timeout (duration_cast( - duration(io_timeout))) -{ - int ret; - - _fd = ::open(_path.c_str(), O_RDWR); - if (_fd == -1) + int fd = ::open(path.c_str(), O_RDWR | O_NONBLOCK); + if(fd == -1) throw std::system_error(errno, std::system_category(), - "RawDevice open failed"); + "RawDevice open failed"); - hidraw_devinfo devinfo{}; - if (-1 == ::ioctl(_fd, HIDIOCGRAWINFO, &devinfo)) { - int err = errno; - ::close(_fd); - throw std::system_error(err, std::system_category(), - "RawDevice HIDIOCGRAWINFO failed"); - } - _vid = devinfo.vendor; - _pid = devinfo.product; - - char name_buf[256]; - if (-1 == (ret = ::ioctl(_fd, HIDIOCGRAWNAME(sizeof(name_buf)), name_buf) - )) { - int err = errno; - ::close(_fd); - throw std::system_error(err, std::system_category(), - "RawDevice HIDIOCGRAWNAME failed"); - } - _name.assign(name_buf, ret - 1); - - _rdesc = getReportDescriptor(_fd); - - if (-1 == ::pipe(_pipe)) { - int err = errno; - close(_fd); - throw std::system_error(err, std::system_category(), - "RawDevice pipe open failed"); - } - - _continue_listen = false; + return fd; } -RawDevice::~RawDevice() +RawDevice::dev_info get_devinfo(int fd) { - if(_fd != -1) - { - ::close(_fd); - ::close(_pipe[0]); - ::close(_pipe[1]); + hidraw_devinfo devinfo{}; + if (-1 == ::ioctl(fd, HIDIOCGRAWINFO, &devinfo)) { + int err = errno; + ::close(fd); + throw std::system_error(err, std::system_category(), + "RawDevice HIDIOCGRAWINFO failed"); } + + return {devinfo.vendor, devinfo.product}; } -std::string RawDevice::hidrawPath() const + +std::string get_name(int fd) +{ + ssize_t len; + char name_buf[256]; + if (-1 == (len = ::ioctl(fd, HIDIOCGRAWNAME(sizeof(name_buf)), name_buf))) { + int err = errno; + ::close(fd); + throw std::system_error(err, std::system_category(), + "RawDevice HIDIOCGRAWNAME failed"); + } + return {name_buf, static_cast(len)}; +} + +RawDevice::RawDevice(std::string path, + std::shared_ptr monitor) : + _valid (true), + _path (std::move(path)), + _fd (get_fd(_path)), + _devinfo (get_devinfo(_fd)), + _name (get_name(_fd)), + _rdesc (getReportDescriptor(_fd)), + _io_monitor (monitor->ioMonitor()) +{ + _io_monitor->add(_fd, { + [this]() { _readReports(); }, + [this]() { _valid = false; }, + [this]() { _valid = false; } + }); +} + +RawDevice::~RawDevice() noexcept +{ + _io_monitor->remove(_fd); + ::close(_fd); +} + +const std::string& RawDevice::rawPath() const { return _path; } -std::string RawDevice::name() const +const std::string& RawDevice::name() const { return _name; } -uint16_t RawDevice::vendorId() const +int16_t RawDevice::vendorId() const { - return _vid; + return _devinfo.vid; } -uint16_t RawDevice::productId() const +int16_t RawDevice::productId() const { - return _pid; + return _devinfo.pid; } std::vector RawDevice::getReportDescriptor(std::string path) { - int fd = ::open(path.c_str(), O_RDWR); + int fd = ::open(path.c_str(), O_RDWR | O_NONBLOCK); if (fd == -1) throw std::system_error(errno, std::system_category(), "open failed"); @@ -165,140 +148,16 @@ std::vector RawDevice::getReportDescriptor(int fd) return std::vector(rdesc.value, rdesc.value + rdesc.size); } -std::vector RawDevice::reportDescriptor() const +const std::vector& RawDevice::reportDescriptor() const { return _rdesc; } -std::vector RawDevice::sendReport(const std::vector& report) +void RawDevice::sendReport(const std::vector& report) { - /* If the listener will stop, handle I/O manually. - * Otherwise, push to queue and wait for result. */ - if(_continue_listen) { - std::mutex send_report; - std::unique_lock lock(send_report); - std::condition_variable cv; - bool top_of_queue = false; - auto task = std::make_shared()>> - ( [this, report, &cv, &top_of_queue] () { - top_of_queue = true; - cv.notify_all(); - return this->_respondToReport(report); - }); - auto f = task->get_future(); - _io_queue.push(task); - interruptRead(false); // Alert listener to prioritise - cv.wait(lock, [&top_of_queue]{ return top_of_queue; }); - auto status = f.wait_for(_io_timeout); - if(status == std::future_status::timeout) { - _continue_respond = false; - interruptRead(); - return f.get(); // Expecting an error, but it could work - } - return f.get(); - } - else { - std::exception_ptr _exception; - auto response = std::async(std::launch::deferred, - [this, report]()->std::vector { - return _respondToReport(report); - }); - auto status = response.wait_for(_io_timeout); - if(status == std::future_status::timeout) { - interruptRead(); - if(response.valid()) - response.wait(); - throw TimeoutError(); - } - return response.get(); - } -} + if(!_valid) + throw InvalidDevice(); -// DJ commands are not systematically acknowledged, do not expect a result. -void RawDevice::sendReportNoResponse(const std::vector& report) -{ - /* If the listener will stop, handle I/O manually. - * Otherwise, push to queue and wait for result. */ - if(_continue_listen) { - auto task = std::make_shared()>> - ([this, report]() { - this->_sendReport(report); - return std::vector(); - }); - auto f = task->get_future(); - _io_queue.push(task); - f.get(); - } - else - _sendReport(report); -} - -std::vector RawDevice::_respondToReport - (const std::vector& request) -{ - _sendReport(request); - _continue_respond = true; - - auto start_point = std::chrono::steady_clock::now(); - - while(_continue_respond) { - std::vector response; - auto current_point = std::chrono::steady_clock::now(); - auto timeout = _io_timeout - std::chrono::duration_cast - (current_point - start_point); - if(timeout.count() <= 0) - throw TimeoutError(); - _readReport(response, MAX_DATA_LENGTH, timeout); - - if(!_continue_respond) - throw TimeoutError(); - - // All reports have the device index at byte 2 - if(response[1] != request[1]) { - if(_continue_listen) - this->_handleEvent(response); - continue; - } - - if(hidpp::ReportType::Short == request[0] || - hidpp::ReportType::Long == request[0]) { - if(hidpp::ReportType::Short != response[0] && - hidpp::ReportType::Long != response[0]) { - if(_continue_listen) - this->_handleEvent(response); - 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; - } - - if(_continue_listen) - this->_handleEvent(response); - } - - return {}; -} - -int RawDevice::_sendReport(const std::vector& report) -{ - std::lock_guard lock(_dev_io); if(logid::global_loglevel <= LogLevel::RAWREPORT) { printf("[RAWREPORT] %s OUT: ", _path.c_str()); for(auto &i : report) @@ -306,180 +165,48 @@ int RawDevice::_sendReport(const std::vector& report) printf("\n"); } - assert(supportedReport(report[0], report.size())); - - int ret = ::write(_fd, report.data(), report.size()); - if(ret == -1) { - ///TODO: This seems like a hacky solution - // Try again before failing - ret = ::write(_fd, report.data(), report.size()); - if(ret == -1) - throw std::system_error(errno, std::system_category(), - "_sendReport write failed"); - } - - return ret; -} - -int RawDevice::_readReport(std::vector &report, - std::size_t maxDataLength) -{ - return _readReport(report, maxDataLength, _io_timeout); -} - -int RawDevice::_readReport(std::vector &report, - std::size_t maxDataLength, std::chrono::milliseconds timeout) -{ - std::lock_guard lock(_dev_io); - int ret; - report.resize(maxDataLength); - - timeval timeout_tv{}; - timeout_tv.tv_sec = duration_cast(_io_timeout) - .count(); - timeout_tv.tv_usec = duration_cast( - _io_timeout).count() % - duration_cast(seconds(1)).count(); - - auto timeout_ms = duration_cast(timeout).count(); - - fd_set fds; - do { - FD_ZERO(&fds); - FD_SET(_fd, &fds); - FD_SET(_pipe[0], &fds); - - ret = select(std::max(_fd, _pipe[0]) + 1, - &fds, nullptr, nullptr, - (timeout_ms > 0 ? nullptr : &timeout_tv)); - } while(ret == -1 && errno == EINTR); - - if(ret == -1) + if(write(_fd, report.data(), report.size()) == -1) throw std::system_error(errno, std::system_category(), - "_readReport select failed"); - - if(FD_ISSET(_fd, &fds)) { - ret = read(_fd, report.data(), report.size()); - if(ret == -1) - throw std::system_error(errno, std::system_category(), - "_readReport read failed"); - report.resize(ret); - } - - if(FD_ISSET(_pipe[0], &fds)) { - char c; - ret = read(_pipe[0], &c, sizeof(char)); - if(ret == -1) - throw std::system_error(errno, std::system_category(), - "_readReport read pipe failed"); - } - - if(0 == ret) - throw backend::TimeoutError(); - - if(logid::global_loglevel <= LogLevel::RAWREPORT) { - printf("[RAWREPORT] %s IN: ", _path.c_str()); - for(auto &i : report) - printf("%02x ", i); - printf("\n"); - } - - return ret; + "sendReport write failed"); } -void RawDevice::interruptRead(bool wait_for_halt) +RawDevice::EvHandlerId RawDevice::addEventHandler(RawEventHandler handler) { - char c = 0; - if(-1 == write(_pipe[1], &c, sizeof(char))) - throw std::system_error(errno, std::system_category(), - "interruptRead write pipe failed"); - - // Ensure I/O has halted - if(wait_for_halt) - std::lock_guard lock(_dev_io); + std::unique_lock lock(_event_handler_lock); + _event_handlers.emplace_front(std::move(handler)); + return _event_handlers.cbegin(); } -void RawDevice::listen() +void RawDevice::removeEventHandler(RawDevice::EvHandlerId id) { - std::lock_guard lock(_listening); - _continue_listen = true; - _listen_condition.notify_all(); - while(_continue_listen) { - while(!_io_queue.empty()) { - auto task = _io_queue.front(); - (*task)(); - _io_queue.pop(); + std::unique_lock lock(_event_handler_lock); + _event_handlers.erase(id); +} + +void RawDevice::_readReports() +{ + uint8_t buf[max_data_length]; + ssize_t len; + + while(-1 != (len = ::read(_fd, buf, max_data_length))) { + assert(len <= max_data_length); + std::vector report(buf, buf + len); + + if(logid::global_loglevel <= LogLevel::RAWREPORT) { + printf("[RAWREPORT] %s IN: ", _path.c_str()); + for(auto &i : report) + printf("%02x ", i); + printf("\n"); } - std::vector report; - _readReport(report, MAX_DATA_LENGTH); - this->_handleEvent(report); + _handleEvent(report); } - - // Listener is stopped, handle I/O queue - while(!_io_queue.empty()) { - auto task = _io_queue.front(); - (*task)(); - _io_queue.pop(); - } - - _continue_listen = false; } -void RawDevice::listenAsync() -{ - std::mutex listen_check; - std::unique_lock check_lock(listen_check); - thread::spawn({[this]() { listen(); }}); - - // Block until RawDevice is listening - _listen_condition.wait(check_lock, [this](){ - return (bool)_continue_listen; - }); -} - -void RawDevice::stopListener() -{ - _continue_listen = false; - interruptRead(); -} - -void RawDevice::addEventHandler(const std::string& nickname, - const std::shared_ptr& handler) -{ - std::unique_lock lock(_event_handler_lock); - assert(_event_handlers.find(nickname) == _event_handlers.end()); - assert(handler); - _event_handlers.emplace(nickname, handler); -} - -void RawDevice::removeEventHandler(const std::string &nickname) -{ - std::unique_lock lock(_event_handler_lock); - _event_handlers.erase(nickname); -} - -const std::map>& -RawDevice::eventHandlers() -{ - std::unique_lock lock(_event_handler_lock); - return _event_handlers; -} - -void RawDevice::_handleEvent(std::vector &report) +void RawDevice::_handleEvent(const std::vector& report) { std::unique_lock lock(_event_handler_lock); 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; + if(handler.condition(report)) + handler.callback(report); } diff --git a/src/logid/backend/raw/RawDevice.h b/src/logid/backend/raw/RawDevice.h index 0a41caa..4bd68f6 100644 --- a/src/logid/backend/raw/RawDevice.h +++ b/src/logid/backend/raw/RawDevice.h @@ -26,78 +26,63 @@ #include #include #include +#include #include "defs.h" #include "../../util/mutex_queue.h" namespace logid::backend::raw { + class DeviceMonitor; + class IOMonitor; + class RawDevice { public: - static bool supportedReport(uint8_t id, uint8_t length); + static constexpr int max_data_length = 32; + typedef std::list::const_iterator EvHandlerId; - explicit RawDevice(std::string path, - double io_timeout); - ~RawDevice(); - std::string hidrawPath() const; + struct dev_info { + int16_t vid; + int16_t pid; + }; - std::string name() const; - uint16_t vendorId() const; - uint16_t productId() const; + RawDevice(std::string path, + std::shared_ptr monitor); + ~RawDevice() noexcept; + + [[nodiscard]] const std::string& rawPath() const; + + [[nodiscard]] const std::string& name() const; + [[nodiscard]] int16_t vendorId() const; + [[nodiscard]] int16_t productId() const; static std::vector getReportDescriptor(std::string path); static std::vector getReportDescriptor(int fd); - std::vector reportDescriptor() const; + [[nodiscard]] const std::vector& reportDescriptor() const; - std::vector sendReport(const std::vector& report); - void sendReportNoResponse(const std::vector& report); - void interruptRead(bool wait_for_halt=true); + void sendReport(const std::vector& report); - void listen(); - void listenAsync(); - void stopListener(); - bool isListening(); - - void addEventHandler(const std::string& nickname, - const std::shared_ptr& handler); - void removeEventHandler(const std::string& nickname); - const std::map>& - eventHandlers(); + EvHandlerId addEventHandler(RawEventHandler handler); + void removeEventHandler(EvHandlerId id); private: - std::mutex _dev_io, _listening; - std::string _path; - int _fd; - int _pipe[2]; - uint16_t _vid; - uint16_t _pid; - std::string _name; - std::vector _rdesc; + void _readReports(); - std::atomic _continue_listen; - std::atomic _continue_respond; - std::condition_variable _listen_condition; + std::atomic_bool _valid; - const std::chrono::milliseconds _io_timeout; + const std::string _path; + const int _fd; + const dev_info _devinfo; + const std::string _name; + const std::vector _rdesc; - std::map> - _event_handlers; + std::shared_ptr _io_monitor; + + std::list _event_handlers; std::mutex _event_handler_lock; - void _handleEvent(std::vector& report); - - /* These will only be used internally and processed with a queue */ - int _sendReport(const std::vector& report); - int _readReport(std::vector& report, std::size_t maxDataLength); - int _readReport(std::vector& report, std::size_t maxDataLength, - std::chrono::milliseconds timeout); - - std::vector _respondToReport(const std::vector& - request); - - mutex_queue()>>> - _io_queue; + void _handleEvent(const std::vector& report); }; } -#endif //LOGID_BACKEND_RAWDEVICE_H \ No newline at end of file +#endif //LOGID_BACKEND_RAWDEVICE_H diff --git a/src/logid/backend/raw/defs.h b/src/logid/backend/raw/defs.h index 388d9c6..53f9848 100644 --- a/src/logid/backend/raw/defs.h +++ b/src/logid/backend/raw/defs.h @@ -29,8 +29,14 @@ namespace raw { struct RawEventHandler { - std::function& )> condition; - std::function& )> callback; + std::function&)> condition; + std::function&)> callback; + + RawEventHandler(std::function&)> cond, + std::function&)> call) : + condition (std::move(cond)), callback (std::move(call)) + { + } }; }}} diff --git a/src/logid/logid.cpp b/src/logid/logid.cpp index 223546f..4b2b4d1 100644 --- a/src/logid/logid.cpp +++ b/src/logid/logid.cpp @@ -57,7 +57,7 @@ void logid::reload() { log_printf(INFO, "Reloading logid..."); finder_reloading.lock(); - finder->stop(); + finder->_stop(); Configuration* old_config = global_config; global_config = new Configuration(config_file.c_str()); delete(old_config); @@ -157,19 +157,6 @@ int main(int argc, char** argv) std::shared_ptr config; std::shared_ptr virtual_input; - auto server = ipcgull::make_server("pizza.pixl.LogiOps", - "/pizza/pixl/LogiOps", - ipcgull::IPCGULL_USER); - - std::thread( [server]() { - try { - server->start(); - } catch(ipcgull::connection_failed& e) { - logPrintf(ERROR, "Lost IPC connection, terminating."); - std::terminate(); - } - } ).detach(); - // Read config try { config = std::make_shared(options.config_file); @@ -178,6 +165,10 @@ int main(int argc, char** argv) config = std::make_shared(); } + auto server = ipcgull::make_server("pizza.pixl.LogiOps", + "/pizza/pixl/LogiOps", + ipcgull::IPCGULL_USER); + //Create a virtual input device try { virtual_input = std::make_unique(LOGID_VIRTUAL_INPUT_NAME); @@ -186,12 +177,16 @@ int main(int argc, char** argv) return EXIT_FAILURE; } - // Scan devices, create listeners, handlers, etc. + // Device manager runs on its own I/O thread asynchronously auto device_manager = DeviceManager::make(config, virtual_input, server); - device_manager->run(); + device_manager->enumerate(); - server->stop_sync(); + try { + server->start(); + } catch(ipcgull::connection_failed& e) { + logPrintf(ERROR, "Lost IPC connection, terminating."); + } return EXIT_SUCCESS; }