/* * 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 "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["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]["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 { auto& mode = gestures[i]["mode"]; if(mode.getType() == libconfig::Setting::TypeString) { std::string mode_str = mode; std::transform(mode_str.begin(), mode_str.end(), mode_str.begin(), ::tolower); if(mode_str == "nopress") // No action continue; else if(mode_str != "onrelease") logPrintf(WARN, "Line %d: Only NoPress and " "OnRelease are supported for the " "None direction, defaulting to " "OnRelease.", mode.getSourceLine()); } else { logPrintf(WARN, "Line %d: mode must be a string, " "defaulting to OnRelease", mode.getSourceLine()); } } catch(libconfig::SettingNotFoundException& e) { // Default to OnRelease } try { _none_action = Action::makeAction(_device, gestures[i]["action"]); } catch (InvalidAction& e) { logPrintf(WARN, "Line %d: %s is not a valid action, " "skipping.", gestures[i]["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::Config::gestures() { return _gestures; } std::shared_ptr GestureAction::Config::noneAction() { return _none_action; }