parent
0b87d3c664
commit
5bf5dc75b5
21 changed files with 764 additions and 103 deletions
@ -0,0 +1,66 @@ |
||||
/*
|
||||
* 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_INPUTDEVICE_H |
||||
#define LOGID_INPUTDEVICE_H |
||||
|
||||
#include <memory> |
||||
|
||||
extern "C" |
||||
{ |
||||
#include <libevdev/libevdev.h> |
||||
#include <libevdev/libevdev-uinput.h> |
||||
}; |
||||
|
||||
namespace logid |
||||
{ |
||||
typedef uint keycode; |
||||
|
||||
class InputDevice |
||||
{ |
||||
public: |
||||
class InvalidEventCode : public std::exception |
||||
{ |
||||
public: |
||||
explicit InvalidEventCode(std::string name); |
||||
const char* what() const noexcept override; |
||||
private: |
||||
std::string _what; |
||||
}; |
||||
explicit InputDevice(const char *name); |
||||
~InputDevice(); |
||||
|
||||
void moveAxis(uint axis, int movement); |
||||
void pressKey(uint code); |
||||
void releaseKey(uint code); |
||||
|
||||
static uint toKeyCode(std::string name); |
||||
static uint toAxisCode(std::string name); |
||||
private: |
||||
void _sendEvent(uint type, uint code, int value); |
||||
|
||||
static uint _toEventCode(uint type, const std::string& name); |
||||
|
||||
libevdev* device; |
||||
libevdev_uinput* ui_device; |
||||
}; |
||||
|
||||
extern std::unique_ptr<InputDevice> virtual_input; |
||||
} |
||||
|
||||
#endif //LOGID_INPUTDEVICE_H
|
@ -0,0 +1,58 @@ |
||||
/*
|
||||
* 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 "Action.h" |
||||
#include "../util/log.h" |
||||
#include "KeypressAction.h" |
||||
|
||||
using namespace logid; |
||||
using namespace logid::actions; |
||||
|
||||
std::shared_ptr<Action> Action::makeAction(Device *device, libconfig::Setting |
||||
&setting) |
||||
{ |
||||
if(!setting.isGroup()) { |
||||
logPrintf(WARN, "Line %d: Action is not a group, ignoring.", |
||||
setting.getSourceLine()); |
||||
throw InvalidAction(); |
||||
} |
||||
|
||||
try { |
||||
auto& action_type = setting.lookup("type"); |
||||
|
||||
if(action_type.getType() != libconfig::Setting::TypeString) { |
||||
logPrintf(WARN, "Line %d: Action type must be a string", |
||||
action_type.getSourceLine()); |
||||
throw InvalidAction(); |
||||
} |
||||
|
||||
std::string type = action_type; |
||||
std::transform(type.begin(), type.end(), type.begin(), ::tolower); |
||||
|
||||
if(type == "keypress") |
||||
return std::make_shared<KeypressAction>(device, setting); |
||||
else |
||||
throw InvalidAction(type); |
||||
|
||||
} catch(libconfig::SettingNotFoundException& e) { |
||||
logPrintf(WARN, "Line %d: Action type is missing, ignoring.", |
||||
setting.getSourceLine()); |
||||
throw InvalidAction(); |
||||
} |
||||
} |
@ -0,0 +1,82 @@ |
||||
/*
|
||||
* 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_H |
||||
#define LOGID_ACTION_H |
||||
|
||||
#include <atomic> |
||||
#include <libconfig.h++> |
||||
#include <memory> |
||||
|
||||
namespace logid { |
||||
class Device; |
||||
namespace actions { |
||||
class InvalidAction : public std::exception |
||||
{ |
||||
public: |
||||
InvalidAction() |
||||
{ |
||||
} |
||||
explicit InvalidAction(std::string& action) : _action (action) |
||||
{ |
||||
} |
||||
const char* what() const noexcept override |
||||
{ |
||||
return _action.c_str(); |
||||
} |
||||
private: |
||||
std::string _action; |
||||
}; |
||||
|
||||
class Action |
||||
{ |
||||
public: |
||||
static std::shared_ptr<Action> makeAction(Device* device, |
||||
libconfig::Setting& setting); |
||||
|
||||
virtual void press() = 0; |
||||
virtual void release() = 0; |
||||
virtual void move(int16_t x, int16_t y) |
||||
{ |
||||
} |
||||
|
||||
virtual bool pressed() |
||||
{ |
||||
return _pressed; |
||||
} |
||||
|
||||
virtual uint8_t reprogFlags() const = 0; |
||||
|
||||
class Config |
||||
{ |
||||
public: |
||||
explicit Config(Device* device) : _device (device) |
||||
{ |
||||
} |
||||
protected: |
||||
Device* _device; |
||||
}; |
||||
protected: |
||||
explicit Action(Device* device) : _device (device), _pressed (false) |
||||
{ |
||||
} |
||||
Device* _device; |
||||
std::atomic<bool> _pressed; |
||||
}; |
||||
}} |
||||
|
||||
#endif //LOGID_ACTION_H
|
@ -0,0 +1,87 @@ |
||||
/*
|
||||
* 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 "KeypressAction.h" |
||||
#include "../util/log.h" |
||||
#include "../InputDevice.h" |
||||
#include "../backend/hidpp20/features/ReprogControls.h" |
||||
|
||||
using namespace logid::actions; |
||||
using namespace logid::backend; |
||||
|
||||
KeypressAction::KeypressAction(Device *device, libconfig::Setting& config) : |
||||
Action(device), _config (device, config) |
||||
{ |
||||
} |
||||
|
||||
void KeypressAction::press() |
||||
{ |
||||
for(auto& key : _config.keys()) |
||||
virtual_input->pressKey(key); |
||||
} |
||||
|
||||
void KeypressAction::release() |
||||
{ |
||||
for(auto& key : _config.keys()) |
||||
virtual_input->releaseKey(key); |
||||
} |
||||
|
||||
uint8_t KeypressAction::reprogFlags() const |
||||
{ |
||||
return hidpp20::ReprogControls::TemporaryDiverted; |
||||
} |
||||
|
||||
KeypressAction::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 &keys = config.lookup("keys"); |
||||
if(keys.isArray() || keys.isList()) { |
||||
int key_count = keys.getLength(); |
||||
for(int i = 0; i < key_count; i++) { |
||||
auto& key = keys[i]; |
||||
if(key.isNumber()) { |
||||
_keys.push_back(key); |
||||
} else if(key.getType() == libconfig::Setting::TypeString) { |
||||
try { |
||||
_keys.push_back(virtual_input->toKeyCode(key)); |
||||
} catch(InputDevice::InvalidEventCode& e) { |
||||
logPrintf(WARN, "Line %d: Invalid keycode %s, skipping." |
||||
, key.getSourceLine(), key.c_str()); |
||||
} |
||||
} else { |
||||
logPrintf(WARN, "Line %d: keycode must be string or int", |
||||
key.getSourceLine(), key.c_str()); |
||||
} |
||||
} |
||||
} |
||||
} catch (libconfig::SettingNotFoundException& e) { |
||||
logPrintf(WARN, "Line %d: keys is a required field, skipping.", |
||||
config.getSourceLine()); |
||||
} |
||||
} |
||||
|
||||
std::vector<uint>& KeypressAction::Config::keys() |
||||
{ |
||||
return _keys; |
||||
} |
@ -0,0 +1,180 @@ |
||||
/*
|
||||
* 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 <sstream> |
||||
#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<hidpp::EventHandler>(); |
||||
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<uint16_t> new_state) |
||||
{ |
||||
// Ensure I/O doesn't occur while updating button state
|
||||
std::lock_guard<std::mutex> 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()); |
||||
} |
||||
} |
||||
|
||||
const std::map<uint8_t, std::shared_ptr<Action>>& RemapButton::Config::buttons() |
||||
{ |
||||
return _buttons; |
||||
} |
@ -0,0 +1,55 @@ |
||||
/*
|
||||
* 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_FEATURE_REMAPBUTTON_H |
||||
#define LOGID_FEATURE_REMAPBUTTON_H |
||||
|
||||
#include "../backend/hidpp20/features/ReprogControls.h" |
||||
#include "DeviceFeature.h" |
||||
#include "../actions/Action.h" |
||||
|
||||
namespace logid { |
||||
namespace features |
||||
{ |
||||
class RemapButton : public DeviceFeature |
||||
{ |
||||
public: |
||||
explicit RemapButton(Device* dev); |
||||
~RemapButton(); |
||||
virtual void configure(); |
||||
virtual void listen(); |
||||
|
||||
class Config : public DeviceFeature::Config |
||||
{ |
||||
public: |
||||
explicit Config(Device* dev); |
||||
const std::map<uint8_t, std::shared_ptr<actions::Action>>& |
||||
buttons(); |
||||
protected: |
||||
void _parseButton(libconfig::Setting& setting); |
||||
std::map<uint8_t, std::shared_ptr<actions::Action>> _buttons; |
||||
}; |
||||
private: |
||||
void _buttonEvent(std::set<uint16_t> new_state); |
||||
Config _config; |
||||
std::shared_ptr<backend::hidpp20::ReprogControls> _reprog_controls; |
||||
std::set<uint16_t> _pressed_buttons; |
||||
std::mutex _button_lock; |
||||
}; |
||||
}} |
||||
|
||||
#endif //LOGID_FEATURE_REMAPBUTTON_H
|
Loading…
Reference in new issue