Only supports OnRelease right now. Also, some bugs were spotted while writing this: - Sometimes deadlocks on startup (cause unknown) - Sometimes valid CIDs will be unknown (bug may have been fixed?)master
parent
41049deb35
commit
0fbeb1e3c9
13 changed files with 636 additions and 13 deletions
@ -0,0 +1,269 @@ |
||||
/*
|
||||
* 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 <algorithm> |
||||
#include "GestureAction.h" |
||||
#include "../Device.h" |
||||
#include "../backend/hidpp20/features/ReprogControls.h" |
||||
|
||||
using namespace logid::actions; |
||||
using namespace logid; |
||||
using namespace logid::backend; |
||||
|
||||
GestureAction::Direction GestureAction::toDirection(std::string direction) |
||||
{ |
||||
std::transform(direction.begin(), direction.end(), direction.begin(), |
||||
::tolower); |
||||
if(direction == "up") |
||||
return Up; |
||||
else if(direction == "down") |
||||
return Down; |
||||
else if(direction == "left") |
||||
return Left; |
||||
else if(direction == "right") |
||||
return Right; |
||||
else if(direction == "none") |
||||
return None; |
||||
else |
||||
throw std::invalid_argument("direction"); |
||||
} |
||||
|
||||
GestureAction::Direction GestureAction::toDirection(int16_t x, int16_t y) |
||||
{ |
||||
if(x >= 0 && y >= 0) |
||||
return x >= y ? Right : Down; |
||||
else if(x < 0 && y >= 0) |
||||
return -x <= y ? Down : Left; |
||||
else if(x <= 0 && y < 0) |
||||
return x <= y ? Left : Up; |
||||
else |
||||
return x <= -y ? Up : Right; |
||||
} |
||||
|
||||
GestureAction::GestureAction(Device* dev, libconfig::Setting& config) : |
||||
Action (dev), _config (dev, config) |
||||
{ |
||||
} |
||||
|
||||
void GestureAction::press() |
||||
{ |
||||
_pressed = true; |
||||
_x = 0, _y = 0; |
||||
for(auto& gesture : _config.gestures()) |
||||
gesture.second->press(); |
||||
} |
||||
|
||||
void GestureAction::release() |
||||
{ |
||||
_pressed = false; |
||||
bool threshold_met = false; |
||||
|
||||
auto d = toDirection(_x, _y); |
||||
auto primary_gesture = _config.gestures().find(d); |
||||
if(primary_gesture != _config.gestures().end()) { |
||||
threshold_met = primary_gesture->second->metThreshold(); |
||||
primary_gesture->second->release(true); |
||||
} |
||||
|
||||
for(auto& gesture : _config.gestures()) { |
||||
if(gesture.first == d) |
||||
continue; |
||||
if(!threshold_met) { |
||||
if(gesture.second->metThreshold()) { |
||||
// If the primary gesture did not meet its threshold, use the
|
||||
// secondary one.
|
||||
threshold_met = true; |
||||
gesture.second->release(true); |
||||
break; |
||||
} |
||||
} else { |
||||
gesture.second->release(false); |
||||
} |
||||
} |
||||
|
||||
if(!threshold_met) { |
||||
if(_config.noneAction()) { |
||||
_config.noneAction()->press(); |
||||
_config.noneAction()->release(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void GestureAction::move(int16_t x, int16_t y) |
||||
{ |
||||
auto new_x = _x + x, new_y = _y + y; |
||||
|
||||
if(abs(x) > 0) { |
||||
if(_x < 0 && new_x >= 0) { // Left -> Origin/Right
|
||||
auto left = _config.gestures().find(Left); |
||||
if(left != _config.gestures().end()) |
||||
left->second->move(_x); |
||||
if(new_x) { // Ignore to origin
|
||||
auto right = _config.gestures().find(Right); |
||||
if(right != _config.gestures().end()) |
||||
right->second->move(new_x); |
||||
} |
||||
} else if(_x > 0 && new_x <= 0) { // Right -> Origin/Left
|
||||
auto right = _config.gestures().find(Right); |
||||
if(right != _config.gestures().end()) |
||||
right->second->move(-_x); |
||||
if(new_x) { // Ignore to origin
|
||||
auto left = _config.gestures().find(Left); |
||||
if(left != _config.gestures().end()) |
||||
left->second->move(-new_x); |
||||
} |
||||
} else if(new_x < 0) { // Origin/Left to Left
|
||||
auto left = _config.gestures().find(Left); |
||||
if(left != _config.gestures().end()) |
||||
left->second->move(-x); |
||||
} else if(new_x > 0) { // Origin/Right to Right
|
||||
auto right = _config.gestures().find(Right); |
||||
if(right != _config.gestures().end()) |
||||
right->second->move(x); |
||||
} |
||||
} |
||||
|
||||
if(abs(y) > 0) { |
||||
if(_y > 0 && new_y <= 0) { // Up -> Origin/Down
|
||||
auto up = _config.gestures().find(Up); |
||||
if(up != _config.gestures().end()) |
||||
up->second->move(_y); |
||||
if(new_y) { // Ignore to origin
|
||||
auto down = _config.gestures().find(Down); |
||||
if(down != _config.gestures().end()) |
||||
down->second->move(new_y); |
||||
} |
||||
} else if(_y < 0 && new_y >= 0) { // Down -> Origin/Up
|
||||
auto down = _config.gestures().find(Down); |
||||
if(down != _config.gestures().end()) |
||||
down->second->move(-_y); |
||||
if(new_y) { // Ignore to origin
|
||||
auto up = _config.gestures().find(Up); |
||||
if(up != _config.gestures().end()) |
||||
up->second->move(-new_y); |
||||
} |
||||
} else if(new_y < 0) { // Origin/Up to Up
|
||||
auto up = _config.gestures().find(Up); |
||||
if(up != _config.gestures().end()) |
||||
up->second->move(-y); |
||||
} else if(new_y > 0) {// Origin/Down to Down
|
||||
auto down = _config.gestures().find(Down); |
||||
if(down != _config.gestures().end()) |
||||
down->second->move(y); |
||||
} |
||||
} |
||||
|
||||
_x = new_x; _y = new_y; |
||||
} |
||||
|
||||
uint8_t GestureAction::reprogFlags() const |
||||
{ |
||||
return (hidpp20::ReprogControls::TemporaryDiverted | |
||||
hidpp20::ReprogControls::RawXYDiverted); |
||||
} |
||||
|
||||
GestureAction::Config::Config(Device* device, libconfig::Setting &root) : |
||||
Action::Config(device) |
||||
{ |
||||
try { |
||||
auto& gestures = root.lookup("gestures"); |
||||
|
||||
if(!gestures.isList()) { |
||||
logPrintf(WARN, "Line %d: gestures must be a list, ignoring.", |
||||
gestures.getSourceLine()); |
||||
return; |
||||
} |
||||
|
||||
int gesture_count = gestures.getLength(); |
||||
|
||||
for(int i = 0; i < gesture_count; i++) { |
||||
if(!gestures[i].isGroup()) { |
||||
logPrintf(WARN, "Line %d: gesture must be a group, skipping.", |
||||
gestures[i].getSourceLine()); |
||||
continue; |
||||
} |
||||
|
||||
Direction d; |
||||
try { |
||||
auto& direction = gestures[i].lookup("direction"); |
||||
if(direction.getType() != libconfig::Setting::TypeString) { |
||||
logPrintf(WARN, "Line %d: direction must be a string, " |
||||
"skipping.", direction.getSourceLine()); |
||||
continue; |
||||
} |
||||
|
||||
try { |
||||
d = toDirection(direction); |
||||
} catch(std::invalid_argument& e) { |
||||
logPrintf(WARN, "Line %d: Invalid direction %s", |
||||
direction.getSourceLine(), (const char*)direction); |
||||
continue; |
||||
} |
||||
} catch(libconfig::SettingNotFoundException& e) { |
||||
logPrintf(WARN, "Line %d: direction is a required field, " |
||||
"skipping.", gestures[i].getSourceLine()); |
||||
continue; |
||||
} |
||||
|
||||
if(_gestures.find(d) != _gestures.end() || (d == None && _none_action)) { |
||||
logPrintf(WARN, "Line %d: Gesture is already defined for " |
||||
"this direction, duplicate ignored.", |
||||
gestures[i].getSourceLine()); |
||||
continue; |
||||
} |
||||
|
||||
if(d == None) { |
||||
try { |
||||
_none_action = Action::makeAction(_device, |
||||
gestures[i].lookup("action")); |
||||
} catch (InvalidAction& e) { |
||||
logPrintf(WARN, "Line %d: %s is not a valid action, " |
||||
"skipping.", gestures[i].lookup("action") |
||||
.getSourceLine(), e.what()); |
||||
} catch (libconfig::SettingNotFoundException& e) { |
||||
logPrintf(WARN, "Line %d: action is a required field, " |
||||
"skipping.", gestures[i].getSourceLine(), |
||||
e.what()); |
||||
} |
||||
continue; |
||||
} |
||||
|
||||
try { |
||||
_gestures.emplace(d, Gesture::makeGesture(_device, |
||||
gestures[i])); |
||||
} catch(InvalidGesture& e) { |
||||
logPrintf(WARN, "Line %d: Invalid gesture: %s", |
||||
gestures[i].getSourceLine(), e.what()); |
||||
} |
||||
} |
||||
|
||||
} catch(libconfig::SettingNotFoundException& e) { |
||||
logPrintf(WARN, "Line %d: gestures is a required field, ignoring.", |
||||
root.getSourceLine()); |
||||
} |
||||
} |
||||
|
||||
std::map<GestureAction::Direction, std::shared_ptr<Gesture>>& |
||||
GestureAction::Config::gestures() |
||||
{ |
||||
return _gestures; |
||||
} |
||||
|
||||
std::shared_ptr<Action> GestureAction::Config::noneAction() |
||||
{ |
||||
return _none_action; |
||||
} |
@ -0,0 +1,67 @@ |
||||
/*
|
||||
* 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_GESTUREACTION_H |
||||
#define LOGID_ACTION_GESTUREACTION_H |
||||
|
||||
#include <map> |
||||
#include <libconfig.h++> |
||||
#include "Action.h" |
||||
#include "gesture/Gesture.h" |
||||
|
||||
namespace logid { |
||||
namespace actions { |
||||
class GestureAction : public Action |
||||
{ |
||||
public: |
||||
enum Direction |
||||
{ |
||||
None, |
||||
Up, |
||||
Down, |
||||
Left, |
||||
Right |
||||
}; |
||||
static Direction toDirection(std::string direction); |
||||
static Direction toDirection(int16_t x, int16_t y); |
||||
|
||||
GestureAction(Device* dev, libconfig::Setting& config); |
||||
|
||||
virtual void press(); |
||||
virtual void release(); |
||||
virtual void move(int16_t x, int16_t y); |
||||
|
||||
virtual uint8_t reprogFlags() const; |
||||
|
||||
class Config : public Action::Config |
||||
{ |
||||
public: |
||||
Config(Device* device, libconfig::Setting& root); |
||||
std::map<Direction, std::shared_ptr<Gesture>>& gestures(); |
||||
std::shared_ptr<Action> noneAction(); |
||||
protected: |
||||
std::map<Direction, std::shared_ptr<Gesture>> _gestures; |
||||
std::shared_ptr<Action> _none_action; |
||||
}; |
||||
|
||||
protected: |
||||
int16_t _x, _y; |
||||
Config _config; |
||||
}; |
||||
}} |
||||
|
||||
#endif //LOGID_ACTION_GESTUREACTION_H
|
@ -0,0 +1,109 @@ |
||||
/*
|
||||
* 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 <algorithm> |
||||
#include "Gesture.h" |
||||
#include "../../util/log.h" |
||||
#include "ReleaseGesture.h" |
||||
#include "../../backend/hidpp20/features/ReprogControls.h" |
||||
|
||||
using namespace logid::actions; |
||||
|
||||
Gesture::Gesture(Device *device) : _device (device) |
||||
{ |
||||
} |
||||
|
||||
Gesture::Config::Config(Device* device, libconfig::Setting& root, |
||||
bool action_required) : _device (device) |
||||
{ |
||||
if(action_required) { |
||||
try { |
||||
_action = Action::makeAction(_device, |
||||
root.lookup("action")); |
||||
} catch (libconfig::SettingNotFoundException &e) { |
||||
throw InvalidGesture("action is missing"); |
||||
} |
||||
|
||||
if(_action->reprogFlags() & backend::hidpp20::ReprogControls::RawXYDiverted) |
||||
throw InvalidGesture("gesture cannot require RawXY"); |
||||
} |
||||
|
||||
_threshold = LOGID_GESTURE_DEFAULT_THRESHOLD; |
||||
try { |
||||
auto& threshold = root.lookup("threshold"); |
||||
if(threshold.getType() == libconfig::Setting::TypeInt) { |
||||
_threshold = (int)threshold; |
||||
if(_threshold <= 0) { |
||||
_threshold = LOGID_GESTURE_DEFAULT_THRESHOLD; |
||||
logPrintf(WARN, "Line %d: threshold must be positive, setting" |
||||
"to default (%d)", threshold.getSourceLine(), |
||||
_threshold); |
||||
} |
||||
} else |
||||
logPrintf(WARN, "Line %d: threshold must be an integer, setting " |
||||
"to default (%d).", threshold.getSourceLine()); |
||||
} catch(libconfig::SettingNotFoundException& e) { |
||||
// Ignore
|
||||
} |
||||
} |
||||
|
||||
std::shared_ptr<Gesture> Gesture::makeGesture(Device *device, |
||||
libconfig::Setting &setting) |
||||
{ |
||||
if(!setting.isGroup()) { |
||||
logPrintf(WARN, "Line %d: Gesture is not a group, ignoring.", |
||||
setting.getSourceLine()); |
||||
throw InvalidGesture(); |
||||
} |
||||
|
||||
try { |
||||
auto& gesture_mode = setting.lookup("mode"); |
||||
|
||||
if(gesture_mode.getType() != libconfig::Setting::TypeString) { |
||||
logPrintf(WARN, "Line %d: Gesture mode must be a string," |
||||
"defaulting to OnRelease.", |
||||
gesture_mode.getSourceLine()); |
||||
return std::make_shared<ReleaseGesture>(device, setting); |
||||
} |
||||
|
||||
std::string type = gesture_mode; |
||||
std::transform(type.begin(), type.end(), type.begin(), ::tolower); |
||||
|
||||
if(type == "onrelease") |
||||
return std::make_shared<ReleaseGesture>(device, setting); |
||||
else { |
||||
logPrintf(WARN, "Line %d: Unknown gesture mode %s, defaulting to " |
||||
"OnRelease.", gesture_mode.getSourceLine(), |
||||
(const char*)gesture_mode); |
||||
return std::make_shared<ReleaseGesture>(device, setting); |
||||
} |
||||
|
||||
} catch(libconfig::SettingNotFoundException& e) { |
||||
return std::make_shared<ReleaseGesture>(device, setting); |
||||
} |
||||
} |
||||
|
||||
int16_t Gesture::Config::threshold() const |
||||
{ |
||||
return _threshold; |
||||
} |
||||
|
||||
std::shared_ptr<Action> Gesture::Config::action() |
||||
{ |
||||
return _action; |
||||
} |
@ -0,0 +1,73 @@ |
||||
/*
|
||||
* 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_GESTURE_H |
||||
#define LOGID_ACTION_GESTURE_H |
||||
|
||||
#include "../Action.h" |
||||
|
||||
#define LOGID_GESTURE_DEFAULT_THRESHOLD 50 |
||||
|
||||
namespace logid { |
||||
namespace actions |
||||
{ |
||||
class InvalidGesture : public std::exception |
||||
{ |
||||
public: |
||||
explicit InvalidGesture(std::string what="") : _what (what) |
||||
{ |
||||
} |
||||
virtual const char* what() |
||||
{ |
||||
return _what.c_str(); |
||||
} |
||||
private: |
||||
std::string _what; |
||||
}; |
||||
|
||||
class Gesture |
||||
{ |
||||
public: |
||||
virtual void press() = 0; |
||||
virtual void release(bool primary=false) = 0; |
||||
virtual void move(int16_t axis) = 0; |
||||
|
||||
virtual bool metThreshold() const = 0; |
||||
|
||||
class Config |
||||
{ |
||||
public: |
||||
Config(Device* device, libconfig::Setting& root, |
||||
bool action_required=true); |
||||
virtual int16_t threshold() const; |
||||
virtual std::shared_ptr<Action> action(); |
||||
protected: |
||||
Device* _device; |
||||
std::shared_ptr<Action> _action; |
||||
int16_t _threshold; |
||||
}; |
||||
|
||||
static std::shared_ptr<Gesture> makeGesture(Device* device, |
||||
libconfig::Setting& setting); |
||||
|
||||
protected: |
||||
explicit Gesture(Device* device); |
||||
Device* _device; |
||||
}; |
||||
}} |
||||
|
||||
#endif //LOGID_ACTION_GESTURE_H
|
@ -0,0 +1,49 @@ |
||||
/*
|
||||
* 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 "ReleaseGesture.h" |
||||
#include "../../util/log.h" |
||||
|
||||
using namespace logid::actions; |
||||
|
||||
ReleaseGesture::ReleaseGesture(Device *device, libconfig::Setting &root) : |
||||
Gesture (device), _config (device, root) |
||||
{ |
||||
} |
||||
|
||||
void ReleaseGesture::press() |
||||
{ |
||||
_axis = 0; |
||||
} |
||||
|
||||
void ReleaseGesture::release(bool primary) |
||||
{ |
||||
if(metThreshold() && primary) { |
||||
_config.action()->press(); |
||||
_config.action()->release(); |
||||
} |
||||
} |
||||
|
||||
void ReleaseGesture::move(int16_t axis) |
||||
{ |
||||
_axis += axis; |
||||
} |
||||
|
||||
bool ReleaseGesture::metThreshold() const |
||||
{ |
||||
return _axis >= _config.threshold(); |
||||
} |
@ -0,0 +1,43 @@ |
||||
/*
|
||||
* 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_RELEASEGESTURE_H |
||||
#define LOGID_ACTION_RELEASEGESTURE_H |
||||
|
||||
#include "Gesture.h" |
||||
|
||||
namespace logid { |
||||
namespace actions |
||||
{ |
||||
class ReleaseGesture : public Gesture |
||||
{ |
||||
public: |
||||
ReleaseGesture(Device* device, libconfig::Setting& root); |
||||
|
||||
virtual void press(); |
||||
virtual void release(bool primary=false); |
||||
virtual void move(int16_t axis); |
||||
|
||||
virtual bool metThreshold() const; |
||||
|
||||
protected: |
||||
int16_t _axis; |
||||
Gesture::Config _config; |
||||
}; |
||||
}} |
||||
|
||||
#endif //LOGID_ACTION_RELEASEGESTURE_H
|
Loading…
Reference in new issue