Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Action type: "Axis" support for mapping buttons press to axis changes #243

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/logid/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/logid/actions/Action.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "CycleDPI.h"
#include "ChangeDPI.h"
#include "ChangeHostAction.h"
#include "AxisAction.h"

using namespace logid;
using namespace logid::actions;
Expand Down Expand Up @@ -68,6 +69,8 @@ std::shared_ptr<Action> Action::makeAction(Device *device, libconfig::Setting
return std::make_shared<NullAction>(device);
else if(type == "changehost")
return std::make_shared<ChangeHostAction>(device, setting);
else if(type == "axis")
return std::make_shared<AxisAction>(device, setting);
else
throw InvalidAction(type);

Expand Down
194 changes: 194 additions & 0 deletions src/logid/actions/AxisAction.cpp
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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<task> repeatTask = std::make_shared<task>([this]() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code basically reserves an entire worker to the axis action regardless of the button state,
which is not ideal. We should instead:

  • Initialize an axis action task in the constructor
  • Queue it during the press event
  • On release, alert the task that it should stop (using a condition variable)
  • In the task, we should use condition_variable::wait_until instead of sleeping

That way, we are only using a worker when necessary.

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();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A unique_lock or a lock_guard should be used instead in press and release,


}

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;
}
60 changes: 60 additions & 0 deletions src/logid/actions/AxisAction.h
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_ACTION_AXIS_H
#define LOGID_ACTION_AXIS_H

#include <mutex>
#include <vector>
#include <libconfig.h++>
#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;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't have the repeat mutex in the config class. Regardless, in the solution I described, it is not needed, so this can be removed.

};
protected:
Config _config;
};
}}

#endif //LOGID_ACTION_KEYPRESS_H