This commit is contained in:
vseprr 2025-07-12 18:59:33 +03:00 committed by GitHub
commit 3cc82f2786
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
127 changed files with 14386 additions and 14114 deletions

View File

@ -1,120 +1,120 @@
cmake_minimum_required(VERSION 3.12)
project(logid)
# C++20 is only needed for string literal template parameters
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../CMake")
find_package(Threads REQUIRED)
find_package(PkgConfig REQUIRED)
add_executable(logid
logid.cpp
util/log.cpp
config/config.cpp
InputDevice.cpp
DeviceManager.cpp
Device.cpp
Receiver.cpp
Configuration.cpp
features/DPI.cpp
features/SmartShift.cpp
features/HiresScroll.cpp
features/RemapButton.cpp
features/DeviceStatus.cpp
features/ThumbWheel.cpp
actions/Action.cpp
actions/NullAction.cpp
actions/KeypressAction.cpp
actions/ToggleHiresScroll.cpp
actions/ToggleSmartShift.cpp
actions/CycleDPI.cpp
actions/ChangeDPI.cpp
actions/GestureAction.cpp
actions/ChangeHostAction.cpp
actions/ChangeProfile.cpp
actions/gesture/Gesture.cpp
actions/gesture/ReleaseGesture.cpp
actions/gesture/ThresholdGesture.cpp
actions/gesture/IntervalGesture.cpp
actions/gesture/AxisGesture.cpp
actions/gesture/NullGesture.cpp
backend/Error.cpp
backend/raw/DeviceMonitor.cpp
backend/raw/RawDevice.cpp
backend/raw/IOMonitor.cpp
backend/hidpp10/Receiver.cpp
backend/hidpp10/ReceiverMonitor.cpp
backend/hidpp/Device.cpp
backend/hidpp/Report.cpp
backend/hidpp10/Error.cpp
backend/hidpp10/Device.cpp
backend/hidpp20/Device.cpp
backend/hidpp20/Error.cpp
backend/hidpp20/Feature.cpp
backend/hidpp20/EssentialFeature.cpp
backend/hidpp20/features/Root.cpp
backend/hidpp20/features/FeatureSet.cpp
backend/hidpp20/features/DeviceName.cpp
backend/hidpp20/features/Reset.cpp
backend/hidpp20/features/AdjustableDPI.cpp
backend/hidpp20/features/SmartShift.cpp
backend/hidpp20/features/ReprogControls.cpp
backend/hidpp20/features/HiresScroll.cpp
backend/hidpp20/features/ChangeHost.cpp
backend/hidpp20/features/WirelessDeviceStatus.cpp
backend/hidpp20/features/ThumbWheel.cpp
util/task.cpp
util/ExceptionHandler.cpp)
set_target_properties(logid PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
pkg_check_modules(PC_EVDEV libevdev REQUIRED)
pkg_check_modules(SYSTEMD "systemd")
pkg_check_modules(LIBCONFIG libconfig REQUIRED)
pkg_check_modules(LIBUDEV libudev REQUIRED)
find_path(EVDEV_INCLUDE_DIR libevdev/libevdev.h
HINTS ${PC_EVDEV_INCLUDE_DIRS} ${PC_EVDEV_INCLUDEDIR})
find_library(EVDEV_LIBRARY
NAMES evdev libevdev)
set(IPCGULL_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/../ipcgull/src/include)
message(${IPCGULL_INCLUDE_DIRS})
include_directories(. ${EVDEV_INCLUDE_DIR} ${LIBUDEV_INCLUDE_DIRECTORIES} ${IPCGULL_INCLUDE_DIRS})
target_link_libraries(logid ${CMAKE_THREAD_LIBS_INIT} ${EVDEV_LIBRARY} config++
${LIBUDEV_LIBRARIES} ipcgull)
install(TARGETS logid DESTINATION bin)
if (SYSTEMD_FOUND)
if ("${SYSTEMD_SERVICES_INSTALL_DIR}" STREQUAL "")
execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE}
--variable=systemdsystemunitdir systemd
OUTPUT_VARIABLE SYSTEMD_SERVICES_INSTALL_DIR)
string(REGEX REPLACE "[ \t\n]+" "" SYSTEMD_SERVICES_INSTALL_DIR
"${SYSTEMD_SERVICES_INSTALL_DIR}")
endif ()
# Install systemd service
configure_file(logid.service.in ${CMAKE_BINARY_DIR}/logid.service)
message(STATUS "systemd units will be installed at ${SYSTEMD_SERVICES_INSTALL_DIR}")
install(FILES ${CMAKE_BINARY_DIR}/logid.service
DESTINATION ${SYSTEMD_SERVICES_INSTALL_DIR}
COMPONENT cp)
elseif (NOT SYSTEMD_FOUND AND SYSTEMD_SERVICES_INSTALL_DIR)
message(FATAL_ERROR "systemd is not found w/ pkg-config but SYSTEMD_SERVICES_INSTALL_DIR is defined.")
endif ()
# Install DBus conf
# TODO: Is there a better way of setting the system policy directory?
set(DBUS_SYSTEM_POLICY_INSTALL_DIR "/usr/share/dbus-1/system.d")
configure_file(logiops-dbus.conf.in ${CMAKE_BINARY_DIR}/pizza.pixl.LogiOps.conf)
message(STATUS "dbus system policy will be installed at ${DBUS_SYSTEM_POLICY_INSTALL_DIR}")
install(FILES ${CMAKE_BINARY_DIR}/pizza.pixl.LogiOps.conf
DESTINATION ${DBUS_SYSTEM_POLICY_INSTALL_DIR}
COMPONENT cp)
cmake_minimum_required(VERSION 3.12)
project(logid)
# C++20 is only needed for string literal template parameters
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../CMake")
find_package(Threads REQUIRED)
find_package(PkgConfig REQUIRED)
add_executable(logid
logid.cpp
util/log.cpp
config/config.cpp
InputDevice.cpp
DeviceManager.cpp
Device.cpp
Receiver.cpp
Configuration.cpp
features/DPI.cpp
features/SmartShift.cpp
features/HiresScroll.cpp
features/RemapButton.cpp
features/DeviceStatus.cpp
features/ThumbWheel.cpp
actions/Action.cpp
actions/NullAction.cpp
actions/KeypressAction.cpp
actions/ToggleHiresScroll.cpp
actions/ToggleSmartShift.cpp
actions/CycleDPI.cpp
actions/ChangeDPI.cpp
actions/GestureAction.cpp
actions/ChangeHostAction.cpp
actions/ChangeProfile.cpp
actions/gesture/Gesture.cpp
actions/gesture/ReleaseGesture.cpp
actions/gesture/ThresholdGesture.cpp
actions/gesture/IntervalGesture.cpp
actions/gesture/AxisGesture.cpp
actions/gesture/NullGesture.cpp
backend/Error.cpp
backend/raw/DeviceMonitor.cpp
backend/raw/RawDevice.cpp
backend/raw/IOMonitor.cpp
backend/hidpp10/Receiver.cpp
backend/hidpp10/ReceiverMonitor.cpp
backend/hidpp/Device.cpp
backend/hidpp/Report.cpp
backend/hidpp10/Error.cpp
backend/hidpp10/Device.cpp
backend/hidpp20/Device.cpp
backend/hidpp20/Error.cpp
backend/hidpp20/Feature.cpp
backend/hidpp20/EssentialFeature.cpp
backend/hidpp20/features/Root.cpp
backend/hidpp20/features/FeatureSet.cpp
backend/hidpp20/features/DeviceName.cpp
backend/hidpp20/features/Reset.cpp
backend/hidpp20/features/AdjustableDPI.cpp
backend/hidpp20/features/SmartShift.cpp
backend/hidpp20/features/ReprogControls.cpp
backend/hidpp20/features/HiresScroll.cpp
backend/hidpp20/features/ChangeHost.cpp
backend/hidpp20/features/WirelessDeviceStatus.cpp
backend/hidpp20/features/ThumbWheel.cpp
util/task.cpp
util/ExceptionHandler.cpp)
set_target_properties(logid PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
pkg_check_modules(PC_EVDEV libevdev REQUIRED)
pkg_check_modules(SYSTEMD "systemd")
pkg_check_modules(LIBCONFIG libconfig REQUIRED)
pkg_check_modules(LIBUDEV libudev REQUIRED)
find_path(EVDEV_INCLUDE_DIR libevdev/libevdev.h
HINTS ${PC_EVDEV_INCLUDE_DIRS} ${PC_EVDEV_INCLUDEDIR})
find_library(EVDEV_LIBRARY
NAMES evdev libevdev)
set(IPCGULL_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/../ipcgull/src/include)
message(${IPCGULL_INCLUDE_DIRS})
include_directories(. ${EVDEV_INCLUDE_DIR} ${LIBUDEV_INCLUDE_DIRECTORIES} ${IPCGULL_INCLUDE_DIRS})
target_link_libraries(logid ${CMAKE_THREAD_LIBS_INIT} ${EVDEV_LIBRARY} config++
${LIBUDEV_LIBRARIES} ipcgull)
install(TARGETS logid DESTINATION bin)
if (SYSTEMD_FOUND)
if ("${SYSTEMD_SERVICES_INSTALL_DIR}" STREQUAL "")
execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE}
--variable=systemdsystemunitdir systemd
OUTPUT_VARIABLE SYSTEMD_SERVICES_INSTALL_DIR)
string(REGEX REPLACE "[ \t\n]+" "" SYSTEMD_SERVICES_INSTALL_DIR
"${SYSTEMD_SERVICES_INSTALL_DIR}")
endif ()
# Install systemd service
configure_file(logid.service.in ${CMAKE_BINARY_DIR}/logid.service)
message(STATUS "systemd units will be installed at ${SYSTEMD_SERVICES_INSTALL_DIR}")
install(FILES ${CMAKE_BINARY_DIR}/logid.service
DESTINATION ${SYSTEMD_SERVICES_INSTALL_DIR}
COMPONENT cp)
elseif (NOT SYSTEMD_FOUND AND SYSTEMD_SERVICES_INSTALL_DIR)
message(FATAL_ERROR "systemd is not found w/ pkg-config but SYSTEMD_SERVICES_INSTALL_DIR is defined.")
endif ()
# Install DBus conf
# TODO: Is there a better way of setting the system policy directory?
set(DBUS_SYSTEM_POLICY_INSTALL_DIR "/usr/share/dbus-1/system.d")
configure_file(logiops-dbus.conf.in ${CMAKE_BINARY_DIR}/pizza.pixl.LogiOps.conf)
message(STATUS "dbus system policy will be installed at ${DBUS_SYSTEM_POLICY_INSTALL_DIR}")
install(FILES ${CMAKE_BINARY_DIR}/pizza.pixl.LogiOps.conf
DESTINATION ${DBUS_SYSTEM_POLICY_INSTALL_DIR}
COMPONENT cp)

View File

@ -1,76 +1,76 @@
/*
* Copyright 2019-2023 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 <Configuration.h>
#include <util/log.h>
#include <utility>
#include <filesystem>
#include <ipc_defs.h>
using namespace logid;
using namespace libconfig;
using namespace logid::config;
Configuration::Configuration(std::string config_file) :
_config_file(std::move(config_file)) {
if (std::filesystem::exists(_config_file)) {
try {
_config.readFile(_config_file.c_str());
} catch (const FileIOException& e) {
logPrintf(ERROR, "I/O Error while reading %s: %s", _config_file.c_str(),
e.what());
throw;
} catch (const ParseException& e) {
logPrintf(ERROR, "Parse error in %s, line %d: %s", e.getFile(),
e.getLine(), e.getError());
throw;
}
Config::operator=(get<Config>(_config.getRoot()));
} else {
logPrintf(INFO, "Config file does not exist, using empty config.");
}
if (!devices.has_value())
devices.emplace();
}
Configuration::Configuration() {
devices.emplace();
}
void Configuration::save() {
config::set(_config.getRoot(), *this);
try {
_config.writeFile(_config_file.c_str());
} catch (const FileIOException& e) {
logPrintf(ERROR, "I/O Error while writing %s: %s",
_config_file.c_str(), e.what());
throw;
} catch (const std::exception& e) {
logPrintf(ERROR, "Error while writing %s: %s",
_config_file.c_str(), e.what());
throw;
}
}
Configuration::IPC::IPC(Configuration* config) :
ipcgull::interface(SERVICE_ROOT_NAME ".Config", {
{"Save", {config, &Configuration::save}}
}, {}, {}) {
}
/*
* Copyright 2019-2023 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 <Configuration.h>
#include <util/log.h>
#include <utility>
#include <filesystem>
#include <ipc_defs.h>
using namespace logid;
using namespace libconfig;
using namespace logid::config;
Configuration::Configuration(std::string config_file) :
_config_file(std::move(config_file)) {
if (std::filesystem::exists(_config_file)) {
try {
_config.readFile(_config_file.c_str());
} catch (const FileIOException& e) {
logPrintf(ERROR, "I/O Error while reading %s: %s", _config_file.c_str(),
e.what());
throw;
} catch (const ParseException& e) {
logPrintf(ERROR, "Parse error in %s, line %d: %s", e.getFile(),
e.getLine(), e.getError());
throw;
}
Config::operator=(get<Config>(_config.getRoot()));
} else {
logPrintf(INFO, "Config file does not exist, using empty config.");
}
if (!devices.has_value())
devices.emplace();
}
Configuration::Configuration() {
devices.emplace();
}
void Configuration::save() {
config::set(_config.getRoot(), *this);
try {
_config.writeFile(_config_file.c_str());
} catch (const FileIOException& e) {
logPrintf(ERROR, "I/O Error while writing %s: %s",
_config_file.c_str(), e.what());
throw;
} catch (const std::exception& e) {
logPrintf(ERROR, "Error while writing %s: %s",
_config_file.c_str(), e.what());
throw;
}
}
Configuration::IPC::IPC(Configuration* config) :
ipcgull::interface(SERVICE_ROOT_NAME ".Config", {
{"Save", {config, &Configuration::save}}
}, {}, {}) {
}

View File

@ -1,58 +1,58 @@
/*
* Copyright 2019-2023 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_CONFIGURATION_H
#define LOGID_CONFIGURATION_H
#include <config/schema.h>
#include <ipcgull/interface.h>
#include <libconfig.h++>
#include <memory>
#include <chrono>
#include <set>
namespace logid {
namespace defaults {
static constexpr double io_timeout = 500;
static constexpr int workers = 4;
static constexpr int gesture_threshold = 50;
}
class Configuration : public config::Config {
public:
explicit Configuration(std::string config_file);
Configuration();
// Reloading is not safe, references will be invalidated
//void reload();
void save();
class IPC : public ipcgull::interface {
public:
explicit IPC(Configuration* config);
};
private:
std::string _config_file;
libconfig::Config _config;
};
}
#endif //LOGID_CONFIGURATION_H
/*
* Copyright 2019-2023 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_CONFIGURATION_H
#define LOGID_CONFIGURATION_H
#include <config/schema.h>
#include <ipcgull/interface.h>
#include <libconfig.h++>
#include <memory>
#include <chrono>
#include <set>
namespace logid {
namespace defaults {
static constexpr double io_timeout = 500;
static constexpr int workers = 4;
static constexpr int gesture_threshold = 50;
}
class Configuration : public config::Config {
public:
explicit Configuration(std::string config_file);
Configuration();
// Reloading is not safe, references will be invalidated
//void reload();
void save();
class IPC : public ipcgull::interface {
public:
explicit IPC(Configuration* config);
};
private:
std::string _config_file;
libconfig::Config _config;
};
}
#endif //LOGID_CONFIGURATION_H

View File

@ -1,366 +1,366 @@
/*
* Copyright 2019-2023 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 <Device.h>
#include <DeviceManager.h>
#include <features/SmartShift.h>
#include <features/DPI.h>
#include <features/RemapButton.h>
#include <features/HiresScroll.h>
#include <features/DeviceStatus.h>
#include <features/ThumbWheel.h>
#include <backend/hidpp20/features/Reset.h>
#include <util/task.h>
#include <util/log.h>
#include <thread>
#include <utility>
#include <ipc_defs.h>
using namespace logid;
using namespace logid::backend;
DeviceNickname::DeviceNickname(const std::shared_ptr<DeviceManager>& manager) :
_nickname(manager->newDeviceNickname()), _manager(manager) {
}
DeviceNickname::operator std::string() const {
return std::to_string(_nickname);
}
DeviceNickname::~DeviceNickname() {
if (auto manager = _manager.lock()) {
std::lock_guard<std::mutex> lock(manager->_nick_lock);
manager->_device_nicknames.erase(_nickname);
}
}
namespace logid {
class DeviceWrapper : public Device {
public:
template<typename... Args>
explicit DeviceWrapper(Args... args) : Device(std::forward<Args>(args)...) {}
};
}
std::shared_ptr<Device> Device::make(
std::string path, backend::hidpp::DeviceIndex index,
std::shared_ptr<DeviceManager> manager) {
auto ret = std::make_shared<DeviceWrapper>(std::move(path),
index,
std::move(manager));
ret->_self = ret;
ret->_ipc_node->manage(ret);
ret->_ipc_interface = ret->_ipc_node->make_interface<IPC>(ret.get());
return ret;
}
std::shared_ptr<Device> Device::make(
std::shared_ptr<backend::raw::RawDevice> raw_device,
backend::hidpp::DeviceIndex index,
std::shared_ptr<DeviceManager> manager) {
auto ret = std::make_shared<DeviceWrapper>(std::move(raw_device),
index,
std::move(manager));
ret->_self = ret;
ret->_ipc_node->manage(ret);
ret->_ipc_interface = ret->_ipc_node->make_interface<IPC>(ret.get());
return ret;
}
std::shared_ptr<Device> Device::make(
Receiver* receiver, backend::hidpp::DeviceIndex index,
std::shared_ptr<DeviceManager> manager) {
auto ret = std::make_shared<DeviceWrapper>(receiver, index, std::move(manager));
ret->_self = ret;
ret->_ipc_node->manage(ret);
ret->_ipc_interface = ret->_ipc_node->make_interface<IPC>(ret.get());
return ret;
}
Device::Device(std::string path, backend::hidpp::DeviceIndex index,
const std::shared_ptr<DeviceManager>& manager) :
_hidpp20(hidpp20::Device::make(path, index, manager,
manager->config()->io_timeout.value_or(
defaults::io_timeout))),
_path(std::move(path)), _index(index),
_config(_getConfig(manager, _hidpp20->name())),
_profile_name(ipcgull::property_readable, ""),
_manager(manager),
_nickname(manager),
_ipc_node(manager->devicesNode()->make_child(_nickname)),
_awake(ipcgull::property_readable, true) {
_init();
}
Device::Device(std::shared_ptr<backend::raw::RawDevice> raw_device,
hidpp::DeviceIndex index, const std::shared_ptr<DeviceManager>& manager) :
_hidpp20(hidpp20::Device::make(
std::move(raw_device), index,
manager->config()->io_timeout.value_or(defaults::io_timeout))),
_path(raw_device->rawPath()), _index(index),
_config(_getConfig(manager, _hidpp20->name())),
_profile_name(ipcgull::property_readable, ""),
_manager(manager),
_nickname(manager),
_ipc_node(manager->devicesNode()->make_child(_nickname)),
_awake(ipcgull::property_readable, true) {
_init();
}
Device::Device(Receiver* receiver, hidpp::DeviceIndex index,
const std::shared_ptr<DeviceManager>& manager) :
_hidpp20(hidpp20::Device::make(
receiver->rawReceiver(), index,
manager->config()->io_timeout.value_or(defaults::io_timeout))),
_path(receiver->path()), _index(index),
_config(_getConfig(manager, _hidpp20->name())),
_profile_name(ipcgull::property_readable, ""),
_manager(manager),
_nickname(manager),
_ipc_node(manager->devicesNode()->make_child(_nickname)),
_awake(ipcgull::property_readable, true) {
_init();
}
void Device::_init() {
logPrintf(INFO, "Device found: %s on %s:%d", name().c_str(),
hidpp20().devicePath().c_str(), _index);
{
std::unique_lock lock(_profile_mutex);
_profile = _config.profiles.find(_config.default_profile);
if (_profile == _config.profiles.end())
_profile = _config.profiles.insert({_config.default_profile, {}}).first;
_profile_name = _config.default_profile;
}
_addFeature<features::DPI>("dpi");
_addFeature<features::SmartShift>("smartshift");
_addFeature<features::HiresScroll>("hiresscroll");
_addFeature<features::RemapButton>("remapbutton");
_addFeature<features::DeviceStatus>("devicestatus");
_addFeature<features::ThumbWheel>("thumbwheel");
_makeResetMechanism();
reset();
for (auto& feature: _features) {
feature.second->configure();
feature.second->listen();
}
}
std::string Device::name() {
return _hidpp20->name();
}
uint16_t Device::pid() {
return _hidpp20->pid();
}
void Device::sleep() {
std::lock_guard<std::mutex> lock(_state_lock);
if (_awake) {
logPrintf(INFO, "%s:%d fell asleep.", _path.c_str(), _index);
_awake = false;
_ipc_interface->notifyStatus();
}
}
void Device::wakeup() {
std::lock_guard<std::mutex> lock(_state_lock);
reconfigure();
if (!_awake) {
_awake = true;
_ipc_interface->notifyStatus();
}
logPrintf(INFO, "%s:%d woke up.", _path.c_str(), _index);
}
void Device::reconfigure() {
reset();
for (auto& feature: _features)
feature.second->configure();
}
void Device::reset() {
if (_reset_mechanism)
(*_reset_mechanism)();
else
logPrintf(DEBUG, "%s:%d tried to reset, but no reset mechanism was "
"available.", _path.c_str(), _index);
}
std::shared_ptr<InputDevice> Device::virtualInput() const {
if (auto manager = _manager.lock()) {
return manager->virtualInput();
} else {
logPrintf(ERROR, "Device manager lost");
logPrintf(ERROR,
"Fatal error occurred, file a bug report,"
" the program will now exit.");
std::terminate();
}
}
std::shared_ptr<ipcgull::node> Device::ipcNode() const {
return _ipc_node;
}
std::vector<std::string> Device::getProfiles() const {
std::shared_lock lock(_profile_mutex);
std::vector<std::string> ret;
for (auto& profile : _config.profiles) {
ret.push_back(profile.first);
}
return ret;
}
void Device::setProfile(const std::string& profile) {
std::unique_lock lock(_profile_mutex);
_profile = _config.profiles.find(profile);
if (_profile == _config.profiles.end())
_profile = _config.profiles.insert({profile, {}}).first;
_profile_name = profile;
for (auto& feature : _features)
feature.second->setProfile(_profile->second);
reconfigure();
}
void Device::setProfileDelayed(const std::string& profile) {
run_task([self_weak = _self, profile](){
if (auto self = self_weak.lock())
self->setProfile(profile);
});
}
void Device::removeProfile(const std::string& profile) {
std::unique_lock lock(_profile_mutex);
if (profile == (std::string)_profile_name)
throw std::invalid_argument("cannot remove active profile");
else if (profile == (std::string)_config.default_profile)
throw std::invalid_argument("cannot remove default profile");
_config.profiles.erase(profile);
}
void Device::clearProfile(const std::string& profile) {
std::unique_lock lock(_profile_mutex);
if (profile == (std::string)_profile_name) {
_profile->second = config::Profile();
for (auto& feature : _features)
feature.second->setProfile(_profile->second);
reconfigure();
} else {
auto it = _config.profiles.find(profile);
if (it != _config.profiles.end()) {
it->second = config::Profile();
} else {
throw std::invalid_argument("unknown profile");
}
}
}
config::Profile& Device::activeProfile() {
std::shared_lock lock(_profile_mutex);
return _profile->second;
}
hidpp20::Device& Device::hidpp20() {
return *_hidpp20;
}
void Device::_makeResetMechanism() {
try {
hidpp20::Reset reset(_hidpp20.get());
_reset_mechanism = std::make_unique<std::function<void()>>(
[dev = _hidpp20] {
hidpp20::Reset reset(dev.get());
reset.reset(reset.getProfile());
});
} catch (hidpp20::UnsupportedFeature& e) {
// Reset unsupported, ignore.
}
}
Device::IPC::IPC(Device* device) :
ipcgull::interface(
SERVICE_ROOT_NAME ".Device",
{
{"GetProfiles", {device, &Device::getProfiles, {"profiles"}}},
{"SetProfile", {device, &Device::setProfile, {"profile"}}},
{"RemoveProfile", {device, &Device::removeProfile, {"profile"}}},
{"ClearProfile", {device, &Device::clearProfile, {"profile"}}}
},
{
{"Name", ipcgull::property<std::string>(
ipcgull::property_readable, device->name())},
{"ProductID", ipcgull::property<uint16_t>(
ipcgull::property_readable, device->pid())},
{"Active", device->_awake},
{"DefaultProfile", device->_config.default_profile},
{"ActiveProfile", device->_profile_name}
}, {
{"StatusChanged", ipcgull::signal::make_signal<bool>({"active"})}
}), _device(*device) {
}
void Device::IPC::notifyStatus() const {
emit_signal("StatusChanged", (bool) (_device._awake));
}
config::Device& Device::_getConfig(
const std::shared_ptr<DeviceManager>& manager,
const std::string& name) {
static std::mutex config_mutex;
std::lock_guard<std::mutex> lock(config_mutex);
auto& devices = manager->config()->devices.value();
if (!devices.count(name)) {
devices.emplace(name, config::Device());
}
auto& device = devices.at(name);
if (std::holds_alternative<config::Profile>(device)) {
config::Device d;
d.profiles["default"] = std::get<config::Profile>(device);
d.default_profile = "default";
device = std::move(d);
}
auto& conf = std::get<config::Device>(device);
if (conf.profiles.empty()) {
conf.profiles["default"] = {};
conf.default_profile = "default";
}
return conf;
}
/*
* Copyright 2019-2023 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 <Device.h>
#include <DeviceManager.h>
#include <features/SmartShift.h>
#include <features/DPI.h>
#include <features/RemapButton.h>
#include <features/HiresScroll.h>
#include <features/DeviceStatus.h>
#include <features/ThumbWheel.h>
#include <backend/hidpp20/features/Reset.h>
#include <util/task.h>
#include <util/log.h>
#include <thread>
#include <utility>
#include <ipc_defs.h>
using namespace logid;
using namespace logid::backend;
DeviceNickname::DeviceNickname(const std::shared_ptr<DeviceManager>& manager) :
_nickname(manager->newDeviceNickname()), _manager(manager) {
}
DeviceNickname::operator std::string() const {
return std::to_string(_nickname);
}
DeviceNickname::~DeviceNickname() {
if (auto manager = _manager.lock()) {
std::lock_guard<std::mutex> lock(manager->_nick_lock);
manager->_device_nicknames.erase(_nickname);
}
}
namespace logid {
class DeviceWrapper : public Device {
public:
template<typename... Args>
explicit DeviceWrapper(Args... args) : Device(std::forward<Args>(args)...) {}
};
}
std::shared_ptr<Device> Device::make(
std::string path, backend::hidpp::DeviceIndex index,
std::shared_ptr<DeviceManager> manager) {
auto ret = std::make_shared<DeviceWrapper>(std::move(path),
index,
std::move(manager));
ret->_self = ret;
ret->_ipc_node->manage(ret);
ret->_ipc_interface = ret->_ipc_node->make_interface<IPC>(ret.get());
return ret;
}
std::shared_ptr<Device> Device::make(
std::shared_ptr<backend::raw::RawDevice> raw_device,
backend::hidpp::DeviceIndex index,
std::shared_ptr<DeviceManager> manager) {
auto ret = std::make_shared<DeviceWrapper>(std::move(raw_device),
index,
std::move(manager));
ret->_self = ret;
ret->_ipc_node->manage(ret);
ret->_ipc_interface = ret->_ipc_node->make_interface<IPC>(ret.get());
return ret;
}
std::shared_ptr<Device> Device::make(
Receiver* receiver, backend::hidpp::DeviceIndex index,
std::shared_ptr<DeviceManager> manager) {
auto ret = std::make_shared<DeviceWrapper>(receiver, index, std::move(manager));
ret->_self = ret;
ret->_ipc_node->manage(ret);
ret->_ipc_interface = ret->_ipc_node->make_interface<IPC>(ret.get());
return ret;
}
Device::Device(std::string path, backend::hidpp::DeviceIndex index,
const std::shared_ptr<DeviceManager>& manager) :
_hidpp20(hidpp20::Device::make(path, index, manager,
manager->config()->io_timeout.value_or(
defaults::io_timeout))),
_path(std::move(path)), _index(index),
_config(_getConfig(manager, _hidpp20->name())),
_profile_name(ipcgull::property_readable, ""),
_manager(manager),
_nickname(manager),
_ipc_node(manager->devicesNode()->make_child(_nickname)),
_awake(ipcgull::property_readable, true) {
_init();
}
Device::Device(std::shared_ptr<backend::raw::RawDevice> raw_device,
hidpp::DeviceIndex index, const std::shared_ptr<DeviceManager>& manager) :
_hidpp20(hidpp20::Device::make(
std::move(raw_device), index,
manager->config()->io_timeout.value_or(defaults::io_timeout))),
_path(raw_device->rawPath()), _index(index),
_config(_getConfig(manager, _hidpp20->name())),
_profile_name(ipcgull::property_readable, ""),
_manager(manager),
_nickname(manager),
_ipc_node(manager->devicesNode()->make_child(_nickname)),
_awake(ipcgull::property_readable, true) {
_init();
}
Device::Device(Receiver* receiver, hidpp::DeviceIndex index,
const std::shared_ptr<DeviceManager>& manager) :
_hidpp20(hidpp20::Device::make(
receiver->rawReceiver(), index,
manager->config()->io_timeout.value_or(defaults::io_timeout))),
_path(receiver->path()), _index(index),
_config(_getConfig(manager, _hidpp20->name())),
_profile_name(ipcgull::property_readable, ""),
_manager(manager),
_nickname(manager),
_ipc_node(manager->devicesNode()->make_child(_nickname)),
_awake(ipcgull::property_readable, true) {
_init();
}
void Device::_init() {
logPrintf(INFO, "Device found: %s on %s:%d", name().c_str(),
hidpp20().devicePath().c_str(), _index);
{
std::unique_lock lock(_profile_mutex);
_profile = _config.profiles.find(_config.default_profile);
if (_profile == _config.profiles.end())
_profile = _config.profiles.insert({_config.default_profile, {}}).first;
_profile_name = _config.default_profile;
}
_addFeature<features::DPI>("dpi");
_addFeature<features::SmartShift>("smartshift");
_addFeature<features::HiresScroll>("hiresscroll");
_addFeature<features::RemapButton>("remapbutton");
_addFeature<features::DeviceStatus>("devicestatus");
_addFeature<features::ThumbWheel>("thumbwheel");
_makeResetMechanism();
reset();
for (auto& feature: _features) {
feature.second->configure();
feature.second->listen();
}
}
std::string Device::name() {
return _hidpp20->name();
}
uint16_t Device::pid() {
return _hidpp20->pid();
}
void Device::sleep() {
std::lock_guard<std::mutex> lock(_state_lock);
if (_awake) {
logPrintf(INFO, "%s:%d fell asleep.", _path.c_str(), _index);
_awake = false;
_ipc_interface->notifyStatus();
}
}
void Device::wakeup() {
std::lock_guard<std::mutex> lock(_state_lock);
reconfigure();
if (!_awake) {
_awake = true;
_ipc_interface->notifyStatus();
}
logPrintf(INFO, "%s:%d woke up.", _path.c_str(), _index);
}
void Device::reconfigure() {
reset();
for (auto& feature: _features)
feature.second->configure();
}
void Device::reset() {
if (_reset_mechanism)
(*_reset_mechanism)();
else
logPrintf(DEBUG, "%s:%d tried to reset, but no reset mechanism was "
"available.", _path.c_str(), _index);
}
std::shared_ptr<InputDevice> Device::virtualInput() const {
if (auto manager = _manager.lock()) {
return manager->virtualInput();
} else {
logPrintf(ERROR, "Device manager lost");
logPrintf(ERROR,
"Fatal error occurred, file a bug report,"
" the program will now exit.");
std::terminate();
}
}
std::shared_ptr<ipcgull::node> Device::ipcNode() const {
return _ipc_node;
}
std::vector<std::string> Device::getProfiles() const {
std::shared_lock lock(_profile_mutex);
std::vector<std::string> ret;
for (auto& profile : _config.profiles) {
ret.push_back(profile.first);
}
return ret;
}
void Device::setProfile(const std::string& profile) {
std::unique_lock lock(_profile_mutex);
_profile = _config.profiles.find(profile);
if (_profile == _config.profiles.end())
_profile = _config.profiles.insert({profile, {}}).first;
_profile_name = profile;
for (auto& feature : _features)
feature.second->setProfile(_profile->second);
reconfigure();
}
void Device::setProfileDelayed(const std::string& profile) {
run_task([self_weak = _self, profile](){
if (auto self = self_weak.lock())
self->setProfile(profile);
});
}
void Device::removeProfile(const std::string& profile) {
std::unique_lock lock(_profile_mutex);
if (profile == (std::string)_profile_name)
throw std::invalid_argument("cannot remove active profile");
else if (profile == (std::string)_config.default_profile)
throw std::invalid_argument("cannot remove default profile");
_config.profiles.erase(profile);
}
void Device::clearProfile(const std::string& profile) {
std::unique_lock lock(_profile_mutex);
if (profile == (std::string)_profile_name) {
_profile->second = config::Profile();
for (auto& feature : _features)
feature.second->setProfile(_profile->second);
reconfigure();
} else {
auto it = _config.profiles.find(profile);
if (it != _config.profiles.end()) {
it->second = config::Profile();
} else {
throw std::invalid_argument("unknown profile");
}
}
}
config::Profile& Device::activeProfile() {
std::shared_lock lock(_profile_mutex);
return _profile->second;
}
hidpp20::Device& Device::hidpp20() {
return *_hidpp20;
}
void Device::_makeResetMechanism() {
try {
hidpp20::Reset reset(_hidpp20.get());
_reset_mechanism = std::make_unique<std::function<void()>>(
[dev = _hidpp20] {
hidpp20::Reset reset(dev.get());
reset.reset(reset.getProfile());
});
} catch (hidpp20::UnsupportedFeature& e) {
// Reset unsupported, ignore.
}
}
Device::IPC::IPC(Device* device) :
ipcgull::interface(
SERVICE_ROOT_NAME ".Device",
{
{"GetProfiles", {device, &Device::getProfiles, {"profiles"}}},
{"SetProfile", {device, &Device::setProfile, {"profile"}}},
{"RemoveProfile", {device, &Device::removeProfile, {"profile"}}},
{"ClearProfile", {device, &Device::clearProfile, {"profile"}}}
},
{
{"Name", ipcgull::property<std::string>(
ipcgull::property_readable, device->name())},
{"ProductID", ipcgull::property<uint16_t>(
ipcgull::property_readable, device->pid())},
{"Active", device->_awake},
{"DefaultProfile", device->_config.default_profile},
{"ActiveProfile", device->_profile_name}
}, {
{"StatusChanged", ipcgull::signal::make_signal<bool>({"active"})}
}), _device(*device) {
}
void Device::IPC::notifyStatus() const {
emit_signal("StatusChanged", (bool) (_device._awake));
}
config::Device& Device::_getConfig(
const std::shared_ptr<DeviceManager>& manager,
const std::string& name) {
static std::mutex config_mutex;
std::lock_guard<std::mutex> lock(config_mutex);
auto& devices = manager->config()->devices.value();
if (!devices.count(name)) {
devices.emplace(name, config::Device());
}
auto& device = devices.at(name);
if (std::holds_alternative<config::Profile>(device)) {
config::Device d;
d.profiles["default"] = std::get<config::Profile>(device);
d.default_profile = "default";
device = std::move(d);
}
auto& conf = std::get<config::Device>(device);
if (conf.profiles.empty()) {
conf.profiles["default"] = {};
conf.default_profile = "default";
}
return conf;
}

View File

@ -1,187 +1,187 @@
/*
* Copyright 2019-2023 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_DEVICE_H
#define LOGID_DEVICE_H
#include <features/DeviceFeature.h>
#include <backend/hidpp20/Device.h>
#include <backend/hidpp/defs.h>
#include <ipcgull/node.h>
#include <ipcgull/interface.h>
#include <Configuration.h>
namespace logid {
class DeviceManager;
class Device;
class Receiver;
class InputDevice;
class DeviceNickname {
public:
explicit DeviceNickname(const std::shared_ptr<DeviceManager>& manager);
DeviceNickname() = delete;
DeviceNickname(const DeviceNickname&) = delete;
~DeviceNickname();
operator std::string() const;
private:
const int _nickname;
const std::weak_ptr<DeviceManager> _manager;
};
/* TODO: Implement HID++ 1.0 support
* Currently, the logid::Device class has a hardcoded requirement
* for an HID++ 2.0 device.
*/
class Device : public ipcgull::object {
public:
std::string name();
uint16_t pid();
[[nodiscard]] config::Profile& activeProfile();
[[nodiscard]] std::vector<std::string> getProfiles() const;
void setProfile(const std::string& profile);
void setProfileDelayed(const std::string& profile);
void removeProfile(const std::string& profile);
void clearProfile(const std::string& profile);
backend::hidpp20::Device& hidpp20();
static std::shared_ptr<Device> make(
std::string path,
backend::hidpp::DeviceIndex index,
std::shared_ptr<DeviceManager> manager);
static std::shared_ptr<Device> make(
std::shared_ptr<backend::raw::RawDevice> raw_device,
backend::hidpp::DeviceIndex index,
std::shared_ptr<DeviceManager> manager);
static std::shared_ptr<Device> make(
Receiver* receiver,
backend::hidpp::DeviceIndex index,
std::shared_ptr<DeviceManager> manager);
void wakeup();
void sleep();
void reconfigure();
void reset();
[[nodiscard]] std::shared_ptr<InputDevice> virtualInput() const;
[[nodiscard]] std::shared_ptr<ipcgull::node> ipcNode() const;
template<typename T>
std::shared_ptr<T> getFeature(const std::string& name) {
auto it = _features.find(name);
if (it == _features.end())
return nullptr;
try {
return std::dynamic_pointer_cast<T>(it->second);
} catch (std::bad_cast& e) {
return nullptr;
}
}
Device(const Device&) = delete;
Device(Device&&) = delete;
private:
friend class DeviceWrapper;
Device(std::string path, backend::hidpp::DeviceIndex index,
const std::shared_ptr<DeviceManager>& manager);
Device(std::shared_ptr<backend::raw::RawDevice> raw_device,
backend::hidpp::DeviceIndex index,
const std::shared_ptr<DeviceManager>& manager);
Device(Receiver* receiver, backend::hidpp::DeviceIndex index,
const std::shared_ptr<DeviceManager>& manager);
static config::Device& _getConfig(
const std::shared_ptr<DeviceManager>& manager,
const std::string& name);
void _init();
/* Adds a feature without calling an error if unsupported */
template<typename T>
void _addFeature(std::string name) {
try {
_features.emplace(name, features::DeviceFeature::make<T>(this));
} catch (features::UnsupportedFeature& e) {
}
}
std::shared_ptr<backend::hidpp20::Device> _hidpp20;
std::string _path;
backend::hidpp::DeviceIndex _index;
std::map<std::string, std::shared_ptr<features::DeviceFeature>> _features;
config::Device& _config;
mutable std::shared_mutex _profile_mutex;
ipcgull::property<std::string> _profile_name;
std::map<std::string, config::Profile>::iterator _profile;
const std::weak_ptr<DeviceManager> _manager;
void _makeResetMechanism();
std::unique_ptr<std::function<void()>> _reset_mechanism;
const DeviceNickname _nickname;
std::shared_ptr<ipcgull::node> _ipc_node;
class IPC : public ipcgull::interface {
private:
Device& _device;
public:
explicit IPC(Device* device);
void notifyStatus() const;
};
ipcgull::property<bool> _awake;
std::mutex _state_lock;
std::weak_ptr<Device> _self;
std::shared_ptr<IPC> _ipc_interface;
};
}
#endif //LOGID_DEVICE_H
/*
* Copyright 2019-2023 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_DEVICE_H
#define LOGID_DEVICE_H
#include <features/DeviceFeature.h>
#include <backend/hidpp20/Device.h>
#include <backend/hidpp/defs.h>
#include <ipcgull/node.h>
#include <ipcgull/interface.h>
#include <Configuration.h>
namespace logid {
class DeviceManager;
class Device;
class Receiver;
class InputDevice;
class DeviceNickname {
public:
explicit DeviceNickname(const std::shared_ptr<DeviceManager>& manager);
DeviceNickname() = delete;
DeviceNickname(const DeviceNickname&) = delete;
~DeviceNickname();
operator std::string() const;
private:
const int _nickname;
const std::weak_ptr<DeviceManager> _manager;
};
/* TODO: Implement HID++ 1.0 support
* Currently, the logid::Device class has a hardcoded requirement
* for an HID++ 2.0 device.
*/
class Device : public ipcgull::object {
public:
std::string name();
uint16_t pid();
[[nodiscard]] config::Profile& activeProfile();
[[nodiscard]] std::vector<std::string> getProfiles() const;
void setProfile(const std::string& profile);
void setProfileDelayed(const std::string& profile);
void removeProfile(const std::string& profile);
void clearProfile(const std::string& profile);
backend::hidpp20::Device& hidpp20();
static std::shared_ptr<Device> make(
std::string path,
backend::hidpp::DeviceIndex index,
std::shared_ptr<DeviceManager> manager);
static std::shared_ptr<Device> make(
std::shared_ptr<backend::raw::RawDevice> raw_device,
backend::hidpp::DeviceIndex index,
std::shared_ptr<DeviceManager> manager);
static std::shared_ptr<Device> make(
Receiver* receiver,
backend::hidpp::DeviceIndex index,
std::shared_ptr<DeviceManager> manager);
void wakeup();
void sleep();
void reconfigure();
void reset();
[[nodiscard]] std::shared_ptr<InputDevice> virtualInput() const;
[[nodiscard]] std::shared_ptr<ipcgull::node> ipcNode() const;
template<typename T>
std::shared_ptr<T> getFeature(const std::string& name) {
auto it = _features.find(name);
if (it == _features.end())
return nullptr;
try {
return std::dynamic_pointer_cast<T>(it->second);
} catch (std::bad_cast& e) {
return nullptr;
}
}
Device(const Device&) = delete;
Device(Device&&) = delete;
private:
friend class DeviceWrapper;
Device(std::string path, backend::hidpp::DeviceIndex index,
const std::shared_ptr<DeviceManager>& manager);
Device(std::shared_ptr<backend::raw::RawDevice> raw_device,
backend::hidpp::DeviceIndex index,
const std::shared_ptr<DeviceManager>& manager);
Device(Receiver* receiver, backend::hidpp::DeviceIndex index,
const std::shared_ptr<DeviceManager>& manager);
static config::Device& _getConfig(
const std::shared_ptr<DeviceManager>& manager,
const std::string& name);
void _init();
/* Adds a feature without calling an error if unsupported */
template<typename T>
void _addFeature(std::string name) {
try {
_features.emplace(name, features::DeviceFeature::make<T>(this));
} catch (features::UnsupportedFeature& e) {
}
}
std::shared_ptr<backend::hidpp20::Device> _hidpp20;
std::string _path;
backend::hidpp::DeviceIndex _index;
std::map<std::string, std::shared_ptr<features::DeviceFeature>> _features;
config::Device& _config;
mutable std::shared_mutex _profile_mutex;
ipcgull::property<std::string> _profile_name;
std::map<std::string, config::Profile>::iterator _profile;
const std::weak_ptr<DeviceManager> _manager;
void _makeResetMechanism();
std::unique_ptr<std::function<void()>> _reset_mechanism;
const DeviceNickname _nickname;
std::shared_ptr<ipcgull::node> _ipc_node;
class IPC : public ipcgull::interface {
private:
Device& _device;
public:
explicit IPC(Device* device);
void notifyStatus() const;
};
ipcgull::property<bool> _awake;
std::mutex _state_lock;
std::weak_ptr<Device> _self;
std::shared_ptr<IPC> _ipc_interface;
};
}
#endif //LOGID_DEVICE_H

View File

@ -1,320 +1,320 @@
/*
* Copyright 2019-2023 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 <DeviceManager.h>
#include <backend/Error.h>
#include <util/log.h>
#include <thread>
#include <sstream>
#include <utility>
#include <ipc_defs.h>
using namespace logid;
using namespace logid::backend;
DeviceManager::DeviceManager(std::shared_ptr<Configuration> config,
std::shared_ptr<InputDevice> virtual_input,
std::shared_ptr<ipcgull::server> server) :
backend::raw::DeviceMonitor(),
_server(std::move(server)), _config(std::move(config)),
_virtual_input(std::move(virtual_input)),
_root_node(ipcgull::node::make_root("")),
_device_node(ipcgull::node::make_root("devices")),
_receiver_node(ipcgull::node::make_root("receivers")) {
_ipc_devices = _root_node->make_interface<DevicesIPC>(this);
_ipc_receivers = _root_node->make_interface<ReceiversIPC>(this);
_ipc_config = _root_node->make_interface<Configuration::IPC>(_config.get());
_device_node->add_server(_server);
_receiver_node->add_server(_server);
_root_node->add_server(_server);
}
std::shared_ptr<Configuration> DeviceManager::config() const {
return _config;
}
std::shared_ptr<InputDevice> DeviceManager::virtualInput() const {
return _virtual_input;
}
std::shared_ptr<const ipcgull::node> DeviceManager::devicesNode() const {
return _device_node;
}
std::shared_ptr<const ipcgull::node> DeviceManager::receiversNode() const {
return _receiver_node;
}
void DeviceManager::addDevice(std::string path) {
bool defaultExists = true;
bool isReceiver = false;
// Check if device is ignored before continuing
{
auto raw_dev = raw::RawDevice::make(path, self<DeviceManager>().lock());
if (config()->ignore.has_value() &&
config()->ignore.value().contains(raw_dev->productId())) {
logPrintf(DEBUG, "%s: Device 0x%04x ignored.",
path.c_str(), raw_dev->productId());
return;
}
}
try {
auto device = hidpp::Device::make(
path, hidpp::DefaultDevice, self<DeviceManager>().lock(),
config()->io_timeout.value_or(defaults::io_timeout));
isReceiver = device->version() == std::make_tuple(1, 0);
} catch (hidpp20::Error& e) {
if (e.code() != hidpp20::Error::UnknownDevice)
throw DeviceNotReady();
defaultExists = false;
} catch (hidpp10::Error& e) {
if (e.code() != hidpp10::Error::UnknownDevice)
throw DeviceNotReady();
defaultExists = false;
} catch (hidpp::Device::InvalidDevice& e) {
if (e.code() == hidpp::Device::InvalidDevice::VirtualNode) {
logPrintf(DEBUG, "Ignoring virtual node on %s", path.c_str());
} else if (e.code() == hidpp::Device::InvalidDevice::Asleep) {
/* May be a valid device, wait */
throw DeviceNotReady();
}
return;
} catch (std::system_error& e) {
logPrintf(WARN, "I/O error on %s: %s, skipping device.", path.c_str(), e.what());
return;
} catch (TimeoutError& e) {
/* Ready and valid non-default devices should throw an UnknownDevice error */
throw DeviceNotReady();
}
if (isReceiver) {
logPrintf(INFO, "Detected receiver at %s", path.c_str());
auto receiver = Receiver::make(path, self<DeviceManager>().lock());
std::lock_guard<std::mutex> lock(_map_lock);
_receivers.emplace(path, receiver);
_ipc_receivers->receiverAdded(receiver);
} else {
/* TODO: Can non-receivers only contain 1 device?
* If the device exists, it is guaranteed to be an HID++ 2.0 device */
if (defaultExists) {
auto device = Device::make(path, hidpp::DefaultDevice, self<DeviceManager>().lock());
std::lock_guard<std::mutex> lock(_map_lock);
_devices.emplace(path, device);
_ipc_devices->deviceAdded(device);
} else {
try {
auto device = Device::make(path, hidpp::CordedDevice, self<DeviceManager>().lock());
std::lock_guard<std::mutex> lock(_map_lock);
_devices.emplace(path, device);
_ipc_devices->deviceAdded(device);
} catch (hidpp10::Error& e) {
if (e.code() != hidpp10::Error::UnknownDevice)
throw DeviceNotReady();
} catch (hidpp20::Error& e) {
if (e.code() != hidpp20::Error::UnknownDevice)
throw DeviceNotReady();
} catch (hidpp::Device::InvalidDevice& e) {
if (e.code() == hidpp::Device::InvalidDevice::Asleep)
throw DeviceNotReady();
} catch (std::system_error& e) {
// This error should have been thrown previously
logPrintf(WARN, "I/O error on %s: %s", path.c_str(), e.what());
} catch (TimeoutError& e) {
throw DeviceNotReady();
}
}
}
}
void DeviceManager::addExternalDevice(const std::shared_ptr<Device>& d) {
_ipc_devices->deviceAdded(d);
}
void DeviceManager::removeExternalDevice(const std::shared_ptr<Device>& d) {
_ipc_devices->deviceRemoved(d);
}
std::mutex& DeviceManager::mutex() const {
return _map_lock;
}
void DeviceManager::removeDevice(std::string path) {
std::lock_guard<std::mutex> lock(_map_lock);
auto receiver = _receivers.find(path);
if (receiver != _receivers.end()) {
_ipc_receivers->receiverRemoved(receiver->second);
_receivers.erase(receiver);
logPrintf(INFO, "Receiver on %s disconnected", path.c_str());
} else {
auto device = _devices.find(path);
if (device != _devices.end()) {
_ipc_devices->deviceRemoved(device->second);
_devices.erase(device);
logPrintf(INFO, "Device on %s disconnected", path.c_str());
}
}
}
DeviceManager::DevicesIPC::DevicesIPC(DeviceManager* manager) :
ipcgull::interface(
SERVICE_ROOT_NAME ".Devices",
{
{"Enumerate", {manager, &DeviceManager::listDevices, {"devices"}}}
},
{},
{
{"DeviceAdded",
ipcgull::make_signal<std::shared_ptr<Device>>(
{"device"})},
{"DeviceRemoved",
ipcgull::make_signal<std::shared_ptr<Device>>(
{"device"})}
}) {
}
std::vector<std::shared_ptr<Device>> DeviceManager::listDevices() const {
std::lock_guard<std::mutex> lock(_map_lock);
std::vector<std::shared_ptr<Device>> devices;
for (auto& x: _devices)
devices.emplace_back(x.second);
for (auto& x: _receivers) {
for (auto& d: x.second->devices())
devices.emplace_back(d.second);
}
return devices;
}
std::vector<std::shared_ptr<Receiver>> DeviceManager::listReceivers() const {
std::lock_guard<std::mutex> lock(_map_lock);
std::vector<std::shared_ptr<Receiver>> receivers;
for (auto& x: _receivers)
receivers.emplace_back(x.second);
return receivers;
}
void DeviceManager::DevicesIPC::deviceAdded(
const std::shared_ptr<Device>& d) {
emit_signal("DeviceAdded", d);
}
void DeviceManager::DevicesIPC::deviceRemoved(
const std::shared_ptr<Device>& d) {
emit_signal("DeviceRemoved", d);
}
DeviceManager::ReceiversIPC::ReceiversIPC(DeviceManager* manager) :
ipcgull::interface(
SERVICE_ROOT_NAME ".Receivers",
{
{"Enumerate", {manager, &DeviceManager::listReceivers,
{"receivers"}}}
},
{},
{
{"ReceiverAdded",
ipcgull::make_signal<std::shared_ptr<Receiver>>(
{"receiver"})},
{"ReceiverRemoved",
ipcgull::make_signal<std::shared_ptr<Receiver>>(
{"receiver"})}
}) {
}
void DeviceManager::ReceiversIPC::receiverAdded(
const std::shared_ptr<Receiver>& r) {
emit_signal("ReceiverAdded", r);
}
void DeviceManager::ReceiversIPC::receiverRemoved(
const std::shared_ptr<Receiver>& r) {
emit_signal("ReceiverRemoved", r);
}
int DeviceManager::newDeviceNickname() {
std::lock_guard<std::mutex> lock(_nick_lock);
auto begin = _device_nicknames.begin();
if (begin != _device_nicknames.end()) {
if (*begin != 0) {
_device_nicknames.insert(0);
return 0;
}
}
const auto i = std::adjacent_find(_device_nicknames.begin(),
_device_nicknames.end(),
[](int l, int r) { return l + 1 < r; });
if (i == _device_nicknames.end()) {
auto end = _device_nicknames.rbegin();
if (end != _device_nicknames.rend()) {
auto ret = *end + 1;
assert(ret > 0);
_device_nicknames.insert(ret);
return ret;
} else {
_device_nicknames.insert(0);
return 0;
}
}
auto ret = *i + 1;
assert(ret > 0);
_device_nicknames.insert(ret);
return ret;
}
int DeviceManager::newReceiverNickname() {
std::lock_guard<std::mutex> lock(_nick_lock);
auto begin = _receiver_nicknames.begin();
if (begin != _receiver_nicknames.end()) {
if (*begin != 0) {
_receiver_nicknames.insert(0);
return 0;
}
}
const auto i = std::adjacent_find(_receiver_nicknames.begin(),
_receiver_nicknames.end(),
[](int l, int r) { return l + 1 < r; });
if (i == _receiver_nicknames.end()) {
auto end = _receiver_nicknames.rbegin();
if (end != _receiver_nicknames.rend()) {
auto ret = *end + 1;
assert(ret > 0);
_receiver_nicknames.insert(ret);
return ret;
} else {
_receiver_nicknames.insert(0);
return 0;
}
}
auto ret = *i + 1;
assert(ret > 0);
_receiver_nicknames.insert(ret);
return ret;
}
/*
* Copyright 2019-2023 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 <DeviceManager.h>
#include <backend/Error.h>
#include <util/log.h>
#include <thread>
#include <sstream>
#include <utility>
#include <ipc_defs.h>
using namespace logid;
using namespace logid::backend;
DeviceManager::DeviceManager(std::shared_ptr<Configuration> config,
std::shared_ptr<InputDevice> virtual_input,
std::shared_ptr<ipcgull::server> server) :
backend::raw::DeviceMonitor(),
_server(std::move(server)), _config(std::move(config)),
_virtual_input(std::move(virtual_input)),
_root_node(ipcgull::node::make_root("")),
_device_node(ipcgull::node::make_root("devices")),
_receiver_node(ipcgull::node::make_root("receivers")) {
_ipc_devices = _root_node->make_interface<DevicesIPC>(this);
_ipc_receivers = _root_node->make_interface<ReceiversIPC>(this);
_ipc_config = _root_node->make_interface<Configuration::IPC>(_config.get());
_device_node->add_server(_server);
_receiver_node->add_server(_server);
_root_node->add_server(_server);
}
std::shared_ptr<Configuration> DeviceManager::config() const {
return _config;
}
std::shared_ptr<InputDevice> DeviceManager::virtualInput() const {
return _virtual_input;
}
std::shared_ptr<const ipcgull::node> DeviceManager::devicesNode() const {
return _device_node;
}
std::shared_ptr<const ipcgull::node> DeviceManager::receiversNode() const {
return _receiver_node;
}
void DeviceManager::addDevice(std::string path) {
bool defaultExists = true;
bool isReceiver = false;
// Check if device is ignored before continuing
{
auto raw_dev = raw::RawDevice::make(path, self<DeviceManager>().lock());
if (config()->ignore.has_value() &&
config()->ignore.value().contains(raw_dev->productId())) {
logPrintf(DEBUG, "%s: Device 0x%04x ignored.",
path.c_str(), raw_dev->productId());
return;
}
}
try {
auto device = hidpp::Device::make(
path, hidpp::DefaultDevice, self<DeviceManager>().lock(),
config()->io_timeout.value_or(defaults::io_timeout));
isReceiver = device->version() == std::make_tuple(1, 0);
} catch (hidpp20::Error& e) {
if (e.code() != hidpp20::Error::UnknownDevice)
throw DeviceNotReady();
defaultExists = false;
} catch (hidpp10::Error& e) {
if (e.code() != hidpp10::Error::UnknownDevice)
throw DeviceNotReady();
defaultExists = false;
} catch (hidpp::Device::InvalidDevice& e) {
if (e.code() == hidpp::Device::InvalidDevice::VirtualNode) {
logPrintf(DEBUG, "Ignoring virtual node on %s", path.c_str());
} else if (e.code() == hidpp::Device::InvalidDevice::Asleep) {
/* May be a valid device, wait */
throw DeviceNotReady();
}
return;
} catch (std::system_error& e) {
logPrintf(WARN, "I/O error on %s: %s, skipping device.", path.c_str(), e.what());
return;
} catch (TimeoutError& e) {
/* Ready and valid non-default devices should throw an UnknownDevice error */
throw DeviceNotReady();
}
if (isReceiver) {
logPrintf(INFO, "Detected receiver at %s", path.c_str());
auto receiver = Receiver::make(path, self<DeviceManager>().lock());
std::lock_guard<std::mutex> lock(_map_lock);
_receivers.emplace(path, receiver);
_ipc_receivers->receiverAdded(receiver);
} else {
/* TODO: Can non-receivers only contain 1 device?
* If the device exists, it is guaranteed to be an HID++ 2.0 device */
if (defaultExists) {
auto device = Device::make(path, hidpp::DefaultDevice, self<DeviceManager>().lock());
std::lock_guard<std::mutex> lock(_map_lock);
_devices.emplace(path, device);
_ipc_devices->deviceAdded(device);
} else {
try {
auto device = Device::make(path, hidpp::CordedDevice, self<DeviceManager>().lock());
std::lock_guard<std::mutex> lock(_map_lock);
_devices.emplace(path, device);
_ipc_devices->deviceAdded(device);
} catch (hidpp10::Error& e) {
if (e.code() != hidpp10::Error::UnknownDevice)
throw DeviceNotReady();
} catch (hidpp20::Error& e) {
if (e.code() != hidpp20::Error::UnknownDevice)
throw DeviceNotReady();
} catch (hidpp::Device::InvalidDevice& e) {
if (e.code() == hidpp::Device::InvalidDevice::Asleep)
throw DeviceNotReady();
} catch (std::system_error& e) {
// This error should have been thrown previously
logPrintf(WARN, "I/O error on %s: %s", path.c_str(), e.what());
} catch (TimeoutError& e) {
throw DeviceNotReady();
}
}
}
}
void DeviceManager::addExternalDevice(const std::shared_ptr<Device>& d) {
_ipc_devices->deviceAdded(d);
}
void DeviceManager::removeExternalDevice(const std::shared_ptr<Device>& d) {
_ipc_devices->deviceRemoved(d);
}
std::mutex& DeviceManager::mutex() const {
return _map_lock;
}
void DeviceManager::removeDevice(std::string path) {
std::lock_guard<std::mutex> lock(_map_lock);
auto receiver = _receivers.find(path);
if (receiver != _receivers.end()) {
_ipc_receivers->receiverRemoved(receiver->second);
_receivers.erase(receiver);
logPrintf(INFO, "Receiver on %s disconnected", path.c_str());
} else {
auto device = _devices.find(path);
if (device != _devices.end()) {
_ipc_devices->deviceRemoved(device->second);
_devices.erase(device);
logPrintf(INFO, "Device on %s disconnected", path.c_str());
}
}
}
DeviceManager::DevicesIPC::DevicesIPC(DeviceManager* manager) :
ipcgull::interface(
SERVICE_ROOT_NAME ".Devices",
{
{"Enumerate", {manager, &DeviceManager::listDevices, {"devices"}}}
},
{},
{
{"DeviceAdded",
ipcgull::make_signal<std::shared_ptr<Device>>(
{"device"})},
{"DeviceRemoved",
ipcgull::make_signal<std::shared_ptr<Device>>(
{"device"})}
}) {
}
std::vector<std::shared_ptr<Device>> DeviceManager::listDevices() const {
std::lock_guard<std::mutex> lock(_map_lock);
std::vector<std::shared_ptr<Device>> devices;
for (auto& x: _devices)
devices.emplace_back(x.second);
for (auto& x: _receivers) {
for (auto& d: x.second->devices())
devices.emplace_back(d.second);
}
return devices;
}
std::vector<std::shared_ptr<Receiver>> DeviceManager::listReceivers() const {
std::lock_guard<std::mutex> lock(_map_lock);
std::vector<std::shared_ptr<Receiver>> receivers;
for (auto& x: _receivers)
receivers.emplace_back(x.second);
return receivers;
}
void DeviceManager::DevicesIPC::deviceAdded(
const std::shared_ptr<Device>& d) {
emit_signal("DeviceAdded", d);
}
void DeviceManager::DevicesIPC::deviceRemoved(
const std::shared_ptr<Device>& d) {
emit_signal("DeviceRemoved", d);
}
DeviceManager::ReceiversIPC::ReceiversIPC(DeviceManager* manager) :
ipcgull::interface(
SERVICE_ROOT_NAME ".Receivers",
{
{"Enumerate", {manager, &DeviceManager::listReceivers,
{"receivers"}}}
},
{},
{
{"ReceiverAdded",
ipcgull::make_signal<std::shared_ptr<Receiver>>(
{"receiver"})},
{"ReceiverRemoved",
ipcgull::make_signal<std::shared_ptr<Receiver>>(
{"receiver"})}
}) {
}
void DeviceManager::ReceiversIPC::receiverAdded(
const std::shared_ptr<Receiver>& r) {
emit_signal("ReceiverAdded", r);
}
void DeviceManager::ReceiversIPC::receiverRemoved(
const std::shared_ptr<Receiver>& r) {
emit_signal("ReceiverRemoved", r);
}
int DeviceManager::newDeviceNickname() {
std::lock_guard<std::mutex> lock(_nick_lock);
auto begin = _device_nicknames.begin();
if (begin != _device_nicknames.end()) {
if (*begin != 0) {
_device_nicknames.insert(0);
return 0;
}
}
const auto i = std::adjacent_find(_device_nicknames.begin(),
_device_nicknames.end(),
[](int l, int r) { return l + 1 < r; });
if (i == _device_nicknames.end()) {
auto end = _device_nicknames.rbegin();
if (end != _device_nicknames.rend()) {
auto ret = *end + 1;
assert(ret > 0);
_device_nicknames.insert(ret);
return ret;
} else {
_device_nicknames.insert(0);
return 0;
}
}
auto ret = *i + 1;
assert(ret > 0);
_device_nicknames.insert(ret);
return ret;
}
int DeviceManager::newReceiverNickname() {
std::lock_guard<std::mutex> lock(_nick_lock);
auto begin = _receiver_nicknames.begin();
if (begin != _receiver_nicknames.end()) {
if (*begin != 0) {
_receiver_nicknames.insert(0);
return 0;
}
}
const auto i = std::adjacent_find(_receiver_nicknames.begin(),
_receiver_nicknames.end(),
[](int l, int r) { return l + 1 < r; });
if (i == _receiver_nicknames.end()) {
auto end = _receiver_nicknames.rbegin();
if (end != _receiver_nicknames.rend()) {
auto ret = *end + 1;
assert(ret > 0);
_receiver_nicknames.insert(ret);
return ret;
} else {
_receiver_nicknames.insert(0);
return 0;
}
}
auto ret = *i + 1;
assert(ret > 0);
_receiver_nicknames.insert(ret);
return ret;
}

View File

@ -1,116 +1,116 @@
/*
* Copyright 2019-2023 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_DEVICEMANAGER_H
#define LOGID_DEVICEMANAGER_H
#include <backend/raw/DeviceMonitor.h>
#include <Device.h>
#include <Receiver.h>
#include <ipcgull/node.h>
#include <ipcgull/interface.h>
namespace logid {
class InputDevice;
class DeviceManager : public backend::raw::DeviceMonitor {
public:
[[nodiscard]] std::shared_ptr<Configuration> config() const;
[[nodiscard]] std::shared_ptr<InputDevice> virtualInput() const;
[[nodiscard]] std::shared_ptr<const ipcgull::node> devicesNode() const;
[[nodiscard]] std::shared_ptr<const ipcgull::node>
receiversNode() const;
void addExternalDevice(const std::shared_ptr<Device>& d);
void removeExternalDevice(const std::shared_ptr<Device>& d);
std::mutex& mutex() const;
protected:
DeviceManager(std::shared_ptr<Configuration> config,
std::shared_ptr<InputDevice> virtual_input,
std::shared_ptr<ipcgull::server> server);
void addDevice(std::string path) final;
void removeDevice(std::string path) final;
private:
class DevicesIPC : public ipcgull::interface {
public:
explicit DevicesIPC(DeviceManager* manager);
void deviceAdded(const std::shared_ptr<Device>& d);
void deviceRemoved(const std::shared_ptr<Device>& d);
};
[[nodiscard]]
std::vector<std::shared_ptr<Device>> listDevices() const;
class ReceiversIPC : public ipcgull::interface {
public:
explicit ReceiversIPC(DeviceManager* manager);
void receiverAdded(const std::shared_ptr<Receiver>& r);
void receiverRemoved(const std::shared_ptr<Receiver>& r);
};
[[nodiscard]]
std::vector<std::shared_ptr<Receiver>> listReceivers() const;
std::shared_ptr<ipcgull::server> _server;
std::shared_ptr<Configuration> _config;
std::shared_ptr<InputDevice> _virtual_input;
std::shared_ptr<ipcgull::node> _root_node;
std::shared_ptr<ipcgull::node> _device_node;
std::shared_ptr<ipcgull::node> _receiver_node;
std::shared_ptr<Configuration::IPC> _ipc_config;
std::shared_ptr<DevicesIPC> _ipc_devices;
std::shared_ptr<ReceiversIPC> _ipc_receivers;
std::map<std::string, std::shared_ptr<Device>> _devices;
std::map<std::string, std::shared_ptr<Receiver>> _receivers;
mutable std::mutex _map_lock;
friend class DeviceNickname;
friend class ReceiverNickname;
[[nodiscard]] int newDeviceNickname();
[[nodiscard]] int newReceiverNickname();
std::mutex _nick_lock;
std::set<int> _device_nicknames;
std::set<int> _receiver_nicknames;
};
}
#endif //LOGID_DEVICEMANAGER_H
/*
* Copyright 2019-2023 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_DEVICEMANAGER_H
#define LOGID_DEVICEMANAGER_H
#include <backend/raw/DeviceMonitor.h>
#include <Device.h>
#include <Receiver.h>
#include <ipcgull/node.h>
#include <ipcgull/interface.h>
namespace logid {
class InputDevice;
class DeviceManager : public backend::raw::DeviceMonitor {
public:
[[nodiscard]] std::shared_ptr<Configuration> config() const;
[[nodiscard]] std::shared_ptr<InputDevice> virtualInput() const;
[[nodiscard]] std::shared_ptr<const ipcgull::node> devicesNode() const;
[[nodiscard]] std::shared_ptr<const ipcgull::node>
receiversNode() const;
void addExternalDevice(const std::shared_ptr<Device>& d);
void removeExternalDevice(const std::shared_ptr<Device>& d);
std::mutex& mutex() const;
protected:
DeviceManager(std::shared_ptr<Configuration> config,
std::shared_ptr<InputDevice> virtual_input,
std::shared_ptr<ipcgull::server> server);
void addDevice(std::string path) final;
void removeDevice(std::string path) final;
private:
class DevicesIPC : public ipcgull::interface {
public:
explicit DevicesIPC(DeviceManager* manager);
void deviceAdded(const std::shared_ptr<Device>& d);
void deviceRemoved(const std::shared_ptr<Device>& d);
};
[[nodiscard]]
std::vector<std::shared_ptr<Device>> listDevices() const;
class ReceiversIPC : public ipcgull::interface {
public:
explicit ReceiversIPC(DeviceManager* manager);
void receiverAdded(const std::shared_ptr<Receiver>& r);
void receiverRemoved(const std::shared_ptr<Receiver>& r);
};
[[nodiscard]]
std::vector<std::shared_ptr<Receiver>> listReceivers() const;
std::shared_ptr<ipcgull::server> _server;
std::shared_ptr<Configuration> _config;
std::shared_ptr<InputDevice> _virtual_input;
std::shared_ptr<ipcgull::node> _root_node;
std::shared_ptr<ipcgull::node> _device_node;
std::shared_ptr<ipcgull::node> _receiver_node;
std::shared_ptr<Configuration::IPC> _ipc_config;
std::shared_ptr<DevicesIPC> _ipc_devices;
std::shared_ptr<ReceiversIPC> _ipc_receivers;
std::map<std::string, std::shared_ptr<Device>> _devices;
std::map<std::string, std::shared_ptr<Receiver>> _receivers;
mutable std::mutex _map_lock;
friend class DeviceNickname;
friend class ReceiverNickname;
[[nodiscard]] int newDeviceNickname();
[[nodiscard]] int newReceiverNickname();
std::mutex _nick_lock;
std::set<int> _device_nicknames;
std::set<int> _receiver_nicknames;
};
}
#endif //LOGID_DEVICEMANAGER_H

View File

@ -1,183 +1,183 @@
/*
* Copyright 2019-2023 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 <InputDevice.h>
#include <system_error>
#include <mutex>
extern "C"
{
#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h>
}
using namespace logid;
InputDevice::InvalidEventCode::InvalidEventCode(const std::string& name) :
_what("Invalid event code " + name) {
}
InputDevice::InvalidEventCode::InvalidEventCode(uint code) :
_what("Invalid event code " + std::to_string(code)) {
}
const char* InputDevice::InvalidEventCode::what() const noexcept {
return _what.c_str();
}
InputDevice::InputDevice(const char* name) {
device = libevdev_new();
libevdev_set_name(device, name);
libevdev_enable_event_type(device, EV_KEY);
for (unsigned int i = 0; i < KEY_CNT; i++) {
// Enable some keys which a normal keyboard should have
// by default, i.e. a-z, modifier keys and so on, see:
// /usr/include/linux/input-event-codes.h
if (i < 128) {
registered_keys[i] = true;
libevdev_enable_event_code(device, EV_KEY, i, nullptr);
} else {
registered_keys[i] = false;
}
}
for (bool& axis: registered_axis)
axis = false;
libevdev_enable_event_type(device, EV_REL);
int err = libevdev_uinput_create_from_device(device,
LIBEVDEV_UINPUT_OPEN_MANAGED, &ui_device);
if (err != 0) {
libevdev_free(device);
throw std::system_error(-err, std::generic_category());
}
}
InputDevice::~InputDevice() {
libevdev_uinput_destroy(ui_device);
libevdev_free(device);
}
void InputDevice::registerKey(uint code) {
// TODO: Maybe print error message, if wrong code is passed?
if (code >= KEY_CNT || registered_keys[code]) {
return;
}
_enableEvent(EV_KEY, code);
registered_keys[code] = true;
}
void InputDevice::registerAxis(uint axis) {
// TODO: Maybe print error message, if wrong code is passed?
if (axis >= REL_CNT || registered_axis[axis]) {
return;
}
_enableEvent(EV_REL, axis);
registered_axis[axis] = true;
}
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);
}
std::string InputDevice::toKeyName(uint code) {
return _toEventName(EV_KEY, code);
}
uint InputDevice::toKeyCode(const std::string& name) {
return _toEventCode(EV_KEY, name);
}
std::string InputDevice::toAxisName(uint code) {
return _toEventName(EV_REL, code);
}
uint InputDevice::toAxisCode(const std::string& name) {
return _toEventCode(EV_REL, name);
}
/* Returns -1 if axis_code is not hi-res */
int InputDevice::getLowResAxis(const uint axis_code) {
/* Some systems don't have these hi-res axes */
#ifdef REL_WHEEL_HI_RES
if (axis_code == REL_WHEEL_HI_RES)
return REL_WHEEL;
#endif
#ifdef REL_HWHEEL_HI_RES
if (axis_code == REL_HWHEEL_HI_RES)
return REL_HWHEEL;
#endif
return -1;
}
std::string InputDevice::_toEventName(uint type, uint code) {
const char* ret = libevdev_event_code_get_name(type, code);
if (!ret)
throw InvalidEventCode(code);
return {ret};
}
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::_enableEvent(const uint type, const uint code) {
std::unique_lock lock(_input_mutex);
libevdev_uinput_destroy(ui_device);
libevdev_enable_event_code(device, type, code, nullptr);
int err = libevdev_uinput_create_from_device(device,
LIBEVDEV_UINPUT_OPEN_MANAGED, &ui_device);
if (err != 0) {
libevdev_free(device);
device = nullptr;
ui_device = nullptr;
throw std::system_error(-err, std::generic_category());
}
}
void InputDevice::_sendEvent(uint type, uint code, int value) {
std::unique_lock lock(_input_mutex);
libevdev_uinput_write_event(ui_device, type, code, value);
libevdev_uinput_write_event(ui_device, EV_SYN, SYN_REPORT, 0);
}
/*
* Copyright 2019-2023 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 <InputDevice.h>
#include <system_error>
#include <mutex>
extern "C"
{
#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h>
}
using namespace logid;
InputDevice::InvalidEventCode::InvalidEventCode(const std::string& name) :
_what("Invalid event code " + name) {
}
InputDevice::InvalidEventCode::InvalidEventCode(uint code) :
_what("Invalid event code " + std::to_string(code)) {
}
const char* InputDevice::InvalidEventCode::what() const noexcept {
return _what.c_str();
}
InputDevice::InputDevice(const char* name) {
device = libevdev_new();
libevdev_set_name(device, name);
libevdev_enable_event_type(device, EV_KEY);
for (unsigned int i = 0; i < KEY_CNT; i++) {
// Enable some keys which a normal keyboard should have
// by default, i.e. a-z, modifier keys and so on, see:
// /usr/include/linux/input-event-codes.h
if (i < 128) {
registered_keys[i] = true;
libevdev_enable_event_code(device, EV_KEY, i, nullptr);
} else {
registered_keys[i] = false;
}
}
for (bool& axis: registered_axis)
axis = false;
libevdev_enable_event_type(device, EV_REL);
int err = libevdev_uinput_create_from_device(device,
LIBEVDEV_UINPUT_OPEN_MANAGED, &ui_device);
if (err != 0) {
libevdev_free(device);
throw std::system_error(-err, std::generic_category());
}
}
InputDevice::~InputDevice() {
libevdev_uinput_destroy(ui_device);
libevdev_free(device);
}
void InputDevice::registerKey(uint code) {
// TODO: Maybe print error message, if wrong code is passed?
if (code >= KEY_CNT || registered_keys[code]) {
return;
}
_enableEvent(EV_KEY, code);
registered_keys[code] = true;
}
void InputDevice::registerAxis(uint axis) {
// TODO: Maybe print error message, if wrong code is passed?
if (axis >= REL_CNT || registered_axis[axis]) {
return;
}
_enableEvent(EV_REL, axis);
registered_axis[axis] = true;
}
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);
}
std::string InputDevice::toKeyName(uint code) {
return _toEventName(EV_KEY, code);
}
uint InputDevice::toKeyCode(const std::string& name) {
return _toEventCode(EV_KEY, name);
}
std::string InputDevice::toAxisName(uint code) {
return _toEventName(EV_REL, code);
}
uint InputDevice::toAxisCode(const std::string& name) {
return _toEventCode(EV_REL, name);
}
/* Returns -1 if axis_code is not hi-res */
int InputDevice::getLowResAxis(const uint axis_code) {
/* Some systems don't have these hi-res axes */
#ifdef REL_WHEEL_HI_RES
if (axis_code == REL_WHEEL_HI_RES)
return REL_WHEEL;
#endif
#ifdef REL_HWHEEL_HI_RES
if (axis_code == REL_HWHEEL_HI_RES)
return REL_HWHEEL;
#endif
return -1;
}
std::string InputDevice::_toEventName(uint type, uint code) {
const char* ret = libevdev_event_code_get_name(type, code);
if (!ret)
throw InvalidEventCode(code);
return {ret};
}
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::_enableEvent(const uint type, const uint code) {
std::unique_lock lock(_input_mutex);
libevdev_uinput_destroy(ui_device);
libevdev_enable_event_code(device, type, code, nullptr);
int err = libevdev_uinput_create_from_device(device,
LIBEVDEV_UINPUT_OPEN_MANAGED, &ui_device);
if (err != 0) {
libevdev_free(device);
device = nullptr;
ui_device = nullptr;
throw std::system_error(-err, std::generic_category());
}
}
void InputDevice::_sendEvent(uint type, uint code, int value) {
std::unique_lock lock(_input_mutex);
libevdev_uinput_write_event(ui_device, type, code, value);
libevdev_uinput_write_event(ui_device, EV_SYN, SYN_REPORT, 0);
}

View File

@ -1,89 +1,89 @@
/*
* Copyright 2019-2023 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>
#include <string>
#include <mutex>
extern "C"
{
#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h>
}
namespace logid {
class InputDevice {
public:
class InvalidEventCode : public std::exception {
public:
explicit InvalidEventCode(const std::string& name);
explicit InvalidEventCode(uint code);
const char* what() const noexcept override;
private:
const std::string _what;
};
explicit InputDevice(const char* name);
~InputDevice();
void registerKey(uint code);
void registerAxis(uint axis);
void moveAxis(uint axis, int movement);
void pressKey(uint code);
void releaseKey(uint code);
static std::string toKeyName(uint code);
static uint toKeyCode(const std::string& name);
static std::string toAxisName(uint code);
static uint toAxisCode(const std::string& name);
static int getLowResAxis(uint axis_code);
private:
void _sendEvent(uint type, uint code, int value);
void _enableEvent(uint type, uint name);
static std::string _toEventName(uint type, uint code);
static uint _toEventCode(uint type, const std::string& name);
bool registered_keys[KEY_CNT]{};
bool registered_axis[REL_CNT]{};
libevdev* device;
libevdev_uinput* ui_device{};
std::mutex _input_mutex;
};
}
#endif //LOGID_INPUTDEVICE_H
/*
* Copyright 2019-2023 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>
#include <string>
#include <mutex>
extern "C"
{
#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h>
}
namespace logid {
class InputDevice {
public:
class InvalidEventCode : public std::exception {
public:
explicit InvalidEventCode(const std::string& name);
explicit InvalidEventCode(uint code);
const char* what() const noexcept override;
private:
const std::string _what;
};
explicit InputDevice(const char* name);
~InputDevice();
void registerKey(uint code);
void registerAxis(uint axis);
void moveAxis(uint axis, int movement);
void pressKey(uint code);
void releaseKey(uint code);
static std::string toKeyName(uint code);
static uint toKeyCode(const std::string& name);
static std::string toAxisName(uint code);
static uint toAxisCode(const std::string& name);
static int getLowResAxis(uint axis_code);
private:
void _sendEvent(uint type, uint code, int value);
void _enableEvent(uint type, uint name);
static std::string _toEventName(uint type, uint code);
static uint _toEventCode(uint type, const std::string& name);
bool registered_keys[KEY_CNT]{};
bool registered_axis[REL_CNT]{};
libevdev* device;
libevdev_uinput* ui_device{};
std::mutex _input_mutex;
};
}
#endif //LOGID_INPUTDEVICE_H

View File

@ -1,234 +1,234 @@
/*
* Copyright 2019-2023 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 <Receiver.h>
#include <DeviceManager.h>
#include <backend/Error.h>
#include <util/log.h>
#include <ipc_defs.h>
using namespace logid;
using namespace logid::backend;
ReceiverNickname::ReceiverNickname(
const std::shared_ptr<DeviceManager>& manager) :
_nickname(manager->newReceiverNickname()), _manager(manager) {
}
ReceiverNickname::operator std::string() const {
return std::to_string(_nickname);
}
ReceiverNickname::~ReceiverNickname() {
if (auto manager = _manager.lock()) {
std::lock_guard<std::mutex> lock(manager->_nick_lock);
manager->_receiver_nicknames.erase(_nickname);
}
}
std::shared_ptr<Receiver> Receiver::make(
const std::string& path,
const std::shared_ptr<DeviceManager>& manager) {
auto ret = ReceiverMonitor::make<Receiver>(path, manager);
ret->_ipc_node->manage(ret);
return ret;
}
Receiver::Receiver(const std::string& path,
const std::shared_ptr<DeviceManager>& manager) :
hidpp10::ReceiverMonitor(path, manager,
manager->config()->io_timeout.value_or(
defaults::io_timeout)),
_path(path), _manager(manager), _nickname(manager),
_ipc_node(manager->receiversNode()->make_child(_nickname)),
_ipc_interface(_ipc_node->make_interface<IPC>(this)) {
}
const Receiver::DeviceList& Receiver::devices() const {
return _devices;
}
Receiver::~Receiver() noexcept {
if (auto manager = _manager.lock()) {
for (auto& d: _devices)
manager->removeExternalDevice(d.second);
}
}
void Receiver::addDevice(hidpp::DeviceConnectionEvent event) {
std::unique_lock<std::mutex> lock(_devices_change);
auto manager = _manager.lock();
if (!manager) {
logPrintf(ERROR, "Orphan Receiver, missing DeviceManager");
logPrintf(ERROR, "Fatal error, file a bug report. Program will now exit.");
std::terminate();
}
try {
// Check if device is ignored before continuing
if (manager->config()->ignore.value_or(std::set<uint16_t>()).contains(event.pid)) {
logPrintf(DEBUG, "%s:%d: Device 0x%04x ignored.",
_path.c_str(), event.index, event.pid);
return;
}
auto dev = _devices.find(event.index);
if (dev != _devices.end()) {
if (event.linkEstablished)
dev->second->wakeup();
else
dev->second->sleep();
return;
}
if (!event.linkEstablished)
return;
auto hidpp_device = hidpp::Device::make(
receiver(), event, manager->config()->io_timeout.value_or(defaults::io_timeout));
auto version = hidpp_device->version();
if (std::get<0>(version) < 2) {
logPrintf(INFO, "Unsupported HID++ 1.0 device on %s:%d connected.",
_path.c_str(), event.index);
return;
}
hidpp_device.reset();
auto device = Device::make(this, event.index, manager);
std::lock_guard<std::mutex> manager_lock(manager->mutex());
_devices.emplace(event.index, device);
manager->addExternalDevice(device);
} catch (hidpp10::Error& e) {
logPrintf(ERROR, "Caught HID++ 1.0 error while trying to initialize %s:%d: %s",
_path.c_str(), event.index, e.what());
} catch (hidpp20::Error& e) {
logPrintf(ERROR, "Caught HID++ 2.0 error while trying to initialize "
"%s:%d: %s", _path.c_str(), event.index, e.what());
} catch (TimeoutError& e) {
if (!event.fromTimeoutCheck)
logPrintf(DEBUG, "%s:%d timed out, waiting for input from device to"
" initialize.", _path.c_str(), event.index);
waitForDevice(event.index);
}
}
void Receiver::removeDevice(hidpp::DeviceIndex index) {
std::unique_lock<std::mutex> lock(_devices_change);
std::unique_lock<std::mutex> manager_lock;
if (auto manager = _manager.lock())
manager_lock = std::unique_lock<std::mutex>(manager->mutex());
auto device = _devices.find(index);
if (device != _devices.end()) {
if (auto manager = _manager.lock())
manager->removeExternalDevice(device->second);
_devices.erase(device);
}
}
void Receiver::pairReady(const hidpp10::DeviceDiscoveryEvent& event,
const std::string& passkey) {
std::string type;
switch (event.deviceType) {
case hidpp::DeviceUnknown:
type = "unknown";
break;
case hidpp::DeviceKeyboard:
type = "keyboard";
break;
case hidpp::DeviceMouse:
type = "mouse";
break;
case hidpp::DeviceNumpad:
type = "numpad";
break;
case hidpp::DevicePresenter:
type = "presenter";
break;
case hidpp::DeviceTouchpad:
type = "touchpad";
break;
case hidpp::DeviceTrackball:
type = "trackball";
break;
}
_ipc_interface->emit_signal("PairReady", event.name, event.pid, type, passkey);
}
const std::string& Receiver::path() const {
return _path;
}
std::shared_ptr<hidpp10::Receiver> Receiver::rawReceiver() {
return receiver();
}
std::vector<std::tuple<int, uint16_t, std::string, uint32_t>> Receiver::pairedDevices() const {
std::vector<std::tuple<int, uint16_t, std::string, uint32_t>> ret;
for (int i = hidpp::WirelessDevice1; i <= hidpp::WirelessDevice6; ++i) {
try {
auto index(static_cast<hidpp::DeviceIndex>(i));
auto pair_info = receiver()->getPairingInfo(index);
auto extended_pair_info = receiver()->getExtendedPairingInfo(index);
auto name = receiver()->getDeviceName(index);
ret.emplace_back(i, pair_info.pid, name, extended_pair_info.serialNumber);
} catch (hidpp10::Error& e) {
}
}
return ret;
}
void Receiver::startPair(uint8_t timeout) {
_startPair(timeout);
}
void Receiver::stopPair() {
_stopPair();
}
void Receiver::unpair(int device) {
receiver()->disconnect(static_cast<hidpp::DeviceIndex>(device));
}
Receiver::IPC::IPC(Receiver* receiver) :
ipcgull::interface(
SERVICE_ROOT_NAME ".Receiver",
{
{"GetPaired", {receiver, &Receiver::pairedDevices, {"devices"}}},
{"StartPair", {receiver, &Receiver::startPair, {"timeout"}}},
{"StopPair", {receiver, &Receiver::stopPair}},
{"Unpair", {receiver, &Receiver::unpair, {"device"}}}
},
{
{"Bolt", ipcgull::property<bool>(ipcgull::property_readable,
receiver->receiver()->bolt())}
}, {
{"PairReady",
ipcgull::signal::make_signal<std::string, uint16_t, std::string,
std::string>(
{"name", "pid", "type", "passkey"})
}
}) {
}
/*
* Copyright 2019-2023 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 <Receiver.h>
#include <DeviceManager.h>
#include <backend/Error.h>
#include <util/log.h>
#include <ipc_defs.h>
using namespace logid;
using namespace logid::backend;
ReceiverNickname::ReceiverNickname(
const std::shared_ptr<DeviceManager>& manager) :
_nickname(manager->newReceiverNickname()), _manager(manager) {
}
ReceiverNickname::operator std::string() const {
return std::to_string(_nickname);
}
ReceiverNickname::~ReceiverNickname() {
if (auto manager = _manager.lock()) {
std::lock_guard<std::mutex> lock(manager->_nick_lock);
manager->_receiver_nicknames.erase(_nickname);
}
}
std::shared_ptr<Receiver> Receiver::make(
const std::string& path,
const std::shared_ptr<DeviceManager>& manager) {
auto ret = ReceiverMonitor::make<Receiver>(path, manager);
ret->_ipc_node->manage(ret);
return ret;
}
Receiver::Receiver(const std::string& path,
const std::shared_ptr<DeviceManager>& manager) :
hidpp10::ReceiverMonitor(path, manager,
manager->config()->io_timeout.value_or(
defaults::io_timeout)),
_path(path), _manager(manager), _nickname(manager),
_ipc_node(manager->receiversNode()->make_child(_nickname)),
_ipc_interface(_ipc_node->make_interface<IPC>(this)) {
}
const Receiver::DeviceList& Receiver::devices() const {
return _devices;
}
Receiver::~Receiver() noexcept {
if (auto manager = _manager.lock()) {
for (auto& d: _devices)
manager->removeExternalDevice(d.second);
}
}
void Receiver::addDevice(hidpp::DeviceConnectionEvent event) {
std::unique_lock<std::mutex> lock(_devices_change);
auto manager = _manager.lock();
if (!manager) {
logPrintf(ERROR, "Orphan Receiver, missing DeviceManager");
logPrintf(ERROR, "Fatal error, file a bug report. Program will now exit.");
std::terminate();
}
try {
// Check if device is ignored before continuing
if (manager->config()->ignore.value_or(std::set<uint16_t>()).contains(event.pid)) {
logPrintf(DEBUG, "%s:%d: Device 0x%04x ignored.",
_path.c_str(), event.index, event.pid);
return;
}
auto dev = _devices.find(event.index);
if (dev != _devices.end()) {
if (event.linkEstablished)
dev->second->wakeup();
else
dev->second->sleep();
return;
}
if (!event.linkEstablished)
return;
auto hidpp_device = hidpp::Device::make(
receiver(), event, manager->config()->io_timeout.value_or(defaults::io_timeout));
auto version = hidpp_device->version();
if (std::get<0>(version) < 2) {
logPrintf(INFO, "Unsupported HID++ 1.0 device on %s:%d connected.",
_path.c_str(), event.index);
return;
}
hidpp_device.reset();
auto device = Device::make(this, event.index, manager);
std::lock_guard<std::mutex> manager_lock(manager->mutex());
_devices.emplace(event.index, device);
manager->addExternalDevice(device);
} catch (hidpp10::Error& e) {
logPrintf(ERROR, "Caught HID++ 1.0 error while trying to initialize %s:%d: %s",
_path.c_str(), event.index, e.what());
} catch (hidpp20::Error& e) {
logPrintf(ERROR, "Caught HID++ 2.0 error while trying to initialize "
"%s:%d: %s", _path.c_str(), event.index, e.what());
} catch (TimeoutError& e) {
if (!event.fromTimeoutCheck)
logPrintf(DEBUG, "%s:%d timed out, waiting for input from device to"
" initialize.", _path.c_str(), event.index);
waitForDevice(event.index);
}
}
void Receiver::removeDevice(hidpp::DeviceIndex index) {
std::unique_lock<std::mutex> lock(_devices_change);
std::unique_lock<std::mutex> manager_lock;
if (auto manager = _manager.lock())
manager_lock = std::unique_lock<std::mutex>(manager->mutex());
auto device = _devices.find(index);
if (device != _devices.end()) {
if (auto manager = _manager.lock())
manager->removeExternalDevice(device->second);
_devices.erase(device);
}
}
void Receiver::pairReady(const hidpp10::DeviceDiscoveryEvent& event,
const std::string& passkey) {
std::string type;
switch (event.deviceType) {
case hidpp::DeviceUnknown:
type = "unknown";
break;
case hidpp::DeviceKeyboard:
type = "keyboard";
break;
case hidpp::DeviceMouse:
type = "mouse";
break;
case hidpp::DeviceNumpad:
type = "numpad";
break;
case hidpp::DevicePresenter:
type = "presenter";
break;
case hidpp::DeviceTouchpad:
type = "touchpad";
break;
case hidpp::DeviceTrackball:
type = "trackball";
break;
}
_ipc_interface->emit_signal("PairReady", event.name, event.pid, type, passkey);
}
const std::string& Receiver::path() const {
return _path;
}
std::shared_ptr<hidpp10::Receiver> Receiver::rawReceiver() {
return receiver();
}
std::vector<std::tuple<int, uint16_t, std::string, uint32_t>> Receiver::pairedDevices() const {
std::vector<std::tuple<int, uint16_t, std::string, uint32_t>> ret;
for (int i = hidpp::WirelessDevice1; i <= hidpp::WirelessDevice6; ++i) {
try {
auto index(static_cast<hidpp::DeviceIndex>(i));
auto pair_info = receiver()->getPairingInfo(index);
auto extended_pair_info = receiver()->getExtendedPairingInfo(index);
auto name = receiver()->getDeviceName(index);
ret.emplace_back(i, pair_info.pid, name, extended_pair_info.serialNumber);
} catch (hidpp10::Error& e) {
}
}
return ret;
}
void Receiver::startPair(uint8_t timeout) {
_startPair(timeout);
}
void Receiver::stopPair() {
_stopPair();
}
void Receiver::unpair(int device) {
receiver()->disconnect(static_cast<hidpp::DeviceIndex>(device));
}
Receiver::IPC::IPC(Receiver* receiver) :
ipcgull::interface(
SERVICE_ROOT_NAME ".Receiver",
{
{"GetPaired", {receiver, &Receiver::pairedDevices, {"devices"}}},
{"StartPair", {receiver, &Receiver::startPair, {"timeout"}}},
{"StopPair", {receiver, &Receiver::stopPair}},
{"Unpair", {receiver, &Receiver::unpair, {"device"}}}
},
{
{"Bolt", ipcgull::property<bool>(ipcgull::property_readable,
receiver->receiver()->bolt())}
}, {
{"PairReady",
ipcgull::signal::make_signal<std::string, uint16_t, std::string,
std::string>(
{"name", "pid", "type", "passkey"})
}
}) {
}

View File

@ -1,100 +1,100 @@
/*
* Copyright 2019-2023 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_RECEIVER_H
#define LOGID_RECEIVER_H
#include <string>
#include <Device.h>
#include <backend/hidpp10/ReceiverMonitor.h>
namespace logid {
class ReceiverNickname {
public:
explicit ReceiverNickname(const std::shared_ptr<DeviceManager>& manager);
ReceiverNickname() = delete;
ReceiverNickname(const ReceiverNickname&) = delete;
~ReceiverNickname();
operator std::string() const;
private:
const int _nickname;
const std::weak_ptr<DeviceManager> _manager;
};
class Receiver : public backend::hidpp10::ReceiverMonitor,
public ipcgull::object {
public:
typedef std::map<backend::hidpp::DeviceIndex, std::shared_ptr<Device>>
DeviceList;
~Receiver() noexcept override;
static std::shared_ptr<Receiver> make(
const std::string& path,
const std::shared_ptr<DeviceManager>& manager);
[[nodiscard]] const std::string& path() const;
std::shared_ptr<backend::hidpp10::Receiver> rawReceiver();
[[nodiscard]] const DeviceList& devices() const;
[[nodiscard]] std::vector<std::tuple<int, uint16_t, std::string, uint32_t>>
pairedDevices() const;
void startPair(uint8_t timeout);
void stopPair();
void unpair(int device);
protected:
Receiver(const std::string& path,
const std::shared_ptr<DeviceManager>& manager);
void addDevice(backend::hidpp::DeviceConnectionEvent event) override;
void removeDevice(backend::hidpp::DeviceIndex index) override;
void pairReady(const backend::hidpp10::DeviceDiscoveryEvent& event,
const std::string& passkey) override;
private:
std::mutex _devices_change;
DeviceList _devices;
std::string _path;
std::weak_ptr<DeviceManager> _manager;
const ReceiverNickname _nickname;
std::shared_ptr<ipcgull::node> _ipc_node;
class IPC : public ipcgull::interface {
public:
explicit IPC(Receiver* receiver);
};
std::shared_ptr<ipcgull::interface> _ipc_interface;
};
}
/*
* Copyright 2019-2023 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_RECEIVER_H
#define LOGID_RECEIVER_H
#include <string>
#include <Device.h>
#include <backend/hidpp10/ReceiverMonitor.h>
namespace logid {
class ReceiverNickname {
public:
explicit ReceiverNickname(const std::shared_ptr<DeviceManager>& manager);
ReceiverNickname() = delete;
ReceiverNickname(const ReceiverNickname&) = delete;
~ReceiverNickname();
operator std::string() const;
private:
const int _nickname;
const std::weak_ptr<DeviceManager> _manager;
};
class Receiver : public backend::hidpp10::ReceiverMonitor,
public ipcgull::object {
public:
typedef std::map<backend::hidpp::DeviceIndex, std::shared_ptr<Device>>
DeviceList;
~Receiver() noexcept override;
static std::shared_ptr<Receiver> make(
const std::string& path,
const std::shared_ptr<DeviceManager>& manager);
[[nodiscard]] const std::string& path() const;
std::shared_ptr<backend::hidpp10::Receiver> rawReceiver();
[[nodiscard]] const DeviceList& devices() const;
[[nodiscard]] std::vector<std::tuple<int, uint16_t, std::string, uint32_t>>
pairedDevices() const;
void startPair(uint8_t timeout);
void stopPair();
void unpair(int device);
protected:
Receiver(const std::string& path,
const std::shared_ptr<DeviceManager>& manager);
void addDevice(backend::hidpp::DeviceConnectionEvent event) override;
void removeDevice(backend::hidpp::DeviceIndex index) override;
void pairReady(const backend::hidpp10::DeviceDiscoveryEvent& event,
const std::string& passkey) override;
private:
std::mutex _devices_change;
DeviceList _devices;
std::string _path;
std::weak_ptr<DeviceManager> _manager;
const ReceiverNickname _nickname;
std::shared_ptr<ipcgull::node> _ipc_node;
class IPC : public ipcgull::interface {
public:
explicit IPC(Receiver* receiver);
};
std::shared_ptr<ipcgull::interface> _ipc_interface;
};
}
#endif //LOGID_RECEIVER_H

View File

@ -1,143 +1,143 @@
/*
* Copyright 2019-2023 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 <actions/Action.h>
#include <actions/KeypressAction.h>
#include <actions/ToggleSmartShift.h>
#include <actions/ToggleHiresScroll.h>
#include <actions/GestureAction.h>
#include <actions/NullAction.h>
#include <actions/CycleDPI.h>
#include <actions/ChangeDPI.h>
#include <actions/ChangeHostAction.h>
#include <actions/ChangeProfile.h>
#include <ipc_defs.h>
using namespace logid;
using namespace logid::actions;
namespace logid::actions {
template<typename T>
struct action_type {
typedef typename T::action type;
};
template<typename T>
struct action_type<const T> : action_type<T> {
};
template<typename T>
struct action_type<T&> : action_type<T> {
};
template<typename T>
std::shared_ptr<Action> _makeAction(
Device* device, T& action,
const std::shared_ptr<ipcgull::node>& parent) {
return parent->make_interface<typename action_type<T>::type>(
device, std::forward<T&>(action), parent);
}
template<typename T>
std::shared_ptr<Action> _makeAction(
Device* device, const std::string& name,
std::optional<T>& config,
const std::shared_ptr<ipcgull::node>& parent) {
if (name == ChangeDPI::interface_name) {
config = config::ChangeDPI();
} else if (name == CycleDPI::interface_name) {
config = config::CycleDPI();
} else if (name == KeypressAction::interface_name) {
config = config::KeypressAction();
} else if (name == NullAction::interface_name) {
config = config::NoAction();
} else if (name == ChangeHostAction::interface_name) {
config = config::ChangeHost();
} else if (name == ToggleHiresScroll::interface_name) {
config = config::ToggleHiresScroll();
} else if (name == ToggleSmartShift::interface_name) {
config = config::ToggleSmartShift();
} else if (name == ChangeProfile::interface_name) {
config = config::ChangeProfile();
} else if (name == "Default") {
config.reset();
return nullptr;
} else {
throw InvalidAction(name);
}
return Action::makeAction(device, config.value(), parent);
}
}
std::shared_ptr<Action> Action::makeAction(
Device* device, const std::string& name,
std::optional<config::BasicAction>& config,
const std::shared_ptr<ipcgull::node>& parent) {
auto ret = _makeAction(device, name, config, parent);
if (ret)
ret->_self = ret;
return ret;
}
std::shared_ptr<Action> Action::makeAction(
Device* device, const std::string& name,
std::optional<config::Action>& config,
const std::shared_ptr<ipcgull::node>& parent) {
try {
auto ret = _makeAction(device, name, config, parent);
if (ret)
ret->_self = ret;
return ret;
} catch (actions::InvalidAction& e) {
if (name == GestureAction::interface_name) {
config = config::GestureAction();
return makeAction(device, config.value(), parent);
}
throw;
}
}
std::shared_ptr<Action> Action::makeAction(
Device* device, config::BasicAction& action,
const std::shared_ptr<ipcgull::node>& parent) {
std::shared_ptr<Action> ret;
std::visit([&device, &ret, &parent](auto&& x) {
ret = _makeAction(device, x, parent);
}, action);
if (ret)
ret->_self = ret;
return ret;
}
std::shared_ptr<Action> Action::makeAction(
Device* device, config::Action& action,
const std::shared_ptr<ipcgull::node>& parent) {
std::shared_ptr<Action> ret;
std::visit([&device, &ret, &parent](auto&& x) {
ret = _makeAction(device, x, parent);
}, action);
if (ret)
ret->_self = ret;
return ret;
}
Action::Action(Device* device, const std::string& name, tables t) :
ipcgull::interface(SERVICE_ROOT_NAME ".Action." + name, std::move(t)),
_device(device), _pressed(false) {
}
/*
* Copyright 2019-2023 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 <actions/Action.h>
#include <actions/KeypressAction.h>
#include <actions/ToggleSmartShift.h>
#include <actions/ToggleHiresScroll.h>
#include <actions/GestureAction.h>
#include <actions/NullAction.h>
#include <actions/CycleDPI.h>
#include <actions/ChangeDPI.h>
#include <actions/ChangeHostAction.h>
#include <actions/ChangeProfile.h>
#include <ipc_defs.h>
using namespace logid;
using namespace logid::actions;
namespace logid::actions {
template<typename T>
struct action_type {
typedef typename T::action type;
};
template<typename T>
struct action_type<const T> : action_type<T> {
};
template<typename T>
struct action_type<T&> : action_type<T> {
};
template<typename T>
std::shared_ptr<Action> _makeAction(
Device* device, T& action,
const std::shared_ptr<ipcgull::node>& parent) {
return parent->make_interface<typename action_type<T>::type>(
device, std::forward<T&>(action), parent);
}
template<typename T>
std::shared_ptr<Action> _makeAction(
Device* device, const std::string& name,
std::optional<T>& config,
const std::shared_ptr<ipcgull::node>& parent) {
if (name == ChangeDPI::interface_name) {
config = config::ChangeDPI();
} else if (name == CycleDPI::interface_name) {
config = config::CycleDPI();
} else if (name == KeypressAction::interface_name) {
config = config::KeypressAction();
} else if (name == NullAction::interface_name) {
config = config::NoAction();
} else if (name == ChangeHostAction::interface_name) {
config = config::ChangeHost();
} else if (name == ToggleHiresScroll::interface_name) {
config = config::ToggleHiresScroll();
} else if (name == ToggleSmartShift::interface_name) {
config = config::ToggleSmartShift();
} else if (name == ChangeProfile::interface_name) {
config = config::ChangeProfile();
} else if (name == "Default") {
config.reset();
return nullptr;
} else {
throw InvalidAction(name);
}
return Action::makeAction(device, config.value(), parent);
}
}
std::shared_ptr<Action> Action::makeAction(
Device* device, const std::string& name,
std::optional<config::BasicAction>& config,
const std::shared_ptr<ipcgull::node>& parent) {
auto ret = _makeAction(device, name, config, parent);
if (ret)
ret->_self = ret;
return ret;
}
std::shared_ptr<Action> Action::makeAction(
Device* device, const std::string& name,
std::optional<config::Action>& config,
const std::shared_ptr<ipcgull::node>& parent) {
try {
auto ret = _makeAction(device, name, config, parent);
if (ret)
ret->_self = ret;
return ret;
} catch (actions::InvalidAction& e) {
if (name == GestureAction::interface_name) {
config = config::GestureAction();
return makeAction(device, config.value(), parent);
}
throw;
}
}
std::shared_ptr<Action> Action::makeAction(
Device* device, config::BasicAction& action,
const std::shared_ptr<ipcgull::node>& parent) {
std::shared_ptr<Action> ret;
std::visit([&device, &ret, &parent](auto&& x) {
ret = _makeAction(device, x, parent);
}, action);
if (ret)
ret->_self = ret;
return ret;
}
std::shared_ptr<Action> Action::makeAction(
Device* device, config::Action& action,
const std::shared_ptr<ipcgull::node>& parent) {
std::shared_ptr<Action> ret;
std::visit([&device, &ret, &parent](auto&& x) {
ret = _makeAction(device, x, parent);
}, action);
if (ret)
ret->_self = ret;
return ret;
}
Action::Action(Device* device, const std::string& name, tables t) :
ipcgull::interface(SERVICE_ROOT_NAME ".Action." + name, std::move(t)),
_device(device), _pressed(false) {
}

View File

@ -1,99 +1,99 @@
/*
* Copyright 2019-2023 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 <memory>
#include <utility>
#include <shared_mutex>
#include <ipcgull/node.h>
#include <ipcgull/interface.h>
#include <config/schema.h>
namespace logid {
class Device;
}
namespace logid::actions {
class InvalidAction : public std::exception {
public:
InvalidAction() = default;
InvalidAction(std::string action) : _action(std::move(action)) {}
[[nodiscard]] const char* what() const noexcept override {
return _action.c_str();
}
private:
std::string _action;
};
class Action : public ipcgull::interface {
public:
static std::shared_ptr<Action> makeAction(
Device* device, const std::string& name,
std::optional<config::BasicAction>& config,
const std::shared_ptr<ipcgull::node>& parent);
static std::shared_ptr<Action> makeAction(
Device* device, const std::string& name,
std::optional<config::Action>& config,
const std::shared_ptr<ipcgull::node>& parent);
static std::shared_ptr<Action> makeAction(
Device* device, config::BasicAction& action,
const std::shared_ptr<ipcgull::node>& parent);
static std::shared_ptr<Action> makeAction(
Device* device, config::Action& action,
const std::shared_ptr<ipcgull::node>& parent);
virtual void press() = 0;
virtual void release() = 0;
virtual void move([[maybe_unused]] int16_t x, [[maybe_unused]] int16_t y) { }
virtual bool pressed() {
return _pressed;
}
[[nodiscard]] virtual uint8_t reprogFlags() const = 0;
virtual ~Action() = default;
protected:
Action(Device* device, const std::string& name, tables t = {});
Device* _device;
std::atomic<bool> _pressed;
mutable std::shared_mutex _config_mutex;
template <typename T>
[[nodiscard]] std::weak_ptr<T> self() const {
return std::dynamic_pointer_cast<T>(_self.lock());
}
private:
std::weak_ptr<Action> _self;
};
}
#endif //LOGID_ACTION_H
/*
* Copyright 2019-2023 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 <memory>
#include <utility>
#include <shared_mutex>
#include <ipcgull/node.h>
#include <ipcgull/interface.h>
#include <config/schema.h>
namespace logid {
class Device;
}
namespace logid::actions {
class InvalidAction : public std::exception {
public:
InvalidAction() = default;
InvalidAction(std::string action) : _action(std::move(action)) {}
[[nodiscard]] const char* what() const noexcept override {
return _action.c_str();
}
private:
std::string _action;
};
class Action : public ipcgull::interface {
public:
static std::shared_ptr<Action> makeAction(
Device* device, const std::string& name,
std::optional<config::BasicAction>& config,
const std::shared_ptr<ipcgull::node>& parent);
static std::shared_ptr<Action> makeAction(
Device* device, const std::string& name,
std::optional<config::Action>& config,
const std::shared_ptr<ipcgull::node>& parent);
static std::shared_ptr<Action> makeAction(
Device* device, config::BasicAction& action,
const std::shared_ptr<ipcgull::node>& parent);
static std::shared_ptr<Action> makeAction(
Device* device, config::Action& action,
const std::shared_ptr<ipcgull::node>& parent);
virtual void press() = 0;
virtual void release() = 0;
virtual void move([[maybe_unused]] int16_t x, [[maybe_unused]] int16_t y) { }
virtual bool pressed() {
return _pressed;
}
[[nodiscard]] virtual uint8_t reprogFlags() const = 0;
virtual ~Action() = default;
protected:
Action(Device* device, const std::string& name, tables t = {});
Device* _device;
std::atomic<bool> _pressed;
mutable std::shared_mutex _config_mutex;
template <typename T>
[[nodiscard]] std::weak_ptr<T> self() const {
return std::dynamic_pointer_cast<T>(_self.lock());
}
private:
std::weak_ptr<Action> _self;
};
}
#endif //LOGID_ACTION_H

View File

@ -1,94 +1,94 @@
/*
* Copyright 2019-2023 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 <actions/ChangeDPI.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/task.h>
#include <util/log.h>
using namespace logid::actions;
const char* ChangeDPI::interface_name = "ChangeDPI";
ChangeDPI::ChangeDPI(
Device* device, config::ChangeDPI& config,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(device, interface_name, {
{
{"GetConfig", {this, &ChangeDPI::getConfig, {"change", "sensor"}}},
{"SetChange", {this, &ChangeDPI::setChange, {"change"}}},
{"SetSensor", {this, &ChangeDPI::setSensor, {"sensor", "reset"}}},
},
{},
{}}), _config(config) {
_dpi = _device->getFeature<features::DPI>("dpi");
if (!_dpi)
logPrintf(WARN, "%s:%d: DPI feature not found, cannot use ChangeDPI action.",
_device->hidpp20().devicePath().c_str(),
_device->hidpp20().deviceIndex());
}
void ChangeDPI::press() {
_pressed = true;
std::shared_lock lock(_config_mutex);
if (_dpi && _config.inc.has_value()) {
run_task([self_weak = self<ChangeDPI>(),
sensor = _config.sensor.value_or(0), inc = _config.inc.value()] {
if (auto self = self_weak.lock()) {
try {
uint16_t last_dpi = self->_dpi->getDPI(sensor);
self->_dpi->setDPI(last_dpi + inc, sensor);
} catch (backend::hidpp20::Error& e) {
if (e.code() == backend::hidpp20::Error::InvalidArgument)
logPrintf(WARN, "%s:%d: Could not get/set DPI for sensor %d",
self->_device->hidpp20().devicePath().c_str(),
self->_device->hidpp20().deviceIndex(), sensor);
else
throw e;
}
}
});
}
}
void ChangeDPI::release() {
_pressed = false;
}
uint8_t ChangeDPI::reprogFlags() const {
return backend::hidpp20::ReprogControls::TemporaryDiverted;
}
std::tuple<int16_t, uint16_t> ChangeDPI::getConfig() const {
std::shared_lock lock(_config_mutex);
return {_config.inc.value_or(0), _config.sensor.value_or(0)};
}
void ChangeDPI::setChange(int16_t change) {
std::unique_lock lock(_config_mutex);
_config.inc = change;
}
void ChangeDPI::setSensor(uint8_t sensor, bool reset) {
std::unique_lock lock(_config_mutex);
if (reset) {
_config.sensor.reset();
} else {
_config.sensor = sensor;
}
}
/*
* Copyright 2019-2023 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 <actions/ChangeDPI.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/task.h>
#include <util/log.h>
using namespace logid::actions;
const char* ChangeDPI::interface_name = "ChangeDPI";
ChangeDPI::ChangeDPI(
Device* device, config::ChangeDPI& config,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(device, interface_name, {
{
{"GetConfig", {this, &ChangeDPI::getConfig, {"change", "sensor"}}},
{"SetChange", {this, &ChangeDPI::setChange, {"change"}}},
{"SetSensor", {this, &ChangeDPI::setSensor, {"sensor", "reset"}}},
},
{},
{}}), _config(config) {
_dpi = _device->getFeature<features::DPI>("dpi");
if (!_dpi)
logPrintf(WARN, "%s:%d: DPI feature not found, cannot use ChangeDPI action.",
_device->hidpp20().devicePath().c_str(),
_device->hidpp20().deviceIndex());
}
void ChangeDPI::press() {
_pressed = true;
std::shared_lock lock(_config_mutex);
if (_dpi && _config.inc.has_value()) {
run_task([self_weak = self<ChangeDPI>(),
sensor = _config.sensor.value_or(0), inc = _config.inc.value()] {
if (auto self = self_weak.lock()) {
try {
uint16_t last_dpi = self->_dpi->getDPI(sensor);
self->_dpi->setDPI(last_dpi + inc, sensor);
} catch (backend::hidpp20::Error& e) {
if (e.code() == backend::hidpp20::Error::InvalidArgument)
logPrintf(WARN, "%s:%d: Could not get/set DPI for sensor %d",
self->_device->hidpp20().devicePath().c_str(),
self->_device->hidpp20().deviceIndex(), sensor);
else
throw e;
}
}
});
}
}
void ChangeDPI::release() {
_pressed = false;
}
uint8_t ChangeDPI::reprogFlags() const {
return backend::hidpp20::ReprogControls::TemporaryDiverted;
}
std::tuple<int16_t, uint16_t> ChangeDPI::getConfig() const {
std::shared_lock lock(_config_mutex);
return {_config.inc.value_or(0), _config.sensor.value_or(0)};
}
void ChangeDPI::setChange(int16_t change) {
std::unique_lock lock(_config_mutex);
_config.inc = change;
}
void ChangeDPI::setSensor(uint8_t sensor, bool reset) {
std::unique_lock lock(_config_mutex);
if (reset) {
_config.sensor.reset();
} else {
_config.sensor = sensor;
}
}

View File

@ -1,50 +1,50 @@
/*
* Copyright 2019-2023 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_CHANGEDPI_H
#define LOGID_ACTION_CHANGEDPI_H
#include <actions/Action.h>
#include <features/DPI.h>
namespace logid::actions {
class ChangeDPI : public Action {
public:
static const char* interface_name;
ChangeDPI(Device* device, config::ChangeDPI& setting,
const std::shared_ptr<ipcgull::node>& parent);
void press() final;
void release() final;
[[nodiscard]] std::tuple<int16_t, uint16_t> getConfig() const;
void setChange(int16_t change);
void setSensor(uint8_t sensor, bool reset);
[[nodiscard]] uint8_t reprogFlags() const final;
protected:
config::ChangeDPI& _config;
std::shared_ptr<features::DPI> _dpi;
};
}
/*
* Copyright 2019-2023 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_CHANGEDPI_H
#define LOGID_ACTION_CHANGEDPI_H
#include <actions/Action.h>
#include <features/DPI.h>
namespace logid::actions {
class ChangeDPI : public Action {
public:
static const char* interface_name;
ChangeDPI(Device* device, config::ChangeDPI& setting,
const std::shared_ptr<ipcgull::node>& parent);
void press() final;
void release() final;
[[nodiscard]] std::tuple<int16_t, uint16_t> getConfig() const;
void setChange(int16_t change);
void setSensor(uint8_t sensor, bool reset);
[[nodiscard]] uint8_t reprogFlags() const final;
protected:
config::ChangeDPI& _config;
std::shared_ptr<features::DPI> _dpi;
};
}
#endif //LOGID_ACTION_CHANGEDPI_H

View File

@ -1,112 +1,112 @@
/*
* Copyright 2019-2023 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 <actions/ChangeHostAction.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <algorithm>
#include <util/task.h>
#include <util/log.h>
using namespace logid::actions;
using namespace logid::backend;
const char* ChangeHostAction::interface_name = "ChangeHost";
ChangeHostAction::ChangeHostAction(
Device* device, config::ChangeHost& config,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent)
: Action(device, interface_name, {
{
{"GetHost", {this, &ChangeHostAction::getHost, {"host"}}},
{"SetHost", {this, &ChangeHostAction::setHost, {"host"}}}
},
{},
{}
}), _config(config) {
if (_config.host.has_value()) {
if (std::holds_alternative<std::string>(_config.host.value())) {
auto& host = std::get<std::string>(_config.host.value());
std::transform(host.begin(), host.end(),
host.begin(), ::tolower);
}
}
try {
_change_host = std::make_shared<hidpp20::ChangeHost>(&device->hidpp20());
} catch (hidpp20::UnsupportedFeature& e) {
logPrintf(WARN, "%s:%d: ChangeHost feature not supported, "
"ChangeHostAction will not work.", device->hidpp20()
.devicePath().c_str(), device->hidpp20().deviceIndex());
}
}
std::string ChangeHostAction::getHost() const {
std::shared_lock lock(_config_mutex);
if (_config.host.has_value()) {
if (std::holds_alternative<std::string>(_config.host.value()))
return std::get<std::string>(_config.host.value());
else
return std::to_string(std::get<int>(_config.host.value()));
} else {
return "";
}
}
void ChangeHostAction::setHost(std::string host) {
std::transform(host.begin(), host.end(),
host.begin(), ::tolower);
std::unique_lock lock(_config_mutex);
if (host == "next" || host == "prev" || host == "previous") {
_config.host = std::move(host);
} else {
_config.host = std::stoi(host);
}
}
void ChangeHostAction::press() {
// Do nothing, wait until release
}
void ChangeHostAction::release() {
std::shared_lock lock(_config_mutex);
if (_change_host && _config.host.has_value()) {
run_task([self_weak = self<ChangeHostAction>(), host = _config.host.value()] {
if (auto self = self_weak.lock()) {
auto host_info = self->_change_host->getHostInfo();
int next_host;
if (std::holds_alternative<std::string>(host)) {
const auto& host_str = std::get<std::string>(host);
if (host_str == "next")
next_host = host_info.currentHost + 1;
else if (host_str == "prev" || host_str == "previous")
next_host = host_info.currentHost - 1;
else
next_host = host_info.currentHost;
} else {
next_host = std::get<int>(host) - 1;
}
next_host %= host_info.hostCount;
if (next_host != host_info.currentHost)
self->_change_host->setHost(next_host);
}
});
}
}
uint8_t ChangeHostAction::reprogFlags() const {
return hidpp20::ReprogControls::TemporaryDiverted;
}
/*
* Copyright 2019-2023 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 <actions/ChangeHostAction.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <algorithm>
#include <util/task.h>
#include <util/log.h>
using namespace logid::actions;
using namespace logid::backend;
const char* ChangeHostAction::interface_name = "ChangeHost";
ChangeHostAction::ChangeHostAction(
Device* device, config::ChangeHost& config,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent)
: Action(device, interface_name, {
{
{"GetHost", {this, &ChangeHostAction::getHost, {"host"}}},
{"SetHost", {this, &ChangeHostAction::setHost, {"host"}}}
},
{},
{}
}), _config(config) {
if (_config.host.has_value()) {
if (std::holds_alternative<std::string>(_config.host.value())) {
auto& host = std::get<std::string>(_config.host.value());
std::transform(host.begin(), host.end(),
host.begin(), ::tolower);
}
}
try {
_change_host = std::make_shared<hidpp20::ChangeHost>(&device->hidpp20());
} catch (hidpp20::UnsupportedFeature& e) {
logPrintf(WARN, "%s:%d: ChangeHost feature not supported, "
"ChangeHostAction will not work.", device->hidpp20()
.devicePath().c_str(), device->hidpp20().deviceIndex());
}
}
std::string ChangeHostAction::getHost() const {
std::shared_lock lock(_config_mutex);
if (_config.host.has_value()) {
if (std::holds_alternative<std::string>(_config.host.value()))
return std::get<std::string>(_config.host.value());
else
return std::to_string(std::get<int>(_config.host.value()));
} else {
return "";
}
}
void ChangeHostAction::setHost(std::string host) {
std::transform(host.begin(), host.end(),
host.begin(), ::tolower);
std::unique_lock lock(_config_mutex);
if (host == "next" || host == "prev" || host == "previous") {
_config.host = std::move(host);
} else {
_config.host = std::stoi(host);
}
}
void ChangeHostAction::press() {
// Do nothing, wait until release
}
void ChangeHostAction::release() {
std::shared_lock lock(_config_mutex);
if (_change_host && _config.host.has_value()) {
run_task([self_weak = self<ChangeHostAction>(), host = _config.host.value()] {
if (auto self = self_weak.lock()) {
auto host_info = self->_change_host->getHostInfo();
int next_host;
if (std::holds_alternative<std::string>(host)) {
const auto& host_str = std::get<std::string>(host);
if (host_str == "next")
next_host = host_info.currentHost + 1;
else if (host_str == "prev" || host_str == "previous")
next_host = host_info.currentHost - 1;
else
next_host = host_info.currentHost;
} else {
next_host = std::get<int>(host) - 1;
}
next_host %= host_info.hostCount;
if (next_host != host_info.currentHost)
self->_change_host->setHost(next_host);
}
});
}
}
uint8_t ChangeHostAction::reprogFlags() const {
return hidpp20::ReprogControls::TemporaryDiverted;
}

View File

@ -1,48 +1,48 @@
/*
* Copyright 2019-2023 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_CHANGEHOSTACTION_H
#define LOGID_ACTION_CHANGEHOSTACTION_H
#include <actions/Action.h>
#include <backend/hidpp20/features/ChangeHost.h>
namespace logid::actions {
class ChangeHostAction : public Action {
public:
static const char* interface_name;
ChangeHostAction(Device* device, config::ChangeHost& config,
const std::shared_ptr<ipcgull::node>& parent);
void press() final;
void release() final;
[[nodiscard]] std::string getHost() const;
void setHost(std::string host);
[[nodiscard]] uint8_t reprogFlags() const final;
protected:
std::shared_ptr<backend::hidpp20::ChangeHost> _change_host;
config::ChangeHost& _config;
};
}
#endif //LOGID_ACTION_CHANGEHOSTACTION_H
/*
* Copyright 2019-2023 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_CHANGEHOSTACTION_H
#define LOGID_ACTION_CHANGEHOSTACTION_H
#include <actions/Action.h>
#include <backend/hidpp20/features/ChangeHost.h>
namespace logid::actions {
class ChangeHostAction : public Action {
public:
static const char* interface_name;
ChangeHostAction(Device* device, config::ChangeHost& config,
const std::shared_ptr<ipcgull::node>& parent);
void press() final;
void release() final;
[[nodiscard]] std::string getHost() const;
void setHost(std::string host);
[[nodiscard]] uint8_t reprogFlags() const final;
protected:
std::shared_ptr<backend::hidpp20::ChangeHost> _change_host;
config::ChangeHost& _config;
};
}
#endif //LOGID_ACTION_CHANGEHOSTACTION_H

View File

@ -1,67 +1,67 @@
/*
* Copyright 2019-2023 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 <actions/ChangeProfile.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <Device.h>
using namespace logid;
using namespace logid::actions;
const char* ChangeProfile::interface_name = "ChangeProfile";
ChangeProfile::ChangeProfile(Device* device, config::ChangeProfile& config,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(device, interface_name, {
{
{"GetProfile", {this, &ChangeProfile::getProfile, {"profile"}}},
{"SetProfile", {this, &ChangeProfile::setProfile, {"profile"}}}
},
{},
{}
}), _config(config) {
}
void ChangeProfile::press() {
}
void ChangeProfile::release() {
std::shared_lock lock(_config_mutex);
if (_config.profile.has_value())
_device->setProfileDelayed(_config.profile.value());
}
uint8_t ChangeProfile::reprogFlags() const {
return backend::hidpp20::ReprogControls::TemporaryDiverted;
}
std::string ChangeProfile::getProfile() {
std::shared_lock lock(_config_mutex);
if (_config.profile.has_value())
return _config.profile.value();
else
return "";
}
void ChangeProfile::setProfile(std::string profile) {
std::unique_lock lock(_config_mutex);
if (profile.empty())
_config.profile->clear();
else
_config.profile = std::move(profile);
}
/*
* Copyright 2019-2023 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 <actions/ChangeProfile.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <Device.h>
using namespace logid;
using namespace logid::actions;
const char* ChangeProfile::interface_name = "ChangeProfile";
ChangeProfile::ChangeProfile(Device* device, config::ChangeProfile& config,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(device, interface_name, {
{
{"GetProfile", {this, &ChangeProfile::getProfile, {"profile"}}},
{"SetProfile", {this, &ChangeProfile::setProfile, {"profile"}}}
},
{},
{}
}), _config(config) {
}
void ChangeProfile::press() {
}
void ChangeProfile::release() {
std::shared_lock lock(_config_mutex);
if (_config.profile.has_value())
_device->setProfileDelayed(_config.profile.value());
}
uint8_t ChangeProfile::reprogFlags() const {
return backend::hidpp20::ReprogControls::TemporaryDiverted;
}
std::string ChangeProfile::getProfile() {
std::shared_lock lock(_config_mutex);
if (_config.profile.has_value())
return _config.profile.value();
else
return "";
}
void ChangeProfile::setProfile(std::string profile) {
std::unique_lock lock(_config_mutex);
if (profile.empty())
_config.profile->clear();
else
_config.profile = std::move(profile);
}

View File

@ -1,47 +1,47 @@
/*
* Copyright 2019-2023 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_CHANGEPROFILE_H
#define LOGID_CHANGEPROFILE_H
#include <actions/Action.h>
namespace logid::actions {
class ChangeProfile : public Action {
public:
static const char* interface_name;
ChangeProfile(Device* device, config::ChangeProfile& setting,
const std::shared_ptr<ipcgull::node>& parent);
void press() final;
void release() final;
[[nodiscard]] uint8_t reprogFlags() const final;
std::string getProfile();
void setProfile(std::string profile);
private:
config::ChangeProfile& _config;
};
}
#endif //LOGID_CHANGEPROFILE_H
/*
* Copyright 2019-2023 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_CHANGEPROFILE_H
#define LOGID_CHANGEPROFILE_H
#include <actions/Action.h>
namespace logid::actions {
class ChangeProfile : public Action {
public:
static const char* interface_name;
ChangeProfile(Device* device, config::ChangeProfile& setting,
const std::shared_ptr<ipcgull::node>& parent);
void press() final;
void release() final;
[[nodiscard]] uint8_t reprogFlags() const final;
std::string getProfile();
void setProfile(std::string profile);
private:
config::ChangeProfile& _config;
};
}
#endif //LOGID_CHANGEPROFILE_H

View File

@ -1,98 +1,98 @@
/*
* Copyright 2019-2023 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 <actions/CycleDPI.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/task.h>
#include <util/log.h>
using namespace logid::actions;
const char* CycleDPI::interface_name = "CycleDPI";
CycleDPI::CycleDPI(Device* device, config::CycleDPI& config,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(device, interface_name, {
{
{"GetDPIs", {this, &CycleDPI::getDPIs, {"dpis"}}},
{"SetDPIs", {this, &CycleDPI::setDPIs, {"dpis"}}}
},
{},
{}
}),
_config(config) {
_dpi = _device->getFeature<features::DPI>("dpi");
if (!_dpi)
logPrintf(WARN, "%s:%d: DPI feature not found, cannot use "
"CycleDPI action.",
_device->hidpp20().devicePath().c_str(),
_device->hidpp20().deviceIndex());
if (_config.dpis.has_value()) {
_current_dpi = _config.dpis.value().begin();
}
}
std::vector<int> CycleDPI::getDPIs() const {
std::shared_lock lock(_config_mutex);
auto dpis = _config.dpis.value_or(std::list<int>());
return {dpis.begin(), dpis.end()};
}
void CycleDPI::setDPIs(const std::vector<int>& dpis) {
std::unique_lock lock(_config_mutex);
std::lock_guard dpi_lock(_dpi_mutex);
_config.dpis.emplace(dpis.begin(), dpis.end());
_current_dpi = _config.dpis->cbegin();
}
void CycleDPI::press() {
_pressed = true;
std::shared_lock lock(_config_mutex);
std::lock_guard dpi_lock(_dpi_mutex);
if (_dpi && _config.dpis.has_value() && _config.dpis.value().empty()) {
++_current_dpi;
if (_current_dpi == _config.dpis.value().end())
_current_dpi = _config.dpis.value().begin();
run_task([self_weak = self<CycleDPI>(), dpi = *_current_dpi] {
if (auto self = self_weak.lock()) {
try {
self->_dpi->setDPI(dpi, self->_config.sensor.value_or(0));
} catch (backend::hidpp20::Error& e) {
if (e.code() == backend::hidpp20::Error::InvalidArgument)
logPrintf(WARN, "%s:%d: Could not set DPI to %d for "
"sensor %d",
self->_device->hidpp20().devicePath().c_str(),
self->_device->hidpp20().deviceIndex(), dpi,
self->_config.sensor.value_or(0));
else
throw e;
}
}
});
}
}
void CycleDPI::release() {
_pressed = false;
}
uint8_t CycleDPI::reprogFlags() const {
return backend::hidpp20::ReprogControls::TemporaryDiverted;
}
/*
* Copyright 2019-2023 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 <actions/CycleDPI.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/task.h>
#include <util/log.h>
using namespace logid::actions;
const char* CycleDPI::interface_name = "CycleDPI";
CycleDPI::CycleDPI(Device* device, config::CycleDPI& config,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(device, interface_name, {
{
{"GetDPIs", {this, &CycleDPI::getDPIs, {"dpis"}}},
{"SetDPIs", {this, &CycleDPI::setDPIs, {"dpis"}}}
},
{},
{}
}),
_config(config) {
_dpi = _device->getFeature<features::DPI>("dpi");
if (!_dpi)
logPrintf(WARN, "%s:%d: DPI feature not found, cannot use "
"CycleDPI action.",
_device->hidpp20().devicePath().c_str(),
_device->hidpp20().deviceIndex());
if (_config.dpis.has_value()) {
_current_dpi = _config.dpis.value().begin();
}
}
std::vector<int> CycleDPI::getDPIs() const {
std::shared_lock lock(_config_mutex);
auto dpis = _config.dpis.value_or(std::list<int>());
return {dpis.begin(), dpis.end()};
}
void CycleDPI::setDPIs(const std::vector<int>& dpis) {
std::unique_lock lock(_config_mutex);
std::lock_guard dpi_lock(_dpi_mutex);
_config.dpis.emplace(dpis.begin(), dpis.end());
_current_dpi = _config.dpis->cbegin();
}
void CycleDPI::press() {
_pressed = true;
std::shared_lock lock(_config_mutex);
std::lock_guard dpi_lock(_dpi_mutex);
if (_dpi && _config.dpis.has_value() && _config.dpis.value().empty()) {
++_current_dpi;
if (_current_dpi == _config.dpis.value().end())
_current_dpi = _config.dpis.value().begin();
run_task([self_weak = self<CycleDPI>(), dpi = *_current_dpi] {
if (auto self = self_weak.lock()) {
try {
self->_dpi->setDPI(dpi, self->_config.sensor.value_or(0));
} catch (backend::hidpp20::Error& e) {
if (e.code() == backend::hidpp20::Error::InvalidArgument)
logPrintf(WARN, "%s:%d: Could not set DPI to %d for "
"sensor %d",
self->_device->hidpp20().devicePath().c_str(),
self->_device->hidpp20().deviceIndex(), dpi,
self->_config.sensor.value_or(0));
else
throw e;
}
}
});
}
}
void CycleDPI::release() {
_pressed = false;
}
uint8_t CycleDPI::reprogFlags() const {
return backend::hidpp20::ReprogControls::TemporaryDiverted;
}

View File

@ -1,50 +1,50 @@
/*
* Copyright 2019-2023 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_CYCLEDPI_H
#define LOGID_ACTION_CYCLEDPI_H
#include <actions/Action.h>
#include <features/DPI.h>
namespace logid::actions {
class CycleDPI : public Action {
public:
static const char* interface_name;
CycleDPI(Device* device, config::CycleDPI& setting,
const std::shared_ptr<ipcgull::node>& parent);
void press() final;
void release() final;
[[nodiscard]] std::vector<int> getDPIs() const;
void setDPIs(const std::vector<int>& dpis);
[[nodiscard]] uint8_t reprogFlags() const final;
protected:
std::mutex _dpi_mutex;
config::CycleDPI& _config;
std::shared_ptr<features::DPI> _dpi;
std::list<int>::const_iterator _current_dpi;
};
}
/*
* Copyright 2019-2023 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_CYCLEDPI_H
#define LOGID_ACTION_CYCLEDPI_H
#include <actions/Action.h>
#include <features/DPI.h>
namespace logid::actions {
class CycleDPI : public Action {
public:
static const char* interface_name;
CycleDPI(Device* device, config::CycleDPI& setting,
const std::shared_ptr<ipcgull::node>& parent);
void press() final;
void release() final;
[[nodiscard]] std::vector<int> getDPIs() const;
void setDPIs(const std::vector<int>& dpis);
[[nodiscard]] uint8_t reprogFlags() const final;
protected:
std::mutex _dpi_mutex;
config::CycleDPI& _config;
std::shared_ptr<features::DPI> _dpi;
std::list<int>::const_iterator _current_dpi;
};
}
#endif //LOGID_ACTION_CYCLEDPI_H

View File

@ -1,261 +1,261 @@
/*
* Copyright 2019-2023 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 <actions/GestureAction.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/log.h>
#include <algorithm>
using namespace logid::actions;
using namespace logid;
using namespace logid::backend;
const char* GestureAction::interface_name = "Gesture";
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");
}
std::string GestureAction::fromDirection(Direction direction) {
switch (direction) {
case Up:
return "up";
case Down:
return "down";
case Left:
return "left";
case Right:
return "right";
case None:
return "none";
}
// This shouldn't happen
throw InvalidGesture();
}
GestureAction::Direction GestureAction::toDirection(int32_t x, int32_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, config::GestureAction& config,
const std::shared_ptr<ipcgull::node>& parent) :
Action(dev, interface_name,
{
{
{"SetGesture", {this, &GestureAction::setGesture, {"direction", "type"}}}
},
{},
{}
}),
_node(parent->make_child("gestures")), _config(config) {
if (_config.gestures.has_value()) {
auto& gestures = _config.gestures.value();
for (auto&& x: gestures) {
try {
auto direction = toDirection(x.first);
_gestures.emplace(
direction,Gesture::makeGesture(
dev, x.second,
_node->make_child(fromDirection(direction))));
if (direction == None) {
auto& gesture = x.second;
std::visit([](auto&& x) {
x.threshold.emplace(0);
}, gesture);
}
} catch (std::invalid_argument& e) {
logPrintf(WARN, "%s is not a direction", x.first.c_str());
}
}
}
}
void GestureAction::press() {
std::shared_lock lock(_config_mutex);
_pressed = true;
_x = 0, _y = 0;
for (auto& gesture: _gestures)
gesture.second->press(false);
}
void GestureAction::release() {
std::shared_lock lock(_config_mutex);
_pressed = false;
bool threshold_met = false;
auto d = toDirection(_x, _y);
auto primary_gesture = _gestures.find(d);
if (primary_gesture != _gestures.end()) {
threshold_met = primary_gesture->second->metThreshold();
primary_gesture->second->release(true);
}
for (auto& gesture: _gestures) {
if (gesture.first == d || gesture.first == None)
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);
}
} else {
gesture.second->release(false);
}
}
auto none_gesture = _gestures.find(None);
if (none_gesture != _gestures.end()) {
none_gesture->second->release(!threshold_met);
}
}
void GestureAction::move(int16_t x, int16_t y) {
std::shared_lock lock(_config_mutex);
int32_t new_x = _x + x, new_y = _y + y;
if (abs(x) > 0) {
if (_x < 0 && new_x >= 0) { // Left -> Origin/Right
auto left = _gestures.find(Left);
if (left != _gestures.end() && left->second)
left->second->move((int16_t) _x);
if (new_x) { // Ignore to origin
auto right = _gestures.find(Right);
if (right != _gestures.end() && right->second)
right->second->move((int16_t) new_x);
}
} else if (_x > 0 && new_x <= 0) { // Right -> Origin/Left
auto right = _gestures.find(Right);
if (right != _gestures.end() && right->second)
right->second->move((int16_t) -_x);
if (new_x) { // Ignore to origin
auto left = _gestures.find(Left);
if (left != _gestures.end() && left->second)
left->second->move((int16_t) -new_x);
}
} else if (new_x < 0) { // Origin/Left to Left
auto left = _gestures.find(Left);
if (left != _gestures.end() && left->second)
left->second->move((int16_t) -x);
} else if (new_x > 0) { // Origin/Right to Right
auto right = _gestures.find(Right);
if (right != _gestures.end() && right->second)
right->second->move(x);
}
}
if (abs(y) > 0) {
if (_y > 0 && new_y <= 0) { // Up -> Origin/Down
auto up = _gestures.find(Up);
if (up != _gestures.end() && up->second)
up->second->move((int16_t) _y);
if (new_y) { // Ignore to origin
auto down = _gestures.find(Down);
if (down != _gestures.end() && down->second)
down->second->move((int16_t) new_y);
}
} else if (_y < 0 && new_y >= 0) { // Down -> Origin/Up
auto down = _gestures.find(Down);
if (down != _gestures.end() && down->second)
down->second->move((int16_t) -_y);
if (new_y) { // Ignore to origin
auto up = _gestures.find(Up);
if (up != _gestures.end() && up->second)
up->second->move((int16_t) -new_y);
}
} else if (new_y < 0) { // Origin/Up to Up
auto up = _gestures.find(Up);
if (up != _gestures.end() && up->second)
up->second->move((int16_t) -y);
} else if (new_y > 0) {// Origin/Down to Down
auto down = _gestures.find(Down);
if (down != _gestures.end() && down->second)
down->second->move(y);
}
}
_x = new_x;
_y = new_y;
}
uint8_t GestureAction::reprogFlags() const {
return (hidpp20::ReprogControls::TemporaryDiverted |
hidpp20::ReprogControls::RawXYDiverted);
}
void GestureAction::setGesture(const std::string& direction, const std::string& type) {
std::unique_lock lock(_config_mutex);
Direction d = toDirection(direction);
auto it = _gestures.find(d);
if (it != _gestures.end()) {
if (pressed()) {
auto current = toDirection(_x, _y);
if (it->second)
it->second->release(current == d);
}
}
auto dir_name = fromDirection(d);
auto& gesture = _config.gestures.value()[dir_name];
_gestures[d].reset();
try {
_gestures[d] = Gesture::makeGesture(
_device, type, gesture,
_node->make_child(dir_name));
} catch (InvalidGesture& e) {
_gestures[d] = Gesture::makeGesture(
_device, gesture,
_node->make_child(dir_name));
throw std::invalid_argument("Invalid gesture type");
}
if (d == None) {
std::visit([](auto&& x) {
x.threshold = 0;
}, gesture);
}
}
/*
* Copyright 2019-2023 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 <actions/GestureAction.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/log.h>
#include <algorithm>
using namespace logid::actions;
using namespace logid;
using namespace logid::backend;
const char* GestureAction::interface_name = "Gesture";
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");
}
std::string GestureAction::fromDirection(Direction direction) {
switch (direction) {
case Up:
return "up";
case Down:
return "down";
case Left:
return "left";
case Right:
return "right";
case None:
return "none";
}
// This shouldn't happen
throw InvalidGesture();
}
GestureAction::Direction GestureAction::toDirection(int32_t x, int32_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, config::GestureAction& config,
const std::shared_ptr<ipcgull::node>& parent) :
Action(dev, interface_name,
{
{
{"SetGesture", {this, &GestureAction::setGesture, {"direction", "type"}}}
},
{},
{}
}),
_node(parent->make_child("gestures")), _config(config) {
if (_config.gestures.has_value()) {
auto& gestures = _config.gestures.value();
for (auto&& x: gestures) {
try {
auto direction = toDirection(x.first);
_gestures.emplace(
direction,Gesture::makeGesture(
dev, x.second,
_node->make_child(fromDirection(direction))));
if (direction == None) {
auto& gesture = x.second;
std::visit([](auto&& x) {
x.threshold.emplace(0);
}, gesture);
}
} catch (std::invalid_argument& e) {
logPrintf(WARN, "%s is not a direction", x.first.c_str());
}
}
}
}
void GestureAction::press() {
std::shared_lock lock(_config_mutex);
_pressed = true;
_x = 0, _y = 0;
for (auto& gesture: _gestures)
gesture.second->press(false);
}
void GestureAction::release() {
std::shared_lock lock(_config_mutex);
_pressed = false;
bool threshold_met = false;
auto d = toDirection(_x, _y);
auto primary_gesture = _gestures.find(d);
if (primary_gesture != _gestures.end()) {
threshold_met = primary_gesture->second->metThreshold();
primary_gesture->second->release(true);
}
for (auto& gesture: _gestures) {
if (gesture.first == d || gesture.first == None)
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);
}
} else {
gesture.second->release(false);
}
}
auto none_gesture = _gestures.find(None);
if (none_gesture != _gestures.end()) {
none_gesture->second->release(!threshold_met);
}
}
void GestureAction::move(int16_t x, int16_t y) {
std::shared_lock lock(_config_mutex);
int32_t new_x = _x + x, new_y = _y + y;
if (abs(x) > 0) {
if (_x < 0 && new_x >= 0) { // Left -> Origin/Right
auto left = _gestures.find(Left);
if (left != _gestures.end() && left->second)
left->second->move((int16_t) _x);
if (new_x) { // Ignore to origin
auto right = _gestures.find(Right);
if (right != _gestures.end() && right->second)
right->second->move((int16_t) new_x);
}
} else if (_x > 0 && new_x <= 0) { // Right -> Origin/Left
auto right = _gestures.find(Right);
if (right != _gestures.end() && right->second)
right->second->move((int16_t) -_x);
if (new_x) { // Ignore to origin
auto left = _gestures.find(Left);
if (left != _gestures.end() && left->second)
left->second->move((int16_t) -new_x);
}
} else if (new_x < 0) { // Origin/Left to Left
auto left = _gestures.find(Left);
if (left != _gestures.end() && left->second)
left->second->move((int16_t) -x);
} else if (new_x > 0) { // Origin/Right to Right
auto right = _gestures.find(Right);
if (right != _gestures.end() && right->second)
right->second->move(x);
}
}
if (abs(y) > 0) {
if (_y > 0 && new_y <= 0) { // Up -> Origin/Down
auto up = _gestures.find(Up);
if (up != _gestures.end() && up->second)
up->second->move((int16_t) _y);
if (new_y) { // Ignore to origin
auto down = _gestures.find(Down);
if (down != _gestures.end() && down->second)
down->second->move((int16_t) new_y);
}
} else if (_y < 0 && new_y >= 0) { // Down -> Origin/Up
auto down = _gestures.find(Down);
if (down != _gestures.end() && down->second)
down->second->move((int16_t) -_y);
if (new_y) { // Ignore to origin
auto up = _gestures.find(Up);
if (up != _gestures.end() && up->second)
up->second->move((int16_t) -new_y);
}
} else if (new_y < 0) { // Origin/Up to Up
auto up = _gestures.find(Up);
if (up != _gestures.end() && up->second)
up->second->move((int16_t) -y);
} else if (new_y > 0) {// Origin/Down to Down
auto down = _gestures.find(Down);
if (down != _gestures.end() && down->second)
down->second->move(y);
}
}
_x = new_x;
_y = new_y;
}
uint8_t GestureAction::reprogFlags() const {
return (hidpp20::ReprogControls::TemporaryDiverted |
hidpp20::ReprogControls::RawXYDiverted);
}
void GestureAction::setGesture(const std::string& direction, const std::string& type) {
std::unique_lock lock(_config_mutex);
Direction d = toDirection(direction);
auto it = _gestures.find(d);
if (it != _gestures.end()) {
if (pressed()) {
auto current = toDirection(_x, _y);
if (it->second)
it->second->release(current == d);
}
}
auto dir_name = fromDirection(d);
auto& gesture = _config.gestures.value()[dir_name];
_gestures[d].reset();
try {
_gestures[d] = Gesture::makeGesture(
_device, type, gesture,
_node->make_child(dir_name));
} catch (InvalidGesture& e) {
_gestures[d] = Gesture::makeGesture(
_device, gesture,
_node->make_child(dir_name));
throw std::invalid_argument("Invalid gesture type");
}
if (d == None) {
std::visit([](auto&& x) {
x.threshold = 0;
}, gesture);
}
}

View File

@ -1,66 +1,66 @@
/*
* Copyright 2019-2023 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_GESTUREACTION_H
#define LOGID_ACTION_GESTUREACTION_H
#include <map>
#include <actions/Action.h>
#include <actions/gesture/Gesture.h>
namespace logid::actions {
class GestureAction : public Action {
public:
static const char* interface_name;
enum Direction {
None,
Up,
Down,
Left,
Right
};
static Direction toDirection(std::string direction);
static std::string fromDirection(Direction direction);
static Direction toDirection(int32_t x, int32_t y);
GestureAction(Device* dev, config::GestureAction& config,
const std::shared_ptr<ipcgull::node>& parent);
void press() final;
void release() final;
void move(int16_t x, int16_t y) final;
uint8_t reprogFlags() const final;
void setGesture(const std::string& direction,
const std::string& type);
protected:
int32_t _x{}, _y{};
std::shared_ptr<ipcgull::node> _node;
std::map<Direction, std::shared_ptr<Gesture>> _gestures;
config::GestureAction& _config;
};
}
#endif //LOGID_ACTION_GESTUREACTION_H
/*
* Copyright 2019-2023 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_GESTUREACTION_H
#define LOGID_ACTION_GESTUREACTION_H
#include <map>
#include <actions/Action.h>
#include <actions/gesture/Gesture.h>
namespace logid::actions {
class GestureAction : public Action {
public:
static const char* interface_name;
enum Direction {
None,
Up,
Down,
Left,
Right
};
static Direction toDirection(std::string direction);
static std::string fromDirection(Direction direction);
static Direction toDirection(int32_t x, int32_t y);
GestureAction(Device* dev, config::GestureAction& config,
const std::shared_ptr<ipcgull::node>& parent);
void press() final;
void release() final;
void move(int16_t x, int16_t y) final;
uint8_t reprogFlags() const final;
void setGesture(const std::string& direction,
const std::string& type);
protected:
int32_t _x{}, _y{};
std::shared_ptr<ipcgull::node> _node;
std::map<Direction, std::shared_ptr<Gesture>> _gestures;
config::GestureAction& _config;
};
}
#endif //LOGID_ACTION_GESTUREACTION_H

View File

@ -1,126 +1,126 @@
/*
* Copyright 2019-2023 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 <actions/KeypressAction.h>
#include <Device.h>
#include <InputDevice.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/log.h>
using namespace logid::actions;
using namespace logid::backend;
const char* KeypressAction::interface_name = "Keypress";
KeypressAction::KeypressAction(
Device* device, config::KeypressAction& config,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(device, interface_name, {
{
{"GetKeys", {this, &KeypressAction::getKeys, {"keys"}}},
{"SetKeys", {this, &KeypressAction::setKeys, {"keys"}}}
},
{},
{}
}), _config(config) {
_setConfig();
}
void KeypressAction::press() {
std::shared_lock lock(_config_mutex);
_pressed = true;
for (auto& key: _keys)
_device->virtualInput()->pressKey(key);
}
void KeypressAction::release() {
std::shared_lock lock(_config_mutex);
_pressed = false;
for (auto& key: _keys)
_device->virtualInput()->releaseKey(key);
}
void KeypressAction::_setConfig() {
_keys.clear();
if (!_config.keys.has_value())
return;
auto& config = _config.keys.value();
if (std::holds_alternative<std::string>(config)) {
const auto& key = std::get<std::string>(config);
try {
auto code = _device->virtualInput()->toKeyCode(key);
_device->virtualInput()->registerKey(code);
_keys.emplace_back(code);
} catch (InputDevice::InvalidEventCode& e) {
logPrintf(WARN, "Invalid keycode %s, skipping.", key.c_str());
}
} else if (std::holds_alternative<uint>(_config.keys.value())) {
const auto& key = std::get<uint>(config);
_device->virtualInput()->registerKey(key);
_keys.emplace_back(key);
} else if (std::holds_alternative<
std::list<std::variant<uint, std::string>>>(config)) {
const auto& keys = std::get<
std::list<std::variant<uint, std::string>>>(config);
for (const auto& key: keys) {
if (std::holds_alternative<std::string>(key)) {
const auto& key_str = std::get<std::string>(key);
try {
auto code = _device->virtualInput()->toKeyCode(key_str);
_device->virtualInput()->registerKey(code);
_keys.emplace_back(code);
} catch (InputDevice::InvalidEventCode& e) {
logPrintf(WARN, "Invalid keycode %s, skipping.",
key_str.c_str());
}
} else if (std::holds_alternative<uint>(key)) {
auto& code = std::get<uint>(key);
_device->virtualInput()->registerKey(code);
_keys.emplace_back(code);
}
}
}
}
uint8_t KeypressAction::reprogFlags() const {
return hidpp20::ReprogControls::TemporaryDiverted;
}
std::vector<std::string> KeypressAction::getKeys() const {
std::shared_lock lock(_config_mutex);
std::vector<std::string> ret;
for (auto& x: _keys)
ret.push_back(InputDevice::toKeyName(x));
return ret;
}
void KeypressAction::setKeys(const std::vector<std::string>& keys) {
std::unique_lock lock(_config_mutex);
if (_pressed)
for (auto& key: _keys)
_device->virtualInput()->releaseKey(key);
_config.keys = std::list<std::variant<uint, std::string>>();
auto& config = std::get<std::list<std::variant<uint, std::string>>>(
_config.keys.value());
for (auto& x: keys)
config.emplace_back(x);
_setConfig();
}
/*
* Copyright 2019-2023 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 <actions/KeypressAction.h>
#include <Device.h>
#include <InputDevice.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/log.h>
using namespace logid::actions;
using namespace logid::backend;
const char* KeypressAction::interface_name = "Keypress";
KeypressAction::KeypressAction(
Device* device, config::KeypressAction& config,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(device, interface_name, {
{
{"GetKeys", {this, &KeypressAction::getKeys, {"keys"}}},
{"SetKeys", {this, &KeypressAction::setKeys, {"keys"}}}
},
{},
{}
}), _config(config) {
_setConfig();
}
void KeypressAction::press() {
std::shared_lock lock(_config_mutex);
_pressed = true;
for (auto& key: _keys)
_device->virtualInput()->pressKey(key);
}
void KeypressAction::release() {
std::shared_lock lock(_config_mutex);
_pressed = false;
for (auto& key: _keys)
_device->virtualInput()->releaseKey(key);
}
void KeypressAction::_setConfig() {
_keys.clear();
if (!_config.keys.has_value())
return;
auto& config = _config.keys.value();
if (std::holds_alternative<std::string>(config)) {
const auto& key = std::get<std::string>(config);
try {
auto code = _device->virtualInput()->toKeyCode(key);
_device->virtualInput()->registerKey(code);
_keys.emplace_back(code);
} catch (InputDevice::InvalidEventCode& e) {
logPrintf(WARN, "Invalid keycode %s, skipping.", key.c_str());
}
} else if (std::holds_alternative<uint>(_config.keys.value())) {
const auto& key = std::get<uint>(config);
_device->virtualInput()->registerKey(key);
_keys.emplace_back(key);
} else if (std::holds_alternative<
std::list<std::variant<uint, std::string>>>(config)) {
const auto& keys = std::get<
std::list<std::variant<uint, std::string>>>(config);
for (const auto& key: keys) {
if (std::holds_alternative<std::string>(key)) {
const auto& key_str = std::get<std::string>(key);
try {
auto code = _device->virtualInput()->toKeyCode(key_str);
_device->virtualInput()->registerKey(code);
_keys.emplace_back(code);
} catch (InputDevice::InvalidEventCode& e) {
logPrintf(WARN, "Invalid keycode %s, skipping.",
key_str.c_str());
}
} else if (std::holds_alternative<uint>(key)) {
auto& code = std::get<uint>(key);
_device->virtualInput()->registerKey(code);
_keys.emplace_back(code);
}
}
}
}
uint8_t KeypressAction::reprogFlags() const {
return hidpp20::ReprogControls::TemporaryDiverted;
}
std::vector<std::string> KeypressAction::getKeys() const {
std::shared_lock lock(_config_mutex);
std::vector<std::string> ret;
for (auto& x: _keys)
ret.push_back(InputDevice::toKeyName(x));
return ret;
}
void KeypressAction::setKeys(const std::vector<std::string>& keys) {
std::unique_lock lock(_config_mutex);
if (_pressed)
for (auto& key: _keys)
_device->virtualInput()->releaseKey(key);
_config.keys = std::list<std::variant<uint, std::string>>();
auto& config = std::get<std::list<std::variant<uint, std::string>>>(
_config.keys.value());
for (auto& x: keys)
config.emplace_back(x);
_setConfig();
}

View File

@ -1,51 +1,51 @@
/*
* Copyright 2019-2023 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_KEYPRESS_H
#define LOGID_ACTION_KEYPRESS_H
#include <vector>
#include <actions/Action.h>
namespace logid::actions {
class KeypressAction : public Action {
public:
static const char* interface_name;
KeypressAction(Device* dev,
config::KeypressAction& config,
const std::shared_ptr<ipcgull::node>& parent);
void press() final;
void release() final;
[[nodiscard]] std::vector<std::string> getKeys() const;
void setKeys(const std::vector<std::string>& keys);
[[nodiscard]] uint8_t reprogFlags() const final;
protected:
config::KeypressAction& _config;
std::list<uint> _keys;
void _setConfig();
};
}
#endif //LOGID_ACTION_KEYPRESS_H
/*
* Copyright 2019-2023 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_KEYPRESS_H
#define LOGID_ACTION_KEYPRESS_H
#include <vector>
#include <actions/Action.h>
namespace logid::actions {
class KeypressAction : public Action {
public:
static const char* interface_name;
KeypressAction(Device* dev,
config::KeypressAction& config,
const std::shared_ptr<ipcgull::node>& parent);
void press() final;
void release() final;
[[nodiscard]] std::vector<std::string> getKeys() const;
void setKeys(const std::vector<std::string>& keys);
[[nodiscard]] uint8_t reprogFlags() const final;
protected:
config::KeypressAction& _config;
std::list<uint> _keys;
void _setConfig();
};
}
#endif //LOGID_ACTION_KEYPRESS_H

View File

@ -1,41 +1,41 @@
/*
* Copyright 2019-2023 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 <actions/NullAction.h>
#include <backend/hidpp20/features/ReprogControls.h>
using namespace logid::actions;
const char* NullAction::interface_name = "None";
NullAction::NullAction(
Device* device,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(device, interface_name) {
}
void NullAction::press() {
_pressed = true;
}
void NullAction::release() {
_pressed = false;
}
uint8_t NullAction::reprogFlags() const {
return backend::hidpp20::ReprogControls::TemporaryDiverted;
/*
* Copyright 2019-2023 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 <actions/NullAction.h>
#include <backend/hidpp20/features/ReprogControls.h>
using namespace logid::actions;
const char* NullAction::interface_name = "None";
NullAction::NullAction(
Device* device,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(device, interface_name) {
}
void NullAction::press() {
_pressed = true;
}
void NullAction::release() {
_pressed = false;
}
uint8_t NullAction::reprogFlags() const {
return backend::hidpp20::ReprogControls::TemporaryDiverted;
}

View File

@ -1,44 +1,44 @@
/*
* Copyright 2019-2023 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_NULL_H
#define LOGID_ACTION_NULL_H
#include <actions/Action.h>
namespace logid::actions {
class NullAction : public Action {
public:
static const char* interface_name;
NullAction(Device* device,
const std::shared_ptr<ipcgull::node>& parent);
NullAction(Device* device, [[maybe_unused]] config::NoAction& config,
const std::shared_ptr<ipcgull::node>& parent) :
NullAction(device, parent) {}
void press() final;
void release() final;
[[nodiscard]] uint8_t reprogFlags() const final;
};
}
#endif //LOGID_ACTION_NULL_H
/*
* Copyright 2019-2023 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_NULL_H
#define LOGID_ACTION_NULL_H
#include <actions/Action.h>
namespace logid::actions {
class NullAction : public Action {
public:
static const char* interface_name;
NullAction(Device* device,
const std::shared_ptr<ipcgull::node>& parent);
NullAction(Device* device, [[maybe_unused]] config::NoAction& config,
const std::shared_ptr<ipcgull::node>& parent) :
NullAction(device, parent) {}
void press() final;
void release() final;
[[nodiscard]] uint8_t reprogFlags() const final;
};
}
#endif //LOGID_ACTION_NULL_H

View File

@ -1,60 +1,60 @@
/*
* Copyright 2019-2023 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 <actions/ToggleHiresScroll.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/task.h>
#include <util/log.h>
using namespace logid::actions;
using namespace logid::backend;
const char* ToggleHiresScroll::interface_name = "ToggleHiresScroll";
ToggleHiresScroll::ToggleHiresScroll(
Device* dev,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(dev, interface_name) {
_hires_scroll = _device->getFeature<features::HiresScroll>("hiresscroll");
if (!_hires_scroll)
logPrintf(WARN, "%s:%d: HiresScroll feature not found, cannot use "
"ToggleHiresScroll action.",
_device->hidpp20().devicePath().c_str(),
_device->hidpp20().devicePath().c_str());
}
void ToggleHiresScroll::press() {
_pressed = true;
if (_hires_scroll) {
run_task([self_weak = self<ToggleHiresScroll>()]() {
if (auto self = self_weak.lock()) {
auto mode = self->_hires_scroll->getMode();
mode ^= backend::hidpp20::HiresScroll::HiRes;
self->_hires_scroll->setMode(mode);
}
});
}
}
void ToggleHiresScroll::release() {
_pressed = false;
}
uint8_t ToggleHiresScroll::reprogFlags() const {
return hidpp20::ReprogControls::TemporaryDiverted;
}
/*
* Copyright 2019-2023 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 <actions/ToggleHiresScroll.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/task.h>
#include <util/log.h>
using namespace logid::actions;
using namespace logid::backend;
const char* ToggleHiresScroll::interface_name = "ToggleHiresScroll";
ToggleHiresScroll::ToggleHiresScroll(
Device* dev,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(dev, interface_name) {
_hires_scroll = _device->getFeature<features::HiresScroll>("hiresscroll");
if (!_hires_scroll)
logPrintf(WARN, "%s:%d: HiresScroll feature not found, cannot use "
"ToggleHiresScroll action.",
_device->hidpp20().devicePath().c_str(),
_device->hidpp20().devicePath().c_str());
}
void ToggleHiresScroll::press() {
_pressed = true;
if (_hires_scroll) {
run_task([self_weak = self<ToggleHiresScroll>()]() {
if (auto self = self_weak.lock()) {
auto mode = self->_hires_scroll->getMode();
mode ^= backend::hidpp20::HiresScroll::HiRes;
self->_hires_scroll->setMode(mode);
}
});
}
}
void ToggleHiresScroll::release() {
_pressed = false;
}
uint8_t ToggleHiresScroll::reprogFlags() const {
return hidpp20::ReprogControls::TemporaryDiverted;
}

View File

@ -1,47 +1,47 @@
/*
* Copyright 2019-2023 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_TOGGLEHIRESSCROLL_H
#define LOGID_ACTION_TOGGLEHIRESSCROLL_H
#include <actions/Action.h>
#include <features/HiresScroll.h>
namespace logid::actions {
class ToggleHiresScroll : public Action {
public:
static const char* interface_name;
ToggleHiresScroll(Device* dev, const std::shared_ptr<ipcgull::node>& parent);
ToggleHiresScroll(Device* device,
[[maybe_unused]] config::ToggleHiresScroll& action,
const std::shared_ptr<ipcgull::node>& parent) :
ToggleHiresScroll(device, parent) {}
void press() final;
void release() final;
[[nodiscard]] uint8_t reprogFlags() const final;
protected:
std::shared_ptr<features::HiresScroll> _hires_scroll;
};
}
#endif //LOGID_ACTION_TOGGLEHIRESSCROLL_H
/*
* Copyright 2019-2023 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_TOGGLEHIRESSCROLL_H
#define LOGID_ACTION_TOGGLEHIRESSCROLL_H
#include <actions/Action.h>
#include <features/HiresScroll.h>
namespace logid::actions {
class ToggleHiresScroll : public Action {
public:
static const char* interface_name;
ToggleHiresScroll(Device* dev, const std::shared_ptr<ipcgull::node>& parent);
ToggleHiresScroll(Device* device,
[[maybe_unused]] config::ToggleHiresScroll& action,
const std::shared_ptr<ipcgull::node>& parent) :
ToggleHiresScroll(device, parent) {}
void press() final;
void release() final;
[[nodiscard]] uint8_t reprogFlags() const final;
protected:
std::shared_ptr<features::HiresScroll> _hires_scroll;
};
}
#endif //LOGID_ACTION_TOGGLEHIRESSCROLL_H

View File

@ -1,62 +1,62 @@
/*
* Copyright 2019-2023 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 <actions/ToggleSmartShift.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/task.h>
#include <util/log.h>
using namespace logid::actions;
using namespace logid::backend;
const char* ToggleSmartShift::interface_name = "ToggleSmartShift";
ToggleSmartShift::ToggleSmartShift(
Device* dev,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(dev, interface_name) {
_smartshift = _device->getFeature<features::SmartShift>("smartshift");
if (!_smartshift)
logPrintf(WARN, "%s:%d: SmartShift feature not found, cannot use "
"ToggleSmartShift action.",
_device->hidpp20().devicePath().c_str(),
_device->hidpp20().deviceIndex());
}
void ToggleSmartShift::press() {
_pressed = true;
if (_smartshift) {
run_task([self_weak = self<ToggleSmartShift>()]() {
if (auto self = self_weak.lock()) {
auto status = self->_smartshift->getStatus();
status.setActive = true;
status.active = !status.active;
self->_smartshift->setStatus(status);
}
});
}
}
void ToggleSmartShift::release() {
_pressed = false;
}
uint8_t ToggleSmartShift::reprogFlags() const {
return hidpp20::ReprogControls::TemporaryDiverted;
}
/*
* Copyright 2019-2023 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 <actions/ToggleSmartShift.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/task.h>
#include <util/log.h>
using namespace logid::actions;
using namespace logid::backend;
const char* ToggleSmartShift::interface_name = "ToggleSmartShift";
ToggleSmartShift::ToggleSmartShift(
Device* dev,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(dev, interface_name) {
_smartshift = _device->getFeature<features::SmartShift>("smartshift");
if (!_smartshift)
logPrintf(WARN, "%s:%d: SmartShift feature not found, cannot use "
"ToggleSmartShift action.",
_device->hidpp20().devicePath().c_str(),
_device->hidpp20().deviceIndex());
}
void ToggleSmartShift::press() {
_pressed = true;
if (_smartshift) {
run_task([self_weak = self<ToggleSmartShift>()]() {
if (auto self = self_weak.lock()) {
auto status = self->_smartshift->getStatus();
status.setActive = true;
status.active = !status.active;
self->_smartshift->setStatus(status);
}
});
}
}
void ToggleSmartShift::release() {
_pressed = false;
}
uint8_t ToggleSmartShift::reprogFlags() const {
return hidpp20::ReprogControls::TemporaryDiverted;
}

View File

@ -1,48 +1,48 @@
/*
* Copyright 2019-2023 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_TOGGLESMARTSHIFT_H
#define LOGID_ACTION_TOGGLESMARTSHIFT_H
#include <actions/Action.h>
#include <features/SmartShift.h>
namespace logid::actions {
class ToggleSmartShift : public Action {
public:
static const char* interface_name;
ToggleSmartShift(Device* dev,
const std::shared_ptr<ipcgull::node>& parent);
ToggleSmartShift(Device* device,
[[maybe_unused]] config::ToggleSmartShift& action,
const std::shared_ptr<ipcgull::node>& parent) :
ToggleSmartShift(device, parent) {}
void press() final;
void release() final;
[[nodiscard]] uint8_t reprogFlags() const final;
protected:
std::shared_ptr<features::SmartShift> _smartshift;
};
}
/*
* Copyright 2019-2023 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_TOGGLESMARTSHIFT_H
#define LOGID_ACTION_TOGGLESMARTSHIFT_H
#include <actions/Action.h>
#include <features/SmartShift.h>
namespace logid::actions {
class ToggleSmartShift : public Action {
public:
static const char* interface_name;
ToggleSmartShift(Device* dev,
const std::shared_ptr<ipcgull::node>& parent);
ToggleSmartShift(Device* device,
[[maybe_unused]] config::ToggleSmartShift& action,
const std::shared_ptr<ipcgull::node>& parent) :
ToggleSmartShift(device, parent) {}
void press() final;
void release() final;
[[nodiscard]] uint8_t reprogFlags() const final;
protected:
std::shared_ptr<features::SmartShift> _smartshift;
};
}
#endif //LOGID_ACTION_TOGGLESMARTSHIFT_H

View File

@ -1,185 +1,185 @@
/*
* Copyright 2019-2023 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 <cmath>
#include <actions/gesture/AxisGesture.h>
#include <Device.h>
#include <InputDevice.h>
#include <util/log.h>
using namespace logid::actions;
const char* AxisGesture::interface_name = "Axis";
AxisGesture::AxisGesture(Device* device, config::AxisGesture& config,
const std::shared_ptr<ipcgull::node>& parent) :
Gesture(device, parent, interface_name, {
{
{"GetConfig", {this, &AxisGesture::getConfig, {"axis", "multiplier", "threshold"}}},
{"SetThreshold", {this, &AxisGesture::setThreshold, {"threshold"}}},
{"SetMultiplier", {this, &AxisGesture::setMultiplier, {"multiplier"}}},
{"SetAxis", {this, &AxisGesture::setAxis, {"axis"}}}
},
{},
{}
}), _multiplier(1), _config(config) {
if (_config.axis.has_value()) {
if (std::holds_alternative<uint>(_config.axis.value())) {
_input_axis = std::get<uint>(_config.axis.value());
} else {
const auto& axis = std::get<std::string>(_config.axis.value());
try {
_input_axis = _device->virtualInput()->toAxisCode(axis);
} catch (InputDevice::InvalidEventCode& e) {
logPrintf(WARN, "Invalid axis %s.");
}
}
}
if (_input_axis.has_value())
_device->virtualInput()->registerAxis(_input_axis.value());
}
void AxisGesture::press(bool init_threshold) {
std::shared_lock lock(_config_mutex);
if (init_threshold) {
_axis = (int32_t) (_config.threshold.value_or(defaults::gesture_threshold));
} else {
_axis = 0;
}
_axis_remainder = 0;
_hires_remainder = 0;
}
void AxisGesture::release(bool primary) {
// Do nothing
(void) primary; // Suppress unused warning
}
void AxisGesture::move(int16_t axis) {
std::shared_lock lock(_config_mutex);
if (!_input_axis.has_value())
return;
const auto threshold = _config.threshold.value_or(
defaults::gesture_threshold);
int32_t new_axis = _axis + axis;
int low_res_axis = InputDevice::getLowResAxis(axis);
int hires_remainder = _hires_remainder;
if (new_axis > threshold) {
double move = axis;
if (_axis < threshold)
move = new_axis - threshold;
bool negative_multiplier = _config.axis_multiplier.value_or(1) < 0;
if (negative_multiplier)
move *= -_config.axis_multiplier.value_or(1);
else
move *= _config.axis_multiplier.value_or(1);
// Handle hi-res multiplier
move *= _multiplier;
double move_floor = floor(move);
_axis_remainder = move - move_floor;
if (_axis_remainder >= 1) {
double int_remainder = floor(_axis_remainder);
move_floor += int_remainder;
_axis_remainder -= int_remainder;
}
if (negative_multiplier)
move_floor = -move_floor;
if (low_res_axis != -1) {
int lowres_movement, hires_movement = (int) move_floor;
_device->virtualInput()->moveAxis(_input_axis.value(), hires_movement);
hires_remainder += hires_movement;
if (abs(hires_remainder) >= 60) {
lowres_movement = hires_remainder / 120;
if (lowres_movement == 0)
lowres_movement = hires_remainder > 0 ? 1 : -1;
hires_remainder -= lowres_movement * 120;
_device->virtualInput()->moveAxis(low_res_axis, lowres_movement);
}
_hires_remainder = hires_remainder;
} else {
_device->virtualInput()->moveAxis(_input_axis.value(), (int) move_floor);
}
}
_axis = new_axis;
}
bool AxisGesture::metThreshold() const {
std::shared_lock lock(_config_mutex);
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
}
bool AxisGesture::wheelCompatibility() const {
return true;
}
void AxisGesture::setHiresMultiplier(double multiplier) {
_hires_multiplier = multiplier;
if (_input_axis.has_value()) {
if (InputDevice::getLowResAxis(_input_axis.value()) != -1)
_multiplier = _config.axis_multiplier.value_or(1) * multiplier;
}
}
std::tuple<std::string, double, int> AxisGesture::getConfig() const {
std::shared_lock lock(_config_mutex);
std::string axis;
if (_config.axis.has_value()) {
if (std::holds_alternative<std::string>(_config.axis.value())) {
axis = std::get<std::string>(_config.axis.value());
} else {
axis = _device->virtualInput()->toAxisName(std::get<uint>(_config.axis.value()));
}
}
return {axis, _config.axis_multiplier.value_or(1), _config.threshold.value_or(0)};
}
void AxisGesture::setAxis(const std::string& axis) {
std::unique_lock lock(_config_mutex);
if (axis.empty()) {
_config.axis.reset();
_input_axis.reset();
} else {
_input_axis = _device->virtualInput()->toAxisCode(axis);
_config.axis = axis;
_device->virtualInput()->registerAxis(_input_axis.value());
}
setHiresMultiplier(_hires_multiplier);
}
void AxisGesture::setMultiplier(double multiplier) {
std::unique_lock lock(_config_mutex);
_config.axis_multiplier = multiplier;
_multiplier = multiplier;
setHiresMultiplier(_hires_multiplier);
}
void AxisGesture::setThreshold(int threshold) {
std::unique_lock lock(_config_mutex);
if (threshold == 0)
_config.threshold.reset();
else
_config.threshold = threshold;
}
/*
* Copyright 2019-2023 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 <cmath>
#include <actions/gesture/AxisGesture.h>
#include <Device.h>
#include <InputDevice.h>
#include <util/log.h>
using namespace logid::actions;
const char* AxisGesture::interface_name = "Axis";
AxisGesture::AxisGesture(Device* device, config::AxisGesture& config,
const std::shared_ptr<ipcgull::node>& parent) :
Gesture(device, parent, interface_name, {
{
{"GetConfig", {this, &AxisGesture::getConfig, {"axis", "multiplier", "threshold"}}},
{"SetThreshold", {this, &AxisGesture::setThreshold, {"threshold"}}},
{"SetMultiplier", {this, &AxisGesture::setMultiplier, {"multiplier"}}},
{"SetAxis", {this, &AxisGesture::setAxis, {"axis"}}}
},
{},
{}
}), _multiplier(1), _config(config) {
if (_config.axis.has_value()) {
if (std::holds_alternative<uint>(_config.axis.value())) {
_input_axis = std::get<uint>(_config.axis.value());
} else {
const auto& axis = std::get<std::string>(_config.axis.value());
try {
_input_axis = _device->virtualInput()->toAxisCode(axis);
} catch (InputDevice::InvalidEventCode& e) {
logPrintf(WARN, "Invalid axis %s.");
}
}
}
if (_input_axis.has_value())
_device->virtualInput()->registerAxis(_input_axis.value());
}
void AxisGesture::press(bool init_threshold) {
std::shared_lock lock(_config_mutex);
if (init_threshold) {
_axis = (int32_t) (_config.threshold.value_or(defaults::gesture_threshold));
} else {
_axis = 0;
}
_axis_remainder = 0;
_hires_remainder = 0;
}
void AxisGesture::release(bool primary) {
// Do nothing
(void) primary; // Suppress unused warning
}
void AxisGesture::move(int16_t axis) {
std::shared_lock lock(_config_mutex);
if (!_input_axis.has_value())
return;
const auto threshold = _config.threshold.value_or(
defaults::gesture_threshold);
int32_t new_axis = _axis + axis;
int low_res_axis = InputDevice::getLowResAxis(axis);
int hires_remainder = _hires_remainder;
if (new_axis > threshold) {
double move = axis;
if (_axis < threshold)
move = new_axis - threshold;
bool negative_multiplier = _config.axis_multiplier.value_or(1) < 0;
if (negative_multiplier)
move *= -_config.axis_multiplier.value_or(1);
else
move *= _config.axis_multiplier.value_or(1);
// Handle hi-res multiplier
move *= _multiplier;
double move_floor = floor(move);
_axis_remainder = move - move_floor;
if (_axis_remainder >= 1) {
double int_remainder = floor(_axis_remainder);
move_floor += int_remainder;
_axis_remainder -= int_remainder;
}
if (negative_multiplier)
move_floor = -move_floor;
if (low_res_axis != -1) {
int lowres_movement, hires_movement = (int) move_floor;
_device->virtualInput()->moveAxis(_input_axis.value(), hires_movement);
hires_remainder += hires_movement;
if (abs(hires_remainder) >= 60) {
lowres_movement = hires_remainder / 120;
if (lowres_movement == 0)
lowres_movement = hires_remainder > 0 ? 1 : -1;
hires_remainder -= lowres_movement * 120;
_device->virtualInput()->moveAxis(low_res_axis, lowres_movement);
}
_hires_remainder = hires_remainder;
} else {
_device->virtualInput()->moveAxis(_input_axis.value(), (int) move_floor);
}
}
_axis = new_axis;
}
bool AxisGesture::metThreshold() const {
std::shared_lock lock(_config_mutex);
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
}
bool AxisGesture::wheelCompatibility() const {
return true;
}
void AxisGesture::setHiresMultiplier(double multiplier) {
_hires_multiplier = multiplier;
if (_input_axis.has_value()) {
if (InputDevice::getLowResAxis(_input_axis.value()) != -1)
_multiplier = _config.axis_multiplier.value_or(1) * multiplier;
}
}
std::tuple<std::string, double, int> AxisGesture::getConfig() const {
std::shared_lock lock(_config_mutex);
std::string axis;
if (_config.axis.has_value()) {
if (std::holds_alternative<std::string>(_config.axis.value())) {
axis = std::get<std::string>(_config.axis.value());
} else {
axis = _device->virtualInput()->toAxisName(std::get<uint>(_config.axis.value()));
}
}
return {axis, _config.axis_multiplier.value_or(1), _config.threshold.value_or(0)};
}
void AxisGesture::setAxis(const std::string& axis) {
std::unique_lock lock(_config_mutex);
if (axis.empty()) {
_config.axis.reset();
_input_axis.reset();
} else {
_input_axis = _device->virtualInput()->toAxisCode(axis);
_config.axis = axis;
_device->virtualInput()->registerAxis(_input_axis.value());
}
setHiresMultiplier(_hires_multiplier);
}
void AxisGesture::setMultiplier(double multiplier) {
std::unique_lock lock(_config_mutex);
_config.axis_multiplier = multiplier;
_multiplier = multiplier;
setHiresMultiplier(_hires_multiplier);
}
void AxisGesture::setThreshold(int threshold) {
std::unique_lock lock(_config_mutex);
if (threshold == 0)
_config.threshold.reset();
else
_config.threshold = threshold;
}

View File

@ -1,62 +1,62 @@
/*
* Copyright 2019-2023 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_AXISGESTURE_H
#define LOGID_ACTION_AXISGESTURE_H
#include <actions/gesture/Gesture.h>
namespace logid::actions {
class AxisGesture : public Gesture {
public:
static const char* interface_name;
AxisGesture(Device* device, config::AxisGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
void press(bool init_threshold) final;
void release(bool primary) final;
void move(int16_t axis) final;
[[nodiscard]] bool wheelCompatibility() const final;
[[nodiscard]] bool metThreshold() const final;
void setHiresMultiplier(double multiplier);
[[nodiscard]] std::tuple<std::string, double, int> getConfig() const;
void setAxis(const std::string& axis);
void setMultiplier(double multiplier);
void setThreshold(int threshold);
protected:
int32_t _axis{};
double _axis_remainder{};
int _hires_remainder{};
std::optional<uint> _input_axis;
double _multiplier;
double _hires_multiplier = 1.0;
config::AxisGesture& _config;
};
}
#endif //LOGID_ACTION_AXISGESTURE_H
/*
* Copyright 2019-2023 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_AXISGESTURE_H
#define LOGID_ACTION_AXISGESTURE_H
#include <actions/gesture/Gesture.h>
namespace logid::actions {
class AxisGesture : public Gesture {
public:
static const char* interface_name;
AxisGesture(Device* device, config::AxisGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
void press(bool init_threshold) final;
void release(bool primary) final;
void move(int16_t axis) final;
[[nodiscard]] bool wheelCompatibility() const final;
[[nodiscard]] bool metThreshold() const final;
void setHiresMultiplier(double multiplier);
[[nodiscard]] std::tuple<std::string, double, int> getConfig() const;
void setAxis(const std::string& axis);
void setMultiplier(double multiplier);
void setThreshold(int threshold);
protected:
int32_t _axis{};
double _axis_remainder{};
int _hires_remainder{};
std::optional<uint> _input_axis;
double _multiplier;
double _hires_multiplier = 1.0;
config::AxisGesture& _config;
};
}
#endif //LOGID_ACTION_AXISGESTURE_H

View File

@ -1,88 +1,88 @@
/*
* Copyright 2019-2023 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 <actions/gesture/Gesture.h>
#include <utility>
#include <actions/gesture/ReleaseGesture.h>
#include <actions/gesture/ThresholdGesture.h>
#include <actions/gesture/IntervalGesture.h>
#include <actions/gesture/AxisGesture.h>
#include <actions/gesture/NullGesture.h>
#include <ipc_defs.h>
using namespace logid;
using namespace logid::actions;
Gesture::Gesture(Device* device,
std::shared_ptr<ipcgull::node> node,
const std::string& name, tables t) :
ipcgull::interface(SERVICE_ROOT_NAME ".Gesture." + name, std::move(t)),
_node(std::move(node)), _device(device) {
}
namespace {
template<typename T>
struct gesture_type {
typedef typename T::gesture type;
};
template<typename T>
struct gesture_type<const T> : gesture_type<T> {
};
template<typename T>
struct gesture_type<T&> : gesture_type<T> {
};
template<typename T>
std::shared_ptr<Gesture> _makeGesture(
Device* device, T& gesture,
const std::shared_ptr<ipcgull::node>& parent) {
return parent->make_interface<typename gesture_type<T>::type>(
device, std::forward<T&>(gesture), parent);
}
}
std::shared_ptr<Gesture> Gesture::makeGesture(
Device* device, config::Gesture& gesture,
const std::shared_ptr<ipcgull::node>& parent) {
return std::visit([&device, &parent](auto&& x) {
return _makeGesture(device, x, parent);
}, gesture);
}
std::shared_ptr<Gesture> Gesture::makeGesture(
Device* device, const std::string& type,
config::Gesture& config,
const std::shared_ptr<ipcgull::node>& parent) {
if (type == AxisGesture::interface_name) {
config = config::AxisGesture();
} else if (type == IntervalGesture::interface_name) {
config = config::IntervalGesture();
} else if (type == ReleaseGesture::interface_name) {
config = config::ReleaseGesture();
} else if (type == ThresholdGesture::interface_name) {
config = config::ThresholdGesture();
} else if (type == NullGesture::interface_name) {
config = config::NoGesture();
} else {
throw InvalidGesture();
}
return makeGesture(device, config, parent);
}
/*
* Copyright 2019-2023 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 <actions/gesture/Gesture.h>
#include <utility>
#include <actions/gesture/ReleaseGesture.h>
#include <actions/gesture/ThresholdGesture.h>
#include <actions/gesture/IntervalGesture.h>
#include <actions/gesture/AxisGesture.h>
#include <actions/gesture/NullGesture.h>
#include <ipc_defs.h>
using namespace logid;
using namespace logid::actions;
Gesture::Gesture(Device* device,
std::shared_ptr<ipcgull::node> node,
const std::string& name, tables t) :
ipcgull::interface(SERVICE_ROOT_NAME ".Gesture." + name, std::move(t)),
_node(std::move(node)), _device(device) {
}
namespace {
template<typename T>
struct gesture_type {
typedef typename T::gesture type;
};
template<typename T>
struct gesture_type<const T> : gesture_type<T> {
};
template<typename T>
struct gesture_type<T&> : gesture_type<T> {
};
template<typename T>
std::shared_ptr<Gesture> _makeGesture(
Device* device, T& gesture,
const std::shared_ptr<ipcgull::node>& parent) {
return parent->make_interface<typename gesture_type<T>::type>(
device, std::forward<T&>(gesture), parent);
}
}
std::shared_ptr<Gesture> Gesture::makeGesture(
Device* device, config::Gesture& gesture,
const std::shared_ptr<ipcgull::node>& parent) {
return std::visit([&device, &parent](auto&& x) {
return _makeGesture(device, x, parent);
}, gesture);
}
std::shared_ptr<Gesture> Gesture::makeGesture(
Device* device, const std::string& type,
config::Gesture& config,
const std::shared_ptr<ipcgull::node>& parent) {
if (type == AxisGesture::interface_name) {
config = config::AxisGesture();
} else if (type == IntervalGesture::interface_name) {
config = config::IntervalGesture();
} else if (type == ReleaseGesture::interface_name) {
config = config::ReleaseGesture();
} else if (type == ThresholdGesture::interface_name) {
config = config::ThresholdGesture();
} else if (type == NullGesture::interface_name) {
config = config::NoGesture();
} else {
throw InvalidGesture();
}
return makeGesture(device, config, parent);
}

View File

@ -1,73 +1,73 @@
/*
* Copyright 2019-2023 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_GESTURE_H
#define LOGID_ACTION_GESTURE_H
#include <utility>
#include <actions/Action.h>
namespace logid::actions {
class InvalidGesture : public std::exception {
public:
explicit InvalidGesture(std::string what = "") : _what(std::move(what)) {
}
[[nodiscard]] const char* what() const noexcept override {
return _what.c_str();
}
private:
std::string _what;
};
class Gesture : public ipcgull::interface {
public:
virtual void press(bool init_threshold) = 0;
virtual void release(bool primary) = 0;
virtual void move(int16_t axis) = 0;
[[nodiscard]] virtual bool wheelCompatibility() const = 0;
[[nodiscard]] virtual bool metThreshold() const = 0;
virtual ~Gesture() = default;
static std::shared_ptr<Gesture> makeGesture(Device* device,
config::Gesture& gesture,
const std::shared_ptr<ipcgull::node>& parent);
static std::shared_ptr<Gesture> makeGesture(
Device* device, const std::string& type,
config::Gesture& gesture,
const std::shared_ptr<ipcgull::node>& parent);
protected:
Gesture(Device* device,
std::shared_ptr<ipcgull::node> parent,
const std::string& name, tables t = {});
mutable std::shared_mutex _config_mutex;
const std::shared_ptr<ipcgull::node> _node;
Device* _device;
};
}
#endif //LOGID_ACTION_GESTURE_H
/*
* Copyright 2019-2023 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_GESTURE_H
#define LOGID_ACTION_GESTURE_H
#include <utility>
#include <actions/Action.h>
namespace logid::actions {
class InvalidGesture : public std::exception {
public:
explicit InvalidGesture(std::string what = "") : _what(std::move(what)) {
}
[[nodiscard]] const char* what() const noexcept override {
return _what.c_str();
}
private:
std::string _what;
};
class Gesture : public ipcgull::interface {
public:
virtual void press(bool init_threshold) = 0;
virtual void release(bool primary) = 0;
virtual void move(int16_t axis) = 0;
[[nodiscard]] virtual bool wheelCompatibility() const = 0;
[[nodiscard]] virtual bool metThreshold() const = 0;
virtual ~Gesture() = default;
static std::shared_ptr<Gesture> makeGesture(Device* device,
config::Gesture& gesture,
const std::shared_ptr<ipcgull::node>& parent);
static std::shared_ptr<Gesture> makeGesture(
Device* device, const std::string& type,
config::Gesture& gesture,
const std::shared_ptr<ipcgull::node>& parent);
protected:
Gesture(Device* device,
std::shared_ptr<ipcgull::node> parent,
const std::string& name, tables t = {});
mutable std::shared_mutex _config_mutex;
const std::shared_ptr<ipcgull::node> _node;
Device* _device;
};
}
#endif //LOGID_ACTION_GESTURE_H

View File

@ -1,117 +1,117 @@
/*
* Copyright 2019-2023 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 <actions/gesture/IntervalGesture.h>
#include <Configuration.h>
#include <util/log.h>
using namespace logid::actions;
const char* IntervalGesture::interface_name = "OnInterval";
IntervalGesture::IntervalGesture(
Device* device, config::IntervalGesture& config,
const std::shared_ptr<ipcgull::node>& parent) :
Gesture(device, parent, interface_name, {
{
{"GetConfig", {this, &IntervalGesture::getConfig, {"interval", "threshold"}}},
{"SetInterval", {this, &IntervalGesture::setInterval, {"interval"}}},
{"SetThreshold", {this, &IntervalGesture::setThreshold, {"interval"}}},
{"SetAction", {this, &IntervalGesture::setAction, {"type"}}}
},
{},
{}
}),
_axis(0), _interval_pass_count(0), _config(config) {
if (config.action) {
try {
_action = Action::makeAction(device, config.action.value(), _node);
} catch (InvalidAction& e) {
logPrintf(WARN, "Mapping gesture to invalid action");
}
}
}
void IntervalGesture::press(bool init_threshold) {
std::shared_lock lock(_config_mutex);
if (init_threshold) {
_axis = (int32_t) _config.threshold.value_or(defaults::gesture_threshold);
} else {
_axis = 0;
}
_interval_pass_count = 0;
}
void IntervalGesture::release([[maybe_unused]] bool primary) {
}
void IntervalGesture::move(int16_t axis) {
std::shared_lock lock(_config_mutex);
if (!_config.interval.has_value())
return;
const auto threshold =
_config.threshold.value_or(defaults::gesture_threshold);
_axis += axis;
if (_axis < threshold)
return;
int32_t new_interval_count = (_axis - threshold) / _config.interval.value();
if (new_interval_count > _interval_pass_count) {
if (_action) {
_action->press();
_action->release();
}
}
_interval_pass_count = new_interval_count;
}
bool IntervalGesture::wheelCompatibility() const {
return true;
}
bool IntervalGesture::metThreshold() const {
std::shared_lock lock(_config_mutex);
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
}
std::tuple<int, int> IntervalGesture::getConfig() const {
std::shared_lock lock(_config_mutex);
return {_config.interval.value_or(0), _config.threshold.value_or(0)};
}
void IntervalGesture::setInterval(int interval) {
std::unique_lock lock(_config_mutex);
if (interval == 0)
_config.interval.reset();
else
_config.interval = interval;
}
void IntervalGesture::setThreshold(int threshold) {
std::unique_lock lock(_config_mutex);
if (threshold == 0)
_config.threshold.reset();
else
_config.threshold = threshold;
}
void IntervalGesture::setAction(const std::string& type) {
std::unique_lock lock(_config_mutex);
_action.reset();
_action = Action::makeAction(_device, type, _config.action, _node);
}
/*
* Copyright 2019-2023 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 <actions/gesture/IntervalGesture.h>
#include <Configuration.h>
#include <util/log.h>
using namespace logid::actions;
const char* IntervalGesture::interface_name = "OnInterval";
IntervalGesture::IntervalGesture(
Device* device, config::IntervalGesture& config,
const std::shared_ptr<ipcgull::node>& parent) :
Gesture(device, parent, interface_name, {
{
{"GetConfig", {this, &IntervalGesture::getConfig, {"interval", "threshold"}}},
{"SetInterval", {this, &IntervalGesture::setInterval, {"interval"}}},
{"SetThreshold", {this, &IntervalGesture::setThreshold, {"interval"}}},
{"SetAction", {this, &IntervalGesture::setAction, {"type"}}}
},
{},
{}
}),
_axis(0), _interval_pass_count(0), _config(config) {
if (config.action) {
try {
_action = Action::makeAction(device, config.action.value(), _node);
} catch (InvalidAction& e) {
logPrintf(WARN, "Mapping gesture to invalid action");
}
}
}
void IntervalGesture::press(bool init_threshold) {
std::shared_lock lock(_config_mutex);
if (init_threshold) {
_axis = (int32_t) _config.threshold.value_or(defaults::gesture_threshold);
} else {
_axis = 0;
}
_interval_pass_count = 0;
}
void IntervalGesture::release([[maybe_unused]] bool primary) {
}
void IntervalGesture::move(int16_t axis) {
std::shared_lock lock(_config_mutex);
if (!_config.interval.has_value())
return;
const auto threshold =
_config.threshold.value_or(defaults::gesture_threshold);
_axis += axis;
if (_axis < threshold)
return;
int32_t new_interval_count = (_axis - threshold) / _config.interval.value();
if (new_interval_count > _interval_pass_count) {
if (_action) {
_action->press();
_action->release();
}
}
_interval_pass_count = new_interval_count;
}
bool IntervalGesture::wheelCompatibility() const {
return true;
}
bool IntervalGesture::metThreshold() const {
std::shared_lock lock(_config_mutex);
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
}
std::tuple<int, int> IntervalGesture::getConfig() const {
std::shared_lock lock(_config_mutex);
return {_config.interval.value_or(0), _config.threshold.value_or(0)};
}
void IntervalGesture::setInterval(int interval) {
std::unique_lock lock(_config_mutex);
if (interval == 0)
_config.interval.reset();
else
_config.interval = interval;
}
void IntervalGesture::setThreshold(int threshold) {
std::unique_lock lock(_config_mutex);
if (threshold == 0)
_config.threshold.reset();
else
_config.threshold = threshold;
}
void IntervalGesture::setAction(const std::string& type) {
std::unique_lock lock(_config_mutex);
_action.reset();
_action = Action::makeAction(_device, type, _config.action, _node);
}

View File

@ -1,58 +1,58 @@
/*
* Copyright 2019-2023 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_INTERVALGESTURE_H
#define LOGID_ACTION_INTERVALGESTURE_H
#include <actions/gesture/Gesture.h>
namespace logid::actions {
class IntervalGesture : public Gesture {
public:
static const char* interface_name;
IntervalGesture(Device* device, config::IntervalGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
void press(bool init_threshold) final;
void release(bool primary) final;
void move(int16_t axis) final;
[[nodiscard]] bool wheelCompatibility() const final;
[[nodiscard]] bool metThreshold() const final;
[[nodiscard]] std::tuple<int, int> getConfig() const;
void setInterval(int interval);
void setThreshold(int threshold);
void setAction(const std::string& type);
protected:
int32_t _axis;
int32_t _interval_pass_count;
std::shared_ptr<Action> _action;
config::IntervalGesture& _config;
private:
};
}
#endif //LOGID_ACTION_INTERVALGESTURE_H
/*
* Copyright 2019-2023 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_INTERVALGESTURE_H
#define LOGID_ACTION_INTERVALGESTURE_H
#include <actions/gesture/Gesture.h>
namespace logid::actions {
class IntervalGesture : public Gesture {
public:
static const char* interface_name;
IntervalGesture(Device* device, config::IntervalGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
void press(bool init_threshold) final;
void release(bool primary) final;
void move(int16_t axis) final;
[[nodiscard]] bool wheelCompatibility() const final;
[[nodiscard]] bool metThreshold() const final;
[[nodiscard]] std::tuple<int, int> getConfig() const;
void setInterval(int interval);
void setThreshold(int threshold);
void setAction(const std::string& type);
protected:
int32_t _axis;
int32_t _interval_pass_count;
std::shared_ptr<Action> _action;
config::IntervalGesture& _config;
private:
};
}
#endif //LOGID_ACTION_INTERVALGESTURE_H

View File

@ -1,51 +1,51 @@
/*
* Copyright 2019-2023 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 <actions/gesture/NullGesture.h>
#include <Configuration.h>
using namespace logid::actions;
const char* NullGesture::interface_name = "None";
NullGesture::NullGesture(Device* device,
config::NoGesture& config,
const std::shared_ptr<ipcgull::node>& parent) :
Gesture(device, parent, interface_name), _config(config) {
}
void NullGesture::press(bool init_threshold) {
_axis = init_threshold ? _config.threshold.value_or(
defaults::gesture_threshold) : 0;
}
void NullGesture::release(bool primary) {
// Do nothing
(void) primary; // Suppress unused warning
}
void NullGesture::move(int16_t axis) {
_axis += axis;
}
bool NullGesture::wheelCompatibility() const {
return true;
}
bool NullGesture::metThreshold() const {
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
/*
* Copyright 2019-2023 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 <actions/gesture/NullGesture.h>
#include <Configuration.h>
using namespace logid::actions;
const char* NullGesture::interface_name = "None";
NullGesture::NullGesture(Device* device,
config::NoGesture& config,
const std::shared_ptr<ipcgull::node>& parent) :
Gesture(device, parent, interface_name), _config(config) {
}
void NullGesture::press(bool init_threshold) {
_axis = init_threshold ? _config.threshold.value_or(
defaults::gesture_threshold) : 0;
}
void NullGesture::release(bool primary) {
// Do nothing
(void) primary; // Suppress unused warning
}
void NullGesture::move(int16_t axis) {
_axis += axis;
}
bool NullGesture::wheelCompatibility() const {
return true;
}
bool NullGesture::metThreshold() const {
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
}

View File

@ -1,48 +1,48 @@
/*
* Copyright 2019-2023 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_NULLGESTURE_H
#define LOGID_ACTION_NULLGESTURE_H
#include <actions/gesture/Gesture.h>
namespace logid::actions {
class NullGesture : public Gesture {
public:
static const char* interface_name;
NullGesture(Device* device,
config::NoGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
void press(bool init_threshold) final;
void release(bool primary) final;
void move(int16_t axis) final;
[[nodiscard]] bool wheelCompatibility() const final;
[[nodiscard]] bool metThreshold() const final;
protected:
int32_t _axis{};
config::NoGesture& _config;
};
}
#endif //LOGID_ACTION_NULLGESTURE_H
/*
* Copyright 2019-2023 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_NULLGESTURE_H
#define LOGID_ACTION_NULLGESTURE_H
#include <actions/gesture/Gesture.h>
namespace logid::actions {
class NullGesture : public Gesture {
public:
static const char* interface_name;
NullGesture(Device* device,
config::NoGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
void press(bool init_threshold) final;
void release(bool primary) final;
void move(int16_t axis) final;
[[nodiscard]] bool wheelCompatibility() const final;
[[nodiscard]] bool metThreshold() const final;
protected:
int32_t _axis{};
config::NoGesture& _config;
};
}
#endif //LOGID_ACTION_NULLGESTURE_H

View File

@ -1,89 +1,89 @@
/*
* Copyright 2019-2023 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 <actions/gesture/ReleaseGesture.h>
#include <Configuration.h>
using namespace logid::actions;
const char* ReleaseGesture::interface_name = "OnRelease";
ReleaseGesture::ReleaseGesture(Device* device, config::ReleaseGesture& config,
const std::shared_ptr<ipcgull::node>& parent) :
Gesture(device, parent, interface_name, {
{
{"GetThreshold", {this, &ReleaseGesture::getThreshold, {"threshold"}}},
{"SetThreshold", {this, &ReleaseGesture::setThreshold, {"threshold"}}},
{"SetAction", {this, &ReleaseGesture::setAction, {"type"}}}
},
{},
{}
}), _config(config) {
if (_config.action.has_value())
_action = Action::makeAction(device, _config.action.value(), _node);
}
void ReleaseGesture::press(bool init_threshold) {
std::shared_lock lock(_config_mutex);
if (init_threshold) {
_axis = (int32_t) (_config.threshold.value_or(defaults::gesture_threshold));
} else {
_axis = 0;
}
}
void ReleaseGesture::release(bool primary) {
if (metThreshold() && primary) {
if (_action) {
_action->press();
_action->release();
}
}
}
void ReleaseGesture::move(int16_t axis) {
_axis += axis;
}
bool ReleaseGesture::wheelCompatibility() const {
return false;
}
bool ReleaseGesture::metThreshold() const {
std::shared_lock lock(_config_mutex);
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
}
int ReleaseGesture::getThreshold() const {
std::shared_lock lock(_config_mutex);
return _config.threshold.value_or(0);
}
void ReleaseGesture::setThreshold(int threshold) {
std::unique_lock lock(_config_mutex);
if (threshold == 0)
_config.threshold.reset();
else
_config.threshold = threshold;
}
void ReleaseGesture::setAction(const std::string& type) {
std::unique_lock lock(_config_mutex);
_action.reset();
_action = Action::makeAction(_device, type, _config.action, _node);
}
/*
* Copyright 2019-2023 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 <actions/gesture/ReleaseGesture.h>
#include <Configuration.h>
using namespace logid::actions;
const char* ReleaseGesture::interface_name = "OnRelease";
ReleaseGesture::ReleaseGesture(Device* device, config::ReleaseGesture& config,
const std::shared_ptr<ipcgull::node>& parent) :
Gesture(device, parent, interface_name, {
{
{"GetThreshold", {this, &ReleaseGesture::getThreshold, {"threshold"}}},
{"SetThreshold", {this, &ReleaseGesture::setThreshold, {"threshold"}}},
{"SetAction", {this, &ReleaseGesture::setAction, {"type"}}}
},
{},
{}
}), _config(config) {
if (_config.action.has_value())
_action = Action::makeAction(device, _config.action.value(), _node);
}
void ReleaseGesture::press(bool init_threshold) {
std::shared_lock lock(_config_mutex);
if (init_threshold) {
_axis = (int32_t) (_config.threshold.value_or(defaults::gesture_threshold));
} else {
_axis = 0;
}
}
void ReleaseGesture::release(bool primary) {
if (metThreshold() && primary) {
if (_action) {
_action->press();
_action->release();
}
}
}
void ReleaseGesture::move(int16_t axis) {
_axis += axis;
}
bool ReleaseGesture::wheelCompatibility() const {
return false;
}
bool ReleaseGesture::metThreshold() const {
std::shared_lock lock(_config_mutex);
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
}
int ReleaseGesture::getThreshold() const {
std::shared_lock lock(_config_mutex);
return _config.threshold.value_or(0);
}
void ReleaseGesture::setThreshold(int threshold) {
std::unique_lock lock(_config_mutex);
if (threshold == 0)
_config.threshold.reset();
else
_config.threshold = threshold;
}
void ReleaseGesture::setAction(const std::string& type) {
std::unique_lock lock(_config_mutex);
_action.reset();
_action = Action::makeAction(_device, type, _config.action, _node);
}

View File

@ -1,54 +1,54 @@
/*
* Copyright 2019-2023 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_RELEASEGESTURE_H
#define LOGID_ACTION_RELEASEGESTURE_H
#include <actions/gesture/Gesture.h>
namespace logid::actions {
class ReleaseGesture : public Gesture {
public:
static const char* interface_name;
ReleaseGesture(Device* device, config::ReleaseGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
void press(bool init_threshold) final;
void release(bool primary) final;
void move(int16_t axis) final;
[[nodiscard]] bool wheelCompatibility() const final;
[[nodiscard]] bool metThreshold() const final;
[[nodiscard]] int getThreshold() const;
void setThreshold(int threshold);
void setAction(const std::string& type);
protected:
int32_t _axis{};
std::shared_ptr<Action> _action;
config::ReleaseGesture& _config;
};
}
#endif //LOGID_ACTION_RELEASEGESTURE_H
/*
* Copyright 2019-2023 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_RELEASEGESTURE_H
#define LOGID_ACTION_RELEASEGESTURE_H
#include <actions/gesture/Gesture.h>
namespace logid::actions {
class ReleaseGesture : public Gesture {
public:
static const char* interface_name;
ReleaseGesture(Device* device, config::ReleaseGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
void press(bool init_threshold) final;
void release(bool primary) final;
void move(int16_t axis) final;
[[nodiscard]] bool wheelCompatibility() const final;
[[nodiscard]] bool metThreshold() const final;
[[nodiscard]] int getThreshold() const;
void setThreshold(int threshold);
void setAction(const std::string& type);
protected:
int32_t _axis{};
std::shared_ptr<Action> _action;
config::ReleaseGesture& _config;
};
}
#endif //LOGID_ACTION_RELEASEGESTURE_H

View File

@ -1,95 +1,95 @@
/*
* Copyright 2019-2023 PixlOne, michtere
*
* 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 <actions/gesture/ThresholdGesture.h>
#include <Configuration.h>
#include <util/log.h>
using namespace logid::actions;
const char* ThresholdGesture::interface_name = "OnRelease";
ThresholdGesture::ThresholdGesture(
Device* device, config::ThresholdGesture& config,
const std::shared_ptr<ipcgull::node>& parent) :
Gesture(device, parent, interface_name, {
{
{"GetThreshold", {this, &ThresholdGesture::getThreshold, {"threshold"}}},
{"SetThreshold", {this, &ThresholdGesture::setThreshold, {"threshold"}}},
{"SetAction", {this, &ThresholdGesture::setAction, {"type"}}}
},
{},
{}
}), _config(config) {
if (config.action) {
try {
_action = Action::makeAction(device, config.action.value(), _node);
} catch (InvalidAction& e) {
logPrintf(WARN, "Mapping gesture to invalid action");
}
}
}
void ThresholdGesture::press(bool init_threshold) {
std::shared_lock lock(_config_mutex);
_axis = init_threshold ? (int32_t) _config.threshold.value_or(defaults::gesture_threshold) : 0;
this->_executed = false;
}
void ThresholdGesture::release([[maybe_unused]] bool primary) {
this->_executed = false;
}
void ThresholdGesture::move(int16_t axis) {
_axis += axis;
if (!this->_executed && metThreshold()) {
if (_action) {
_action->press();
_action->release();
}
this->_executed = true;
}
}
bool ThresholdGesture::metThreshold() const {
std::shared_lock lock(_config_mutex);
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
}
bool ThresholdGesture::wheelCompatibility() const {
return false;
}
int ThresholdGesture::getThreshold() const {
std::shared_lock lock(_config_mutex);
return _config.threshold.value_or(0);
}
void ThresholdGesture::setThreshold(int threshold) {
std::unique_lock lock(_config_mutex);
if (threshold == 0)
_config.threshold.reset();
else
_config.threshold = threshold;
}
void ThresholdGesture::setAction(const std::string& type) {
std::unique_lock lock(_config_mutex);
_action.reset();
_action = Action::makeAction(_device, type, _config.action, _node);
}
/*
* Copyright 2019-2023 PixlOne, michtere
*
* 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 <actions/gesture/ThresholdGesture.h>
#include <Configuration.h>
#include <util/log.h>
using namespace logid::actions;
const char* ThresholdGesture::interface_name = "OnRelease";
ThresholdGesture::ThresholdGesture(
Device* device, config::ThresholdGesture& config,
const std::shared_ptr<ipcgull::node>& parent) :
Gesture(device, parent, interface_name, {
{
{"GetThreshold", {this, &ThresholdGesture::getThreshold, {"threshold"}}},
{"SetThreshold", {this, &ThresholdGesture::setThreshold, {"threshold"}}},
{"SetAction", {this, &ThresholdGesture::setAction, {"type"}}}
},
{},
{}
}), _config(config) {
if (config.action) {
try {
_action = Action::makeAction(device, config.action.value(), _node);
} catch (InvalidAction& e) {
logPrintf(WARN, "Mapping gesture to invalid action");
}
}
}
void ThresholdGesture::press(bool init_threshold) {
std::shared_lock lock(_config_mutex);
_axis = init_threshold ? (int32_t) _config.threshold.value_or(defaults::gesture_threshold) : 0;
this->_executed = false;
}
void ThresholdGesture::release([[maybe_unused]] bool primary) {
this->_executed = false;
}
void ThresholdGesture::move(int16_t axis) {
_axis += axis;
if (!this->_executed && metThreshold()) {
if (_action) {
_action->press();
_action->release();
}
this->_executed = true;
}
}
bool ThresholdGesture::metThreshold() const {
std::shared_lock lock(_config_mutex);
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
}
bool ThresholdGesture::wheelCompatibility() const {
return false;
}
int ThresholdGesture::getThreshold() const {
std::shared_lock lock(_config_mutex);
return _config.threshold.value_or(0);
}
void ThresholdGesture::setThreshold(int threshold) {
std::unique_lock lock(_config_mutex);
if (threshold == 0)
_config.threshold.reset();
else
_config.threshold = threshold;
}
void ThresholdGesture::setAction(const std::string& type) {
std::unique_lock lock(_config_mutex);
_action.reset();
_action = Action::makeAction(_device, type, _config.action, _node);
}

View File

@ -1,57 +1,57 @@
/*
* Copyright 2019-2023 PixlOne, michtere
*
* 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_THRESHOLDGESTURE_H
#define LOGID_ACTION_THRESHOLDGESTURE_H
#include <actions/gesture/Gesture.h>
namespace logid::actions {
class ThresholdGesture : public Gesture {
public:
static const char* interface_name;
ThresholdGesture(Device* device, config::ThresholdGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
void press(bool init_threshold) final;
void release(bool primary) final;
void move(int16_t axis) final;
[[nodiscard]] bool metThreshold() const final;
[[nodiscard]] bool wheelCompatibility() const final;
[[nodiscard]] int getThreshold() const;
void setThreshold(int threshold);
void setAction(const std::string& type);
protected:
int32_t _axis{};
std::shared_ptr<actions::Action> _action;
config::ThresholdGesture& _config;
private:
bool _executed = false;
};
}
#endif //LOGID_ACTION_THRESHOLDGESTURE_H
/*
* Copyright 2019-2023 PixlOne, michtere
*
* 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_THRESHOLDGESTURE_H
#define LOGID_ACTION_THRESHOLDGESTURE_H
#include <actions/gesture/Gesture.h>
namespace logid::actions {
class ThresholdGesture : public Gesture {
public:
static const char* interface_name;
ThresholdGesture(Device* device, config::ThresholdGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
void press(bool init_threshold) final;
void release(bool primary) final;
void move(int16_t axis) final;
[[nodiscard]] bool metThreshold() const final;
[[nodiscard]] bool wheelCompatibility() const final;
[[nodiscard]] int getThreshold() const;
void setThreshold(int threshold);
void setAction(const std::string& type);
protected:
int32_t _axis{};
std::shared_ptr<actions::Action> _action;
config::ThresholdGesture& _config;
private:
bool _executed = false;
};
}
#endif //LOGID_ACTION_THRESHOLDGESTURE_H

View File

@ -1,29 +1,29 @@
/*
* Copyright 2019-2023 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 <backend/Error.h>
using namespace logid::backend;
const char* DeviceNotReady::what() const noexcept {
return "device not ready";
}
const char* TimeoutError::what() const noexcept {
return "Device timed out";
}
/*
* Copyright 2019-2023 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 <backend/Error.h>
using namespace logid::backend;
const char* DeviceNotReady::what() const noexcept {
return "device not ready";
}
const char* TimeoutError::what() const noexcept {
return "Device timed out";
}

View File

@ -1,38 +1,38 @@
/*
* Copyright 2019-2023 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_BACKEND_ERROR_H
#define LOGID_BACKEND_ERROR_H
#include <stdexcept>
namespace logid::backend {
class DeviceNotReady : public std::exception {
public:
[[nodiscard]] const char* what() const noexcept override;
};
class TimeoutError : public std::exception {
public:
TimeoutError() = default;
[[nodiscard]] const char* what() const noexcept override;
};
}
/*
* Copyright 2019-2023 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_BACKEND_ERROR_H
#define LOGID_BACKEND_ERROR_H
#include <stdexcept>
namespace logid::backend {
class DeviceNotReady : public std::exception {
public:
[[nodiscard]] const char* what() const noexcept override;
};
class TimeoutError : public std::exception {
public:
TimeoutError() = default;
[[nodiscard]] const char* what() const noexcept override;
};
}
#endif //LOGID_BACKEND_ERROR_H

View File

@ -1,137 +1,137 @@
/*
* Copyright 2019-2023 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_EVENTHANDLERLIST_H
#define LOGID_EVENTHANDLERLIST_H
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <list>
#include <atomic>
template <class T>
class EventHandlerLock;
template <class T>
class EventHandlerList {
public:
typedef std::list<std::pair<typename T::EventHandler, std::atomic_bool>> list_t;
typedef typename list_t::iterator iterator_t;
private:
list_t list;
std::shared_mutex mutex;
std::shared_mutex add_mutex;
void cleanup() {
std::unique_lock lock(mutex, std::try_to_lock);
if (lock.owns_lock()) {
std::list<iterator_t> to_remove;
for (auto it = list.begin(); it != list.end(); ++it) {
if (!it->second)
to_remove.push_back(it);
}
for(auto& it : to_remove)
list.erase(it);
}
}
public:
iterator_t add(typename T::EventHandler handler) {
std::unique_lock add_lock(add_mutex);
list.emplace_front(std::move(handler), true);
return list.begin();
}
void remove(iterator_t iterator) {
std::unique_lock lock(mutex, std::try_to_lock);
if (lock.owns_lock()) {
std::unique_lock add_lock(add_mutex);
list.erase(iterator);
} else {
iterator->second = false;
}
}
template <typename Arg>
void run_all(Arg arg) {
cleanup();
std::shared_lock lock(mutex);
std::shared_lock add_lock(add_mutex);
for (auto& handler : list) {
add_lock.unlock();
if (handler.second) {
if (handler.first.condition(arg))
handler.first.callback(arg);
}
add_lock.lock();
}
}
};
template <class T>
class EventHandlerLock {
typedef EventHandlerList<T> list_t;
typedef typename list_t::iterator_t iterator_t;
friend T;
std::weak_ptr<list_t> _list;
iterator_t _iterator;
EventHandlerLock(const std::shared_ptr<list_t>& list, iterator_t iterator) :
_list (list), _iterator (iterator) {
}
public:
EventHandlerLock() = default;
EventHandlerLock(const EventHandlerLock&) = delete;
EventHandlerLock(EventHandlerLock&& o) noexcept : _list (o._list), _iterator (o._iterator) {
o._list.reset();
}
EventHandlerLock& operator=(const EventHandlerLock&) = delete;
EventHandlerLock& operator=(EventHandlerLock&& o) noexcept {
if (this != &o) {
if (auto list = _list.lock()) {
this->_list.reset();
list->remove(_iterator);
}
this->_list = o._list;
o._list.reset();
this->_iterator = o._iterator;
}
return *this;
}
~EventHandlerLock() {
if(auto list = _list.lock())
list->remove(_iterator);
}
[[nodiscard]] bool empty() const noexcept {
return _list.expired();
}
};
#endif //LOGID_EVENTHANDLERLIST_H
/*
* Copyright 2019-2023 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_EVENTHANDLERLIST_H
#define LOGID_EVENTHANDLERLIST_H
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <list>
#include <atomic>
template <class T>
class EventHandlerLock;
template <class T>
class EventHandlerList {
public:
typedef std::list<std::pair<typename T::EventHandler, std::atomic_bool>> list_t;
typedef typename list_t::iterator iterator_t;
private:
list_t list;
std::shared_mutex mutex;
std::shared_mutex add_mutex;
void cleanup() {
std::unique_lock lock(mutex, std::try_to_lock);
if (lock.owns_lock()) {
std::list<iterator_t> to_remove;
for (auto it = list.begin(); it != list.end(); ++it) {
if (!it->second)
to_remove.push_back(it);
}
for(auto& it : to_remove)
list.erase(it);
}
}
public:
iterator_t add(typename T::EventHandler handler) {
std::unique_lock add_lock(add_mutex);
list.emplace_front(std::move(handler), true);
return list.begin();
}
void remove(iterator_t iterator) {
std::unique_lock lock(mutex, std::try_to_lock);
if (lock.owns_lock()) {
std::unique_lock add_lock(add_mutex);
list.erase(iterator);
} else {
iterator->second = false;
}
}
template <typename Arg>
void run_all(Arg arg) {
cleanup();
std::shared_lock lock(mutex);
std::shared_lock add_lock(add_mutex);
for (auto& handler : list) {
add_lock.unlock();
if (handler.second) {
if (handler.first.condition(arg))
handler.first.callback(arg);
}
add_lock.lock();
}
}
};
template <class T>
class EventHandlerLock {
typedef EventHandlerList<T> list_t;
typedef typename list_t::iterator_t iterator_t;
friend T;
std::weak_ptr<list_t> _list;
iterator_t _iterator;
EventHandlerLock(const std::shared_ptr<list_t>& list, iterator_t iterator) :
_list (list), _iterator (iterator) {
}
public:
EventHandlerLock() = default;
EventHandlerLock(const EventHandlerLock&) = delete;
EventHandlerLock(EventHandlerLock&& o) noexcept : _list (o._list), _iterator (o._iterator) {
o._list.reset();
}
EventHandlerLock& operator=(const EventHandlerLock&) = delete;
EventHandlerLock& operator=(EventHandlerLock&& o) noexcept {
if (this != &o) {
if (auto list = _list.lock()) {
this->_list.reset();
list->remove(_iterator);
}
this->_list = o._list;
o._list.reset();
this->_iterator = o._iterator;
}
return *this;
}
~EventHandlerLock() {
if(auto list = _list.lock())
list->remove(_iterator);
}
[[nodiscard]] bool empty() const noexcept {
return _list.expired();
}
};
#endif //LOGID_EVENTHANDLERLIST_H

View File

@ -1,320 +1,320 @@
/*
* Copyright 2019-2023 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 <backend/hidpp20/Device.h>
#include <backend/hidpp20/features/Root.h>
#include <backend/hidpp20/features/DeviceName.h>
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp10/Receiver.h>
#include <backend/Error.h>
#include <cassert>
#include <utility>
using namespace logid::backend;
using namespace logid::backend::hidpp;
using namespace std::chrono;
const char* Device::InvalidDevice::what() const noexcept {
switch (_reason) {
case NoHIDPPReport:
return "Invalid HID++ device";
case InvalidRawDevice:
return "Invalid raw device";
case Asleep:
return "Device asleep";
case VirtualNode:
return "Virtual device";
default:
return "Invalid device";
}
}
Device::InvalidDevice::Reason Device::InvalidDevice::code() const noexcept {
return _reason;
}
Device::Device(const std::string& path, DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) :
io_timeout(duration_cast<milliseconds>(
duration<double, std::milli>(timeout))),
_raw_device(raw::RawDevice::make(path, monitor)),
_receiver(nullptr), _path(path), _index(index) {
}
Device::Device(std::shared_ptr<raw::RawDevice> raw_device, DeviceIndex index,
double timeout) :
io_timeout(duration_cast<milliseconds>(
duration<double, std::milli>(timeout))),
_raw_device(std::move(raw_device)), _receiver(nullptr),
_path(_raw_device->rawPath()), _index(index) {
}
Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceConnectionEvent event, double timeout) :
io_timeout(duration_cast<milliseconds>(
duration<double, std::milli>(timeout))),
_raw_device(receiver->rawDevice()), _receiver(receiver),
_path(receiver->rawDevice()->rawPath()), _index(event.index) {
// Device will throw an error soon, just do it now
if (!event.linkEstablished)
throw InvalidDevice(InvalidDevice::Asleep);
if (!event.fromTimeoutCheck)
_pid = event.pid;
else
_pid = receiver->getPairingInfo(_index).pid;
}
Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
DeviceIndex index, double timeout) :
io_timeout(duration_cast<milliseconds>(
duration<double, std::milli>(timeout))),
_raw_device(receiver->rawDevice()),
_receiver(receiver), _path(receiver->rawDevice()->rawPath()),
_index(index) {
_pid = receiver->getPairingInfo(_index).pid;
}
const std::string& Device::devicePath() const {
return _path;
}
DeviceIndex Device::deviceIndex() const {
return _index;
}
const std::tuple<uint8_t, uint8_t>& Device::version() const {
return _version;
}
void Device::_setupReportsAndInit() {
_event_handlers = std::make_shared<EventHandlerList<Device>>();
supported_reports = getSupportedReports(_raw_device->reportDescriptor());
if (!supported_reports)
throw InvalidDevice(InvalidDevice::NoHIDPPReport);
/* hid_logitech_dj creates virtual /dev/hidraw nodes for receiver
* devices. We should ignore these devices.
*/
if (_raw_device->isSubDevice())
throw InvalidDevice(InvalidDevice::VirtualNode);
_raw_handler = _raw_device->addEventHandler(
{[index = _index](
const std::vector<uint8_t>& report) -> bool {
return (report[Offset::Type] == Report::Type::Short ||
report[Offset::Type] == Report::Type::Long) &&
(report[Offset::DeviceIndex] == index);
},
[self_weak = _self](const std::vector<uint8_t>& report) -> void {
Report _report(report);
if(auto self = self_weak.lock())
self->handleEvent(_report);
}});
_init();
}
void Device::_init() {
try {
hidpp20::Root root(this);
_version = root.getVersion();
} catch (hidpp10::Error& e) {
// Valid HID++ 1.0 devices should send an InvalidSubID error
if (e.code() != hidpp10::Error::InvalidSubID)
throw;
// HID++ 2.0 is not supported, assume HID++ 1.0
_version = std::make_tuple(1, 0);
} catch (hidpp20::Error& e) {
/* Should never happen, device not ready? */
throw DeviceNotReady();
}
/* Do a stability test before going further */
if (std::get<0>(_version) >= 2) {
if (!isStable20()) {
throw DeviceNotReady();
}
} else {
if (!isStable10()) {
throw DeviceNotReady();
}
}
if (!_receiver) {
_pid = _raw_device->productId();
if (std::get<0>(_version) >= 2) {
try {
hidpp20::DeviceName deviceName(this);
_name = deviceName.getName();
} catch (hidpp20::UnsupportedFeature& e) {
_name = _raw_device->name();
}
} else {
_name = _raw_device->name();
}
} else {
if (std::get<0>(_version) >= 2) {
try {
hidpp20::DeviceName deviceName(this);
_name = deviceName.getName();
} catch (hidpp20::UnsupportedFeature& e) {
_name = _receiver->getDeviceName(_index);
}
} else {
_name = _receiver->getDeviceName(_index);
}
}
}
EventHandlerLock<Device> Device::addEventHandler(EventHandler handler) {
return {_event_handlers, _event_handlers->add(std::move(handler))};
}
void Device::handleEvent(Report& report) {
if (responseReport(report))
return;
_event_handlers->run_all(report);
}
Report Device::sendReport(const Report& report) {
/* Must complete transaction before next send */
std::lock_guard send_lock(_send_mutex);
_sent_sub_id = report.subId();
_sent_address = report.address();
std::unique_lock lock(_response_mutex);
_sendReport(report);
bool valid = _response_cv.wait_for(
lock, io_timeout, [this]() {
return _response.has_value();
});
if (!valid) {
_sent_sub_id.reset();
throw TimeoutError();
}
Response response = _response.value();
_response.reset();
_sent_sub_id.reset();
_sent_address.reset();
if (std::holds_alternative<Report>(response)) {
return std::get<Report>(response);
} else if (std::holds_alternative<Report::Hidpp10Error>(response)) {
auto error = std::get<Report::Hidpp10Error>(response);
throw hidpp10::Error(error.error_code, error.device_index);
} else if (std::holds_alternative<Report::Hidpp20Error>(response)) {
auto error = std::get<Report::Hidpp20Error>(response);
throw hidpp20::Error(error.error_code, error.device_index);
}
// Should not be reached
throw std::runtime_error("unhandled variant type");
}
bool Device::responseReport(const Report& report) {
std::lock_guard lock(_response_mutex);
Response response = report;
uint8_t sub_id;
uint8_t address;
Report::Hidpp10Error hidpp10_error{};
Report::Hidpp20Error hidpp20_error{};
if (report.isError10(hidpp10_error)) {
sub_id = hidpp10_error.sub_id;
address = hidpp10_error.address;
response = hidpp10_error;
} else if (report.isError20(hidpp20_error)) {
sub_id = hidpp20_error.feature_index;
address = (hidpp20_error.function << 4) | (hidpp20_error.software_id & 0x0f);
} else {
sub_id = report.subId();
address = report.address();
}
if (sub_id == _sent_sub_id && address == _sent_address) {
_response = response;
_response_cv.notify_all();
return true;
} else {
return false;
}
}
const std::shared_ptr<raw::RawDevice>& Device::rawDevice() const {
return _raw_device;
}
void Device::_sendReport(Report report) {
reportFixup(report);
_raw_device->sendReport(report.rawReport());
}
void Device::sendReportNoACK(const Report& report) {
std::lock_guard lock(_send_mutex);
_sendReport(report);
}
bool Device::isStable10() {
return true;
}
bool Device::isStable20() {
static const std::string ping_seq = "hello";
hidpp20::Root root(this);
try {
for (auto c: ping_seq) {
if (root.ping(c) != c)
return false;
}
} catch (std::exception& e) {
return false;
}
return true;
}
void Device::reportFixup(Report& report) const {
switch (report.type()) {
case Report::Type::Short:
if (!(supported_reports & ShortReportSupported))
report.setType(Report::Type::Long);
break;
case Report::Type::Long:
/* Report can be truncated, but that isn't a good idea. */
assert(supported_reports & LongReportSupported);
}
}
const std::string& Device::name() const {
return _name;
}
uint16_t Device::pid() const {
return _pid;
}
/*
* Copyright 2019-2023 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 <backend/hidpp20/Device.h>
#include <backend/hidpp20/features/Root.h>
#include <backend/hidpp20/features/DeviceName.h>
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp10/Receiver.h>
#include <backend/Error.h>
#include <cassert>
#include <utility>
using namespace logid::backend;
using namespace logid::backend::hidpp;
using namespace std::chrono;
const char* Device::InvalidDevice::what() const noexcept {
switch (_reason) {
case NoHIDPPReport:
return "Invalid HID++ device";
case InvalidRawDevice:
return "Invalid raw device";
case Asleep:
return "Device asleep";
case VirtualNode:
return "Virtual device";
default:
return "Invalid device";
}
}
Device::InvalidDevice::Reason Device::InvalidDevice::code() const noexcept {
return _reason;
}
Device::Device(const std::string& path, DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) :
io_timeout(duration_cast<milliseconds>(
duration<double, std::milli>(timeout))),
_raw_device(raw::RawDevice::make(path, monitor)),
_receiver(nullptr), _path(path), _index(index) {
}
Device::Device(std::shared_ptr<raw::RawDevice> raw_device, DeviceIndex index,
double timeout) :
io_timeout(duration_cast<milliseconds>(
duration<double, std::milli>(timeout))),
_raw_device(std::move(raw_device)), _receiver(nullptr),
_path(_raw_device->rawPath()), _index(index) {
}
Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceConnectionEvent event, double timeout) :
io_timeout(duration_cast<milliseconds>(
duration<double, std::milli>(timeout))),
_raw_device(receiver->rawDevice()), _receiver(receiver),
_path(receiver->rawDevice()->rawPath()), _index(event.index) {
// Device will throw an error soon, just do it now
if (!event.linkEstablished)
throw InvalidDevice(InvalidDevice::Asleep);
if (!event.fromTimeoutCheck)
_pid = event.pid;
else
_pid = receiver->getPairingInfo(_index).pid;
}
Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
DeviceIndex index, double timeout) :
io_timeout(duration_cast<milliseconds>(
duration<double, std::milli>(timeout))),
_raw_device(receiver->rawDevice()),
_receiver(receiver), _path(receiver->rawDevice()->rawPath()),
_index(index) {
_pid = receiver->getPairingInfo(_index).pid;
}
const std::string& Device::devicePath() const {
return _path;
}
DeviceIndex Device::deviceIndex() const {
return _index;
}
const std::tuple<uint8_t, uint8_t>& Device::version() const {
return _version;
}
void Device::_setupReportsAndInit() {
_event_handlers = std::make_shared<EventHandlerList<Device>>();
supported_reports = getSupportedReports(_raw_device->reportDescriptor());
if (!supported_reports)
throw InvalidDevice(InvalidDevice::NoHIDPPReport);
/* hid_logitech_dj creates virtual /dev/hidraw nodes for receiver
* devices. We should ignore these devices.
*/
if (_raw_device->isSubDevice())
throw InvalidDevice(InvalidDevice::VirtualNode);
_raw_handler = _raw_device->addEventHandler(
{[index = _index](
const std::vector<uint8_t>& report) -> bool {
return (report[Offset::Type] == Report::Type::Short ||
report[Offset::Type] == Report::Type::Long) &&
(report[Offset::DeviceIndex] == index);
},
[self_weak = _self](const std::vector<uint8_t>& report) -> void {
Report _report(report);
if(auto self = self_weak.lock())
self->handleEvent(_report);
}});
_init();
}
void Device::_init() {
try {
hidpp20::Root root(this);
_version = root.getVersion();
} catch (hidpp10::Error& e) {
// Valid HID++ 1.0 devices should send an InvalidSubID error
if (e.code() != hidpp10::Error::InvalidSubID)
throw;
// HID++ 2.0 is not supported, assume HID++ 1.0
_version = std::make_tuple(1, 0);
} catch (hidpp20::Error& e) {
/* Should never happen, device not ready? */
throw DeviceNotReady();
}
/* Do a stability test before going further */
if (std::get<0>(_version) >= 2) {
if (!isStable20()) {
throw DeviceNotReady();
}
} else {
if (!isStable10()) {
throw DeviceNotReady();
}
}
if (!_receiver) {
_pid = _raw_device->productId();
if (std::get<0>(_version) >= 2) {
try {
hidpp20::DeviceName deviceName(this);
_name = deviceName.getName();
} catch (hidpp20::UnsupportedFeature& e) {
_name = _raw_device->name();
}
} else {
_name = _raw_device->name();
}
} else {
if (std::get<0>(_version) >= 2) {
try {
hidpp20::DeviceName deviceName(this);
_name = deviceName.getName();
} catch (hidpp20::UnsupportedFeature& e) {
_name = _receiver->getDeviceName(_index);
}
} else {
_name = _receiver->getDeviceName(_index);
}
}
}
EventHandlerLock<Device> Device::addEventHandler(EventHandler handler) {
return {_event_handlers, _event_handlers->add(std::move(handler))};
}
void Device::handleEvent(Report& report) {
if (responseReport(report))
return;
_event_handlers->run_all(report);
}
Report Device::sendReport(const Report& report) {
/* Must complete transaction before next send */
std::lock_guard send_lock(_send_mutex);
_sent_sub_id = report.subId();
_sent_address = report.address();
std::unique_lock lock(_response_mutex);
_sendReport(report);
bool valid = _response_cv.wait_for(
lock, io_timeout, [this]() {
return _response.has_value();
});
if (!valid) {
_sent_sub_id.reset();
throw TimeoutError();
}
Response response = _response.value();
_response.reset();
_sent_sub_id.reset();
_sent_address.reset();
if (std::holds_alternative<Report>(response)) {
return std::get<Report>(response);
} else if (std::holds_alternative<Report::Hidpp10Error>(response)) {
auto error = std::get<Report::Hidpp10Error>(response);
throw hidpp10::Error(error.error_code, error.device_index);
} else if (std::holds_alternative<Report::Hidpp20Error>(response)) {
auto error = std::get<Report::Hidpp20Error>(response);
throw hidpp20::Error(error.error_code, error.device_index);
}
// Should not be reached
throw std::runtime_error("unhandled variant type");
}
bool Device::responseReport(const Report& report) {
std::lock_guard lock(_response_mutex);
Response response = report;
uint8_t sub_id;
uint8_t address;
Report::Hidpp10Error hidpp10_error{};
Report::Hidpp20Error hidpp20_error{};
if (report.isError10(hidpp10_error)) {
sub_id = hidpp10_error.sub_id;
address = hidpp10_error.address;
response = hidpp10_error;
} else if (report.isError20(hidpp20_error)) {
sub_id = hidpp20_error.feature_index;
address = (hidpp20_error.function << 4) | (hidpp20_error.software_id & 0x0f);
} else {
sub_id = report.subId();
address = report.address();
}
if (sub_id == _sent_sub_id && address == _sent_address) {
_response = response;
_response_cv.notify_all();
return true;
} else {
return false;
}
}
const std::shared_ptr<raw::RawDevice>& Device::rawDevice() const {
return _raw_device;
}
void Device::_sendReport(Report report) {
reportFixup(report);
_raw_device->sendReport(report.rawReport());
}
void Device::sendReportNoACK(const Report& report) {
std::lock_guard lock(_send_mutex);
_sendReport(report);
}
bool Device::isStable10() {
return true;
}
bool Device::isStable20() {
static const std::string ping_seq = "hello";
hidpp20::Root root(this);
try {
for (auto c: ping_seq) {
if (root.ping(c) != c)
return false;
}
} catch (std::exception& e) {
return false;
}
return true;
}
void Device::reportFixup(Report& report) const {
switch (report.type()) {
case Report::Type::Short:
if (!(supported_reports & ShortReportSupported))
report.setType(Report::Type::Long);
break;
case Report::Type::Long:
/* Report can be truncated, but that isn't a good idea. */
assert(supported_reports & LongReportSupported);
}
}
const std::string& Device::name() const {
return _name;
}
uint16_t Device::pid() const {
return _pid;
}

View File

@ -1,186 +1,186 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP_DEVICE_H
#define LOGID_BACKEND_HIDPP_DEVICE_H
#include <backend/raw/RawDevice.h>
#include <backend/hidpp/Report.h>
#include <backend/hidpp/defs.h>
#include <backend/EventHandlerList.h>
#include <optional>
#include <variant>
#include <string>
#include <memory>
#include <functional>
#include <map>
namespace logid::backend::hidpp10 {
// Need to define here for a constructor
class Receiver;
}
namespace logid::backend::hidpp {
struct DeviceConnectionEvent;
template<typename T>
class _deviceWrapper : public T {
friend class Device;
public:
template<typename... Args>
explicit _deviceWrapper(Args... args) : T(std::forward<Args>(args)...) {}
template<typename... Args>
static std::shared_ptr<T> make(Args... args) {
return std::make_shared<_deviceWrapper>(std::forward<Args>(args)...);
}
};
class Device {
template<typename T>
friend
class _deviceWrapper;
public:
struct EventHandler {
std::function<bool(Report&)> condition;
std::function<void(Report&)> callback;
};
class InvalidDevice : std::exception {
public:
enum Reason {
NoHIDPPReport,
InvalidRawDevice,
Asleep,
VirtualNode
};
explicit InvalidDevice(Reason reason) : _reason(reason) {}
[[nodiscard]] const char* what() const noexcept override;
[[nodiscard]] virtual Reason code() const noexcept;
private:
Reason _reason;
};
[[nodiscard]] const std::string& devicePath() const;
[[nodiscard]] DeviceIndex deviceIndex() const;
[[nodiscard]] const std::tuple<uint8_t, uint8_t>& version() const;
[[nodiscard]] const std::string& name() const;
[[nodiscard]] uint16_t pid() const;
EventHandlerLock<Device> addEventHandler(EventHandler handler);
virtual Report sendReport(const Report& report);
virtual void sendReportNoACK(const Report& report);
void handleEvent(Report& report);
[[nodiscard]] const std::shared_ptr<raw::RawDevice>& rawDevice() const;
Device(const Device&) = delete;
Device(Device&&) = delete;
virtual ~Device() = default;
protected:
Device(const std::string& path, DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout);
Device(std::shared_ptr<raw::RawDevice> raw_device, DeviceIndex index,
double timeout);
Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceConnectionEvent event, double timeout);
Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
DeviceIndex index, double timeout);
// Returns whether the report is a response
virtual bool responseReport(const Report& report);
bool isStable20();
bool isStable10();
void _sendReport(Report report);
void reportFixup(Report& report) const;
const std::chrono::milliseconds io_timeout;
uint8_t supported_reports{};
std::mutex _response_mutex;
std::condition_variable _response_cv;
private:
void _setupReportsAndInit();
void _init();
std::shared_ptr<raw::RawDevice> _raw_device;
EventHandlerLock<raw::RawDevice> _raw_handler;
std::shared_ptr<hidpp10::Receiver> _receiver;
std::string _path;
DeviceIndex _index;
std::tuple<uint8_t, uint8_t> _version;
uint16_t _pid{};
std::string _name;
std::mutex _send_mutex;
typedef std::variant<Report, Report::Hidpp10Error, Report::Hidpp20Error> Response;
std::optional<Response> _response;
std::optional<uint8_t> _sent_sub_id{};
std::optional<uint8_t> _sent_address{};
std::shared_ptr<EventHandlerList<Device>> _event_handlers;
std::weak_ptr<Device> _self;
protected:
template<typename T, typename... Args>
static std::shared_ptr<T> makeDerived(Args... args) {
auto device = _deviceWrapper<T>::make(std::forward<Args>(args)...);
device->_self = device;
device->_setupReportsAndInit();
return device;
}
public:
template<typename... Args>
static std::shared_ptr<Device> make(Args... args) {
return makeDerived<Device>(std::forward<Args>(args)...);
}
};
typedef Device::EventHandler EventHandler;
}
/*
* Copyright 2019-2023 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_BACKEND_HIDPP_DEVICE_H
#define LOGID_BACKEND_HIDPP_DEVICE_H
#include <backend/raw/RawDevice.h>
#include <backend/hidpp/Report.h>
#include <backend/hidpp/defs.h>
#include <backend/EventHandlerList.h>
#include <optional>
#include <variant>
#include <string>
#include <memory>
#include <functional>
#include <map>
namespace logid::backend::hidpp10 {
// Need to define here for a constructor
class Receiver;
}
namespace logid::backend::hidpp {
struct DeviceConnectionEvent;
template<typename T>
class _deviceWrapper : public T {
friend class Device;
public:
template<typename... Args>
explicit _deviceWrapper(Args... args) : T(std::forward<Args>(args)...) {}
template<typename... Args>
static std::shared_ptr<T> make(Args... args) {
return std::make_shared<_deviceWrapper>(std::forward<Args>(args)...);
}
};
class Device {
template<typename T>
friend
class _deviceWrapper;
public:
struct EventHandler {
std::function<bool(Report&)> condition;
std::function<void(Report&)> callback;
};
class InvalidDevice : std::exception {
public:
enum Reason {
NoHIDPPReport,
InvalidRawDevice,
Asleep,
VirtualNode
};
explicit InvalidDevice(Reason reason) : _reason(reason) {}
[[nodiscard]] const char* what() const noexcept override;
[[nodiscard]] virtual Reason code() const noexcept;
private:
Reason _reason;
};
[[nodiscard]] const std::string& devicePath() const;
[[nodiscard]] DeviceIndex deviceIndex() const;
[[nodiscard]] const std::tuple<uint8_t, uint8_t>& version() const;
[[nodiscard]] const std::string& name() const;
[[nodiscard]] uint16_t pid() const;
EventHandlerLock<Device> addEventHandler(EventHandler handler);
virtual Report sendReport(const Report& report);
virtual void sendReportNoACK(const Report& report);
void handleEvent(Report& report);
[[nodiscard]] const std::shared_ptr<raw::RawDevice>& rawDevice() const;
Device(const Device&) = delete;
Device(Device&&) = delete;
virtual ~Device() = default;
protected:
Device(const std::string& path, DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout);
Device(std::shared_ptr<raw::RawDevice> raw_device, DeviceIndex index,
double timeout);
Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceConnectionEvent event, double timeout);
Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
DeviceIndex index, double timeout);
// Returns whether the report is a response
virtual bool responseReport(const Report& report);
bool isStable20();
bool isStable10();
void _sendReport(Report report);
void reportFixup(Report& report) const;
const std::chrono::milliseconds io_timeout;
uint8_t supported_reports{};
std::mutex _response_mutex;
std::condition_variable _response_cv;
private:
void _setupReportsAndInit();
void _init();
std::shared_ptr<raw::RawDevice> _raw_device;
EventHandlerLock<raw::RawDevice> _raw_handler;
std::shared_ptr<hidpp10::Receiver> _receiver;
std::string _path;
DeviceIndex _index;
std::tuple<uint8_t, uint8_t> _version;
uint16_t _pid{};
std::string _name;
std::mutex _send_mutex;
typedef std::variant<Report, Report::Hidpp10Error, Report::Hidpp20Error> Response;
std::optional<Response> _response;
std::optional<uint8_t> _sent_sub_id{};
std::optional<uint8_t> _sent_address{};
std::shared_ptr<EventHandlerList<Device>> _event_handlers;
std::weak_ptr<Device> _self;
protected:
template<typename T, typename... Args>
static std::shared_ptr<T> makeDerived(Args... args) {
auto device = _deviceWrapper<T>::make(std::forward<Args>(args)...);
device->_self = device;
device->_setupReportsAndInit();
return device;
}
public:
template<typename... Args>
static std::shared_ptr<Device> make(Args... args) {
return makeDerived<Device>(std::forward<Args>(args)...);
}
};
typedef Device::EventHandler EventHandler;
}
#endif //LOGID_BACKEND_HIDPP_DEVICE_H

View File

@ -1,297 +1,297 @@
/*
* Copyright 2019-2023 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 <backend/hidpp/Report.h>
#include <array>
#include <algorithm>
#include <cassert>
#include <backend/hidpp10/Error.h>
#include <backend/hidpp20/Error.h>
using namespace logid::backend::hidpp;
using namespace logid::backend;
/* Report descriptors were sourced from cvuchener/hidpp */
static const std::array<uint8_t, 22> ShortReportDesc = {
0xA1, 0x01, // Collection (Application)
0x85, 0x10, // Report ID (16)
0x75, 0x08, // Report Size (8)
0x95, 0x06, // Report Count (6)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x09, 0x01, // Usage (0001 - Vendor)
0x81, 0x00, // Input (Data, Array, Absolute)
0x09, 0x01, // Usage (0001 - Vendor)
0x91, 0x00, // Output (Data, Array, Absolute)
0xC0 // End Collection
};
static const std::array<uint8_t, 22> LongReportDesc = {
0xA1, 0x01, // Collection (Application)
0x85, 0x11, // Report ID (17)
0x75, 0x08, // Report Size (8)
0x95, 0x13, // Report Count (19)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x09, 0x02, // Usage (0002 - Vendor)
0x81, 0x00, // Input (Data, Array, Absolute)
0x09, 0x02, // Usage (0002 - Vendor)
0x91, 0x00, // Output (Data, Array, Absolute)
0xC0 // End Collection
};
/* Alternative versions from the G602 */
static const std::array<uint8_t, 22> ShortReportDesc2 = {
0xA1, 0x01, // Collection (Application)
0x85, 0x10, // Report ID (16)
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x09, 0x01, // Usage (0001 - Vendor)
0x81, 0x00, // Input (Data, Array, Absolute)
0x09, 0x01, // Usage (0001 - Vendor)
0x91, 0x00, // Output (Data, Array, Absolute)
0xC0 // End Collection
};
static const std::array<uint8_t, 22> LongReportDesc2 = {
0xA1, 0x01, // Collection (Application)
0x85, 0x11, // Report ID (17)
0x95, 0x13, // Report Count (19)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x09, 0x02, // Usage (0002 - Vendor)
0x81, 0x00, // Input (Data, Array, Absolute)
0x09, 0x02, // Usage (0002 - Vendor)
0x91, 0x00, // Output (Data, Array, Absolute)
0xC0 // End Collection
};
uint8_t hidpp::getSupportedReports(const std::vector<uint8_t>& report_desc) {
uint8_t ret = 0;
auto it = std::search(report_desc.begin(), report_desc.end(),
ShortReportDesc.begin(), ShortReportDesc.end());
if (it == report_desc.end())
it = std::search(report_desc.begin(), report_desc.end(),
ShortReportDesc2.begin(), ShortReportDesc2.end());
if (it != report_desc.end())
ret |= ShortReportSupported;
it = std::search(report_desc.begin(), report_desc.end(),
LongReportDesc.begin(), LongReportDesc.end());
if (it == report_desc.end())
it = std::search(report_desc.begin(), report_desc.end(),
LongReportDesc2.begin(), LongReportDesc2.end());
if (it != report_desc.end())
ret |= LongReportSupported;
return ret;
}
const char* Report::InvalidReportID::what() const noexcept {
return "Invalid report ID";
}
const char* Report::InvalidReportLength::what() const noexcept {
return "Invalid report length";
}
Report::Report(Report::Type type, DeviceIndex device_index,
uint8_t sub_id, uint8_t address) {
switch (type) {
case Type::Short:
_data.resize(HeaderLength + ShortParamLength);
break;
case Type::Long:
_data.resize(HeaderLength + LongParamLength);
break;
default:
throw InvalidReportID();
}
_data[Offset::Type] = type;
_data[Offset::DeviceIndex] = device_index;
_data[Offset::SubID] = sub_id;
_data[Offset::Address] = address;
}
Report::Report(Report::Type type, DeviceIndex device_index,
uint8_t feature_index, uint8_t function, uint8_t sw_id) {
assert(function <= 0x0f);
assert(sw_id <= 0x0f);
switch (type) {
case Type::Short:
_data.resize(HeaderLength + ShortParamLength);
break;
case Type::Long:
_data.resize(HeaderLength + LongParamLength);
break;
default:
throw InvalidReportID();
}
_data[Offset::Type] = type;
_data[Offset::DeviceIndex] = device_index;
_data[Offset::Feature] = feature_index;
_data[Offset::Function] = (function & 0x0f) << 4 |
(sw_id & 0x0f);
}
Report::Report(const std::vector<uint8_t>& data) :
_data(data) {
_data.resize(HeaderLength + LongParamLength);
// Truncating data is entirely valid here.
switch (_data[Offset::Type]) {
case Type::Short:
_data.resize(HeaderLength + ShortParamLength);
break;
case Type::Long:
_data.resize(HeaderLength + LongParamLength);
break;
default:
throw InvalidReportID();
}
}
Report::Type Report::type() const {
return static_cast<Report::Type>(_data[Offset::Type]);
}
void Report::setType(Report::Type type) {
switch (type) {
case Type::Short:
_data.resize(HeaderLength + ShortParamLength);
break;
case Type::Long:
_data.resize(HeaderLength + LongParamLength);
break;
default:
throw InvalidReportID();
}
_data[Offset::Type] = type;
}
hidpp::DeviceIndex Report::deviceIndex() const {
return static_cast<hidpp::DeviceIndex>(_data[Offset::DeviceIndex]);
}
[[maybe_unused]] void Report::setDeviceIndex(hidpp::DeviceIndex index) {
_data[Offset::DeviceIndex] = index;
}
uint8_t Report::feature() const {
return _data[Offset::Feature];
}
[[maybe_unused]] void Report::setFeature(uint8_t feature) {
_data[Offset::Parameters] = feature;
}
uint8_t Report::subId() const {
return _data[Offset::SubID];
}
[[maybe_unused]] void Report::setSubId(uint8_t sub_id) {
_data[Offset::SubID] = sub_id;
}
uint8_t Report::function() const {
return (_data[Offset::Function] >> 4) & 0x0f;
}
[[maybe_unused]] void Report::setFunction(uint8_t function) {
_data[Offset::Function] &= 0x0f;
_data[Offset::Function] |= (function & 0x0f) << 4;
}
uint8_t Report::swId() const {
return _data[Offset::Function] & 0x0f;
}
void Report::setSwId(uint8_t sw_id) {
_data[Offset::Function] &= 0xf0;
_data[Offset::Function] |= sw_id & 0x0f;
}
uint8_t Report::address() const {
return _data[Offset::Address];
}
[[maybe_unused]] void Report::setAddress(uint8_t address) {
_data[Offset::Address] = address;
}
std::vector<uint8_t>::iterator Report::paramBegin() {
return _data.begin() + Offset::Parameters;
}
std::vector<uint8_t>::iterator Report::paramEnd() {
return _data.end();
}
std::vector<uint8_t>::const_iterator Report::paramBegin() const {
return _data.begin() + Offset::Parameters;
}
std::vector<uint8_t>::const_iterator Report::paramEnd() const {
return _data.end();
}
void Report::setParams(const std::vector<uint8_t>& _params) {
assert(_params.size() <= _data.size() - HeaderLength);
for (std::size_t i = 0; i < _params.size(); i++)
_data[Offset::Parameters + i] = _params[i];
}
bool Report::isError10(Report::Hidpp10Error& error) const {
if (_data[Offset::Type] != Type::Short ||
_data[Offset::SubID] != hidpp10::ErrorID)
return false;
error.device_index = deviceIndex();
error.sub_id = _data[3];
error.address = _data[4];
error.error_code = _data[5];
return true;
}
bool Report::isError20(Report::Hidpp20Error& error) const {
if (_data[Offset::Type] != Type::Long ||
_data[Offset::Feature] != hidpp20::ErrorID)
return false;
error.device_index = deviceIndex();
error.feature_index = _data[3];
error.function = (_data[4] >> 4) & 0x0f;
error.software_id = _data[4] & 0x0f;
error.error_code = _data[5];
return true;
}
const std::vector<uint8_t>& Report::rawReport() const {
return _data;
}
/*
* Copyright 2019-2023 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 <backend/hidpp/Report.h>
#include <array>
#include <algorithm>
#include <cassert>
#include <backend/hidpp10/Error.h>
#include <backend/hidpp20/Error.h>
using namespace logid::backend::hidpp;
using namespace logid::backend;
/* Report descriptors were sourced from cvuchener/hidpp */
static const std::array<uint8_t, 22> ShortReportDesc = {
0xA1, 0x01, // Collection (Application)
0x85, 0x10, // Report ID (16)
0x75, 0x08, // Report Size (8)
0x95, 0x06, // Report Count (6)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x09, 0x01, // Usage (0001 - Vendor)
0x81, 0x00, // Input (Data, Array, Absolute)
0x09, 0x01, // Usage (0001 - Vendor)
0x91, 0x00, // Output (Data, Array, Absolute)
0xC0 // End Collection
};
static const std::array<uint8_t, 22> LongReportDesc = {
0xA1, 0x01, // Collection (Application)
0x85, 0x11, // Report ID (17)
0x75, 0x08, // Report Size (8)
0x95, 0x13, // Report Count (19)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x09, 0x02, // Usage (0002 - Vendor)
0x81, 0x00, // Input (Data, Array, Absolute)
0x09, 0x02, // Usage (0002 - Vendor)
0x91, 0x00, // Output (Data, Array, Absolute)
0xC0 // End Collection
};
/* Alternative versions from the G602 */
static const std::array<uint8_t, 22> ShortReportDesc2 = {
0xA1, 0x01, // Collection (Application)
0x85, 0x10, // Report ID (16)
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x09, 0x01, // Usage (0001 - Vendor)
0x81, 0x00, // Input (Data, Array, Absolute)
0x09, 0x01, // Usage (0001 - Vendor)
0x91, 0x00, // Output (Data, Array, Absolute)
0xC0 // End Collection
};
static const std::array<uint8_t, 22> LongReportDesc2 = {
0xA1, 0x01, // Collection (Application)
0x85, 0x11, // Report ID (17)
0x95, 0x13, // Report Count (19)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x09, 0x02, // Usage (0002 - Vendor)
0x81, 0x00, // Input (Data, Array, Absolute)
0x09, 0x02, // Usage (0002 - Vendor)
0x91, 0x00, // Output (Data, Array, Absolute)
0xC0 // End Collection
};
uint8_t hidpp::getSupportedReports(const std::vector<uint8_t>& report_desc) {
uint8_t ret = 0;
auto it = std::search(report_desc.begin(), report_desc.end(),
ShortReportDesc.begin(), ShortReportDesc.end());
if (it == report_desc.end())
it = std::search(report_desc.begin(), report_desc.end(),
ShortReportDesc2.begin(), ShortReportDesc2.end());
if (it != report_desc.end())
ret |= ShortReportSupported;
it = std::search(report_desc.begin(), report_desc.end(),
LongReportDesc.begin(), LongReportDesc.end());
if (it == report_desc.end())
it = std::search(report_desc.begin(), report_desc.end(),
LongReportDesc2.begin(), LongReportDesc2.end());
if (it != report_desc.end())
ret |= LongReportSupported;
return ret;
}
const char* Report::InvalidReportID::what() const noexcept {
return "Invalid report ID";
}
const char* Report::InvalidReportLength::what() const noexcept {
return "Invalid report length";
}
Report::Report(Report::Type type, DeviceIndex device_index,
uint8_t sub_id, uint8_t address) {
switch (type) {
case Type::Short:
_data.resize(HeaderLength + ShortParamLength);
break;
case Type::Long:
_data.resize(HeaderLength + LongParamLength);
break;
default:
throw InvalidReportID();
}
_data[Offset::Type] = type;
_data[Offset::DeviceIndex] = device_index;
_data[Offset::SubID] = sub_id;
_data[Offset::Address] = address;
}
Report::Report(Report::Type type, DeviceIndex device_index,
uint8_t feature_index, uint8_t function, uint8_t sw_id) {
assert(function <= 0x0f);
assert(sw_id <= 0x0f);
switch (type) {
case Type::Short:
_data.resize(HeaderLength + ShortParamLength);
break;
case Type::Long:
_data.resize(HeaderLength + LongParamLength);
break;
default:
throw InvalidReportID();
}
_data[Offset::Type] = type;
_data[Offset::DeviceIndex] = device_index;
_data[Offset::Feature] = feature_index;
_data[Offset::Function] = (function & 0x0f) << 4 |
(sw_id & 0x0f);
}
Report::Report(const std::vector<uint8_t>& data) :
_data(data) {
_data.resize(HeaderLength + LongParamLength);
// Truncating data is entirely valid here.
switch (_data[Offset::Type]) {
case Type::Short:
_data.resize(HeaderLength + ShortParamLength);
break;
case Type::Long:
_data.resize(HeaderLength + LongParamLength);
break;
default:
throw InvalidReportID();
}
}
Report::Type Report::type() const {
return static_cast<Report::Type>(_data[Offset::Type]);
}
void Report::setType(Report::Type type) {
switch (type) {
case Type::Short:
_data.resize(HeaderLength + ShortParamLength);
break;
case Type::Long:
_data.resize(HeaderLength + LongParamLength);
break;
default:
throw InvalidReportID();
}
_data[Offset::Type] = type;
}
hidpp::DeviceIndex Report::deviceIndex() const {
return static_cast<hidpp::DeviceIndex>(_data[Offset::DeviceIndex]);
}
[[maybe_unused]] void Report::setDeviceIndex(hidpp::DeviceIndex index) {
_data[Offset::DeviceIndex] = index;
}
uint8_t Report::feature() const {
return _data[Offset::Feature];
}
[[maybe_unused]] void Report::setFeature(uint8_t feature) {
_data[Offset::Parameters] = feature;
}
uint8_t Report::subId() const {
return _data[Offset::SubID];
}
[[maybe_unused]] void Report::setSubId(uint8_t sub_id) {
_data[Offset::SubID] = sub_id;
}
uint8_t Report::function() const {
return (_data[Offset::Function] >> 4) & 0x0f;
}
[[maybe_unused]] void Report::setFunction(uint8_t function) {
_data[Offset::Function] &= 0x0f;
_data[Offset::Function] |= (function & 0x0f) << 4;
}
uint8_t Report::swId() const {
return _data[Offset::Function] & 0x0f;
}
void Report::setSwId(uint8_t sw_id) {
_data[Offset::Function] &= 0xf0;
_data[Offset::Function] |= sw_id & 0x0f;
}
uint8_t Report::address() const {
return _data[Offset::Address];
}
[[maybe_unused]] void Report::setAddress(uint8_t address) {
_data[Offset::Address] = address;
}
std::vector<uint8_t>::iterator Report::paramBegin() {
return _data.begin() + Offset::Parameters;
}
std::vector<uint8_t>::iterator Report::paramEnd() {
return _data.end();
}
std::vector<uint8_t>::const_iterator Report::paramBegin() const {
return _data.begin() + Offset::Parameters;
}
std::vector<uint8_t>::const_iterator Report::paramEnd() const {
return _data.end();
}
void Report::setParams(const std::vector<uint8_t>& _params) {
assert(_params.size() <= _data.size() - HeaderLength);
for (std::size_t i = 0; i < _params.size(); i++)
_data[Offset::Parameters + i] = _params[i];
}
bool Report::isError10(Report::Hidpp10Error& error) const {
if (_data[Offset::Type] != Type::Short ||
_data[Offset::SubID] != hidpp10::ErrorID)
return false;
error.device_index = deviceIndex();
error.sub_id = _data[3];
error.address = _data[4];
error.error_code = _data[5];
return true;
}
bool Report::isError20(Report::Hidpp20Error& error) const {
if (_data[Offset::Type] != Type::Long ||
_data[Offset::Feature] != hidpp20::ErrorID)
return false;
error.device_index = deviceIndex();
error.feature_index = _data[3];
error.function = (_data[4] >> 4) & 0x0f;
error.software_id = _data[4] & 0x0f;
error.error_code = _data[5];
return true;
}
const std::vector<uint8_t>& Report::rawReport() const {
return _data;
}

View File

@ -1,135 +1,135 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP_REPORT_H
#define LOGID_BACKEND_HIDPP_REPORT_H
#include <backend/raw/RawDevice.h>
#include <backend/hidpp/defs.h>
#include <cstdint>
namespace logid::backend::hidpp {
uint8_t getSupportedReports(const std::vector<uint8_t>& report_desc);
/* Some devices only support a subset of these reports */
static constexpr uint8_t ShortReportSupported = 1U;
static constexpr uint8_t LongReportSupported = (1U<<1);
/* Very long reports exist, however they have not been encountered so far */
namespace Offset {
static constexpr uint8_t Type = 0;
static constexpr uint8_t DeviceIndex = 1;
static constexpr uint8_t SubID = 2;
static constexpr uint8_t Feature = 2;
static constexpr uint8_t Address = 3;
static constexpr uint8_t Function = 3;
static constexpr uint8_t Parameters = 4;
}
class Report {
public:
typedef ReportType::ReportType Type;
class InvalidReportID : public std::exception {
public:
InvalidReportID() = default;
[[nodiscard]] const char* what() const noexcept override;
};
class InvalidReportLength : public std::exception {
public:
InvalidReportLength() = default;
[[nodiscard]] const char* what() const noexcept override;
};
static constexpr std::size_t MaxDataLength = 20;
Report(Report::Type type, DeviceIndex device_index,
uint8_t sub_id,
uint8_t address);
Report(Report::Type type, DeviceIndex device_index,
uint8_t feature_index,
uint8_t function,
uint8_t sw_id);
explicit Report(const std::vector<uint8_t>& data);
[[nodiscard]] Report::Type type() const;
void setType(Report::Type type);
[[nodiscard]] DeviceIndex deviceIndex() const;
[[maybe_unused]] void setDeviceIndex(DeviceIndex index);
[[nodiscard]] uint8_t feature() const;
[[maybe_unused]] void setFeature(uint8_t feature);
[[nodiscard]] uint8_t subId() const;
[[maybe_unused]] void setSubId(uint8_t sub_id);
[[nodiscard]] uint8_t function() const;
[[maybe_unused]] void setFunction(uint8_t function);
[[nodiscard]] uint8_t swId() const;
void setSwId(uint8_t sw_id);
[[nodiscard]] uint8_t address() const;
[[maybe_unused]] void setAddress(uint8_t address);
[[nodiscard]] std::vector<uint8_t>::iterator paramBegin();
[[nodiscard]] std::vector<uint8_t>::iterator paramEnd();
[[nodiscard]] std::vector<uint8_t>::const_iterator paramBegin() const;
[[nodiscard]] std::vector<uint8_t>::const_iterator paramEnd() const;
void setParams(const std::vector<uint8_t>& _params);
struct Hidpp10Error {
hidpp::DeviceIndex device_index;
uint8_t sub_id, address, error_code;
};
bool isError10(Hidpp10Error& error) const;
struct Hidpp20Error {
hidpp::DeviceIndex device_index;
uint8_t feature_index, function, software_id, error_code;
};
bool isError20(Hidpp20Error& error) const;
[[nodiscard]] const std::vector<uint8_t>& rawReport() const;
static constexpr std::size_t HeaderLength = 4;
private:
std::vector<uint8_t> _data;
};
}
/*
* Copyright 2019-2023 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_BACKEND_HIDPP_REPORT_H
#define LOGID_BACKEND_HIDPP_REPORT_H
#include <backend/raw/RawDevice.h>
#include <backend/hidpp/defs.h>
#include <cstdint>
namespace logid::backend::hidpp {
uint8_t getSupportedReports(const std::vector<uint8_t>& report_desc);
/* Some devices only support a subset of these reports */
static constexpr uint8_t ShortReportSupported = 1U;
static constexpr uint8_t LongReportSupported = (1U<<1);
/* Very long reports exist, however they have not been encountered so far */
namespace Offset {
static constexpr uint8_t Type = 0;
static constexpr uint8_t DeviceIndex = 1;
static constexpr uint8_t SubID = 2;
static constexpr uint8_t Feature = 2;
static constexpr uint8_t Address = 3;
static constexpr uint8_t Function = 3;
static constexpr uint8_t Parameters = 4;
}
class Report {
public:
typedef ReportType::ReportType Type;
class InvalidReportID : public std::exception {
public:
InvalidReportID() = default;
[[nodiscard]] const char* what() const noexcept override;
};
class InvalidReportLength : public std::exception {
public:
InvalidReportLength() = default;
[[nodiscard]] const char* what() const noexcept override;
};
static constexpr std::size_t MaxDataLength = 20;
Report(Report::Type type, DeviceIndex device_index,
uint8_t sub_id,
uint8_t address);
Report(Report::Type type, DeviceIndex device_index,
uint8_t feature_index,
uint8_t function,
uint8_t sw_id);
explicit Report(const std::vector<uint8_t>& data);
[[nodiscard]] Report::Type type() const;
void setType(Report::Type type);
[[nodiscard]] DeviceIndex deviceIndex() const;
[[maybe_unused]] void setDeviceIndex(DeviceIndex index);
[[nodiscard]] uint8_t feature() const;
[[maybe_unused]] void setFeature(uint8_t feature);
[[nodiscard]] uint8_t subId() const;
[[maybe_unused]] void setSubId(uint8_t sub_id);
[[nodiscard]] uint8_t function() const;
[[maybe_unused]] void setFunction(uint8_t function);
[[nodiscard]] uint8_t swId() const;
void setSwId(uint8_t sw_id);
[[nodiscard]] uint8_t address() const;
[[maybe_unused]] void setAddress(uint8_t address);
[[nodiscard]] std::vector<uint8_t>::iterator paramBegin();
[[nodiscard]] std::vector<uint8_t>::iterator paramEnd();
[[nodiscard]] std::vector<uint8_t>::const_iterator paramBegin() const;
[[nodiscard]] std::vector<uint8_t>::const_iterator paramEnd() const;
void setParams(const std::vector<uint8_t>& _params);
struct Hidpp10Error {
hidpp::DeviceIndex device_index;
uint8_t sub_id, address, error_code;
};
bool isError10(Hidpp10Error& error) const;
struct Hidpp20Error {
hidpp::DeviceIndex device_index;
uint8_t feature_index, function, software_id, error_code;
};
bool isError20(Hidpp20Error& error) const;
[[nodiscard]] const std::vector<uint8_t>& rawReport() const;
static constexpr std::size_t HeaderLength = 4;
private:
std::vector<uint8_t> _data;
};
}
#endif //LOGID_BACKEND_HIDPP_REPORT_H

View File

@ -1,51 +1,51 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP_DEFS_H
#define LOGID_BACKEND_HIDPP_DEFS_H
#include <cstdint>
namespace logid::backend::hidpp {
namespace ReportType {
enum ReportType : uint8_t {
Short = 0x10,
Long = 0x11
};
}
enum DeviceIndex : uint8_t {
DefaultDevice = 0xff,
CordedDevice = 0,
WirelessDevice1 = 1,
WirelessDevice2 [[maybe_unused]] = 2,
WirelessDevice3 [[maybe_unused]] = 3,
WirelessDevice4 [[maybe_unused]] = 4,
WirelessDevice5 [[maybe_unused]] = 5,
WirelessDevice6 = 6,
};
static constexpr uint8_t softwareID = 2;
/* For sending reports with no response, use a different SW ID */
static constexpr uint8_t noAckSoftwareID = 3;
static constexpr std::size_t ShortParamLength = 3;
static constexpr std::size_t LongParamLength = 16;
}
/*
* Copyright 2019-2023 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_BACKEND_HIDPP_DEFS_H
#define LOGID_BACKEND_HIDPP_DEFS_H
#include <cstdint>
namespace logid::backend::hidpp {
namespace ReportType {
enum ReportType : uint8_t {
Short = 0x10,
Long = 0x11
};
}
enum DeviceIndex : uint8_t {
DefaultDevice = 0xff,
CordedDevice = 0,
WirelessDevice1 = 1,
WirelessDevice2 [[maybe_unused]] = 2,
WirelessDevice3 [[maybe_unused]] = 3,
WirelessDevice4 [[maybe_unused]] = 4,
WirelessDevice5 [[maybe_unused]] = 5,
WirelessDevice6 = 6,
};
static constexpr uint8_t softwareID = 2;
/* For sending reports with no response, use a different SW ID */
static constexpr uint8_t noAckSoftwareID = 3;
static constexpr std::size_t ShortParamLength = 3;
static constexpr std::size_t LongParamLength = 16;
}
#endif //LOGID_BACKEND_HIDPP_DEFS_H

View File

@ -1,164 +1,164 @@
/*
* Copyright 2019-2023 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 <backend/hidpp10/Device.h>
#include <backend/Error.h>
#include <cassert>
#include <utility>
using namespace logid::backend;
using namespace logid::backend::hidpp10;
hidpp::Report setupRegReport(hidpp::DeviceIndex index,
uint8_t sub_id, uint8_t address,
const std::vector<uint8_t>& params) {
hidpp::Report::Type type = params.size() <= hidpp::ShortParamLength ?
hidpp::Report::Type::Short : hidpp::Report::Type::Long;
if (sub_id == SetRegisterLong) {
// When setting a long register, the report must be long.
type = hidpp::Report::Type::Long;
}
hidpp::Report request(type, index, sub_id, address);
std::copy(params.begin(), params.end(), request.paramBegin());
return request;
}
Device::Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) :
hidpp::Device(path, index, monitor, timeout) {
}
Device::Device(std::shared_ptr<raw::RawDevice> raw_dev, hidpp::DeviceIndex index,
double timeout) : hidpp::Device(std::move(raw_dev), index, timeout) {
}
Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceIndex index,
double timeout)
: hidpp::Device(receiver, index, timeout) {
}
void Device::ResponseSlot::reset() {
response.reset();
sub_id.reset();
}
hidpp::Report Device::sendReport(const hidpp::Report& report) {
auto& response_slot = _responses[report.subId() % SubIDCount];
std::unique_lock<std::mutex> lock(_response_mutex);
_response_cv.wait(lock, [&response_slot]() {
return !response_slot.sub_id.has_value();
});
response_slot.sub_id = report.subId();
_sendReport(report);
bool valid = _response_cv.wait_for(lock, io_timeout, [&response_slot]() {
return response_slot.response.has_value();
});
if (!valid) {
response_slot.reset();
throw TimeoutError();
}
auto response = response_slot.response.value();
response_slot.reset();
if (std::holds_alternative<hidpp::Report>(response)) {
return std::get<hidpp::Report>(response);
} else { // if(std::holds_alternative<hidpp::Report::Hidpp10Error>(response))
auto error = std::get<hidpp::Report::Hidpp10Error>(response);
throw Error(error.error_code, error.device_index);
}
}
bool Device::responseReport(const hidpp::Report& report) {
std::lock_guard<std::mutex> lock(_response_mutex);
uint8_t sub_id;
bool is_error = false;
hidpp::Report::Hidpp10Error hidpp10_error{};
if (report.isError10(hidpp10_error)) {
sub_id = hidpp10_error.sub_id;
is_error = true;
} else {
sub_id = report.subId();
}
auto& response_slot = _responses[sub_id % SubIDCount];
if (!response_slot.sub_id.has_value() || response_slot.sub_id.value() != sub_id)
return false;
if (is_error) {
response_slot.response = hidpp10_error;
} else {
response_slot.response = report;
}
_response_cv.notify_all();
return true;
}
std::vector<uint8_t> Device::getRegister(uint8_t address,
const std::vector<uint8_t>& params,
hidpp::Report::Type type) {
assert(params.size() <= hidpp::LongParamLength);
uint8_t sub_id = type == hidpp::Report::Type::Short ?
GetRegisterShort : GetRegisterLong;
return accessRegister(sub_id, address, params);
}
std::vector<uint8_t> Device::setRegister(uint8_t address,
const std::vector<uint8_t>& params,
hidpp::Report::Type type) {
assert(params.size() <= hidpp::LongParamLength);
uint8_t sub_id = type == hidpp::Report::Type::Short ?
SetRegisterShort : SetRegisterLong;
return accessRegister(sub_id, address, params);
}
void Device::setRegisterNoResponse(uint8_t address,
const std::vector<uint8_t>& params,
hidpp::Report::Type type) {
assert(params.size() <= hidpp::LongParamLength);
uint8_t sub_id = type == hidpp::Report::Type::Short ?
SetRegisterShort : SetRegisterLong;
return accessRegisterNoResponse(sub_id, address, params);
}
std::vector<uint8_t> Device::accessRegister(uint8_t sub_id, uint8_t address,
const std::vector<uint8_t>& params) {
auto response = sendReport(setupRegReport(deviceIndex(), sub_id, address, params));
return {response.paramBegin(), response.paramEnd()};
}
void Device::accessRegisterNoResponse(uint8_t sub_id, uint8_t address,
const std::vector<uint8_t>& params) {
sendReportNoACK(setupRegReport(deviceIndex(), sub_id, address, params));
}
/*
* Copyright 2019-2023 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 <backend/hidpp10/Device.h>
#include <backend/Error.h>
#include <cassert>
#include <utility>
using namespace logid::backend;
using namespace logid::backend::hidpp10;
hidpp::Report setupRegReport(hidpp::DeviceIndex index,
uint8_t sub_id, uint8_t address,
const std::vector<uint8_t>& params) {
hidpp::Report::Type type = params.size() <= hidpp::ShortParamLength ?
hidpp::Report::Type::Short : hidpp::Report::Type::Long;
if (sub_id == SetRegisterLong) {
// When setting a long register, the report must be long.
type = hidpp::Report::Type::Long;
}
hidpp::Report request(type, index, sub_id, address);
std::copy(params.begin(), params.end(), request.paramBegin());
return request;
}
Device::Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) :
hidpp::Device(path, index, monitor, timeout) {
}
Device::Device(std::shared_ptr<raw::RawDevice> raw_dev, hidpp::DeviceIndex index,
double timeout) : hidpp::Device(std::move(raw_dev), index, timeout) {
}
Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceIndex index,
double timeout)
: hidpp::Device(receiver, index, timeout) {
}
void Device::ResponseSlot::reset() {
response.reset();
sub_id.reset();
}
hidpp::Report Device::sendReport(const hidpp::Report& report) {
auto& response_slot = _responses[report.subId() % SubIDCount];
std::unique_lock<std::mutex> lock(_response_mutex);
_response_cv.wait(lock, [&response_slot]() {
return !response_slot.sub_id.has_value();
});
response_slot.sub_id = report.subId();
_sendReport(report);
bool valid = _response_cv.wait_for(lock, io_timeout, [&response_slot]() {
return response_slot.response.has_value();
});
if (!valid) {
response_slot.reset();
throw TimeoutError();
}
auto response = response_slot.response.value();
response_slot.reset();
if (std::holds_alternative<hidpp::Report>(response)) {
return std::get<hidpp::Report>(response);
} else { // if(std::holds_alternative<hidpp::Report::Hidpp10Error>(response))
auto error = std::get<hidpp::Report::Hidpp10Error>(response);
throw Error(error.error_code, error.device_index);
}
}
bool Device::responseReport(const hidpp::Report& report) {
std::lock_guard<std::mutex> lock(_response_mutex);
uint8_t sub_id;
bool is_error = false;
hidpp::Report::Hidpp10Error hidpp10_error{};
if (report.isError10(hidpp10_error)) {
sub_id = hidpp10_error.sub_id;
is_error = true;
} else {
sub_id = report.subId();
}
auto& response_slot = _responses[sub_id % SubIDCount];
if (!response_slot.sub_id.has_value() || response_slot.sub_id.value() != sub_id)
return false;
if (is_error) {
response_slot.response = hidpp10_error;
} else {
response_slot.response = report;
}
_response_cv.notify_all();
return true;
}
std::vector<uint8_t> Device::getRegister(uint8_t address,
const std::vector<uint8_t>& params,
hidpp::Report::Type type) {
assert(params.size() <= hidpp::LongParamLength);
uint8_t sub_id = type == hidpp::Report::Type::Short ?
GetRegisterShort : GetRegisterLong;
return accessRegister(sub_id, address, params);
}
std::vector<uint8_t> Device::setRegister(uint8_t address,
const std::vector<uint8_t>& params,
hidpp::Report::Type type) {
assert(params.size() <= hidpp::LongParamLength);
uint8_t sub_id = type == hidpp::Report::Type::Short ?
SetRegisterShort : SetRegisterLong;
return accessRegister(sub_id, address, params);
}
void Device::setRegisterNoResponse(uint8_t address,
const std::vector<uint8_t>& params,
hidpp::Report::Type type) {
assert(params.size() <= hidpp::LongParamLength);
uint8_t sub_id = type == hidpp::Report::Type::Short ?
SetRegisterShort : SetRegisterLong;
return accessRegisterNoResponse(sub_id, address, params);
}
std::vector<uint8_t> Device::accessRegister(uint8_t sub_id, uint8_t address,
const std::vector<uint8_t>& params) {
auto response = sendReport(setupRegReport(deviceIndex(), sub_id, address, params));
return {response.paramBegin(), response.paramEnd()};
}
void Device::accessRegisterNoResponse(uint8_t sub_id, uint8_t address,
const std::vector<uint8_t>& params) {
sendReportNoACK(setupRegReport(deviceIndex(), sub_id, address, params));
}

View File

@ -1,94 +1,94 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP10_DEVICE_H
#define LOGID_BACKEND_HIDPP10_DEVICE_H
#include <optional>
#include <variant>
#include <backend/hidpp/Device.h>
#include <backend/hidpp10/Error.h>
#include <backend/hidpp10/defs.h>
namespace logid::backend::hidpp10 {
class Device : public hidpp::Device {
public:
hidpp::Report sendReport(const hidpp::Report& report) final;
std::vector<uint8_t> getRegister(uint8_t address,
const std::vector<uint8_t>& params,
hidpp::Report::Type type);
std::vector<uint8_t> setRegister(uint8_t address,
const std::vector<uint8_t>& params,
hidpp::Report::Type type);
void setRegisterNoResponse(uint8_t address, const std::vector<uint8_t>& params,
hidpp::Report::Type type);
protected:
Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout);
Device(std::shared_ptr<raw::RawDevice> raw_dev,
hidpp::DeviceIndex index, double timeout);
Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceIndex index, double timeout);
bool responseReport(const hidpp::Report& report) final;
private:
typedef std::variant<hidpp::Report, hidpp::Report::Hidpp10Error> Response;
struct ResponseSlot {
std::optional<Response> response;
std::optional<uint8_t> sub_id;
void reset();
};
std::array<ResponseSlot, SubIDCount> _responses;
std::vector<uint8_t> accessRegister(
uint8_t sub_id, uint8_t address, const std::vector<uint8_t>& params);
void accessRegisterNoResponse(
uint8_t sub_id, uint8_t address, const std::vector<uint8_t>& params);
protected:
template<typename T, typename... Args>
static std::shared_ptr<T> makeDerived(Args... args) {
auto device = hidpp::Device::makeDerived<T>(std::forward<Args>(args)...);
if (std::get<0>(device->version()) != 1)
throw std::invalid_argument("not a hid++ 1.0 device");
return device;
}
public:
template<typename... Args>
static std::shared_ptr<Device> make(Args... args) {
return makeDerived<Device>(std::forward<Args>(args)...);
}
};
}
/*
* Copyright 2019-2023 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_BACKEND_HIDPP10_DEVICE_H
#define LOGID_BACKEND_HIDPP10_DEVICE_H
#include <optional>
#include <variant>
#include <backend/hidpp/Device.h>
#include <backend/hidpp10/Error.h>
#include <backend/hidpp10/defs.h>
namespace logid::backend::hidpp10 {
class Device : public hidpp::Device {
public:
hidpp::Report sendReport(const hidpp::Report& report) final;
std::vector<uint8_t> getRegister(uint8_t address,
const std::vector<uint8_t>& params,
hidpp::Report::Type type);
std::vector<uint8_t> setRegister(uint8_t address,
const std::vector<uint8_t>& params,
hidpp::Report::Type type);
void setRegisterNoResponse(uint8_t address, const std::vector<uint8_t>& params,
hidpp::Report::Type type);
protected:
Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout);
Device(std::shared_ptr<raw::RawDevice> raw_dev,
hidpp::DeviceIndex index, double timeout);
Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceIndex index, double timeout);
bool responseReport(const hidpp::Report& report) final;
private:
typedef std::variant<hidpp::Report, hidpp::Report::Hidpp10Error> Response;
struct ResponseSlot {
std::optional<Response> response;
std::optional<uint8_t> sub_id;
void reset();
};
std::array<ResponseSlot, SubIDCount> _responses;
std::vector<uint8_t> accessRegister(
uint8_t sub_id, uint8_t address, const std::vector<uint8_t>& params);
void accessRegisterNoResponse(
uint8_t sub_id, uint8_t address, const std::vector<uint8_t>& params);
protected:
template<typename T, typename... Args>
static std::shared_ptr<T> makeDerived(Args... args) {
auto device = hidpp::Device::makeDerived<T>(std::forward<Args>(args)...);
if (std::get<0>(device->version()) != 1)
throw std::invalid_argument("not a hid++ 1.0 device");
return device;
}
public:
template<typename... Args>
static std::shared_ptr<Device> make(Args... args) {
return makeDerived<Device>(std::forward<Args>(args)...);
}
};
}
#endif //LOGID_BACKEND_HIDPP10_DEVICE_H

View File

@ -1,68 +1,68 @@
/*
* Copyright 2019-2023 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 <backend/hidpp10/Error.h>
#include <cassert>
using namespace logid::backend;
using namespace logid::backend::hidpp10;
Error::Error(uint8_t code, hidpp::DeviceIndex index) : _code(code), _index(index) {
assert(code != Success);
}
const char* Error::what() const noexcept {
switch (_code) {
case Success:
return "Success";
case InvalidSubID:
return "Invalid sub ID";
case InvalidAddress:
return "Invalid address";
case InvalidValue:
return "Invalid value";
case ConnectFail:
return "Connection failure";
case TooManyDevices:
return "Too many devices";
case AlreadyExists:
return "Already exists";
case Busy:
return "Busy";
case UnknownDevice:
return "Unknown device";
case ResourceError:
return "Resource error";
case RequestUnavailable:
return "Request unavailable";
case InvalidParameterValue:
return "Invalid parameter value";
case WrongPINCode:
return "Wrong PIN code";
default:
return "Unknown error code";
}
}
uint8_t Error::code() const noexcept {
return _code;
}
hidpp::DeviceIndex Error::deviceIndex() const noexcept {
return _index;
}
/*
* Copyright 2019-2023 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 <backend/hidpp10/Error.h>
#include <cassert>
using namespace logid::backend;
using namespace logid::backend::hidpp10;
Error::Error(uint8_t code, hidpp::DeviceIndex index) : _code(code), _index(index) {
assert(code != Success);
}
const char* Error::what() const noexcept {
switch (_code) {
case Success:
return "Success";
case InvalidSubID:
return "Invalid sub ID";
case InvalidAddress:
return "Invalid address";
case InvalidValue:
return "Invalid value";
case ConnectFail:
return "Connection failure";
case TooManyDevices:
return "Too many devices";
case AlreadyExists:
return "Already exists";
case Busy:
return "Busy";
case UnknownDevice:
return "Unknown device";
case ResourceError:
return "Resource error";
case RequestUnavailable:
return "Request unavailable";
case InvalidParameterValue:
return "Invalid parameter value";
case WrongPINCode:
return "Wrong PIN code";
default:
return "Unknown error code";
}
}
uint8_t Error::code() const noexcept {
return _code;
}
hidpp::DeviceIndex Error::deviceIndex() const noexcept {
return _index;
}

View File

@ -1,61 +1,61 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP10_ERROR_H
#define LOGID_BACKEND_HIDPP10_ERROR_H
#include <backend/hidpp/defs.h>
#include <cstdint>
#include <exception>
namespace logid::backend::hidpp10 {
static constexpr uint8_t ErrorID = 0x8f;
class Error : public std::exception {
public:
enum ErrorCode : uint8_t {
Success = 0x00,
InvalidSubID = 0x01,
InvalidAddress = 0x02,
InvalidValue = 0x03,
ConnectFail = 0x04,
TooManyDevices = 0x05,
AlreadyExists = 0x06,
Busy = 0x07,
UnknownDevice = 0x08,
ResourceError = 0x09,
RequestUnavailable = 0x0A,
InvalidParameterValue = 0x0B,
WrongPINCode = 0x0C
};
Error(uint8_t code, hidpp::DeviceIndex index);
[[nodiscard]] const char* what() const noexcept override;
[[nodiscard]] uint8_t code() const noexcept;
[[nodiscard]] hidpp::DeviceIndex deviceIndex() const noexcept;
private:
uint8_t _code;
hidpp::DeviceIndex _index;
};
}
/*
* Copyright 2019-2023 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_BACKEND_HIDPP10_ERROR_H
#define LOGID_BACKEND_HIDPP10_ERROR_H
#include <backend/hidpp/defs.h>
#include <cstdint>
#include <exception>
namespace logid::backend::hidpp10 {
static constexpr uint8_t ErrorID = 0x8f;
class Error : public std::exception {
public:
enum ErrorCode : uint8_t {
Success = 0x00,
InvalidSubID = 0x01,
InvalidAddress = 0x02,
InvalidValue = 0x03,
ConnectFail = 0x04,
TooManyDevices = 0x05,
AlreadyExists = 0x06,
Busy = 0x07,
UnknownDevice = 0x08,
ResourceError = 0x09,
RequestUnavailable = 0x0A,
InvalidParameterValue = 0x0B,
WrongPINCode = 0x0C
};
Error(uint8_t code, hidpp::DeviceIndex index);
[[nodiscard]] const char* what() const noexcept override;
[[nodiscard]] uint8_t code() const noexcept;
[[nodiscard]] hidpp::DeviceIndex deviceIndex() const noexcept;
private:
uint8_t _code;
hidpp::DeviceIndex _index;
};
}
#endif //LOGID_BACKEND_HIDPP10_ERROR_H

View File

@ -1,380 +1,380 @@
/*
* Copyright 2019-2023 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 <backend/hidpp10/Receiver.h>
#include <cassert>
using namespace logid::backend::hidpp10;
using namespace logid::backend;
const char* InvalidReceiver::what() const noexcept {
return "Not a receiver";
}
Receiver::Receiver(const std::string& path, const std::shared_ptr<raw::DeviceMonitor>& monitor,
double timeout) : Device(path, hidpp::DefaultDevice, monitor, timeout) {
}
void Receiver::_receiverCheck() {
// Check if the device is a receiver
try {
getNotificationFlags();
} catch (hidpp10::Error& e) {
if (e.code() == Error::InvalidAddress)
throw InvalidReceiver();
}
// TODO: is there a better way of checking if this is a bolt receiver?
_is_bolt = pid() == 0xc548;
}
Receiver::NotificationFlags Receiver::getNotificationFlags() {
auto response = getRegister(EnableHidppNotifications, {}, hidpp::ReportType::Short);
NotificationFlags flags{};
flags.deviceBatteryStatus = response[0] & (1 << 4);
flags.receiverWirelessNotifications = response[1] & (1 << 0);
flags.receiverSoftwarePresent = response[1] & (1 << 3);
return flags;
}
void Receiver::setNotifications(NotificationFlags flags) {
std::vector<uint8_t> request(3);
if (flags.deviceBatteryStatus)
request[0] |= (1 << 4);
if (flags.receiverWirelessNotifications)
request[1] |= 1;
if (flags.receiverSoftwarePresent)
request[1] |= (1 << 3);
setRegister(EnableHidppNotifications, request, hidpp::ReportType::Short);
}
void Receiver::enumerate() {
setRegisterNoResponse(ConnectionState, {2}, hidpp::ReportType::Short);
}
///TODO: Investigate usage
uint8_t Receiver::getConnectionState(hidpp::DeviceIndex index) {
auto response = getRegister(ConnectionState, {index}, hidpp::ReportType::Short);
return response[0];
}
void Receiver::startPairing(uint8_t timeout) {
std::vector<uint8_t> request(3);
if (_is_bolt)
throw std::invalid_argument("unifying pairing on bolt");
request[0] = 1;
request[1] = hidpp::DefaultDevice;
request[2] = timeout;
if (_is_bolt) {
setRegister(BoltDevicePairing, request, hidpp::ReportType::Long);
} else {
setRegister(DevicePairing, request, hidpp::ReportType::Short);
}
}
// bolt pairing request from solaar
void Receiver::startBoltPairing(const DeviceDiscoveryEvent& discovery) {
std::vector<uint8_t> request(10);
request[0] = 1; // start pair
request[1] = 0; // slot, from solaar. what does this mean?
for(int i = 0; i < 6; ++i)
request[2 + i] = (discovery.address >> (i*8)) & 0xff;
request[8] = discovery.authentication;
// TODO: what does entropy do?
request[9] = (discovery.deviceType == hidpp::DeviceKeyboard) ? 10 : 20;
setRegister(BoltDevicePairing, request, hidpp::ReportType::Long);
}
void Receiver::stopPairing() {
std::vector<uint8_t> request(3);
request[0] = 2;
request[1] = hidpp::DefaultDevice;
if (_is_bolt)
setRegister(BoltDevicePairing, request, hidpp::ReportType::Long);
else
setRegister(DevicePairing, request, hidpp::ReportType::Short);
}
void Receiver::startDiscover(uint8_t timeout) {
std::vector<uint8_t> request = {timeout, 1};
if (!_is_bolt)
throw std::invalid_argument("not a bolt receiver");
setRegister(BoltDeviceDiscovery, request, hidpp::ReportType::Short);
}
void Receiver::stopDiscover() {
std::vector<uint8_t> request = {0, 2};
if (!_is_bolt)
throw std::invalid_argument("not a bolt receiver");
setRegister(BoltDeviceDiscovery, request, hidpp::ReportType::Short);
}
void Receiver::disconnect(hidpp::DeviceIndex index) {
std::vector<uint8_t> request(2);
request[0] = _is_bolt ? 3 : 2;
request[1] = index;
if (_is_bolt)
setRegister(BoltDevicePairing, request, hidpp::ReportType::Long);
else
setRegister(DevicePairing, request, hidpp::ReportType::Short);
}
std::map<hidpp::DeviceIndex, uint8_t> Receiver::getDeviceActivity() {
auto response = getRegister(DeviceActivity, {}, hidpp::ReportType::Long);
std::map<hidpp::DeviceIndex, uint8_t> device_activity;
for (uint8_t i = hidpp::WirelessDevice1; i <= hidpp::WirelessDevice6; i++)
device_activity[static_cast<hidpp::DeviceIndex>(i)] = response[i];
return device_activity;
}
struct Receiver::PairingInfo
Receiver::getPairingInfo(hidpp::DeviceIndex index) {
std::vector<uint8_t> request(1);
request[0] = index;
if (_is_bolt)
request[0] += 0x50;
else
request[0] += 0x1f;
auto response = getRegister(PairingInfo, request, hidpp::ReportType::Long);
struct PairingInfo info{};
if (_is_bolt) {
info = {
.destinationId = 0, // no longer given?
.reportInterval = 0, // no longer given?
.pid = (uint16_t) ((response[3] << 8) | response[2]),
.deviceType = static_cast<hidpp::DeviceType>(response[1])
};
} else {
info = {
.destinationId = response[1],
.reportInterval = response[2],
.pid = (uint16_t) ((response[3] << 8) | response[4]),
.deviceType = static_cast<hidpp::DeviceType>(response[7])
};
}
return info;
}
struct Receiver::ExtendedPairingInfo
Receiver::getExtendedPairingInfo(hidpp::DeviceIndex index) {
const int device_num_offset = _is_bolt ? 0x50 : 0x2f;
const int serial_num_offset = _is_bolt ? 4 : 1;
const int report_offset = _is_bolt ? 8 : 5;
const int psl_offset = _is_bolt ? 12 : 8;
std::vector<uint8_t> request(1, index + device_num_offset);
auto response = getRegister(PairingInfo, request, hidpp::ReportType::Long);
ExtendedPairingInfo info{};
info.serialNumber = 0;
for (uint8_t i = 0; i < 4; i++)
info.serialNumber |= (response[i + serial_num_offset] << 8 * i);
for (uint8_t i = 0; i < 4; i++)
info.reportTypes[i] = response[i + report_offset];
uint8_t psl = response[psl_offset] & 0xf;
if (psl > 0xc)
info.powerSwitchLocation = PowerSwitchLocation::Reserved;
else
info.powerSwitchLocation = static_cast<PowerSwitchLocation>(psl);
return info;
}
std::string Receiver::getDeviceName(hidpp::DeviceIndex index) {
std::vector<uint8_t> request(2);
std::string name;
request[0] = index;
if (_is_bolt) {
/* Undocumented, deduced the following
* param 1 refers to part of string, 1-indexed
*
* response at 0x01 is [reg] [param 1] [size] [str...]
* response at 0x02-... is [next part of str...]
*/
request[0] += 0x60;
request[1] = 0x01;
auto resp = getRegister(PairingInfo, request, hidpp::ReportType::Long);
const uint8_t size = resp[2];
const uint8_t chunk_size = resp.size() - 3;
const uint8_t chunks = size / chunk_size + (size % chunk_size ? 1 : 0);
name.resize(size, ' ');
for (int i = 0; i < chunks; ++i) {
for (int j = 0; j < chunk_size; ++j) {
name[i * chunk_size + j] = (char) resp[j + 3];
}
if (i < chunks - 1) {
request[1] = i + 1;
resp = getRegister(PairingInfo, request, hidpp::ReportType::Long);
}
}
} else {
request[0] += 0x3f;
auto response = getRegister(PairingInfo, request, hidpp::ReportType::Long);
const uint8_t size = response[1];
name.resize(size, ' ');
for (std::size_t i = 0; i < size && i + 2 < response.size(); i++)
name[i] = (char) (response[i + 2]);
}
return name;
}
hidpp::DeviceIndex Receiver::deviceDisconnectionEvent(const hidpp::Report& report) {
assert(report.subId() == DeviceDisconnection);
return report.deviceIndex();
}
hidpp::DeviceConnectionEvent Receiver::deviceConnectionEvent(const hidpp::Report& report) {
assert(report.subId() == DeviceConnection);
auto data = report.paramBegin();
return {
.index = report.deviceIndex(),
.pid = (uint16_t) ((data[2] << 8) | data[1]),
.deviceType = static_cast<hidpp::DeviceType>(data[0] & 0x0f),
.unifying = ((report.address() & 0b111) == 0x04),
.softwarePresent = bool(data[0] & (1 << 4)),
.encrypted = (bool) (data[0] & (1 << 5)),
.linkEstablished = !(data[0] & (1 << 6)),
.withPayload = (bool) (data[0] & (1 << 7)),
.fromTimeoutCheck = false,
};
}
bool Receiver::fillDeviceDiscoveryEvent(DeviceDiscoveryEvent& event,
const hidpp::Report& report) {
assert(report.subId() == DeviceDiscovered);
auto data = report.paramBegin();
if (data[1] == 0) {
// device discovery event
uint64_t address = 0 ;
for (int i = 0; i < 6; ++i)
address |= ((uint64_t)(data[6 + i]) << (8*i));
event.deviceType = static_cast<hidpp::DeviceType>(data[3]);
event.pid = (data[5] << 8) | data[4];
event.address = address;
event.authentication = data[14];
event.seq_num = report.address();
event.name = "";
return false;
} else {
/* bad sequence, do not continue */
if (event.seq_num != report.address())
return false;
const int block_size = hidpp::LongParamLength - 3;
if (data[1] == 1) {
event.name.resize(data[2], ' ');
}
for(int i = 0; i < block_size; ++i) {
const size_t j = (data[1]-1)*block_size + i;
if (j < event.name.size()) {
event.name[j] = (char)data[i + 3];
} else {
return true;
}
}
return false;
}
}
PairStatusEvent Receiver::pairStatusEvent(const hidpp::Report& report) {
assert(report.subId() == PairStatus);
return {
.pairing = (bool)(report.paramBegin()[0] & 1),
.error = static_cast<PairingError>(report.paramBegin()[1])
};
}
BoltPairStatusEvent Receiver::boltPairStatusEvent(const hidpp::Report& report) {
assert(report.subId() == BoltPairStatus);
return {
.pairing = report.address() == 0,
.error = report.paramBegin()[1]
};
}
DiscoveryStatusEvent Receiver::discoveryStatusEvent(const hidpp::Report& report) {
assert(report.subId() == DiscoveryStatus);
return {
.discovering = report.address() == 0,
.error = report.paramBegin()[1]
};
}
std::string Receiver::passkeyEvent(const hidpp::Report& report) {
assert(report.subId() == PasskeyRequest);
return {report.paramBegin(), report.paramBegin() + 6};
}
bool Receiver::bolt() const {
return _is_bolt;
}
/*
* Copyright 2019-2023 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 <backend/hidpp10/Receiver.h>
#include <cassert>
using namespace logid::backend::hidpp10;
using namespace logid::backend;
const char* InvalidReceiver::what() const noexcept {
return "Not a receiver";
}
Receiver::Receiver(const std::string& path, const std::shared_ptr<raw::DeviceMonitor>& monitor,
double timeout) : Device(path, hidpp::DefaultDevice, monitor, timeout) {
}
void Receiver::_receiverCheck() {
// Check if the device is a receiver
try {
getNotificationFlags();
} catch (hidpp10::Error& e) {
if (e.code() == Error::InvalidAddress)
throw InvalidReceiver();
}
// TODO: is there a better way of checking if this is a bolt receiver?
_is_bolt = pid() == 0xc548;
}
Receiver::NotificationFlags Receiver::getNotificationFlags() {
auto response = getRegister(EnableHidppNotifications, {}, hidpp::ReportType::Short);
NotificationFlags flags{};
flags.deviceBatteryStatus = response[0] & (1 << 4);
flags.receiverWirelessNotifications = response[1] & (1 << 0);
flags.receiverSoftwarePresent = response[1] & (1 << 3);
return flags;
}
void Receiver::setNotifications(NotificationFlags flags) {
std::vector<uint8_t> request(3);
if (flags.deviceBatteryStatus)
request[0] |= (1 << 4);
if (flags.receiverWirelessNotifications)
request[1] |= 1;
if (flags.receiverSoftwarePresent)
request[1] |= (1 << 3);
setRegister(EnableHidppNotifications, request, hidpp::ReportType::Short);
}
void Receiver::enumerate() {
setRegisterNoResponse(ConnectionState, {2}, hidpp::ReportType::Short);
}
///TODO: Investigate usage
uint8_t Receiver::getConnectionState(hidpp::DeviceIndex index) {
auto response = getRegister(ConnectionState, {index}, hidpp::ReportType::Short);
return response[0];
}
void Receiver::startPairing(uint8_t timeout) {
std::vector<uint8_t> request(3);
if (_is_bolt)
throw std::invalid_argument("unifying pairing on bolt");
request[0] = 1;
request[1] = hidpp::DefaultDevice;
request[2] = timeout;
if (_is_bolt) {
setRegister(BoltDevicePairing, request, hidpp::ReportType::Long);
} else {
setRegister(DevicePairing, request, hidpp::ReportType::Short);
}
}
// bolt pairing request from solaar
void Receiver::startBoltPairing(const DeviceDiscoveryEvent& discovery) {
std::vector<uint8_t> request(10);
request[0] = 1; // start pair
request[1] = 0; // slot, from solaar. what does this mean?
for(int i = 0; i < 6; ++i)
request[2 + i] = (discovery.address >> (i*8)) & 0xff;
request[8] = discovery.authentication;
// TODO: what does entropy do?
request[9] = (discovery.deviceType == hidpp::DeviceKeyboard) ? 10 : 20;
setRegister(BoltDevicePairing, request, hidpp::ReportType::Long);
}
void Receiver::stopPairing() {
std::vector<uint8_t> request(3);
request[0] = 2;
request[1] = hidpp::DefaultDevice;
if (_is_bolt)
setRegister(BoltDevicePairing, request, hidpp::ReportType::Long);
else
setRegister(DevicePairing, request, hidpp::ReportType::Short);
}
void Receiver::startDiscover(uint8_t timeout) {
std::vector<uint8_t> request = {timeout, 1};
if (!_is_bolt)
throw std::invalid_argument("not a bolt receiver");
setRegister(BoltDeviceDiscovery, request, hidpp::ReportType::Short);
}
void Receiver::stopDiscover() {
std::vector<uint8_t> request = {0, 2};
if (!_is_bolt)
throw std::invalid_argument("not a bolt receiver");
setRegister(BoltDeviceDiscovery, request, hidpp::ReportType::Short);
}
void Receiver::disconnect(hidpp::DeviceIndex index) {
std::vector<uint8_t> request(2);
request[0] = _is_bolt ? 3 : 2;
request[1] = index;
if (_is_bolt)
setRegister(BoltDevicePairing, request, hidpp::ReportType::Long);
else
setRegister(DevicePairing, request, hidpp::ReportType::Short);
}
std::map<hidpp::DeviceIndex, uint8_t> Receiver::getDeviceActivity() {
auto response = getRegister(DeviceActivity, {}, hidpp::ReportType::Long);
std::map<hidpp::DeviceIndex, uint8_t> device_activity;
for (uint8_t i = hidpp::WirelessDevice1; i <= hidpp::WirelessDevice6; i++)
device_activity[static_cast<hidpp::DeviceIndex>(i)] = response[i];
return device_activity;
}
struct Receiver::PairingInfo
Receiver::getPairingInfo(hidpp::DeviceIndex index) {
std::vector<uint8_t> request(1);
request[0] = index;
if (_is_bolt)
request[0] += 0x50;
else
request[0] += 0x1f;
auto response = getRegister(PairingInfo, request, hidpp::ReportType::Long);
struct PairingInfo info{};
if (_is_bolt) {
info = {
.destinationId = 0, // no longer given?
.reportInterval = 0, // no longer given?
.pid = (uint16_t) ((response[3] << 8) | response[2]),
.deviceType = static_cast<hidpp::DeviceType>(response[1])
};
} else {
info = {
.destinationId = response[1],
.reportInterval = response[2],
.pid = (uint16_t) ((response[3] << 8) | response[4]),
.deviceType = static_cast<hidpp::DeviceType>(response[7])
};
}
return info;
}
struct Receiver::ExtendedPairingInfo
Receiver::getExtendedPairingInfo(hidpp::DeviceIndex index) {
const int device_num_offset = _is_bolt ? 0x50 : 0x2f;
const int serial_num_offset = _is_bolt ? 4 : 1;
const int report_offset = _is_bolt ? 8 : 5;
const int psl_offset = _is_bolt ? 12 : 8;
std::vector<uint8_t> request(1, index + device_num_offset);
auto response = getRegister(PairingInfo, request, hidpp::ReportType::Long);
ExtendedPairingInfo info{};
info.serialNumber = 0;
for (uint8_t i = 0; i < 4; i++)
info.serialNumber |= (response[i + serial_num_offset] << 8 * i);
for (uint8_t i = 0; i < 4; i++)
info.reportTypes[i] = response[i + report_offset];
uint8_t psl = response[psl_offset] & 0xf;
if (psl > 0xc)
info.powerSwitchLocation = PowerSwitchLocation::Reserved;
else
info.powerSwitchLocation = static_cast<PowerSwitchLocation>(psl);
return info;
}
std::string Receiver::getDeviceName(hidpp::DeviceIndex index) {
std::vector<uint8_t> request(2);
std::string name;
request[0] = index;
if (_is_bolt) {
/* Undocumented, deduced the following
* param 1 refers to part of string, 1-indexed
*
* response at 0x01 is [reg] [param 1] [size] [str...]
* response at 0x02-... is [next part of str...]
*/
request[0] += 0x60;
request[1] = 0x01;
auto resp = getRegister(PairingInfo, request, hidpp::ReportType::Long);
const uint8_t size = resp[2];
const uint8_t chunk_size = resp.size() - 3;
const uint8_t chunks = size / chunk_size + (size % chunk_size ? 1 : 0);
name.resize(size, ' ');
for (int i = 0; i < chunks; ++i) {
for (int j = 0; j < chunk_size; ++j) {
name[i * chunk_size + j] = (char) resp[j + 3];
}
if (i < chunks - 1) {
request[1] = i + 1;
resp = getRegister(PairingInfo, request, hidpp::ReportType::Long);
}
}
} else {
request[0] += 0x3f;
auto response = getRegister(PairingInfo, request, hidpp::ReportType::Long);
const uint8_t size = response[1];
name.resize(size, ' ');
for (std::size_t i = 0; i < size && i + 2 < response.size(); i++)
name[i] = (char) (response[i + 2]);
}
return name;
}
hidpp::DeviceIndex Receiver::deviceDisconnectionEvent(const hidpp::Report& report) {
assert(report.subId() == DeviceDisconnection);
return report.deviceIndex();
}
hidpp::DeviceConnectionEvent Receiver::deviceConnectionEvent(const hidpp::Report& report) {
assert(report.subId() == DeviceConnection);
auto data = report.paramBegin();
return {
.index = report.deviceIndex(),
.pid = (uint16_t) ((data[2] << 8) | data[1]),
.deviceType = static_cast<hidpp::DeviceType>(data[0] & 0x0f),
.unifying = ((report.address() & 0b111) == 0x04),
.softwarePresent = bool(data[0] & (1 << 4)),
.encrypted = (bool) (data[0] & (1 << 5)),
.linkEstablished = !(data[0] & (1 << 6)),
.withPayload = (bool) (data[0] & (1 << 7)),
.fromTimeoutCheck = false,
};
}
bool Receiver::fillDeviceDiscoveryEvent(DeviceDiscoveryEvent& event,
const hidpp::Report& report) {
assert(report.subId() == DeviceDiscovered);
auto data = report.paramBegin();
if (data[1] == 0) {
// device discovery event
uint64_t address = 0 ;
for (int i = 0; i < 6; ++i)
address |= ((uint64_t)(data[6 + i]) << (8*i));
event.deviceType = static_cast<hidpp::DeviceType>(data[3]);
event.pid = (data[5] << 8) | data[4];
event.address = address;
event.authentication = data[14];
event.seq_num = report.address();
event.name = "";
return false;
} else {
/* bad sequence, do not continue */
if (event.seq_num != report.address())
return false;
const int block_size = hidpp::LongParamLength - 3;
if (data[1] == 1) {
event.name.resize(data[2], ' ');
}
for(int i = 0; i < block_size; ++i) {
const size_t j = (data[1]-1)*block_size + i;
if (j < event.name.size()) {
event.name[j] = (char)data[i + 3];
} else {
return true;
}
}
return false;
}
}
PairStatusEvent Receiver::pairStatusEvent(const hidpp::Report& report) {
assert(report.subId() == PairStatus);
return {
.pairing = (bool)(report.paramBegin()[0] & 1),
.error = static_cast<PairingError>(report.paramBegin()[1])
};
}
BoltPairStatusEvent Receiver::boltPairStatusEvent(const hidpp::Report& report) {
assert(report.subId() == BoltPairStatus);
return {
.pairing = report.address() == 0,
.error = report.paramBegin()[1]
};
}
DiscoveryStatusEvent Receiver::discoveryStatusEvent(const hidpp::Report& report) {
assert(report.subId() == DiscoveryStatus);
return {
.discovering = report.address() == 0,
.error = report.paramBegin()[1]
};
}
std::string Receiver::passkeyEvent(const hidpp::Report& report) {
assert(report.subId() == PasskeyRequest);
return {report.paramBegin(), report.paramBegin() + 6};
}
bool Receiver::bolt() const {
return _is_bolt;
}

View File

@ -1,221 +1,221 @@
/*
* Copyright 2019-2023 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_BACKEND_DJ_RECEIVER_H
#define LOGID_BACKEND_DJ_RECEIVER_H
#include <cstdint>
#include <backend/hidpp10/Device.h>
namespace logid::backend::hidpp {
enum DeviceType : uint8_t {
DeviceUnknown = 0x00,
DeviceKeyboard = 0x01,
DeviceMouse = 0x02,
DeviceNumpad = 0x03,
DevicePresenter = 0x04,
/* 0x05-0x07 is reserved */
DeviceTrackball = 0x08,
DeviceTouchpad = 0x09
};
struct DeviceConnectionEvent {
DeviceIndex index{};
uint16_t pid{};
DeviceType deviceType = DeviceUnknown;
bool unifying{};
bool softwarePresent{};
bool encrypted{};
bool linkEstablished{};
bool withPayload{};
bool fromTimeoutCheck = false; // Fake field
};
}
namespace logid::backend::hidpp10 {
struct DeviceDiscoveryEvent {
hidpp::DeviceType deviceType = hidpp::DeviceUnknown;
uint8_t seq_num{};
uint64_t address{};
uint16_t pid{};
uint8_t authentication{};
std::string name;
};
enum PairingError : uint8_t {
NoPairingError = 0x00,
Timeout = 0x01,
UnsupportedDevice = 0x02,
TooManyDevices = 0x03,
/* Errors 0x04-0x05 are reserved */
ConnectionSeqTimeout = 0x06,
};
struct PairStatusEvent {
bool pairing{};
PairingError error = NoPairingError;
};
struct BoltPairStatusEvent {
bool pairing{};
uint8_t error;
};
struct DiscoveryStatusEvent {
bool discovering{};
uint8_t error{}; // don't know the error codes
};
class InvalidReceiver : public std::exception {
public:
[[nodiscard]] const char* what() const noexcept override;
};
class Receiver : public Device {
public:
/* The following functions deal with HID++ 1.0 features.
* While these are not technically DJ functions, it is redundant
* to have a separate hidpp10::Receiver class for these functions.
*/
enum Events : uint8_t {
// These events are identical to their DJ counterparts
DeviceDisconnection = 0x40,
DeviceConnection = 0x41,
PairStatus = 0x4a,
PasskeyRequest = 0x4d,
DeviceDiscovered = 0x4f,
DiscoveryStatus = 0x53,
BoltPairStatus = 0x54,
};
enum Registers : uint8_t {
EnableHidppNotifications = 0x00,
ConnectionState = 0x02,
DevicePairing = 0xb2,
DeviceActivity = 0xb3,
PairingInfo = 0xb5,
BoltDeviceDiscovery = 0xc0,
BoltDevicePairing = 0xc1,
};
struct NotificationFlags {
bool deviceBatteryStatus;
bool receiverWirelessNotifications;
bool receiverSoftwarePresent;
};
NotificationFlags getNotificationFlags();
void setNotifications(NotificationFlags flags);
void enumerate();
uint8_t getConnectionState(hidpp::DeviceIndex index);
void startPairing(uint8_t timeout = 0);
void startBoltPairing(const DeviceDiscoveryEvent& discovery);
void stopPairing();
void startDiscover(uint8_t timeout = 0);
void stopDiscover();
void disconnect(hidpp::DeviceIndex index);
std::map<hidpp::DeviceIndex, uint8_t> getDeviceActivity();
[[nodiscard]] bool bolt() const;
struct PairingInfo {
uint8_t destinationId;
uint8_t reportInterval;
uint16_t pid;
hidpp::DeviceType deviceType;
};
enum class PowerSwitchLocation : uint8_t {
Reserved = 0x0,
Base = 0x1,
TopCase = 0x2,
TopRightEdge = 0x3,
Other = 0x4,
TopLeft = 0x5,
BottomLeft = 0x6,
TopRight = 0x7,
BottomRight = 0x8,
TopEdge = 0x9,
RightEdge = 0xa,
LeftEdge = 0xb,
BottomEdge = 0xc
};
struct ExtendedPairingInfo {
uint32_t serialNumber;
uint8_t reportTypes[4];
PowerSwitchLocation powerSwitchLocation;
};
struct PairingInfo getPairingInfo(hidpp::DeviceIndex index);
struct ExtendedPairingInfo getExtendedPairingInfo(hidpp::DeviceIndex index);
std::string getDeviceName(hidpp::DeviceIndex index);
static hidpp::DeviceIndex deviceDisconnectionEvent(const hidpp::Report& report);
static hidpp::DeviceConnectionEvent deviceConnectionEvent(const hidpp::Report& report);
static PairStatusEvent pairStatusEvent(const hidpp::Report& report);
static BoltPairStatusEvent boltPairStatusEvent(const hidpp::Report& report);
static DiscoveryStatusEvent discoveryStatusEvent(const hidpp::Report& report);
static bool fillDeviceDiscoveryEvent(DeviceDiscoveryEvent& event,
const hidpp::Report& report);
static std::string passkeyEvent(const hidpp::Report& report);
protected:
Receiver(const std::string& path,
const std::shared_ptr<raw::DeviceMonitor>& monitor,
double timeout);
private:
void _receiverCheck();
bool _is_bolt = false;
public:
template <typename... Args>
static std::shared_ptr<Receiver> make(Args... args) {
auto receiver = makeDerived<Receiver>(std::forward<Args>(args)...);
receiver->_receiverCheck();
return receiver;
}
};
}
/*
* Copyright 2019-2023 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_BACKEND_DJ_RECEIVER_H
#define LOGID_BACKEND_DJ_RECEIVER_H
#include <cstdint>
#include <backend/hidpp10/Device.h>
namespace logid::backend::hidpp {
enum DeviceType : uint8_t {
DeviceUnknown = 0x00,
DeviceKeyboard = 0x01,
DeviceMouse = 0x02,
DeviceNumpad = 0x03,
DevicePresenter = 0x04,
/* 0x05-0x07 is reserved */
DeviceTrackball = 0x08,
DeviceTouchpad = 0x09
};
struct DeviceConnectionEvent {
DeviceIndex index{};
uint16_t pid{};
DeviceType deviceType = DeviceUnknown;
bool unifying{};
bool softwarePresent{};
bool encrypted{};
bool linkEstablished{};
bool withPayload{};
bool fromTimeoutCheck = false; // Fake field
};
}
namespace logid::backend::hidpp10 {
struct DeviceDiscoveryEvent {
hidpp::DeviceType deviceType = hidpp::DeviceUnknown;
uint8_t seq_num{};
uint64_t address{};
uint16_t pid{};
uint8_t authentication{};
std::string name;
};
enum PairingError : uint8_t {
NoPairingError = 0x00,
Timeout = 0x01,
UnsupportedDevice = 0x02,
TooManyDevices = 0x03,
/* Errors 0x04-0x05 are reserved */
ConnectionSeqTimeout = 0x06,
};
struct PairStatusEvent {
bool pairing{};
PairingError error = NoPairingError;
};
struct BoltPairStatusEvent {
bool pairing{};
uint8_t error;
};
struct DiscoveryStatusEvent {
bool discovering{};
uint8_t error{}; // don't know the error codes
};
class InvalidReceiver : public std::exception {
public:
[[nodiscard]] const char* what() const noexcept override;
};
class Receiver : public Device {
public:
/* The following functions deal with HID++ 1.0 features.
* While these are not technically DJ functions, it is redundant
* to have a separate hidpp10::Receiver class for these functions.
*/
enum Events : uint8_t {
// These events are identical to their DJ counterparts
DeviceDisconnection = 0x40,
DeviceConnection = 0x41,
PairStatus = 0x4a,
PasskeyRequest = 0x4d,
DeviceDiscovered = 0x4f,
DiscoveryStatus = 0x53,
BoltPairStatus = 0x54,
};
enum Registers : uint8_t {
EnableHidppNotifications = 0x00,
ConnectionState = 0x02,
DevicePairing = 0xb2,
DeviceActivity = 0xb3,
PairingInfo = 0xb5,
BoltDeviceDiscovery = 0xc0,
BoltDevicePairing = 0xc1,
};
struct NotificationFlags {
bool deviceBatteryStatus;
bool receiverWirelessNotifications;
bool receiverSoftwarePresent;
};
NotificationFlags getNotificationFlags();
void setNotifications(NotificationFlags flags);
void enumerate();
uint8_t getConnectionState(hidpp::DeviceIndex index);
void startPairing(uint8_t timeout = 0);
void startBoltPairing(const DeviceDiscoveryEvent& discovery);
void stopPairing();
void startDiscover(uint8_t timeout = 0);
void stopDiscover();
void disconnect(hidpp::DeviceIndex index);
std::map<hidpp::DeviceIndex, uint8_t> getDeviceActivity();
[[nodiscard]] bool bolt() const;
struct PairingInfo {
uint8_t destinationId;
uint8_t reportInterval;
uint16_t pid;
hidpp::DeviceType deviceType;
};
enum class PowerSwitchLocation : uint8_t {
Reserved = 0x0,
Base = 0x1,
TopCase = 0x2,
TopRightEdge = 0x3,
Other = 0x4,
TopLeft = 0x5,
BottomLeft = 0x6,
TopRight = 0x7,
BottomRight = 0x8,
TopEdge = 0x9,
RightEdge = 0xa,
LeftEdge = 0xb,
BottomEdge = 0xc
};
struct ExtendedPairingInfo {
uint32_t serialNumber;
uint8_t reportTypes[4];
PowerSwitchLocation powerSwitchLocation;
};
struct PairingInfo getPairingInfo(hidpp::DeviceIndex index);
struct ExtendedPairingInfo getExtendedPairingInfo(hidpp::DeviceIndex index);
std::string getDeviceName(hidpp::DeviceIndex index);
static hidpp::DeviceIndex deviceDisconnectionEvent(const hidpp::Report& report);
static hidpp::DeviceConnectionEvent deviceConnectionEvent(const hidpp::Report& report);
static PairStatusEvent pairStatusEvent(const hidpp::Report& report);
static BoltPairStatusEvent boltPairStatusEvent(const hidpp::Report& report);
static DiscoveryStatusEvent discoveryStatusEvent(const hidpp::Report& report);
static bool fillDeviceDiscoveryEvent(DeviceDiscoveryEvent& event,
const hidpp::Report& report);
static std::string passkeyEvent(const hidpp::Report& report);
protected:
Receiver(const std::string& path,
const std::shared_ptr<raw::DeviceMonitor>& monitor,
double timeout);
private:
void _receiverCheck();
bool _is_bolt = false;
public:
template <typename... Args>
static std::shared_ptr<Receiver> make(Args... args) {
auto receiver = makeDerived<Receiver>(std::forward<Args>(args)...);
receiver->_receiverCheck();
return receiver;
}
};
}
#endif //LOGID_BACKEND_DJ_RECEIVER_H

View File

@ -1,252 +1,252 @@
/*
* Copyright 2019-2023 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 <backend/hidpp10/ReceiverMonitor.h>
#include <backend/Error.h>
#include <util/task.h>
#include <util/log.h>
using namespace logid::backend::hidpp10;
using namespace logid::backend::hidpp;
ReceiverMonitor::ReceiverMonitor(const std::string& path,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout)
: _receiver(Receiver::make(path, monitor, timeout)) {
Receiver::NotificationFlags notification_flags{true, true, true};
_receiver->setNotifications(notification_flags);
}
void ReceiverMonitor::_ready() {
if (_connect_ev_handler.empty()) {
_connect_ev_handler = _receiver->rawDevice()->addEventHandler(
{[](const std::vector<uint8_t>& report) -> bool {
if (report[Offset::Type] == Report::Type::Short ||
report[Offset::Type] == Report::Type::Long) {
uint8_t sub_id = report[Offset::SubID];
return (sub_id == Receiver::DeviceConnection ||
sub_id == Receiver::DeviceDisconnection);
}
return false;
}, [self_weak = _self](const std::vector<uint8_t>& raw) -> void {
/* Running in a new thread prevents deadlocks since the
* receiver may be enumerating.
*/
hidpp::Report report(raw);
if (auto self = self_weak.lock()) {
run_task([self_weak, report]() {
auto self = self_weak.lock();
if (!self)
return;
if (report.subId() == Receiver::DeviceConnection) {
self->_addHandler(Receiver::deviceConnectionEvent(report));
} else if (report.subId() == Receiver::DeviceDisconnection) {
self->_removeHandler(Receiver::deviceDisconnectionEvent(report));
}
});
}
}
});
}
if (_discover_ev_handler.empty()) {
_discover_ev_handler = _receiver->addEventHandler(
{[](const hidpp::Report& report) -> bool {
return (report.subId() == Receiver::DeviceDiscovered) &&
(report.type() == Report::Type::Long);
},
[self_weak = _self](const hidpp::Report& report) {
auto self = self_weak.lock();
if (!self)
return;
std::lock_guard lock(self->_pair_mutex);
if (self->_pair_state == Discovering) {
bool filled = Receiver::fillDeviceDiscoveryEvent(
self->_discovery_event, report);
if (filled) {
self->_pair_state = FindingPasskey;
run_task([self_weak, event = self->_discovery_event]() {
if (auto self = self_weak.lock())
self->receiver()->startBoltPairing(event);
});
}
}
}
});
}
if (_passkey_ev_handler.empty()) {
_passkey_ev_handler = _receiver->addEventHandler(
{[](const hidpp::Report& report) -> bool {
return report.subId() == Receiver::PasskeyRequest &&
report.type() == hidpp::Report::Type::Long;
},
[self_weak = _self](const hidpp::Report& report) {
if (auto self = self_weak.lock()) {
std::lock_guard lock(self->_pair_mutex);
if (self->_pair_state == FindingPasskey) {
auto passkey = Receiver::passkeyEvent(report);
self->_pair_state = Pairing;
self->pairReady(self->_discovery_event, passkey);
}
}
}
});
}
if (_pair_status_handler.empty()) {
_pair_status_handler = _receiver->addEventHandler(
{[](const hidpp::Report& report) -> bool {
return report.subId() == Receiver::DiscoveryStatus ||
report.subId() == Receiver::PairStatus ||
report.subId() == Receiver::BoltPairStatus;
},
[self_weak = _self](const hidpp::Report& report) {
auto self = self_weak.lock();
if (!self)
return;
std::lock_guard lock(self->_pair_mutex);
// TODO: forward status to user
if (report.subId() == Receiver::DiscoveryStatus) {
auto event = Receiver::discoveryStatusEvent(report);
if (self->_pair_state == Discovering && !event.discovering)
self->_pair_state = NotPairing;
} else if (report.subId() == Receiver::PairStatus) {
auto event = Receiver::pairStatusEvent(report);
if ((self->_pair_state == FindingPasskey ||
self->_pair_state == Pairing) && !event.pairing)
self->_pair_state = NotPairing;
} else if (report.subId() == Receiver::BoltPairStatus) {
auto event = Receiver::boltPairStatusEvent(report);
if ((self->_pair_state == FindingPasskey ||
self->_pair_state == Pairing) && !event.pairing)
self->_pair_state = NotPairing;
}
}
});
}
enumerate();
}
void ReceiverMonitor::enumerate() {
_receiver->enumerate();
}
void ReceiverMonitor::waitForDevice(hidpp::DeviceIndex index) {
const std::lock_guard lock(_wait_mutex);
if (!_waiters.count(index)) {
_waiters.emplace(index, _receiver->rawDevice()->addEventHandler(
{[index](const std::vector<uint8_t>& report) -> bool {
/* Connection events should be handled by connect_ev_handler */
auto sub_id = report[Offset::SubID];
return report[Offset::DeviceIndex] == index &&
sub_id != Receiver::DeviceConnection &&
sub_id != Receiver::DeviceDisconnection;
},
[self_weak = _self, index](
[[maybe_unused]] const std::vector<uint8_t>& report) {
hidpp::DeviceConnectionEvent event{};
event.withPayload = false;
event.linkEstablished = true;
event.index = index;
event.fromTimeoutCheck = true;
run_task([self_weak, event]() {
if (auto self = self_weak.lock())
self->_addHandler(event);
});
}
}));
}
}
std::shared_ptr<Receiver> ReceiverMonitor::receiver() const {
return _receiver;
}
void ReceiverMonitor::_startPair(uint8_t timeout) {
{
std::lock_guard lock(_pair_mutex);
_pair_state = _receiver->bolt() ? Discovering : Pairing;
_discovery_event = {};
}
if (_receiver->bolt())
receiver()->startDiscover(timeout);
else
receiver()->startPairing(timeout);
}
void ReceiverMonitor::_stopPair() {
PairState last_state;
{
std::lock_guard lock(_pair_mutex);
last_state = _pair_state;
_pair_state = NotPairing;
}
if (last_state == Discovering)
receiver()->stopDiscover();
else if (last_state == Pairing || last_state == FindingPasskey)
receiver()->stopPairing();
}
void ReceiverMonitor::_addHandler(const hidpp::DeviceConnectionEvent& event, int tries) {
auto device_path = _receiver->devicePath();
try {
addDevice(event);
const std::lock_guard lock(_wait_mutex);
_waiters.erase(event.index);
} catch (DeviceNotReady& e) {
if (tries == max_tries) {
logPrintf(WARN, "Failed to add device %s:%d after %d tries."
"Treating as failure.", device_path.c_str(), event.index, max_tries);
} else {
/* Do exponential backoff for 2^tries * backoff ms. */
std::chrono::milliseconds wait((1 << tries) * ready_backoff);
logPrintf(DEBUG, "Failed to add device %s:%d on try %d, backing off for %dms",
device_path.c_str(), event.index, tries + 1, wait.count());
run_task_after([self_weak = _self, event, tries]() {
if (auto self = self_weak.lock())
self->_addHandler(event, tries + 1);
}, wait);
}
} catch (std::exception& e) {
logPrintf(ERROR, "Failed to add device %d to receiver on %s: %s",
event.index, device_path.c_str(), e.what());
}
}
void ReceiverMonitor::_removeHandler(hidpp::DeviceIndex index) {
try {
removeDevice(index);
} catch (std::exception& e) {
logPrintf(ERROR, "Failed to remove device %d from receiver on %s: %s",
index, _receiver->devicePath().c_str(), e.what());
}
}
/*
* Copyright 2019-2023 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 <backend/hidpp10/ReceiverMonitor.h>
#include <backend/Error.h>
#include <util/task.h>
#include <util/log.h>
using namespace logid::backend::hidpp10;
using namespace logid::backend::hidpp;
ReceiverMonitor::ReceiverMonitor(const std::string& path,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout)
: _receiver(Receiver::make(path, monitor, timeout)) {
Receiver::NotificationFlags notification_flags{true, true, true};
_receiver->setNotifications(notification_flags);
}
void ReceiverMonitor::_ready() {
if (_connect_ev_handler.empty()) {
_connect_ev_handler = _receiver->rawDevice()->addEventHandler(
{[](const std::vector<uint8_t>& report) -> bool {
if (report[Offset::Type] == Report::Type::Short ||
report[Offset::Type] == Report::Type::Long) {
uint8_t sub_id = report[Offset::SubID];
return (sub_id == Receiver::DeviceConnection ||
sub_id == Receiver::DeviceDisconnection);
}
return false;
}, [self_weak = _self](const std::vector<uint8_t>& raw) -> void {
/* Running in a new thread prevents deadlocks since the
* receiver may be enumerating.
*/
hidpp::Report report(raw);
if (auto self = self_weak.lock()) {
run_task([self_weak, report]() {
auto self = self_weak.lock();
if (!self)
return;
if (report.subId() == Receiver::DeviceConnection) {
self->_addHandler(Receiver::deviceConnectionEvent(report));
} else if (report.subId() == Receiver::DeviceDisconnection) {
self->_removeHandler(Receiver::deviceDisconnectionEvent(report));
}
});
}
}
});
}
if (_discover_ev_handler.empty()) {
_discover_ev_handler = _receiver->addEventHandler(
{[](const hidpp::Report& report) -> bool {
return (report.subId() == Receiver::DeviceDiscovered) &&
(report.type() == Report::Type::Long);
},
[self_weak = _self](const hidpp::Report& report) {
auto self = self_weak.lock();
if (!self)
return;
std::lock_guard lock(self->_pair_mutex);
if (self->_pair_state == Discovering) {
bool filled = Receiver::fillDeviceDiscoveryEvent(
self->_discovery_event, report);
if (filled) {
self->_pair_state = FindingPasskey;
run_task([self_weak, event = self->_discovery_event]() {
if (auto self = self_weak.lock())
self->receiver()->startBoltPairing(event);
});
}
}
}
});
}
if (_passkey_ev_handler.empty()) {
_passkey_ev_handler = _receiver->addEventHandler(
{[](const hidpp::Report& report) -> bool {
return report.subId() == Receiver::PasskeyRequest &&
report.type() == hidpp::Report::Type::Long;
},
[self_weak = _self](const hidpp::Report& report) {
if (auto self = self_weak.lock()) {
std::lock_guard lock(self->_pair_mutex);
if (self->_pair_state == FindingPasskey) {
auto passkey = Receiver::passkeyEvent(report);
self->_pair_state = Pairing;
self->pairReady(self->_discovery_event, passkey);
}
}
}
});
}
if (_pair_status_handler.empty()) {
_pair_status_handler = _receiver->addEventHandler(
{[](const hidpp::Report& report) -> bool {
return report.subId() == Receiver::DiscoveryStatus ||
report.subId() == Receiver::PairStatus ||
report.subId() == Receiver::BoltPairStatus;
},
[self_weak = _self](const hidpp::Report& report) {
auto self = self_weak.lock();
if (!self)
return;
std::lock_guard lock(self->_pair_mutex);
// TODO: forward status to user
if (report.subId() == Receiver::DiscoveryStatus) {
auto event = Receiver::discoveryStatusEvent(report);
if (self->_pair_state == Discovering && !event.discovering)
self->_pair_state = NotPairing;
} else if (report.subId() == Receiver::PairStatus) {
auto event = Receiver::pairStatusEvent(report);
if ((self->_pair_state == FindingPasskey ||
self->_pair_state == Pairing) && !event.pairing)
self->_pair_state = NotPairing;
} else if (report.subId() == Receiver::BoltPairStatus) {
auto event = Receiver::boltPairStatusEvent(report);
if ((self->_pair_state == FindingPasskey ||
self->_pair_state == Pairing) && !event.pairing)
self->_pair_state = NotPairing;
}
}
});
}
enumerate();
}
void ReceiverMonitor::enumerate() {
_receiver->enumerate();
}
void ReceiverMonitor::waitForDevice(hidpp::DeviceIndex index) {
const std::lock_guard lock(_wait_mutex);
if (!_waiters.count(index)) {
_waiters.emplace(index, _receiver->rawDevice()->addEventHandler(
{[index](const std::vector<uint8_t>& report) -> bool {
/* Connection events should be handled by connect_ev_handler */
auto sub_id = report[Offset::SubID];
return report[Offset::DeviceIndex] == index &&
sub_id != Receiver::DeviceConnection &&
sub_id != Receiver::DeviceDisconnection;
},
[self_weak = _self, index](
[[maybe_unused]] const std::vector<uint8_t>& report) {
hidpp::DeviceConnectionEvent event{};
event.withPayload = false;
event.linkEstablished = true;
event.index = index;
event.fromTimeoutCheck = true;
run_task([self_weak, event]() {
if (auto self = self_weak.lock())
self->_addHandler(event);
});
}
}));
}
}
std::shared_ptr<Receiver> ReceiverMonitor::receiver() const {
return _receiver;
}
void ReceiverMonitor::_startPair(uint8_t timeout) {
{
std::lock_guard lock(_pair_mutex);
_pair_state = _receiver->bolt() ? Discovering : Pairing;
_discovery_event = {};
}
if (_receiver->bolt())
receiver()->startDiscover(timeout);
else
receiver()->startPairing(timeout);
}
void ReceiverMonitor::_stopPair() {
PairState last_state;
{
std::lock_guard lock(_pair_mutex);
last_state = _pair_state;
_pair_state = NotPairing;
}
if (last_state == Discovering)
receiver()->stopDiscover();
else if (last_state == Pairing || last_state == FindingPasskey)
receiver()->stopPairing();
}
void ReceiverMonitor::_addHandler(const hidpp::DeviceConnectionEvent& event, int tries) {
auto device_path = _receiver->devicePath();
try {
addDevice(event);
const std::lock_guard lock(_wait_mutex);
_waiters.erase(event.index);
} catch (DeviceNotReady& e) {
if (tries == max_tries) {
logPrintf(WARN, "Failed to add device %s:%d after %d tries."
"Treating as failure.", device_path.c_str(), event.index, max_tries);
} else {
/* Do exponential backoff for 2^tries * backoff ms. */
std::chrono::milliseconds wait((1 << tries) * ready_backoff);
logPrintf(DEBUG, "Failed to add device %s:%d on try %d, backing off for %dms",
device_path.c_str(), event.index, tries + 1, wait.count());
run_task_after([self_weak = _self, event, tries]() {
if (auto self = self_weak.lock())
self->_addHandler(event, tries + 1);
}, wait);
}
} catch (std::exception& e) {
logPrintf(ERROR, "Failed to add device %d to receiver on %s: %s",
event.index, device_path.c_str(), e.what());
}
}
void ReceiverMonitor::_removeHandler(hidpp::DeviceIndex index) {
try {
removeDevice(index);
} catch (std::exception& e) {
logPrintf(ERROR, "Failed to remove device %d from receiver on %s: %s",
index, _receiver->devicePath().c_str(), e.what());
}
}

View File

@ -1,119 +1,119 @@
/*
* Copyright 2019-2023 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_BACKEND_DJ_RECEIVERMONITOR_H
#define LOGID_BACKEND_DJ_RECEIVERMONITOR_H
#include <backend/hidpp10/Receiver.h>
#include <backend/hidpp/defs.h>
#include <cstdint>
#include <string>
namespace logid::backend::hidpp10 {
template<typename T>
class _receiverMonitorWrapper : public T {
friend class ReceiverMonitor;
public:
template<typename... Args>
explicit _receiverMonitorWrapper(Args... args) : T(std::forward<Args>(args)...) {}
template<typename... Args>
static std::shared_ptr<T> make(Args... args) {
return std::make_shared<_receiverMonitorWrapper>(std::forward<Args>(args)...);
}
};
static constexpr int max_tries = 5;
static constexpr int ready_backoff = 250;
// This class will run on the RawDevice thread,
class ReceiverMonitor {
public:
void enumerate();
ReceiverMonitor(const ReceiverMonitor&) = delete;
ReceiverMonitor(ReceiverMonitor&&) = delete;
protected:
ReceiverMonitor(const std::string& path,
const std::shared_ptr<raw::DeviceMonitor>& monitor,
double timeout);
virtual void addDevice(hidpp::DeviceConnectionEvent event) = 0;
virtual void removeDevice(hidpp::DeviceIndex index) = 0;
virtual void pairReady(const hidpp10::DeviceDiscoveryEvent& event,
const std::string& passkey) = 0;
void _startPair(uint8_t timeout = 0);
void _stopPair();
void waitForDevice(hidpp::DeviceIndex index);
[[nodiscard]] std::shared_ptr<Receiver> receiver() const;
private:
void _ready();
void _addHandler(const hidpp::DeviceConnectionEvent& event, int tries = 0);
void _removeHandler(hidpp::DeviceIndex index);
std::shared_ptr<Receiver> _receiver;
enum PairState {
NotPairing,
Discovering,
FindingPasskey,
Pairing,
};
std::mutex _pair_mutex;
DeviceDiscoveryEvent _discovery_event;
PairState _pair_state = NotPairing;
EventHandlerLock<raw::RawDevice> _connect_ev_handler;
EventHandlerLock<hidpp::Device> _discover_ev_handler;
EventHandlerLock<hidpp::Device> _passkey_ev_handler;
EventHandlerLock<hidpp::Device> _pair_status_handler;
std::weak_ptr<ReceiverMonitor> _self;
std::mutex _wait_mutex;
std::map<hidpp::DeviceIndex, EventHandlerLock<raw::RawDevice>> _waiters;
public:
template<typename T, typename... Args>
static std::shared_ptr<T> make(Args... args) {
auto receiver_monitor = _receiverMonitorWrapper<T>::make(std::forward<Args>(args)...);
receiver_monitor->_self = receiver_monitor;
receiver_monitor->_ready();
return receiver_monitor;
}
};
}
/*
* Copyright 2019-2023 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_BACKEND_DJ_RECEIVERMONITOR_H
#define LOGID_BACKEND_DJ_RECEIVERMONITOR_H
#include <backend/hidpp10/Receiver.h>
#include <backend/hidpp/defs.h>
#include <cstdint>
#include <string>
namespace logid::backend::hidpp10 {
template<typename T>
class _receiverMonitorWrapper : public T {
friend class ReceiverMonitor;
public:
template<typename... Args>
explicit _receiverMonitorWrapper(Args... args) : T(std::forward<Args>(args)...) {}
template<typename... Args>
static std::shared_ptr<T> make(Args... args) {
return std::make_shared<_receiverMonitorWrapper>(std::forward<Args>(args)...);
}
};
static constexpr int max_tries = 5;
static constexpr int ready_backoff = 250;
// This class will run on the RawDevice thread,
class ReceiverMonitor {
public:
void enumerate();
ReceiverMonitor(const ReceiverMonitor&) = delete;
ReceiverMonitor(ReceiverMonitor&&) = delete;
protected:
ReceiverMonitor(const std::string& path,
const std::shared_ptr<raw::DeviceMonitor>& monitor,
double timeout);
virtual void addDevice(hidpp::DeviceConnectionEvent event) = 0;
virtual void removeDevice(hidpp::DeviceIndex index) = 0;
virtual void pairReady(const hidpp10::DeviceDiscoveryEvent& event,
const std::string& passkey) = 0;
void _startPair(uint8_t timeout = 0);
void _stopPair();
void waitForDevice(hidpp::DeviceIndex index);
[[nodiscard]] std::shared_ptr<Receiver> receiver() const;
private:
void _ready();
void _addHandler(const hidpp::DeviceConnectionEvent& event, int tries = 0);
void _removeHandler(hidpp::DeviceIndex index);
std::shared_ptr<Receiver> _receiver;
enum PairState {
NotPairing,
Discovering,
FindingPasskey,
Pairing,
};
std::mutex _pair_mutex;
DeviceDiscoveryEvent _discovery_event;
PairState _pair_state = NotPairing;
EventHandlerLock<raw::RawDevice> _connect_ev_handler;
EventHandlerLock<hidpp::Device> _discover_ev_handler;
EventHandlerLock<hidpp::Device> _passkey_ev_handler;
EventHandlerLock<hidpp::Device> _pair_status_handler;
std::weak_ptr<ReceiverMonitor> _self;
std::mutex _wait_mutex;
std::map<hidpp::DeviceIndex, EventHandlerLock<raw::RawDevice>> _waiters;
public:
template<typename T, typename... Args>
static std::shared_ptr<T> make(Args... args) {
auto receiver_monitor = _receiverMonitorWrapper<T>::make(std::forward<Args>(args)...);
receiver_monitor->_self = receiver_monitor;
receiver_monitor->_ready();
return receiver_monitor;
}
};
}
#endif //LOGID_BACKEND_DJ_RECEIVERMONITOR_H

View File

@ -1,33 +1,33 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP10_DEFS_H
#define LOGID_BACKEND_HIDPP10_DEFS_H
namespace logid::backend::hidpp10 {
enum SubID : uint8_t {
SetRegisterShort = 0x80,
GetRegisterShort = 0x81,
SetRegisterLong = 0x82,
GetRegisterLong = 0x83,
};
static constexpr size_t SubIDCount = 4;
}
/*
* Copyright 2019-2023 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_BACKEND_HIDPP10_DEFS_H
#define LOGID_BACKEND_HIDPP10_DEFS_H
namespace logid::backend::hidpp10 {
enum SubID : uint8_t {
SetRegisterShort = 0x80,
GetRegisterShort = 0x81,
SetRegisterLong = 0x82,
GetRegisterLong = 0x83,
};
static constexpr size_t SubIDCount = 4;
}
#endif //LOGID_BACKEND_HIDPP10_DEFS_H

View File

@ -1,162 +1,162 @@
/*
* Copyright 2019-2023 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 <cassert>
#include <backend/hidpp20/Device.h>
#include <backend/Error.h>
#include <backend/hidpp10/Receiver.h>
using namespace logid::backend;
using namespace logid::backend::hidpp20;
Device::Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) :
hidpp::Device(path, index, monitor, timeout) {
}
Device::Device(std::shared_ptr<raw::RawDevice> raw_device,
hidpp::DeviceIndex index, double timeout) :
hidpp::Device(std::move(raw_device), index, timeout) {
}
Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceConnectionEvent event, double timeout) :
hidpp::Device(receiver, event, timeout) {
}
Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceIndex index, double timeout)
: hidpp::Device(receiver, index, timeout) {
}
std::vector<uint8_t> Device::callFunction(uint8_t feature_index,
uint8_t function, std::vector<uint8_t>& params) {
hidpp::Report::Type type;
assert(params.size() <= hidpp::LongParamLength);
if (params.size() <= hidpp::ShortParamLength)
type = hidpp::Report::Type::Short;
else if (params.size() <= hidpp::LongParamLength)
type = hidpp::Report::Type::Long;
else
throw hidpp::Report::InvalidReportID();
hidpp::Report request(type, deviceIndex(), feature_index, function,
hidpp::softwareID);
std::copy(params.begin(), params.end(), request.paramBegin());
auto response = this->sendReport(request);
return {response.paramBegin(), response.paramEnd()};
}
void Device::callFunctionNoResponse(uint8_t feature_index, uint8_t function,
std::vector<uint8_t>& params) {
hidpp::Report::Type type;
assert(params.size() <= hidpp::LongParamLength);
if (params.size() <= hidpp::ShortParamLength)
type = hidpp::Report::Type::Short;
else if (params.size() <= hidpp::LongParamLength)
type = hidpp::Report::Type::Long;
else
throw hidpp::Report::InvalidReportID();
hidpp::Report request(type, deviceIndex(), feature_index, function, hidpp::softwareID);
std::copy(params.begin(), params.end(), request.paramBegin());
this->sendReportNoACK(request);
}
hidpp::Report Device::sendReport(const hidpp::Report& report) {
auto& response_slot = _responses[report.feature() % _responses.size()];
std::unique_lock<std::mutex> response_lock(_response_mutex);
_response_cv.wait(response_lock, [&response_slot]() {
return !response_slot.feature.has_value();
});
response_slot.feature = report.feature();
_sendReport(report);
bool valid = _response_cv.wait_for(
response_lock, io_timeout,
[&response_slot]() {
return response_slot.response.has_value();
});
if (!valid) {
response_slot.reset();
throw TimeoutError();
}
assert(response_slot.response.has_value());
auto response = response_slot.response.value();
response_slot.reset();
if (std::holds_alternative<hidpp::Report>(response)) {
return std::get<hidpp::Report>(response);
} else { // if(std::holds_alternative<Error::ErrorCode>(response))
auto error = std::get<hidpp::Report::Hidpp20Error>(response);
throw Error(error.error_code, error.device_index);
}
}
void Device::sendReportNoACK(const hidpp::Report& report) {
hidpp::Report no_ack_report(report);
no_ack_report.setSwId(hidpp::noAckSoftwareID);
_sendReport(std::move(no_ack_report));
}
bool Device::responseReport(const hidpp::Report& report) {
auto& response_slot = _responses[report.feature() % _responses.size()];
std::lock_guard<std::mutex> lock(_response_mutex);
uint8_t sw_id, feature;
bool is_error = false;
hidpp::Report::Hidpp20Error hidpp20_error{};
if (report.isError20(hidpp20_error)) {
is_error = true;
sw_id = hidpp20_error.software_id;
feature = hidpp20_error.feature_index;
} else {
sw_id = report.swId();
feature = report.feature();
}
if (sw_id != hidpp::softwareID)
return false;
if (!response_slot.feature || response_slot.feature.value() != feature) {
return false;
}
if (is_error) {
response_slot.response = hidpp20_error;
} else {
response_slot.response = report;
}
_response_cv.notify_all();
return true;
}
void Device::ResponseSlot::reset() {
response.reset();
feature.reset();
}
/*
* Copyright 2019-2023 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 <cassert>
#include <backend/hidpp20/Device.h>
#include <backend/Error.h>
#include <backend/hidpp10/Receiver.h>
using namespace logid::backend;
using namespace logid::backend::hidpp20;
Device::Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) :
hidpp::Device(path, index, monitor, timeout) {
}
Device::Device(std::shared_ptr<raw::RawDevice> raw_device,
hidpp::DeviceIndex index, double timeout) :
hidpp::Device(std::move(raw_device), index, timeout) {
}
Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceConnectionEvent event, double timeout) :
hidpp::Device(receiver, event, timeout) {
}
Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceIndex index, double timeout)
: hidpp::Device(receiver, index, timeout) {
}
std::vector<uint8_t> Device::callFunction(uint8_t feature_index,
uint8_t function, std::vector<uint8_t>& params) {
hidpp::Report::Type type;
assert(params.size() <= hidpp::LongParamLength);
if (params.size() <= hidpp::ShortParamLength)
type = hidpp::Report::Type::Short;
else if (params.size() <= hidpp::LongParamLength)
type = hidpp::Report::Type::Long;
else
throw hidpp::Report::InvalidReportID();
hidpp::Report request(type, deviceIndex(), feature_index, function,
hidpp::softwareID);
std::copy(params.begin(), params.end(), request.paramBegin());
auto response = this->sendReport(request);
return {response.paramBegin(), response.paramEnd()};
}
void Device::callFunctionNoResponse(uint8_t feature_index, uint8_t function,
std::vector<uint8_t>& params) {
hidpp::Report::Type type;
assert(params.size() <= hidpp::LongParamLength);
if (params.size() <= hidpp::ShortParamLength)
type = hidpp::Report::Type::Short;
else if (params.size() <= hidpp::LongParamLength)
type = hidpp::Report::Type::Long;
else
throw hidpp::Report::InvalidReportID();
hidpp::Report request(type, deviceIndex(), feature_index, function, hidpp::softwareID);
std::copy(params.begin(), params.end(), request.paramBegin());
this->sendReportNoACK(request);
}
hidpp::Report Device::sendReport(const hidpp::Report& report) {
auto& response_slot = _responses[report.feature() % _responses.size()];
std::unique_lock<std::mutex> response_lock(_response_mutex);
_response_cv.wait(response_lock, [&response_slot]() {
return !response_slot.feature.has_value();
});
response_slot.feature = report.feature();
_sendReport(report);
bool valid = _response_cv.wait_for(
response_lock, io_timeout,
[&response_slot]() {
return response_slot.response.has_value();
});
if (!valid) {
response_slot.reset();
throw TimeoutError();
}
assert(response_slot.response.has_value());
auto response = response_slot.response.value();
response_slot.reset();
if (std::holds_alternative<hidpp::Report>(response)) {
return std::get<hidpp::Report>(response);
} else { // if(std::holds_alternative<Error::ErrorCode>(response))
auto error = std::get<hidpp::Report::Hidpp20Error>(response);
throw Error(error.error_code, error.device_index);
}
}
void Device::sendReportNoACK(const hidpp::Report& report) {
hidpp::Report no_ack_report(report);
no_ack_report.setSwId(hidpp::noAckSoftwareID);
_sendReport(std::move(no_ack_report));
}
bool Device::responseReport(const hidpp::Report& report) {
auto& response_slot = _responses[report.feature() % _responses.size()];
std::lock_guard<std::mutex> lock(_response_mutex);
uint8_t sw_id, feature;
bool is_error = false;
hidpp::Report::Hidpp20Error hidpp20_error{};
if (report.isError20(hidpp20_error)) {
is_error = true;
sw_id = hidpp20_error.software_id;
feature = hidpp20_error.feature_index;
} else {
sw_id = report.swId();
feature = report.feature();
}
if (sw_id != hidpp::softwareID)
return false;
if (!response_slot.feature || response_slot.feature.value() != feature) {
return false;
}
if (is_error) {
response_slot.response = hidpp20_error;
} else {
response_slot.response = report;
}
_response_cv.notify_all();
return true;
}
void Device::ResponseSlot::reset() {
response.reset();
feature.reset();
}

View File

@ -1,82 +1,82 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_DEVICE_H
#define LOGID_BACKEND_HIDPP20_DEVICE_H
#include <cstdint>
#include <optional>
#include <variant>
#include <backend/hidpp20/Error.h>
#include <backend/hidpp/Device.h>
namespace logid::backend::hidpp20 {
class Device : public hidpp::Device {
public:
std::vector<uint8_t> callFunction(uint8_t feature_index,
uint8_t function,
std::vector<uint8_t>& params);
void callFunctionNoResponse(uint8_t feature_index,
uint8_t function,
std::vector<uint8_t>& params);
hidpp::Report sendReport(const hidpp::Report& report) final;
void sendReportNoACK(const hidpp::Report& report) final;
protected:
Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout);
Device(std::shared_ptr<raw::RawDevice> raw_device,
hidpp::DeviceIndex index, double timeout);
Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceConnectionEvent event, double timeout);
Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceIndex index, double timeout);
bool responseReport(const hidpp::Report& report) final;
private:
typedef std::variant<hidpp::Report, hidpp::Report::Hidpp20Error> Response;
struct ResponseSlot {
std::optional<Response> response;
std::optional<uint8_t> feature;
void reset();
};
/* Multiplex responses on lower nibble of SubID, ignore upper nibble for space */
std::array<ResponseSlot, 16> _responses;
public:
template <typename... Args>
static std::shared_ptr<Device> make(Args... args) {
auto device = makeDerived<Device>(std::forward<Args>(args)...);
if (std::get<0>(device->version()) < 2)
throw std::invalid_argument("not a hid++ 2.0 device");
return device;
}
};
}
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_DEVICE_H
#define LOGID_BACKEND_HIDPP20_DEVICE_H
#include <cstdint>
#include <optional>
#include <variant>
#include <backend/hidpp20/Error.h>
#include <backend/hidpp/Device.h>
namespace logid::backend::hidpp20 {
class Device : public hidpp::Device {
public:
std::vector<uint8_t> callFunction(uint8_t feature_index,
uint8_t function,
std::vector<uint8_t>& params);
void callFunctionNoResponse(uint8_t feature_index,
uint8_t function,
std::vector<uint8_t>& params);
hidpp::Report sendReport(const hidpp::Report& report) final;
void sendReportNoACK(const hidpp::Report& report) final;
protected:
Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout);
Device(std::shared_ptr<raw::RawDevice> raw_device,
hidpp::DeviceIndex index, double timeout);
Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceConnectionEvent event, double timeout);
Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceIndex index, double timeout);
bool responseReport(const hidpp::Report& report) final;
private:
typedef std::variant<hidpp::Report, hidpp::Report::Hidpp20Error> Response;
struct ResponseSlot {
std::optional<Response> response;
std::optional<uint8_t> feature;
void reset();
};
/* Multiplex responses on lower nibble of SubID, ignore upper nibble for space */
std::array<ResponseSlot, 16> _responses;
public:
template <typename... Args>
static std::shared_ptr<Device> make(Args... args) {
auto device = makeDerived<Device>(std::forward<Args>(args)...);
if (std::get<0>(device->version()) < 2)
throw std::invalid_argument("not a hid++ 2.0 device");
return device;
}
};
}
#endif //LOGID_BACKEND_HIDPP20_DEVICE_H

View File

@ -1,64 +1,64 @@
/*
* Copyright 2019-2023 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 <backend/hidpp20/Error.h>
#include <cassert>
using namespace logid::backend;
using namespace logid::backend::hidpp20;
Error::Error(uint8_t code, hidpp::DeviceIndex index) : _code(code), _index (index) {
assert(_code != NoError);
}
const char* Error::what() const noexcept {
switch (_code) {
case NoError:
return "No error";
case Unknown:
return "Unknown";
case InvalidArgument:
return "Invalid argument";
case OutOfRange:
return "Out of range";
case HardwareError:
return "Hardware error";
case LogitechInternal:
return "Logitech internal feature";
case InvalidFeatureIndex:
return "Invalid feature index";
case InvalidFunctionID:
return "Invalid function ID";
case Busy:
return "Busy";
case Unsupported:
return "Unsupported";
case UnknownDevice:
return "Unknown device";
default:
return "Unknown error code";
}
}
uint8_t Error::code() const noexcept {
return _code;
}
hidpp::DeviceIndex Error::deviceIndex() const noexcept {
return _index;
/*
* Copyright 2019-2023 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 <backend/hidpp20/Error.h>
#include <cassert>
using namespace logid::backend;
using namespace logid::backend::hidpp20;
Error::Error(uint8_t code, hidpp::DeviceIndex index) : _code(code), _index (index) {
assert(_code != NoError);
}
const char* Error::what() const noexcept {
switch (_code) {
case NoError:
return "No error";
case Unknown:
return "Unknown";
case InvalidArgument:
return "Invalid argument";
case OutOfRange:
return "Out of range";
case HardwareError:
return "Hardware error";
case LogitechInternal:
return "Logitech internal feature";
case InvalidFeatureIndex:
return "Invalid feature index";
case InvalidFunctionID:
return "Invalid function ID";
case Busy:
return "Busy";
case Unsupported:
return "Unsupported";
case UnknownDevice:
return "Unknown device";
default:
return "Unknown error code";
}
}
uint8_t Error::code() const noexcept {
return _code;
}
hidpp::DeviceIndex Error::deviceIndex() const noexcept {
return _index;
}

View File

@ -1,59 +1,59 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_ERROR_H
#define LOGID_BACKEND_HIDPP20_ERROR_H
#include <backend/hidpp/defs.h>
#include <stdexcept>
#include <cstdint>
namespace logid::backend::hidpp20 {
static constexpr uint8_t ErrorID = 0xFF;
class Error : public std::exception {
public:
enum ErrorCode : uint8_t {
NoError = 0,
Unknown = 1,
InvalidArgument = 2,
OutOfRange = 3,
HardwareError = 4,
LogitechInternal = 5,
InvalidFeatureIndex = 6,
InvalidFunctionID = 7,
Busy = 8,
Unsupported = 9,
UnknownDevice = 10
};
Error(uint8_t code, hidpp::DeviceIndex index);
[[nodiscard]] const char* what() const noexcept override;
[[nodiscard]] uint8_t code() const noexcept;
[[nodiscard]] hidpp::DeviceIndex deviceIndex() const noexcept;
private:
uint8_t _code;
hidpp::DeviceIndex _index;
};
}
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_ERROR_H
#define LOGID_BACKEND_HIDPP20_ERROR_H
#include <backend/hidpp/defs.h>
#include <stdexcept>
#include <cstdint>
namespace logid::backend::hidpp20 {
static constexpr uint8_t ErrorID = 0xFF;
class Error : public std::exception {
public:
enum ErrorCode : uint8_t {
NoError = 0,
Unknown = 1,
InvalidArgument = 2,
OutOfRange = 3,
HardwareError = 4,
LogitechInternal = 5,
InvalidFeatureIndex = 6,
InvalidFunctionID = 7,
Busy = 8,
Unsupported = 9,
UnknownDevice = 10
};
Error(uint8_t code, hidpp::DeviceIndex index);
[[nodiscard]] const char* what() const noexcept override;
[[nodiscard]] uint8_t code() const noexcept;
[[nodiscard]] hidpp::DeviceIndex deviceIndex() const noexcept;
private:
uint8_t _code;
hidpp::DeviceIndex _index;
};
}
#endif //LOGID_BACKEND_HIDPP20_ERROR_H

View File

@ -1,66 +1,66 @@
/*
* Copyright 2019-2023 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 <backend/hidpp20/Feature.h>
#include <backend/hidpp20/EssentialFeature.h>
#include <backend/hidpp20/features/Root.h>
#include <backend/hidpp20/Error.h>
#include <cassert>
using namespace logid::backend::hidpp20;
std::vector<uint8_t> EssentialFeature::callFunction(uint8_t function_id,
std::vector<uint8_t>& params) {
hidpp::Report::Type type;
assert(params.size() <= hidpp::LongParamLength);
if (params.size() <= hidpp::ShortParamLength)
type = hidpp::Report::Type::Short;
else if (params.size() <= hidpp::LongParamLength)
type = hidpp::Report::Type::Long;
else
throw hidpp::Report::InvalidReportID();
hidpp::Report request(type, _device->deviceIndex(), _index, function_id, hidpp::softwareID);
std::copy(params.begin(), params.end(), request.paramBegin());
auto response = _device->sendReport(request);
return {response.paramBegin(), response.paramEnd()};
}
EssentialFeature::EssentialFeature(hidpp::Device* dev, uint16_t _id) :
_device(dev) {
_index = hidpp20::FeatureID::ROOT;
if (_id) {
std::vector<uint8_t> getFunc_req(2);
getFunc_req[0] = (_id >> 8) & 0xff;
getFunc_req[1] = _id & 0xff;
try {
_index = this->callFunction(Root::GetFeature, getFunc_req).at(0);
} catch (Error& e) {
if (e.code() == Error::InvalidFeatureIndex)
throw UnsupportedFeature(_id);
throw e;
}
// 0 if not found
if (!_index)
throw UnsupportedFeature(_id);
}
}
/*
* Copyright 2019-2023 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 <backend/hidpp20/Feature.h>
#include <backend/hidpp20/EssentialFeature.h>
#include <backend/hidpp20/features/Root.h>
#include <backend/hidpp20/Error.h>
#include <cassert>
using namespace logid::backend::hidpp20;
std::vector<uint8_t> EssentialFeature::callFunction(uint8_t function_id,
std::vector<uint8_t>& params) {
hidpp::Report::Type type;
assert(params.size() <= hidpp::LongParamLength);
if (params.size() <= hidpp::ShortParamLength)
type = hidpp::Report::Type::Short;
else if (params.size() <= hidpp::LongParamLength)
type = hidpp::Report::Type::Long;
else
throw hidpp::Report::InvalidReportID();
hidpp::Report request(type, _device->deviceIndex(), _index, function_id, hidpp::softwareID);
std::copy(params.begin(), params.end(), request.paramBegin());
auto response = _device->sendReport(request);
return {response.paramBegin(), response.paramEnd()};
}
EssentialFeature::EssentialFeature(hidpp::Device* dev, uint16_t _id) :
_device(dev) {
_index = hidpp20::FeatureID::ROOT;
if (_id) {
std::vector<uint8_t> getFunc_req(2);
getFunc_req[0] = (_id >> 8) & 0xff;
getFunc_req[1] = _id & 0xff;
try {
_index = this->callFunction(Root::GetFeature, getFunc_req).at(0);
} catch (Error& e) {
if (e.code() == Error::InvalidFeatureIndex)
throw UnsupportedFeature(_id);
throw e;
}
// 0 if not found
if (!_index)
throw UnsupportedFeature(_id);
}
}

View File

@ -1,48 +1,48 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_ESSENTIAL_FEATURE_H
#define LOGID_BACKEND_HIDPP20_ESSENTIAL_FEATURE_H
// WARNING: UNSAFE
/* This class is only meant to provide essential HID++ 2.0 features to the
* hidpp::Device class. No version checks are provided here
*/
#include <backend/hidpp/Device.h>
namespace logid::backend::hidpp20 {
class EssentialFeature {
public:
static const uint16_t ID;
virtual uint16_t getID() = 0;
protected:
EssentialFeature(hidpp::Device* dev, uint16_t _id);
std::vector<uint8_t> callFunction(uint8_t function_id,
std::vector<uint8_t>& params);
hidpp::Device* const _device;
uint8_t _index;
};
}
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_ESSENTIAL_FEATURE_H
#define LOGID_BACKEND_HIDPP20_ESSENTIAL_FEATURE_H
// WARNING: UNSAFE
/* This class is only meant to provide essential HID++ 2.0 features to the
* hidpp::Device class. No version checks are provided here
*/
#include <backend/hidpp/Device.h>
namespace logid::backend::hidpp20 {
class EssentialFeature {
public:
static const uint16_t ID;
virtual uint16_t getID() = 0;
protected:
EssentialFeature(hidpp::Device* dev, uint16_t _id);
std::vector<uint8_t> callFunction(uint8_t function_id,
std::vector<uint8_t>& params);
hidpp::Device* const _device;
uint8_t _index;
};
}
#endif //LOGID_BACKEND_HIDPP20_ESSENTIAL_FEATURE_H

View File

@ -1,68 +1,68 @@
/*
* Copyright 2019-2023 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 <backend/hidpp20/Feature.h>
#include <backend/hidpp20/Device.h>
#include <backend/hidpp20/features/Root.h>
using namespace logid::backend::hidpp20;
const char* UnsupportedFeature::what() const noexcept {
return "Unsupported feature";
}
uint16_t UnsupportedFeature::code() const noexcept {
return _f_id;
}
std::vector<uint8_t> Feature::callFunction(uint8_t function_id,
std::vector<uint8_t>& params) {
return _device->callFunction(_index, function_id, params);
}
void Feature::callFunctionNoResponse(uint8_t function_id,
std::vector<uint8_t>& params) {
_device->callFunctionNoResponse(_index, function_id, params);
}
Feature::Feature(Device* dev, uint16_t _id) : _device(dev) {
_index = hidpp20::FeatureID::ROOT;
if (_id) {
std::vector<uint8_t> getFunc_req(2);
getFunc_req[0] = (_id >> 8) & 0xff;
getFunc_req[1] = _id & 0xff;
try {
auto getFunc_resp = this->callFunction(Root::GetFeature, getFunc_req);
_index = getFunc_resp[0];
} catch (Error& e) {
if (e.code() == Error::InvalidFeatureIndex)
throw UnsupportedFeature(_id);
throw e;
}
// 0 if not found
if (!_index)
throw UnsupportedFeature(_id);
}
}
uint8_t Feature::featureIndex() const {
return _index;
/*
* Copyright 2019-2023 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 <backend/hidpp20/Feature.h>
#include <backend/hidpp20/Device.h>
#include <backend/hidpp20/features/Root.h>
using namespace logid::backend::hidpp20;
const char* UnsupportedFeature::what() const noexcept {
return "Unsupported feature";
}
uint16_t UnsupportedFeature::code() const noexcept {
return _f_id;
}
std::vector<uint8_t> Feature::callFunction(uint8_t function_id,
std::vector<uint8_t>& params) {
return _device->callFunction(_index, function_id, params);
}
void Feature::callFunctionNoResponse(uint8_t function_id,
std::vector<uint8_t>& params) {
_device->callFunctionNoResponse(_index, function_id, params);
}
Feature::Feature(Device* dev, uint16_t _id) : _device(dev) {
_index = hidpp20::FeatureID::ROOT;
if (_id) {
std::vector<uint8_t> getFunc_req(2);
getFunc_req[0] = (_id >> 8) & 0xff;
getFunc_req[1] = _id & 0xff;
try {
auto getFunc_resp = this->callFunction(Root::GetFeature, getFunc_req);
_index = getFunc_resp[0];
} catch (Error& e) {
if (e.code() == Error::InvalidFeatureIndex)
throw UnsupportedFeature(_id);
throw e;
}
// 0 if not found
if (!_index)
throw UnsupportedFeature(_id);
}
}
uint8_t Feature::featureIndex() const {
return _index;
}

View File

@ -1,63 +1,63 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_H
#define LOGID_BACKEND_HIDPP20_FEATURE_H
#include <cstdint>
#include <exception>
#include <vector>
namespace logid::backend::hidpp20 {
class Device;
class UnsupportedFeature : public std::exception {
public:
explicit UnsupportedFeature(uint16_t ID) : _f_id(ID) {}
[[nodiscard]] const char* what() const noexcept override;
[[nodiscard]] uint16_t code() const noexcept;
private:
uint16_t _f_id;
};
class Feature {
public:
static const uint16_t ID;
virtual uint16_t getID() = 0;
[[nodiscard]] uint8_t featureIndex() const;
virtual ~Feature() = default;
protected:
explicit Feature(Device* dev, uint16_t _id);
std::vector<uint8_t> callFunction(uint8_t function_id, std::vector<uint8_t>& params);
void callFunctionNoResponse(uint8_t function_id, std::vector<uint8_t>& params);
Device* const _device;
uint8_t _index;
};
}
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_H
#define LOGID_BACKEND_HIDPP20_FEATURE_H
#include <cstdint>
#include <exception>
#include <vector>
namespace logid::backend::hidpp20 {
class Device;
class UnsupportedFeature : public std::exception {
public:
explicit UnsupportedFeature(uint16_t ID) : _f_id(ID) {}
[[nodiscard]] const char* what() const noexcept override;
[[nodiscard]] uint16_t code() const noexcept;
private:
uint16_t _f_id;
};
class Feature {
public:
static const uint16_t ID;
virtual uint16_t getID() = 0;
[[nodiscard]] uint8_t featureIndex() const;
virtual ~Feature() = default;
protected:
explicit Feature(Device* dev, uint16_t _id);
std::vector<uint8_t> callFunction(uint8_t function_id, std::vector<uint8_t>& params);
void callFunctionNoResponse(uint8_t function_id, std::vector<uint8_t>& params);
Device* const _device;
uint8_t _index;
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_H

View File

@ -1,129 +1,129 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATUREDEFS
#define LOGID_BACKEND_HIDPP20_FEATUREDEFS
#include <cstdint>
namespace logid::backend::hidpp20 {
struct feature_info {
uint16_t feature_id;
bool obsolete;
bool internal;
bool hidden;
};
namespace FeatureID {
enum FeatureID : uint16_t {
ROOT = 0x0000,
FEATURE_SET = 0x0001,
FEATURE_INFO = 0x0002,
FW_VERSION = 0x0003,
DEVICE_NAME = 0x0005,
DEVICE_GROUPS = 0x0006,
DEVICE_FRIENDLY_NAME = 0x0007,
RESET = 0x0020,
CRYPTO_IDENTIFIER = 0x0021,
DFUCONTROL = 0x00c0,
DFUCONTROL_V2 = 0x00c1,
DFUCONTROL_V3 = 0x00c2,
DFU = 0xd000,
BATTERY_STATUS = 0x1000,
BATTERY_VOLTAGE = 0x1001,
CHARGING_CONTROL = 0x1010,
LED_CONTROL = 0x1300,
GENERIC_TEST = 0x1800,
DEVICE_RESET = 0x1802,
OOB_STATE = 0x1805,
CONFIGURABLE_DEVICE_PROPERTIES = 0x1806,
CHANGE_HOST = 0x1814,
HOSTS_INFO = 0x1815,
BACKLIGHT = 0x1981,
BACKLIGHT_V2 = 0x1982,
BACKLIGHT_V3 = 0x1983,
PRESENTER_CONTROL = 0x1a00,
SENSOR_3D = 0x1a01,
REPROG_CONTROLS = 0x1b00,
REPROG_CONTROLS_V2 = 0x1b01,
REPROG_CONTROLS_V2_2 = 0x1b02,
REPROG_CONTROLS_V3 = 0x1b03,
REPROG_CONTROLS_V4 = 0x1b04,
PERSISTENT_REMAPPABLE_ACTION = 0x1bc0,
WIRELESS_DEVICE_STATUS = 0x1d4b,
ENABLE_HIDDEN_FEATURE = 0x1e00,
FIRMWARE_PROPERTIES = 0x1f1f,
ADC_MEASUREMENT = 0x1f20,
LEFT_RIGHT_SWAP = 0x2001,
SWAP_BUTTON = 0x2005,
POINTER_AXES_ORIENTATION = 0x2006,
VERTICAL_SCROLLING = 0x2100,
SMART_SHIFT = 0x2110,
SMART_SHIFT_V2 = 0x2111,
HIRES_SCROLLING = 0x2120,
HIRES_SCROLLING_V2 = 0x2121, // Referred to as Hi-res wheel in cvuchener/hidpp, seems to be V2?
LORES_SCROLLING = 0x2130,
THUMB_WHEEL = 0x2150,
MOUSE_POINTER = 0x2200, // Possibly predecessor to 0x2201?
ADJUSTABLE_DPI = 0x2201,
ANGLE_SNAPPING = 0x2230,
SURFACE_TUNING = 0x2240,
HYBRID_TRACKING = 0x2400,
FN_INVERSION = 0x40a0,
FN_INVERSION_V2 = 0x40a2, // Is 0x40a1 skipped?
FN_INVERSION_V3 = 0x40a3,
ENCRYPTION = 0x4100,
LOCK_KEY_STATE = 0x4220,
SOLAR_DASHBOARD = 0x4301,
KEYBOARD_LAYOUT = 0x4520,
KEYBOARD_DISABLE = 0x4521,
DISABLE_KEYS = 0x4522,
MULTIPLATFORM = 0x4530, // Dual platform only?
MULTIPLATFORM_V2 = 0x4531,
KEYBOARD_LAYOUT_V2 = 0x4540,
CROWN = 0x4600,
TOUCHPAD_FW = 0x6010,
TOUCHPAD_SW = 0x6011,
TOUCHPAD_FW_WIN8 = 0x6012,
TOUCHMOUSE_RAW = 0x6100,
// TOUCHMOUSE_6120 = 0x6120, (Keeping this commented out until a better name is found)
GESTURE = 0x6500,
GESTURE_V2 = 0x6501,
G_KEY = 0x8010,
M_KEY = 0x8020,
// MR = 0x8030, (Keeping this commented out until a better name is found)
BRIGHTNESS_CONTROL = 0x8040,
REPORT_RATE = 0x8060,
RGB_EFFECTS = 0x8070,
RGB_EFFECTS_V2 = 0x8071,
PER_KEY_LIGHTING = 0x8080,
PER_KEY_LIGHTING_V2 = 0x8081,
MODE_STATUS = 0x8100,
MOUSE_BUTTON_SPY = 0x8110,
LATENCY_MONITORING = 0x8111,
GAMING_ATTACHMENTS = 0x8120,
FORCE_FEEDBACK = 0x8123,
SIDETONE = 0x8300,
EQUALIZER = 0x8310,
HEADSET_OUT = 0x8320
};
}
}
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATUREDEFS
#define LOGID_BACKEND_HIDPP20_FEATUREDEFS
#include <cstdint>
namespace logid::backend::hidpp20 {
struct feature_info {
uint16_t feature_id;
bool obsolete;
bool internal;
bool hidden;
};
namespace FeatureID {
enum FeatureID : uint16_t {
ROOT = 0x0000,
FEATURE_SET = 0x0001,
FEATURE_INFO = 0x0002,
FW_VERSION = 0x0003,
DEVICE_NAME = 0x0005,
DEVICE_GROUPS = 0x0006,
DEVICE_FRIENDLY_NAME = 0x0007,
RESET = 0x0020,
CRYPTO_IDENTIFIER = 0x0021,
DFUCONTROL = 0x00c0,
DFUCONTROL_V2 = 0x00c1,
DFUCONTROL_V3 = 0x00c2,
DFU = 0xd000,
BATTERY_STATUS = 0x1000,
BATTERY_VOLTAGE = 0x1001,
CHARGING_CONTROL = 0x1010,
LED_CONTROL = 0x1300,
GENERIC_TEST = 0x1800,
DEVICE_RESET = 0x1802,
OOB_STATE = 0x1805,
CONFIGURABLE_DEVICE_PROPERTIES = 0x1806,
CHANGE_HOST = 0x1814,
HOSTS_INFO = 0x1815,
BACKLIGHT = 0x1981,
BACKLIGHT_V2 = 0x1982,
BACKLIGHT_V3 = 0x1983,
PRESENTER_CONTROL = 0x1a00,
SENSOR_3D = 0x1a01,
REPROG_CONTROLS = 0x1b00,
REPROG_CONTROLS_V2 = 0x1b01,
REPROG_CONTROLS_V2_2 = 0x1b02,
REPROG_CONTROLS_V3 = 0x1b03,
REPROG_CONTROLS_V4 = 0x1b04,
PERSISTENT_REMAPPABLE_ACTION = 0x1bc0,
WIRELESS_DEVICE_STATUS = 0x1d4b,
ENABLE_HIDDEN_FEATURE = 0x1e00,
FIRMWARE_PROPERTIES = 0x1f1f,
ADC_MEASUREMENT = 0x1f20,
LEFT_RIGHT_SWAP = 0x2001,
SWAP_BUTTON = 0x2005,
POINTER_AXES_ORIENTATION = 0x2006,
VERTICAL_SCROLLING = 0x2100,
SMART_SHIFT = 0x2110,
SMART_SHIFT_V2 = 0x2111,
HIRES_SCROLLING = 0x2120,
HIRES_SCROLLING_V2 = 0x2121, // Referred to as Hi-res wheel in cvuchener/hidpp, seems to be V2?
LORES_SCROLLING = 0x2130,
THUMB_WHEEL = 0x2150,
MOUSE_POINTER = 0x2200, // Possibly predecessor to 0x2201?
ADJUSTABLE_DPI = 0x2201,
ANGLE_SNAPPING = 0x2230,
SURFACE_TUNING = 0x2240,
HYBRID_TRACKING = 0x2400,
FN_INVERSION = 0x40a0,
FN_INVERSION_V2 = 0x40a2, // Is 0x40a1 skipped?
FN_INVERSION_V3 = 0x40a3,
ENCRYPTION = 0x4100,
LOCK_KEY_STATE = 0x4220,
SOLAR_DASHBOARD = 0x4301,
KEYBOARD_LAYOUT = 0x4520,
KEYBOARD_DISABLE = 0x4521,
DISABLE_KEYS = 0x4522,
MULTIPLATFORM = 0x4530, // Dual platform only?
MULTIPLATFORM_V2 = 0x4531,
KEYBOARD_LAYOUT_V2 = 0x4540,
CROWN = 0x4600,
TOUCHPAD_FW = 0x6010,
TOUCHPAD_SW = 0x6011,
TOUCHPAD_FW_WIN8 = 0x6012,
TOUCHMOUSE_RAW = 0x6100,
// TOUCHMOUSE_6120 = 0x6120, (Keeping this commented out until a better name is found)
GESTURE = 0x6500,
GESTURE_V2 = 0x6501,
G_KEY = 0x8010,
M_KEY = 0x8020,
// MR = 0x8030, (Keeping this commented out until a better name is found)
BRIGHTNESS_CONTROL = 0x8040,
REPORT_RATE = 0x8060,
RGB_EFFECTS = 0x8070,
RGB_EFFECTS_V2 = 0x8071,
PER_KEY_LIGHTING = 0x8080,
PER_KEY_LIGHTING_V2 = 0x8081,
MODE_STATUS = 0x8100,
MOUSE_BUTTON_SPY = 0x8110,
LATENCY_MONITORING = 0x8111,
GAMING_ATTACHMENTS = 0x8120,
FORCE_FEEDBACK = 0x8123,
SIDETONE = 0x8300,
EQUALIZER = 0x8310,
HEADSET_OUT = 0x8320
};
}
}
#endif //LOGID_BACKEND_HIDPP20_FEATUREDEFS

View File

@ -1,82 +1,82 @@
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/AdjustableDPI.h>
using namespace logid::backend::hidpp20;
AdjustableDPI::AdjustableDPI(Device* dev) : Feature(dev, ID) {
}
uint8_t AdjustableDPI::getSensorCount() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetSensorCount, params);
return response[0];
}
AdjustableDPI::SensorDPIList AdjustableDPI::getSensorDPIList(uint8_t sensor) {
SensorDPIList dpi_list{};
std::vector<uint8_t> params(1);
params[0] = sensor;
auto response = callFunction(GetSensorDPIList, params);
dpi_list.dpiStep = false;
for (std::size_t i = 1; i < response.size(); i += 2) {
uint16_t dpi = response[i + 1];
dpi |= (response[i] << 8);
if (!dpi)
break;
if (dpi >= 0xe000) {
dpi_list.isRange = true;
dpi_list.dpiStep = dpi - 0xe000;
} else {
dpi_list.dpis.push_back(dpi);
}
}
return dpi_list;
}
uint16_t AdjustableDPI::getDefaultSensorDPI(uint8_t sensor) {
std::vector<uint8_t> params(1);
params[0] = sensor;
auto response = callFunction(GetSensorDPI, params);
uint16_t default_dpi = response[4];
default_dpi |= (response[3] << 8);
return default_dpi;
}
uint16_t AdjustableDPI::getSensorDPI(uint8_t sensor) {
std::vector<uint8_t> params(1);
params[0] = sensor;
auto response = callFunction(GetSensorDPI, params);
uint16_t dpi = response[2];
dpi |= (response[1] << 8);
return dpi;
}
void AdjustableDPI::setSensorDPI(uint8_t sensor, uint16_t dpi) {
std::vector<uint8_t> params(3);
params[0] = sensor;
params[1] = (dpi >> 8);
params[2] = (dpi & 0xFF);
callFunction(SetSensorDPI, params);
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/AdjustableDPI.h>
using namespace logid::backend::hidpp20;
AdjustableDPI::AdjustableDPI(Device* dev) : Feature(dev, ID) {
}
uint8_t AdjustableDPI::getSensorCount() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetSensorCount, params);
return response[0];
}
AdjustableDPI::SensorDPIList AdjustableDPI::getSensorDPIList(uint8_t sensor) {
SensorDPIList dpi_list{};
std::vector<uint8_t> params(1);
params[0] = sensor;
auto response = callFunction(GetSensorDPIList, params);
dpi_list.dpiStep = false;
for (std::size_t i = 1; i < response.size(); i += 2) {
uint16_t dpi = response[i + 1];
dpi |= (response[i] << 8);
if (!dpi)
break;
if (dpi >= 0xe000) {
dpi_list.isRange = true;
dpi_list.dpiStep = dpi - 0xe000;
} else {
dpi_list.dpis.push_back(dpi);
}
}
return dpi_list;
}
uint16_t AdjustableDPI::getDefaultSensorDPI(uint8_t sensor) {
std::vector<uint8_t> params(1);
params[0] = sensor;
auto response = callFunction(GetSensorDPI, params);
uint16_t default_dpi = response[4];
default_dpi |= (response[3] << 8);
return default_dpi;
}
uint16_t AdjustableDPI::getSensorDPI(uint8_t sensor) {
std::vector<uint8_t> params(1);
params[0] = sensor;
auto response = callFunction(GetSensorDPI, params);
uint16_t dpi = response[2];
dpi |= (response[1] << 8);
return dpi;
}
void AdjustableDPI::setSensorDPI(uint8_t sensor, uint16_t dpi) {
std::vector<uint8_t> params(3);
params[0] = sensor;
params[1] = (dpi >> 8);
params[2] = (dpi & 0xFF);
callFunction(SetSensorDPI, params);
}

View File

@ -1,58 +1,58 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_ADJUSTABLEDPI_H
#define LOGID_BACKEND_HIDPP20_FEATURE_ADJUSTABLEDPI_H
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
namespace logid::backend::hidpp20 {
class AdjustableDPI : public Feature {
public:
static const uint16_t ID = FeatureID::ADJUSTABLE_DPI;
[[nodiscard]] uint16_t getID() final { return ID; }
enum Function {
GetSensorCount = 0,
GetSensorDPIList = 1,
GetSensorDPI = 2,
SetSensorDPI = 3
};
explicit AdjustableDPI(Device* dev);
uint8_t getSensorCount();
struct SensorDPIList {
std::vector<uint16_t> dpis;
bool isRange;
uint16_t dpiStep;
};
SensorDPIList getSensorDPIList(uint8_t sensor);
uint16_t getDefaultSensorDPI(uint8_t sensor);
uint16_t getSensorDPI(uint8_t sensor);
void setSensorDPI(uint8_t sensor, uint16_t dpi);
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_ADJUSTABLEDPI_H
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_ADJUSTABLEDPI_H
#define LOGID_BACKEND_HIDPP20_FEATURE_ADJUSTABLEDPI_H
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
namespace logid::backend::hidpp20 {
class AdjustableDPI : public Feature {
public:
static const uint16_t ID = FeatureID::ADJUSTABLE_DPI;
[[nodiscard]] uint16_t getID() final { return ID; }
enum Function {
GetSensorCount = 0,
GetSensorDPIList = 1,
GetSensorDPI = 2,
SetSensorDPI = 3
};
explicit AdjustableDPI(Device* dev);
uint8_t getSensorCount();
struct SensorDPIList {
std::vector<uint16_t> dpis;
bool isRange;
uint16_t dpiStep;
};
SensorDPIList getSensorDPIList(uint8_t sensor);
uint16_t getDefaultSensorDPI(uint8_t sensor);
uint16_t getSensorDPI(uint8_t sensor);
void setSensorDPI(uint8_t sensor, uint16_t dpi);
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_ADJUSTABLEDPI_H

View File

@ -1,75 +1,75 @@
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/ChangeHost.h>
#include <backend/hidpp20/Device.h>
using namespace logid::backend::hidpp20;
ChangeHost::ChangeHost(Device* dev) : Feature(dev, ID), _host_count(0) {
}
ChangeHost::HostInfo ChangeHost::getHostInfo() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetHostInfo, params);
HostInfo info{};
info.hostCount = response[0];
info.currentHost = response[1];
info.enhancedHostSwitch = response[2] & 1;
if (!_host_count)
_host_count = info.hostCount;
return info;
}
void ChangeHost::setHost(uint8_t host) {
/* Expect connection to be severed here, send without response
*
* Since there is no response, we have to emulate any kind of
* error that may be returned (i.e. InvalidArgument as per the docs)
*/
if (!_host_count)
getHostInfo();
if (host >= _host_count)
throw Error(hidpp20::Error::InvalidArgument, _device->deviceIndex());
std::vector<uint8_t> params = {host};
callFunctionNoResponse(SetCurrentHost, params);
}
[[maybe_unused]]
std::vector<uint8_t> ChangeHost::getCookies() {
if (!_host_count)
getHostInfo();
std::vector<uint8_t> params(0);
auto response = callFunction(GetCookies, params);
response.resize(_host_count);
return response;
}
[[maybe_unused]]
void ChangeHost::setCookie(uint8_t host, uint8_t cookie) {
std::vector<uint8_t> params = {host, cookie};
callFunction(SetCookie, params);
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/ChangeHost.h>
#include <backend/hidpp20/Device.h>
using namespace logid::backend::hidpp20;
ChangeHost::ChangeHost(Device* dev) : Feature(dev, ID), _host_count(0) {
}
ChangeHost::HostInfo ChangeHost::getHostInfo() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetHostInfo, params);
HostInfo info{};
info.hostCount = response[0];
info.currentHost = response[1];
info.enhancedHostSwitch = response[2] & 1;
if (!_host_count)
_host_count = info.hostCount;
return info;
}
void ChangeHost::setHost(uint8_t host) {
/* Expect connection to be severed here, send without response
*
* Since there is no response, we have to emulate any kind of
* error that may be returned (i.e. InvalidArgument as per the docs)
*/
if (!_host_count)
getHostInfo();
if (host >= _host_count)
throw Error(hidpp20::Error::InvalidArgument, _device->deviceIndex());
std::vector<uint8_t> params = {host};
callFunctionNoResponse(SetCurrentHost, params);
}
[[maybe_unused]]
std::vector<uint8_t> ChangeHost::getCookies() {
if (!_host_count)
getHostInfo();
std::vector<uint8_t> params(0);
auto response = callFunction(GetCookies, params);
response.resize(_host_count);
return response;
}
[[maybe_unused]]
void ChangeHost::setCookie(uint8_t host, uint8_t cookie) {
std::vector<uint8_t> params = {host, cookie};
callFunction(SetCookie, params);
}

View File

@ -1,59 +1,59 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_CHANGEHOST_H
#define LOGID_BACKEND_HIDPP20_FEATURE_CHANGEHOST_H
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp20/Feature.h>
namespace logid::backend::hidpp20 {
class ChangeHost : public Feature {
public:
static const uint16_t ID = FeatureID::CHANGE_HOST;
[[nodiscard]] uint16_t getID() final { return ID; }
explicit ChangeHost(Device* dev);
enum Function {
GetHostInfo = 0,
SetCurrentHost = 1,
GetCookies = 2,
SetCookie = 3
};
struct HostInfo {
uint8_t hostCount;
uint8_t currentHost;
bool enhancedHostSwitch;
};
HostInfo getHostInfo();
void setHost(uint8_t host);
[[maybe_unused]] [[maybe_unused]] std::vector<uint8_t> getCookies();
[[maybe_unused]] [[maybe_unused]] void setCookie(uint8_t host, uint8_t cookie);
private:
uint8_t _host_count;
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_CHANGEHOST_H
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_CHANGEHOST_H
#define LOGID_BACKEND_HIDPP20_FEATURE_CHANGEHOST_H
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp20/Feature.h>
namespace logid::backend::hidpp20 {
class ChangeHost : public Feature {
public:
static const uint16_t ID = FeatureID::CHANGE_HOST;
[[nodiscard]] uint16_t getID() final { return ID; }
explicit ChangeHost(Device* dev);
enum Function {
GetHostInfo = 0,
SetCurrentHost = 1,
GetCookies = 2,
SetCookie = 3
};
struct HostInfo {
uint8_t hostCount;
uint8_t currentHost;
bool enhancedHostSwitch;
};
HostInfo getHostInfo();
void setHost(uint8_t host);
[[maybe_unused]] [[maybe_unused]] std::vector<uint8_t> getCookies();
[[maybe_unused]] [[maybe_unused]] void setCookie(uint8_t host, uint8_t cookie);
private:
uint8_t _host_count;
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_CHANGEHOST_H

View File

@ -1,64 +1,64 @@
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/DeviceName.h>
using namespace logid::backend;
using namespace logid::backend::hidpp20;
namespace {
std::string _getName(uint8_t length,
const std::function<std::vector<uint8_t>(std::vector<uint8_t>)>& fcall) {
uint8_t function_calls = length / hidpp::LongParamLength;
if (length % hidpp::LongParamLength)
function_calls++;
std::vector<uint8_t> params(1);
std::string name;
for (uint8_t i = 0; i < function_calls; i++) {
params[0] = i * hidpp::LongParamLength;
auto name_section = fcall(params);
for (std::size_t j = 0; j < hidpp::LongParamLength; j++) {
if (params[0] + j >= length)
return name;
name += (char) name_section[j];
}
}
return name;
}
}
DeviceName::DeviceName(hidpp::Device* dev) :
EssentialFeature(dev, ID) {
}
uint8_t DeviceName::getNameLength() {
std::vector<uint8_t> params(0);
auto response = this->callFunction(DeviceName::Function::GetLength, params);
return response[0];
}
std::string DeviceName::getName() {
return _getName(getNameLength(), [this]
(std::vector<uint8_t> params) -> std::vector<uint8_t> {
return this->callFunction(DeviceName::Function::GetDeviceName, params);
});
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/DeviceName.h>
using namespace logid::backend;
using namespace logid::backend::hidpp20;
namespace {
std::string _getName(uint8_t length,
const std::function<std::vector<uint8_t>(std::vector<uint8_t>)>& fcall) {
uint8_t function_calls = length / hidpp::LongParamLength;
if (length % hidpp::LongParamLength)
function_calls++;
std::vector<uint8_t> params(1);
std::string name;
for (uint8_t i = 0; i < function_calls; i++) {
params[0] = i * hidpp::LongParamLength;
auto name_section = fcall(params);
for (std::size_t j = 0; j < hidpp::LongParamLength; j++) {
if (params[0] + j >= length)
return name;
name += (char) name_section[j];
}
}
return name;
}
}
DeviceName::DeviceName(hidpp::Device* dev) :
EssentialFeature(dev, ID) {
}
uint8_t DeviceName::getNameLength() {
std::vector<uint8_t> params(0);
auto response = this->callFunction(DeviceName::Function::GetLength, params);
return response[0];
}
std::string DeviceName::getName() {
return _getName(getNameLength(), [this]
(std::vector<uint8_t> params) -> std::vector<uint8_t> {
return this->callFunction(DeviceName::Function::GetDeviceName, params);
});
}

View File

@ -1,45 +1,45 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_DEVICENAME_H
#define LOGID_BACKEND_HIDPP20_FEATURE_DEVICENAME_H
#include <backend/hidpp20/EssentialFeature.h>
#include <backend/hidpp20/feature_defs.h>
namespace logid::backend::hidpp20 {
class DeviceName : public EssentialFeature {
public:
static const uint16_t ID = FeatureID::DEVICE_NAME;
enum Function : uint8_t {
GetLength = 0,
GetDeviceName = 1
};
[[nodiscard]] uint16_t getID() final { return ID; }
explicit DeviceName(hidpp::Device* device);
[[nodiscard]] uint8_t getNameLength();
[[nodiscard]] std::string getName();
};
}
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_DEVICENAME_H
#define LOGID_BACKEND_HIDPP20_FEATURE_DEVICENAME_H
#include <backend/hidpp20/EssentialFeature.h>
#include <backend/hidpp20/feature_defs.h>
namespace logid::backend::hidpp20 {
class DeviceName : public EssentialFeature {
public:
static const uint16_t ID = FeatureID::DEVICE_NAME;
enum Function : uint8_t {
GetLength = 0,
GetDeviceName = 1
};
[[nodiscard]] uint16_t getID() final { return ID; }
explicit DeviceName(hidpp::Device* device);
[[nodiscard]] uint8_t getNameLength();
[[nodiscard]] std::string getName();
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_DEVICENAME_H

View File

@ -1,49 +1,49 @@
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/FeatureSet.h>
using namespace logid::backend::hidpp20;
[[maybe_unused]]
FeatureSet::FeatureSet(Device* device) : Feature(device, ID) {
}
uint8_t FeatureSet::getFeatureCount() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetFeatureCount, params);
return response[0];
}
uint16_t FeatureSet::getFeature(uint8_t feature_index) {
std::vector<uint8_t> params(1);
params[0] = feature_index;
auto response = callFunction(GetFeature, params);
uint16_t feature_id = (response[0] << 8);
feature_id |= response[1];
return feature_id;
}
[[maybe_unused]]
std::map<uint8_t, uint16_t> FeatureSet::getFeatures() {
uint8_t feature_count = getFeatureCount();
std::map<uint8_t, uint16_t> features;
for (uint8_t i = 0; i < feature_count; i++)
features[i] = getFeature(i);
return features;
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/FeatureSet.h>
using namespace logid::backend::hidpp20;
[[maybe_unused]]
FeatureSet::FeatureSet(Device* device) : Feature(device, ID) {
}
uint8_t FeatureSet::getFeatureCount() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetFeatureCount, params);
return response[0];
}
uint16_t FeatureSet::getFeature(uint8_t feature_index) {
std::vector<uint8_t> params(1);
params[0] = feature_index;
auto response = callFunction(GetFeature, params);
uint16_t feature_id = (response[0] << 8);
feature_id |= response[1];
return feature_id;
}
[[maybe_unused]]
std::map<uint8_t, uint16_t> FeatureSet::getFeatures() {
uint8_t feature_count = getFeatureCount();
std::map<uint8_t, uint16_t> features;
for (uint8_t i = 0; i < feature_count; i++)
features[i] = getFeature(i);
return features;
}

View File

@ -1,49 +1,49 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_FEATURESET_H
#define LOGID_BACKEND_HIDPP20_FEATURE_FEATURESET_H
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
#include <map>
namespace logid::backend::hidpp20 {
class FeatureSet : public Feature {
public:
static const uint16_t ID = FeatureID::FEATURE_SET;
[[nodiscard]] uint16_t getID() final { return ID; }
enum Function : uint8_t {
GetFeatureCount = 0,
GetFeature = 1
};
[[maybe_unused]] [[maybe_unused]]
explicit FeatureSet(Device* device);
[[nodiscard]] uint8_t getFeatureCount();
[[nodiscard]] uint16_t getFeature(uint8_t feature_index);
[[maybe_unused]]
[[nodiscard]] std::map<uint8_t, uint16_t> getFeatures();
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_FEATURESET_H
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_FEATURESET_H
#define LOGID_BACKEND_HIDPP20_FEATURE_FEATURESET_H
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
#include <map>
namespace logid::backend::hidpp20 {
class FeatureSet : public Feature {
public:
static const uint16_t ID = FeatureID::FEATURE_SET;
[[nodiscard]] uint16_t getID() final { return ID; }
enum Function : uint8_t {
GetFeatureCount = 0,
GetFeature = 1
};
[[maybe_unused]] [[maybe_unused]]
explicit FeatureSet(Device* device);
[[nodiscard]] uint8_t getFeatureCount();
[[nodiscard]] uint16_t getFeature(uint8_t feature_index);
[[maybe_unused]]
[[nodiscard]] std::map<uint8_t, uint16_t> getFeatures();
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_FEATURESET_H

View File

@ -1,68 +1,68 @@
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/HiresScroll.h>
#include <cassert>
using namespace logid::backend::hidpp20;
HiresScroll::HiresScroll(Device* device) : Feature(device, ID) {
}
HiresScroll::Capabilities HiresScroll::getCapabilities() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetCapabilities, params);
Capabilities capabilities{};
capabilities.multiplier = response[0];
capabilities.flags = response[1];
return capabilities;
}
uint8_t HiresScroll::getMode() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetMode, params);
return response[0];
}
void HiresScroll::setMode(uint8_t mode) {
std::vector<uint8_t> params(1);
params[0] = mode;
callFunction(SetMode, params);
}
[[maybe_unused]] bool HiresScroll::getRatchetState() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetRatchetState, params);
return params[0];
}
HiresScroll::WheelStatus HiresScroll::wheelMovementEvent(const hidpp::Report& report) {
assert(report.function() == WheelMovement);
WheelStatus status{};
status.hiRes = report.paramBegin()[0] & 1 << 4;
status.periods = report.paramBegin()[0] & 0x0F;
status.deltaV = (int16_t) (report.paramBegin()[1] << 8 | report.paramBegin()[2]);
return status;
}
[[maybe_unused]]
HiresScroll::RatchetState HiresScroll::ratchetSwitchEvent(const hidpp::Report& report) {
assert(report.function() == RatchetSwitch);
// Possible bad cast
return static_cast<RatchetState>(report.paramBegin()[0]);
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/HiresScroll.h>
#include <cassert>
using namespace logid::backend::hidpp20;
HiresScroll::HiresScroll(Device* device) : Feature(device, ID) {
}
HiresScroll::Capabilities HiresScroll::getCapabilities() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetCapabilities, params);
Capabilities capabilities{};
capabilities.multiplier = response[0];
capabilities.flags = response[1];
return capabilities;
}
uint8_t HiresScroll::getMode() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetMode, params);
return response[0];
}
void HiresScroll::setMode(uint8_t mode) {
std::vector<uint8_t> params(1);
params[0] = mode;
callFunction(SetMode, params);
}
[[maybe_unused]] bool HiresScroll::getRatchetState() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetRatchetState, params);
return params[0];
}
HiresScroll::WheelStatus HiresScroll::wheelMovementEvent(const hidpp::Report& report) {
assert(report.function() == WheelMovement);
WheelStatus status{};
status.hiRes = report.paramBegin()[0] & 1 << 4;
status.periods = report.paramBegin()[0] & 0x0F;
status.deltaV = (int16_t) (report.paramBegin()[1] << 8 | report.paramBegin()[2]);
return status;
}
[[maybe_unused]]
HiresScroll::RatchetState HiresScroll::ratchetSwitchEvent(const hidpp::Report& report) {
assert(report.function() == RatchetSwitch);
// Possible bad cast
return static_cast<RatchetState>(report.paramBegin()[0]);
}

View File

@ -1,90 +1,90 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H
#define LOGID_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp/Report.h>
namespace logid::backend::hidpp20 {
class HiresScroll : public Feature {
public:
///TODO: Hires scroll V1?
static const uint16_t ID = FeatureID::HIRES_SCROLLING_V2;
uint16_t getID() final { return ID; }
enum Function : uint8_t {
GetCapabilities = 0,
GetMode = 1,
SetMode = 2,
GetRatchetState = 3
};
enum Event : uint8_t {
WheelMovement = 0,
RatchetSwitch = 1,
};
enum Capability : uint8_t {
Invertible = 1 << 3,
HasRatchet = 1 << 2
};
enum Mode : uint8_t {
Inverted = 1 << 2,
HiRes = 1 << 1,
Target = 1
};
enum RatchetState : uint8_t {
FreeWheel = 0,
Ratchet = 1
};
struct Capabilities {
uint8_t multiplier;
uint8_t flags;
};
struct WheelStatus {
bool hiRes;
uint8_t periods;
int16_t deltaV;
};
explicit HiresScroll(Device* device);
Capabilities getCapabilities();
uint8_t getMode();
void setMode(uint8_t mode);
[[maybe_unused]]
bool getRatchetState();
static WheelStatus wheelMovementEvent(const hidpp::Report& report);
[[maybe_unused]]
static RatchetState ratchetSwitchEvent(const hidpp::Report& report);
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H
#define LOGID_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp/Report.h>
namespace logid::backend::hidpp20 {
class HiresScroll : public Feature {
public:
///TODO: Hires scroll V1?
static const uint16_t ID = FeatureID::HIRES_SCROLLING_V2;
uint16_t getID() final { return ID; }
enum Function : uint8_t {
GetCapabilities = 0,
GetMode = 1,
SetMode = 2,
GetRatchetState = 3
};
enum Event : uint8_t {
WheelMovement = 0,
RatchetSwitch = 1,
};
enum Capability : uint8_t {
Invertible = 1 << 3,
HasRatchet = 1 << 2
};
enum Mode : uint8_t {
Inverted = 1 << 2,
HiRes = 1 << 1,
Target = 1
};
enum RatchetState : uint8_t {
FreeWheel = 0,
Ratchet = 1
};
struct Capabilities {
uint8_t multiplier;
uint8_t flags;
};
struct WheelStatus {
bool hiRes;
uint8_t periods;
int16_t deltaV;
};
explicit HiresScroll(Device* device);
Capabilities getCapabilities();
uint8_t getMode();
void setMode(uint8_t mode);
[[maybe_unused]]
bool getRatchetState();
static WheelStatus wheelMovementEvent(const hidpp::Report& report);
[[maybe_unused]]
static RatchetState ratchetSwitchEvent(const hidpp::Report& report);
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H

View File

@ -1,184 +1,184 @@
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/ReprogControls.h>
#include <backend/hidpp20/Error.h>
#include <backend/hidpp20/Device.h>
#include <cassert>
using namespace logid::backend::hidpp20;
// Define all the ReprogControls versions
#define DEFINE_REPROG(T, Base) \
T::T(Device* dev, uint16_t _id) : Base(dev, _id) { } \
T::T(Device* dev) : T(dev, ID) { }
DEFINE_REPROG(ReprogControls, Feature)
DEFINE_REPROG(ReprogControlsV2, ReprogControls)
DEFINE_REPROG(ReprogControlsV2_2, ReprogControlsV2)
DEFINE_REPROG(ReprogControlsV3, ReprogControlsV2_2)
DEFINE_REPROG(ReprogControlsV4, ReprogControlsV3)
template<typename T>
std::shared_ptr<T> make_reprog(Device* dev) {
try {
return std::make_shared<T>(dev);
} catch (UnsupportedFeature& e) {
return {};
}
}
std::shared_ptr<ReprogControls> ReprogControls::autoVersion(Device* dev) {
if (auto v4 = make_reprog<ReprogControlsV4>(dev)) {
return v4;
} else if (auto v3 = make_reprog<ReprogControlsV3>(dev)) {
return v3;
} else if (auto v2_2 = make_reprog<ReprogControlsV2_2>(dev)) {
return v2_2;
} else if (auto v2 = make_reprog<ReprogControlsV2>(dev)) {
return v2;
}
return std::make_shared<ReprogControls>(dev);
}
uint8_t ReprogControls::getControlCount() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetControlCount, params);
return response[0];
}
ReprogControls::ControlInfo ReprogControls::getControlInfo(uint8_t index) {
std::vector<uint8_t> params(1);
ControlInfo info{};
params[0] = index;
auto response = callFunction(GetControlInfo, params);
info.controlID = response[1];
info.controlID |= response[0] << 8;
info.taskID = response[3];
info.taskID |= response[2] << 8;
info.flags = response[4];
info.position = response[5];
info.group = response[6];
info.groupMask = response[7];
info.additionalFlags = response[8];
return info;
}
void ReprogControls::initCidMap() {
std::unique_lock<std::mutex> lock(_cids_populating);
if (_cids_initialized)
return;
uint8_t controls = getControlCount();
for (uint8_t i = 0; i < controls; i++) {
auto info = getControlInfo(i);
_cids.emplace(info.controlID, info);
}
_cids_initialized = true;
}
const std::map<uint16_t, ReprogControls::ControlInfo>&
ReprogControls::getControls() const {
return _cids;
}
ReprogControls::ControlInfo ReprogControls::getControlIdInfo(uint16_t cid) {
if (!_cids_initialized)
initCidMap();
auto it = _cids.find(cid);
if (it == _cids.end())
throw Error(Error::InvalidArgument, _device->deviceIndex());
else
return it->second;
}
[[maybe_unused]] ReprogControls::ControlInfo ReprogControls::getControlReporting(uint16_t cid) {
// Emulate this function, only Reprog controls v4 supports this
auto info = getControlIdInfo(cid);
ControlInfo report{};
report.controlID = cid;
report.flags = 0;
if (info.flags & TemporaryDivertable)
report.flags |= TemporaryDiverted;
if (info.flags & PersistentlyDivertable)
report.flags |= PersistentlyDiverted;
if (info.additionalFlags & RawXY)
report.flags |= RawXYDiverted;
return report;
}
void ReprogControls::setControlReporting(uint16_t cid, ControlInfo info) {
// This function does not exist pre-v4 and cannot be emulated, ignore.
(void) cid;
(void) info; // Suppress unused warnings
}
std::set<uint16_t> ReprogControls::divertedButtonEvent(
const hidpp::Report& report) {
assert(report.function() == DivertedButtonEvent);
std::set<uint16_t> buttons;
uint8_t cids = std::distance(report.paramBegin(), report.paramEnd()) / 2;
for (uint8_t i = 0; i < cids; i++) {
uint16_t cid = report.paramBegin()[2 * i + 1];
cid |= report.paramBegin()[2 * i] << 8;
if (cid)
buttons.insert(cid);
else
break;
}
return buttons;
}
ReprogControls::Move ReprogControls::divertedRawXYEvent(const hidpp::Report
& report) {
assert(report.function() == DivertedRawXYEvent);
Move move{};
move.x = (int16_t) ((report.paramBegin()[0] << 8) | report.paramBegin()[1]);
move.y = (int16_t) ((report.paramBegin()[2] << 8) | report.paramBegin()[3]);
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(uint16_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);
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/ReprogControls.h>
#include <backend/hidpp20/Error.h>
#include <backend/hidpp20/Device.h>
#include <cassert>
using namespace logid::backend::hidpp20;
// Define all the ReprogControls versions
#define DEFINE_REPROG(T, Base) \
T::T(Device* dev, uint16_t _id) : Base(dev, _id) { } \
T::T(Device* dev) : T(dev, ID) { }
DEFINE_REPROG(ReprogControls, Feature)
DEFINE_REPROG(ReprogControlsV2, ReprogControls)
DEFINE_REPROG(ReprogControlsV2_2, ReprogControlsV2)
DEFINE_REPROG(ReprogControlsV3, ReprogControlsV2_2)
DEFINE_REPROG(ReprogControlsV4, ReprogControlsV3)
template<typename T>
std::shared_ptr<T> make_reprog(Device* dev) {
try {
return std::make_shared<T>(dev);
} catch (UnsupportedFeature& e) {
return {};
}
}
std::shared_ptr<ReprogControls> ReprogControls::autoVersion(Device* dev) {
if (auto v4 = make_reprog<ReprogControlsV4>(dev)) {
return v4;
} else if (auto v3 = make_reprog<ReprogControlsV3>(dev)) {
return v3;
} else if (auto v2_2 = make_reprog<ReprogControlsV2_2>(dev)) {
return v2_2;
} else if (auto v2 = make_reprog<ReprogControlsV2>(dev)) {
return v2;
}
return std::make_shared<ReprogControls>(dev);
}
uint8_t ReprogControls::getControlCount() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetControlCount, params);
return response[0];
}
ReprogControls::ControlInfo ReprogControls::getControlInfo(uint8_t index) {
std::vector<uint8_t> params(1);
ControlInfo info{};
params[0] = index;
auto response = callFunction(GetControlInfo, params);
info.controlID = response[1];
info.controlID |= response[0] << 8;
info.taskID = response[3];
info.taskID |= response[2] << 8;
info.flags = response[4];
info.position = response[5];
info.group = response[6];
info.groupMask = response[7];
info.additionalFlags = response[8];
return info;
}
void ReprogControls::initCidMap() {
std::unique_lock<std::mutex> lock(_cids_populating);
if (_cids_initialized)
return;
uint8_t controls = getControlCount();
for (uint8_t i = 0; i < controls; i++) {
auto info = getControlInfo(i);
_cids.emplace(info.controlID, info);
}
_cids_initialized = true;
}
const std::map<uint16_t, ReprogControls::ControlInfo>&
ReprogControls::getControls() const {
return _cids;
}
ReprogControls::ControlInfo ReprogControls::getControlIdInfo(uint16_t cid) {
if (!_cids_initialized)
initCidMap();
auto it = _cids.find(cid);
if (it == _cids.end())
throw Error(Error::InvalidArgument, _device->deviceIndex());
else
return it->second;
}
[[maybe_unused]] ReprogControls::ControlInfo ReprogControls::getControlReporting(uint16_t cid) {
// Emulate this function, only Reprog controls v4 supports this
auto info = getControlIdInfo(cid);
ControlInfo report{};
report.controlID = cid;
report.flags = 0;
if (info.flags & TemporaryDivertable)
report.flags |= TemporaryDiverted;
if (info.flags & PersistentlyDivertable)
report.flags |= PersistentlyDiverted;
if (info.additionalFlags & RawXY)
report.flags |= RawXYDiverted;
return report;
}
void ReprogControls::setControlReporting(uint16_t cid, ControlInfo info) {
// This function does not exist pre-v4 and cannot be emulated, ignore.
(void) cid;
(void) info; // Suppress unused warnings
}
std::set<uint16_t> ReprogControls::divertedButtonEvent(
const hidpp::Report& report) {
assert(report.function() == DivertedButtonEvent);
std::set<uint16_t> buttons;
uint8_t cids = std::distance(report.paramBegin(), report.paramEnd()) / 2;
for (uint8_t i = 0; i < cids; i++) {
uint16_t cid = report.paramBegin()[2 * i + 1];
cid |= report.paramBegin()[2 * i] << 8;
if (cid)
buttons.insert(cid);
else
break;
}
return buttons;
}
ReprogControls::Move ReprogControls::divertedRawXYEvent(const hidpp::Report
& report) {
assert(report.function() == DivertedRawXYEvent);
Move move{};
move.x = (int16_t) ((report.paramBegin()[0] << 8) | report.paramBegin()[1]);
move.y = (int16_t) ((report.paramBegin()[2] << 8) | report.paramBegin()[3]);
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(uint16_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);
}

View File

@ -1,175 +1,175 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H
#define LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp/Report.h>
#include <map>
#include <set>
#include <memory>
namespace logid::backend::hidpp20 {
class ReprogControls : public Feature {
public:
enum Function {
GetControlCount = 0,
GetControlInfo = 1,
GetControlReporting = 2,
SetControlReporting = 3
};
enum Event {
DivertedButtonEvent = 0,
DivertedRawXYEvent = 1
};
struct ControlInfo {
uint16_t controlID;
uint16_t taskID;
uint8_t flags;
uint8_t position; // F key position, 0 if not an Fx key
uint8_t group;
uint8_t groupMask;
uint8_t additionalFlags;
};
enum ControlInfoFlags : uint8_t {
MouseButton = 1, //Mouse button
FKey = 1 << 1, //Fx key
Hotkey = 1 << 2,
FnToggle = 1 << 3,
ReprogHint = 1 << 4,
TemporaryDivertable = 1 << 5,
PersistentlyDivertable = 1 << 6,
Virtual = 1 << 7
};
enum ControlInfoAdditionalFlags : uint8_t {
RawXY = 1 << 0
};
enum ControlReportingFlags : uint8_t {
TemporaryDiverted = 1 << 0,
ChangeTemporaryDivert = 1 << 1,
PersistentlyDiverted = 1 << 2,
ChangePersistentDivert [[maybe_unused]] = 1 << 3,
RawXYDiverted = 1 << 4,
ChangeRawXYDivert = 1 << 5
};
struct Move {
int16_t x;
int16_t y;
};
static const uint16_t ID = FeatureID::REPROG_CONTROLS;
[[nodiscard]] uint16_t getID() override { return ID; }
[[nodiscard]] virtual bool supportsRawXY() { return false; }
explicit ReprogControls(Device* dev);
[[nodiscard]] virtual uint8_t getControlCount();
[[nodiscard]] virtual ControlInfo getControlInfo(uint8_t cid);
[[nodiscard]] virtual ControlInfo getControlIdInfo(uint16_t cid);
virtual void initCidMap();
[[nodiscard]] const std::map<uint16_t, ControlInfo>& getControls() const;
// Only controlId and flags will be set
[[maybe_unused]]
[[nodiscard]] virtual ControlInfo getControlReporting(uint16_t cid);
// Only controlId (for remap) and flags will be read
virtual void setControlReporting(uint16_t cid, ControlInfo info);
[[nodiscard]] static std::set<uint16_t> divertedButtonEvent(const hidpp::Report& report);
[[nodiscard]] static Move divertedRawXYEvent(const hidpp::Report& report);
[[nodiscard]] static std::shared_ptr<ReprogControls> autoVersion(Device* dev);
protected:
ReprogControls(Device* dev, uint16_t _id);
std::map<uint16_t, ControlInfo> _cids;
bool _cids_initialized = false;
std::mutex _cids_populating;
};
class ReprogControlsV2 : public ReprogControls {
public:
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V2;
[[nodiscard]] uint16_t getID() override { return ID; }
explicit ReprogControlsV2(Device* dev);
protected:
ReprogControlsV2(Device* dev, uint16_t _id);
};
class ReprogControlsV2_2 : public ReprogControlsV2 {
public:
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V2_2;
[[nodiscard]] uint16_t getID() override { return ID; }
explicit ReprogControlsV2_2(Device* dev);
protected:
ReprogControlsV2_2(Device* dev, uint16_t _id);
};
class ReprogControlsV3 : public ReprogControlsV2_2 {
public:
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V3;
[[nodiscard]] uint16_t getID() override { return ID; }
explicit ReprogControlsV3(Device* dev);
protected:
ReprogControlsV3(Device* dev, uint16_t _id);
};
class ReprogControlsV4 : public ReprogControlsV3 {
public:
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V4;
[[nodiscard]] uint16_t getID() final { return ID; }
[[nodiscard]] bool supportsRawXY() override { return true; }
[[nodiscard]] ControlInfo getControlReporting(uint16_t cid) override;
void setControlReporting(uint16_t cid, ControlInfo info) override;
explicit ReprogControlsV4(Device* dev);
protected:
[[maybe_unused]]
ReprogControlsV4(Device* dev, uint16_t _id);
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H
#define LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp/Report.h>
#include <map>
#include <set>
#include <memory>
namespace logid::backend::hidpp20 {
class ReprogControls : public Feature {
public:
enum Function {
GetControlCount = 0,
GetControlInfo = 1,
GetControlReporting = 2,
SetControlReporting = 3
};
enum Event {
DivertedButtonEvent = 0,
DivertedRawXYEvent = 1
};
struct ControlInfo {
uint16_t controlID;
uint16_t taskID;
uint8_t flags;
uint8_t position; // F key position, 0 if not an Fx key
uint8_t group;
uint8_t groupMask;
uint8_t additionalFlags;
};
enum ControlInfoFlags : uint8_t {
MouseButton = 1, //Mouse button
FKey = 1 << 1, //Fx key
Hotkey = 1 << 2,
FnToggle = 1 << 3,
ReprogHint = 1 << 4,
TemporaryDivertable = 1 << 5,
PersistentlyDivertable = 1 << 6,
Virtual = 1 << 7
};
enum ControlInfoAdditionalFlags : uint8_t {
RawXY = 1 << 0
};
enum ControlReportingFlags : uint8_t {
TemporaryDiverted = 1 << 0,
ChangeTemporaryDivert = 1 << 1,
PersistentlyDiverted = 1 << 2,
ChangePersistentDivert [[maybe_unused]] = 1 << 3,
RawXYDiverted = 1 << 4,
ChangeRawXYDivert = 1 << 5
};
struct Move {
int16_t x;
int16_t y;
};
static const uint16_t ID = FeatureID::REPROG_CONTROLS;
[[nodiscard]] uint16_t getID() override { return ID; }
[[nodiscard]] virtual bool supportsRawXY() { return false; }
explicit ReprogControls(Device* dev);
[[nodiscard]] virtual uint8_t getControlCount();
[[nodiscard]] virtual ControlInfo getControlInfo(uint8_t cid);
[[nodiscard]] virtual ControlInfo getControlIdInfo(uint16_t cid);
virtual void initCidMap();
[[nodiscard]] const std::map<uint16_t, ControlInfo>& getControls() const;
// Only controlId and flags will be set
[[maybe_unused]]
[[nodiscard]] virtual ControlInfo getControlReporting(uint16_t cid);
// Only controlId (for remap) and flags will be read
virtual void setControlReporting(uint16_t cid, ControlInfo info);
[[nodiscard]] static std::set<uint16_t> divertedButtonEvent(const hidpp::Report& report);
[[nodiscard]] static Move divertedRawXYEvent(const hidpp::Report& report);
[[nodiscard]] static std::shared_ptr<ReprogControls> autoVersion(Device* dev);
protected:
ReprogControls(Device* dev, uint16_t _id);
std::map<uint16_t, ControlInfo> _cids;
bool _cids_initialized = false;
std::mutex _cids_populating;
};
class ReprogControlsV2 : public ReprogControls {
public:
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V2;
[[nodiscard]] uint16_t getID() override { return ID; }
explicit ReprogControlsV2(Device* dev);
protected:
ReprogControlsV2(Device* dev, uint16_t _id);
};
class ReprogControlsV2_2 : public ReprogControlsV2 {
public:
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V2_2;
[[nodiscard]] uint16_t getID() override { return ID; }
explicit ReprogControlsV2_2(Device* dev);
protected:
ReprogControlsV2_2(Device* dev, uint16_t _id);
};
class ReprogControlsV3 : public ReprogControlsV2_2 {
public:
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V3;
[[nodiscard]] uint16_t getID() override { return ID; }
explicit ReprogControlsV3(Device* dev);
protected:
ReprogControlsV3(Device* dev, uint16_t _id);
};
class ReprogControlsV4 : public ReprogControlsV3 {
public:
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V4;
[[nodiscard]] uint16_t getID() final { return ID; }
[[nodiscard]] bool supportsRawXY() override { return true; }
[[nodiscard]] ControlInfo getControlReporting(uint16_t cid) override;
void setControlReporting(uint16_t cid, ControlInfo info) override;
explicit ReprogControlsV4(Device* dev);
protected:
[[maybe_unused]]
ReprogControlsV4(Device* dev, uint16_t _id);
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H

View File

@ -1,39 +1,39 @@
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/Reset.h>
using namespace logid::backend::hidpp20;
Reset::Reset(Device* device) : Feature(device, ID) {
}
uint16_t Reset::getProfile() {
std::vector<uint8_t> params(0);
auto results = callFunction(GetProfile, params);
uint16_t profile = results[1];
profile |= (results[0] << 8);
return profile;
}
void Reset::reset(uint16_t profile) {
std::vector<uint8_t> params(2);
params[0] = (profile >> 8) & 0xff;
params[1] = profile & 0xff;
callFunction(ResetToProfile, params);
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/Reset.h>
using namespace logid::backend::hidpp20;
Reset::Reset(Device* device) : Feature(device, ID) {
}
uint16_t Reset::getProfile() {
std::vector<uint8_t> params(0);
auto results = callFunction(GetProfile, params);
uint16_t profile = results[1];
profile |= (results[0] << 8);
return profile;
}
void Reset::reset(uint16_t profile) {
std::vector<uint8_t> params(2);
params[0] = (profile >> 8) & 0xff;
params[1] = profile & 0xff;
callFunction(ResetToProfile, params);
}

View File

@ -1,44 +1,44 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_RESET_H
#define LOGID_BACKEND_HIDPP20_FEATURE_RESET_H
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
namespace logid::backend::hidpp20 {
class Reset : public Feature {
public:
static const uint16_t ID = FeatureID::RESET;
[[nodiscard]] uint16_t getID() final { return ID; }
enum Function : uint8_t {
GetProfile = 0,
ResetToProfile = 1
};
explicit Reset(Device* device);
uint16_t getProfile();
void reset(uint16_t profile = 0);
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_RESET_H
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_RESET_H
#define LOGID_BACKEND_HIDPP20_FEATURE_RESET_H
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
namespace logid::backend::hidpp20 {
class Reset : public Feature {
public:
static const uint16_t ID = FeatureID::RESET;
[[nodiscard]] uint16_t getID() final { return ID; }
enum Function : uint8_t {
GetProfile = 0,
ResetToProfile = 1
};
explicit Reset(Device* device);
uint16_t getProfile();
void reset(uint16_t profile = 0);
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_RESET_H

View File

@ -1,81 +1,81 @@
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/Root.h>
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/Error.h>
using namespace logid::backend::hidpp20;
namespace {
std::vector<uint8_t> _genGetFeatureParams(uint16_t feature_id) {
std::vector<uint8_t> params(2);
params[0] = feature_id & 0xff;
params[1] = (feature_id >> 8) & 0xff;
return params;
}
feature_info _genGetFeatureInfo(uint16_t feature_id,
std::vector<uint8_t> response) {
feature_info info{};
info.feature_id = response[0];
if (!info.feature_id)
throw UnsupportedFeature(feature_id);
info.hidden = response[1] & Root::FeatureFlag::Hidden;
info.obsolete = response[1] & Root::FeatureFlag::Obsolete;
info.internal = response[1] & Root::FeatureFlag::Internal;
return info;
}
}
Root::Root(hidpp::Device* dev) : EssentialFeature(dev, ID) {
}
feature_info Root::getFeature(uint16_t feature_id) {
auto params = _genGetFeatureParams(feature_id);
try {
auto response = this->callFunction(Root::Function::GetFeature, params);
return _genGetFeatureInfo(feature_id, response);
} catch (Error& e) {
if (e.code() == Error::InvalidFeatureIndex)
throw UnsupportedFeature(feature_id);
throw e;
}
}
uint8_t Root::ping(uint8_t byte) {
std::vector<uint8_t> params(3);
params[2] = byte;
auto response = this->callFunction(Root::Function::Ping, params);
return response[2];
}
std::tuple<uint8_t, uint8_t> Root::getVersion() {
std::vector<uint8_t> params(0);
auto response = this->callFunction(Root::Function::Ping, params);
if (response[0] == 0x11)
return std::make_tuple(1, 0);
return std::make_tuple(response[0], response[1]);
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/Root.h>
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/Error.h>
using namespace logid::backend::hidpp20;
namespace {
std::vector<uint8_t> _genGetFeatureParams(uint16_t feature_id) {
std::vector<uint8_t> params(2);
params[0] = feature_id & 0xff;
params[1] = (feature_id >> 8) & 0xff;
return params;
}
feature_info _genGetFeatureInfo(uint16_t feature_id,
std::vector<uint8_t> response) {
feature_info info{};
info.feature_id = response[0];
if (!info.feature_id)
throw UnsupportedFeature(feature_id);
info.hidden = response[1] & Root::FeatureFlag::Hidden;
info.obsolete = response[1] & Root::FeatureFlag::Obsolete;
info.internal = response[1] & Root::FeatureFlag::Internal;
return info;
}
}
Root::Root(hidpp::Device* dev) : EssentialFeature(dev, ID) {
}
feature_info Root::getFeature(uint16_t feature_id) {
auto params = _genGetFeatureParams(feature_id);
try {
auto response = this->callFunction(Root::Function::GetFeature, params);
return _genGetFeatureInfo(feature_id, response);
} catch (Error& e) {
if (e.code() == Error::InvalidFeatureIndex)
throw UnsupportedFeature(feature_id);
throw e;
}
}
uint8_t Root::ping(uint8_t byte) {
std::vector<uint8_t> params(3);
params[2] = byte;
auto response = this->callFunction(Root::Function::Ping, params);
return response[2];
}
std::tuple<uint8_t, uint8_t> Root::getVersion() {
std::vector<uint8_t> params(0);
auto response = this->callFunction(Root::Function::Ping, params);
if (response[0] == 0x11)
return std::make_tuple(1, 0);
return std::make_tuple(response[0], response[1]);
}

View File

@ -1,54 +1,54 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_ROOT_H
#define LOGID_BACKEND_HIDPP20_FEATURE_ROOT_H
#include <backend/hidpp20/EssentialFeature.h>
#include <backend/hidpp20/feature_defs.h>
namespace logid::backend::hidpp20 {
class Root : public EssentialFeature {
public:
static const uint16_t ID = FeatureID::ROOT;
uint16_t getID() final { return ID; }
explicit Root(hidpp::Device* device);
enum Function : uint8_t {
GetFeature = 0,
Ping = 1
};
feature_info getFeature(uint16_t feature_id);
uint8_t ping(uint8_t byte);
std::tuple<uint8_t, uint8_t> getVersion();
enum FeatureFlag : uint8_t {
Obsolete = 1 << 7,
Hidden = 1 << 6,
Internal = 1 << 5
};
};
}
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_ROOT_H
#define LOGID_BACKEND_HIDPP20_FEATURE_ROOT_H
#include <backend/hidpp20/EssentialFeature.h>
#include <backend/hidpp20/feature_defs.h>
namespace logid::backend::hidpp20 {
class Root : public EssentialFeature {
public:
static const uint16_t ID = FeatureID::ROOT;
uint16_t getID() final { return ID; }
explicit Root(hidpp::Device* device);
enum Function : uint8_t {
GetFeature = 0,
Ping = 1
};
feature_info getFeature(uint16_t feature_id);
uint8_t ping(uint8_t byte);
std::tuple<uint8_t, uint8_t> getVersion();
enum FeatureFlag : uint8_t {
Obsolete = 1 << 7,
Hidden = 1 << 6,
Internal = 1 << 5
};
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_ROOT_H

View File

@ -1,125 +1,125 @@
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/SmartShift.h>
using namespace logid::backend::hidpp20;
SmartShift::SmartShift(Device* dev) : SmartShift(dev, ID) {
}
SmartShift::SmartShift(Device* dev, uint16_t feature_id) :
Feature(dev, feature_id) {
}
SmartShiftV2::SmartShiftV2(Device* dev) : SmartShift(dev, ID) {
}
template<typename T>
std::shared_ptr<T> make_smartshift(Device* dev) {
try {
return std::make_shared<T>(dev);
} catch (UnsupportedFeature& e) {
return {};
}
}
std::shared_ptr<SmartShift> SmartShift::autoVersion(Device* dev) {
if (auto v2 = make_smartshift<SmartShiftV2>(dev))
return v2;
return std::make_shared<SmartShift>(dev);
}
SmartShift::Status SmartShift::getStatus() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetStatus, params);
return {
.active = static_cast<bool>(response[0] - 1),
.autoDisengage = response[1],
.torque = 0,
.setActive = false,
.setAutoDisengage = false,
.setTorque = false
};
}
SmartShift::Defaults SmartShift::getDefaults() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetStatus, params);
return {
.autoDisengage = response[2],
.torque = 0,
.maxForce = 0,
};
}
void SmartShift::setStatus(Status status) {
std::vector<uint8_t> params(3);
if (status.setActive)
params[0] = status.active + 1;
if (status.setAutoDisengage)
params[1] = status.autoDisengage;
callFunction(SetStatus, params);
}
SmartShift::Defaults SmartShiftV2::getDefaults() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetCapabilities, params);
return {
.autoDisengage = response[1],
.torque = response[2],
.maxForce = response[3],
};
}
SmartShift::Status SmartShiftV2::getStatus() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetStatus, params);
return {
.active = static_cast<bool>(response[0] - 1),
.autoDisengage = response[1],
.torque = response[2],
.setActive = false, .setAutoDisengage = false, .setTorque = false,
};
}
void SmartShiftV2::setStatus(Status status) {
std::vector<uint8_t> params(3);
if (status.setActive)
params[0] = status.active + 1;
if (status.setAutoDisengage)
params[1] = status.autoDisengage;
if (status.setTorque)
params[2] = status.torque;
callFunction(SetStatus, params);
}
bool SmartShiftV2::supportsTorque() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetCapabilities, params);
return static_cast<bool>(response[0] & 1);
}
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/SmartShift.h>
using namespace logid::backend::hidpp20;
SmartShift::SmartShift(Device* dev) : SmartShift(dev, ID) {
}
SmartShift::SmartShift(Device* dev, uint16_t feature_id) :
Feature(dev, feature_id) {
}
SmartShiftV2::SmartShiftV2(Device* dev) : SmartShift(dev, ID) {
}
template<typename T>
std::shared_ptr<T> make_smartshift(Device* dev) {
try {
return std::make_shared<T>(dev);
} catch (UnsupportedFeature& e) {
return {};
}
}
std::shared_ptr<SmartShift> SmartShift::autoVersion(Device* dev) {
if (auto v2 = make_smartshift<SmartShiftV2>(dev))
return v2;
return std::make_shared<SmartShift>(dev);
}
SmartShift::Status SmartShift::getStatus() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetStatus, params);
return {
.active = static_cast<bool>(response[0] - 1),
.autoDisengage = response[1],
.torque = 0,
.setActive = false,
.setAutoDisengage = false,
.setTorque = false
};
}
SmartShift::Defaults SmartShift::getDefaults() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetStatus, params);
return {
.autoDisengage = response[2],
.torque = 0,
.maxForce = 0,
};
}
void SmartShift::setStatus(Status status) {
std::vector<uint8_t> params(3);
if (status.setActive)
params[0] = status.active + 1;
if (status.setAutoDisengage)
params[1] = status.autoDisengage;
callFunction(SetStatus, params);
}
SmartShift::Defaults SmartShiftV2::getDefaults() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetCapabilities, params);
return {
.autoDisengage = response[1],
.torque = response[2],
.maxForce = response[3],
};
}
SmartShift::Status SmartShiftV2::getStatus() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetStatus, params);
return {
.active = static_cast<bool>(response[0] - 1),
.autoDisengage = response[1],
.torque = response[2],
.setActive = false, .setAutoDisengage = false, .setTorque = false,
};
}
void SmartShiftV2::setStatus(Status status) {
std::vector<uint8_t> params(3);
if (status.setActive)
params[0] = status.active + 1;
if (status.setAutoDisengage)
params[1] = status.autoDisengage;
if (status.setTorque)
params[2] = status.torque;
callFunction(SetStatus, params);
}
bool SmartShiftV2::supportsTorque() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetCapabilities, params);
return static_cast<bool>(response[0] & 1);
}

View File

@ -1,90 +1,90 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_SMARTSHIFT_H
#define LOGID_BACKEND_HIDPP20_FEATURE_SMARTSHIFT_H
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp20/Feature.h>
#include <memory>
namespace logid::backend::hidpp20 {
class SmartShift : public Feature {
public:
static const uint16_t ID = FeatureID::SMART_SHIFT;
uint16_t getID() override { return ID; }
enum Function {
GetStatus = 0,
SetStatus = 1
};
explicit SmartShift(Device* dev);
struct Defaults {
uint8_t autoDisengage;
uint8_t torque;
uint8_t maxForce;
};
struct Status {
bool active;
uint8_t autoDisengage;
uint8_t torque;
bool setActive, setAutoDisengage, setTorque;
};
[[nodiscard]] virtual bool supportsTorque() { return false; }
[[nodiscard]] virtual Defaults getDefaults();
[[nodiscard]] virtual Status getStatus();
virtual void setStatus(Status status);
[[nodiscard]] static std::shared_ptr<SmartShift> autoVersion(Device* dev);
protected:
SmartShift(Device* dev, uint16_t feature_id);
};
class SmartShiftV2 : public SmartShift
{
public:
static const uint16_t ID = FeatureID::SMART_SHIFT_V2;
uint16_t getID() final { return ID; }
enum Function {
GetCapabilities = 0,
GetStatus = 1,
SetStatus = 2
};
explicit SmartShiftV2(Device* dev);
[[nodiscard]] bool supportsTorque() final;
[[nodiscard]] Defaults getDefaults() final;
[[nodiscard]] Status getStatus() final;
void setStatus(Status status) final;
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_SMARTSHIFT_H
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_SMARTSHIFT_H
#define LOGID_BACKEND_HIDPP20_FEATURE_SMARTSHIFT_H
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp20/Feature.h>
#include <memory>
namespace logid::backend::hidpp20 {
class SmartShift : public Feature {
public:
static const uint16_t ID = FeatureID::SMART_SHIFT;
uint16_t getID() override { return ID; }
enum Function {
GetStatus = 0,
SetStatus = 1
};
explicit SmartShift(Device* dev);
struct Defaults {
uint8_t autoDisengage;
uint8_t torque;
uint8_t maxForce;
};
struct Status {
bool active;
uint8_t autoDisengage;
uint8_t torque;
bool setActive, setAutoDisengage, setTorque;
};
[[nodiscard]] virtual bool supportsTorque() { return false; }
[[nodiscard]] virtual Defaults getDefaults();
[[nodiscard]] virtual Status getStatus();
virtual void setStatus(Status status);
[[nodiscard]] static std::shared_ptr<SmartShift> autoVersion(Device* dev);
protected:
SmartShift(Device* dev, uint16_t feature_id);
};
class SmartShiftV2 : public SmartShift
{
public:
static const uint16_t ID = FeatureID::SMART_SHIFT_V2;
uint16_t getID() final { return ID; }
enum Function {
GetCapabilities = 0,
GetStatus = 1,
SetStatus = 2
};
explicit SmartShiftV2(Device* dev);
[[nodiscard]] bool supportsTorque() final;
[[nodiscard]] Defaults getDefaults() final;
[[nodiscard]] Status getStatus() final;
void setStatus(Status status) final;
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_SMARTSHIFT_H

View File

@ -1,79 +1,79 @@
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/ThumbWheel.h>
#include <cassert>
using namespace logid::backend::hidpp20;
ThumbWheel::ThumbWheel(Device* dev) : Feature(dev, ID) {
}
ThumbWheel::ThumbwheelInfo ThumbWheel::getInfo() {
std::vector<uint8_t> params(0), response;
ThumbwheelInfo info{};
response = callFunction(GetInfo, params);
info.nativeRes = response[1];
info.nativeRes |= (response[0] << 8);
info.divertedRes = response[3];
info.divertedRes |= (response[2] << 8);
info.defaultDirection = response[4] ? 1 : -1; /* 1 increment to the right */
info.capabilities = response[5];
info.timeElapsed = response[7];
info.timeElapsed |= response[6] << 8;
return info;
}
ThumbWheel::ThumbwheelStatus ThumbWheel::getStatus() {
std::vector<uint8_t> params(0), response;
ThumbwheelStatus status{};
response = callFunction(GetStatus, params);
status.diverted = response[0];
status.inverted = response[1] & 1;
status.touch = response[1] & (1 << 1);
status.proxy = response[1] & (1 << 2);
return status;
}
ThumbWheel::ThumbwheelStatus ThumbWheel::setStatus(bool divert, bool invert) {
std::vector<uint8_t> params(2), response;
ThumbwheelStatus status{};
params[0] = divert;
params[1] = invert;
response = callFunction(SetReporting, params);
status.diverted = response[0];
status.inverted = response[1] & 1;
return status;
}
ThumbWheel::ThumbwheelEvent ThumbWheel::thumbwheelEvent(const hidpp::Report& report) {
assert(report.function() == Event);
ThumbwheelEvent event{};
event.rotation = (int16_t) ((report.paramBegin()[0] << 8) | report.paramBegin()[1]);
event.timestamp = report.paramBegin()[3];
event.timestamp |= report.paramBegin()[2] << 8;
event.rotationStatus = static_cast<RotationStatus>(report.paramBegin()[4]);
event.flags = report.paramBegin()[5];
return event;
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/ThumbWheel.h>
#include <cassert>
using namespace logid::backend::hidpp20;
ThumbWheel::ThumbWheel(Device* dev) : Feature(dev, ID) {
}
ThumbWheel::ThumbwheelInfo ThumbWheel::getInfo() {
std::vector<uint8_t> params(0), response;
ThumbwheelInfo info{};
response = callFunction(GetInfo, params);
info.nativeRes = response[1];
info.nativeRes |= (response[0] << 8);
info.divertedRes = response[3];
info.divertedRes |= (response[2] << 8);
info.defaultDirection = response[4] ? 1 : -1; /* 1 increment to the right */
info.capabilities = response[5];
info.timeElapsed = response[7];
info.timeElapsed |= response[6] << 8;
return info;
}
ThumbWheel::ThumbwheelStatus ThumbWheel::getStatus() {
std::vector<uint8_t> params(0), response;
ThumbwheelStatus status{};
response = callFunction(GetStatus, params);
status.diverted = response[0];
status.inverted = response[1] & 1;
status.touch = response[1] & (1 << 1);
status.proxy = response[1] & (1 << 2);
return status;
}
ThumbWheel::ThumbwheelStatus ThumbWheel::setStatus(bool divert, bool invert) {
std::vector<uint8_t> params(2), response;
ThumbwheelStatus status{};
params[0] = divert;
params[1] = invert;
response = callFunction(SetReporting, params);
status.diverted = response[0];
status.inverted = response[1] & 1;
return status;
}
ThumbWheel::ThumbwheelEvent ThumbWheel::thumbwheelEvent(const hidpp::Report& report) {
assert(report.function() == Event);
ThumbwheelEvent event{};
event.rotation = (int16_t) ((report.paramBegin()[0] << 8) | report.paramBegin()[1]);
event.timestamp = report.paramBegin()[3];
event.timestamp |= report.paramBegin()[2] << 8;
event.rotationStatus = static_cast<RotationStatus>(report.paramBegin()[4]);
event.flags = report.paramBegin()[5];
return event;
}

View File

@ -1,90 +1,90 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_THUMBWHEEL_H
#define LOGID_BACKEND_HIDPP20_FEATURE_THUMBWHEEL_H
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp/Report.h>
namespace logid::backend::hidpp20 {
class ThumbWheel : public Feature {
public:
static const uint16_t ID = FeatureID::THUMB_WHEEL;
uint16_t getID() final { return ID; }
enum Function {
GetInfo = 0,
GetStatus = 1,
SetReporting = 2
};
enum Event {
Event = 0 /* Catch-all event */
};
explicit ThumbWheel(Device* dev);
enum Capabilities : uint8_t {
Timestamp = 1,
Touch = 1 << 1,
Proxy = 1 << 2,
SingleTap = 1 << 3
};
struct ThumbwheelInfo {
uint16_t nativeRes;
uint16_t divertedRes;
int8_t defaultDirection;
uint8_t capabilities;
uint16_t timeElapsed;
};
struct ThumbwheelStatus {
bool diverted;
bool inverted;
bool touch;
bool proxy;
};
enum RotationStatus : uint8_t {
Inactive = 0,
Start = 1,
Active = 2,
Stop = 3
};
struct ThumbwheelEvent {
int16_t rotation;
uint16_t timestamp;
RotationStatus rotationStatus;
uint8_t flags;
};
[[nodiscard]] ThumbwheelInfo getInfo();
[[nodiscard]] ThumbwheelStatus getStatus();
ThumbwheelStatus setStatus(bool divert, bool invert);
[[nodiscard]] static ThumbwheelEvent thumbwheelEvent(const hidpp::Report& report);
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_THUMBWHEEL_H
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_THUMBWHEEL_H
#define LOGID_BACKEND_HIDPP20_FEATURE_THUMBWHEEL_H
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp/Report.h>
namespace logid::backend::hidpp20 {
class ThumbWheel : public Feature {
public:
static const uint16_t ID = FeatureID::THUMB_WHEEL;
uint16_t getID() final { return ID; }
enum Function {
GetInfo = 0,
GetStatus = 1,
SetReporting = 2
};
enum Event {
Event = 0 /* Catch-all event */
};
explicit ThumbWheel(Device* dev);
enum Capabilities : uint8_t {
Timestamp = 1,
Touch = 1 << 1,
Proxy = 1 << 2,
SingleTap = 1 << 3
};
struct ThumbwheelInfo {
uint16_t nativeRes;
uint16_t divertedRes;
int8_t defaultDirection;
uint8_t capabilities;
uint16_t timeElapsed;
};
struct ThumbwheelStatus {
bool diverted;
bool inverted;
bool touch;
bool proxy;
};
enum RotationStatus : uint8_t {
Inactive = 0,
Start = 1,
Active = 2,
Stop = 3
};
struct ThumbwheelEvent {
int16_t rotation;
uint16_t timestamp;
RotationStatus rotationStatus;
uint8_t flags;
};
[[nodiscard]] ThumbwheelInfo getInfo();
[[nodiscard]] ThumbwheelStatus getStatus();
ThumbwheelStatus setStatus(bool divert, bool invert);
[[nodiscard]] static ThumbwheelEvent thumbwheelEvent(const hidpp::Report& report);
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_THUMBWHEEL_H

View File

@ -1,35 +1,35 @@
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/WirelessDeviceStatus.h>
#include <cassert>
using namespace logid::backend::hidpp20;
WirelessDeviceStatus::WirelessDeviceStatus(Device* dev) : Feature(dev, ID) {
}
WirelessDeviceStatus::Status WirelessDeviceStatus::statusBroadcastEvent(
const hidpp::Report& report) {
assert(report.function() == StatusBroadcast);
Status status = {};
auto params = report.paramBegin();
status.reconnection = params[0];
status.reconfNeeded = params[1];
status.powerSwitch = params[2];
return status;
/*
* Copyright 2019-2023 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 <backend/hidpp20/features/WirelessDeviceStatus.h>
#include <cassert>
using namespace logid::backend::hidpp20;
WirelessDeviceStatus::WirelessDeviceStatus(Device* dev) : Feature(dev, ID) {
}
WirelessDeviceStatus::Status WirelessDeviceStatus::statusBroadcastEvent(
const hidpp::Report& report) {
assert(report.function() == StatusBroadcast);
Status status = {};
auto params = report.paramBegin();
status.reconnection = params[0];
status.reconfNeeded = params[1];
status.powerSwitch = params[2];
return status;
}

View File

@ -1,48 +1,48 @@
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_WIRELESSDEVICESTATUS_H
#define LOGID_BACKEND_HIDPP20_FEATURE_WIRELESSDEVICESTATUS_H
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp/Report.h>
namespace logid::backend::hidpp20 {
class WirelessDeviceStatus : public Feature {
public:
static constexpr uint16_t ID = FeatureID::WIRELESS_DEVICE_STATUS;
[[nodiscard]] uint16_t getID() final { return ID; }
explicit WirelessDeviceStatus(Device* dev);
enum Event : uint8_t {
StatusBroadcast = 0
};
struct Status {
bool reconnection;
bool reconfNeeded;
bool powerSwitch;
};
static Status statusBroadcastEvent(const hidpp::Report& report);
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_WIRELESSDEVICESTATUS_H
/*
* Copyright 2019-2023 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_BACKEND_HIDPP20_FEATURE_WIRELESSDEVICESTATUS_H
#define LOGID_BACKEND_HIDPP20_FEATURE_WIRELESSDEVICESTATUS_H
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp/Report.h>
namespace logid::backend::hidpp20 {
class WirelessDeviceStatus : public Feature {
public:
static constexpr uint16_t ID = FeatureID::WIRELESS_DEVICE_STATUS;
[[nodiscard]] uint16_t getID() final { return ID; }
explicit WirelessDeviceStatus(Device* dev);
enum Event : uint8_t {
StatusBroadcast = 0
};
struct Status {
bool reconnection;
bool reconfNeeded;
bool powerSwitch;
};
static Status statusBroadcastEvent(const hidpp::Report& report);
};
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_WIRELESSDEVICESTATUS_H

View File

@ -1,194 +1,194 @@
/*
* Copyright 2019-2023 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 <backend/raw/DeviceMonitor.h>
#include <backend/raw/IOMonitor.h>
#include <backend/raw/RawDevice.h>
#include <backend/hidpp/Device.h>
#include <backend/Error.h>
#include <util/task.h>
#include <util/log.h>
#include <system_error>
extern "C"
{
#include <libudev.h>
}
using namespace logid;
using namespace logid::backend::raw;
DeviceMonitor::DeviceMonitor() : _io_monitor(std::make_shared<IOMonitor>()),
_ready(false) {
int ret;
_udev_context = udev_new();
if (!_udev_context)
throw std::runtime_error("udev_new failed");
_udev_monitor = udev_monitor_new_from_netlink(_udev_context,
"udev");
if (!_udev_monitor) {
if (_udev_context)
udev_unref(_udev_context);
throw std::runtime_error("udev_monitor_new_from_netlink failed");
}
ret = udev_monitor_filter_add_match_subsystem_devtype(
_udev_monitor, "hidraw", nullptr);
if (0 != ret) {
if (_udev_monitor)
udev_monitor_unref(_udev_monitor);
if (_udev_context)
udev_unref(_udev_context);
throw std::system_error(
-ret, std::system_category(),
"udev_monitor_filter_add_match_subsystem_devtype");
}
ret = udev_monitor_enable_receiving(_udev_monitor);
if (0 != ret) {
if (_udev_monitor)
udev_monitor_unref(_udev_monitor);
if (_udev_context)
udev_unref(_udev_context);
throw std::system_error(-ret, std::system_category(),
"udev_monitor_enable_receiving");
}
_fd = udev_monitor_get_fd(_udev_monitor);
}
DeviceMonitor::~DeviceMonitor() {
if (_ready)
_io_monitor->remove(_fd);
if (_udev_monitor)
udev_monitor_unref(_udev_monitor);
if (_udev_context)
udev_unref(_udev_context);
}
void DeviceMonitor::ready() {
if (_ready)
return;
_ready = true;
_io_monitor->add(_fd, {
[self_weak = _self]() {
if (auto self = self_weak.lock()) {
struct udev_device* device = udev_monitor_receive_device(self->_udev_monitor);
std::string action = udev_device_get_action(device);
std::string dev_node = udev_device_get_devnode(device);
if (action == "add")
run_task([self_weak, dev_node]() {
if (auto self = self_weak.lock())
self->_addHandler(dev_node);
});
else if (action == "remove")
run_task([self_weak, dev_node]() {
if (auto self = self_weak.lock())
self->_removeHandler(dev_node);
});
udev_device_unref(device);
}
},
[]() {
throw std::runtime_error("udev hangup");
},
[]() {
throw std::runtime_error("udev error");
}
});
}
void DeviceMonitor::enumerate() {
int ret;
struct udev_enumerate* udev_enum = udev_enumerate_new(_udev_context);
ret = udev_enumerate_add_match_subsystem(udev_enum, "hidraw");
if (0 != ret)
throw std::system_error(-ret, std::system_category(),
"udev_enumerate_add_match_subsystem");
ret = udev_enumerate_scan_devices(udev_enum);
if (0 != ret)
throw std::system_error(-ret, std::system_category(),
"udev_enumerate_scan_devices");
struct udev_list_entry* udev_enum_entry;
udev_list_entry_foreach(udev_enum_entry,
udev_enumerate_get_list_entry(udev_enum)) {
const char* name = udev_list_entry_get_name(udev_enum_entry);
struct udev_device* device = udev_device_new_from_syspath(_udev_context, name);
if (device) {
const char* dev_node_cstr = udev_device_get_devnode(device);
if (dev_node_cstr) {
const std::string dev_node {dev_node_cstr};
udev_device_unref(device);
_addHandler(dev_node);
} else {
udev_device_unref(device);
}
}
}
udev_enumerate_unref(udev_enum);
}
void DeviceMonitor::_addHandler(const std::string& device, int tries) {
try {
auto supported_reports = backend::hidpp::getSupportedReports(
RawDevice::getReportDescriptor(device));
if (supported_reports)
addDevice(device);
else
logPrintf(DEBUG, "Unsupported device %s ignored", device.c_str());
} catch (backend::DeviceNotReady& e) {
if (tries == max_tries) {
logPrintf(WARN, "Failed to add device %s after %d tries. Treating as failure.",
device.c_str(), max_tries);
} else {
/* Do exponential backoff for 2^tries * backoff ms. */
std::chrono::milliseconds wait((1 << tries) * ready_backoff);
logPrintf(DEBUG, "Failed to add device %s on try %d, backing off for %dms",
device.c_str(), tries + 1, wait.count());
run_task_after([self_weak = _self, device, tries]() {
if (auto self = self_weak.lock())
self->_addHandler(device, tries + 1);
}, wait);
}
} catch (std::exception& e) {
logPrintf(WARN, "Error adding device %s: %s", device.c_str(), e.what());
}
}
void DeviceMonitor::_removeHandler(const std::string& device) {
try {
removeDevice(device);
} catch (std::exception& e) {
logPrintf(WARN, "Error removing device %s: %s",
device.c_str(), e.what());
}
}
std::shared_ptr<IOMonitor> DeviceMonitor::ioMonitor() const {
return _io_monitor;
}
/*
* Copyright 2019-2023 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 <backend/raw/DeviceMonitor.h>
#include <backend/raw/IOMonitor.h>
#include <backend/raw/RawDevice.h>
#include <backend/hidpp/Device.h>
#include <backend/Error.h>
#include <util/task.h>
#include <util/log.h>
#include <system_error>
extern "C"
{
#include <libudev.h>
}
using namespace logid;
using namespace logid::backend::raw;
DeviceMonitor::DeviceMonitor() : _io_monitor(std::make_shared<IOMonitor>()),
_ready(false) {
int ret;
_udev_context = udev_new();
if (!_udev_context)
throw std::runtime_error("udev_new failed");
_udev_monitor = udev_monitor_new_from_netlink(_udev_context,
"udev");
if (!_udev_monitor) {
if (_udev_context)
udev_unref(_udev_context);
throw std::runtime_error("udev_monitor_new_from_netlink failed");
}
ret = udev_monitor_filter_add_match_subsystem_devtype(
_udev_monitor, "hidraw", nullptr);
if (0 != ret) {
if (_udev_monitor)
udev_monitor_unref(_udev_monitor);
if (_udev_context)
udev_unref(_udev_context);
throw std::system_error(
-ret, std::system_category(),
"udev_monitor_filter_add_match_subsystem_devtype");
}
ret = udev_monitor_enable_receiving(_udev_monitor);
if (0 != ret) {
if (_udev_monitor)
udev_monitor_unref(_udev_monitor);
if (_udev_context)
udev_unref(_udev_context);
throw std::system_error(-ret, std::system_category(),
"udev_monitor_enable_receiving");
}
_fd = udev_monitor_get_fd(_udev_monitor);
}
DeviceMonitor::~DeviceMonitor() {
if (_ready)
_io_monitor->remove(_fd);
if (_udev_monitor)
udev_monitor_unref(_udev_monitor);
if (_udev_context)
udev_unref(_udev_context);
}
void DeviceMonitor::ready() {
if (_ready)
return;
_ready = true;
_io_monitor->add(_fd, {
[self_weak = _self]() {
if (auto self = self_weak.lock()) {
struct udev_device* device = udev_monitor_receive_device(self->_udev_monitor);
std::string action = udev_device_get_action(device);
std::string dev_node = udev_device_get_devnode(device);
if (action == "add")
run_task([self_weak, dev_node]() {
if (auto self = self_weak.lock())
self->_addHandler(dev_node);
});
else if (action == "remove")
run_task([self_weak, dev_node]() {
if (auto self = self_weak.lock())
self->_removeHandler(dev_node);
});
udev_device_unref(device);
}
},
[]() {
throw std::runtime_error("udev hangup");
},
[]() {
throw std::runtime_error("udev error");
}
});
}
void DeviceMonitor::enumerate() {
int ret;
struct udev_enumerate* udev_enum = udev_enumerate_new(_udev_context);
ret = udev_enumerate_add_match_subsystem(udev_enum, "hidraw");
if (0 != ret)
throw std::system_error(-ret, std::system_category(),
"udev_enumerate_add_match_subsystem");
ret = udev_enumerate_scan_devices(udev_enum);
if (0 != ret)
throw std::system_error(-ret, std::system_category(),
"udev_enumerate_scan_devices");
struct udev_list_entry* udev_enum_entry;
udev_list_entry_foreach(udev_enum_entry,
udev_enumerate_get_list_entry(udev_enum)) {
const char* name = udev_list_entry_get_name(udev_enum_entry);
struct udev_device* device = udev_device_new_from_syspath(_udev_context, name);
if (device) {
const char* dev_node_cstr = udev_device_get_devnode(device);
if (dev_node_cstr) {
const std::string dev_node {dev_node_cstr};
udev_device_unref(device);
_addHandler(dev_node);
} else {
udev_device_unref(device);
}
}
}
udev_enumerate_unref(udev_enum);
}
void DeviceMonitor::_addHandler(const std::string& device, int tries) {
try {
auto supported_reports = backend::hidpp::getSupportedReports(
RawDevice::getReportDescriptor(device));
if (supported_reports)
addDevice(device);
else
logPrintf(DEBUG, "Unsupported device %s ignored", device.c_str());
} catch (backend::DeviceNotReady& e) {
if (tries == max_tries) {
logPrintf(WARN, "Failed to add device %s after %d tries. Treating as failure.",
device.c_str(), max_tries);
} else {
/* Do exponential backoff for 2^tries * backoff ms. */
std::chrono::milliseconds wait((1 << tries) * ready_backoff);
logPrintf(DEBUG, "Failed to add device %s on try %d, backing off for %dms",
device.c_str(), tries + 1, wait.count());
run_task_after([self_weak = _self, device, tries]() {
if (auto self = self_weak.lock())
self->_addHandler(device, tries + 1);
}, wait);
}
} catch (std::exception& e) {
logPrintf(WARN, "Error adding device %s: %s", device.c_str(), e.what());
}
}
void DeviceMonitor::_removeHandler(const std::string& device) {
try {
removeDevice(device);
} catch (std::exception& e) {
logPrintf(WARN, "Error removing device %s: %s",
device.c_str(), e.what());
}
}
std::shared_ptr<IOMonitor> DeviceMonitor::ioMonitor() const {
return _io_monitor;
}

View File

@ -1,101 +1,101 @@
/*
* Copyright 2019-2023 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_BACKEND_RAW_DEVICEMONITOR_H
#define LOGID_BACKEND_RAW_DEVICEMONITOR_H
#include <string>
#include <mutex>
#include <atomic>
#include <memory>
extern "C"
{
struct udev;
struct udev_monitor;
}
namespace logid::backend::raw {
class IOMonitor;
static constexpr int max_tries = 5;
static constexpr int ready_backoff = 500;
template<typename T>
class _deviceMonitorWrapper : public T {
friend class Device;
public:
template<typename... Args>
explicit _deviceMonitorWrapper(Args... args) : T(std::forward<Args>(args)...) {}
template<typename... Args>
static std::shared_ptr<T> make(Args... args) {
return std::make_shared<_deviceMonitorWrapper>(std::forward<Args>(args)...);
}
};
class DeviceMonitor {
public:
virtual ~DeviceMonitor();
void enumerate();
[[nodiscard]] std::shared_ptr<IOMonitor> ioMonitor() const;
template<typename T, typename... Args>
static std::shared_ptr<T> make(Args... args) {
auto device_monitor = _deviceMonitorWrapper<T>::make(std::forward<Args>(args)...);
device_monitor->_self = device_monitor;
device_monitor->ready();
return device_monitor;
}
protected:
DeviceMonitor();
// This should be run once the derived class is ready
void ready();
virtual void addDevice(std::string device) = 0;
virtual void removeDevice(std::string device) = 0;
template<typename T>
[[nodiscard]] std::weak_ptr<T> self() const {
return std::dynamic_pointer_cast<T>(_self.lock());
}
private:
void _addHandler(const std::string& device, int tries = 0);
void _removeHandler(const std::string& device);
std::shared_ptr<IOMonitor> _io_monitor;
struct udev* _udev_context;
struct udev_monitor* _udev_monitor;
int _fd;
bool _ready;
std::weak_ptr<DeviceMonitor> _self;
};
}
/*
* Copyright 2019-2023 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_BACKEND_RAW_DEVICEMONITOR_H
#define LOGID_BACKEND_RAW_DEVICEMONITOR_H
#include <string>
#include <mutex>
#include <atomic>
#include <memory>
extern "C"
{
struct udev;
struct udev_monitor;
}
namespace logid::backend::raw {
class IOMonitor;
static constexpr int max_tries = 5;
static constexpr int ready_backoff = 500;
template<typename T>
class _deviceMonitorWrapper : public T {
friend class Device;
public:
template<typename... Args>
explicit _deviceMonitorWrapper(Args... args) : T(std::forward<Args>(args)...) {}
template<typename... Args>
static std::shared_ptr<T> make(Args... args) {
return std::make_shared<_deviceMonitorWrapper>(std::forward<Args>(args)...);
}
};
class DeviceMonitor {
public:
virtual ~DeviceMonitor();
void enumerate();
[[nodiscard]] std::shared_ptr<IOMonitor> ioMonitor() const;
template<typename T, typename... Args>
static std::shared_ptr<T> make(Args... args) {
auto device_monitor = _deviceMonitorWrapper<T>::make(std::forward<Args>(args)...);
device_monitor->_self = device_monitor;
device_monitor->ready();
return device_monitor;
}
protected:
DeviceMonitor();
// This should be run once the derived class is ready
void ready();
virtual void addDevice(std::string device) = 0;
virtual void removeDevice(std::string device) = 0;
template<typename T>
[[nodiscard]] std::weak_ptr<T> self() const {
return std::dynamic_pointer_cast<T>(_self.lock());
}
private:
void _addHandler(const std::string& device, int tries = 0);
void _removeHandler(const std::string& device);
std::shared_ptr<IOMonitor> _io_monitor;
struct udev* _udev_context;
struct udev_monitor* _udev_monitor;
int _fd;
bool _ready;
std::weak_ptr<DeviceMonitor> _self;
};
}
#endif //LOGID_BACKEND_RAW_DEVICEMONITOR_H

View File

@ -1,38 +1,38 @@
/*
* Copyright 2019-2023 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_BACKEND_RAW_DEFS_H
#define LOGID_BACKEND_RAW_DEFS_H
#include <functional>
#include <cstdint>
#include <vector>
namespace logid::backend::raw {
struct RawEventHandler {
std::function<bool(const std::vector<uint8_t>&)> condition;
std::function<void(const std::vector<uint8_t>&)> callback;
RawEventHandler(std::function<bool(const std::vector<uint8_t>&)> cond,
std::function<void(const std::vector<uint8_t>&)> call) :
condition(std::move(cond)), callback(std::move(call)) {
}
};
}
/*
* Copyright 2019-2023 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_BACKEND_RAW_DEFS_H
#define LOGID_BACKEND_RAW_DEFS_H
#include <functional>
#include <cstdint>
#include <vector>
namespace logid::backend::raw {
struct RawEventHandler {
std::function<bool(const std::vector<uint8_t>&)> condition;
std::function<void(const std::vector<uint8_t>&)> callback;
RawEventHandler(std::function<bool(const std::vector<uint8_t>&)> cond,
std::function<void(const std::vector<uint8_t>&)> call) :
condition(std::move(cond)), callback(std::move(call)) {
}
};
}
#endif //LOGID_BACKEND_RAW_DEFS_H

View File

@ -1,167 +1,167 @@
/*
* Copyright 2019-2023 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 <backend/raw/IOMonitor.h>
#include <util/log.h>
#include <optional>
extern "C"
{
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>
}
using namespace logid::backend::raw;
IOHandler::IOHandler(std::function<void()> r,
std::function<void()> hup,
std::function<void()> err) :
read(std::move(r)),
hangup(std::move(hup)),
error(std::move(err)) {
}
IOMonitor::IOMonitor() : _epoll_fd(epoll_create1(0)),
_event_fd(eventfd(0, EFD_NONBLOCK)) {
if (_epoll_fd < 0) {
if (_event_fd >= 0)
close(_event_fd);
throw std::runtime_error("failed to create epoll fd");
}
if (_event_fd < 0) {
close(_epoll_fd);
throw std::runtime_error("failed to create event fd");
}
struct epoll_event event{};
event.events = EPOLLIN;
event.data.fd = _event_fd;
if (::epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, _event_fd, &event)) {
throw std::system_error(errno, std::generic_category());
}
_fds.emplace(_event_fd, nullptr);
_io_thread = std::make_unique<std::thread>([this]() {
_listen();
});
}
IOMonitor::~IOMonitor() noexcept {
_stop();
if (_event_fd >= 0)
::close(_event_fd);
if (_epoll_fd >= 0)
::close(_epoll_fd);
}
void IOMonitor::_listen() {
std::unique_lock lock(_run_mutex);
std::vector<struct epoll_event> events;
if (_is_running)
throw std::runtime_error("IOMonitor double run");
_is_running = true;
while (_is_running) {
if (events.size() != _fds.size())
events.resize(_fds.size());
int ev_count = ::epoll_wait(_epoll_fd, events.data(), (int) events.size(), -1);
for (int i = 0; i < ev_count; ++i) {
std::shared_ptr<IOHandler> handler;
if (events[i].data.fd == _event_fd) {
if (events[i].events & EPOLLIN) {
lock.unlock();
/* Wait until done yielding */
const std::lock_guard yield_lock(_yield_mutex);
uint64_t event;
while (-1 != ::eventfd_read(_event_fd, &event)) { }
lock.lock();
}
} else {
try {
handler = _fds.at(events[i].data.fd);
} catch (std::out_of_range& e) {
continue;
}
lock.unlock();
try {
if (events[i].events & EPOLLIN)
handler->read();
if (events[i].events & EPOLLHUP)
handler->hangup();
if (events[i].events & EPOLLERR)
handler->error();
} catch (std::exception& e) {
logPrintf(ERROR, "Unhandled I/O handler error: %s", e.what());
}
lock.lock();
}
}
}
}
void IOMonitor::_stop() noexcept {
_is_running = false;
_yield();
_io_thread->join();
}
std::unique_lock<std::mutex> IOMonitor::_yield() noexcept {
/* Prevent listener thread from grabbing lock during yielding */
std::unique_lock yield_lock(_yield_mutex);
std::unique_lock run_lock(_run_mutex, std::try_to_lock);
if (!run_lock.owns_lock()) {
::eventfd_write(_event_fd, 1);
run_lock = std::unique_lock<std::mutex>(_run_mutex);
}
return run_lock;
}
void IOMonitor::add(int fd, IOHandler handler) {
const auto lock = _yield();
struct epoll_event event{};
event.events = EPOLLIN | EPOLLHUP | EPOLLERR;
event.data.fd = fd;
if (!_fds.contains(fd)) {
if (::epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, fd, &event))
throw std::system_error(errno, std::generic_category());
_fds.emplace(fd, std::make_shared<IOHandler>(std::move(handler)));
} else {
throw std::runtime_error("duplicate io fd");
}
}
void IOMonitor::remove(int fd) noexcept {
const auto lock = _yield();
::epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, fd, nullptr);
_fds.erase(fd);
/*
* Copyright 2019-2023 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 <backend/raw/IOMonitor.h>
#include <util/log.h>
#include <optional>
extern "C"
{
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>
}
using namespace logid::backend::raw;
IOHandler::IOHandler(std::function<void()> r,
std::function<void()> hup,
std::function<void()> err) :
read(std::move(r)),
hangup(std::move(hup)),
error(std::move(err)) {
}
IOMonitor::IOMonitor() : _epoll_fd(epoll_create1(0)),
_event_fd(eventfd(0, EFD_NONBLOCK)) {
if (_epoll_fd < 0) {
if (_event_fd >= 0)
close(_event_fd);
throw std::runtime_error("failed to create epoll fd");
}
if (_event_fd < 0) {
close(_epoll_fd);
throw std::runtime_error("failed to create event fd");
}
struct epoll_event event{};
event.events = EPOLLIN;
event.data.fd = _event_fd;
if (::epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, _event_fd, &event)) {
throw std::system_error(errno, std::generic_category());
}
_fds.emplace(_event_fd, nullptr);
_io_thread = std::make_unique<std::thread>([this]() {
_listen();
});
}
IOMonitor::~IOMonitor() noexcept {
_stop();
if (_event_fd >= 0)
::close(_event_fd);
if (_epoll_fd >= 0)
::close(_epoll_fd);
}
void IOMonitor::_listen() {
std::unique_lock lock(_run_mutex);
std::vector<struct epoll_event> events;
if (_is_running)
throw std::runtime_error("IOMonitor double run");
_is_running = true;
while (_is_running) {
if (events.size() != _fds.size())
events.resize(_fds.size());
int ev_count = ::epoll_wait(_epoll_fd, events.data(), (int) events.size(), -1);
for (int i = 0; i < ev_count; ++i) {
std::shared_ptr<IOHandler> handler;
if (events[i].data.fd == _event_fd) {
if (events[i].events & EPOLLIN) {
lock.unlock();
/* Wait until done yielding */
const std::lock_guard yield_lock(_yield_mutex);
uint64_t event;
while (-1 != ::eventfd_read(_event_fd, &event)) { }
lock.lock();
}
} else {
try {
handler = _fds.at(events[i].data.fd);
} catch (std::out_of_range& e) {
continue;
}
lock.unlock();
try {
if (events[i].events & EPOLLIN)
handler->read();
if (events[i].events & EPOLLHUP)
handler->hangup();
if (events[i].events & EPOLLERR)
handler->error();
} catch (std::exception& e) {
logPrintf(ERROR, "Unhandled I/O handler error: %s", e.what());
}
lock.lock();
}
}
}
}
void IOMonitor::_stop() noexcept {
_is_running = false;
_yield();
_io_thread->join();
}
std::unique_lock<std::mutex> IOMonitor::_yield() noexcept {
/* Prevent listener thread from grabbing lock during yielding */
std::unique_lock yield_lock(_yield_mutex);
std::unique_lock run_lock(_run_mutex, std::try_to_lock);
if (!run_lock.owns_lock()) {
::eventfd_write(_event_fd, 1);
run_lock = std::unique_lock<std::mutex>(_run_mutex);
}
return run_lock;
}
void IOMonitor::add(int fd, IOHandler handler) {
const auto lock = _yield();
struct epoll_event event{};
event.events = EPOLLIN | EPOLLHUP | EPOLLERR;
event.data.fd = fd;
if (!_fds.contains(fd)) {
if (::epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, fd, &event))
throw std::system_error(errno, std::generic_category());
_fds.emplace(fd, std::make_shared<IOHandler>(std::move(handler)));
} else {
throw std::runtime_error("duplicate io fd");
}
}
void IOMonitor::remove(int fd) noexcept {
const auto lock = _yield();
::epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, fd, nullptr);
_fds.erase(fd);
}

View File

@ -1,75 +1,75 @@
/*
* Copyright 2019-2023 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_BACKEND_RAW_IOMONITOR_H
#define LOGID_BACKEND_RAW_IOMONITOR_H
#include <atomic>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <condition_variable>
#include <thread>
namespace logid::backend::raw {
struct IOHandler {
std::function<void()> read;
std::function<void()> hangup;
std::function<void()> error;
IOHandler(std::function<void()> r,
std::function<void()> hup,
std::function<void()> err);
};
class IOMonitor {
public:
IOMonitor();
IOMonitor(IOMonitor&&) = delete;
IOMonitor(const IOMonitor&) = delete;
IOMonitor& operator=(IOMonitor&&) = delete;
IOMonitor& operator=(const IOMonitor&) = delete;
~IOMonitor() noexcept;
void add(int fd, IOHandler handler);
void remove(int fd) noexcept;
private:
void _listen(); // This is a blocking call
void _stop() noexcept;
std::unique_lock<std::mutex> _yield() noexcept;
std::unique_ptr<std::thread> _io_thread;
std::mutex _run_mutex;
std::mutex _yield_mutex;
std::map<int, std::shared_ptr<IOHandler>> _fds;
std::atomic_bool _is_running;
const int _epoll_fd;
const int _event_fd;
};
}
#endif //LOGID_BACKEND_RAW_IOMONITOR_H
/*
* Copyright 2019-2023 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_BACKEND_RAW_IOMONITOR_H
#define LOGID_BACKEND_RAW_IOMONITOR_H
#include <atomic>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <condition_variable>
#include <thread>
namespace logid::backend::raw {
struct IOHandler {
std::function<void()> read;
std::function<void()> hangup;
std::function<void()> error;
IOHandler(std::function<void()> r,
std::function<void()> hup,
std::function<void()> err);
};
class IOMonitor {
public:
IOMonitor();
IOMonitor(IOMonitor&&) = delete;
IOMonitor(const IOMonitor&) = delete;
IOMonitor& operator=(IOMonitor&&) = delete;
IOMonitor& operator=(const IOMonitor&) = delete;
~IOMonitor() noexcept;
void add(int fd, IOHandler handler);
void remove(int fd) noexcept;
private:
void _listen(); // This is a blocking call
void _stop() noexcept;
std::unique_lock<std::mutex> _yield() noexcept;
std::unique_ptr<std::thread> _io_thread;
std::mutex _run_mutex;
std::mutex _yield_mutex;
std::map<int, std::shared_ptr<IOHandler>> _fds;
std::atomic_bool _is_running;
const int _epoll_fd;
const int _event_fd;
};
}
#endif //LOGID_BACKEND_RAW_IOMONITOR_H

View File

@ -1,248 +1,248 @@
/*
* Copyright 2019-2023 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 <backend/raw/RawDevice.h>
#include <backend/raw/DeviceMonitor.h>
#include <backend/raw/IOMonitor.h>
#include <util/log.h>
#include <string>
#include <system_error>
#include <utility>
#include <regex>
extern "C"
{
#include <cassert>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/hidraw.h>
#include <linux/input.h>
}
using namespace logid::backend::raw;
using namespace logid::backend;
using namespace std::chrono;
static constexpr int max_write_tries = 8;
static const std::regex virtual_path_regex(R"~((.*\/)(.*:)([0-9]+))~");
int get_fd(const std::string& path) {
int fd = ::open(path.c_str(), O_RDWR | O_NONBLOCK);
if (fd == -1)
throw std::system_error(errno, std::system_category(),
"RawDevice open failed");
return fd;
}
RawDevice::dev_info get_dev_info(int fd) {
hidraw_devinfo dev_info{};
if (-1 == ::ioctl(fd, HIDIOCGRAWINFO, &dev_info)) {
int err = errno;
::close(fd);
throw std::system_error(err, std::system_category(),
"RawDevice HIDIOCGRAWINFO failed");
}
RawDevice::BusType type = RawDevice::OtherBus;
switch (dev_info.bustype) {
case BUS_USB:
type = RawDevice::USB;
break;
case BUS_BLUETOOTH:
type = RawDevice::Bluetooth;
break;
default:;
}
return {
.vid = dev_info.vendor,
.pid = dev_info.product,
.bus_type = type
};
}
std::string get_phys(int fd) {
ssize_t len;
char buf[256];
if (-1 == (len = ::ioctl(fd, HIDIOCGRAWPHYS(sizeof(buf)), buf))) {
int err = errno;
::close(fd);
throw std::system_error(err, std::system_category(),
"RawDevice HIDIOCGRAWPHYS failed");
}
return {buf, static_cast<size_t>(len) - 1};
}
std::string get_name(int fd) {
ssize_t len;
char name_buf[256];
if (-1 == (len = ::ioctl(fd, HIDIOCGRAWNAME(sizeof(name_buf)), name_buf))) {
int err = errno;
::close(fd);
throw std::system_error(err, std::system_category(),
"RawDevice HIDIOCGRAWNAME failed");
}
return {name_buf, static_cast<size_t>(len) - 1};
}
RawDevice::RawDevice(std::string path, const std::shared_ptr<DeviceMonitor>& monitor) :
_valid(true), _path(std::move(path)), _fd(get_fd(_path)),
_dev_info(get_dev_info(_fd)), _name(get_name(_fd)),
_report_desc(getReportDescriptor(_fd)), _io_monitor(monitor->ioMonitor()),
_event_handlers(std::make_shared<EventHandlerList<RawDevice>>()) {
if (busType() == USB) {
auto phys = get_phys(_fd);
_sub_device = std::regex_match(phys, virtual_path_regex);
}
}
void RawDevice::_ready() {
_io_monitor->add(_fd, {
[self_weak = _self]() {
if (auto self = self_weak.lock())
self->_readReports();
},
[self_weak = _self]() {
if (auto self = self_weak.lock())
self->_valid = false;
},
[self_weak = _self]() {
if (auto self = self_weak.lock())
self->_valid = false;
}
});
}
RawDevice::~RawDevice() noexcept {
_io_monitor->remove(_fd);
::close(_fd);
}
const std::string& RawDevice::rawPath() const {
return _path;
}
const std::string& RawDevice::name() const {
return _name;
}
[[maybe_unused]]
int16_t RawDevice::vendorId() const {
return _dev_info.vid;
}
int16_t RawDevice::productId() const {
return _dev_info.pid;
}
RawDevice::BusType RawDevice::busType() const {
return _dev_info.bus_type;
}
bool RawDevice::isSubDevice() const {
return _sub_device;
}
std::vector<uint8_t> RawDevice::getReportDescriptor(const std::string& path) {
int fd = ::open(path.c_str(), O_RDWR | O_NONBLOCK);
if (fd == -1)
throw std::system_error(errno, std::system_category(),
"open failed");
auto report_desc = getReportDescriptor(fd);
::close(fd);
return report_desc;
}
std::vector<uint8_t> RawDevice::getReportDescriptor(int fd) {
hidraw_report_descriptor report_desc{};
if (-1 == ::ioctl(fd, HIDIOCGRDESCSIZE, &report_desc.size)) {
int err = errno;
::close(fd);
throw std::system_error(err, std::system_category(),
"RawDevice HIDIOCGRDESCSIZE failed");
}
if (-1 == ::ioctl(fd, HIDIOCGRDESC, &report_desc)) {
int err = errno;
::close(fd);
throw std::system_error(err, std::system_category(),
"RawDevice HIDIOCGRDESC failed");
}
return {report_desc.value, report_desc.value + report_desc.size};
}
const std::vector<uint8_t>& RawDevice::reportDescriptor() const {
return _report_desc;
}
void RawDevice::sendReport(const std::vector<uint8_t>& report) {
if (!_valid) {
// We could throw an error here, but this will likely be closed soon.
return;
}
if (logid::global_loglevel <= LogLevel::RAWREPORT) {
printf("[RAWREPORT] %s OUT: ", _path.c_str());
for (auto& i: report)
printf("%02x ", i);
printf("\n");
}
for (int i = 0; i < max_write_tries && write(_fd, report.data(), report.size()) == -1; ++i) {
auto err = errno;
if (err != EPIPE)
throw std::system_error(err, std::system_category(),
"sendReport write failed");
}
}
EventHandlerLock<RawDevice> RawDevice::addEventHandler(RawEventHandler handler) {
return {_event_handlers, _event_handlers->add(std::forward<RawEventHandler>(handler))};
}
void RawDevice::_readReports() {
uint8_t buf[max_data_length];
ssize_t len;
while (-1 != (len = ::read(_fd, buf, max_data_length))) {
assert(len <= max_data_length);
std::vector<uint8_t> report(buf, buf + len);
if (logid::global_loglevel <= LogLevel::RAWREPORT) {
printf("[RAWREPORT] %s IN: ", _path.c_str());
for (auto& i: report)
printf("%02x ", i);
printf("\n");
}
_handleEvent(report);
}
}
void RawDevice::_handleEvent(const std::vector<uint8_t>& report) {
_event_handlers->run_all(report);
}
/*
* Copyright 2019-2023 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 <backend/raw/RawDevice.h>
#include <backend/raw/DeviceMonitor.h>
#include <backend/raw/IOMonitor.h>
#include <util/log.h>
#include <string>
#include <system_error>
#include <utility>
#include <regex>
extern "C"
{
#include <cassert>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/hidraw.h>
#include <linux/input.h>
}
using namespace logid::backend::raw;
using namespace logid::backend;
using namespace std::chrono;
static constexpr int max_write_tries = 8;
static const std::regex virtual_path_regex(R"~((.*\/)(.*:)([0-9]+))~");
int get_fd(const std::string& path) {
int fd = ::open(path.c_str(), O_RDWR | O_NONBLOCK);
if (fd == -1)
throw std::system_error(errno, std::system_category(),
"RawDevice open failed");
return fd;
}
RawDevice::dev_info get_dev_info(int fd) {
hidraw_devinfo dev_info{};
if (-1 == ::ioctl(fd, HIDIOCGRAWINFO, &dev_info)) {
int err = errno;
::close(fd);
throw std::system_error(err, std::system_category(),
"RawDevice HIDIOCGRAWINFO failed");
}
RawDevice::BusType type = RawDevice::OtherBus;
switch (dev_info.bustype) {
case BUS_USB:
type = RawDevice::USB;
break;
case BUS_BLUETOOTH:
type = RawDevice::Bluetooth;
break;
default:;
}
return {
.vid = dev_info.vendor,
.pid = dev_info.product,
.bus_type = type
};
}
std::string get_phys(int fd) {
ssize_t len;
char buf[256];
if (-1 == (len = ::ioctl(fd, HIDIOCGRAWPHYS(sizeof(buf)), buf))) {
int err = errno;
::close(fd);
throw std::system_error(err, std::system_category(),
"RawDevice HIDIOCGRAWPHYS failed");
}
return {buf, static_cast<size_t>(len) - 1};
}
std::string get_name(int fd) {
ssize_t len;
char name_buf[256];
if (-1 == (len = ::ioctl(fd, HIDIOCGRAWNAME(sizeof(name_buf)), name_buf))) {
int err = errno;
::close(fd);
throw std::system_error(err, std::system_category(),
"RawDevice HIDIOCGRAWNAME failed");
}
return {name_buf, static_cast<size_t>(len) - 1};
}
RawDevice::RawDevice(std::string path, const std::shared_ptr<DeviceMonitor>& monitor) :
_valid(true), _path(std::move(path)), _fd(get_fd(_path)),
_dev_info(get_dev_info(_fd)), _name(get_name(_fd)),
_report_desc(getReportDescriptor(_fd)), _io_monitor(monitor->ioMonitor()),
_event_handlers(std::make_shared<EventHandlerList<RawDevice>>()) {
if (busType() == USB) {
auto phys = get_phys(_fd);
_sub_device = std::regex_match(phys, virtual_path_regex);
}
}
void RawDevice::_ready() {
_io_monitor->add(_fd, {
[self_weak = _self]() {
if (auto self = self_weak.lock())
self->_readReports();
},
[self_weak = _self]() {
if (auto self = self_weak.lock())
self->_valid = false;
},
[self_weak = _self]() {
if (auto self = self_weak.lock())
self->_valid = false;
}
});
}
RawDevice::~RawDevice() noexcept {
_io_monitor->remove(_fd);
::close(_fd);
}
const std::string& RawDevice::rawPath() const {
return _path;
}
const std::string& RawDevice::name() const {
return _name;
}
[[maybe_unused]]
int16_t RawDevice::vendorId() const {
return _dev_info.vid;
}
int16_t RawDevice::productId() const {
return _dev_info.pid;
}
RawDevice::BusType RawDevice::busType() const {
return _dev_info.bus_type;
}
bool RawDevice::isSubDevice() const {
return _sub_device;
}
std::vector<uint8_t> RawDevice::getReportDescriptor(const std::string& path) {
int fd = ::open(path.c_str(), O_RDWR | O_NONBLOCK);
if (fd == -1)
throw std::system_error(errno, std::system_category(),
"open failed");
auto report_desc = getReportDescriptor(fd);
::close(fd);
return report_desc;
}
std::vector<uint8_t> RawDevice::getReportDescriptor(int fd) {
hidraw_report_descriptor report_desc{};
if (-1 == ::ioctl(fd, HIDIOCGRDESCSIZE, &report_desc.size)) {
int err = errno;
::close(fd);
throw std::system_error(err, std::system_category(),
"RawDevice HIDIOCGRDESCSIZE failed");
}
if (-1 == ::ioctl(fd, HIDIOCGRDESC, &report_desc)) {
int err = errno;
::close(fd);
throw std::system_error(err, std::system_category(),
"RawDevice HIDIOCGRDESC failed");
}
return {report_desc.value, report_desc.value + report_desc.size};
}
const std::vector<uint8_t>& RawDevice::reportDescriptor() const {
return _report_desc;
}
void RawDevice::sendReport(const std::vector<uint8_t>& report) {
if (!_valid) {
// We could throw an error here, but this will likely be closed soon.
return;
}
if (logid::global_loglevel <= LogLevel::RAWREPORT) {
printf("[RAWREPORT] %s OUT: ", _path.c_str());
for (auto& i: report)
printf("%02x ", i);
printf("\n");
}
for (int i = 0; i < max_write_tries && write(_fd, report.data(), report.size()) == -1; ++i) {
auto err = errno;
if (err != EPIPE)
throw std::system_error(err, std::system_category(),
"sendReport write failed");
}
}
EventHandlerLock<RawDevice> RawDevice::addEventHandler(RawEventHandler handler) {
return {_event_handlers, _event_handlers->add(std::forward<RawEventHandler>(handler))};
}
void RawDevice::_readReports() {
uint8_t buf[max_data_length];
ssize_t len;
while (-1 != (len = ::read(_fd, buf, max_data_length))) {
assert(len <= max_data_length);
std::vector<uint8_t> report(buf, buf + len);
if (logid::global_loglevel <= LogLevel::RAWREPORT) {
printf("[RAWREPORT] %s IN: ", _path.c_str());
for (auto& i: report)
printf("%02x ", i);
printf("\n");
}
_handleEvent(report);
}
}
void RawDevice::_handleEvent(const std::vector<uint8_t>& report) {
_event_handlers->run_all(report);
}

View File

@ -1,124 +1,124 @@
/*
* Copyright 2019-2023 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_BACKEND_RAWDEVICE_H
#define LOGID_BACKEND_RAWDEVICE_H
#include <backend/raw/EventHandler.h>
#include <backend/EventHandlerList.h>
#include <string>
#include <vector>
#include <shared_mutex>
#include <atomic>
#include <future>
#include <set>
#include <list>
namespace logid::backend::raw {
class DeviceMonitor;
class IOMonitor;
template <typename T>
class RawDeviceWrapper : public T {
public:
template <typename... Args>
RawDeviceWrapper(Args... args) : T(std::forward<Args>(args)...) { }
};
class RawDevice {
template <typename>
friend class RawDeviceWrapper;
public:
static constexpr int max_data_length = 32;
typedef RawEventHandler EventHandler;
enum BusType {
USB,
Bluetooth,
OtherBus
};
struct dev_info {
int16_t vid;
int16_t pid;
BusType bus_type;
};
template <typename... Args>
static std::shared_ptr<RawDevice> make(Args... args) {
auto raw_dev = std::make_shared<RawDeviceWrapper<RawDevice>>(
std::forward<Args>(args)...);
raw_dev->_self = raw_dev;
raw_dev->_ready();
return raw_dev;
}
~RawDevice() noexcept;
[[nodiscard]] const std::string& rawPath() const;
[[nodiscard]] const std::string& name() const;
[[maybe_unused]]
[[nodiscard]] int16_t vendorId() const;
[[nodiscard]] int16_t productId() const;
[[nodiscard]] BusType busType() const;
[[nodiscard]] bool isSubDevice() const;
static std::vector<uint8_t> getReportDescriptor(const std::string& path);
static std::vector<uint8_t> getReportDescriptor(int fd);
[[nodiscard]] const std::vector<uint8_t>& reportDescriptor() const;
void sendReport(const std::vector<uint8_t>& report);
[[nodiscard]] EventHandlerLock<RawDevice> addEventHandler(RawEventHandler handler);
private:
RawDevice(std::string path, const std::shared_ptr<DeviceMonitor>& monitor);
void _ready();
void _readReports();
std::atomic_bool _valid;
const std::string _path;
const int _fd;
const dev_info _dev_info;
const std::string _name;
const std::vector<uint8_t> _report_desc;
std::shared_ptr<IOMonitor> _io_monitor;
std::weak_ptr<RawDevice> _self;
bool _sub_device = false;
std::shared_ptr<EventHandlerList<RawDevice>> _event_handlers;
void _handleEvent(const std::vector<uint8_t>& report);
};
}
#endif //LOGID_BACKEND_RAWDEVICE_H
/*
* Copyright 2019-2023 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_BACKEND_RAWDEVICE_H
#define LOGID_BACKEND_RAWDEVICE_H
#include <backend/raw/EventHandler.h>
#include <backend/EventHandlerList.h>
#include <string>
#include <vector>
#include <shared_mutex>
#include <atomic>
#include <future>
#include <set>
#include <list>
namespace logid::backend::raw {
class DeviceMonitor;
class IOMonitor;
template <typename T>
class RawDeviceWrapper : public T {
public:
template <typename... Args>
RawDeviceWrapper(Args... args) : T(std::forward<Args>(args)...) { }
};
class RawDevice {
template <typename>
friend class RawDeviceWrapper;
public:
static constexpr int max_data_length = 32;
typedef RawEventHandler EventHandler;
enum BusType {
USB,
Bluetooth,
OtherBus
};
struct dev_info {
int16_t vid;
int16_t pid;
BusType bus_type;
};
template <typename... Args>
static std::shared_ptr<RawDevice> make(Args... args) {
auto raw_dev = std::make_shared<RawDeviceWrapper<RawDevice>>(
std::forward<Args>(args)...);
raw_dev->_self = raw_dev;
raw_dev->_ready();
return raw_dev;
}
~RawDevice() noexcept;
[[nodiscard]] const std::string& rawPath() const;
[[nodiscard]] const std::string& name() const;
[[maybe_unused]]
[[nodiscard]] int16_t vendorId() const;
[[nodiscard]] int16_t productId() const;
[[nodiscard]] BusType busType() const;
[[nodiscard]] bool isSubDevice() const;
static std::vector<uint8_t> getReportDescriptor(const std::string& path);
static std::vector<uint8_t> getReportDescriptor(int fd);
[[nodiscard]] const std::vector<uint8_t>& reportDescriptor() const;
void sendReport(const std::vector<uint8_t>& report);
[[nodiscard]] EventHandlerLock<RawDevice> addEventHandler(RawEventHandler handler);
private:
RawDevice(std::string path, const std::shared_ptr<DeviceMonitor>& monitor);
void _ready();
void _readReports();
std::atomic_bool _valid;
const std::string _path;
const int _fd;
const dev_info _dev_info;
const std::string _name;
const std::vector<uint8_t> _report_desc;
std::shared_ptr<IOMonitor> _io_monitor;
std::weak_ptr<RawDevice> _self;
bool _sub_device = false;
std::shared_ptr<EventHandlerList<RawDevice>> _event_handlers;
void _handleEvent(const std::vector<uint8_t>& report);
};
}
#endif //LOGID_BACKEND_RAWDEVICE_H

View File

@ -1,30 +1,30 @@
/*
* Copyright 2019-2023 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 <config/schema.h>
#include <util/log.h>
using namespace logid;
const char config::keys::name[] = "name";
const char config::keys::cid[] = "cid";
const char config::keys::direction[] = "direction";
void config::logError(const libconfig::Setting& setting, std::exception& e) {
logPrintf(WARN, "Error at line %d: %s", setting.getSourceLine(), e.what());
/*
* Copyright 2019-2023 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 <config/schema.h>
#include <util/log.h>
using namespace logid;
const char config::keys::name[] = "name";
const char config::keys::cid[] = "cid";
const char config::keys::direction[] = "direction";
void config::logError(const libconfig::Setting& setting, std::exception& e) {
logPrintf(WARN, "Error at line %d: %s", setting.getSourceLine(), e.what());
}

View File

@ -1,203 +1,203 @@
/*
* Copyright 2022 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_CONFIG_GROUP_H
#define LOGID_CONFIG_GROUP_H
#include <libconfig.h++>
#include <type_traits>
#include <functional>
#include <utility>
#include <algorithm>
namespace logid::config {
template<typename T>
void set(libconfig::Setting& parent,
const std::string& name,
const T& t);
template<typename T>
void set(libconfig::Setting& parent, const T& t);
template<typename T>
auto get(const libconfig::Setting& parent, const std::string& name);
template<typename T>
void append(libconfig::Setting& list, const T& t);
template<typename T, typename... M>
struct group_io {
};
template<typename T>
struct group_io<T> {
static void get(const libconfig::Setting&, T*,
const std::vector<std::string>&, const std::size_t) {}
static void set(libconfig::Setting&, const T*,
const std::vector<std::string>&, const std::size_t) {}
};
template<typename T, typename A, typename... M>
struct group_io<T, A, M...> {
static void get(const libconfig::Setting& s, T* t,
const std::vector<std::string>& names,
const std::size_t index, A T::* arg, M T::*... rest) {
auto& x = t->*(arg);
A old{x};
try {
x = config::get<A>(s, names[index]);
group_io<T, M...>::get(s, t, names, index + 1, rest...);
} catch (libconfig::SettingTypeException& e) {
x = old;
throw;
} catch (libconfig::SettingException& e) {
x = old;
throw libconfig::SettingTypeException(s);
}
}
static void set(libconfig::Setting& s, const T* t,
const std::vector<std::string>& names,
const std::size_t index, A T::* arg, M T::*... rest) {
config::set(s, names[index], t->*(arg));
group_io<T, M...>::set(s, t, names, index + 1, rest...);
}
};
template<typename Sign>
struct signed_group;
struct group {
private:
const std::vector<std::string> _names;
const std::function<void(const libconfig::Setting&, group*,
const std::vector<std::string>&)> _getter;
const std::function<void(libconfig::Setting&, const group*,
const std::vector<std::string>&)> _setter;
template<typename Sign>
friend
struct signed_group;
protected:
template<typename T, typename... M>
explicit group(const std::array<std::string, sizeof...(M)>& names,
M T::*... args) :
_names(names.begin(), names.end()),
_getter([args...](const libconfig::Setting& s, group* g,
const std::vector<std::string>& names) {
T* t = dynamic_cast<T*>(g);
group_io<T, M...>::get(s, t, names, 0, args...);
}),
_setter([args...](libconfig::Setting& s, const group* g,
const std::vector<std::string>& names) {
const T* t = dynamic_cast<const T*>(g);
group_io<T, M...>::set(s, t, names, 0, args...);
}) {
static_assert(std::is_base_of<group, T>::value);
}
group() : _getter([](const libconfig::Setting&, group*,
const std::vector<std::string>&) {}),
_setter([](libconfig::Setting&, const group*,
const std::vector<std::string>&) {}) {}
public:
group(const group& o) = default;
group(group&& o) noexcept = default;
group& operator=(const group&) {
return *this;
}
group& operator=(group&&) noexcept {
return *this;
}
virtual ~group() = default;
virtual void _save(libconfig::Setting& setting) const {
_setter(setting, this, _names);
}
virtual void _load(const libconfig::Setting& setting) {
_getter(setting, this, _names);
}
};
template<typename T>
struct normalize_signature {
static const T& make(const T& ret) { return ret; }
};
template<>
struct normalize_signature<std::string> {
static std::string make(const std::string& data) {
std::string ret = data;
std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower);
return ret;
}
};
template<typename Sign>
struct signed_group : public group {
private:
const std::string _sig_field;
const Sign _signature;
protected:
signed_group(std::string sign_name, const Sign& sign_data) :
group(), _sig_field(std::move(sign_name)),
_signature(normalize_signature<Sign>::make(sign_data)) {}
template<typename T, typename... M>
signed_group(
std::string sign_name, const Sign& sign_data,
const std::array<std::string, sizeof...(M)>& names,
M T::*... args) : group(names, args...),
_sig_field(std::move(sign_name)),
_signature(normalize_signature<Sign>::make(sign_data)) {}
public:
signed_group(const signed_group& o) = default;
signed_group(signed_group&& o) noexcept = default;
signed_group& operator=(const signed_group&) {
return *this;
}
signed_group& operator=(signed_group&&) noexcept {
return *this;
}
void _save(libconfig::Setting& setting) const override {
set(setting, _sig_field, _signature);
_setter(setting, this, _names);
}
void _load(const libconfig::Setting& setting) override {
if (normalize_signature<Sign>::make(get<Sign>(setting, _sig_field))
!= _signature)
throw libconfig::SettingTypeException(setting);
_getter(setting, this, _names);
}
};
}
#endif //LOGID_CONFIG_GROUP_H
/*
* Copyright 2022 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_CONFIG_GROUP_H
#define LOGID_CONFIG_GROUP_H
#include <libconfig.h++>
#include <type_traits>
#include <functional>
#include <utility>
#include <algorithm>
namespace logid::config {
template<typename T>
void set(libconfig::Setting& parent,
const std::string& name,
const T& t);
template<typename T>
void set(libconfig::Setting& parent, const T& t);
template<typename T>
auto get(const libconfig::Setting& parent, const std::string& name);
template<typename T>
void append(libconfig::Setting& list, const T& t);
template<typename T, typename... M>
struct group_io {
};
template<typename T>
struct group_io<T> {
static void get(const libconfig::Setting&, T*,
const std::vector<std::string>&, const std::size_t) {}
static void set(libconfig::Setting&, const T*,
const std::vector<std::string>&, const std::size_t) {}
};
template<typename T, typename A, typename... M>
struct group_io<T, A, M...> {
static void get(const libconfig::Setting& s, T* t,
const std::vector<std::string>& names,
const std::size_t index, A T::* arg, M T::*... rest) {
auto& x = t->*(arg);
A old{x};
try {
x = config::get<A>(s, names[index]);
group_io<T, M...>::get(s, t, names, index + 1, rest...);
} catch (libconfig::SettingTypeException& e) {
x = old;
throw;
} catch (libconfig::SettingException& e) {
x = old;
throw libconfig::SettingTypeException(s);
}
}
static void set(libconfig::Setting& s, const T* t,
const std::vector<std::string>& names,
const std::size_t index, A T::* arg, M T::*... rest) {
config::set(s, names[index], t->*(arg));
group_io<T, M...>::set(s, t, names, index + 1, rest...);
}
};
template<typename Sign>
struct signed_group;
struct group {
private:
const std::vector<std::string> _names;
const std::function<void(const libconfig::Setting&, group*,
const std::vector<std::string>&)> _getter;
const std::function<void(libconfig::Setting&, const group*,
const std::vector<std::string>&)> _setter;
template<typename Sign>
friend
struct signed_group;
protected:
template<typename T, typename... M>
explicit group(const std::array<std::string, sizeof...(M)>& names,
M T::*... args) :
_names(names.begin(), names.end()),
_getter([args...](const libconfig::Setting& s, group* g,
const std::vector<std::string>& names) {
T* t = dynamic_cast<T*>(g);
group_io<T, M...>::get(s, t, names, 0, args...);
}),
_setter([args...](libconfig::Setting& s, const group* g,
const std::vector<std::string>& names) {
const T* t = dynamic_cast<const T*>(g);
group_io<T, M...>::set(s, t, names, 0, args...);
}) {
static_assert(std::is_base_of<group, T>::value);
}
group() : _getter([](const libconfig::Setting&, group*,
const std::vector<std::string>&) {}),
_setter([](libconfig::Setting&, const group*,
const std::vector<std::string>&) {}) {}
public:
group(const group& o) = default;
group(group&& o) noexcept = default;
group& operator=(const group&) {
return *this;
}
group& operator=(group&&) noexcept {
return *this;
}
virtual ~group() = default;
virtual void _save(libconfig::Setting& setting) const {
_setter(setting, this, _names);
}
virtual void _load(const libconfig::Setting& setting) {
_getter(setting, this, _names);
}
};
template<typename T>
struct normalize_signature {
static const T& make(const T& ret) { return ret; }
};
template<>
struct normalize_signature<std::string> {
static std::string make(const std::string& data) {
std::string ret = data;
std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower);
return ret;
}
};
template<typename Sign>
struct signed_group : public group {
private:
const std::string _sig_field;
const Sign _signature;
protected:
signed_group(std::string sign_name, const Sign& sign_data) :
group(), _sig_field(std::move(sign_name)),
_signature(normalize_signature<Sign>::make(sign_data)) {}
template<typename T, typename... M>
signed_group(
std::string sign_name, const Sign& sign_data,
const std::array<std::string, sizeof...(M)>& names,
M T::*... args) : group(names, args...),
_sig_field(std::move(sign_name)),
_signature(normalize_signature<Sign>::make(sign_data)) {}
public:
signed_group(const signed_group& o) = default;
signed_group(signed_group&& o) noexcept = default;
signed_group& operator=(const signed_group&) {
return *this;
}
signed_group& operator=(signed_group&&) noexcept {
return *this;
}
void _save(libconfig::Setting& setting) const override {
set(setting, _sig_field, _signature);
_setter(setting, this, _names);
}
void _load(const libconfig::Setting& setting) override {
if (normalize_signature<Sign>::make(get<Sign>(setting, _sig_field))
!= _signature)
throw libconfig::SettingTypeException(setting);
_getter(setting, this, _names);
}
};
}
#endif //LOGID_CONFIG_GROUP_H

Some files were not shown because too many files have changed in this diff Show More