Merge branch 'master' into config-invalid-enforce

This commit is contained in:
pixl 2023-05-02 21:07:56 -04:00 committed by GitHub
commit cdca3e8312
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
145 changed files with 8644 additions and 6418 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "src/ipcgull"]
path = src/ipcgull
url = git@github.com:PixlOne/ipcgull.git

View File

@ -8,14 +8,16 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -Wall -Wextra")
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Git)
option(USE_USER_BUS "Uses user bus" OFF)
# Set version number
if(GIT_FOUND AND EXISTS ${CMAKE_SOURCE_DIR}/.git)
execute_process(COMMAND ${GIT_EXECUTABLE} describe
find_package(Git REQUIRED)
# Set version number and update submodules
if(EXISTS ${CMAKE_SOURCE_DIR}/.git)
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags
OUTPUT_VARIABLE LOGIOPS_VERSION
RESULT_VARIABLE LOGIOPS_VERSION_RET
ERROR_QUIET)
@ -25,10 +27,18 @@ if(GIT_FOUND AND EXISTS ${CMAKE_SOURCE_DIR}/.git)
OUTPUT_VARIABLE LOGIOPS_COMMIT_HASH)
set(LOGIOPS_VERSION git-${LOGIOPS_COMMIT_HASH})
endif()
execute_process(COMMAND ${GIT_EXECUTABLE}
submodule update --init --recursive)
string(REGEX REPLACE "\n$" "" LOGIOPS_VERSION ${LOGIOPS_VERSION})
elseif(EXISTS ${CMAKE_SOURCE_DIR}/version.txt)
file(READ version.txt LOGIOPS_VERSION)
string(REGEX REPLACE "\n$" "" LOGIOPS_VERSION ${LOGIOPS_VERSION})
IF(NOT EXISTS src/ipcgull)
message(FATAL_ERROR "Missing ipcgull submodule")
endif()
endif()
if(NOT LOGIOPS_VERSION)
@ -38,4 +48,9 @@ message("LogiOps Version Number: ${LOGIOPS_VERSION}")
add_definitions( -DLOGIOPS_VERSION="${LOGIOPS_VERSION}")
if(USE_USER_BUS)
add_definitions(-DUSE_USER_BUS)
endif()
add_subdirectory(src/ipcgull)
add_subdirectory(src/logid)

View File

@ -13,19 +13,20 @@ Default location for the configuration file is /etc/logid.cfg, but another can b
## Dependencies
This project requires a C++14 compiler, `cmake`, `libevdev`, `libudev`, and `libconfig`. For popular distributions, I've included commands below.
This project requires a C++17 compiler, `cmake`, `libevdev`, `libudev`, `glib`, and `libconfig`.
For popular distributions, I've included commands below.
**Arch Linux:** `sudo pacman -S cmake libevdev libconfig pkgconf`
**Arch Linux:** `sudo pacman -S cmake libevdev libconfig pkgconf glib2`
**Debian/Ubuntu:** `sudo apt install cmake libevdev-dev libudev-dev libconfig++-dev`
**Debian/Ubuntu:** `sudo apt install cmake libevdev-dev libudev-dev libconfig++-dev libglib2.0`
**Fedora:** `sudo dnf install cmake libevdev-devel systemd-devel libconfig-devel gcc-c++`
**Fedora:** `sudo dnf install cmake libevdev-devel systemd-devel libconfig-devel gcc-c++ glib2`
**Gentoo Linux:** `sudo emerge dev-libs/libconfig dev-libs/libevdev dev-util/cmake virtual/libudev`
**Gentoo Linux:** `sudo emerge dev-libs/libconfig dev-libs/libevdev dev-libs/glib dev-util/cmake virtual/libudev`
**Solus:** `sudo eopkg install libevdev-devel libconfig-devel libgudev-devel`
**Solus:** `sudo eopkg install libevdev-devel libconfig-devel libgudev-devel glib2`
**openSUSE:** `sudo zypper install cmake libevdev-devel systemd-devel libconfig-devel gcc-c++ libconfig++-devel libudev-devel`
**openSUSE:** `sudo zypper install cmake libevdev-devel systemd-devel libconfig-devel gcc-c++ libconfig++-devel libudev-devel glib2`
## Building
@ -34,12 +35,18 @@ To build this project, run:
```bash
mkdir build
cd build
cmake ..
cmake -DCMAKE_BUILD_TYPE=Release ..
make
```
To install, run `sudo make install` after building. You can set the daemon to start at boot by running `sudo systemctl enable logid` or `sudo systemctl enable --now logid` if you want to enable and start the daemon.
## Development
The project may only run as root, but for development purposes, you may find it
convenient to run as non-root on the user bus. You must compile with the CMake
flag `-DUSE_USER_BUS=ON` to use the user bus.
## Donate
This program is (and will always be) provided free of charge. If you would like to support the development of this project by donating, you can donate to my Ko-Fi below.

1
src/ipcgull Submodule

@ -0,0 +1 @@
Subproject commit 3b19a11e8adf5608c9facd6a4e93d8833cbc4e3f

View File

@ -1,7 +1,8 @@
cmake_minimum_required(VERSION 3.10)
project(logid)
set(CMAKE_CXX_STANDARD 14)
# C++20 is only needed for string literal template parameters
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../CMake")
@ -11,6 +12,7 @@ find_package(PkgConfig REQUIRED)
add_executable(logid
logid.cpp
util/log.cpp
config/util.cpp
InputDevice.cpp
DeviceManager.cpp
Device.cpp
@ -31,6 +33,7 @@ add_executable(logid
actions/ChangeDPI.cpp
actions/GestureAction.cpp
actions/ChangeHostAction.cpp
actions/ChangeProfile.cpp
actions/gesture/Gesture.cpp
actions/gesture/ReleaseGesture.cpp
actions/gesture/ThresholdGesture.cpp
@ -40,9 +43,9 @@ add_executable(logid
backend/Error.cpp
backend/raw/DeviceMonitor.cpp
backend/raw/RawDevice.cpp
backend/dj/Receiver.cpp
backend/dj/ReceiverMonitor.cpp
backend/dj/Error.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
@ -62,44 +65,58 @@ add_executable(logid
backend/hidpp20/features/ChangeHost.cpp
backend/hidpp20/features/WirelessDeviceStatus.cpp
backend/hidpp20/features/ThumbWheel.cpp
backend/dj/Report.cpp
util/mutex_queue.h
util/workqueue.cpp
util/worker_thread.cpp
util/task.cpp
util/thread.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(DBUS "dbus-1")
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})
HINTS ${PC_EVDEV_INCLUDE_DIRS} ${PC_EVDEV_INCLUDEDIR})
find_library(EVDEV_LIBRARY
NAMES evdev libevdev)
include_directories(${EVDEV_INCLUDE_DIR} ${LIBUDEV_INCLUDE_DIRECTORIES})
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})
${LIBUDEV_LIBRARIES} ipcgull)
install(TARGETS logid DESTINATION bin)
if (SYSTEMD_FOUND AND "${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}")
configure_file(logid.service.cmake ${CMAKE_BINARY_DIR}/logid.service)
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)
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()
endif ()
if(DBUS_FOUND)
# 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)
endif()

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -16,163 +16,56 @@
*
*/
#include <Configuration.h>
#include <util/log.h>
#include <utility>
#include <vector>
#include <map>
#include "Configuration.h"
#include "util/log.h"
#include <ipc_defs.h>
using namespace logid;
using namespace libconfig;
using namespace std::chrono;
using namespace logid::config;
Configuration::Configuration(const std::string& config_file)
{
Configuration::Configuration(std::string config_file) :
_config_file(std::move(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());
exit(EXIT_FAILURE);
} catch(const ParseException &e) {
_config.readFile(_config_file);
} 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());
exit(EXIT_FAILURE);
e.getLine(), e.getError());
throw;
}
const Setting &root = _config.getRoot();
Config::operator=(get<Config>(_config.getRoot()));
if (!devices.has_value())
devices.emplace();
}
Configuration::Configuration() {
devices.emplace();
}
void Configuration::save() {
config::set(_config.getRoot(), *this);
try {
auto& worker_count = root["workers"];
if(worker_count.getType() == Setting::TypeInt) {
_worker_threads = worker_count;
if(_worker_threads < 0)
logPrintf(WARN, "Line %d: workers cannot be negative.",
worker_count.getSourceLine());
} else {
logPrintf(WARN, "Line %d: workers must be an integer.",
worker_count.getSourceLine());
}
} catch(const SettingNotFoundException& e) {
// Ignore
}
try {
auto& timeout = root["io_timeout"];
if(timeout.isNumber()) {
if(timeout.getType() == Setting::TypeFloat)
_io_timeout = duration_cast<milliseconds>(
duration<double, std::milli>(timeout));
else
_io_timeout = milliseconds((int)timeout);
} else
logPrintf(WARN, "Line %d: io_timeout must be a number.",
timeout.getSourceLine());
} catch(const SettingNotFoundException& e) {
// Ignore
}
try {
auto& devices = root["devices"];
for(int i = 0; i < devices.getLength(); i++) {
const Setting& device = devices[i];
std::string name;
try {
if(!device.lookupValue("name", name)) {
logPrintf(WARN, "Line %d: 'name' must be a string, skipping"
" device.", device["name"].getSourceLine());
continue;
}
} catch(SettingNotFoundException &e) {
logPrintf(WARN, "Line %d: Missing name field, skipping device."
, device.getSourceLine());
continue;
}
_device_paths.insert({name, device.getPath()});
}
}
catch(const SettingNotFoundException &e) {
logPrintf(WARN, "No devices listed in config file.");
}
try {
auto& ignore = root.lookup("ignore");
if(ignore.getType() == libconfig::Setting::TypeInt) {
_ignore_list.insert((int)ignore);
} else if(ignore.isList() || ignore.isArray()) {
int ignore_count = ignore.getLength();
for(int i = 0; i < ignore_count; i++) {
if(ignore[i].getType() != libconfig::Setting::TypeInt) {
logPrintf(WARN, "Line %d: ignore must refer to device PIDs",
ignore[i].getSourceLine());
if(ignore.isArray())
break;
} else
_ignore_list.insert((int)ignore[i]);
}
}
} catch(const SettingNotFoundException& e) {
// May be called blacklist
try {
auto& ignore = root.lookup("blacklist");
if(ignore.getType() == libconfig::Setting::TypeInt) {
_ignore_list.insert((int)ignore);
} else if(ignore.isList() || ignore.isArray()) {
int ignore_count = ignore.getLength();
for(int i = 0; i < ignore_count; i++) {
if(ignore[i].getType() != libconfig::Setting::TypeInt) {
logPrintf(WARN, "Line %d: blacklist must refer to "
"device PIDs",
ignore[i].getSourceLine());
if(ignore.isArray())
break;
} else
_ignore_list.insert((int)ignore[i]);
}
}
} catch(const SettingNotFoundException& e) {
// Ignore
}
_config.writeFile(_config_file);
} 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;
}
}
libconfig::Setting& Configuration::getSetting(const std::string& path)
{
return _config.lookup(path);
}
std::string Configuration::getDevice(const std::string& name)
{
auto it = _device_paths.find(name);
if(it == _device_paths.end())
throw DeviceNotFound(name);
else
return it->second;
}
bool Configuration::isIgnored(uint16_t pid) const
{
return _ignore_list.find(pid) != _ignore_list.end();
}
Configuration::DeviceNotFound::DeviceNotFound(std::string name) :
_name (std::move(name))
{
}
const char * Configuration::DeviceNotFound::what() const noexcept
{
return _name.c_str();
}
int Configuration::workerCount() const
{
return _worker_threads;
}
std::chrono::milliseconds Configuration::ioTimeout() const
{
return _io_timeout;
}
Configuration::IPC::IPC(Configuration* config) :
ipcgull::interface(SERVICE_ROOT_NAME ".Config", {
{"Save", {config, &Configuration::save}}
}, {}, {}) {
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -19,46 +19,40 @@
#ifndef LOGID_CONFIGURATION_H
#define LOGID_CONFIGURATION_H
#include <map>
#include <config/schema.h>
#include <ipcgull/interface.h>
#include <libconfig.h++>
#include <memory>
#include <chrono>
#include <set>
#define LOGID_DEFAULT_IO_TIMEOUT std::chrono::seconds(2)
#define LOGID_DEFAULT_WORKER_COUNT 4
namespace logid {
namespace defaults {
static constexpr double io_timeout = 500;
static constexpr int workers = 4;
static constexpr int gesture_threshold = 50;
}
namespace logid
{
class Configuration
{
class Configuration : public config::Config {
public:
explicit Configuration(const std::string& config_file);
Configuration() = default;
libconfig::Setting& getSetting(const std::string& path);
std::string getDevice(const std::string& name);
bool isIgnored(uint16_t pid) const;
explicit Configuration(std::string config_file);
class DeviceNotFound : public std::exception
{
Configuration();
// Reloading is not safe, references will be invalidated
//void reload();
void save();
class IPC : public ipcgull::interface {
public:
explicit DeviceNotFound(std::string name);
const char* what() const noexcept override;
private:
std::string _name;
explicit IPC(Configuration* config);
};
std::chrono::milliseconds ioTimeout() const;
int workerCount() const;
private:
std::map<std::string, std::string> _device_paths;
std::set<uint16_t> _ignore_list;
std::chrono::milliseconds _io_timeout = LOGID_DEFAULT_IO_TIMEOUT;
int _worker_threads = LOGID_DEFAULT_WORKER_COUNT;
std::string _config_file;
libconfig::Config _config;
};
extern std::shared_ptr<Configuration> global_config;
}
#endif //LOGID_CONFIGURATION_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -16,47 +16,141 @@
*
*/
#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 "util/log.h"
#include "features/DPI.h"
#include "Device.h"
#include "Receiver.h"
#include "features/SmartShift.h"
#include "features/RemapButton.h"
#include "backend/hidpp20/features/Reset.h"
#include "features/HiresScroll.h"
#include "features/DeviceStatus.h"
#include "features/ThumbWheel.h"
#include <utility>
#include <ipc_defs.h>
using namespace logid;
using namespace logid::backend;
Device::Device(std::string path, backend::hidpp::DeviceIndex index) :
_hidpp20 (path, index), _path (std::move(path)), _index (index),
_config (global_config, this), _receiver (nullptr)
{
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, ""),
_receiver(nullptr),
_manager(manager),
_nickname(manager),
_ipc_node(manager->devicesNode()->make_child(_nickname)),
_awake(ipcgull::property_readable, true) {
_init();
}
Device::Device(const std::shared_ptr<backend::raw::RawDevice>& raw_device,
hidpp::DeviceIndex index) : _hidpp20(raw_device, index), _path
(raw_device->hidrawPath()), _index (index),
_config (global_config, this), _receiver (nullptr)
{
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, ""),
_receiver(nullptr),
_manager(manager),
_nickname(manager),
_ipc_node(manager->devicesNode()->make_child(_nickname)),
_awake(ipcgull::property_readable, true) {
_init();
}
Device::Device(Receiver* receiver, hidpp::DeviceIndex index) : _hidpp20
(receiver->rawReceiver(), index), _path (receiver->path()), _index (index),
_config (global_config, this), _receiver (receiver)
{
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, ""),
_receiver(receiver),
_manager(manager),
_nickname(manager),
_ipc_node(manager->devicesNode()->make_child(_nickname)),
_awake(ipcgull::property_readable, true) {
_init();
}
void Device::_init()
{
void Device::_init() {
logPrintf(INFO, "Device found: %s on %s:%d", name().c_str(),
hidpp20().devicePath().c_str(), _index);
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");
@ -68,85 +162,207 @@ void Device::_init()
_makeResetMechanism();
reset();
for(auto& feature: _features) {
for (auto& feature: _features) {
feature.second->configure();
feature.second->listen();
}
_hidpp20.listen();
}
std::string Device::name()
{
return _hidpp20.name();
std::string Device::name() {
return _hidpp20->name();
}
uint16_t Device::pid()
{
return _hidpp20.pid();
uint16_t Device::pid() {
return _hidpp20->pid();
}
void Device::sleep()
{
logPrintf(INFO, "%s:%d fell asleep.", _path.c_str(), _index);
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()
{
void Device::wakeup() {
std::lock_guard<std::mutex> lock(_state_lock);
logPrintf(INFO, "%s:%d woke up.", _path.c_str(), _index);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
reconfigure();
if (!_awake) {
_awake = true;
_ipc_interface->notifyStatus();
}
}
void Device::reconfigure() {
reset();
for(auto& feature: _features)
for (auto& feature: _features)
feature.second->configure();
}
void Device::reset()
{
if(_reset_mechanism)
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);
}
DeviceConfig& Device::config()
{
return _config;
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();
}
}
hidpp20::Device& Device::hidpp20()
{
return _hidpp20;
std::shared_ptr<ipcgull::node> Device::ipcNode() const {
return _ipc_node;
}
void Device::_makeResetMechanism()
{
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);
hidpp20::Reset reset(_hidpp20.get());
_reset_mechanism = std::make_unique<std::function<void()>>(
[dev=&this->_hidpp20]{
hidpp20::Reset reset(dev);
reset.reset(reset.getProfile());
[dev = _hidpp20] {
hidpp20::Reset reset(dev.get());
reset.reset(reset.getProfile());
});
} catch(hidpp20::UnsupportedFeature& e) {
} catch (hidpp20::UnsupportedFeature& e) {
// Reset unsupported, ignore.
}
}
DeviceConfig::DeviceConfig(const std::shared_ptr<Configuration>& config, Device*
device) : _device (device), _config (config)
{
try {
_root_setting = config->getDevice(device->name());
} catch(Configuration::DeviceNotFound& e) {
logPrintf(INFO, "Device %s not configured, using default config.",
device->name().c_str());
}
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) {
}
libconfig::Setting& DeviceConfig::getSetting(const std::string& path)
{
return _config->getSetting(_root_setting + '/' + path);
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -19,90 +19,169 @@
#ifndef LOGID_DEVICE_H
#define LOGID_DEVICE_H
#include "backend/hidpp/defs.h"
#include "backend/hidpp20/Device.h"
#include "features/DeviceFeature.h"
#include "Configuration.h"
#include "util/log.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;
namespace logid
{
class Device;
class Receiver;
class DeviceConfig
{
class InputDevice;
class DeviceNickname {
public:
DeviceConfig(const std::shared_ptr<Configuration>& config, Device*
device);
libconfig::Setting& getSetting(const std::string& path);
explicit DeviceNickname(const std::shared_ptr<DeviceManager>& manager);
DeviceNickname() = delete;
DeviceNickname(const DeviceNickname&) = delete;
~DeviceNickname();
operator std::string() const;
private:
Device* _device;
std::string _root_setting;
std::shared_ptr<Configuration> _config;
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
{
class Device : public ipcgull::object {
public:
Device(std::string path, backend::hidpp::DeviceIndex index);
Device(const std::shared_ptr<backend::raw::RawDevice>& raw_device,
backend::hidpp::DeviceIndex index);
Device(Receiver* receiver, backend::hidpp::DeviceIndex index);
std::string name();
uint16_t pid();
DeviceConfig& config();
[[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(std::string name) {
std::shared_ptr<T> getFeature(const std::string& name) {
auto it = _features.find(name);
if(it == _features.end())
if (it == _features.end())
return nullptr;
try {
return std::dynamic_pointer_cast<T>(it->second);
} catch(std::bad_cast& e) {
logPrintf(ERROR, "bad_cast while getting device feature %s: %s",
name.c_str(), e.what());
} 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)
{
void _addFeature(std::string name) {
try {
_features.emplace(name, std::make_shared<T>(this));
_features.emplace(name, features::DeviceFeature::make<T>(this));
} catch (features::UnsupportedFeature& e) {
}
}
backend::hidpp20::Device _hidpp20;
std::shared_ptr<backend::hidpp20::Device> _hidpp20;
std::string _path;
backend::hidpp::DeviceIndex _index;
std::map<std::string, std::shared_ptr<features::DeviceFeature>>
_features;
DeviceConfig _config;
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;
Receiver* _receiver;
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;
};
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -16,95 +16,305 @@
*
*/
#include <DeviceManager.h>
#include <backend/Error.h>
#include <util/log.h>
#include <thread>
#include <sstream>
#include "DeviceManager.h"
#include "Receiver.h"
#include "util/log.h"
#include "backend/hidpp10/Error.h"
#include "backend/Error.h"
#include <utility>
#include <ipc_defs.h>
using namespace logid;
using namespace logid::backend;
void DeviceManager::addDevice(std::string path)
{
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
{
raw::RawDevice raw_dev(path);
if(global_config->isIgnored(raw_dev.productId())) {
raw::RawDevice raw_dev(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());
path.c_str(), raw_dev.productId());
return;
}
}
try {
hidpp::Device device(path, hidpp::DefaultDevice);
isReceiver = device.version() == std::make_tuple(1, 0);
} catch(hidpp10::Error &e) {
if(e.code() != hidpp10::Error::UnknownDevice)
throw;
} catch(hidpp::Device::InvalidDevice &e) { // Ignore
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(std::system_error &e) {
logPrintf(WARN, "I/O error on %s: %s, skipping device.",
path.c_str(), e.what());
} 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 (TimeoutError &e) {
logPrintf(WARN, "Device %s timed out.", path.c_str());
defaultExists = false;
} 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) {
if (isReceiver) {
logPrintf(INFO, "Detected receiver at %s", path.c_str());
auto receiver = std::make_shared<Receiver>(path);
receiver->run();
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 = std::make_shared<Device>(path, hidpp::DefaultDevice);
_devices.emplace(path, device);
/* 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 = std::make_shared<Device>(path,
hidpp::CordedDevice);
auto device = Device::make(path, hidpp::CordedDevice, self<DeviceManager>().lock());
std::lock_guard<std::mutex> lock(_map_lock);
_devices.emplace(path, device);
} catch(hidpp10::Error &e) {
if(e.code() != hidpp10::Error::UnknownDevice)
throw;
else
logPrintf(WARN,
"HID++ 1.0 error while trying to initialize %s:"
"%s", path.c_str(), e.what());
} catch(hidpp::Device::InvalidDevice &e) { // Ignore
} catch(std::system_error &e) {
_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());
logPrintf(WARN, "I/O error on %s: %s", path.c_str(), e.what());
} catch (TimeoutError& e) {
throw DeviceNotReady();
}
}
}
}
void DeviceManager::removeDevice(std::string path)
{
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()) {
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()) {
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -19,32 +19,98 @@
#ifndef LOGID_DEVICEMANAGER_H
#define LOGID_DEVICEMANAGER_H
#include <map>
#include <thread>
#include <mutex>
#include <backend/raw/DeviceMonitor.h>
#include <Device.h>
#include <Receiver.h>
#include <ipcgull/node.h>
#include <ipcgull/interface.h>
#include "backend/raw/DeviceMonitor.h"
#include "backend/hidpp/Device.h"
#include "Device.h"
#include "Receiver.h"
namespace logid {
class InputDevice;
namespace logid
{
class DeviceManager : public backend::raw::DeviceMonitor
{
class DeviceManager : public backend::raw::DeviceMonitor {
public:
DeviceManager() = default;
[[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:
void addDevice(std::string path) override;
void removeDevice(std::string path) override;
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;
};
extern std::unique_ptr<DeviceManager> device_manager;
}
#endif //LOGID_DEVICEMANAGER_H
#endif //LOGID_DEVICEMANAGER_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -16,9 +16,9 @@
*
*/
#include <InputDevice.h>
#include <system_error>
#include "InputDevice.h"
#include <mutex>
extern "C"
{
@ -29,17 +29,18 @@ extern "C"
using namespace logid;
InputDevice::InvalidEventCode::InvalidEventCode(const std::string& name) :
_what ("Invalid event code " + name)
{
_what("Invalid event code " + name) {
}
const char* InputDevice::InvalidEventCode::what() const noexcept
{
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)
{
InputDevice::InputDevice(const char* name) {
device = libevdev_new();
libevdev_set_name(device, name);
@ -56,27 +57,28 @@ InputDevice::InputDevice(const char* name)
}
}
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);
LIBEVDEV_UINPUT_OPEN_MANAGED, &ui_device);
if(err != 0) {
if (err != 0) {
libevdev_free(device);
throw std::system_error(-err, std::generic_category());
}
}
InputDevice::~InputDevice()
{
InputDevice::~InputDevice() {
libevdev_uinput_destroy(ui_device);
libevdev_free(device);
}
void InputDevice::registerKey(uint code)
{
void InputDevice::registerKey(uint code) {
// TODO: Maybe print error message, if wrong code is passed?
if(registered_keys[code] || code > KEY_CNT) {
if (code >= KEY_CNT || registered_keys[code]) {
return;
}
@ -85,10 +87,9 @@ void InputDevice::registerKey(uint code)
registered_keys[code] = true;
}
void InputDevice::registerAxis(uint axis)
{
void InputDevice::registerAxis(uint axis) {
// TODO: Maybe print error message, if wrong code is passed?
if(registered_axis[axis] || axis > REL_CNT) {
if (axis >= REL_CNT || registered_axis[axis]) {
return;
}
@ -97,67 +98,77 @@ void InputDevice::registerAxis(uint axis)
registered_axis[axis] = true;
}
void InputDevice::moveAxis(uint axis, int movement)
{
void InputDevice::moveAxis(uint axis, int movement) {
_sendEvent(EV_REL, axis, movement);
}
void InputDevice::pressKey(uint code)
{
void InputDevice::pressKey(uint code) {
_sendEvent(EV_KEY, code, 1);
}
void InputDevice::releaseKey(uint code)
{
void InputDevice::releaseKey(uint code) {
_sendEvent(EV_KEY, code, 0);
}
uint InputDevice::toKeyCode(const std::string& name)
{
std::string InputDevice::toKeyName(uint code) {
return _toEventName(EV_KEY, code);
}
uint InputDevice::toKeyCode(const std::string& name) {
return _toEventCode(EV_KEY, name);
}
uint InputDevice::toAxisCode(const std::string& 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)
{
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)
if (axis_code == REL_WHEEL_HI_RES)
return REL_WHEEL;
#endif
#ifdef REL_HWHEEL_HI_RES
if(axis_code == REL_HWHEEL_HI_RES)
if (axis_code == REL_HWHEEL_HI_RES)
return REL_HWHEEL;
#endif
return -1;
}
uint InputDevice::_toEventCode(uint type, const std::string& name)
{
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)
if (code == -1)
throw InvalidEventCode(name);
return code;
}
void InputDevice::_enableEvent(const uint type, const uint 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);
LIBEVDEV_UINPUT_OPEN_MANAGED, &ui_device);
if(err != 0) {
if (err != 0) {
libevdev_free(device);
device = nullptr;
ui_device = nullptr;
@ -165,8 +176,8 @@ void InputDevice::_enableEvent(const uint type, const uint code)
}
}
void InputDevice::_sendEvent(uint type, uint code, int value)
{
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -28,45 +28,61 @@ extern "C"
#include <libevdev/libevdev-uinput.h>
}
namespace logid
{
class InputDevice
{
namespace logid {
class InputDevice {
public:
class InvalidEventCode : public std::exception
{
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);
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];
bool registered_keys[KEY_CNT]{};
bool registered_axis[REL_CNT]{};
libevdev* device;
libevdev_uinput* ui_device{};
};
extern std::unique_ptr<InputDevice> virtual_input;
std::mutex _input_mutex;
};
}
#endif //LOGID_INPUTDEVICE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -16,85 +16,219 @@
*
*/
#include "Receiver.h"
#include "util/log.h"
#include "backend/hidpp10/Error.h"
#include "backend/hidpp20/Error.h"
#include "backend/Error.h"
#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;
Receiver::Receiver(const std::string& path) :
dj::ReceiverMonitor(path), _path (path)
{
ReceiverNickname::ReceiverNickname(
const std::shared_ptr<DeviceManager>& manager) :
_nickname(manager->newReceiverNickname()), _manager(manager) {
}
void Receiver::addDevice(hidpp::DeviceConnectionEvent event)
{
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(global_config->isIgnored(event.pid)) {
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)
if (dev != _devices.end()) {
if (event.linkEstablished)
dev->second->wakeup();
else
dev->second->sleep();
return;
}
if(!event.linkEstablished)
if (!event.linkEstablished)
return;
hidpp::Device hidpp_device(receiver(), event);
auto hidpp_device = hidpp::Device::make(
receiver(), event, manager->config()->io_timeout.value_or(defaults::io_timeout));
auto version = hidpp_device.version();
auto version = hidpp_device->version();
if(std::get<0>(version) < 2) {
if (std::get<0>(version) < 2) {
logPrintf(INFO, "Unsupported HID++ 1.0 device on %s:%d connected.",
_path.c_str(), event.index);
_path.c_str(), event.index);
return;
}
std::shared_ptr<Device> device = std::make_shared<Device>(this,
event.index);
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) {
} 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)
"%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)
{
void Receiver::removeDevice(hidpp::DeviceIndex index) {
std::unique_lock<std::mutex> lock(_devices_change);
_devices.erase(index);
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);
}
}
const std::string& Receiver::path() const
{
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<dj::Receiver> Receiver::rawReceiver()
{
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -20,24 +20,80 @@
#define LOGID_RECEIVER_H
#include <string>
#include "backend/dj/ReceiverMonitor.h"
#include "Device.h"
#include <Device.h>
#include <backend/hidpp10/ReceiverMonitor.h>
namespace logid
{
class Receiver : public backend::dj::ReceiverMonitor
{
namespace logid {
class ReceiverNickname {
public:
explicit Receiver(const std::string& path);
const std::string& path() const;
std::shared_ptr<backend::dj::Receiver> rawReceiver();
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;
std::map<backend::hidpp::DeviceIndex, std::shared_ptr<Device>> _devices;
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;
};
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -16,64 +16,128 @@
*
*/
#include <algorithm>
#include "Action.h"
#include "../util/log.h"
#include "KeypressAction.h"
#include "ToggleSmartShift.h"
#include "ToggleHiresScroll.h"
#include "GestureAction.h"
#include "NullAction.h"
#include "CycleDPI.h"
#include "ChangeDPI.h"
#include "ChangeHostAction.h"
#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;
std::shared_ptr<Action> Action::makeAction(Device *device, libconfig::Setting
&setting)
{
if(!setting.isGroup()) {
logPrintf(WARN, "Line %d: Action is not a group, ignoring.",
setting.getSourceLine());
throw InvalidAction();
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);
}
try {
auto& action_type = setting.lookup("type");
if(action_type.getType() != libconfig::Setting::TypeString) {
logPrintf(WARN, "Line %d: Action type must be a string",
action_type.getSourceLine());
throw InvalidAction();
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);
}
std::string type = action_type;
std::transform(type.begin(), type.end(), type.begin(), ::tolower);
if(type == "keypress")
return std::make_shared<KeypressAction>(device, setting);
else if(type == "togglesmartshift")
return std::make_shared<ToggleSmartShift>(device);
else if(type == "togglehiresscroll")
return std::make_shared<ToggleHiresScroll>(device);
else if(type == "gestures")
return std::make_shared<GestureAction>(device, setting);
else if(type == "cycledpi")
return std::make_shared<CycleDPI>(device, setting);
else if(type == "changedpi")
return std::make_shared<ChangeDPI>(device, setting);
else if(type == "none")
return std::make_shared<NullAction>(device);
else if(type == "changehost")
return std::make_shared<ChangeHostAction>(device, setting);
else
throw InvalidAction(type);
} catch(libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: Action type is missing, ignoring.",
setting.getSourceLine());
throw InvalidAction();
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -19,67 +19,81 @@
#define LOGID_ACTION_H
#include <atomic>
#include <libconfig.h++>
#include <memory>
#include <utility>
#include <shared_mutex>
#include <ipcgull/node.h>
#include <ipcgull/interface.h>
#include <config/schema.h>
namespace logid {
class Device;
namespace actions {
class InvalidAction : public std::exception
{
}
namespace logid::actions {
class InvalidAction : public std::exception {
public:
InvalidAction()
{
}
explicit InvalidAction(std::string& action) : _action (action)
{
}
const char* what() const noexcept override
{
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
{
class Action : public ipcgull::interface {
public:
static std::shared_ptr<Action> makeAction(Device* device,
libconfig::Setting& setting);
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(int16_t x, int16_t y)
{
// Suppress unused warning
(void)x; (void)y;
}
virtual bool pressed()
{
virtual void release() = 0;
virtual void move([[maybe_unused]] int16_t x, [[maybe_unused]] int16_t y) { }
virtual bool pressed() {
return _pressed;
}
virtual uint8_t reprogFlags() const = 0;
[[nodiscard]] virtual uint8_t reprogFlags() const = 0;
virtual ~Action() = default;
class Config
{
protected:
explicit Config(Device* device) : _device (device)
{
}
Device* _device;
};
protected:
explicit Action(Device* device) : _device (device), _pressed (false)
{
}
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,95 +15,80 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ChangeDPI.h"
#include "../Device.h"
#include "../util/task.h"
#include "../util/log.h"
#include "../backend/hidpp20/Error.h"
#include "../backend/hidpp20/features/ReprogControls.h"
#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;
ChangeDPI::ChangeDPI(Device *device, libconfig::Setting &setting) :
Action(device), _config(device, setting)
{
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.",
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()
{
void ChangeDPI::press() {
_pressed = true;
if(_dpi) {
task::spawn([this]{
try {
uint16_t last_dpi = _dpi->getDPI(_config.sensor());
_dpi->setDPI(last_dpi + _config.interval(), _config.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",
_device->hidpp20().devicePath().c_str(),
_device->hidpp20().deviceIndex(),
_config.sensor());
else
throw e;
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()
{
void ChangeDPI::release() {
_pressed = false;
}
uint8_t ChangeDPI::reprogFlags() const
{
uint8_t ChangeDPI::reprogFlags() const {
return backend::hidpp20::ReprogControls::TemporaryDiverted;
}
ChangeDPI::Config::Config(Device *device, libconfig::Setting &config) :
Action::Config(device), _interval (0), _sensor (0)
{
if(!config.isGroup()) {
logPrintf(WARN, "Line %d: action must be an object, skipping.",
config.getSourceLine());
return;
}
try {
auto& inc = config.lookup("inc");
if(inc.getType() != libconfig::Setting::TypeInt)
logPrintf(WARN, "Line %d: inc must be an integer",
inc.getSourceLine());
_interval = (int)inc;
} catch(libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: inc is a required field, skipping.",
config.getSourceLine());
}
try {
auto& sensor = config.lookup("sensor");
if(sensor.getType() != libconfig::Setting::TypeInt)
logPrintf(WARN, "Line %d: sensor must be an integer",
sensor.getSourceLine());
_sensor = (int)sensor;
} catch(libconfig::SettingNotFoundException& e) {
// Ignore
}
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)};
}
uint16_t ChangeDPI::Config::interval() const
{
return _interval;
void ChangeDPI::setChange(int16_t change) {
std::unique_lock lock(_config_mutex);
_config.inc = change;
}
uint8_t ChangeDPI::Config::sensor() const
{
return _sensor;
}
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,37 +18,33 @@
#ifndef LOGID_ACTION_CHANGEDPI_H
#define LOGID_ACTION_CHANGEDPI_H
#include <libconfig.h++>
#include "Action.h"
#include "../features/DPI.h"
#include <actions/Action.h>
#include <features/DPI.h>
namespace logid {
namespace actions {
class ChangeDPI : public Action
{
public:
explicit ChangeDPI(Device* device, libconfig::Setting& setting);
namespace logid::actions {
class ChangeDPI : public Action {
public:
static const char* interface_name;
virtual void press();
virtual void release();
ChangeDPI(Device* device, config::ChangeDPI& setting,
const std::shared_ptr<ipcgull::node>& parent);
virtual uint8_t reprogFlags() const;
void press() final;
class Config : public Action::Config
{
public:
Config(Device* device, libconfig::Setting& setting);
uint16_t interval() const;
uint8_t sensor() const;
private:
uint16_t _interval;
uint8_t _sensor;
};
void release() final;
protected:
Config _config;
std::shared_ptr<features::DPI> _dpi;
};
}}
[[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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,105 +15,98 @@
* 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 "ChangeHostAction.h"
#include "../Device.h"
#include "../backend/hidpp20/features/ReprogControls.h"
#include "../util/task.h"
#include <util/task.h>
#include <util/log.h>
using namespace logid::actions;
using namespace logid::backend;
ChangeHostAction::ChangeHostAction(Device *device, libconfig::Setting&
config) : Action(device), _config (device, config)
{
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());
.devicePath().c_str(), device->hidpp20().deviceIndex());
}
}
void ChangeHostAction::press()
{
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()
{
if(_change_host) {
task::spawn([this] {
auto host_info = _change_host->getHostInfo();
auto next_host = _config.nextHost(host_info);
if(next_host != host_info.currentHost)
_change_host->setHost(next_host);
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
{
uint8_t ChangeHostAction::reprogFlags() const {
return hidpp20::ReprogControls::TemporaryDiverted;
}
ChangeHostAction::Config::Config(Device *device, libconfig::Setting& config)
: Action::Config(device)
{
try {
auto& host = config.lookup("host");
if(host.getType() == libconfig::Setting::TypeInt) {
_offset = false;
_host = host;
_host--; // hosts are one-indexed in config
if(_host < 0) {
logPrintf(WARN, "Line %d: host must be positive.",
host.getSourceLine());
_offset = true;
_host = 0;
}
} else if(host.getType() == libconfig::Setting::TypeString) {
_offset = true;
std::string hostmode = host;
std::transform(hostmode.begin(), hostmode.end(),
hostmode.begin(), ::tolower);
if(hostmode == "next")
_host = 1;
else if(hostmode == "prev" || hostmode == "previous")
_host = -1;
else {
logPrintf(WARN, "Line %d: host must equal an integer, 'next',"
"or 'prev'.", host.getSourceLine());
_host = 0;
}
} else {
logPrintf(WARN, "Line %d: host must equal an integer, 'next',"
"or 'prev'.", host.getSourceLine());
_offset = true;
_host = 0;
}
} catch (libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: host is a required field, skipping.",
config.getSourceLine());
_offset = true;
_host = 0;
}
}
uint8_t ChangeHostAction::Config::nextHost(hidpp20::ChangeHost::HostInfo info)
{
if(_offset) {
return (info.currentHost + _host) % info.hostCount;
} else {
if(_host >= info.hostCount || _host < 0) {
logPrintf(WARN, "No such host %d, defaulting to current.",
_host+1);
return info.currentHost;
} else
return _host;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,37 +18,31 @@
#ifndef LOGID_ACTION_CHANGEHOSTACTION_H
#define LOGID_ACTION_CHANGEHOSTACTION_H
#include <libconfig.h++>
#include "Action.h"
#include "../backend/hidpp20/features/ChangeHost.h"
#include <actions/Action.h>
#include <backend/hidpp20/features/ChangeHost.h>
namespace logid {
namespace actions
{
class ChangeHostAction : public Action
{
namespace logid::actions {
class ChangeHostAction : public Action {
public:
ChangeHostAction(Device* device, libconfig::Setting& config);
static const char* interface_name;
virtual void press();
virtual void release();
ChangeHostAction(Device* device, config::ChangeHost& config,
const std::shared_ptr<ipcgull::node>& parent);
virtual uint8_t reprogFlags() const;
void press() final;
class Config : public Action::Config
{
public:
Config(Device* device, libconfig::Setting& setting);
uint8_t nextHost(backend::hidpp20::ChangeHost::HostInfo info);
private:
bool _offset;
int _host;
};
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 _config;
config::ChangeHost& _config;
};
}}
}
#endif //LOGID_ACTION_CHANGEHOSTACTION_H

View File

@ -0,0 +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);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,34 +15,33 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_CHANGEPROFILE_H
#define LOGID_CHANGEPROFILE_H
#ifndef LOGID_BACKEND_DJ_ERROR_H
#define LOGID_BACKEND_DJ_ERROR_H
#include <actions/Action.h>
#include <cstdint>
#include <stdexcept>
namespace logid {
namespace backend {
namespace dj
{
class Error : public std::exception
{
namespace logid::actions {
class ChangeProfile : public Action {
public:
enum ErrorCode : uint8_t
{
Unknown = 0x00,
KeepAliveTimeout = 0x01
};
static const char* interface_name;
explicit Error(uint8_t code);
ChangeProfile(Device* device, config::ChangeProfile& setting,
const std::shared_ptr<ipcgull::node>& parent);
const char* what() const noexcept override;
uint8_t code() const noexcept;
void press() final;
void release() final;
[[nodiscard]] uint8_t reprogFlags() const final;
std::string getProfile();
void setProfile(std::string profile);
private:
uint8_t _code;
config::ChangeProfile& _config;
};
}}}
}
#endif //LOGID_BACKEND_DJ_ERROR_H
#endif //LOGID_CHANGEPROFILE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,119 +15,84 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "CycleDPI.h"
#include "../Device.h"
#include "../util/task.h"
#include "../util/log.h"
#include "../backend/hidpp20/Error.h"
#include "../backend/hidpp20/features/ReprogControls.h"
#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;
using namespace libconfig;
CycleDPI::CycleDPI(Device* device, libconfig::Setting& setting) :
Action (device), _config (device, setting)
{
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)
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();
}
}
void CycleDPI::press()
{
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;
if(_dpi && !_config.empty()) {
task::spawn([this](){
uint16_t dpi = _config.nextDPI();
try {
_dpi->setDPI(dpi, _config.sensor());
} 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", _device->hidpp20().devicePath().c_str(),
_device->hidpp20().deviceIndex(), dpi,
_config.sensor());
else
throw e;
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()
{
void CycleDPI::release() {
_pressed = false;
}
uint8_t CycleDPI::reprogFlags() const
{
uint8_t CycleDPI::reprogFlags() const {
return backend::hidpp20::ReprogControls::TemporaryDiverted;
}
CycleDPI::Config::Config(Device *device, libconfig::Setting &config) :
Action::Config(device), _current_index (0), _sensor (0)
{
if(!config.isGroup()) {
logPrintf(WARN, "Line %d: action must be an object, skipping.",
config.getSourceLine());
return;
}
try {
auto& sensor = config.lookup("sensor");
if(sensor.getType() != Setting::TypeInt)
logPrintf(WARN, "Line %d: sensor must be an integer",
sensor.getSourceLine());
_sensor = (int)sensor;
} catch(libconfig::SettingNotFoundException& e) {
// Ignore
}
try {
auto& dpis = config.lookup("dpis");
if(!dpis.isList() && !dpis.isArray()) {
logPrintf(WARN, "Line %d: dpis must be a list or array, skipping.",
dpis.getSourceLine());
return;
}
int dpi_count = dpis.getLength();
for(int i = 0; i < dpi_count; i++) {
if(dpis[i].getType() != Setting::TypeInt) {
logPrintf(WARN, "Line %d: dpis must be integers, skipping.",
dpis[i].getSourceLine());
if(dpis.isList())
continue;
else
break;
}
_dpis.push_back((int)(dpis[i]));
}
} catch (libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: dpis is a required field, skipping.",
config.getSourceLine());
}
}
uint16_t CycleDPI::Config::nextDPI()
{
uint16_t dpi = _dpis[_current_index++];
if(_current_index >= _dpis.size())
_current_index = 0;
return dpi;
}
bool CycleDPI::Config::empty() const
{
return _dpis.empty();
}
uint8_t CycleDPI::Config::sensor() const
{
return _sensor;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,39 +18,33 @@
#ifndef LOGID_ACTION_CYCLEDPI_H
#define LOGID_ACTION_CYCLEDPI_H
#include <libconfig.h++>
#include "Action.h"
#include "../features/DPI.h"
#include <actions/Action.h>
#include <features/DPI.h>
namespace logid {
namespace actions {
class CycleDPI : public Action
{
namespace logid::actions {
class CycleDPI : public Action {
public:
explicit CycleDPI(Device* device, libconfig::Setting& setting);
static const char* interface_name;
virtual void press();
virtual void release();
CycleDPI(Device* device, config::CycleDPI& setting,
const std::shared_ptr<ipcgull::node>& parent);
virtual uint8_t reprogFlags() const;
void press() final;
class Config : public Action::Config
{
public:
Config(Device* device, libconfig::Setting& setting);
uint16_t nextDPI();
bool empty() const;
uint8_t sensor() const;
private:
std::size_t _current_index;
std::vector<uint16_t> _dpis;
uint8_t _sensor;
};
void release() final;
[[nodiscard]] std::vector<int> getDPIs() const;
void setDPIs(const std::vector<int>& dpis);
[[nodiscard]] uint8_t reprogFlags() const final;
protected:
Config _config;
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,75 +15,117 @@
* 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>
#include "GestureAction.h"
#include "../Device.h"
#include "../backend/hidpp20/features/ReprogControls.h"
using namespace logid::actions;
using namespace logid;
using namespace logid::backend;
GestureAction::Direction GestureAction::toDirection(std::string direction)
{
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")
::tolower);
if (direction == "up")
return Up;
else if(direction == "down")
else if (direction == "down")
return Down;
else if(direction == "left")
else if (direction == "left")
return Left;
else if(direction == "right")
else if (direction == "right")
return Right;
else if(direction == "none")
else if (direction == "none")
return None;
else
throw std::invalid_argument("direction");
}
GestureAction::Direction GestureAction::toDirection(int16_t x, int16_t y)
{
if(x >= 0 && y >= 0)
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)
else if (x < 0 && y >= 0)
return -x <= y ? Down : Left;
else if(x <= 0 && y < 0)
else if (x <= 0 /* && y < 0 */)
return x <= y ? Left : Up;
else
return x <= -y ? Up : Right;
}
GestureAction::GestureAction(Device* dev, libconfig::Setting& config) :
Action (dev), _config (dev, config)
{
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))));
} catch (std::invalid_argument& e) {
logPrintf(WARN, "%s is not a direction", x.first.c_str());
}
}
}
}
void GestureAction::press()
{
void GestureAction::press() {
std::shared_lock lock(_config_mutex);
_pressed = true;
_x = 0, _y = 0;
for(auto& gesture : _config.gestures())
gesture.second->press();
for (auto& gesture: _gestures)
gesture.second->press(false);
}
void GestureAction::release()
{
void GestureAction::release() {
std::shared_lock lock(_config_mutex);
_pressed = false;
bool threshold_met = false;
auto d = toDirection(_x, _y);
auto primary_gesture = _config.gestures().find(d);
if(primary_gesture != _config.gestures().end()) {
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 : _config.gestures()) {
if(gesture.first == d)
for (auto& gesture: _gestures) {
if (gesture.first == d)
continue;
if(!threshold_met) {
if(gesture.second->metThreshold()) {
if (!threshold_met) {
if (gesture.second->metThreshold()) {
// If the primary gesture did not meet its threshold, use the
// secondary one.
threshold_met = true;
@ -95,197 +137,117 @@ void GestureAction::release()
}
}
if(!threshold_met) {
if(_config.noneAction()) {
_config.noneAction()->press();
_config.noneAction()->release();
}
if (!threshold_met) {
try {
auto none = _gestures.at(None);
if (none) {
none->press(false);
none->release(false);
}
} catch (std::out_of_range& e) {}
}
}
void GestureAction::move(int16_t x, int16_t y)
{
auto new_x = _x + x, new_y = _y + y;
void GestureAction::move(int16_t x, int16_t y) {
std::shared_lock lock(_config_mutex);
if(abs(x) > 0) {
if(_x < 0 && new_x >= 0) { // Left -> Origin/Right
auto left = _config.gestures().find(Left);
if(left != _config.gestures().end())
left->second->move(_x);
if(new_x) { // Ignore to origin
auto right = _config.gestures().find(Right);
if(right != _config.gestures().end())
right->second->move(new_x);
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 = _config.gestures().find(Right);
if(right != _config.gestures().end())
right->second->move(-_x);
if(new_x) { // Ignore to origin
auto left = _config.gestures().find(Left);
if(left != _config.gestures().end())
left->second->move(-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 = _config.gestures().find(Left);
if(left != _config.gestures().end())
left->second->move(-x);
} else if(new_x > 0) { // Origin/Right to Right
auto right = _config.gestures().find(Right);
if(right != _config.gestures().end())
} 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 = _config.gestures().find(Up);
if(up != _config.gestures().end())
up->second->move(_y);
if(new_y) { // Ignore to origin
auto down = _config.gestures().find(Down);
if(down != _config.gestures().end())
down->second->move(new_y);
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 = _config.gestures().find(Down);
if(down != _config.gestures().end())
down->second->move(-_y);
if(new_y) { // Ignore to origin
auto up = _config.gestures().find(Up);
if(up != _config.gestures().end())
up->second->move(-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 = _config.gestures().find(Up);
if(up != _config.gestures().end())
up->second->move(-y);
} else if(new_y > 0) {// Origin/Down to Down
auto down = _config.gestures().find(Down);
if(down != _config.gestures().end())
} 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;
_x = new_x;
_y = new_y;
}
uint8_t GestureAction::reprogFlags() const
{
uint8_t GestureAction::reprogFlags() const {
return (hidpp20::ReprogControls::TemporaryDiverted |
hidpp20::ReprogControls::RawXYDiverted);
hidpp20::ReprogControls::RawXYDiverted);
}
GestureAction::Config::Config(Device* device, libconfig::Setting &root) :
Action::Config(device)
{
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);
_gestures[d].reset();
try {
auto& gestures = root.lookup("gestures");
if(!gestures.isList()) {
logPrintf(WARN, "Line %d: gestures must be a list, ignoring.",
gestures.getSourceLine());
return;
}
int gesture_count = gestures.getLength();
for(int i = 0; i < gesture_count; i++) {
if(!gestures[i].isGroup()) {
logPrintf(WARN, "Line %d: gesture must be a group, skipping.",
gestures[i].getSourceLine());
continue;
}
Direction d;
try {
auto& direction = gestures[i].lookup("direction");
if(direction.getType() != libconfig::Setting::TypeString) {
logPrintf(WARN, "Line %d: direction must be a string, "
"skipping.", direction.getSourceLine());
continue;
}
try {
d = toDirection(direction);
} catch(std::invalid_argument& e) {
logPrintf(WARN, "Line %d: Invalid direction %s",
direction.getSourceLine(), (const char*)direction);
continue;
}
} catch(libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: direction is a required field, "
"skipping.", gestures[i].getSourceLine());
continue;
}
if(_gestures.find(d) != _gestures.end() || (d == None && _none_action)) {
logPrintf(WARN, "Line %d: Gesture is already defined for "
"this direction, duplicate ignored.",
gestures[i].getSourceLine());
continue;
}
if(d == None) {
try {
auto& mode = gestures[i].lookup("mode");
if(mode.getType() == libconfig::Setting::TypeString) {
std::string mode_str = mode;
std::transform(mode_str.begin(), mode_str.end(),
mode_str.begin(), ::tolower);
if(mode_str == "nopress") // No action
continue;
else if(mode_str != "onrelease")
logPrintf(WARN, "Line %d: Only NoPress and "
"OnRelease are supported for the "
"None direction, defaulting to "
"OnRelease.", mode.getSourceLine());
} else {
logPrintf(WARN, "Line %d: mode must be a string, "
"defaulting to OnRelease",
mode.getSourceLine());
}
} catch(libconfig::SettingNotFoundException& e) {
// Default to OnRelease
}
try {
_none_action = Action::makeAction(_device,
gestures[i].lookup("action"));
} catch (InvalidAction& e) {
logPrintf(WARN, "Line %d: %s is not a valid action, "
"skipping.", gestures[i].lookup("action")
.getSourceLine(), e.what());
} catch (libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: action is a required field, "
"skipping.", gestures[i].getSourceLine(),
e.what());
}
continue;
}
try {
_gestures.emplace(d, Gesture::makeGesture(_device,
gestures[i]));
} catch(InvalidGesture& e) {
logPrintf(WARN, "Line %d: Invalid gesture: %s",
gestures[i].getSourceLine(), e.what());
}
}
} catch(libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: gestures is a required field, ignoring.",
root.getSourceLine());
_gestures[d] = Gesture::makeGesture(
_device, type, _config.gestures.value()[dir_name],
_node->make_child(dir_name));
} catch (InvalidGesture& e) {
_gestures[d] = Gesture::makeGesture(
_device, _config.gestures.value()[dir_name],
_node->make_child(dir_name));
throw std::invalid_argument("Invalid gesture type");
}
}
std::map<GestureAction::Direction, std::shared_ptr<Gesture>>&
GestureAction::Config::gestures()
{
return _gestures;
}
std::shared_ptr<Action> GestureAction::Config::noneAction()
{
return _none_action;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -19,49 +19,48 @@
#define LOGID_ACTION_GESTUREACTION_H
#include <map>
#include <libconfig.h++>
#include "Action.h"
#include "gesture/Gesture.h"
#include <actions/Action.h>
#include <actions/gesture/Gesture.h>
namespace logid {
namespace actions {
class GestureAction : public Action
{
namespace logid::actions {
class GestureAction : public Action {
public:
enum Direction
{
static const char* interface_name;
enum Direction {
None,
Up,
Down,
Left,
Right
};
static Direction toDirection(std::string direction);
static Direction toDirection(int16_t x, int16_t y);
GestureAction(Device* dev, libconfig::Setting& config);
static std::string fromDirection(Direction direction);
virtual void press();
virtual void release();
virtual void move(int16_t x, int16_t y);
static Direction toDirection(int32_t x, int32_t y);
virtual uint8_t reprogFlags() const;
GestureAction(Device* dev, config::GestureAction& config,
const std::shared_ptr<ipcgull::node>& parent);
class Config : public Action::Config
{
public:
Config(Device* device, libconfig::Setting& root);
std::map<Direction, std::shared_ptr<Gesture>>& gestures();
std::shared_ptr<Action> noneAction();
protected:
std::map<Direction, std::shared_ptr<Gesture>> _gestures;
std::shared_ptr<Action> _none_action;
};
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:
int16_t _x, _y;
Config _config;
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,77 +15,112 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "KeypressAction.h"
#include "../util/log.h"
#include "../InputDevice.h"
#include "../backend/hidpp20/features/ReprogControls.h"
#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;
KeypressAction::KeypressAction(Device *device, libconfig::Setting& config) :
Action(device), _config (device, config)
{
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()
{
void KeypressAction::press() {
std::shared_lock lock(_config_mutex);
_pressed = true;
for(auto& key : _config.keys())
virtual_input->pressKey(key);
for (auto& key: _keys)
_device->virtualInput()->pressKey(key);
}
void KeypressAction::release()
{
void KeypressAction::release() {
std::shared_lock lock(_config_mutex);
_pressed = false;
for(auto& key : _config.keys())
virtual_input->releaseKey(key);
for (auto& key: _keys)
_device->virtualInput()->releaseKey(key);
}
uint8_t KeypressAction::reprogFlags() const
{
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;
}
KeypressAction::Config::Config(Device* device, libconfig::Setting& config) :
Action::Config(device)
{
if(!config.isGroup()) {
logPrintf(WARN, "Line %d: action must be an object, skipping.",
config.getSourceLine());
return;
}
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));
try {
auto &keys = config.lookup("keys");
if(keys.isArray() || keys.isList()) {
int key_count = keys.getLength();
for(int i = 0; i < key_count; i++) {
auto& key = keys[i];
if(key.isNumber()) {
_keys.push_back(key);
virtual_input->registerKey(key);
} else if(key.getType() == libconfig::Setting::TypeString) {
try {
_keys.push_back(virtual_input->toKeyCode(key));
virtual_input->registerKey(virtual_input->toKeyCode(key));
} catch(InputDevice::InvalidEventCode& e) {
logPrintf(WARN, "Line %d: Invalid keycode %s, skipping."
, key.getSourceLine(), key.c_str());
}
} else {
logPrintf(WARN, "Line %d: keycode must be string or int",
key.getSourceLine(), key.c_str());
}
}
}
} catch (libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: keys is a required field, skipping.",
config.getSourceLine());
}
return ret;
}
std::vector<uint>& KeypressAction::Config::keys()
{
return _keys;
}
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -19,32 +19,33 @@
#define LOGID_ACTION_KEYPRESS_H
#include <vector>
#include <libconfig.h++>
#include "Action.h"
#include <actions/Action.h>
namespace logid {
namespace actions {
class KeypressAction : public Action
{
namespace logid::actions {
class KeypressAction : public Action {
public:
KeypressAction(Device* dev, libconfig::Setting& config);
static const char* interface_name;
virtual void press();
virtual void release();
KeypressAction(Device* dev,
config::KeypressAction& config,
const std::shared_ptr<ipcgull::node>& parent);
virtual uint8_t reprogFlags() const;
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;
class Config : public Action::Config
{
public:
explicit Config(Device* device, libconfig::Setting& root);
std::vector<uint>& keys();
protected:
std::vector<uint> _keys;
};
protected:
Config _config;
config::KeypressAction& _config;
std::list<uint> _keys;
void _setConfig();
};
}}
}
#endif //LOGID_ACTION_KEYPRESS_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,27 +15,27 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "NullAction.h"
#include "../Device.h"
#include "../backend/hidpp20/features/ReprogControls.h"
#include <actions/NullAction.h>
#include <backend/hidpp20/features/ReprogControls.h>
using namespace logid::actions;
NullAction::NullAction(Device* device) : Action(device)
{
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()
{
void NullAction::press() {
_pressed = true;
}
void NullAction::release()
{
void NullAction::release() {
_pressed = false;
}
uint8_t NullAction::reprogFlags() const
{
uint8_t NullAction::reprogFlags() const {
return backend::hidpp20::ReprogControls::TemporaryDiverted;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,22 +18,27 @@
#ifndef LOGID_ACTION_NULL_H
#define LOGID_ACTION_NULL_H
#include "Action.h"
#include <actions/Action.h>
namespace logid {
namespace actions
{
class NullAction : public Action
{
namespace logid::actions {
class NullAction : public Action {
public:
explicit NullAction(Device* device);
static const char* interface_name;
virtual void press();
virtual void release();
NullAction(Device* device,
const std::shared_ptr<ipcgull::node>& parent);
virtual uint8_t reprogFlags() const;
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,43 +15,46 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ToggleHiresScroll.h"
#include "../Device.h"
#include "../util/task.h"
#include "../backend/hidpp20/features/ReprogControls.h"
#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;
ToggleHiresScroll::ToggleHiresScroll(Device *dev) : Action (dev)
{
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)
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()
{
void ToggleHiresScroll::press() {
_pressed = true;
if(_hires_scroll)
{
task::spawn([hires=this->_hires_scroll](){
auto mode = hires->getMode();
mode ^= backend::hidpp20::HiresScroll::HiRes;
hires->setMode(mode);
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()
{
void ToggleHiresScroll::release() {
_pressed = false;
}
uint8_t ToggleHiresScroll::reprogFlags() const
{
uint8_t ToggleHiresScroll::reprogFlags() const {
return hidpp20::ReprogControls::TemporaryDiverted;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,24 +18,30 @@
#ifndef LOGID_ACTION_TOGGLEHIRESSCROLL_H
#define LOGID_ACTION_TOGGLEHIRESSCROLL_H
#include "Action.h"
#include "../features/HiresScroll.h"
#include <actions/Action.h>
#include <features/HiresScroll.h>
namespace logid {
namespace actions
{
class ToggleHiresScroll : public Action
{
namespace logid::actions {
class ToggleHiresScroll : public Action {
public:
explicit ToggleHiresScroll(Device* dev);
static const char* interface_name;
virtual void press();
virtual void release();
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;
virtual uint8_t reprogFlags() const;
protected:
std::shared_ptr<features::HiresScroll> _hires_scroll;
};
}}
}
#endif //LOGID_ACTION_TOGGLEHIRESSCROLL_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,43 +15,48 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ToggleSmartShift.h"
#include "../Device.h"
#include "../backend/hidpp20/features/ReprogControls.h"
#include "../util/task.h"
#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;
ToggleSmartShift::ToggleSmartShift(Device *dev) : Action (dev)
{
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)
if (!_smartshift)
logPrintf(WARN, "%s:%d: SmartShift feature not found, cannot use "
"ToggleSmartShift action.",
_device->hidpp20().devicePath().c_str(),
_device->hidpp20().deviceIndex());
_device->hidpp20().devicePath().c_str(),
_device->hidpp20().deviceIndex());
}
void ToggleSmartShift::press()
{
void ToggleSmartShift::press() {
_pressed = true;
if(_smartshift) {
task::spawn([ss=this->_smartshift](){
auto status = ss->getStatus();
status.setActive = true;
status.active = !status.active;
ss->setStatus(status);
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()
{
void ToggleSmartShift::release() {
_pressed = false;
}
uint8_t ToggleSmartShift::reprogFlags() const
{
uint8_t ToggleSmartShift::reprogFlags() const {
return hidpp20::ReprogControls::TemporaryDiverted;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,24 +18,31 @@
#ifndef LOGID_ACTION_TOGGLESMARTSHIFT_H
#define LOGID_ACTION_TOGGLESMARTSHIFT_H
#include <libconfig.h++>
#include "Action.h"
#include "../features/SmartShift.h"
#include <actions/Action.h>
#include <features/SmartShift.h>
namespace logid {
namespace actions {
class ToggleSmartShift : public Action
{
namespace logid::actions {
class ToggleSmartShift : public Action {
public:
explicit ToggleSmartShift(Device* dev);
static const char* interface_name;
virtual void press();
virtual void release();
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;
virtual uint8_t reprogFlags() const;
protected:
std::shared_ptr<features::SmartShift> _smartshift;
};
}}
}
#endif //LOGID_ACTION_TOGGLESMARTSHIFT_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -16,161 +16,170 @@
*
*/
#include <cmath>
#include "AxisGesture.h"
#include "../../InputDevice.h"
#include "../../util/log.h"
#include <actions/gesture/AxisGesture.h>
#include <Device.h>
#include <InputDevice.h>
#include <util/log.h>
using namespace logid::actions;
AxisGesture::AxisGesture(Device *device, libconfig::Setting &root) :
Gesture (device), _config (device, root)
{
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)
{
_axis = init_threshold ? _config.threshold() : 0;
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)
{
void AxisGesture::release(bool primary) {
// Do nothing
(void)primary; // Suppress unused warning
(void) primary; // Suppress unused warning
}
void AxisGesture::move(int16_t axis)
{
int16_t new_axis = _axis+axis;
int low_res_axis = InputDevice::getLowResAxis(_config.axis());
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 > _config.threshold()) {
if (new_axis > threshold) {
double move = axis;
if(_axis < _config.threshold())
move = new_axis - _config.threshold();
bool negative_multiplier = _config.multiplier() < 0;
if(negative_multiplier)
move *= -_config.multiplier();
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.multiplier();
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) {
if (_axis_remainder >= 1) {
double int_remainder = floor(_axis_remainder);
move_floor += int_remainder;
_axis_remainder -= int_remainder;
}
if(negative_multiplier)
if (negative_multiplier)
move_floor = -move_floor;
if(low_res_axis != -1) {
int lowres_movement = 0, hires_movement = move_floor;
virtual_input->moveAxis(_config.axis(), hires_movement);
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)
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;
virtual_input->moveAxis(low_res_axis, lowres_movement);
hires_remainder -= lowres_movement * 120;
_device->virtualInput()->moveAxis(low_res_axis, lowres_movement);
}
_hires_remainder = hires_remainder;
} else {
virtual_input->moveAxis(_config.axis(), move_floor);
_device->virtualInput()->moveAxis(_input_axis.value(), (int) move_floor);
}
}
_axis = new_axis;
}
bool AxisGesture::metThreshold() const
{
return _axis >= _config.threshold();
bool AxisGesture::metThreshold() const {
std::shared_lock lock(_config_mutex);
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
}
void AxisGesture::setHiresMultiplier(double multiplier)
{
_config.setHiresMultiplier(multiplier);
}
AxisGesture::Config::Config(Device *device, libconfig::Setting &setting) :
Gesture::Config(device, setting, false)
{
try {
auto& axis = setting.lookup("axis");
if(axis.isNumber()) {
_axis = axis;
virtual_input->registerAxis(_axis);
} else if(axis.getType() == libconfig::Setting::TypeString) {
try {
_axis = virtual_input->toAxisCode(axis);
virtual_input->registerAxis(_axis);
} catch(InputDevice::InvalidEventCode& e) {
logPrintf(WARN, "Line %d: Invalid axis %s, skipping."
, axis.getSourceLine(), axis.c_str());
}
} else {
logPrintf(WARN, "Line %d: axis must be string or int, skipping.",
axis.getSourceLine(), axis.c_str());
throw InvalidGesture();
}
} catch(libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: axis is a required field, skippimg.",
setting.getSourceLine());
throw InvalidGesture();
}
try {
auto& multiplier = setting.lookup("axis_multiplier");
if(multiplier.isNumber()) {
if(multiplier.getType() == libconfig::Setting::TypeFloat)
_multiplier = multiplier;
else
_multiplier = (int)multiplier;
} else {
logPrintf(WARN, "Line %d: axis_multiplier must be a number, "
"setting to default (1).",
multiplier.getSourceLine());
}
} catch(libconfig::SettingNotFoundException& e) {
// Ignore
}
int low_res_axis = InputDevice::getLowResAxis(_axis);
if(low_res_axis != -1) {
_multiplier *= 120;
virtual_input->registerAxis(low_res_axis);
}
}
unsigned int AxisGesture::Config::axis() const
{
return _axis;
}
double AxisGesture::Config::multiplier() const
{
return _multiplier;
}
bool AxisGesture::wheelCompatibility() const
{
bool AxisGesture::wheelCompatibility() const {
return true;
}
void AxisGesture::Config::setHiresMultiplier(double multiplier)
{
if(_hires_multiplier == multiplier || multiplier == 0)
return;
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;
}
}
if(InputDevice::getLowResAxis(_axis) != -1) {
_multiplier *= _hires_multiplier;
_multiplier /= 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()));
}
}
_hires_multiplier = multiplier;
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,44 +18,45 @@
#ifndef LOGID_ACTION_AXISGESTURE_H
#define LOGID_ACTION_AXISGESTURE_H
#include "Gesture.h"
#include <actions/gesture/Gesture.h>
namespace logid {
namespace actions
{
class AxisGesture : public Gesture
{
public:
AxisGesture(Device* device, libconfig::Setting& root);
namespace logid::actions {
class AxisGesture : public Gesture {
public:
static const char* interface_name;
virtual void press(bool init_threshold=false);
virtual void release(bool primary=false);
virtual void move(int16_t axis);
AxisGesture(Device* device, config::AxisGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
virtual bool wheelCompatibility() const;
virtual bool metThreshold() const;
void press(bool init_threshold) final;
void setHiresMultiplier(double multiplier);
void release(bool primary) final;
class Config : public Gesture::Config
{
public:
Config(Device* device, libconfig::Setting& setting);
unsigned int axis() const;
double multiplier() const;
void setHiresMultiplier(double multiplier);
private:
unsigned int _axis;
double _multiplier = 1;
double _hires_multiplier = 1;
};
void move(int16_t axis) final;
protected:
int16_t _axis;
double _axis_remainder;
int _hires_remainder;
Config _config;
};
}}
[[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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -16,106 +16,73 @@
*
*/
#include <algorithm>
#include "Gesture.h"
#include "../../util/log.h"
#include "ReleaseGesture.h"
#include "ThresholdGesture.h"
#include "../../backend/hidpp20/features/ReprogControls.h"
#include "IntervalGesture.h"
#include "AxisGesture.h"
#include "NullGesture.h"
#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) : _device (device)
{
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) {
}
Gesture::Config::Config(Device* device, libconfig::Setting& root,
bool action_required) : _device (device)
{
if(action_required) {
try {
_action = Action::makeAction(_device,
root.lookup("action"));
} catch (libconfig::SettingNotFoundException &e) {
throw InvalidGesture("action is missing");
}
namespace {
template<typename T>
struct gesture_type {
typedef typename T::gesture type;
};
if(_action->reprogFlags() & backend::hidpp20::ReprogControls::RawXYDiverted)
throw InvalidGesture("gesture cannot require RawXY");
}
template<typename T>
struct gesture_type<const T> : gesture_type<T> {
};
_threshold = LOGID_GESTURE_DEFAULT_THRESHOLD;
try {
auto& threshold = root.lookup("threshold");
if(threshold.getType() == libconfig::Setting::TypeInt) {
_threshold = (int)threshold;
if(_threshold <= 0) {
_threshold = LOGID_GESTURE_DEFAULT_THRESHOLD;
logPrintf(WARN, "Line %d: threshold must be positive, setting "
"to default (%d)", threshold.getSourceLine(),
_threshold);
}
} else
logPrintf(WARN, "Line %d: threshold must be an integer, setting "
"to default (%d).", threshold.getSourceLine());
} catch(libconfig::SettingNotFoundException& e) {
// Ignore
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,
libconfig::Setting &setting)
{
if(!setting.isGroup()) {
logPrintf(WARN, "Line %d: Gesture is not a group, ignoring.",
setting.getSourceLine());
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();
}
try {
auto& gesture_mode = setting.lookup("mode");
if(gesture_mode.getType() != libconfig::Setting::TypeString) {
logPrintf(WARN, "Line %d: Gesture mode must be a string,"
"defaulting to OnRelease.",
gesture_mode.getSourceLine());
return std::make_shared<ReleaseGesture>(device, setting);
}
std::string type = gesture_mode;
std::transform(type.begin(), type.end(), type.begin(), ::tolower);
if(type == "onrelease")
return std::make_shared<ReleaseGesture>(device, setting);
else if(type == "onthreshold")
return std::make_shared<ThresholdGesture>(device, setting);
else if(type == "oninterval" || type == "onfewpixels")
return std::make_shared<IntervalGesture>(device, setting);
else if(type == "axis")
return std::make_shared<AxisGesture>(device, setting);
else if(type == "nopress")
return std::make_shared<NullGesture>(device, setting);
else {
logPrintf(WARN, "Line %d: Unknown gesture mode %s, defaulting to "
"OnRelease.", gesture_mode.getSourceLine(),
(const char*)gesture_mode);
return std::make_shared<ReleaseGesture>(device, setting);
}
} catch(libconfig::SettingNotFoundException& e) {
return std::make_shared<ReleaseGesture>(device, setting);
}
return makeGesture(device, config, parent);
}
int16_t Gesture::Config::threshold() const
{
return _threshold;
}
std::shared_ptr<Action> Gesture::Config::action()
{
return _action;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,59 +18,56 @@
#ifndef LOGID_ACTION_GESTURE_H
#define LOGID_ACTION_GESTURE_H
#include "../Action.h"
#include <utility>
#include <actions/Action.h>
#define LOGID_GESTURE_DEFAULT_THRESHOLD 50
namespace logid {
namespace actions
{
class InvalidGesture : public std::exception
{
namespace logid::actions {
class InvalidGesture : public std::exception {
public:
explicit InvalidGesture(std::string what="") : _what (what)
{
explicit InvalidGesture(std::string what = "") : _what(std::move(what)) {
}
virtual const char* what() const noexcept
{
[[nodiscard]] const char* what() const noexcept override {
return _what.c_str();
}
private:
std::string _what;
};
class Gesture
{
class Gesture : public ipcgull::interface {
public:
virtual void press(bool init_threshold=false) = 0;
virtual void release(bool primary=false) = 0;
virtual void press(bool init_threshold) = 0;
virtual void release(bool primary) = 0;
virtual void move(int16_t axis) = 0;
virtual bool wheelCompatibility() const = 0;
virtual bool metThreshold() const = 0;
[[nodiscard]] virtual bool wheelCompatibility() const = 0;
[[nodiscard]] virtual bool metThreshold() const = 0;
virtual ~Gesture() = default;
class Config
{
public:
Config(Device* device, libconfig::Setting& root,
bool action_required=true);
virtual int16_t threshold() const;
virtual std::shared_ptr<Action> action();
protected:
Device* _device;
std::shared_ptr<Action> _action;
int16_t _threshold;
};
static std::shared_ptr<Gesture> makeGesture(Device* device,
libconfig::Setting& setting);
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:
explicit Gesture(Device* device);
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,82 +15,103 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "IntervalGesture.h"
#include "../../util/log.h"
#include <actions/gesture/IntervalGesture.h>
#include <Configuration.h>
#include <util/log.h>
using namespace logid::actions;
IntervalGesture::IntervalGesture(Device *device, libconfig::Setting &root) :
Gesture (device), _config (device, root)
{
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)
{
_axis = init_threshold ? _config.threshold() : 0;
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(bool primary)
{
// Do nothing
(void)primary; // Suppress unused warning
void IntervalGesture::release([[maybe_unused]] bool primary) {
}
void IntervalGesture::move(int16_t axis)
{
_axis += axis;
if(_axis < _config.threshold())
void IntervalGesture::move(int16_t axis) {
std::shared_lock lock(_config_mutex);
if (!_config.interval.has_value())
return;
int16_t new_interval_count = (_axis - _config.threshold())/
_config.interval();
if(new_interval_count > _interval_pass_count) {
_config.action()->press();
_config.action()->release();
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
{
bool IntervalGesture::wheelCompatibility() const {
return true;
}
bool IntervalGesture::metThreshold() const
{
return _axis >= _config.threshold();
bool IntervalGesture::metThreshold() const {
std::shared_lock lock(_config_mutex);
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
}
IntervalGesture::Config::Config(Device *device, libconfig::Setting &setting) :
Gesture::Config(device, setting)
{
try {
auto& interval = setting.lookup("interval");
if(interval.getType() != libconfig::Setting::TypeInt) {
logPrintf(WARN, "Line %d: interval must be an integer, skipping.",
interval.getSourceLine());
throw InvalidGesture();
}
_interval = (int)interval;
} catch(libconfig::SettingNotFoundException& e) {
try {
// pixels is an alias for interval
auto& interval = setting.lookup("pixels");
if(interval.getType() != libconfig::Setting::TypeInt) {
logPrintf(WARN, "Line %d: pixels must be an integer, skipping.",
interval.getSourceLine());
throw InvalidGesture();
}
_interval = (int)interval;
} catch(libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: interval is a required field, skipping.",
setting.getSourceLine());
}
}
std::tuple<int, int> IntervalGesture::getConfig() const {
std::shared_lock lock(_config_mutex);
return {_config.interval.value_or(0), _config.threshold.value_or(0)};
}
int16_t IntervalGesture::Config::interval() const
{
return _interval;
}
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,37 +18,41 @@
#ifndef LOGID_ACTION_INTERVALGESTURE_H
#define LOGID_ACTION_INTERVALGESTURE_H
#include "Gesture.h"
#include <actions/gesture/Gesture.h>
namespace logid {
namespace actions
{
class IntervalGesture : public Gesture
{
namespace logid::actions {
class IntervalGesture : public Gesture {
public:
IntervalGesture(Device* device, libconfig::Setting& root);
static const char* interface_name;
virtual void press(bool init_threshold=false);
virtual void release(bool primary=false);
virtual void move(int16_t axis);
IntervalGesture(Device* device, config::IntervalGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
virtual bool wheelCompatibility() const;
virtual bool metThreshold() const;
void press(bool init_threshold) final;
class Config : public Gesture::Config
{
public:
Config(Device* device, libconfig::Setting& setting);
int16_t interval() const;
private:
int16_t _interval;
};
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:
int16_t _axis;
int16_t _interval_pass_count;
Config _config;
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,37 +15,37 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "NullGesture.h"
#include <actions/gesture/NullGesture.h>
#include <Configuration.h>
using namespace logid::actions;
NullGesture::NullGesture(Device *device, libconfig::Setting& setting) :
Gesture (device), _config (device, setting, false)
{
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() : 0;
void NullGesture::press(bool init_threshold) {
_axis = init_threshold ? _config.threshold.value_or(
defaults::gesture_threshold) : 0;
}
void NullGesture::release(bool primary)
{
void NullGesture::release(bool primary) {
// Do nothing
(void)primary; // Suppress unused warning
(void) primary; // Suppress unused warning
}
void NullGesture::move(int16_t axis)
{
void NullGesture::move(int16_t axis) {
_axis += axis;
}
bool NullGesture::wheelCompatibility() const
{
bool NullGesture::wheelCompatibility() const {
return true;
}
bool NullGesture::metThreshold() const
{
return _axis > _config.threshold();
bool NullGesture::metThreshold() const {
return _axis > _config.threshold.value_or(defaults::gesture_threshold);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,26 +18,31 @@
#ifndef LOGID_ACTION_NULLGESTURE_H
#define LOGID_ACTION_NULLGESTURE_H
#include "Gesture.h"
#include <actions/gesture/Gesture.h>
namespace logid {
namespace actions
{
class NullGesture : public Gesture
{
namespace logid::actions {
class NullGesture : public Gesture {
public:
NullGesture(Device* device, libconfig::Setting& setting);
static const char* interface_name;
virtual void press(bool init_threshold=false);
virtual void release(bool primary=false);
virtual void move(int16_t axis);
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;
virtual bool wheelCompatibility() const;
virtual bool metThreshold() const;
protected:
int16_t _axis;
Gesture::Config _config;
int32_t _axis{};
config::NoGesture& _config;
};
}}
}
#endif //LOGID_ACTION_NULLGESTURE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,39 +15,75 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ReleaseGesture.h"
#include <actions/gesture/ReleaseGesture.h>
#include <Configuration.h>
using namespace logid::actions;
ReleaseGesture::ReleaseGesture(Device *device, libconfig::Setting &root) :
Gesture (device), _config (device, root)
{
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)
{
_axis = init_threshold ? _config.threshold() : 0;
}
void ReleaseGesture::release(bool primary)
{
if(metThreshold() && primary) {
_config.action()->press();
_config.action()->release();
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::move(int16_t axis)
{
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
{
bool ReleaseGesture::wheelCompatibility() const {
return false;
}
bool ReleaseGesture::metThreshold() const
{
return _axis >= _config.threshold();
}
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,27 +18,37 @@
#ifndef LOGID_ACTION_RELEASEGESTURE_H
#define LOGID_ACTION_RELEASEGESTURE_H
#include "Gesture.h"
#include <actions/gesture/Gesture.h>
namespace logid {
namespace actions
{
class ReleaseGesture : public Gesture
{
namespace logid::actions {
class ReleaseGesture : public Gesture {
public:
ReleaseGesture(Device* device, libconfig::Setting& root);
static const char* interface_name;
virtual void press(bool init_threshold=false);
virtual void release(bool primary=false);
virtual void move(int16_t axis);
ReleaseGesture(Device* device, config::ReleaseGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
virtual bool wheelCompatibility() const;
virtual bool metThreshold() const;
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:
int16_t _axis;
Gesture::Config _config;
int32_t _axis{};
std::shared_ptr<Action> _action;
config::ReleaseGesture& _config;
};
}}
}
#endif //LOGID_ACTION_RELEASEGESTURE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne, michtere
* 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
@ -15,45 +15,81 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ThresholdGesture.h"
#include <actions/gesture/ThresholdGesture.h>
#include <Configuration.h>
#include <util/log.h>
using namespace logid::actions;
ThresholdGesture::ThresholdGesture(Device *device, libconfig::Setting &root) :
Gesture (device), _config (device, root)
{
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)
{
_axis = init_threshold ? _config.threshold() : 0;
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(bool primary)
{
(void)primary; // Suppress unused warning
void ThresholdGesture::release([[maybe_unused]] bool primary) {
this->_executed = false;
}
void ThresholdGesture::move(int16_t axis)
{
void ThresholdGesture::move(int16_t axis) {
_axis += axis;
if(!this->_executed && metThreshold()) {
_config.action()->press();
_config.action()->release();
if (!this->_executed && metThreshold()) {
if (_action) {
_action->press();
_action->release();
}
this->_executed = true;
}
}
bool ThresholdGesture::metThreshold() const
{
return _axis >= _config.threshold();
bool ThresholdGesture::metThreshold() const {
std::shared_lock lock(_config_mutex);
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
}
bool ThresholdGesture::wheelCompatibility() const
{
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne, michtere
* 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
@ -18,31 +18,40 @@
#ifndef LOGID_ACTION_THRESHOLDGESTURE_H
#define LOGID_ACTION_THRESHOLDGESTURE_H
#include "Gesture.h"
#include <actions/gesture/Gesture.h>
namespace logid {
namespace actions
{
class ThresholdGesture : public Gesture
{
namespace logid::actions {
class ThresholdGesture : public Gesture {
public:
ThresholdGesture(Device* device, libconfig::Setting& root);
static const char* interface_name;
virtual void press(bool init_threshold=false);
virtual void release(bool primary=false);
virtual void move(int16_t axis);
ThresholdGesture(Device* device, config::ThresholdGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
virtual bool metThreshold() const;
void press(bool init_threshold) final;
virtual bool wheelCompatibility() const;
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:
int16_t _axis;
Gesture::Config _config;
int32_t _axis{};
std::shared_ptr<actions::Action> _action;
config::ThresholdGesture& _config;
private:
bool _executed = false;
};
}}
}
#endif //LOGID_ACTION_THRESHOLDGESTURE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -16,9 +16,14 @@
*
*/
#include "Error.h"
#include <backend/Error.h>
const char *logid::backend::TimeoutError::what() const noexcept
{
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -21,14 +21,18 @@
#include <stdexcept>
namespace logid {
namespace backend {
class TimeoutError: public std::exception
{
public:
TimeoutError() = default;
const char* what() const noexcept override;
};
}}
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

@ -0,0 +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_EVENTHANDLERLIST_H
#define LOGID_EVENTHANDLERLIST_H
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <list>
template <class T>
class EventHandlerLock;
template <class T>
struct EventHandlerList {
typedef std::list<typename T::EventHandler> list_t;
typedef list_t::const_iterator iterator_t;
std::list<typename T::EventHandler> list;
mutable std::shared_mutex mutex;
void remove(iterator_t iterator) {
std::unique_lock lock(mutex);
list.erase(iterator);
}
};
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,381 +0,0 @@
/*
* Copyright 2019-2020 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <cassert>
#include "Report.h"
#include "Receiver.h"
#include "Error.h"
#include "../../util/thread.h"
using namespace logid::backend::dj;
using namespace logid::backend;
InvalidReceiver::InvalidReceiver(Reason reason) : _reason (reason)
{
}
const char* InvalidReceiver::what() const noexcept
{
switch(_reason) {
case NoDJReports:
return "No DJ reports";
default:
return "Invalid receiver";
}
}
InvalidReceiver::Reason InvalidReceiver::code() const noexcept
{
return _reason;
}
Receiver::Receiver(std::string path) :
_raw_device (std::make_shared<raw::RawDevice>(std::move(path))),
_hidpp10_device (_raw_device, hidpp::DefaultDevice)
{
if(!supportsDjReports(_raw_device->reportDescriptor()))
throw InvalidReceiver(InvalidReceiver::NoDJReports);
}
void Receiver::enumerateDj()
{
_sendDjRequest(hidpp::DefaultDevice, GetPairedDevices,{});
}
Receiver::NotificationFlags Receiver::getHidppNotifications()
{
auto response = _hidpp10_device.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::enableHidppNotifications(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);
_hidpp10_device.setRegister(EnableHidppNotifications, request,
hidpp::ReportType::Short);
}
void Receiver::enumerateHidpp()
{
/* This isn't in the documentation but this is how solaar does it
* All I know is that when (p0 & 2), devices are enumerated
*/
_hidpp10_device.setRegister(ConnectionState, {2},
hidpp::ReportType::Short);
}
///TODO: Investigate usage
uint8_t Receiver::getConnectionState(hidpp::DeviceIndex index)
{
auto response = _hidpp10_device.getRegister(ConnectionState, {index},
hidpp::ReportType::Short);
return response[0];
}
void Receiver::startPairing(uint8_t timeout)
{
///TODO: Device number == Device index?
std::vector<uint8_t> request(3);
request[0] = 1;
request[1] = hidpp::DefaultDevice;
request[2] = timeout;
_hidpp10_device.setRegister(DevicePairing, request,
hidpp::ReportType::Short);
}
void Receiver::stopPairing()
{
///TODO: Device number == Device index?
std::vector<uint8_t> request(3);
request[0] = 2;
request[1] = hidpp::DefaultDevice;
_hidpp10_device.setRegister(DevicePairing, request,
hidpp::ReportType::Short);
}
void Receiver::disconnect(hidpp::DeviceIndex index)
{
///TODO: Device number == Device index?
std::vector<uint8_t> request(3);
request[0] = 3;
request[1] = index;
_hidpp10_device.setRegister(DevicePairing, request,
hidpp::ReportType::Short);
}
std::map<hidpp::DeviceIndex, uint8_t> Receiver::getDeviceActivity()
{
auto response = _hidpp10_device.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;
request[0] += 0x1f;
auto response = _hidpp10_device.getRegister(PairingInfo, request,
hidpp::ReportType::Long);
struct PairingInfo info{};
info.destinationId = response[1];
info.reportInterval = response[2];
info.pid = response[4];
info.pid |= (response[3] << 8);
info.deviceType = static_cast<DeviceType::DeviceType>(response[7]);
return info;
}
struct Receiver::ExtendedPairingInfo
Receiver::getExtendedPairingInfo(hidpp::DeviceIndex index)
{
std::vector<uint8_t> request(1);
request[0] = index;
request[0] += 0x2f;
auto response = _hidpp10_device.getRegister(PairingInfo, request,
hidpp::ReportType::Long);
ExtendedPairingInfo info{};
info.serialNumber = 0;
for(uint8_t i = 0; i < 4; i++)
info.serialNumber |= (response[i+1] << 8*i);
for(uint8_t i = 0; i < 4; i++)
info.reportTypes[i] = response[i + 5];
uint8_t psl = response[8] & 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(1);
request[0] = index;
request[0] += 0x3f;
auto response = _hidpp10_device.getRegister(PairingInfo, request,
hidpp::ReportType::Long);
uint8_t size = response[1];
assert(size <= 14);
std::string name(size, ' ');
for(std::size_t i = 0; i < size; i++)
name[i] = 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);
hidpp::DeviceConnectionEvent event{};
event.index = report.deviceIndex();
event.unifying = ((report.address() & 0b111) == 0x04);
event.deviceType = static_cast<DeviceType::DeviceType>(
report.paramBegin()[0] & 0x0f);
event.softwarePresent = report.paramBegin()[0] & (1<<4);
event.encrypted = report.paramBegin()[0] & (1<<5);
event.linkEstablished = !(report.paramBegin()[0] & (1<<6));
event.withPayload = report.paramBegin()[0] & (1<<7);
event.fromTimeoutCheck = false;
event.pid =(report.paramBegin()[2] << 8);
event.pid |= report.paramBegin()[1];
return event;
}
void Receiver::_handleDjEvent(Report& report)
{
for(auto& handler : _dj_event_handlers)
if(handler.second->condition(report))
handler.second->callback(report);
}
void Receiver::_handleHidppEvent(hidpp::Report &report)
{
for(auto& handler : _hidpp_event_handlers)
if(handler.second->condition(report))
handler.second->callback(report);
}
void Receiver::addDjEventHandler(const std::string& nickname,
const std::shared_ptr<EventHandler>& handler)
{
assert(_dj_event_handlers.find(nickname) == _dj_event_handlers.end());
_dj_event_handlers.emplace(nickname, handler);
}
void Receiver::removeDjEventHandler(const std::string &nickname)
{
_dj_event_handlers.erase(nickname);
}
const std::map<std::string, std::shared_ptr<EventHandler>>&
Receiver::djEventHandlers()
{
return _dj_event_handlers;
}
void Receiver::addHidppEventHandler(const std::string& nickname,
const std::shared_ptr<hidpp::EventHandler>& handler)
{
assert(_hidpp_event_handlers.find(nickname) == _hidpp_event_handlers.end());
_hidpp_event_handlers.emplace(nickname, handler);
}
void Receiver::removeHidppEventHandler(const std::string &nickname)
{
_hidpp_event_handlers.erase(nickname);
}
const std::map<std::string, std::shared_ptr<hidpp::EventHandler>>&
Receiver::hidppEventHandlers()
{
return _hidpp_event_handlers;
}
void Receiver::_sendDjRequest(hidpp::DeviceIndex index, uint8_t function,
const std::vector<uint8_t>&& params)
{
assert(params.size() <= LongParamLength);
Report::Type type = params.size() <= ShortParamLength ?
ReportType::Short : ReportType::Long;
Report request(type, index, function);
std::copy(params.begin(), params.end(), request.paramBegin());
_raw_device->sendReportNoResponse(request.rawData());
}
Receiver::ConnectionStatusEvent Receiver::connectionStatusEvent(Report& report)
{
assert(report.feature() == ConnectionStatus);
ConnectionStatusEvent event{};
event.index = report.index();
event.linkLost = report.paramBegin()[0];
return event;
}
void Receiver::listen()
{
if(!_raw_device->isListening())
_raw_device->listenAsync();
if(_raw_device->eventHandlers().find("RECV_HIDPP") ==
_raw_device->eventHandlers().end()) {
// Pass all HID++ events on DefaultDevice to handleHidppEvent
std::shared_ptr<raw::RawEventHandler> hidpp_handler =
std::make_shared<raw::RawEventHandler>();
hidpp_handler->condition = [](std::vector<uint8_t>& report)->bool
{
return (report[hidpp::Offset::Type] == hidpp::Report::Type::Short ||
report[hidpp::Offset::Type] == hidpp::Report::Type::Long);
};
hidpp_handler->callback = [this](std::vector<uint8_t>& report)
->void {
hidpp::Report _report(report);
this->_handleHidppEvent(_report);
};
_raw_device->addEventHandler("RECV_HIDPP", hidpp_handler);
}
if(_raw_device->eventHandlers().find("RECV_DJ") ==
_raw_device->eventHandlers().end()) {
// Pass all DJ events with device index to handleDjEvent
std::shared_ptr<raw::RawEventHandler> dj_handler =
std::make_shared<raw::RawEventHandler>();
dj_handler->condition = [](std::vector<uint8_t>& report)->bool
{
return (report[Offset::Type] == Report::Type::Short ||
report[Offset::Type] == Report::Type::Long);
};
dj_handler->callback = [this](std::vector<uint8_t>& report)->void
{
Report _report(report);
this->_handleDjEvent(_report);
};
_raw_device->addEventHandler("RECV_DJ", dj_handler);
}
}
void Receiver::stopListening()
{
_raw_device->removeEventHandler("RECV_HIDPP");
_raw_device->removeEventHandler("RECV_DJ");
if(_raw_device->eventHandlers().empty())
_raw_device->stopListener();
}
std::shared_ptr<raw::RawDevice> Receiver::rawDevice() const
{
return _raw_device;
}

View File

@ -1,213 +0,0 @@
/*
* Copyright 2019-2020 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_DJ_RECEIVER_H
#define LOGID_BACKEND_DJ_RECEIVER_H
#include <cstdint>
#include "../raw/RawDevice.h"
#include "Report.h"
#include "../hidpp/Report.h"
#include "../hidpp10/Device.h"
namespace logid {
namespace backend {
namespace dj
{
struct EventHandler
{
std::function<bool(Report&)> condition;
std::function<void(Report&)> callback;
};
class InvalidReceiver : public std::exception
{
public:
enum Reason
{
NoDJReports
};
explicit InvalidReceiver(Reason reason);
const char* what() const noexcept override;
Reason code() const noexcept;
private:
Reason _reason;
};
class Receiver final
{
public:
explicit Receiver(std::string path);
enum DjEvents : uint8_t
{
DeviceDisconnection = 0x40,
DeviceConnection = 0x41,
ConnectionStatus = 0x42
};
enum DjCommands : uint8_t
{
SwitchAndKeepAlive = 0x80,
GetPairedDevices = 0x81
};
void enumerateDj();
struct ConnectionStatusEvent
{
hidpp::DeviceIndex index;
bool linkLost;
};
ConnectionStatusEvent connectionStatusEvent(dj::Report& report);
/* 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 HidppEvents : uint8_t
{
// These events are identical to their DJ counterparts
// DeviceDisconnection = 0x40,
// DeviceConnection = 0x41,
LockingChange = 0x4a
};
enum HidppRegisters : uint8_t
{
EnableHidppNotifications = 0x00,
ConnectionState = 0x02,
DevicePairing = 0xb2,
DeviceActivity = 0xb3,
PairingInfo = 0xb5
};
struct NotificationFlags
{
bool deviceBatteryStatus;
bool receiverWirelessNotifications;
bool receiverSoftwarePresent;
};
NotificationFlags getHidppNotifications();
void enableHidppNotifications(NotificationFlags flags);
void enumerateHidpp();
uint8_t getConnectionState(hidpp::DeviceIndex index);
void startPairing(uint8_t timeout = 0);
void stopPairing();
void disconnect(hidpp::DeviceIndex index);
std::map<hidpp::DeviceIndex, uint8_t> getDeviceActivity();
struct PairingInfo
{
uint8_t destinationId;
uint8_t reportInterval;
uint16_t pid;
DeviceType::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);
void listen();
void stopListening();
void addDjEventHandler(const std::string& nickname,
const std::shared_ptr<EventHandler>& handler);
void removeDjEventHandler(const std::string& nickname);
const std::map<std::string, std::shared_ptr<EventHandler>>&
djEventHandlers();
void addHidppEventHandler(const std::string& nickname,
const std::shared_ptr<hidpp::EventHandler>& handler);
void removeHidppEventHandler(const std::string& nickname);
const std::map<std::string, std::shared_ptr<hidpp::EventHandler>>&
hidppEventHandlers();
std::shared_ptr<raw::RawDevice> rawDevice() const;
private:
void _sendDjRequest(hidpp::DeviceIndex index, uint8_t function,
const std::vector<uint8_t>&& params);
void _handleDjEvent(dj::Report& report);
void _handleHidppEvent(hidpp::Report& report);
std::map<std::string, std::shared_ptr<EventHandler>>
_dj_event_handlers;
std::map<std::string, std::shared_ptr<hidpp::EventHandler>>
_hidpp_event_handlers;
std::shared_ptr<raw::RawDevice> _raw_device;
hidpp10::Device _hidpp10_device;
};
}
namespace hidpp
{
struct DeviceConnectionEvent
{
hidpp::DeviceIndex index;
uint16_t pid;
dj::DeviceType::DeviceType deviceType;
bool unifying;
bool softwarePresent;
bool encrypted;
bool linkEstablished;
bool withPayload;
bool fromTimeoutCheck = false; // Fake field
};
}}}
#endif //LOGID_BACKEND_DJ_RECEIVER_H

View File

@ -1,137 +0,0 @@
/*
* Copyright 2019-2020 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ReceiverMonitor.h"
#include "../../util/task.h"
#include "../../util/log.h"
#include <utility>
#include <cassert>
using namespace logid::backend::dj;
ReceiverMonitor::ReceiverMonitor(std::string path) : _receiver (
std::make_shared<Receiver>(std::move(path)))
{
assert(_receiver->hidppEventHandlers().find("RECVMON") ==
_receiver->hidppEventHandlers().end());
assert(_receiver->djEventHandlers().find("RECVMON") ==
_receiver->djEventHandlers().end());
Receiver::NotificationFlags notification_flags{
true,
true,
true};
_receiver->enableHidppNotifications(notification_flags);
}
ReceiverMonitor::~ReceiverMonitor()
{
this->stop();
}
void ReceiverMonitor::run()
{
_receiver->listen();
if(_receiver->hidppEventHandlers().find("RECVMON") ==
_receiver->hidppEventHandlers().end()) {
std::shared_ptr<hidpp::EventHandler> event_handler =
std::make_shared<hidpp::EventHandler>();
event_handler->condition = [](hidpp::Report &report) -> bool {
return (report.subId() == Receiver::DeviceConnection ||
report.subId() == Receiver::DeviceDisconnection);
};
event_handler->callback = [this](hidpp::Report &report) -> void {
/* Running in a new thread prevents deadlocks since the
* receiver may be enumerating.
*/
task::spawn({[this, report]() {
if (report.subId() == Receiver::DeviceConnection)
this->addDevice(this->_receiver->deviceConnectionEvent
(report));
else if (report.subId() == Receiver::DeviceDisconnection)
this->removeDevice(this->_receiver->
deviceDisconnectionEvent(report));
}}, {[report, path=this->_receiver->rawDevice()->hidrawPath()]
(std::exception& e) {
if(report.subId() == Receiver::DeviceConnection)
logPrintf(ERROR, "Failed to add device %d to receiver "
"on %s: %s", report.deviceIndex(),
path.c_str(), e.what());
else if(report.subId() == Receiver::DeviceDisconnection)
logPrintf(ERROR, "Failed to remove device %d from "
"receiver on %s: %s", report.deviceIndex()
,path.c_str(), e.what());
}});
};
_receiver->addHidppEventHandler("RECVMON", event_handler);
}
enumerate();
}
void ReceiverMonitor::stop()
{
_receiver->removeHidppEventHandler("RECVMON");
_receiver->stopListening();
}
void ReceiverMonitor::enumerate()
{
_receiver->enumerateHidpp();
}
void ReceiverMonitor::waitForDevice(hidpp::DeviceIndex index)
{
std::string nickname = "WAIT_DEV_" + std::to_string(index);
auto handler = std::make_shared<raw::RawEventHandler>();
handler->condition = [index](std::vector<uint8_t>& report)->bool {
return report[Offset::DeviceIndex] == index;
};
handler->callback = [this, index, nickname](std::vector<uint8_t>& report) {
(void)report; // Suppress unused warning
hidpp::DeviceConnectionEvent event{};
event.withPayload = false;
event.linkEstablished = true;
event.index = index;
event.fromTimeoutCheck = true;
task::spawn({[this, event, nickname]() {
_receiver->rawDevice()->removeEventHandler(nickname);
this->addDevice(event);
}}, {[path=_receiver->rawDevice()->hidrawPath(), event]
(std::exception& e) {
logPrintf(ERROR, "Failed to add device %d to receiver "
"on %s: %s", event.index,
path.c_str(), e.what());
}});
};
_receiver->rawDevice()->addEventHandler(nickname, handler);
}
std::shared_ptr<Receiver> ReceiverMonitor::receiver() const
{
return _receiver;
}

View File

@ -1,61 +0,0 @@
/*
* Copyright 2019-2020 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_DJ_RECEIVERMONITOR_H
#define LOGID_BACKEND_DJ_RECEIVERMONITOR_H
#include <cstdint>
#include <string>
#include "Receiver.h"
#include "../hidpp/defs.h"
namespace logid {
namespace backend {
namespace dj
{
// This class will run on the RawDevice thread,
class ReceiverMonitor
{
public:
explicit ReceiverMonitor(std::string path);
virtual ~ReceiverMonitor();
void enumerate();
void run();
void stop();
protected:
virtual void addDevice(hidpp::DeviceConnectionEvent event) = 0;
virtual void removeDevice(hidpp::DeviceIndex index) = 0;
void waitForDevice(hidpp::DeviceIndex index);
// Internal methods for derived class
void _pair(uint8_t timeout = 0);
void _stopPairing();
void _unpair();
std::shared_ptr<Receiver> receiver() const;
private:
std::shared_ptr<Receiver> _receiver;
};
}}}
#endif //LOGID_BACKEND_DJ_RECEIVERMONITOR_H

View File

@ -1,135 +0,0 @@
/*
* Copyright 2019-2020 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <array>
#include <algorithm>
#include <cassert>
#include "Report.h"
using namespace logid::backend::dj;
using namespace logid::backend;
static const std::array<uint8_t, 34> DJReportDesc = {
0xA1, 0x01, // Collection (Application)
0x85, 0x20, // Report ID (32)
0x95, 0x0E, // Report Count (14)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x09, 0x41, // Usage (0x41)
0x81, 0x00, // Input (Data, Array, Absolute)
0x09, 0x41, // Usage (0x41)
0x91, 0x00, // Output (Data, Array, Absolute)
0x85, 0x21, // Report ID (33)
0x95, 0x1F, // Report Count (31)
0x09, 0x42, // Usage (0x42)
0x81, 0x00, // Input (Data, Array, Absolute)
0x09, 0x42, // Usage (0x42)
0x91, 0x00, // Output (Data, Array, Absolute)
0xC0 // End Collection
};
static const std::array<uint8_t, 39> DJReportDesc2 = {
0xA1, 0x01, // Collection (Application)
0x85, 0x20, // Report ID (32)
0x75, 0x08, // Report Size (8)
0x95, 0x0E, // Report Count (14)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x09, 0x41, // Usage (0x41)
0x81, 0x00, // Input (Data, Array, Absolute)
0x09, 0x41, // Usage (0x41)
0x91, 0x00, // Output (Data, Array, Absolute)
0x85, 0x21, // Report ID (33)
0x95, 0x1F, // Report Count (31)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x09, 0x42, // Usage (0x42)
0x81, 0x00, // Input (Data, Array, Absolute)
0x09, 0x42, // Usage (0x42)
0x91, 0x00, // Output (Data, Array, Absolute)
0xC0 // End Collection
};
bool dj::supportsDjReports(std::vector<uint8_t>&& rdesc)
{
auto it = std::search(rdesc.begin(), rdesc.end(),
DJReportDesc.begin(), DJReportDesc.end());
if(it == rdesc.end())
it = std::search(rdesc.begin(), rdesc.end(),
DJReportDesc2.begin(), DJReportDesc2.end());
return it != rdesc.end();
}
Report::Report(std::vector<uint8_t>& data) : _data (data)
{
switch(data[Offset::Type]) {
case ReportType::Short:
_data.resize(HeaderLength+ShortParamLength);
break;
case ReportType::Long:
_data.resize(HeaderLength+LongParamLength);
break;
default:
assert(false);
}
}
Report::Report(Report::Type type, hidpp::DeviceIndex index, uint8_t feature)
{
switch(type) {
case ReportType::Short:
_data.resize(HeaderLength+ShortParamLength);
break;
case ReportType::Long:
_data.resize(HeaderLength+LongParamLength);
break;
default:
assert(false);
}
_data[Offset::Type] = type;
_data[Offset::DeviceIndex] = index;
_data[Offset::Feature] = feature;
}
Report::Type Report::type() const
{
return static_cast<Type>(_data[Offset::Type]);
}
hidpp::DeviceIndex Report::index() const
{
return static_cast<hidpp::DeviceIndex>(_data[Offset::DeviceIndex]);
}
uint8_t Report::feature() const
{
return _data[Offset::Feature];
}
std::vector<uint8_t>::iterator Report::paramBegin()
{
return _data.begin() + Offset::Parameters;
}
std::vector<uint8_t> Report::rawData() const
{
return _data;
}

View File

@ -1,58 +0,0 @@
/*
* Copyright 2019-2020 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_DJ_REPORT_H
#define LOGID_BACKEND_DJ_REPORT_H
#include <cstdint>
#include "../raw/RawDevice.h"
#include "defs.h"
#include "../hidpp/defs.h"
namespace logid {
namespace backend {
namespace dj
{
namespace Offset
{
static constexpr uint8_t Type = 0;
static constexpr uint8_t DeviceIndex = 1;
static constexpr uint8_t Feature = 2;
static constexpr uint8_t Parameters = 3;
}
bool supportsDjReports(std::vector<uint8_t>&& rdesc);
class Report
{
public:
typedef ReportType::ReportType Type;
explicit Report(std::vector<uint8_t>& data);
Report(Type type, hidpp::DeviceIndex index, uint8_t feature);
Type type() const;
hidpp::DeviceIndex index() const;
uint8_t feature() const;
std::vector<uint8_t>::iterator paramBegin();
std::vector<uint8_t> rawData() const;
private:
std::vector<uint8_t> _data;
};
}}}
#endif //LOGID_BACKEND_DJ_REPORT_H

View File

@ -1,59 +0,0 @@
/*
* Copyright 2019-2020 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_DJ_DEFS_H
#define LOGID_BACKEND_DJ_DEFS_H
#include <cstdint>
namespace logid {
namespace backend {
namespace dj
{
namespace ReportType
{
enum ReportType : uint8_t
{
Short = 0x20,
Long = 0x21
};
}
namespace DeviceType
{
enum DeviceType : uint8_t
{
Unknown = 0x00,
Keyboard = 0x01,
Mouse = 0x02,
Numpad = 0x03,
Presenter = 0x04,
/* 0x05-0x07 is reserved */
Trackball = 0x08,
Touchpad = 0x09
};
}
static constexpr uint8_t ErrorFeature = 0x7f;
static constexpr std::size_t HeaderLength = 3;
static constexpr std::size_t ShortParamLength = 12;
static constexpr std::size_t LongParamLength = 29;
}}}
#endif //LOGID_BACKEND_DJ_DEFS_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -16,130 +16,195 @@
*
*/
#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>
#include "../../util/thread.h"
#include "Device.h"
#include "Report.h"
#include "../hidpp20/features/Root.h"
#include "../hidpp20/features/DeviceName.h"
#include "../hidpp20/Error.h"
#include "../hidpp10/Error.h"
#include "../dj/Receiver.h"
using namespace logid::backend;
using namespace logid::backend::hidpp;
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";
default:
return "Invalid device";
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
{
Device::InvalidDevice::Reason Device::InvalidDevice::code() const noexcept {
return _reason;
}
Device::Device(const std::string& path, DeviceIndex index):
_raw_device (std::make_shared<raw::RawDevice>(path)), _receiver (nullptr),
_path (path), _index (index)
{
_init();
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(std::make_shared<raw::RawDevice>(path, monitor)),
_receiver(nullptr), _path(path), _index(index) {
}
Device::Device(std::shared_ptr<raw::RawDevice> raw_device, DeviceIndex index) :
_raw_device (std::move(raw_device)), _receiver (nullptr),
_path (_raw_device->hidrawPath()), _index (index)
{
_init();
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(std::shared_ptr<dj::Receiver> receiver,
hidpp::DeviceConnectionEvent event) :
_raw_device (receiver->rawDevice()), _receiver (receiver),
_path (receiver->rawDevice()->hidrawPath()), _index (event.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)
if (!event.linkEstablished)
throw InvalidDevice(InvalidDevice::Asleep);
if(!event.fromTimeoutCheck)
if (!event.fromTimeoutCheck)
_pid = event.pid;
else
_pid = receiver->getPairingInfo(_index).pid;
_init();
}
Device::Device(std::shared_ptr<dj::Receiver> receiver,
DeviceIndex index) : _raw_device (receiver->rawDevice()),
_receiver (receiver), _path (receiver->rawDevice()->hidrawPath()),
_index (index)
{
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;
_init();
}
std::string Device::devicePath() const
{
const std::string& Device::devicePath() const {
return _path;
}
DeviceIndex Device::deviceIndex() const
{
DeviceIndex Device::deviceIndex() const {
return _index;
}
std::tuple<uint8_t, uint8_t> Device::version() const
{
const std::tuple<uint8_t, uint8_t>& Device::version() const {
return _version;
}
void Device::_init()
{
_listening = false;
_supported_reports = getSupportedReports(_raw_device->reportDescriptor());
if(!_supported_reports)
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 (_index == hidpp::DefaultDevice) {
_raw_handler = _raw_device->addEventHandler(
{[](const std::vector<uint8_t>& report) -> bool {
return (report[Offset::Type] == Report::Type::Short ||
report[Offset::Type] == Report::Type::Long);
},
[self_weak = _self](const std::vector<uint8_t>& report) -> void {
Report _report(report);
if(auto self = self_weak.lock())
self->handleEvent(_report);
}});
try {
auto rsp = sendReport({ReportType::Short, _index,
hidpp20::FeatureID::ROOT, hidpp20::Root::Ping,
hidpp::softwareID});
if (rsp.deviceIndex() != _index)
throw InvalidDevice(InvalidDevice::VirtualNode);
} catch (hidpp10::Error& e) {
if (e.deviceIndex() != _index)
throw InvalidDevice(InvalidDevice::VirtualNode);
} catch (hidpp20::Error& e) {
/* This shouldn't happen, the device must not be ready */
if (e.deviceIndex() != _index)
throw InvalidDevice(InvalidDevice::VirtualNode);
else
throw DeviceNotReady();
}
}
_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::EssentialRoot root(this);
hidpp20::Root root(this);
_version = root.getVersion();
} catch(hidpp10::Error &e) {
} catch (hidpp10::Error& e) {
// Valid HID++ 1.0 devices should send an InvalidSubID error
if(e.code() != hidpp10::Error::InvalidSubID)
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();
}
if(!_receiver) {
/* 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) {
if (std::get<0>(_version) >= 2) {
try {
hidpp20::EssentialDeviceName deviceName(this);
hidpp20::DeviceName deviceName(this);
_name = deviceName.getName();
} catch(hidpp20::UnsupportedFeature &e) {
} catch (hidpp20::UnsupportedFeature& e) {
_name = _raw_device->name();
}
} else {
_name = _raw_device->name();
}
} else {
if(std::get<0>(_version) >= 2) {
if (std::get<0>(_version) >= 2) {
try {
hidpp20::EssentialDeviceName deviceName(this);
hidpp20::DeviceName deviceName(this);
_name = deviceName.getName();
} catch(hidpp20::UnsupportedFeature &e) {
} catch (hidpp20::UnsupportedFeature& e) {
_name = _receiver->getDeviceName(_index);
}
} else {
@ -148,121 +213,135 @@ void Device::_init()
}
}
Device::~Device()
{
if(_listening)
_raw_device->removeEventHandler("DEV_" + std::to_string(_index));
EventHandlerLock<Device> Device::addEventHandler(EventHandler handler) {
std::unique_lock lock(_event_handlers->mutex);
_event_handlers->list.emplace_front(std::move(handler));
return {_event_handlers, _event_handlers->list.cbegin()};
}
void Device::addEventHandler(const std::string& nickname,
const std::shared_ptr<EventHandler>& handler)
{
assert(_event_handlers.find(nickname) == _event_handlers.end());
void Device::handleEvent(Report& report) {
if (responseReport(report))
return;
_event_handlers.emplace(nickname, handler);
std::shared_lock lock(_event_handlers->mutex);
for (auto& handler: _event_handlers->list)
if (handler.condition(report))
handler.callback(report);
}
void Device::removeEventHandler(const std::string& nickname)
{
_event_handlers.erase(nickname);
}
Report Device::sendReport(const Report& report) {
/* Must complete transaction before next send */
std::lock_guard send_lock(_send_mutex);
_sent_sub_id = report.subId();
std::unique_lock lock(_response_mutex);
_sendReport(report);
const std::map<std::string, std::shared_ptr<EventHandler>>&
Device::eventHandlers()
{
return _event_handlers;
}
bool valid = _response_cv.wait_for(
lock, io_timeout, [this]() {
return _response.has_value();
});
void Device::handleEvent(Report& report)
{
for(auto& handler : _event_handlers)
if(handler.second->condition(report))
handler.second->callback(report);
}
Report Device::sendReport(Report& report)
{
switch(report.type())
{
case Report::Type::Short:
if(!(_supported_reports & HIDPP_REPORT_SHORT_SUPPORTED))
report.setType(Report::Type::Long);
break;
case Report::Type::Long:
/* Report can be truncated, but that isn't a good idea. */
assert(_supported_reports & HIDPP_REPORT_LONG_SUPPORTED);
if (!valid) {
_sent_sub_id.reset();
throw TimeoutError();
}
auto raw_response = _raw_device->sendReport(report.rawReport());
Response response = _response.value();
_response.reset();
_sent_sub_id.reset();
Report response(raw_response);
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;
Report::Hidpp10Error hidpp10_error{};
if(response.isError10(&hidpp10_error))
throw hidpp10::Error(hidpp10_error.error_code);
Report::Hidpp20Error hidpp20_error{};
if(response.isError20(&hidpp20_error))
throw hidpp20::Error(hidpp20_error.error_code);
return response;
}
void Device::sendReportNoResponse(Report &report)
{
switch(report.type())
{
case Report::Type::Short:
if(!(_supported_reports & HIDPP_REPORT_SHORT_SUPPORTED))
report.setType(Report::Type::Long);
break;
case Report::Type::Long:
/* Report can be truncated, but that isn't a good idea. */
assert(_supported_reports & HIDPP_REPORT_LONG_SUPPORTED);
if (report.isError10(hidpp10_error)) {
sub_id = hidpp10_error.sub_id;
response = hidpp10_error;
} else if (report.isError20(hidpp20_error)) {
sub_id = hidpp20_error.feature_index;
response = hidpp20_error;
} else {
sub_id = report.subId();
}
if (sub_id == _sent_sub_id) {
_response = response;
_response_cv.notify_all();
return true;
} else {
return false;
}
_raw_device->sendReportNoResponse(report.rawReport());
}
std::string Device::name() const
{
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 constexpr 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
{
uint16_t Device::pid() const {
return _pid;
}
void Device::listen()
{
if(!_raw_device->isListening())
_raw_device->listenAsync();
// Pass all HID++ events with device index to this device.
auto handler = std::make_shared<raw::RawEventHandler>();
handler->condition = [index=this->_index](std::vector<uint8_t>& report)
->bool {
return (report[Offset::Type] == Report::Type::Short ||
report[Offset::Type] == Report::Type::Long) &&
(report[Offset::DeviceIndex] == index);
};
handler->callback = [this](std::vector<uint8_t>& report)->void {
Report _report(report);
this->handleEvent(_report);
};
_raw_device->addEventHandler("DEV_" + std::to_string(_index), handler);
_listening = true;
}
void Device::stopListening()
{
if(_listening)
_raw_device->removeEventHandler("DEV_" + std::to_string(_index));
_listening = false;
if(!_raw_device->eventHandlers().empty())
_raw_device->stopListener();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -19,94 +19,164 @@
#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>
#include "../raw/RawDevice.h"
#include "Report.h"
#include "defs.h"
namespace logid {
namespace backend {
namespace dj
{
namespace logid::backend::hidpp10 {
// Need to define here for a constructor
class Receiver;
}
namespace hidpp
{
namespace logid::backend::hidpp {
struct DeviceConnectionEvent;
struct EventHandler
{
std::function<bool(Report&)> condition;
std::function<void(Report&)> callback;
};
class Device
{
public:
class InvalidDevice : std::exception
{
namespace {
template <typename T>
class DeviceWrapper : public T {
friend class Device;
public:
enum Reason
{
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
Asleep,
VirtualNode
};
InvalidDevice(Reason reason) : _reason (reason) {}
virtual const char* what() const noexcept;
virtual Reason code() const noexcept;
explicit InvalidDevice(Reason reason) : _reason(reason) {}
[[nodiscard]] const char* what() const noexcept override;
[[nodiscard]] virtual Reason code() const noexcept;
private:
Reason _reason;
};
explicit Device(const std::string& path, DeviceIndex index);
Device(std::shared_ptr<raw::RawDevice> raw_device,
DeviceIndex index);
Device(std::shared_ptr<dj::Receiver> receiver,
hidpp::DeviceConnectionEvent event);
Device(std::shared_ptr<dj::Receiver> receiver,
DeviceIndex index);
~Device();
[[nodiscard]] const std::string& devicePath() const;
std::string devicePath() const;
DeviceIndex deviceIndex() const;
std::tuple<uint8_t, uint8_t> version() const;
[[nodiscard]] DeviceIndex deviceIndex() const;
std::string name() const;
uint16_t pid() const;
[[nodiscard]] const std::tuple<uint8_t, uint8_t>& version() const;
void listen(); // Runs asynchronously
void stopListening();
[[nodiscard]] const std::string& name() const;
void addEventHandler(const std::string& nickname,
const std::shared_ptr<EventHandler>& handler);
void removeEventHandler(const std::string& nickname);
const std::map<std::string, std::shared_ptr<EventHandler>>&
eventHandlers();
[[nodiscard]] uint16_t pid() const;
Report sendReport(Report& report);
void sendReportNoResponse(Report& report);
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;
std::shared_ptr<dj::Receiver> _receiver;
EventHandlerLock<raw::RawDevice> _raw_handler;
std::shared_ptr<hidpp10::Receiver> _receiver;
std::string _path;
DeviceIndex _index;
uint8_t _supported_reports;
std::tuple<uint8_t, uint8_t> _version;
uint16_t _pid;
uint16_t _pid{};
std::string _name;
std::atomic<bool> _listening;
std::mutex _send_mutex;
std::map<std::string, std::shared_ptr<EventHandler>> _event_handlers;
typedef std::variant<Report, Report::Hidpp10Error, Report::Hidpp20Error> Response;
std::optional<Response> _response;
std::optional<uint8_t> _sent_sub_id{};
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -16,119 +16,115 @@
*
*/
#include <backend/hidpp/Report.h>
#include <array>
#include <algorithm>
#include <cassert>
#include "Report.h"
#include "../hidpp10/Error.h"
#include "../hidpp20/Error.h"
#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
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
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
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
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(std::vector<uint8_t>&& rdesc)
{
uint8_t hidpp::getSupportedReports(const std::vector<uint8_t>& report_desc) {
uint8_t ret = 0;
auto it = std::search(rdesc.begin(), rdesc.end(),
ShortReportDesc.begin(), ShortReportDesc.end());
if(it == rdesc.end())
it = std::search(rdesc.begin(), rdesc.end(),
ShortReportDesc2.begin(), ShortReportDesc2.end());
if(it != rdesc.end())
ret |= HIDPP_REPORT_SHORT_SUPPORTED;
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(rdesc.begin(), rdesc.end(),
LongReportDesc.begin(), LongReportDesc.end());
if(it == rdesc.end())
it = std::search(rdesc.begin(), rdesc.end(),
LongReportDesc2.begin(), LongReportDesc2.end());
if(it != rdesc.end())
ret |= HIDPP_REPORT_LONG_SUPPORTED;
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
{
const char* Report::InvalidReportID::what() const noexcept {
return "Invalid report ID";
}
const char *Report::InvalidReportLength::what() const noexcept
{
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();
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;
@ -138,185 +134,164 @@ Report::Report(Report::Type type, DeviceIndex device_index,
}
Report::Report(Report::Type type, DeviceIndex device_index,
uint8_t feature_index, uint8_t function, uint8_t sw_id)
{
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();
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);
(sw_id & 0x0f);
}
Report::Report(const std::vector<uint8_t>& data) :
_data (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();
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
{
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();
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
{
hidpp::DeviceIndex Report::deviceIndex() const {
return static_cast<hidpp::DeviceIndex>(_data[Offset::DeviceIndex]);
}
void Report::setDeviceIndex(hidpp::DeviceIndex index)
{
[[maybe_unused]] void Report::setDeviceIndex(hidpp::DeviceIndex index) {
_data[Offset::DeviceIndex] = index;
}
uint8_t Report::feature() const
{
uint8_t Report::feature() const {
return _data[Offset::Feature];
}
void Report::setFeature(uint8_t feature)
{
[[maybe_unused]] void Report::setFeature(uint8_t feature) {
_data[Offset::Parameters] = feature;
}
uint8_t Report::subId() const
{
uint8_t Report::subId() const {
return _data[Offset::SubID];
}
void Report::setSubId(uint8_t sub_id)
{
[[maybe_unused]] void Report::setSubId(uint8_t sub_id) {
_data[Offset::SubID] = sub_id;
}
uint8_t Report::function() const
{
uint8_t Report::function() const {
return (_data[Offset::Function] >> 4) & 0x0f;
}
void Report::setFunction(uint8_t function)
{
[[maybe_unused]] void Report::setFunction(uint8_t function) {
_data[Offset::Function] &= 0x0f;
_data[Offset::Function] |= (function & 0x0f) << 4;
}
uint8_t Report::swId() const
{
uint8_t Report::swId() const {
return _data[Offset::Function] & 0x0f;
}
void Report::setSwId(uint8_t sub_id)
{
_data[Offset::Function] &= 0x0f;
_data[Offset::Function] |= sub_id & 0x0f;
void Report::setSwId(uint8_t sw_id) {
_data[Offset::Function] &= 0xf0;
_data[Offset::Function] |= sw_id & 0x0f;
}
uint8_t Report::address() const
{
uint8_t Report::address() const {
return _data[Offset::Address];
}
void Report::setAddress(uint8_t address)
{
[[maybe_unused]] void Report::setAddress(uint8_t address) {
_data[Offset::Address] = address;
}
std::vector<uint8_t>::iterator Report::paramBegin()
{
std::vector<uint8_t>::iterator Report::paramBegin() {
return _data.begin() + Offset::Parameters;
}
std::vector<uint8_t>::iterator Report::paramEnd()
{
std::vector<uint8_t>::iterator Report::paramEnd() {
return _data.end();
}
std::vector<uint8_t>::const_iterator Report::paramBegin() const
{
std::vector<uint8_t>::const_iterator Report::paramBegin() const {
return _data.begin() + Offset::Parameters;
}
std::vector<uint8_t>::const_iterator Report::paramEnd() const
{
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);
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++)
for (std::size_t i = 0; i < _params.size(); i++)
_data[Offset::Parameters + i] = _params[i];
}
bool Report::isError10(Report::Hidpp10Error *error)
{
assert(error != nullptr);
if(_data[Offset::Type] != Type::Short ||
bool Report::isError10(Report::Hidpp10Error& error) const {
if (_data[Offset::Type] != Type::Short ||
_data[Offset::SubID] != hidpp10::ErrorID)
return false;
error->sub_id = _data[3];
error->address = _data[4];
error->error_code = _data[5];
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)
{
assert(error != nullptr);
if(_data[Offset::Type] != Type::Long ||
bool Report::isError20(Report::Hidpp20Error& error) const {
if (_data[Offset::Type] != Type::Long ||
_data[Offset::Feature] != hidpp20::ErrorID)
return false;
error->feature_index= _data[3];
error->function = (_data[4] >> 4) & 0x0f;
error->software_id = _data[4] & 0x0f;
error->error_code = _data[5];
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -19,23 +19,19 @@
#ifndef LOGID_BACKEND_HIDPP_REPORT_H
#define LOGID_BACKEND_HIDPP_REPORT_H
#include <backend/raw/RawDevice.h>
#include <backend/hidpp/defs.h>
#include <cstdint>
#include "../raw/RawDevice.h"
#include "defs.h"
/* Some devices only support a subset of these reports */
#define HIDPP_REPORT_SHORT_SUPPORTED 1U
#define HIDPP_REPORT_LONG_SUPPORTED 1U<<1U
/* Very long reports exist, however they have not been encountered so far */
namespace logid::backend::hidpp {
uint8_t getSupportedReports(const std::vector<uint8_t>& report_desc);
namespace logid {
namespace backend {
namespace hidpp
{
uint8_t getSupportedReports(std::vector<uint8_t>&& rdesc);
/* 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
{
namespace Offset {
static constexpr uint8_t Type = 0;
static constexpr uint8_t DeviceIndex = 1;
static constexpr uint8_t SubID = 2;
@ -45,23 +41,22 @@ namespace hidpp
static constexpr uint8_t Parameters = 4;
}
class Report
{
class Report {
public:
typedef ReportType::ReportType Type;
class InvalidReportID: public std::exception
{
class InvalidReportID : public std::exception {
public:
InvalidReportID() = default;
const char* what() const noexcept override;
[[nodiscard]] const char* what() const noexcept override;
};
class InvalidReportLength: public std::exception
{
class InvalidReportLength : public std::exception {
public:
InvalidReportLength() = default;;
const char* what() const noexcept override;
InvalidReportLength() = default;
[[nodiscard]] const char* what() const noexcept override;
};
static constexpr std::size_t MaxDataLength = 20;
@ -69,57 +64,72 @@ namespace hidpp
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);
uint8_t feature_index,
uint8_t function,
uint8_t sw_id);
explicit Report(const std::vector<uint8_t>& data);
Report::Type type() const;
[[nodiscard]] Report::Type type() const;
void setType(Report::Type type);
logid::backend::hidpp::DeviceIndex deviceIndex() const;
void setDeviceIndex(hidpp::DeviceIndex index);
[[nodiscard]] DeviceIndex deviceIndex() const;
uint8_t feature() const;
void setFeature(uint8_t feature);
[[maybe_unused]] void setDeviceIndex(DeviceIndex index);
uint8_t subId() const;
void setSubId(uint8_t sub_id);
[[nodiscard]] uint8_t feature() const;
uint8_t function() const;
void setFunction(uint8_t function);
[[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;
uint8_t swId() const;
void setSwId(uint8_t sw_id);
uint8_t address() const;
void setAddress(uint8_t address);
[[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;
std::vector<uint8_t>::iterator paramBegin();
std::vector<uint8_t>::iterator paramEnd();
std::vector<uint8_t>::const_iterator paramBegin() const;
std::vector<uint8_t>::const_iterator paramEnd() const;
void setParams(const std::vector<uint8_t>& _params);
struct Hidpp10Error
{
struct Hidpp10Error {
hidpp::DeviceIndex device_index;
uint8_t sub_id, address, error_code;
};
bool isError10(Hidpp10Error* error);
struct Hidpp20Error
{
bool isError10(Hidpp10Error& error) const;
struct Hidpp20Error {
hidpp::DeviceIndex device_index;
uint8_t feature_index, function, software_id, error_code;
};
bool isError20(Hidpp20Error* error);
std::vector<uint8_t> rawReport () const { return _data; }
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -19,37 +19,33 @@
#ifndef LOGID_BACKEND_HIDPP_DEFS_H
#define LOGID_BACKEND_HIDPP_DEFS_H
#define LOGID_HIDPP_SOFTWARE_ID 0
#include <cstdint>
namespace logid {
namespace backend {
namespace hidpp
{
namespace ReportType
{
enum ReportType : uint8_t
{
namespace logid::backend::hidpp {
namespace ReportType {
enum ReportType : uint8_t {
Short = 0x10,
Long = 0x11
};
}
enum DeviceIndex: uint8_t
{
enum DeviceIndex : uint8_t {
DefaultDevice = 0xff,
CordedDevice = 0,
WirelessDevice1 = 1,
WirelessDevice2 = 2,
WirelessDevice3 = 3,
WirelessDevice4 = 4,
WirelessDevice5 = 5,
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 = 1;
static constexpr std::size_t ShortParamLength = 3;
static constexpr std::size_t LongParamLength = 16;
} } }
}
#endif //LOGID_BACKEND_HIDPP_DEFS_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -16,47 +16,106 @@
*
*/
#include <backend/hidpp10/Device.h>
#include <backend/Error.h>
#include <cassert>
#include <utility>
#include "Device.h"
#include "defs.h"
using namespace logid::backend;
using namespace logid::backend::hidpp10;
Device::Device(const std::string &path, hidpp::DeviceIndex index) :
hidpp::Device(path, index)
{
assert(version() == std::make_tuple(1, 0));
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) : hidpp::Device(std::move(raw_dev), index)
{
assert(version() == std::make_tuple(1, 0));
Device::Device(std::shared_ptr<raw::RawDevice> raw_dev, hidpp::DeviceIndex index,
double timeout) : hidpp::Device(std::move(raw_dev), index, timeout) {
}
Device::Device(std::shared_ptr<dj::Receiver> receiver, hidpp::DeviceIndex index)
: hidpp::Device(receiver, index)
{
assert(version() == std::make_tuple(1, 0));
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)
{
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;
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)
{
hidpp::Report::Type type) {
assert(params.size() <= hidpp::LongParamLength);
uint8_t sub_id = type == hidpp::Report::Type::Short ?
@ -66,16 +125,20 @@ std::vector<uint8_t> Device::setRegister(uint8_t address,
}
std::vector<uint8_t> Device::accessRegister(uint8_t sub_id, uint8_t address,
const std::vector<uint8_t> &params)
{
const std::vector<uint8_t>& params) {
hidpp::Report::Type type = params.size() <= hidpp::ShortParamLength ?
hidpp::Report::Type::Short : hidpp::Report::Type::Long;
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, deviceIndex(), sub_id, address);
std::copy(params.begin(), params.end(), request.paramBegin());
auto response = sendReport(request);
return std::vector<uint8_t>(response.paramBegin(), response.paramEnd());
return {response.paramBegin(), response.paramEnd()};
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -19,30 +19,66 @@
#ifndef LOGID_BACKEND_HIDPP10_DEVICE_H
#define LOGID_BACKEND_HIDPP10_DEVICE_H
#include "../hidpp/Device.h"
#include <optional>
#include <variant>
#include <backend/hidpp/Device.h>
#include <backend/hidpp10/Error.h>
#include <backend/hidpp10/defs.h>
namespace logid {
namespace backend {
namespace hidpp10
{
class Device : public hidpp::Device
{
namespace logid::backend::hidpp10 {
class Device : public hidpp::Device {
public:
Device(const std::string& path, hidpp::DeviceIndex index);
Device(std::shared_ptr<raw::RawDevice> raw_dev,
hidpp::DeviceIndex index);
Device(std::shared_ptr<dj::Receiver> receiver,
hidpp::DeviceIndex index);
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);
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);
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:
std::vector<uint8_t> accessRegister(uint8_t sub_id,
uint8_t address, const std::vector<uint8_t>& params);
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);
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -16,52 +16,53 @@
*
*/
#include <backend/hidpp10/Error.h>
#include <cassert>
#include <string>
#include "Error.h"
using namespace logid::backend;
using namespace logid::backend::hidpp10;
Error::Error(uint8_t code): _code(code)
{
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";
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
{
uint8_t Error::code() const noexcept {
return _code;
}
}
hidpp::DeviceIndex Error::deviceIndex() const noexcept {
return _index;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -19,18 +19,16 @@
#ifndef LOGID_BACKEND_HIDPP10_ERROR_H
#define LOGID_BACKEND_HIDPP10_ERROR_H
#include <backend/hidpp/defs.h>
#include <cstdint>
#include <exception>
namespace logid {
namespace backend {
namespace hidpp10 {
namespace logid::backend::hidpp10 {
static constexpr uint8_t ErrorID = 0x8f;
class Error: public std::exception
{
class Error : public std::exception {
public:
enum ErrorCode: uint8_t
{
enum ErrorCode : uint8_t {
Success = 0x00,
InvalidSubID = 0x01,
InvalidAddress = 0x02,
@ -46,14 +44,18 @@ namespace hidpp10 {
WrongPINCode = 0x0C
};
explicit Error(uint8_t code);
Error(uint8_t code, hidpp::DeviceIndex index);
const char* what() const noexcept override;
uint8_t code() const noexcept;
[[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

@ -0,0 +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() {
setRegister(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

@ -0,0 +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;
}
};
}
#endif //LOGID_BACKEND_DJ_RECEIVER_H

View File

@ -0,0 +1,247 @@
/*
* 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) {
auto handler_id = std::make_shared<EventHandlerLock<raw::RawDevice>>();
*handler_id = _receiver->rawDevice()->addEventHandler(
{[index](const std::vector<uint8_t>& report) -> bool {
return report[Offset::DeviceIndex] == index;
},
[self_weak = _self, index, handler_id](
[[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, handler_id]() {
*handler_id = {};
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);
} 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

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -17,67 +17,146 @@
*/
#include <cassert>
#include <backend/hidpp20/Device.h>
#include <backend/Error.h>
#include <backend/hidpp10/Receiver.h>
#include "Device.h"
#include "../hidpp/defs.h"
using namespace logid::backend;
using namespace logid::backend::hidpp20;
Device::Device(std::string path, hidpp::DeviceIndex index)
: hidpp::Device(path, index)
{
assert(std::get<0>(version()) >= 2);
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)
: hidpp::Device(raw_device, index)
{
assert(std::get<0>(version()) >= 2);
Device::Device(std::shared_ptr<raw::RawDevice> raw_device,
hidpp::DeviceIndex index, double timeout) :
hidpp::Device(std::move(raw_device), index, timeout) {
}
Device::Device(std::shared_ptr<dj::Receiver> receiver, hidpp::DeviceIndex index)
: hidpp::Device(receiver, index)
{
assert(std::get<0>(version()) >= 2);
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)
{
uint8_t function, std::vector<uint8_t>& params) {
hidpp::Report::Type type;
assert(params.size() <= hidpp::LongParamLength);
if(params.size() <= hidpp::ShortParamLength)
if (params.size() <= hidpp::ShortParamLength)
type = hidpp::Report::Type::Short;
else if(params.size() <= hidpp::LongParamLength)
else if (params.size() <= hidpp::LongParamLength)
type = hidpp::Report::Type::Long;
else
throw hidpp::Report::InvalidReportID();
hidpp::Report request(type, deviceIndex(), feature_index, function,
LOGID_HIDPP_SOFTWARE_ID);
hidpp::softwareID);
std::copy(params.begin(), params.end(), request.paramBegin());
auto response = this->sendReport(request);
return std::vector<uint8_t>(response.paramBegin(), response.paramEnd());
return {response.paramBegin(), response.paramEnd()};
}
void Device::callFunctionNoResponse(uint8_t feature_index, uint8_t function,
std::vector<uint8_t> &params)
{
std::vector<uint8_t>& params) {
hidpp::Report::Type type;
assert(params.size() <= hidpp::LongParamLength);
if(params.size() <= hidpp::ShortParamLength)
if (params.size() <= hidpp::ShortParamLength)
type = hidpp::Report::Type::Short;
else if(params.size() <= hidpp::LongParamLength)
else if (params.size() <= hidpp::LongParamLength)
type = hidpp::Report::Type::Long;
else
throw hidpp::Report::InvalidReportID();
hidpp::Report request(type, deviceIndex(), feature_index, function,
LOGID_HIDPP_SOFTWARE_ID);
hidpp::Report request(type, deviceIndex(), feature_index, function, hidpp::softwareID);
std::copy(params.begin(), params.end(), request.paramBegin());
this->sendReportNoResponse(request);
}
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -19,28 +19,64 @@
#ifndef LOGID_BACKEND_HIDPP20_DEVICE_H
#define LOGID_BACKEND_HIDPP20_DEVICE_H
#include "../hidpp/Device.h"
#include <cstdint>
#include <optional>
#include <variant>
#include <backend/hidpp20/Error.h>
#include <backend/hidpp/Device.h>
namespace logid {
namespace backend {
namespace hidpp20 {
class Device : public hidpp::Device
{
namespace logid::backend::hidpp20 {
class Device : public hidpp::Device {
public:
Device(std::string path, hidpp::DeviceIndex index);
Device(std::shared_ptr<raw::RawDevice> raw_device, hidpp::DeviceIndex index);
Device(std::shared_ptr<dj::Receiver> receiver, hidpp::DeviceIndex
index);
std::vector<uint8_t> callFunction(uint8_t feature_index,
uint8_t function,
std::vector<uint8_t>& params);
uint8_t function,
std::vector<uint8_t>& params);
void callFunctionNoResponse(uint8_t feature_index,
uint8_t function,
std::vector<uint8_t>& params);
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -16,47 +16,49 @@
*
*/
#include <backend/hidpp20/Error.h>
#include <cassert>
#include "Error.h"
using namespace logid::backend;
using namespace logid::backend::hidpp20;
Error::Error(uint8_t code) : _code (code)
{
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";
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
{
uint8_t Error::code() const noexcept {
return _code;
}
hidpp::DeviceIndex Error::deviceIndex() const noexcept {
return _index;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -19,18 +19,16 @@
#ifndef LOGID_BACKEND_HIDPP20_ERROR_H
#define LOGID_BACKEND_HIDPP20_ERROR_H
#include <backend/hidpp/defs.h>
#include <stdexcept>
#include <cstdint>
namespace logid {
namespace backend {
namespace hidpp20 {
namespace logid::backend::hidpp20 {
static constexpr uint8_t ErrorID = 0xFF;
class Error: public std::exception
{
class Error : public std::exception {
public:
enum ErrorCode: uint8_t {
enum ErrorCode : uint8_t {
NoError = 0,
Unknown = 1,
InvalidArgument = 2,
@ -44,14 +42,18 @@ namespace hidpp20 {
UnknownDevice = 10
};
explicit Error(uint8_t code);
Error(uint8_t code, hidpp::DeviceIndex index);
const char* what() const noexcept override;
uint8_t code() const noexcept;
[[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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -16,57 +16,51 @@
*
*/
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/EssentialFeature.h>
#include <backend/hidpp20/features/Root.h>
#include <backend/hidpp20/Error.h>
#include <cassert>
#include "EssentialFeature.h"
#include "feature_defs.h"
#include "features/Root.h"
#include "Error.h"
using namespace logid::backend::hidpp20;
std::vector<uint8_t> EssentialFeature::callFunction(uint8_t function_id,
std::vector<uint8_t>& params)
{
std::vector<uint8_t>& params) {
hidpp::Report::Type type;
assert(params.size() <= hidpp::LongParamLength);
if(params.size() <= hidpp::ShortParamLength)
if (params.size() <= hidpp::ShortParamLength)
type = hidpp::Report::Type::Short;
else if(params.size() <= hidpp::LongParamLength)
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,
LOGID_HIDPP_SOFTWARE_ID);
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 std::vector<uint8_t>(response.paramBegin(), response.paramEnd());
return {response.paramBegin(), response.paramEnd()};
}
EssentialFeature::EssentialFeature(hidpp::Device* dev, uint16_t _id) :
_device (dev)
{
_device(dev) {
_index = hidpp20::FeatureID::ROOT;
if(_id)
{
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)
_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)
if (!_index)
throw UnsupportedFeature(_id);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -25,26 +25,24 @@
* hidpp::Device class. No version checks are provided here
*/
#include "Device.h"
#include <backend/hidpp/Device.h>
namespace logid {
namespace backend {
namespace hidpp20
{
class EssentialFeature
{
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);
private:
hidpp::Device* _device;
std::vector<uint8_t>& params);
hidpp::Device* const _device;
uint8_t _index;
};
}}}
}
#endif //LOGID_BACKEND_HIDPP20_ESSENTIAL_FEATURE_H

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -20,38 +20,44 @@
#define LOGID_BACKEND_HIDPP20_FEATURE_H
#include <cstdint>
#include "Device.h"
#include <exception>
#include <vector>
namespace logid {
namespace backend {
namespace hidpp20 {
class UnsupportedFeature : public std::exception
{
namespace logid::backend::hidpp20 {
class Device;
class UnsupportedFeature : public std::exception {
public:
explicit UnsupportedFeature(uint16_t ID) : _f_id (ID) {}
const char* what() const noexcept override;
uint16_t code() const noexcept;
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
{
class Feature {
public:
static const uint16_t ID;
virtual uint16_t getID() = 0;
uint8_t featureIndex();
[[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);
private:
Device* _device;
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -21,9 +21,7 @@
#include <cstdint>
namespace logid {
namespace backend {
namespace hidpp20 {
namespace logid::backend::hidpp20 {
struct feature_info {
uint16_t feature_id;
bool obsolete;
@ -31,10 +29,8 @@ namespace hidpp20 {
bool hidden;
};
namespace FeatureID
{
enum FeatureID : uint16_t
{
namespace FeatureID {
enum FeatureID : uint16_t {
ROOT = 0x0000,
FEATURE_SET = 0x0001,
FEATURE_INFO = 0x0002,
@ -127,6 +123,6 @@ namespace hidpp20 {
};
}
}}}
}
#endif //LOGID_BACKEND_HIDPP20_FEATUREDEFS

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,35 +15,32 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "AdjustableDPI.h"
#include <backend/hidpp20/features/AdjustableDPI.h>
using namespace logid::backend::hidpp20;
AdjustableDPI::AdjustableDPI(Device* dev) : Feature(dev, ID)
{
AdjustableDPI::AdjustableDPI(Device* dev) : Feature(dev, ID) {
}
uint8_t AdjustableDPI::getSensorCount()
{
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)
{
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) {
for (std::size_t i = 1; i < response.size(); i += 2) {
uint16_t dpi = response[i + 1];
dpi |= (response[i] << 8);
if(!dpi)
if (!dpi)
break;
if(dpi >= 0xe000) {
if (dpi >= 0xe000) {
dpi_list.isRange = true;
dpi_list.dpiStep = dpi - 0xe000;
} else {
@ -54,8 +51,7 @@ AdjustableDPI::SensorDPIList AdjustableDPI::getSensorDPIList(uint8_t sensor)
return dpi_list;
}
uint16_t AdjustableDPI::getDefaultSensorDPI(uint8_t sensor)
{
uint16_t AdjustableDPI::getDefaultSensorDPI(uint8_t sensor) {
std::vector<uint8_t> params(1);
params[0] = sensor;
auto response = callFunction(GetSensorDPI, params);
@ -66,8 +62,7 @@ uint16_t AdjustableDPI::getDefaultSensorDPI(uint8_t sensor)
return default_dpi;
}
uint16_t AdjustableDPI::getSensorDPI(uint8_t sensor)
{
uint16_t AdjustableDPI::getSensorDPI(uint8_t sensor) {
std::vector<uint8_t> params(1);
params[0] = sensor;
auto response = callFunction(GetSensorDPI, params);
@ -78,8 +73,7 @@ uint16_t AdjustableDPI::getSensorDPI(uint8_t sensor)
return dpi;
}
void AdjustableDPI::setSensorDPI(uint8_t sensor, uint16_t dpi)
{
void AdjustableDPI::setSensorDPI(uint8_t sensor, uint16_t dpi) {
std::vector<uint8_t> params(3);
params[0] = sensor;
params[1] = (dpi >> 8);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,18 +18,15 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_ADJUSTABLEDPI_H
#define LOGID_BACKEND_HIDPP20_FEATURE_ADJUSTABLEDPI_H
#include "../feature_defs.h"
#include "../Feature.h"
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
namespace logid {
namespace backend {
namespace hidpp20
{
class AdjustableDPI : public Feature
{
namespace logid::backend::hidpp20 {
class AdjustableDPI : public Feature {
public:
static const uint16_t ID = FeatureID::ADJUSTABLE_DPI;
virtual uint16_t getID() { return ID; }
[[nodiscard]] uint16_t getID() final { return ID; }
enum Function {
GetSensorCount = 0,
@ -38,23 +35,24 @@ namespace hidpp20
SetSensorDPI = 3
};
AdjustableDPI(Device* dev);
explicit AdjustableDPI(Device* dev);
uint8_t getSensorCount();
struct SensorDPIList
{
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,17 +15,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ChangeHost.h"
#include "../Error.h"
#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::ChangeHost(Device* dev) : Feature(dev, ID), _host_count(0) {
}
ChangeHost::HostInfo ChangeHost::getHostInfo()
{
ChangeHost::HostInfo ChangeHost::getHostInfo() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetHostInfo, params);
@ -34,33 +32,32 @@ ChangeHost::HostInfo ChangeHost::getHostInfo()
info.currentHost = response[1];
info.enhancedHostSwitch = response[2] & 1;
if(!_host_count)
if (!_host_count)
_host_count = info.hostCount;
return info;
}
void ChangeHost::setHost(uint8_t host)
{
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)
if (!_host_count)
getHostInfo();
if(host >= _host_count)
throw hidpp20::Error(hidpp20::Error::InvalidArgument);
if (host >= _host_count)
throw Error(hidpp20::Error::InvalidArgument, _device->deviceIndex());
std::vector<uint8_t> params = {host};
callFunctionNoResponse(SetCurrentHost, params);
}
std::vector<uint8_t> ChangeHost::getCookies()
{
if(!_host_count)
[[maybe_unused]]
std::vector<uint8_t> ChangeHost::getCookies() {
if (!_host_count)
getHostInfo();
std::vector<uint8_t> params(0);
@ -71,8 +68,8 @@ std::vector<uint8_t> ChangeHost::getCookies()
return response;
}
void ChangeHost::setCookie(uint8_t host, uint8_t cookie)
{
[[maybe_unused]]
void ChangeHost::setCookie(uint8_t host, uint8_t cookie) {
std::vector<uint8_t> params = {host, cookie};
callFunction(SetCookie, params);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,20 +18,17 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_CHANGEHOST_H
#define LOGID_BACKEND_HIDPP20_FEATURE_CHANGEHOST_H
#include "../feature_defs.h"
#include "../Feature.h"
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp20/Feature.h>
namespace logid {
namespace backend {
namespace hidpp20
{
class ChangeHost : public Feature
{
namespace logid::backend::hidpp20 {
class ChangeHost : public Feature {
public:
static const uint16_t ID = FeatureID::CHANGE_HOST;
virtual uint16_t getID() { return ID; }
ChangeHost(Device* dev);
[[nodiscard]] uint16_t getID() final { return ID; }
explicit ChangeHost(Device* dev);
enum Function {
GetHostInfo = 0,
@ -40,22 +37,23 @@ namespace hidpp20
SetCookie = 3
};
struct HostInfo
{
struct HostInfo {
uint8_t hostCount;
uint8_t currentHost;
bool enhancedHostSwitch;
[[maybe_unused]] bool enhancedHostSwitch;
};
HostInfo getHostInfo();
void setHost(uint8_t host);
std::vector<uint8_t> getCookies();
void setCookie(uint8_t host, uint8_t cookie);
[[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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -16,71 +16,49 @@
*
*/
#include <cmath>
#include "DeviceName.h"
#include <backend/hidpp20/features/DeviceName.h>
using namespace logid::backend;
using namespace logid::backend::hidpp20;
DeviceName::DeviceName(Device* dev) : Feature(dev, ID)
{
}
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;
uint8_t DeviceName::getNameLength()
{
std::vector<uint8_t> params(0);
auto response = this->callFunction(Function::GetLength, params);
return response[0];
}
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 += name_section[j];
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;
}
return name;
}
std::string DeviceName::getName()
{
return _getName(getNameLength(), [this]
(std::vector<uint8_t> params)->std::vector<uint8_t> {
return this->callFunction(Function::GetDeviceName, params);
});
DeviceName::DeviceName(hidpp::Device* dev) :
EssentialFeature(dev, ID) {
}
EssentialDeviceName::EssentialDeviceName(hidpp::Device* dev) :
EssentialFeature(dev, ID)
{
}
uint8_t EssentialDeviceName::getNameLength()
{
uint8_t DeviceName::getNameLength() {
std::vector<uint8_t> params(0);
auto response = this->callFunction(DeviceName::Function::GetLength, params);
return response[0];
}
std::string EssentialDeviceName::getName()
{
std::string DeviceName::getName() {
return _getName(getNameLength(), [this]
(std::vector<uint8_t> params)->std::vector<uint8_t> {
(std::vector<uint8_t> params) -> std::vector<uint8_t> {
return this->callFunction(DeviceName::Function::GetDeviceName, params);
});
}

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,23 +15,21 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "FeatureSet.h"
#include <backend/hidpp20/features/FeatureSet.h>
using namespace logid::backend::hidpp20;
FeatureSet::FeatureSet(Device *device) : Feature(device, ID)
{
[[maybe_unused]]
FeatureSet::FeatureSet(Device* device) : Feature(device, ID) {
}
uint8_t FeatureSet::getFeatureCount()
{
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)
{
uint16_t FeatureSet::getFeature(uint8_t feature_index) {
std::vector<uint8_t> params(1);
params[0] = feature_index;
auto response = callFunction(GetFeature, params);
@ -41,11 +39,11 @@ uint16_t FeatureSet::getFeature(uint8_t feature_index)
return feature_id;
}
std::map<uint8_t, uint16_t> FeatureSet::getFeatures()
{
[[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++)
for (uint8_t i = 0; i < feature_count; i++)
features[i] = getFeature(i);
return features;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,31 +18,32 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_FEATURESET_H
#define LOGID_BACKEND_HIDPP20_FEATURE_FEATURESET_H
#include "../Feature.h"
#include "../feature_defs.h"
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
#include <map>
namespace logid {
namespace backend {
namespace hidpp20
{
class FeatureSet : public Feature
{
namespace logid::backend::hidpp20 {
class FeatureSet : public Feature {
public:
static const uint16_t ID = FeatureID::FEATURE_SET;
virtual uint16_t getID() { return ID; }
enum Function : uint8_t
{
[[nodiscard]] uint16_t getID() final { return ID; }
enum Function : uint8_t {
GetFeatureCount = 0,
GetFeature = 1
};
[[maybe_unused]] [[maybe_unused]]
explicit FeatureSet(Device* device);
uint8_t getFeatureCount();
uint16_t getFeature(uint8_t feature_index);
std::map<uint8_t, uint16_t> getFeatures();
[[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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,17 +15,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <backend/hidpp20/features/HiresScroll.h>
#include <cassert>
#include "HiresScroll.h"
using namespace logid::backend::hidpp20;
HiresScroll::HiresScroll(Device *device) : Feature(device, ID)
{
HiresScroll::HiresScroll(Device* device) : Feature(device, ID) {
}
HiresScroll::Capabilities HiresScroll::getCapabilities()
{
HiresScroll::Capabilities HiresScroll::getCapabilities() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetCapabilities, params);
@ -35,41 +33,35 @@ HiresScroll::Capabilities HiresScroll::getCapabilities()
return capabilities;
}
uint8_t HiresScroll::getMode()
{
uint8_t HiresScroll::getMode() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetMode, params);
return response[0];
}
void HiresScroll::setMode(uint8_t mode)
{
void HiresScroll::setMode(uint8_t mode) {
std::vector<uint8_t> params(1);
params[0] = mode;
callFunction(SetMode, params);
}
bool HiresScroll::getRatchetState()
{
[[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)
{
HiresScroll::WheelStatus HiresScroll::wheelMovementEvent(const hidpp::Report& report) {
assert(report.function() == WheelMovement);
WheelStatus status{};
status.hiRes = report.paramBegin()[0] & 1<<4;
status.hiRes = report.paramBegin()[0] & 1 << 4;
status.periods = report.paramBegin()[0] & 0x0F;
status.deltaV = report.paramBegin()[1] << 8 | report.paramBegin()[2];
status.deltaV = (int16_t) (report.paramBegin()[1] << 8 | report.paramBegin()[2]);
return status;
}
HiresScroll::RatchetState HiresScroll::ratchetSwitchEvent(const hidpp::Report
&report)
{
[[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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,61 +18,52 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H
#define LOGID_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H
#include "../Feature.h"
#include "../feature_defs.h"
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp/Report.h>
namespace logid {
namespace backend {
namespace hidpp20
{
class HiresScroll : public Feature
{
namespace logid::backend::hidpp20 {
class HiresScroll : public Feature {
public:
///TODO: Hires scroll V1?
static const uint16_t ID = FeatureID::HIRES_SCROLLING_V2;
virtual uint16_t getID() { return ID; }
enum Function : uint8_t
{
uint16_t getID() final { return ID; }
enum Function : uint8_t {
GetCapabilities = 0,
GetMode = 1,
SetMode = 2,
GetRatchetState = 3
};
enum Event : uint8_t
{
enum Event : uint8_t {
WheelMovement = 0,
RatchetSwitch = 1,
};
enum Capability : uint8_t
{
Invertable = 1<<3,
HasRatchet = 1<<2
enum Capability : uint8_t {
Invertible = 1 << 3,
HasRatchet = 1 << 2
};
enum Mode : uint8_t
{
Inverted = 1<<2,
HiRes = 1<<1,
enum Mode : uint8_t {
Inverted = 1 << 2,
HiRes = 1 << 1,
Target = 1
};
enum RatchetState : uint8_t
{
enum RatchetState : uint8_t {
FreeWheel = 0,
Ratchet = 1
};
struct Capabilities
{
struct Capabilities {
uint8_t multiplier;
uint8_t flags;
};
struct WheelStatus
{
struct WheelStatus {
bool hiRes;
uint8_t periods;
int16_t deltaV;
@ -81,13 +72,19 @@ namespace hidpp20
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,53 +15,58 @@
* 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>
#include "../Error.h"
#include "ReprogControls.h"
using namespace logid::backend::hidpp20;
#define DEFINE_REPROG(x, base) \
x::x(Device* dev) : base(dev, ID) \
{ \
} \
x::x(Device* dev, uint16_t _id) : base(dev, _id) \
{ \
}
// 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 MAKE_REPROG(x, dev) \
try { \
return std::make_shared<x>(dev); \
} catch(UnsupportedFeature &e) {\
}
// Define all of the ReprogControls versions
DEFINE_REPROG(ReprogControls, Feature)
DEFINE_REPROG(ReprogControlsV2, ReprogControls)
DEFINE_REPROG(ReprogControlsV2_2, ReprogControlsV2)
DEFINE_REPROG(ReprogControlsV3, ReprogControlsV2_2)
DEFINE_REPROG(ReprogControlsV4, ReprogControlsV3)
std::shared_ptr<ReprogControls> ReprogControls::autoVersion(Device *dev)
{
MAKE_REPROG(ReprogControlsV4, dev)
MAKE_REPROG(ReprogControlsV3, dev)
MAKE_REPROG(ReprogControlsV2_2, dev)
MAKE_REPROG(ReprogControlsV2, dev)
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;
}
// If base version cannot be made, throw error
return std::make_shared<ReprogControls>(dev);
}
uint8_t ReprogControls::getControlCount()
{
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)
{
ReprogControls::ControlInfo ReprogControls::getControlInfo(uint8_t index) {
std::vector<uint8_t> params(1);
ControlInfo info{};
params[0] = index;
@ -79,13 +84,12 @@ ReprogControls::ControlInfo ReprogControls::getControlInfo(uint8_t index)
return info;
}
void ReprogControls::initCidMap()
{
void ReprogControls::initCidMap() {
std::unique_lock<std::mutex> lock(_cids_populating);
if(_cids_initialized)
if (_cids_initialized)
return;
uint8_t controls = getControlCount();
for(uint8_t i = 0; i < controls; i++) {
for (uint8_t i = 0; i < controls; i++) {
auto info = getControlInfo(i);
_cids.emplace(info.controlID, info);
}
@ -93,57 +97,53 @@ void ReprogControls::initCidMap()
}
const std::map<uint16_t, ReprogControls::ControlInfo>&
ReprogControls::getControls() const
{
ReprogControls::getControls() const {
return _cids;
}
ReprogControls::ControlInfo ReprogControls::getControlIdInfo(uint16_t cid)
{
if(!_cids_initialized)
ReprogControls::ControlInfo ReprogControls::getControlIdInfo(uint16_t cid) {
if (!_cids_initialized)
initCidMap();
auto it = _cids.find(cid);
if(it == _cids.end())
throw Error(Error::InvalidArgument);
if (it == _cids.end())
throw Error(Error::InvalidArgument, _device->deviceIndex());
else
return it->second;
}
ReprogControls::ControlInfo ReprogControls::getControlReporting(uint16_t cid)
{
[[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)
if (info.flags & TemporaryDivertable)
report.flags |= TemporaryDiverted;
if(info.flags & PersisentlyDivertable)
if (info.flags & PersistentlyDivertable)
report.flags |= PersistentlyDiverted;
if(info.additionalFlags & RawXY)
if (info.additionalFlags & RawXY)
report.flags |= RawXYDiverted;
return report;
}
void ReprogControls::setControlReporting(uint8_t cid, ControlInfo info)
{
void ReprogControls::setControlReporting(uint8_t cid, ControlInfo info) {
// This function does not exist pre-v4 and cannot be emulated, ignore.
(void)cid; (void)info; // Suppress unused warnings
(void) cid;
(void) info; // Suppress unused warnings
}
std::set<uint16_t> ReprogControls::divertedButtonEvent(
const hidpp::Report& report)
{
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)
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;
@ -152,19 +152,15 @@ std::set<uint16_t> ReprogControls::divertedButtonEvent(
}
ReprogControls::Move ReprogControls::divertedRawXYEvent(const hidpp::Report
&report)
{
& report) {
assert(report.function() == DivertedRawXYEvent);
Move move{};
move.x = report.paramBegin()[1];
move.x |= report.paramBegin()[0] << 8;
move.y = report.paramBegin()[3];
move.y |= report.paramBegin()[2] << 8;
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)
{
ReprogControls::ControlInfo ReprogControlsV4::getControlReporting(uint16_t cid) {
std::vector<uint8_t> params(2);
ControlInfo info{};
params[0] = (cid >> 8) & 0xff;
@ -177,8 +173,7 @@ ReprogControls::ControlInfo ReprogControlsV4::getControlReporting(uint16_t cid)
return info;
}
void ReprogControlsV4::setControlReporting(uint8_t cid, ControlInfo info)
{
void ReprogControlsV4::setControlReporting(uint8_t cid, ControlInfo info) {
std::vector<uint8_t> params(5);
params[0] = (cid >> 8) & 0xff;
params[1] = cid & 0xff;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,17 +18,15 @@
#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>
#include "../feature_defs.h"
#include "../Feature.h"
namespace logid {
namespace backend {
namespace hidpp20
{
class ReprogControls : public Feature
{
namespace logid::backend::hidpp20 {
class ReprogControls : public Feature {
public:
enum Function {
GetControlCount = 0,
@ -41,8 +39,7 @@ namespace hidpp20
DivertedRawXYEvent = 1
};
struct ControlInfo
{
struct ControlInfo {
uint16_t controlID;
uint16_t taskID;
uint8_t flags;
@ -52,121 +49,127 @@ namespace hidpp20
uint8_t additionalFlags;
};
enum ControlInfoFlags: uint8_t
{
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,
PersisentlyDivertable = 1<<6,
Virtual = 1<<7
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 ControlInfoAdditionalFlags : uint8_t {
RawXY = 1 << 0
};
enum ControlReportingFlags: uint8_t {
TemporaryDiverted = 1<<0,
ChangeTemporaryDivert = 1<<1,
PersistentlyDiverted = 1<<2,
ChangePersistentDivert = 1<<3,
RawXYDiverted = 1<<4,
ChangeRawXYDivert = 1<<5
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
{
struct Move {
int16_t x;
int16_t y;
};
static const uint16_t ID = FeatureID::REPROG_CONTROLS;
virtual uint16_t getID() { return ID; }
virtual bool supportsRawXY() { return false; }
[[nodiscard]] uint16_t getID() override { return ID; }
[[nodiscard]] virtual bool supportsRawXY() { return false; }
explicit ReprogControls(Device* dev);
virtual uint8_t getControlCount();
[[nodiscard]] virtual uint8_t getControlCount();
virtual ControlInfo getControlInfo(uint8_t cid);
[[nodiscard]] virtual ControlInfo getControlInfo(uint8_t cid);
virtual ControlInfo getControlIdInfo(uint16_t cid);
[[nodiscard]] virtual ControlInfo getControlIdInfo(uint16_t cid);
virtual void initCidMap();
const std::map<uint16_t, ControlInfo>& getControls() const;
[[nodiscard]] const std::map<uint16_t, ControlInfo>& getControls() const;
// Onlu controlId and flags will be set
virtual ControlInfo getControlReporting(uint16_t cid);
// 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(uint8_t cid, ControlInfo info);
static std::set<uint16_t> divertedButtonEvent(const hidpp::Report&
report);
[[nodiscard]] static std::set<uint16_t> divertedButtonEvent(const hidpp::Report& report);
static Move divertedRawXYEvent(const hidpp::Report& report);
[[nodiscard]] static Move divertedRawXYEvent(const hidpp::Report& report);
[[nodiscard]] static std::shared_ptr<ReprogControls> autoVersion(Device* dev);
static std::shared_ptr<ReprogControls> autoVersion(Device *dev);
protected:
ReprogControls(Device* dev, uint16_t _id);
std::map<uint16_t, ControlInfo> _cids;
bool _cids_initialized = false;
std::mutex _cids_populating;
};
class ReprogControlsV2 : public ReprogControls
{
class ReprogControlsV2 : public ReprogControls {
public:
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V2;
virtual uint16_t getID() override { return ID; }
[[nodiscard]] uint16_t getID() override { return ID; }
explicit ReprogControlsV2(Device* dev);
protected:
ReprogControlsV2(Device* dev, uint16_t _id);
};
class ReprogControlsV2_2 : public ReprogControlsV2
{
class ReprogControlsV2_2 : public ReprogControlsV2 {
public:
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V2_2;
virtual uint16_t getID() override { return ID; }
[[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
{
class ReprogControlsV3 : public ReprogControlsV2_2 {
public:
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V3;
virtual uint16_t getID() override { return ID; }
[[nodiscard]] uint16_t getID() override { return ID; }
explicit ReprogControlsV3(Device* dev);
protected:
ReprogControlsV3(Device* dev, uint16_t _id);
};
class ReprogControlsV4 : public ReprogControlsV3
{
class ReprogControlsV4 : public ReprogControlsV3 {
public:
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V4;
virtual uint16_t getID() override { return ID; }
bool supportsRawXY() override { return true; }
[[nodiscard]] uint16_t getID() final { return ID; }
ControlInfo getControlReporting(uint16_t cid) override;
[[nodiscard]] bool supportsRawXY() override { return true; }
[[nodiscard]] ControlInfo getControlReporting(uint16_t cid) override;
void setControlReporting(uint8_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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,16 +15,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "Reset.h"
#include <backend/hidpp20/features/Reset.h>
using namespace logid::backend::hidpp20;
Reset::Reset(Device *device) : Feature(device, ID)
{
Reset::Reset(Device* device) : Feature(device, ID) {
}
uint16_t Reset::getProfile()
{
uint16_t Reset::getProfile() {
std::vector<uint8_t> params(0);
auto results = callFunction(GetProfile, params);
@ -33,8 +31,7 @@ uint16_t Reset::getProfile()
return profile;
}
void Reset::reset(uint16_t profile)
{
void Reset::reset(uint16_t profile) {
std::vector<uint8_t> params(2);
params[0] = (profile >> 8) & 0xff;
params[1] = profile & 0xff;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,30 +18,27 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_RESET_H
#define LOGID_BACKEND_HIDPP20_FEATURE_RESET_H
#include "../Feature.h"
#include "../feature_defs.h"
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
namespace logid {
namespace backend {
namespace hidpp20
{
class Reset : public Feature
{
namespace logid::backend::hidpp20 {
class Reset : public Feature {
public:
static const uint16_t ID = FeatureID::RESET;
virtual uint16_t getID() { return ID; }
enum Function : uint8_t
{
GetProfile = 0,
ResetToProfile = 1
[[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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -16,81 +16,66 @@
*
*/
#include "Root.h"
#include "../Error.h"
#include <backend/hidpp20/features/Root.h>
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/Error.h>
using namespace logid::backend::hidpp20;
Root::Root(Device* dev) : Feature(dev, ID)
{
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;
}
}
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;
Root::Root(hidpp::Device* dev) : EssentialFeature(dev, ID) {
}
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;
}
feature_info Root::getFeature(uint16_t feature_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)
} catch (Error& e) {
if (e.code() == Error::InvalidFeatureIndex)
throw UnsupportedFeature(feature_id);
throw e;
}
}
std::tuple<uint8_t, uint8_t> Root::getVersion()
{
std::vector<uint8_t> params(0);
auto response = this->callFunction(Function::Ping, params);
uint8_t Root::ping(uint8_t byte) {
std::vector<uint8_t> params(3);
params[2] = byte;
return std::make_tuple(response[0], response[1]);
auto response = this->callFunction(Root::Function::Ping, params);
return response[2];
}
EssentialRoot::EssentialRoot(hidpp::Device* dev) : EssentialFeature(dev, ID)
{
}
feature_info EssentialRoot::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;
}
}
std::tuple<uint8_t, uint8_t> EssentialRoot::getVersion()
{
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,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -19,50 +19,36 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_ROOT_H
#define LOGID_BACKEND_HIDPP20_FEATURE_ROOT_H
#include "../Feature.h"
#include "../EssentialFeature.h"
#include "../feature_defs.h"
#include <backend/hidpp20/EssentialFeature.h>
#include <backend/hidpp20/feature_defs.h>
namespace logid {
namespace backend {
namespace hidpp20
{
class Root : public Feature
{
namespace logid::backend::hidpp20 {
class Root : public EssentialFeature {
public:
static const uint16_t ID = FeatureID::ROOT;
virtual uint16_t getID() { return ID; }
enum Function : uint8_t
{
uint16_t getID() final { return ID; }
explicit Root(hidpp::Device* device);
enum Function : uint8_t {
GetFeature = 0,
Ping = 1
};
explicit Root(Device* device);
feature_info getFeature(uint16_t feature_id);
uint8_t ping(uint8_t byte);
feature_info getFeature (uint16_t feature_id);
std::tuple<uint8_t, uint8_t> getVersion();
enum FeatureFlag : uint8_t
{
Obsolete = 1<<7,
Hidden = 1<<6,
Internal = 1<<5
enum FeatureFlag : uint8_t {
Obsolete = 1 << 7,
Hidden = 1 << 6,
Internal = 1 << 5
};
};
class EssentialRoot : public EssentialFeature
{
public:
static const uint16_t ID = FeatureID::ROOT;
virtual uint16_t getID() { return ID; }
explicit EssentialRoot(hidpp::Device* device);
feature_info getFeature(uint16_t feature_id);
std::tuple<uint8_t, uint8_t> getVersion();
};
}}}
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_ROOT_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -15,33 +15,30 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "SmartShift.h"
#include <backend/hidpp20/features/SmartShift.h>
using namespace logid::backend::hidpp20;
SmartShift::SmartShift(Device* dev) : Feature(dev, ID)
{
SmartShift::SmartShift(Device* dev) : Feature(dev, ID) {
}
SmartShift::SmartshiftStatus SmartShift::getStatus()
{
SmartShift::SmartshiftStatus SmartShift::getStatus() {
std::vector<uint8_t> params(0);
SmartshiftStatus status{};
auto response = callFunction(GetStatus, params);
status.active = response[0]-1;
status.active = response[0] - 1;
status.autoDisengage = response[1];
status.defaultAutoDisengage = response[2];
return status;
}
void SmartShift::setStatus(SmartshiftStatus status)
{
void SmartShift::setStatus(SmartshiftStatus status) {
std::vector<uint8_t> params(3);
if(status.setActive)
if (status.setActive)
params[0] = status.active + 1;
if(status.setAutoDisengage)
if (status.setAutoDisengage)
params[1] = status.autoDisengage;
if(status.setDefaultAutoDisengage)
if (status.setDefaultAutoDisengage)
params[2] = status.defaultAutoDisengage;
callFunction(SetStatus, params);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,18 +18,15 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_SMARTSHIFT_H
#define LOGID_BACKEND_HIDPP20_FEATURE_SMARTSHIFT_H
#include "../feature_defs.h"
#include "../Feature.h"
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp20/Feature.h>
namespace logid {
namespace backend {
namespace hidpp20
{
class SmartShift : public Feature
{
namespace logid::backend::hidpp20 {
class SmartShift : public Feature {
public:
static const uint16_t ID = FeatureID::SMART_SHIFT;
virtual uint16_t getID() { return ID; }
uint16_t getID() final { return ID; }
enum Function {
GetStatus = 0,
@ -38,8 +35,7 @@ namespace hidpp20
explicit SmartShift(Device* dev);
struct SmartshiftStatus
{
struct SmartshiftStatus {
bool active;
uint8_t autoDisengage;
uint8_t defaultAutoDisengage;
@ -47,8 +43,9 @@ namespace hidpp20
};
SmartshiftStatus getStatus();
void setStatus(SmartshiftStatus status);
};
}}}
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_SMARTSHIFT_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -16,17 +16,15 @@
*
*/
#include <backend/hidpp20/features/ThumbWheel.h>
#include <cassert>
#include "ThumbWheel.h"
using namespace logid::backend::hidpp20;
ThumbWheel::ThumbWheel(Device *dev) : Feature(dev, ID)
{
ThumbWheel::ThumbWheel(Device* dev) : Feature(dev, ID) {
}
ThumbWheel::ThumbwheelInfo ThumbWheel::getInfo()
{
ThumbWheel::ThumbwheelInfo ThumbWheel::getInfo() {
std::vector<uint8_t> params(0), response;
ThumbwheelInfo info{};
response = callFunction(GetInfo, params);
@ -43,20 +41,20 @@ ThumbWheel::ThumbwheelInfo ThumbWheel::getInfo()
return info;
}
ThumbWheel::ThumbwheelStatus ThumbWheel::getStatus()
{
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)
{
ThumbWheel::ThumbwheelStatus ThumbWheel::setStatus(bool divert, bool invert) {
std::vector<uint8_t> params(2), response;
ThumbwheelStatus status{};
params[0] = divert;
@ -69,12 +67,10 @@ ThumbWheel::ThumbwheelStatus ThumbWheel::setStatus(bool divert, bool invert)
return status;
}
ThumbWheel::ThumbwheelEvent ThumbWheel::thumbwheelEvent(hidpp::Report& report)
{
ThumbWheel::ThumbwheelEvent ThumbWheel::thumbwheelEvent(const hidpp::Report& report) {
assert(report.function() == Event);
ThumbwheelEvent event{};
event.rotation = report.paramBegin()[1];
event.rotation |= report.paramBegin()[0] << 8;
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]);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* 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
@ -18,18 +18,16 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_THUMBWHEEL_H
#define LOGID_BACKEND_HIDPP20_FEATURE_THUMBWHEEL_H
#include "../feature_defs.h"
#include "../Feature.h"
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp/Report.h>
namespace logid {
namespace backend {
namespace hidpp20
{
class ThumbWheel : public Feature
{
namespace logid::backend::hidpp20 {
class ThumbWheel : public Feature {
public:
static const uint16_t ID = FeatureID::THUMB_WHEEL;
virtual uint16_t getID() { return ID; }
uint16_t getID() final { return ID; }
enum Function {
GetInfo = 0,
@ -43,12 +41,11 @@ namespace hidpp20
explicit ThumbWheel(Device* dev);
enum Capabilities : uint8_t
{
enum Capabilities : uint8_t {
Timestamp = 1,
Touch = 1<<1,
Proxy = 1<<2,
SingleTap = 1<<3
Touch = 1 << 1,
Proxy = 1 << 2,
SingleTap = 1 << 3
};
struct ThumbwheelInfo {
@ -66,8 +63,7 @@ namespace hidpp20
bool proxy;
};
enum RotationStatus : uint8_t
{
enum RotationStatus : uint8_t {
Inactive = 0,
Start = 1,
Active = 2,
@ -81,13 +77,14 @@ namespace hidpp20
uint8_t flags;
};
ThumbwheelInfo getInfo();
ThumbwheelStatus getStatus();
[[nodiscard]] ThumbwheelInfo getInfo();
[[nodiscard]] ThumbwheelStatus getStatus();
ThumbwheelStatus setStatus(bool divert, bool invert);
ThumbwheelEvent thumbwheelEvent(hidpp::Report& report);
[[nodiscard]] static ThumbwheelEvent thumbwheelEvent(const hidpp::Report& report);
};
}}}
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_THUMBWHEEL_H

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