From 8348782f27ecc998ddc4f86a5a892d33a7f21672 Mon Sep 17 00:00:00 2001 From: pixl Date: Sat, 22 Aug 2020 16:59:10 -0400 Subject: [PATCH] Implement ThumbWheel feature This feature has not been tested as it only works on devices with the 0x2150 Thumb wheel feature (e.g. MX Master 3). --- src/logid/CMakeLists.txt | 1 + src/logid/Device.cpp | 2 + src/logid/features/ThumbWheel.cpp | 300 ++++++++++++++++++++++++++++++ src/logid/features/ThumbWheel.h | 74 ++++++++ 4 files changed, 377 insertions(+) create mode 100644 src/logid/features/ThumbWheel.cpp create mode 100644 src/logid/features/ThumbWheel.h diff --git a/src/logid/CMakeLists.txt b/src/logid/CMakeLists.txt index 27b804fe..00ee796f 100644 --- a/src/logid/CMakeLists.txt +++ b/src/logid/CMakeLists.txt @@ -21,6 +21,7 @@ add_executable(logid features/HiresScroll.cpp features/RemapButton.cpp features/DeviceStatus.cpp + features/ThumbWheel.cpp actions/Action.cpp actions/NullAction.cpp actions/KeypressAction.cpp diff --git a/src/logid/Device.cpp b/src/logid/Device.cpp index c84def27..c907aa19 100644 --- a/src/logid/Device.cpp +++ b/src/logid/Device.cpp @@ -25,6 +25,7 @@ #include "backend/hidpp20/features/Reset.h" #include "features/HiresScroll.h" #include "features/DeviceStatus.h" +#include "features/ThumbWheel.h" using namespace logid; using namespace logid::backend; @@ -61,6 +62,7 @@ void Device::_init() _addFeature("hiresscroll"); _addFeature("remapbutton"); _addFeature("devicestatus"); + _addFeature("thumbwheel"); _makeResetMechanism(); reset(); diff --git a/src/logid/features/ThumbWheel.cpp b/src/logid/features/ThumbWheel.cpp new file mode 100644 index 00000000..57bb0555 --- /dev/null +++ b/src/logid/features/ThumbWheel.cpp @@ -0,0 +1,300 @@ +/* + * 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 "ThumbWheel.h" +#include "../Device.h" +#include "../actions/gesture/AxisGesture.h" + +using namespace logid::features; +using namespace logid::backend; +using namespace logid; + +#define FLAG_STR(b) (_wheel_info.capabilities & _thumb_wheel->b ? "YES" : \ + "NO") + +#define SCROLL_EVENTHANDLER_NAME "THUMB_WHEEL" + +ThumbWheel::ThumbWheel(Device *dev) : DeviceFeature(dev), _wheel_info(), + _config(dev) +{ + try { + _thumb_wheel = std::make_shared(&dev->hidpp20()); + } catch(hidpp20::UnsupportedFeature& e) { + throw UnsupportedFeature(); + } + + _wheel_info = _thumb_wheel->getInfo(); + + logPrintf(DEBUG,"Thumb wheel detected (0x2150), capabilities:"); + logPrintf(DEBUG, "timestamp | touch | proximity | single tap"); + logPrintf(DEBUG, "%-9s | %-5s | %-9s | %-10s", FLAG_STR(Timestamp), + FLAG_STR(Touch), FLAG_STR(Proxy), FLAG_STR(SingleTap)); + logPrintf(DEBUG, "Thumb wheel resolution: native (%d), diverted (%d)", + _wheel_info.nativeRes, _wheel_info.divertedRes); + + if(_config.leftAction()) { + try { + auto left_axis = std::dynamic_pointer_cast( + _config.leftAction()); + // TODO: How do hires multipliers work on 0x2150 thumbwheels? + if(left_axis) + left_axis->setHiresMultiplier(_wheel_info.divertedRes); + } catch(std::bad_cast& e) { } + + _config.leftAction()->press(true); + } + + if(_config.rightAction()) { + try { + auto right_axis = std::dynamic_pointer_cast( + _config.rightAction()); + if(right_axis) + right_axis->setHiresMultiplier(_wheel_info.divertedRes); + } catch(std::bad_cast& e) { } + + _config.rightAction()->press(true); + } +} + +void ThumbWheel::configure() +{ + _thumb_wheel->setStatus(_config.divert(), _config.invert()); +} + +void ThumbWheel::listen() +{ + if(_device->hidpp20().eventHandlers().find(SCROLL_EVENTHANDLER_NAME) == + _device->hidpp20().eventHandlers().end()) { + auto handler = std::make_shared(); + handler->condition = [index=_thumb_wheel->featureIndex()] + (hidpp::Report& report)->bool { + return (report.feature() == index) && (report.function() == + hidpp20::ThumbWheel::Event); + }; + + handler->callback = [this](hidpp::Report& report)->void { + this->_handleEvent(_thumb_wheel->thumbwheelEvent(report)); + }; + + _device->hidpp20().addEventHandler(SCROLL_EVENTHANDLER_NAME, handler); + } +} + +void ThumbWheel::_handleEvent(hidpp20::ThumbWheel::ThumbwheelEvent event) +{ + if(event.flags & hidpp20::ThumbWheel::SingleTap) { + auto action = _config.tapAction(); + if(action) { + action->press(); + action->release(); + } + } + + if((bool)(event.flags & hidpp20::ThumbWheel::Proxy) != _last_proxy) { + _last_proxy = !_last_proxy; + auto action = _config.proxyAction(); + if(action) { + if(_last_proxy) + action->press(); + else + action->release(); + } + } + + if((bool)(event.flags & hidpp20::ThumbWheel::Touch) != _last_touch) { + _last_touch = !_last_touch; + auto action = _config.touchAction(); + if(action) { + if(_last_proxy) + action->press(); + else + action->release(); + } + } + + if(event.rotationStatus != hidpp20::ThumbWheel::Inactive) { + // Make right positive unless inverted + event.rotation *= _wheel_info.defaultDirection; + + if(event.rotationStatus == hidpp20::ThumbWheel::Start) { + if(_config.rightAction()) + _config.rightAction()->press(true); + if(_config.leftAction()) + _config.leftAction()->press(true); + _last_direction = 0; + } + + if(event.rotation) { + int8_t direction = event.rotation > 0 ? 1 : -1; + std::shared_ptr scroll_action; + std::shared_ptr opposite_scroll; + + if(event.rotation > 0) { + scroll_action = _config.rightAction(); + opposite_scroll = _config.leftAction(); + } else { + scroll_action = _config.leftAction(); + opposite_scroll = _config.rightAction(); + } + + if(direction != _last_direction) { + if(opposite_scroll) + opposite_scroll->release(); + if(scroll_action) { + scroll_action->press(true); + scroll_action->move(direction * event.rotation); + } + } + + _last_direction = direction; + } + + if(event.rotationStatus == hidpp20::ThumbWheel::Stop) { + if(_config.rightAction()) + _config.rightAction()->release(); + if(_config.leftAction()) + _config.leftAction()->release(); + } + } +} + +ThumbWheel::Config::Config(Device* dev) : DeviceFeature::Config(dev) +{ + try { + auto& config_root = dev->config().getSetting("thumbwheel"); + if(!config_root.isGroup()) { + logPrintf(WARN, "Line %d: thumbwheel must be a group", + config_root.getSourceLine()); + return; + } + + try { + auto& divert = config_root.lookup("divert"); + if(divert.getType() == libconfig::Setting::TypeBoolean) { + _divert = divert; + } else { + logPrintf(WARN, "Line %d: divert must be a boolean", + divert.getSourceLine()); + } + } catch(libconfig::SettingNotFoundException& e) { } + + try { + auto& invert = config_root.lookup("invert"); + if(invert.getType() == libconfig::Setting::TypeBoolean) { + _invert = invert; + } else { + logPrintf(WARN, "Line %d: invert must be a boolean, ignoring.", + invert.getSourceLine()); + } + } catch(libconfig::SettingNotFoundException& e) { } + + if(_divert) { + _left_action = _genGesture(dev, config_root, "left"); + if(!_left_action) + logPrintf(WARN, "Line %d: divert is true but no left action " + "was set", config_root.getSourceLine()); + + _right_action = _genGesture(dev, config_root, "right"); + if(!_right_action) + logPrintf(WARN, "Line %d: divert is true but no right action " + "was set", config_root.getSourceLine()); + } + + _proxy_action = _genAction(dev, config_root, "proxy"); + _tap_action = _genAction(dev, config_root, "tap"); + _touch_action = _genAction(dev, config_root, "touch"); + } catch(libconfig::SettingNotFoundException& e) { + // ThumbWheel not configured, use default + } +} + +std::shared_ptr ThumbWheel::Config::_genAction(Device* dev, + libconfig::Setting& config_root, const std::string& name) +{ + try { + auto& a_group = config_root.lookup(name); + try { + return actions::Action::makeAction(dev, a_group); + } catch(actions::InvalidAction& e) { + logPrintf(WARN, "Line %d: Invalid action", + a_group.getSourceLine()); + } + } catch(libconfig::SettingNotFoundException& e) { + + } + return nullptr; +} + +std::shared_ptr ThumbWheel::Config::_genGesture(Device* dev, + libconfig::Setting& config_root, const std::string& name) +{ + try { + auto& g_group = config_root.lookup(name); + try { + auto g = actions::Gesture::makeGesture(dev, g_group); + if(g->wheelCompatibility()) { + return g; + } else { + logPrintf(WARN, "Line %d: This gesture cannot be used" + " as a scroll action.", + g_group.getSourceLine()); + } + } catch(actions::InvalidGesture& e) { + logPrintf(WARN, "Line %d: Invalid scroll action", + g_group.getSourceLine()); + } + } catch(libconfig::SettingNotFoundException& e) { + + } + return nullptr; +} + +bool ThumbWheel::Config::divert() const +{ + return _divert; +} + +bool ThumbWheel::Config::invert() const +{ + return _invert; +} + +const std::shared_ptr& ThumbWheel::Config::leftAction() const +{ + return _left_action; +} + +const std::shared_ptr& ThumbWheel::Config::rightAction() const +{ + return _right_action; +} + +const std::shared_ptr& ThumbWheel::Config::proxyAction() const +{ + return _proxy_action; +} + +const std::shared_ptr& ThumbWheel::Config::tapAction() const +{ + return _tap_action; +} + +const std::shared_ptr& ThumbWheel::Config::touchAction() const +{ + return _touch_action; +} \ No newline at end of file diff --git a/src/logid/features/ThumbWheel.h b/src/logid/features/ThumbWheel.h new file mode 100644 index 00000000..fae93eaf --- /dev/null +++ b/src/logid/features/ThumbWheel.h @@ -0,0 +1,74 @@ +/* + * 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_THUMBWHEEL_H +#define LOGID_FEATURE_THUMBWHEEL_H + +#include "../backend/hidpp20/features/ThumbWheel.h" +#include "DeviceFeature.h" +#include "../actions/gesture/Gesture.h" + +namespace logid { +namespace features +{ + class ThumbWheel : public DeviceFeature + { + public: + explicit ThumbWheel(Device* dev); + virtual void configure(); + virtual void listen(); + + class Config : public DeviceFeature::Config + { + public: + explicit Config(Device* dev); + bool divert() const; + bool invert() const; + + const std::shared_ptr& leftAction() const; + const std::shared_ptr& rightAction() const; + const std::shared_ptr& proxyAction() const; + const std::shared_ptr& tapAction() const; + const std::shared_ptr& touchAction() const; + protected: + bool _divert = false; + bool _invert = false; + + static std::shared_ptr _genGesture(Device* dev, + libconfig::Setting& setting, const std::string& name); + static std::shared_ptr _genAction(Device* dev, + libconfig::Setting& setting, const std::string& name); + + std::shared_ptr _left_action; + std::shared_ptr _right_action; + std::shared_ptr _proxy_action; + std::shared_ptr _tap_action; + std::shared_ptr _touch_action; + }; + private: + void _handleEvent(backend::hidpp20::ThumbWheel::ThumbwheelEvent event); + + std::shared_ptr _thumb_wheel; + backend::hidpp20::ThumbWheel::ThumbwheelInfo _wheel_info; + int8_t _last_direction = 0; + bool _last_proxy = false; + bool _last_touch = false; + Config _config; + }; +}} + +#endif //LOGID_FEATURE_THUMBWHEEL_H