/* * 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 "../Device.h" #include "RemapButton.h" #include "../backend/hidpp20/Error.h" using namespace logid::features; using namespace logid::backend; using namespace logid::actions; #define HIDPP20_REPROG_REBIND (hidpp20::ReprogControls::ChangeTemporaryDivert \ | hidpp20::ReprogControls::ChangeRawXYDivert) #define EVENTHANDLER_NAME "REMAP_BUTTON" RemapButton::RemapButton(Device *dev): DeviceFeature(dev), _config (dev), _reprog_controls (hidpp20::ReprogControls::autoVersion(&dev->hidpp20())) { } RemapButton::~RemapButton() { _device->hidpp20().removeEventHandler(EVENTHANDLER_NAME); } void RemapButton::configure() { ///TODO: DJ reporting trickery if cannot be remapped for(auto& i : _config.buttons()) { hidpp20::ReprogControls::ControlInfo info{}; try { info = _reprog_controls->getControlIdInfo(i.first); } catch(hidpp20::Error& e) { if(e.code() == hidpp20::Error::InvalidArgument) { logPrintf(WARN, "%s: CID 0x%02x does not exist.", _device->name().c_str(), i.first); continue; } throw e; } if((i.second->reprogFlags() & hidpp20::ReprogControls::RawXYDiverted) && (!_reprog_controls->supportsRawXY() || (info.additionalFlags & hidpp20::ReprogControls::RawXY))) logPrintf(WARN, "%s: Cannot divert raw XY movements for CID " "0x%02x", _device->name().c_str(), i.first); hidpp20::ReprogControls::ControlInfo report{}; report.controlID = i.first; report.flags = HIDPP20_REPROG_REBIND; report.flags |= i.second->reprogFlags(); _reprog_controls->setControlReporting(i.first, report); } } void RemapButton::listen() { if(_device->hidpp20().eventHandlers().find(EVENTHANDLER_NAME) == _device->hidpp20().eventHandlers().end()) { auto handler = std::make_shared(); handler->condition = [index=_reprog_controls->featureIndex()] (hidpp::Report& report)->bool { return (report.feature() == index) && ((report.function() == hidpp20::ReprogControls::DivertedButtonEvent) || (report .function() == hidpp20::ReprogControls::DivertedRawXYEvent)); }; handler->callback = [this](hidpp::Report& report)->void { if(report.function() == hidpp20::ReprogControls::DivertedButtonEvent) this->_buttonEvent(_reprog_controls->divertedButtonEvent( report)); else { // RawXY auto divertedXY = _reprog_controls->divertedRawXYEvent(report); for(auto& button : this->_config.buttons()) if(button.second->pressed()) button.second->move(divertedXY.x, divertedXY.y); } }; _device->hidpp20().addEventHandler(EVENTHANDLER_NAME, handler); }; } void RemapButton::_buttonEvent(std::set new_state) { // Ensure I/O doesn't occur while updating button state std::lock_guard lock(_button_lock); // Press all added buttons for(auto& i : new_state) { auto old_i = _pressed_buttons.find(i); if(old_i != _pressed_buttons.end()) { _pressed_buttons.erase(old_i); } else { auto action = _config.buttons().find(i); if(action != _config.buttons().end()) action->second->press(); } } // Release all removed buttons for(auto& i : _pressed_buttons) { auto action = _config.buttons().find(i); if(action != _config.buttons().end()) action->second->release(); } _pressed_buttons = new_state; } RemapButton::Config::Config(Device *dev) : DeviceFeature::Config(dev) { try { auto& config_root = dev->config().getSetting("buttons"); if(!config_root.isList()) { logPrintf(WARN, "Line %d: buttons must be a list.", config_root.getSourceLine()); return; } int button_count = config_root.getLength(); for(int i = 0; i < button_count; i++) _parseButton(config_root[i]); } catch(libconfig::SettingNotFoundException& e) { // buttons not configured, use default } } void RemapButton::Config::_parseButton(libconfig::Setting &setting) { if(!setting.isGroup()) { logPrintf(WARN, "Line %d: button must be an object, ignoring.", setting.getSourceLine()); return; } uint16_t cid; try { auto& cid_setting = setting.lookup("cid"); if(!cid_setting.isNumber()) { logPrintf(WARN, "Line %d: cid must be a number, ignoring.", cid_setting.getSourceLine()); return; } cid = (int)cid_setting; } catch(libconfig::SettingNotFoundException& e) { logPrintf(WARN, "Line %d: cid is required, ignoring.", setting.getSourceLine()); return; } try { _buttons.emplace(cid, Action::makeAction(_device, setting.lookup("action"))); } catch(libconfig::SettingNotFoundException& e) { logPrintf(WARN, "Line %d: action is required, ignoring.", setting.getSourceLine()); } catch(InvalidAction& e) { logPrintf(WARN, "Line %d: %s is not a valid action, ignoring.", setting["action"].getSourceLine(), e.what()); } } const std::map>& RemapButton::Config::buttons() { return _buttons; }