From 6555e4beddf539c74b889c7f90107ee0b2d21182 Mon Sep 17 00:00:00 2001 From: Soul Trace Date: Sun, 9 May 2021 01:15:51 +0300 Subject: [PATCH] Add Action type: "Axis" support for mapping buttons press to axis changes This is something like Gesture mode: "Axis", but works on mouse buttons. Configure it like the following (this example will map mouse wheel tilts to vertical scrolling): ``` buttons: ( { # Tilt wheel left cid: 0x5b; action: { type: "Axis"; axis: "REL_WHEEL_HI_RES"; move: -120; rate: 100; } }, { # Tilt wheel right cid: 0x5d; action: { type: "Axis"; axis: "REL_WHEEL_HI_RES"; move: 120; rate: 100; } } ) ``` '## Axis This maps pressed buttons to axis change (scrolling etc) with configurable move distance and autorepeat rate. \### axis This is a required string/integer field that defines the axis to be changed. For a list of axis strings, refer to [linux/input-event-codes.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h). (e.g. `axis: "REL_HWHEEL";`). Alternatively, you may use integer axis code to define the axis. \###move This is an optional integer field that defines the axis change per one repeat. Use negative values to invert scroll direction. Regular/HI_RES axis ratio is 1/120 (1 step for regular axis == 120 steps for HI_RES axis). (e.g. `move: -1;`) The default value is 1 for the regular axis and 120 for the HI_RES axis. \### rate This is an optional integer field that defines autorepeat rate (ms). (e.g. `rate: 100;`) The default value is 100 (repeat axis change 10 times per second as 1000/100 == 10). --- src/logid/CMakeLists.txt | 1 + src/logid/actions/Action.cpp | 3 + src/logid/actions/AxisAction.cpp | 194 +++++++++++++++++++++++++++++++ src/logid/actions/AxisAction.h | 60 ++++++++++ 4 files changed, 258 insertions(+) create mode 100644 src/logid/actions/AxisAction.cpp create mode 100644 src/logid/actions/AxisAction.h diff --git a/src/logid/CMakeLists.txt b/src/logid/CMakeLists.txt index 00ee796f..2077b5d2 100644 --- a/src/logid/CMakeLists.txt +++ b/src/logid/CMakeLists.txt @@ -23,6 +23,7 @@ add_executable(logid features/DeviceStatus.cpp features/ThumbWheel.cpp actions/Action.cpp + actions/AxisAction.cpp actions/NullAction.cpp actions/KeypressAction.cpp actions/ToggleHiresScroll.cpp diff --git a/src/logid/actions/Action.cpp b/src/logid/actions/Action.cpp index ecac552b..9e1711c8 100644 --- a/src/logid/actions/Action.cpp +++ b/src/logid/actions/Action.cpp @@ -27,6 +27,7 @@ #include "CycleDPI.h" #include "ChangeDPI.h" #include "ChangeHostAction.h" +#include "AxisAction.h" using namespace logid; using namespace logid::actions; @@ -68,6 +69,8 @@ std::shared_ptr Action::makeAction(Device *device, libconfig::Setting return std::make_shared(device); else if(type == "changehost") return std::make_shared(device, setting); + else if(type == "axis") + return std::make_shared(device, setting); else throw InvalidAction(type); diff --git a/src/logid/actions/AxisAction.cpp b/src/logid/actions/AxisAction.cpp new file mode 100644 index 00000000..99791bbf --- /dev/null +++ b/src/logid/actions/AxisAction.cpp @@ -0,0 +1,194 @@ +/* + * 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 "AxisAction.h" +#include "../util/log.h" +#include "../InputDevice.h" +#include "../backend/hidpp20/features/ReprogControls.h" +#include "../util/task.h" +#include "../util/workqueue.h" + +using namespace logid::actions; +using namespace logid::backend; + +AxisAction::AxisAction(Device *device, libconfig::Setting& config) : + Action(device), _config (device, config) +{ + _config.repeatMutex().lock(); + std::shared_ptr repeatTask = std::make_shared([this]() { + int axis = _config.axis(); + int move = _config.move(); + uint rate = _config.rate(); + logPrintf(DEBUG, "Started repeat task for axis %d with move %d at rate %d", + axis, move, rate); + int low_res_axis = InputDevice::getLowResAxis(axis); + int lowres_movement = move / _config.hiResMoveMultiplier(); + int hires_remainder = move % _config.hiResMoveMultiplier(); + while (true) { + _config.repeatMutex().lock(); + if(low_res_axis != -1) { +// logPrintf(DEBUG, "Moving axis %d to %d:%d", axis, lowres_movement, hires_remainder); + virtual_input->moveAxis(low_res_axis, lowres_movement); + virtual_input->moveAxis(axis, hires_remainder); + } else { +// logPrintf(DEBUG, "Moving axis %d to %d", axis, move); + virtual_input->moveAxis(axis, move); + } + _config.repeatMutex().unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(rate)); + } + }, [this](std::exception& e){ + logPrintf(ERROR, "Error during repeat: %s", + e.what()); + }); + global_workqueue->queue(repeatTask); + repeatTask->waitStart(); +} + +void AxisAction::press() +{ + _pressed = true; + _config.repeatMutex().unlock(); + +} + +void AxisAction::release() +{ + _pressed = false; + _config.repeatMutex().lock(); +} + +uint8_t AxisAction::reprogFlags() const +{ + return hidpp20::ReprogControls::TemporaryDiverted; +} + +AxisAction::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& axis = config["axis"]; + if(axis.isNumber()) { + _axis = axis; + virtual_input->registerAxis(axis); + registerLowResAxis(axis); + if (registerLowResAxis(axis)) { + _move *= _hiResMoveMultiplier; + } + } else if(axis.getType() == libconfig::Setting::TypeString) { + try { + _axis = virtual_input->toAxisCode(axis); + virtual_input->registerAxis(_axis); + if (registerLowResAxis(_axis)) { + _move *= _hiResMoveMultiplier; + } + } catch(InputDevice::InvalidEventCode& e) { + logPrintf(WARN, "Line %d: Invalid axis %s, skipping.", + axis.getSourceLine(), axis.c_str()); + return; + } + } else { + logPrintf(WARN, "Line %d: axis must be string or int", + axis.getSourceLine(), axis.c_str()); + return; + } + } catch (libconfig::SettingNotFoundException& e) { + logPrintf(WARN, "Line %d: axis is a required field, skipping.", + config.getSourceLine()); + } + + try { + auto& move = config["move"]; + if(move.isNumber()) { + _move = move; + } else { + logPrintf(WARN, "Line %d: move must be int", + move.getSourceLine(), move.c_str()); + throw InvalidAction(); + } + } catch (libconfig::SettingNotFoundException& e) { + logPrintf(WARN, "Line %d: move is not defined, using default value %d.", + config.getSourceLine(), _move); + } + if (_move == 0) { + logPrintf(WARN, "Line %d: move == 0 - this is pointless, but continue with it.", + config.getSourceLine()); + } + + + try { + auto& rate = config["rate"]; + if(rate.isNumber()) { + if ((int)rate > 0) { + _rate = rate; + } else { + logPrintf(WARN, "Line %d: rate is invalid, using default value %ums.", + config.getSourceLine(), _rate); + } + } else { + logPrintf(WARN, "Line %d: rate must be int", + rate.getSourceLine(), rate.c_str()); + throw InvalidAction(); + } + } catch (libconfig::SettingNotFoundException& e) { + logPrintf(WARN, "Line %d: rate is not defined, using default value %ums.", + config.getSourceLine(), _rate); + } + logPrintf(DEBUG, "Axis: configured axis %d with move %d at rate %d", + _axis, _move, _rate); +} + +bool AxisAction::Config::registerLowResAxis(const uint axis_code) { + bool registered = false; + int low_res_axis = InputDevice::getLowResAxis(axis_code); + if (low_res_axis != -1) { + virtual_input->registerAxis(low_res_axis); + registered = true; + } + return registered; +} + +uint AxisAction::Config::axis() +{ + return _axis; +} + +int AxisAction::Config::move() +{ + return _move; +} + +uint AxisAction::Config::rate() +{ + return _rate; +} + +int AxisAction::Config::hiResMoveMultiplier() +{ + return _hiResMoveMultiplier; +} + +std::mutex& AxisAction::Config::repeatMutex() +{ + return _repeatMutex; +} diff --git a/src/logid/actions/AxisAction.h b/src/logid/actions/AxisAction.h new file mode 100644 index 00000000..142a8ad7 --- /dev/null +++ b/src/logid/actions/AxisAction.h @@ -0,0 +1,60 @@ +/* + * 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_AXIS_H +#define LOGID_ACTION_AXIS_H + +#include +#include +#include +#include "Action.h" + +namespace logid { +namespace actions { + class AxisAction : public Action + { + public: + AxisAction(Device* dev, libconfig::Setting& config); + + virtual void press(); + virtual void release(); + + virtual uint8_t reprogFlags() const; + + class Config : public Action::Config + { + public: + explicit Config(Device* device, libconfig::Setting& root); + uint axis(); + int move(); + uint rate(); + int hiResMoveMultiplier(); + std::mutex& repeatMutex(); + protected: + bool registerLowResAxis(const uint axis_code); + uint _axis; + int _move = 1; + uint _rate = 100; + int _hiResMoveMultiplier = 120; + std::mutex _repeatMutex; + }; + protected: + Config _config; + }; +}} + +#endif //LOGID_ACTION_KEYPRESS_H