diff --git a/src/logid/CMakeLists.txt b/src/logid/CMakeLists.txt index a1904c3..4032e9f 100644 --- a/src/logid/CMakeLists.txt +++ b/src/logid/CMakeLists.txt @@ -11,12 +11,16 @@ find_package(PkgConfig REQUIRED) add_executable(logid logid.cpp util/log.cpp + InputDevice.cpp DeviceManager.cpp Device.cpp Receiver.cpp Configuration.cpp features/DPI.cpp features/SmartShift.cpp + features/RemapButton.cpp + actions/Action.cpp + actions/KeypressAction.cpp backend/Error.cpp backend/raw/DeviceMonitor.cpp backend/raw/RawDevice.cpp diff --git a/src/logid/Configuration.cpp b/src/logid/Configuration.cpp index 9846951..7a16266 100644 --- a/src/logid/Configuration.cpp +++ b/src/logid/Configuration.cpp @@ -86,7 +86,7 @@ Configuration::DeviceNotFound::DeviceNotFound(std::string name) : { } -const char * Configuration::DeviceNotFound::what() +const char * Configuration::DeviceNotFound::what() const noexcept { return _name.c_str(); } diff --git a/src/logid/Configuration.h b/src/logid/Configuration.h index b9d5200..2d2166a 100644 --- a/src/logid/Configuration.h +++ b/src/logid/Configuration.h @@ -37,7 +37,7 @@ namespace logid { public: explicit DeviceNotFound(std::string name); - virtual const char* what(); + const char* what() const noexcept override; private: std::string _name; }; diff --git a/src/logid/Device.cpp b/src/logid/Device.cpp index 9e9212c..7aa3e11 100644 --- a/src/logid/Device.cpp +++ b/src/logid/Device.cpp @@ -20,6 +20,7 @@ #include "features/DPI.h" #include "Device.h" #include "features/SmartShift.h" +#include "features/RemapButton.h" using namespace logid; using namespace logid::backend; @@ -46,9 +47,14 @@ void Device::_init() _addFeature("dpi"); _addFeature("smartshift"); + _addFeature("remapbutton"); - for(auto& feature: _features) + for(auto& feature: _features) { feature.second->configure(); + feature.second->listen(); + } + + _hidpp20.listen(); } std::string Device::name() @@ -69,6 +75,9 @@ void Device::sleep() void Device::wakeup() { logPrintf(INFO, "%s:%d woke up.", _path.c_str(), _index); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + for(auto& feature: _features) + feature.second->configure(); } DeviceConfig& Device::config() diff --git a/src/logid/EvdevDevice.cpp b/src/logid/InputDevice.cpp similarity index 54% rename from src/logid/EvdevDevice.cpp rename to src/logid/InputDevice.cpp index 05eb3dc..972d127 100644 --- a/src/logid/EvdevDevice.cpp +++ b/src/logid/InputDevice.cpp @@ -16,19 +16,35 @@ * */ +#include +#include + +#include "InputDevice.h" + +extern "C" +{ #include #include -#include - -#include "EvdevDevice.h" +}; using namespace logid; -EvdevDevice::EvdevDevice(const char* name) +InputDevice::InvalidEventCode::InvalidEventCode(std::string name) +{ + _what = "Invalid event code " + name; +} + +const char* InputDevice::InvalidEventCode::what() const noexcept +{ + return _what.c_str(); +} + +InputDevice::InputDevice(const char* name) { device = libevdev_new(); libevdev_set_name(device, name); + ///TODO: Is it really a good idea to enable all events? libevdev_enable_event_type(device, EV_KEY); for(int i = 0; i < KEY_CNT; i++) libevdev_enable_event_code(device, EV_KEY, i, nullptr); @@ -36,26 +52,56 @@ EvdevDevice::EvdevDevice(const char* name) for(int i = 0; i < REL_CNT; i++) libevdev_enable_event_code(device, EV_REL, i, nullptr); - int err = libevdev_uinput_create_from_device(device, LIBEVDEV_UINPUT_OPEN_MANAGED, &ui_device); + int err = libevdev_uinput_create_from_device(device, + LIBEVDEV_UINPUT_OPEN_MANAGED, &ui_device); if(err != 0) throw std::system_error(-err, std::generic_category()); } -void EvdevDevice::moveAxis(unsigned int axis, int movement) -{ - libevdev_uinput_write_event(ui_device, EV_REL, axis, movement); - libevdev_uinput_write_event(ui_device, EV_SYN, SYN_REPORT, 0); -} - -void EvdevDevice::sendEvent(unsigned int type, unsigned int code, int value) -{ - libevdev_uinput_write_event(ui_device, type, code, value); - libevdev_uinput_write_event(ui_device, EV_SYN, SYN_REPORT, 0); -} - -EvdevDevice::~EvdevDevice() +InputDevice::~InputDevice() { libevdev_uinput_destroy(ui_device); libevdev_free(device); } + +void InputDevice::moveAxis(uint axis, int movement) +{ + _sendEvent(EV_REL, axis, movement); +} + +void InputDevice::pressKey(uint code) +{ + _sendEvent(EV_KEY, code, 1); +} + +void InputDevice::releaseKey(uint code) +{ + _sendEvent(EV_KEY, code, 0); +} + +uint InputDevice::toKeyCode(std::string name) +{ + return _toEventCode(EV_KEY, std::move(name)); +} + +uint InputDevice::toAxisCode(std::string name) +{ + return _toEventCode(EV_KEY, std::move(name)); +} + +uint InputDevice::_toEventCode(uint type, const std::string& name) +{ + int code = libevdev_event_code_from_name(type, name.c_str()); + + if(code == -1) + throw InvalidEventCode(name); + + return code; +} + +void InputDevice::_sendEvent(uint type, uint code, int value) +{ + libevdev_uinput_write_event(ui_device, type, code, value); + libevdev_uinput_write_event(ui_device, EV_SYN, SYN_REPORT, 0); +} \ No newline at end of file diff --git a/src/logid/InputDevice.h b/src/logid/InputDevice.h new file mode 100644 index 0000000..0072271 --- /dev/null +++ b/src/logid/InputDevice.h @@ -0,0 +1,66 @@ +/* + * Copyright 2019-2020 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_INPUTDEVICE_H +#define LOGID_INPUTDEVICE_H + +#include + +extern "C" +{ +#include +#include +}; + +namespace logid +{ + typedef uint keycode; + + class InputDevice + { + public: + class InvalidEventCode : public std::exception + { + public: + explicit InvalidEventCode(std::string name); + const char* what() const noexcept override; + private: + std::string _what; + }; + explicit InputDevice(const char *name); + ~InputDevice(); + + void moveAxis(uint axis, int movement); + void pressKey(uint code); + void releaseKey(uint code); + + static uint toKeyCode(std::string name); + static uint toAxisCode(std::string name); + private: + void _sendEvent(uint type, uint code, int value); + + static uint _toEventCode(uint type, const std::string& name); + + libevdev* device; + libevdev_uinput* ui_device; + }; + + extern std::unique_ptr virtual_input; +} + +#endif //LOGID_INPUTDEVICE_H \ No newline at end of file diff --git a/src/logid/actions/Action.cpp b/src/logid/actions/Action.cpp new file mode 100644 index 0000000..44e9dd1 --- /dev/null +++ b/src/logid/actions/Action.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2019-2020 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 "Action.h" +#include "../util/log.h" +#include "KeypressAction.h" + +using namespace logid; +using namespace logid::actions; + +std::shared_ptr Action::makeAction(Device *device, libconfig::Setting + &setting) +{ + if(!setting.isGroup()) { + logPrintf(WARN, "Line %d: Action is not a group, ignoring.", + setting.getSourceLine()); + throw InvalidAction(); + } + + try { + auto& action_type = setting.lookup("type"); + + if(action_type.getType() != libconfig::Setting::TypeString) { + logPrintf(WARN, "Line %d: Action type must be a string", + action_type.getSourceLine()); + throw InvalidAction(); + } + + std::string type = action_type; + std::transform(type.begin(), type.end(), type.begin(), ::tolower); + + if(type == "keypress") + return std::make_shared(device, setting); + else + throw InvalidAction(type); + + } catch(libconfig::SettingNotFoundException& e) { + logPrintf(WARN, "Line %d: Action type is missing, ignoring.", + setting.getSourceLine()); + throw InvalidAction(); + } +} \ No newline at end of file diff --git a/src/logid/actions/Action.h b/src/logid/actions/Action.h new file mode 100644 index 0000000..6bdb87c --- /dev/null +++ b/src/logid/actions/Action.h @@ -0,0 +1,82 @@ +/* + * Copyright 2019-2020 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_ACTION_H +#define LOGID_ACTION_H + +#include +#include +#include + +namespace logid { + class Device; +namespace actions { + class InvalidAction : public std::exception + { + public: + InvalidAction() + { + } + explicit InvalidAction(std::string& action) : _action (action) + { + } + const char* what() const noexcept override + { + return _action.c_str(); + } + private: + std::string _action; + }; + + class Action + { + public: + static std::shared_ptr makeAction(Device* device, + libconfig::Setting& setting); + + virtual void press() = 0; + virtual void release() = 0; + virtual void move(int16_t x, int16_t y) + { + } + + virtual bool pressed() + { + return _pressed; + } + + virtual uint8_t reprogFlags() const = 0; + + class Config + { + public: + explicit Config(Device* device) : _device (device) + { + } + protected: + Device* _device; + }; + protected: + explicit Action(Device* device) : _device (device), _pressed (false) + { + } + Device* _device; + std::atomic _pressed; + }; +}} + +#endif //LOGID_ACTION_H diff --git a/src/logid/actions/KeypressAction.cpp b/src/logid/actions/KeypressAction.cpp new file mode 100644 index 0000000..f5a3540 --- /dev/null +++ b/src/logid/actions/KeypressAction.cpp @@ -0,0 +1,87 @@ +/* + * Copyright 2019-2020 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 "KeypressAction.h" +#include "../util/log.h" +#include "../InputDevice.h" +#include "../backend/hidpp20/features/ReprogControls.h" + +using namespace logid::actions; +using namespace logid::backend; + +KeypressAction::KeypressAction(Device *device, libconfig::Setting& config) : + Action(device), _config (device, config) +{ +} + +void KeypressAction::press() +{ + for(auto& key : _config.keys()) + virtual_input->pressKey(key); +} + +void KeypressAction::release() +{ + for(auto& key : _config.keys()) + virtual_input->releaseKey(key); +} + +uint8_t KeypressAction::reprogFlags() const +{ + return hidpp20::ReprogControls::TemporaryDiverted; +} + +KeypressAction::Config::Config(Device* device, libconfig::Setting& config) : + Action::Config(device) +{ + if(!config.isGroup()) { + logPrintf(WARN, "Line %d: action must be an object, skipping.", + config.getSourceLine()); + return; + } + + try { + auto &keys = config.lookup("keys"); + if(keys.isArray() || keys.isList()) { + int key_count = keys.getLength(); + for(int i = 0; i < key_count; i++) { + auto& key = keys[i]; + if(key.isNumber()) { + _keys.push_back(key); + } else if(key.getType() == libconfig::Setting::TypeString) { + try { + _keys.push_back(virtual_input->toKeyCode(key)); + } catch(InputDevice::InvalidEventCode& e) { + logPrintf(WARN, "Line %d: Invalid keycode %s, skipping." + , key.getSourceLine(), key.c_str()); + } + } else { + logPrintf(WARN, "Line %d: keycode must be string or int", + key.getSourceLine(), key.c_str()); + } + } + } + } catch (libconfig::SettingNotFoundException& e) { + logPrintf(WARN, "Line %d: keys is a required field, skipping.", + config.getSourceLine()); + } +} + +std::vector& KeypressAction::Config::keys() +{ + return _keys; +} \ No newline at end of file diff --git a/src/logid/EvdevDevice.h b/src/logid/actions/KeypressAction.h similarity index 50% rename from src/logid/EvdevDevice.h rename to src/logid/actions/KeypressAction.h index a37584c..4385faf 100644 --- a/src/logid/EvdevDevice.h +++ b/src/logid/actions/KeypressAction.h @@ -15,31 +15,36 @@ * along with this program. If not, see . * */ +#ifndef LOGID_ACTION_KEYPRESS_H +#define LOGID_ACTION_KEYPRESS_H -#ifndef LOGID_EVDEVDEVICE_H -#define LOGID_EVDEVDEVICE_H +#include +#include +#include "Action.h" -#include -#include - -namespace logid -{ - class EvdevDevice +namespace logid { +namespace actions { + class KeypressAction : public Action { public: - EvdevDevice(const char *name); + KeypressAction(Device* dev, libconfig::Setting& config); - ~EvdevDevice(); + virtual void press(); + virtual void release(); - void moveAxis(unsigned int axis, int movement); + virtual uint8_t reprogFlags() const; - void sendEvent(unsigned int type, unsigned int code, int value); - - libevdev *device; - libevdev_uinput *ui_device; + class Config : public Action::Config + { + public: + explicit Config(Device* device, libconfig::Setting& root); + std::vector& keys(); + protected: + std::vector _keys; + }; + protected: + Config _config; }; +}} - extern EvdevDevice* global_evdev; -} - -#endif //LOGID_EVDEVDEVICE_H \ No newline at end of file +#endif //LOGID_ACTION_KEYPRESS_H diff --git a/src/logid/backend/hidpp/Device.cpp b/src/logid/backend/hidpp/Device.cpp index d6d5a64..ae3f061 100644 --- a/src/logid/backend/hidpp/Device.cpp +++ b/src/logid/backend/hidpp/Device.cpp @@ -92,6 +92,7 @@ std::tuple Device::version() const void Device::_init() { + _listening = false; _supported_reports = getSupportedReports(_raw_device->reportDescriptor()); if(!_supported_reports) throw InvalidDevice(InvalidDevice::NoHIDPPReport); @@ -127,7 +128,8 @@ void Device::_init() Device::~Device() { - _raw_device->removeEventHandler("DEV_" + std::to_string(_index)); + if(_listening) + _raw_device->removeEventHandler("DEV_" + std::to_string(_index)); } void Device::addEventHandler(const std::string& nickname, @@ -144,6 +146,12 @@ void Device::removeEventHandler(const std::string& nickname) _event_handlers.erase(nickname); } +const std::map>& + Device::eventHandlers() +{ + return _event_handlers; +} + void Device::handleEvent(Report& report) { for(auto& handler : _event_handlers) @@ -192,31 +200,33 @@ uint16_t Device::pid() const void Device::listen() { if(!_raw_device->isListening()) - ///TODO: Kill RawDevice? thread::spawn({[raw=this->_raw_device]() { raw->listen(); }}); // Pass all HID++ events with device index to this device. - std::shared_ptr handler; - handler->condition = [this](std::vector& report)->bool - { + 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] == this->_index); + (report[Offset::DeviceIndex] == index); }; - handler->callback = [this](std::vector& report)->void - { + 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() { - _raw_device->removeEventHandler("DEV_" + std::to_string(_index)); + if(_listening) + _raw_device->removeEventHandler("DEV_" + std::to_string(_index)); + + _listening = false; if(!_raw_device->eventHandlers().empty()) _raw_device->stopListener(); diff --git a/src/logid/backend/hidpp/Device.h b/src/logid/backend/hidpp/Device.h index cc6cade..0ea2062 100644 --- a/src/logid/backend/hidpp/Device.h +++ b/src/logid/backend/hidpp/Device.h @@ -55,7 +55,7 @@ namespace hidpp Asleep }; InvalidDevice(Reason reason) : _reason (reason) {} - virtual const char *what() const noexcept; + virtual const char* what() const noexcept; virtual Reason code() const noexcept; private: Reason _reason; @@ -81,6 +81,8 @@ namespace hidpp 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); @@ -98,6 +100,8 @@ namespace hidpp uint16_t _pid; std::string _name; + std::atomic _listening; + std::map> _event_handlers; }; } } } diff --git a/src/logid/backend/hidpp/defs.h b/src/logid/backend/hidpp/defs.h index a884e65..1590e06 100644 --- a/src/logid/backend/hidpp/defs.h +++ b/src/logid/backend/hidpp/defs.h @@ -21,6 +21,8 @@ #define LOGID_HIDPP_SOFTWARE_ID 0 +#include + namespace logid { namespace backend { namespace hidpp diff --git a/src/logid/backend/hidpp20/Feature.cpp b/src/logid/backend/hidpp20/Feature.cpp index 6f64db5..18c30b1 100644 --- a/src/logid/backend/hidpp20/Feature.cpp +++ b/src/logid/backend/hidpp20/Feature.cpp @@ -63,3 +63,8 @@ Feature::Feature(Device* dev, uint16_t _id) : _device (dev) throw UnsupportedFeature(_id); } } + +uint8_t Feature::featureIndex() +{ + return _index; +} \ No newline at end of file diff --git a/src/logid/backend/hidpp20/Feature.h b/src/logid/backend/hidpp20/Feature.h index f68cfa0..cc4732b 100644 --- a/src/logid/backend/hidpp20/Feature.h +++ b/src/logid/backend/hidpp20/Feature.h @@ -40,7 +40,7 @@ namespace hidpp20 { public: static const uint16_t ID; virtual uint16_t getID() = 0; - + uint8_t featureIndex(); protected: explicit Feature(Device* dev, uint16_t _id); std::vector callFunction(uint8_t function_id, diff --git a/src/logid/backend/hidpp20/features/ReprogControls.cpp b/src/logid/backend/hidpp20/features/ReprogControls.cpp index d9835bd..4200385 100644 --- a/src/logid/backend/hidpp20/features/ReprogControls.cpp +++ b/src/logid/backend/hidpp20/features/ReprogControls.cpp @@ -16,6 +16,7 @@ * */ #include +#include "../Error.h" #include "ReprogControls.h" using namespace logid::backend::hidpp20; @@ -30,7 +31,7 @@ x::x(Device* dev, uint16_t _id) : base(dev, _id) \ #define MAKE_REPROG(x, dev) \ try { \ - return x(dev); \ + return std::make_shared(dev); \ } catch(UnsupportedFeature &e) {\ } @@ -41,7 +42,7 @@ DEFINE_REPROG(ReprogControlsV2_2, ReprogControlsV2); DEFINE_REPROG(ReprogControlsV3, ReprogControlsV2_2); DEFINE_REPROG(ReprogControlsV4, ReprogControlsV3); -ReprogControls ReprogControls::autoVersion(Device *dev) +std::shared_ptr ReprogControls::autoVersion(Device *dev) { MAKE_REPROG(ReprogControlsV4, dev); MAKE_REPROG(ReprogControlsV3, dev); @@ -49,7 +50,7 @@ ReprogControls ReprogControls::autoVersion(Device *dev) MAKE_REPROG(ReprogControlsV2, dev); // If base version cannot be made, throw error - return ReprogControls(dev); + return std::make_shared(dev); } uint8_t ReprogControls::getControlCount() @@ -78,29 +79,43 @@ ReprogControls::ControlInfo ReprogControls::getControlInfo(uint8_t index) return info; } +ReprogControls::ControlInfo ReprogControls::getControlIdInfo(uint16_t cid) +{ + if(_cids.empty()) { + for(uint8_t i = 0; i < getControlCount(); i++) { + auto info = getControlInfo(i); + _cids.emplace(info.controlID, info); + } + } + + auto it = _cids.find(cid); + if(it == _cids.end()) + throw Error(Error::InvalidArgument); + else + return it->second; +} + ReprogControls::ControlInfo ReprogControls::getControlReporting(uint16_t cid) { - std::vector params(2); - ControlInfo info{}; - params[0] = (cid >> 8) & 0xff; - params[1] = cid & 0xff; - auto response = callFunction(GetControlReporting, params); + // Emulate this function, only Reprog controls v4 supports this + auto info = getControlIdInfo(cid); - info.controlID = response[1]; - info.controlID |= response[0] << 8; - info.flags = response[2]; - return info; + ControlInfo report{}; + report.controlID = cid; + report.flags = 0; + if(info.flags & TemporaryDivertable) + report.flags |= TemporaryDiverted; + if(info.flags & PersisentlyDivertable) + report.flags |= PersistentlyDiverted; + if(info.additionalFlags & RawXY) + report.flags |= RawXYDiverted; + + return report; } void ReprogControls::setControlReporting(uint8_t cid, ControlInfo info) { - std::vector params(5); - params[0] = (cid >> 8) & 0xff; - params[1] = cid & 0xff; - params[2] = info.flags; - params[3] = (info.controlID >> 8) & 0xff; - params[4] = info.controlID & 0xff; - callFunction(SetControlReporting, params); + // This function does not exist pre-v4 and cannot be emulated, ignore. } std::set ReprogControls::divertedButtonEvent( @@ -131,3 +146,28 @@ ReprogControls::Move ReprogControls::divertedRawXYEvent(const hidpp::Report move.y |= report.paramBegin()[2] << 8; return move; } + +ReprogControls::ControlInfo ReprogControlsV4::getControlReporting(uint16_t cid) +{ + std::vector params(2); + ControlInfo info{}; + params[0] = (cid >> 8) & 0xff; + params[1] = cid & 0xff; + auto response = callFunction(GetControlReporting, params); + + info.controlID = response[1]; + info.controlID |= response[0] << 8; + info.flags = response[2]; + return info; +} + +void ReprogControlsV4::setControlReporting(uint8_t cid, ControlInfo info) +{ + std::vector params(5); + params[0] = (cid >> 8) & 0xff; + params[1] = cid & 0xff; + params[2] = info.flags; + params[3] = (info.controlID >> 8) & 0xff; + params[4] = info.controlID & 0xff; + callFunction(SetControlReporting, params); +} \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/ReprogControls.h b/src/logid/backend/hidpp20/features/ReprogControls.h index 3197040..143dafd 100644 --- a/src/logid/backend/hidpp20/features/ReprogControls.h +++ b/src/logid/backend/hidpp20/features/ReprogControls.h @@ -18,6 +18,8 @@ #ifndef LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H #define LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H +#include + #include "../feature_defs.h" #include "../Feature.h" @@ -28,27 +30,17 @@ namespace hidpp20 class ReprogControls : public Feature { public: - static const uint16_t ID = FeatureID::REPROG_CONTROLS; - virtual uint16_t getID() { return ID; } - - virtual bool supportsRawXY() { return false; } - enum Function { GetControlCount = 0, GetControlInfo = 1, GetControlReporting = 2, SetControlReporting = 3 }; - enum Event { DivertedButtonEvent = 0, DivertedRawXYEvent = 1 }; - explicit ReprogControls(Device* dev); - - virtual uint8_t getControlCount(); - struct ControlInfo { uint16_t controlID; @@ -68,24 +60,41 @@ namespace hidpp20 FnToggle = 1<<3, ReprogHint = 1<<4, TemporaryDivertable = 1<<5, - PerisentlyDiverable = 1<<6, + PersisentlyDivertable = 1<<6, Virtual = 1<<7 }; enum ControlInfoAdditionalFlags: uint8_t { RawXY = 1<<0 }; - virtual ControlInfo getControlInfo(uint8_t index); - enum ControlReportingFlags: uint8_t { TemporaryDiverted = 1<<0, ChangeTemporaryDivert = 1<<1, - PersistentDiverted = 1<<2, + PersistentlyDiverted = 1<<2, ChangePersistentDivert = 1<<3, RawXYDiverted = 1<<4, ChangeRawXYDivert = 1<<5 }; + struct Move + { + int16_t x; + int16_t y; + }; + + static const uint16_t ID = FeatureID::REPROG_CONTROLS; + virtual uint16_t getID() { return ID; } + + virtual bool supportsRawXY() { return false; } + + explicit ReprogControls(Device* dev); + + virtual uint8_t getControlCount(); + + virtual ControlInfo getControlInfo(uint8_t cid); + + virtual ControlInfo getControlIdInfo(uint16_t cid); + // Onlu controlId and flags will be set virtual ControlInfo getControlReporting(uint16_t cid); @@ -95,24 +104,19 @@ namespace hidpp20 static std::set divertedButtonEvent(const hidpp::Report& report); - struct Move - { - int16_t x; - int16_t y; - }; - static Move divertedRawXYEvent(const hidpp::Report& report); - static ReprogControls autoVersion(Device *dev); + static std::shared_ptr autoVersion(Device *dev); protected: ReprogControls(Device* dev, uint16_t _id); + std::map _cids; }; class ReprogControlsV2 : public ReprogControls { public: static const uint16_t ID = FeatureID::REPROG_CONTROLS_V2; - uint16_t getID() override { return ID; } + virtual uint16_t getID() { return ID; } explicit ReprogControlsV2(Device* dev); protected: @@ -123,7 +127,7 @@ namespace hidpp20 { public: static const uint16_t ID = FeatureID::REPROG_CONTROLS_V2_2; - uint16_t getID() override { return ID; } + virtual uint16_t getID() { return ID; } explicit ReprogControlsV2_2(Device* dev); protected: @@ -134,7 +138,7 @@ namespace hidpp20 { public: static const uint16_t ID = FeatureID::REPROG_CONTROLS_V3; - uint16_t getID() override { return ID; } + virtual uint16_t getID() { return ID; } explicit ReprogControlsV3(Device* dev); protected: @@ -145,10 +149,14 @@ namespace hidpp20 { public: static const uint16_t ID = FeatureID::REPROG_CONTROLS_V4; - uint16_t getID() override { return ID; } + virtual uint16_t getID() { return ID; } bool supportsRawXY() override { return true; } + ControlInfo getControlReporting(uint16_t cid) override; + + void setControlReporting(uint8_t cid, ControlInfo info) override; + explicit ReprogControlsV4(Device* dev); protected: ReprogControlsV4(Device* dev, uint16_t _id); diff --git a/src/logid/backend/raw/RawDevice.cpp b/src/logid/backend/raw/RawDevice.cpp index 9719f49..680bdd2 100644 --- a/src/logid/backend/raw/RawDevice.cpp +++ b/src/logid/backend/raw/RawDevice.cpp @@ -354,6 +354,7 @@ void RawDevice::addEventHandler(const std::string& nickname, { auto it = _event_handlers.find(nickname); assert(it == _event_handlers.end()); + assert(handler); _event_handlers.emplace(nickname, handler); } diff --git a/src/logid/features/RemapButton.cpp b/src/logid/features/RemapButton.cpp new file mode 100644 index 0000000..58aa786 --- /dev/null +++ b/src/logid/features/RemapButton.cpp @@ -0,0 +1,180 @@ +/* + * Copyright 2019-2020 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 "../Device.h" +#include "RemapButton.h" +#include "../backend/hidpp20/Error.h" + +using namespace logid::features; +using namespace logid::backend; +using namespace logid::actions; + +#define HIDPP20_REPROG_REBIND (hidpp20::ReprogControls::ChangeTemporaryDivert \ +| hidpp20::ReprogControls::ChangeRawXYDivert) + +#define EVENTHANDLER_NAME "REMAP_BUTTON" + +RemapButton::RemapButton(Device *dev): DeviceFeature(dev), _config (dev), + _reprog_controls (hidpp20::ReprogControls::autoVersion(&dev->hidpp20())) +{ +} + +RemapButton::~RemapButton() +{ + _device->hidpp20().removeEventHandler(EVENTHANDLER_NAME); +} + +void RemapButton::configure() +{ + ///TODO: DJ reporting trickery if cannot be remapped + for(auto& i : _config.buttons()) { + hidpp20::ReprogControls::ControlInfo info{}; + try { + info = _reprog_controls->getControlIdInfo(i.first); + } catch(hidpp20::Error& e) { + if(e.code() == hidpp20::Error::InvalidArgument) { + logPrintf(WARN, "%s: CID 0x%02x does not exist.", + _device->name().c_str(), i.first); + continue; + } + throw e; + } + + if((i.second->reprogFlags() & hidpp20::ReprogControls::RawXYDiverted) && + (!_reprog_controls->supportsRawXY() || (info.additionalFlags & + hidpp20::ReprogControls::RawXY))) + logPrintf(WARN, "%s: Cannot divert raw XY movements for CID " + "0x%02x", _device->name().c_str(), i.first); + + hidpp20::ReprogControls::ControlInfo report{}; + report.controlID = i.first; + report.flags = HIDPP20_REPROG_REBIND; + report.flags |= i.second->reprogFlags(); + _reprog_controls->setControlReporting(i.first, report); + } +} + +void RemapButton::listen() +{ + if(_device->hidpp20().eventHandlers().find(EVENTHANDLER_NAME) == + _device->hidpp20().eventHandlers().end()) { + auto handler = std::make_shared(); + handler->condition = [index=_reprog_controls->featureIndex()] + (hidpp::Report& report)->bool { + return (report.feature() == index) && ((report.function() == + hidpp20::ReprogControls::DivertedButtonEvent) || (report + .function() == hidpp20::ReprogControls::DivertedRawXYEvent)); + }; + + handler->callback = [this](hidpp::Report& report)->void { + if(report.function() == + hidpp20::ReprogControls::DivertedButtonEvent) + this->_buttonEvent(_reprog_controls->divertedButtonEvent( + report)); + else { // RawXY + auto divertedXY = _reprog_controls->divertedRawXYEvent(report); + for(auto& button : this->_config.buttons()) + if(button.second->pressed()) + button.second->move(divertedXY.x, divertedXY.y); + } + }; + + _device->hidpp20().addEventHandler(EVENTHANDLER_NAME, handler); + }; +} + +void RemapButton::_buttonEvent(std::set new_state) +{ + // Ensure I/O doesn't occur while updating button state + std::lock_guard lock(_button_lock); + + // Press all added buttons + for(auto& i : new_state) { + auto old_i = _pressed_buttons.find(i); + if(old_i != _pressed_buttons.end()) { + _pressed_buttons.erase(old_i); + } else { + auto action = _config.buttons().find(i); + if(action != _config.buttons().end()) + action->second->press(); + } + } + + // Release all removed buttons + for(auto& i : _pressed_buttons) { + auto action = _config.buttons().find(i); + if(action != _config.buttons().end()) + action->second->release(); + } + + _pressed_buttons = new_state; +} + +RemapButton::Config::Config(Device *dev) : DeviceFeature::Config(dev) +{ + try { + auto& config_root = dev->config().getSetting("buttons"); + if(!config_root.isList()) { + logPrintf(WARN, "Line %d: buttons must be a list.", + config_root.getSourceLine()); + return; + } + int button_count = config_root.getLength(); + for(int i = 0; i < button_count; i++) + _parseButton(config_root[i]); + } catch(libconfig::SettingNotFoundException& e) { + // buttons not configured, use default + } +} + +void RemapButton::Config::_parseButton(libconfig::Setting &setting) +{ + if(!setting.isGroup()) { + logPrintf(WARN, "Line %d: button must be an object, ignoring.", + setting.getSourceLine()); + return; + } + + uint16_t cid; + try { + auto& cid_setting = setting.lookup("cid"); + if(!cid_setting.isNumber()) { + logPrintf(WARN, "Line %d: cid must be a number, ignoring.", + cid_setting.getSourceLine()); + return; + } + cid = (int)cid_setting; + } catch(libconfig::SettingNotFoundException& e) { + logPrintf(WARN, "Line %d: cid is required, ignoring.", + setting.getSourceLine()); + return; + } + + try { + _buttons.emplace(cid, Action::makeAction(_device, + setting.lookup("action"))); + } catch(libconfig::SettingNotFoundException& e) { + logPrintf(WARN, "Line %d: action is required, ignoring.", + setting.getSourceLine()); + } +} + +const std::map>& RemapButton::Config::buttons() +{ + return _buttons; +} \ No newline at end of file diff --git a/src/logid/features/RemapButton.h b/src/logid/features/RemapButton.h new file mode 100644 index 0000000..a45e767 --- /dev/null +++ b/src/logid/features/RemapButton.h @@ -0,0 +1,55 @@ +/* + * Copyright 2019-2020 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_FEATURE_REMAPBUTTON_H +#define LOGID_FEATURE_REMAPBUTTON_H + +#include "../backend/hidpp20/features/ReprogControls.h" +#include "DeviceFeature.h" +#include "../actions/Action.h" + +namespace logid { +namespace features +{ + class RemapButton : public DeviceFeature + { + public: + explicit RemapButton(Device* dev); + ~RemapButton(); + virtual void configure(); + virtual void listen(); + + class Config : public DeviceFeature::Config + { + public: + explicit Config(Device* dev); + const std::map>& + buttons(); + protected: + void _parseButton(libconfig::Setting& setting); + std::map> _buttons; + }; + private: + void _buttonEvent(std::set new_state); + Config _config; + std::shared_ptr _reprog_controls; + std::set _pressed_buttons; + std::mutex _button_lock; + }; +}} + +#endif //LOGID_FEATURE_REMAPBUTTON_H diff --git a/src/logid/logid.cpp b/src/logid/logid.cpp index ac6a446..6fc1197 100644 --- a/src/logid/logid.cpp +++ b/src/logid/logid.cpp @@ -24,8 +24,9 @@ #include "util/log.h" #include "DeviceManager.h" #include "logid.h" +#include "InputDevice.h" -#define evdev_name "logid" +#define LOGID_VIRTUAL_INPUT_NAME "LogiOps Virtual Input" #define DEFAULT_CONFIG_FILE "/etc/logid.cfg" #ifndef LOGIOPS_VERSION @@ -40,6 +41,7 @@ std::string config_file = DEFAULT_CONFIG_FILE; LogLevel logid::global_loglevel = INFO; std::shared_ptr logid::global_config; std::unique_ptr logid::device_manager; +std::unique_ptr logid::virtual_input; bool logid::kill_logid = false; std::mutex logid::device_manager_reload; @@ -163,17 +165,14 @@ int main(int argc, char** argv) global_config = std::make_shared(); } - /* - //Create an evdev device called 'logid' - try { global_evdev = new EvdevDevice(evdev_name); } - catch(std::system_error& e) - { - log_printf(ERROR, "Could not create evdev device: %s", e.what()); + //Create a virtual input device + try { + virtual_input = std::make_unique(LOGID_VIRTUAL_INPUT_NAME); + } catch(std::system_error& e) { + logPrintf(ERROR, "Could not create input device: %s", e.what()); return EXIT_FAILURE; } - */ - // Scan devices, create listeners, handlers, etc. device_manager = std::make_unique();