Implement RemapButton feature
Many changes were made here, too many to list here.
This commit is contained in:
parent
0b87d3c664
commit
5bf5dc75b5
|
@ -11,12 +11,16 @@ find_package(PkgConfig REQUIRED)
|
|||
add_executable(logid
|
||||
logid.cpp
|
||||
util/log.cpp
|
||||
InputDevice.cpp
|
||||
DeviceManager.cpp
|
||||
Device.cpp
|
||||
Receiver.cpp
|
||||
Configuration.cpp
|
||||
features/DPI.cpp
|
||||
features/SmartShift.cpp
|
||||
features/RemapButton.cpp
|
||||
actions/Action.cpp
|
||||
actions/KeypressAction.cpp
|
||||
backend/Error.cpp
|
||||
backend/raw/DeviceMonitor.cpp
|
||||
backend/raw/RawDevice.cpp
|
||||
|
|
|
@ -86,7 +86,7 @@ Configuration::DeviceNotFound::DeviceNotFound(std::string name) :
|
|||
{
|
||||
}
|
||||
|
||||
const char * Configuration::DeviceNotFound::what()
|
||||
const char * Configuration::DeviceNotFound::what() const noexcept
|
||||
{
|
||||
return _name.c_str();
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace logid
|
|||
{
|
||||
public:
|
||||
explicit DeviceNotFound(std::string name);
|
||||
virtual const char* what();
|
||||
const char* what() const noexcept override;
|
||||
private:
|
||||
std::string _name;
|
||||
};
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "features/DPI.h"
|
||||
#include "Device.h"
|
||||
#include "features/SmartShift.h"
|
||||
#include "features/RemapButton.h"
|
||||
|
||||
using namespace logid;
|
||||
using namespace logid::backend;
|
||||
|
@ -46,9 +47,14 @@ void Device::_init()
|
|||
|
||||
_addFeature<features::DPI>("dpi");
|
||||
_addFeature<features::SmartShift>("smartshift");
|
||||
_addFeature<features::RemapButton>("remapbutton");
|
||||
|
||||
for(auto& feature: _features)
|
||||
for(auto& feature: _features) {
|
||||
feature.second->configure();
|
||||
feature.second->listen();
|
||||
}
|
||||
|
||||
_hidpp20.listen();
|
||||
}
|
||||
|
||||
std::string Device::name()
|
||||
|
@ -69,6 +75,9 @@ void Device::sleep()
|
|||
void Device::wakeup()
|
||||
{
|
||||
logPrintf(INFO, "%s:%d woke up.", _path.c_str(), _index);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
for(auto& feature: _features)
|
||||
feature.second->configure();
|
||||
}
|
||||
|
||||
DeviceConfig& Device::config()
|
||||
|
|
|
@ -16,19 +16,35 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
|
||||
#include "InputDevice.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <libevdev/libevdev.h>
|
||||
#include <libevdev/libevdev-uinput.h>
|
||||
#include <system_error>
|
||||
|
||||
#include "EvdevDevice.h"
|
||||
};
|
||||
|
||||
using namespace logid;
|
||||
|
||||
EvdevDevice::EvdevDevice(const char* name)
|
||||
InputDevice::InvalidEventCode::InvalidEventCode(std::string name)
|
||||
{
|
||||
_what = "Invalid event code " + name;
|
||||
}
|
||||
|
||||
const char* InputDevice::InvalidEventCode::what() const noexcept
|
||||
{
|
||||
return _what.c_str();
|
||||
}
|
||||
|
||||
InputDevice::InputDevice(const char* name)
|
||||
{
|
||||
device = libevdev_new();
|
||||
libevdev_set_name(device, name);
|
||||
|
||||
///TODO: Is it really a good idea to enable all events?
|
||||
libevdev_enable_event_type(device, EV_KEY);
|
||||
for(int i = 0; i < KEY_CNT; i++)
|
||||
libevdev_enable_event_code(device, EV_KEY, i, nullptr);
|
||||
|
@ -36,26 +52,56 @@ EvdevDevice::EvdevDevice(const char* name)
|
|||
for(int i = 0; i < REL_CNT; i++)
|
||||
libevdev_enable_event_code(device, EV_REL, i, nullptr);
|
||||
|
||||
int err = libevdev_uinput_create_from_device(device, LIBEVDEV_UINPUT_OPEN_MANAGED, &ui_device);
|
||||
int err = libevdev_uinput_create_from_device(device,
|
||||
LIBEVDEV_UINPUT_OPEN_MANAGED, &ui_device);
|
||||
|
||||
if(err != 0)
|
||||
throw std::system_error(-err, std::generic_category());
|
||||
}
|
||||
|
||||
void EvdevDevice::moveAxis(unsigned int axis, int movement)
|
||||
{
|
||||
libevdev_uinput_write_event(ui_device, EV_REL, axis, movement);
|
||||
libevdev_uinput_write_event(ui_device, EV_SYN, SYN_REPORT, 0);
|
||||
}
|
||||
|
||||
void EvdevDevice::sendEvent(unsigned int type, unsigned int code, int value)
|
||||
{
|
||||
libevdev_uinput_write_event(ui_device, type, code, value);
|
||||
libevdev_uinput_write_event(ui_device, EV_SYN, SYN_REPORT, 0);
|
||||
}
|
||||
|
||||
EvdevDevice::~EvdevDevice()
|
||||
InputDevice::~InputDevice()
|
||||
{
|
||||
libevdev_uinput_destroy(ui_device);
|
||||
libevdev_free(device);
|
||||
}
|
||||
|
||||
void InputDevice::moveAxis(uint axis, int movement)
|
||||
{
|
||||
_sendEvent(EV_REL, axis, movement);
|
||||
}
|
||||
|
||||
void InputDevice::pressKey(uint code)
|
||||
{
|
||||
_sendEvent(EV_KEY, code, 1);
|
||||
}
|
||||
|
||||
void InputDevice::releaseKey(uint code)
|
||||
{
|
||||
_sendEvent(EV_KEY, code, 0);
|
||||
}
|
||||
|
||||
uint InputDevice::toKeyCode(std::string name)
|
||||
{
|
||||
return _toEventCode(EV_KEY, std::move(name));
|
||||
}
|
||||
|
||||
uint InputDevice::toAxisCode(std::string name)
|
||||
{
|
||||
return _toEventCode(EV_KEY, std::move(name));
|
||||
}
|
||||
|
||||
uint InputDevice::_toEventCode(uint type, const std::string& name)
|
||||
{
|
||||
int code = libevdev_event_code_from_name(type, name.c_str());
|
||||
|
||||
if(code == -1)
|
||||
throw InvalidEventCode(name);
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
void InputDevice::_sendEvent(uint type, uint code, int value)
|
||||
{
|
||||
libevdev_uinput_write_event(ui_device, type, code, value);
|
||||
libevdev_uinput_write_event(ui_device, EV_SYN, SYN_REPORT, 0);
|
||||
}
|
66
src/logid/InputDevice.h
Normal file
66
src/logid/InputDevice.h
Normal file
|
@ -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
|
58
src/logid/actions/Action.cpp
Normal file
58
src/logid/actions/Action.cpp
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
82
src/logid/actions/Action.h
Normal file
82
src/logid/actions/Action.h
Normal file
|
@ -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
|
87
src/logid/actions/KeypressAction.cpp
Normal file
87
src/logid/actions/KeypressAction.cpp
Normal file
|
@ -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;
|
||||
}
|
|
@ -15,31 +15,36 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#ifndef LOGID_ACTION_KEYPRESS_H
|
||||
#define LOGID_ACTION_KEYPRESS_H
|
||||
|
||||
#ifndef LOGID_EVDEVDEVICE_H
|
||||
#define LOGID_EVDEVDEVICE_H
|
||||
#include <vector>
|
||||
#include <libconfig.h++>
|
||||
#include "Action.h"
|
||||
|
||||
#include <libevdev/libevdev.h>
|
||||
#include <libevdev/libevdev-uinput.h>
|
||||
|
||||
namespace logid
|
||||
{
|
||||
class EvdevDevice
|
||||
namespace logid {
|
||||
namespace actions {
|
||||
class KeypressAction : public Action
|
||||
{
|
||||
public:
|
||||
EvdevDevice(const char *name);
|
||||
KeypressAction(Device* dev, libconfig::Setting& config);
|
||||
|
||||
~EvdevDevice();
|
||||
virtual void press();
|
||||
virtual void release();
|
||||
|
||||
void moveAxis(unsigned int axis, int movement);
|
||||
virtual uint8_t reprogFlags() const;
|
||||
|
||||
void sendEvent(unsigned int type, unsigned int code, int value);
|
||||
|
||||
libevdev *device;
|
||||
libevdev_uinput *ui_device;
|
||||
class Config : public Action::Config
|
||||
{
|
||||
public:
|
||||
explicit Config(Device* device, libconfig::Setting& root);
|
||||
std::vector<uint>& keys();
|
||||
protected:
|
||||
std::vector<uint> _keys;
|
||||
};
|
||||
protected:
|
||||
Config _config;
|
||||
};
|
||||
}}
|
||||
|
||||
extern EvdevDevice* global_evdev;
|
||||
}
|
||||
|
||||
#endif //LOGID_EVDEVDEVICE_H
|
||||
#endif //LOGID_ACTION_KEYPRESS_H
|
|
@ -92,6 +92,7 @@ std::tuple<uint8_t, uint8_t> Device::version() const
|
|||
|
||||
void Device::_init()
|
||||
{
|
||||
_listening = false;
|
||||
_supported_reports = getSupportedReports(_raw_device->reportDescriptor());
|
||||
if(!_supported_reports)
|
||||
throw InvalidDevice(InvalidDevice::NoHIDPPReport);
|
||||
|
@ -127,6 +128,7 @@ void Device::_init()
|
|||
|
||||
Device::~Device()
|
||||
{
|
||||
if(_listening)
|
||||
_raw_device->removeEventHandler("DEV_" + std::to_string(_index));
|
||||
}
|
||||
|
||||
|
@ -144,6 +146,12 @@ void Device::removeEventHandler(const std::string& nickname)
|
|||
_event_handlers.erase(nickname);
|
||||
}
|
||||
|
||||
const std::map<std::string, std::shared_ptr<EventHandler>>&
|
||||
Device::eventHandlers()
|
||||
{
|
||||
return _event_handlers;
|
||||
}
|
||||
|
||||
void Device::handleEvent(Report& report)
|
||||
{
|
||||
for(auto& handler : _event_handlers)
|
||||
|
@ -192,32 +200,34 @@ uint16_t Device::pid() const
|
|||
void Device::listen()
|
||||
{
|
||||
if(!_raw_device->isListening())
|
||||
///TODO: Kill RawDevice?
|
||||
thread::spawn({[raw=this->_raw_device]() {
|
||||
raw->listen();
|
||||
}});
|
||||
|
||||
// Pass all HID++ events with device index to this device.
|
||||
std::shared_ptr<raw::RawEventHandler> handler;
|
||||
handler->condition = [this](std::vector<uint8_t>& report)->bool
|
||||
{
|
||||
auto handler = std::make_shared<raw::RawEventHandler>();
|
||||
handler->condition = [index=this->_index](std::vector<uint8_t>& report)
|
||||
->bool {
|
||||
return (report[Offset::Type] == Report::Type::Short ||
|
||||
report[Offset::Type] == Report::Type::Long) &&
|
||||
(report[Offset::DeviceIndex] == this->_index);
|
||||
(report[Offset::DeviceIndex] == index);
|
||||
};
|
||||
handler->callback = [this](std::vector<uint8_t>& report)->void
|
||||
{
|
||||
handler->callback = [this](std::vector<uint8_t>& report)->void {
|
||||
Report _report(report);
|
||||
this->handleEvent(_report);
|
||||
};
|
||||
|
||||
_raw_device->addEventHandler("DEV_" + std::to_string(_index), handler);
|
||||
_listening = true;
|
||||
}
|
||||
|
||||
void Device::stopListening()
|
||||
{
|
||||
if(_listening)
|
||||
_raw_device->removeEventHandler("DEV_" + std::to_string(_index));
|
||||
|
||||
_listening = false;
|
||||
|
||||
if(!_raw_device->eventHandlers().empty())
|
||||
_raw_device->stopListener();
|
||||
}
|
|
@ -81,6 +81,8 @@ namespace hidpp
|
|||
void addEventHandler(const std::string& nickname,
|
||||
const std::shared_ptr<EventHandler>& handler);
|
||||
void removeEventHandler(const std::string& nickname);
|
||||
const std::map<std::string, std::shared_ptr<EventHandler>>&
|
||||
eventHandlers();
|
||||
|
||||
Report sendReport(Report& report);
|
||||
|
||||
|
@ -98,6 +100,8 @@ namespace hidpp
|
|||
uint16_t _pid;
|
||||
std::string _name;
|
||||
|
||||
std::atomic<bool> _listening;
|
||||
|
||||
std::map<std::string, std::shared_ptr<EventHandler>> _event_handlers;
|
||||
};
|
||||
} } }
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
#define LOGID_HIDPP_SOFTWARE_ID 0
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace logid {
|
||||
namespace backend {
|
||||
namespace hidpp
|
||||
|
|
|
@ -63,3 +63,8 @@ Feature::Feature(Device* dev, uint16_t _id) : _device (dev)
|
|||
throw UnsupportedFeature(_id);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Feature::featureIndex()
|
||||
{
|
||||
return _index;
|
||||
}
|
|
@ -40,7 +40,7 @@ namespace hidpp20 {
|
|||
public:
|
||||
static const uint16_t ID;
|
||||
virtual uint16_t getID() = 0;
|
||||
|
||||
uint8_t featureIndex();
|
||||
protected:
|
||||
explicit Feature(Device* dev, uint16_t _id);
|
||||
std::vector<uint8_t> callFunction(uint8_t function_id,
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*
|
||||
*/
|
||||
#include <cassert>
|
||||
#include "../Error.h"
|
||||
#include "ReprogControls.h"
|
||||
|
||||
using namespace logid::backend::hidpp20;
|
||||
|
@ -30,7 +31,7 @@ x::x(Device* dev, uint16_t _id) : base(dev, _id) \
|
|||
|
||||
#define MAKE_REPROG(x, dev) \
|
||||
try { \
|
||||
return x(dev); \
|
||||
return std::make_shared<x>(dev); \
|
||||
} catch(UnsupportedFeature &e) {\
|
||||
}
|
||||
|
||||
|
@ -41,7 +42,7 @@ DEFINE_REPROG(ReprogControlsV2_2, ReprogControlsV2);
|
|||
DEFINE_REPROG(ReprogControlsV3, ReprogControlsV2_2);
|
||||
DEFINE_REPROG(ReprogControlsV4, ReprogControlsV3);
|
||||
|
||||
ReprogControls ReprogControls::autoVersion(Device *dev)
|
||||
std::shared_ptr<ReprogControls> ReprogControls::autoVersion(Device *dev)
|
||||
{
|
||||
MAKE_REPROG(ReprogControlsV4, dev);
|
||||
MAKE_REPROG(ReprogControlsV3, dev);
|
||||
|
@ -49,7 +50,7 @@ ReprogControls ReprogControls::autoVersion(Device *dev)
|
|||
MAKE_REPROG(ReprogControlsV2, dev);
|
||||
|
||||
// If base version cannot be made, throw error
|
||||
return ReprogControls(dev);
|
||||
return std::make_shared<ReprogControls>(dev);
|
||||
}
|
||||
|
||||
uint8_t ReprogControls::getControlCount()
|
||||
|
@ -78,29 +79,43 @@ ReprogControls::ControlInfo ReprogControls::getControlInfo(uint8_t index)
|
|||
return info;
|
||||
}
|
||||
|
||||
ReprogControls::ControlInfo ReprogControls::getControlIdInfo(uint16_t cid)
|
||||
{
|
||||
if(_cids.empty()) {
|
||||
for(uint8_t i = 0; i < getControlCount(); i++) {
|
||||
auto info = getControlInfo(i);
|
||||
_cids.emplace(info.controlID, info);
|
||||
}
|
||||
}
|
||||
|
||||
auto it = _cids.find(cid);
|
||||
if(it == _cids.end())
|
||||
throw Error(Error::InvalidArgument);
|
||||
else
|
||||
return it->second;
|
||||
}
|
||||
|
||||
ReprogControls::ControlInfo ReprogControls::getControlReporting(uint16_t cid)
|
||||
{
|
||||
std::vector<uint8_t> params(2);
|
||||
ControlInfo info{};
|
||||
params[0] = (cid >> 8) & 0xff;
|
||||
params[1] = cid & 0xff;
|
||||
auto response = callFunction(GetControlReporting, params);
|
||||
// Emulate this function, only Reprog controls v4 supports this
|
||||
auto info = getControlIdInfo(cid);
|
||||
|
||||
info.controlID = response[1];
|
||||
info.controlID |= response[0] << 8;
|
||||
info.flags = response[2];
|
||||
return info;
|
||||
ControlInfo report{};
|
||||
report.controlID = cid;
|
||||
report.flags = 0;
|
||||
if(info.flags & TemporaryDivertable)
|
||||
report.flags |= TemporaryDiverted;
|
||||
if(info.flags & PersisentlyDivertable)
|
||||
report.flags |= PersistentlyDiverted;
|
||||
if(info.additionalFlags & RawXY)
|
||||
report.flags |= RawXYDiverted;
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
void ReprogControls::setControlReporting(uint8_t cid, ControlInfo info)
|
||||
{
|
||||
std::vector<uint8_t> params(5);
|
||||
params[0] = (cid >> 8) & 0xff;
|
||||
params[1] = cid & 0xff;
|
||||
params[2] = info.flags;
|
||||
params[3] = (info.controlID >> 8) & 0xff;
|
||||
params[4] = info.controlID & 0xff;
|
||||
callFunction(SetControlReporting, params);
|
||||
// This function does not exist pre-v4 and cannot be emulated, ignore.
|
||||
}
|
||||
|
||||
std::set<uint16_t> ReprogControls::divertedButtonEvent(
|
||||
|
@ -131,3 +146,28 @@ ReprogControls::Move ReprogControls::divertedRawXYEvent(const hidpp::Report
|
|||
move.y |= report.paramBegin()[2] << 8;
|
||||
return move;
|
||||
}
|
||||
|
||||
ReprogControls::ControlInfo ReprogControlsV4::getControlReporting(uint16_t cid)
|
||||
{
|
||||
std::vector<uint8_t> params(2);
|
||||
ControlInfo info{};
|
||||
params[0] = (cid >> 8) & 0xff;
|
||||
params[1] = cid & 0xff;
|
||||
auto response = callFunction(GetControlReporting, params);
|
||||
|
||||
info.controlID = response[1];
|
||||
info.controlID |= response[0] << 8;
|
||||
info.flags = response[2];
|
||||
return info;
|
||||
}
|
||||
|
||||
void ReprogControlsV4::setControlReporting(uint8_t cid, ControlInfo info)
|
||||
{
|
||||
std::vector<uint8_t> params(5);
|
||||
params[0] = (cid >> 8) & 0xff;
|
||||
params[1] = cid & 0xff;
|
||||
params[2] = info.flags;
|
||||
params[3] = (info.controlID >> 8) & 0xff;
|
||||
params[4] = info.controlID & 0xff;
|
||||
callFunction(SetControlReporting, params);
|
||||
}
|
|
@ -18,6 +18,8 @@
|
|||
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H
|
||||
#define LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "../feature_defs.h"
|
||||
#include "../Feature.h"
|
||||
|
||||
|
@ -28,27 +30,17 @@ namespace hidpp20
|
|||
class ReprogControls : public Feature
|
||||
{
|
||||
public:
|
||||
static const uint16_t ID = FeatureID::REPROG_CONTROLS;
|
||||
virtual uint16_t getID() { return ID; }
|
||||
|
||||
virtual bool supportsRawXY() { return false; }
|
||||
|
||||
enum Function {
|
||||
GetControlCount = 0,
|
||||
GetControlInfo = 1,
|
||||
GetControlReporting = 2,
|
||||
SetControlReporting = 3
|
||||
};
|
||||
|
||||
enum Event {
|
||||
DivertedButtonEvent = 0,
|
||||
DivertedRawXYEvent = 1
|
||||
};
|
||||
|
||||
explicit ReprogControls(Device* dev);
|
||||
|
||||
virtual uint8_t getControlCount();
|
||||
|
||||
struct ControlInfo
|
||||
{
|
||||
uint16_t controlID;
|
||||
|
@ -68,24 +60,41 @@ namespace hidpp20
|
|||
FnToggle = 1<<3,
|
||||
ReprogHint = 1<<4,
|
||||
TemporaryDivertable = 1<<5,
|
||||
PerisentlyDiverable = 1<<6,
|
||||
PersisentlyDivertable = 1<<6,
|
||||
Virtual = 1<<7
|
||||
};
|
||||
enum ControlInfoAdditionalFlags: uint8_t {
|
||||
RawXY = 1<<0
|
||||
};
|
||||
|
||||
virtual ControlInfo getControlInfo(uint8_t index);
|
||||
|
||||
enum ControlReportingFlags: uint8_t {
|
||||
TemporaryDiverted = 1<<0,
|
||||
ChangeTemporaryDivert = 1<<1,
|
||||
PersistentDiverted = 1<<2,
|
||||
PersistentlyDiverted = 1<<2,
|
||||
ChangePersistentDivert = 1<<3,
|
||||
RawXYDiverted = 1<<4,
|
||||
ChangeRawXYDivert = 1<<5
|
||||
};
|
||||
|
||||
struct Move
|
||||
{
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
};
|
||||
|
||||
static const uint16_t ID = FeatureID::REPROG_CONTROLS;
|
||||
virtual uint16_t getID() { return ID; }
|
||||
|
||||
virtual bool supportsRawXY() { return false; }
|
||||
|
||||
explicit ReprogControls(Device* dev);
|
||||
|
||||
virtual uint8_t getControlCount();
|
||||
|
||||
virtual ControlInfo getControlInfo(uint8_t cid);
|
||||
|
||||
virtual ControlInfo getControlIdInfo(uint16_t cid);
|
||||
|
||||
// Onlu controlId and flags will be set
|
||||
virtual ControlInfo getControlReporting(uint16_t cid);
|
||||
|
||||
|
@ -95,24 +104,19 @@ namespace hidpp20
|
|||
static std::set<uint16_t> divertedButtonEvent(const hidpp::Report&
|
||||
report);
|
||||
|
||||
struct Move
|
||||
{
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
};
|
||||
|
||||
static Move divertedRawXYEvent(const hidpp::Report& report);
|
||||
|
||||
static ReprogControls autoVersion(Device *dev);
|
||||
static std::shared_ptr<ReprogControls> autoVersion(Device *dev);
|
||||
protected:
|
||||
ReprogControls(Device* dev, uint16_t _id);
|
||||
std::map<uint16_t, ControlInfo> _cids;
|
||||
};
|
||||
|
||||
class ReprogControlsV2 : public ReprogControls
|
||||
{
|
||||
public:
|
||||
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V2;
|
||||
uint16_t getID() override { return ID; }
|
||||
virtual uint16_t getID() { return ID; }
|
||||
|
||||
explicit ReprogControlsV2(Device* dev);
|
||||
protected:
|
||||
|
@ -123,7 +127,7 @@ namespace hidpp20
|
|||
{
|
||||
public:
|
||||
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V2_2;
|
||||
uint16_t getID() override { return ID; }
|
||||
virtual uint16_t getID() { return ID; }
|
||||
|
||||
explicit ReprogControlsV2_2(Device* dev);
|
||||
protected:
|
||||
|
@ -134,7 +138,7 @@ namespace hidpp20
|
|||
{
|
||||
public:
|
||||
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V3;
|
||||
uint16_t getID() override { return ID; }
|
||||
virtual uint16_t getID() { return ID; }
|
||||
|
||||
explicit ReprogControlsV3(Device* dev);
|
||||
protected:
|
||||
|
@ -145,10 +149,14 @@ namespace hidpp20
|
|||
{
|
||||
public:
|
||||
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V4;
|
||||
uint16_t getID() override { return ID; }
|
||||
virtual uint16_t getID() { return ID; }
|
||||
|
||||
bool supportsRawXY() override { return true; }
|
||||
|
||||
ControlInfo getControlReporting(uint16_t cid) override;
|
||||
|
||||
void setControlReporting(uint8_t cid, ControlInfo info) override;
|
||||
|
||||
explicit ReprogControlsV4(Device* dev);
|
||||
protected:
|
||||
ReprogControlsV4(Device* dev, uint16_t _id);
|
||||
|
|
|
@ -354,6 +354,7 @@ void RawDevice::addEventHandler(const std::string& nickname,
|
|||
{
|
||||
auto it = _event_handlers.find(nickname);
|
||||
assert(it == _event_handlers.end());
|
||||
assert(handler);
|
||||
_event_handlers.emplace(nickname, handler);
|
||||
}
|
||||
|
||||
|
|
180
src/logid/features/RemapButton.cpp
Normal file
180
src/logid/features/RemapButton.cpp
Normal file
|
@ -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;
|
||||
}
|
55
src/logid/features/RemapButton.h
Normal file
55
src/logid/features/RemapButton.h
Normal file
|
@ -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
|
|
@ -24,8 +24,9 @@
|
|||
#include "util/log.h"
|
||||
#include "DeviceManager.h"
|
||||
#include "logid.h"
|
||||
#include "InputDevice.h"
|
||||
|
||||
#define evdev_name "logid"
|
||||
#define LOGID_VIRTUAL_INPUT_NAME "LogiOps Virtual Input"
|
||||
#define DEFAULT_CONFIG_FILE "/etc/logid.cfg"
|
||||
|
||||
#ifndef LOGIOPS_VERSION
|
||||
|
@ -40,6 +41,7 @@ std::string config_file = DEFAULT_CONFIG_FILE;
|
|||
LogLevel logid::global_loglevel = INFO;
|
||||
std::shared_ptr<Configuration> logid::global_config;
|
||||
std::unique_ptr<DeviceManager> logid::device_manager;
|
||||
std::unique_ptr<InputDevice> logid::virtual_input;
|
||||
|
||||
bool logid::kill_logid = false;
|
||||
std::mutex logid::device_manager_reload;
|
||||
|
@ -163,17 +165,14 @@ int main(int argc, char** argv)
|
|||
global_config = std::make_shared<Configuration>();
|
||||
}
|
||||
|
||||
/*
|
||||
//Create an evdev device called 'logid'
|
||||
try { global_evdev = new EvdevDevice(evdev_name); }
|
||||
catch(std::system_error& e)
|
||||
{
|
||||
log_printf(ERROR, "Could not create evdev device: %s", e.what());
|
||||
//Create a virtual input device
|
||||
try {
|
||||
virtual_input = std::make_unique<InputDevice>(LOGID_VIRTUAL_INPUT_NAME);
|
||||
} catch(std::system_error& e) {
|
||||
logPrintf(ERROR, "Could not create input device: %s", e.what());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
// Scan devices, create listeners, handlers, etc.
|
||||
device_manager = std::make_unique<DeviceManager>();
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user