Compare commits

..

No commits in common. "main" and "v0.2.1" have entirely different histories.
main ... v0.2.1

151 changed files with 5904 additions and 9863 deletions

View File

@ -1,25 +0,0 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*.{h,cpp}]
end_of_line = lf
indent_style = space
indent_size = 4
insert_final_newline = true
# Matches multiple files with brace expansion notation
# Set default charset
[*.json]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 4
insert_final_newline = true
# 4 space indentation
[CMakeLists.txt]
end_of_line = lf
indent_style = space
indent_size = 4
insert_final_newline = true

View File

@ -1,55 +0,0 @@
name: Build test
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
name: Build
runs-on: ubuntu-latest
container: ${{ matrix.container }}
strategy:
matrix:
container: [ 'ubuntu:latest', 'ubuntu:20.04', 'fedora:latest', 'archlinux:base-devel' ]
steps:
- name: Install dependencies (Ubuntu)
if: startsWith(matrix.container, 'ubuntu')
env:
DEBIAN_FRONTEND: noninteractive
TZ: Etc/UTC
run: |
apt-get update -y -q
apt-get install -y -q \
build-essential cmake git pkg-config \
libevdev-dev libudev-dev libconfig++-dev libglib2.0-dev
- name: Install dependencies (Fedora)
if: startsWith(matrix.container, 'fedora')
run: |
dnf update -y
dnf install -y \
cmake git libevdev-devel \
systemd-devel libconfig-devel gcc-c++ glib2-devel
- name: Install dependencies (Arch Linux)
if: startsWith(matrix.container, 'archlinux')
run: |
pacman -Syu --noconfirm \
cmake git libevdev libconfig systemd-libs glib2
- name: Checkout repository
uses: actions/checkout@v3
with:
submodules: recursive
- name: Build LogiOps
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_FLAGS="-Werror"
cmake --build build -j$(nproc)

View File

@ -1,35 +0,0 @@
name: Publish release tarball
on:
push:
tags:
- 'v*.*'
jobs:
publish:
name: Publish release tarball
runs-on: ubuntu-latest
env:
ARCHIVE_NAME: logiops-${{ github.ref_name }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
submodules: recursive
- name: Add version info
run: echo ${{ github.ref_name }} > version.txt
- name: Remove git repo info
run: find . -name '.git' | xargs rm -rf
- name: Create tarball
run: |
find * -type f| tar caf /tmp/$ARCHIVE_NAME.tar.gz \
--xform s:^:$ARCHIVE_NAME/: --verbatim-files-from -T-
mv /tmp/$ARCHIVE_NAME.tar.gz .
- name: Upload release asset
uses: softprops/action-gh-release@v0.1.15
with:
files: logiops-${{ github.ref_name }}.tar.gz

37
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,37 @@
name: release-ci
on:
push:
types:
- tags
workflow_dispatch:
jobs:
job:
name: ${{ matrix.os }}-release
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
include:
- os: ubuntu-latest
triplet: x64-linux
installDependencies: 'sudo apt-get update -m && sudo apt-get install libconfig++-dev libevdev-dev libudev-dev'
steps:
- uses: actions/checkout@v1
with:
submodules: true
- name: Install dependencies
run: '${{ matrix.installDependencies }}'
- name: Run CMake+Make
uses: lukka/run-cmake@v2
id: runcmake
with:
cmakeGenerator: 'UnixMakefiles'
cmakeListsOrSettingsJson: 'CMakeListsTxtBasic'
cmakeListsTxtPath: '${{ github.workspace }}/CMakeLists.txt'
cmakeAdditionalArgs: '-DFORCE_BUILD_HIDPP=True'
buildWithCMakeArgs: '-- -v'
cmakeBuildType: 'Release'
buildDirectory: '${{ runner.workspace }}/build/'

3
.gitmodules vendored
View File

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

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.12)
cmake_minimum_required(VERSION 3.10)
set(CMAKE_INSTALL_PREFIX /usr)
@ -6,22 +6,18 @@ project(logiops)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
set(CMAKE_CXX_FLAGS_NONE "${CMAKE_CXX_FLAGS_NONE} -DNDEBUG")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -Wall -Wextra")
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
option(USE_USER_BUS "Uses user bus" OFF)
find_package(Git)
# Set version number and update submodules
if(EXISTS ${PROJECT_SOURCE_DIR}/.git AND GIT_FOUND)
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags
# Set version number
if(GIT_FOUND AND EXISTS ${CMAKE_SOURCE_DIR}/.git)
execute_process(COMMAND ${GIT_EXECUTABLE} describe
OUTPUT_VARIABLE LOGIOPS_VERSION
RESULT_VARIABLE LOGIOPS_VERSION_RET
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
ERROR_QUIET)
if(NOT LOGIOPS_VERSION_RET EQUAL "0")
execute_process(COMMAND ${GIT_EXECUTABLE}
@ -29,20 +25,10 @@ if(EXISTS ${PROJECT_SOURCE_DIR}/.git AND GIT_FOUND)
OUTPUT_VARIABLE LOGIOPS_COMMIT_HASH)
set(LOGIOPS_VERSION git-${LOGIOPS_COMMIT_HASH})
endif()
execute_process(COMMAND ${GIT_EXECUTABLE}
submodule update --init --recursive
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
string(REGEX REPLACE "\n$" "" LOGIOPS_VERSION ${LOGIOPS_VERSION})
elseif(EXISTS ${PROJECT_SOURCE_DIR}/version.txt)
elseif(EXISTS ${CMAKE_SOURCE_DIR}/version.txt)
file(READ version.txt LOGIOPS_VERSION)
string(REGEX REPLACE "\n$" "" LOGIOPS_VERSION ${LOGIOPS_VERSION})
endif()
IF(NOT EXISTS ${PROJECT_SOURCE_DIR}/src/ipcgull)
message(FATAL_ERROR "Missing ipcgull submodule")
endif()
if(NOT LOGIOPS_VERSION)
@ -52,9 +38,4 @@ 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

@ -1,6 +1,4 @@
# LogiOps
![Build Status](https://github.com/PixlOne/logiops/actions/workflows/build-test.yml/badge.svg)
# logiops
This is an unofficial driver for Logitech mice and keyboard.
@ -9,45 +7,30 @@ This is currently only compatible with HID++ \>2.0 devices.
## Configuration
[Refer to the wiki for details.](https://github.com/PixlOne/logiops/wiki/Configuration)
You may also refer to [logid.example.cfg](./logid.example.cfg) for an example.
You may also refer to logid.example.cfg for an example.
Default location for the configuration file is /etc/logid.cfg, but another can be specified using the `-c` flag.
Default location for the configuration file is /etc/logid.cfg.
## Dependencies
This project requires a C++20 compiler, `cmake`, `libevdev`, `libudev`, `glib`, and `libconfig`.
For popular distributions, I've included commands below.
This project requires a C++14 compiler, cmake, libevdev, libudev, and libconfig. For popular distributions, I've included commands below.
**Arch Linux:** `sudo pacman -S base-devel cmake libevdev libconfig systemd-libs glib2`
**Debian/Ubuntu:** `sudo apt install cmake libevdev-dev libudev-dev libconfig++-dev`
**Debian/Ubuntu:** `sudo apt install build-essential cmake pkg-config libevdev-dev libudev-dev libconfig++-dev libglib2.0-dev`
**Fedora:** `sudo dnf install cmake libevdev-devel systemd-devel libconfig-devel gcc-c++ glib2-devel`
**Gentoo Linux:** `sudo emerge dev-libs/libconfig dev-libs/libevdev dev-libs/glib dev-util/cmake virtual/libudev`
**Solus:** `sudo eopkg install cmake libevdev-devel libconfig-devel libgudev-devel glib2-devel`
**openSUSE:** `sudo zypper install cmake libevdev-devel systemd-devel libconfig-devel gcc-c++ libconfig++-devel libudev-devel glib2-devel`
**Arch Linux:** `sudo pacman -S cmake libevdev libconfig libudev`
## Building
To build this project, run:
```bash
```
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake ..
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.
To install, run `sudo make install` after building. You can set the daemon to start at boot by running `sudo systemctl start logid`.
## 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.
@ -60,10 +43,7 @@ I'm also looking for contributors to help in my project; feel free to submit a p
[For a list of tested devices, check TESTED.md](TESTED.md)
## Credits
Logitech, Logi, and their logos are trademarks or registered trademarks of Logitech Europe S.A. and/or its affiliates in the United States and/or other countries. This software is an independent product that is not endorsed or created by Logitech.
## Special Thanks
Thanks to the following people for contributing to this repository.
- [Clément Vuchener & contributors for creating the old HID++ library](https://github.com/cvuchener/hidpp)

View File

@ -2,21 +2,12 @@
This is not by any means an exhaustive list. Many more devices are supported but these devices are the ones that are confirmed to be working. To add to this list, submit a pull request adding your device.
| Device | Compatible? | Config Name |
| :-----------------: | :---------: | :------------------------------------: |
| MX Master 3S | Yes | `MX Master 3S` |
| MX Master 3 | Yes | `Wireless Mouse MX Master 3` |
| MX Master 3 for Mac | Yes | `MX Master 3 for Mac` |
| MX Master 2S | Yes | `Wireless Mouse MX Master 2S` |
| MX Master | Yes | `Wireless Mouse MX Master` |
| MX Anywhere S2 | Yes | `Wireless Mobile Mouse MX Anywhere 2S` |
| MX Anywhere 3 | Yes | `MX Anywhere 3` |
| MX Vertical | Yes | `MX Vertical Advanced Ergonomic Mouse` |
| MX Ergo | Yes | `MX Ergo Multi-Device Trackball ` |
| MX Ergo M575 | Yes | `ERGO M575 Trackball` |
| M720 | Yes | `M720 Triathlon Multi-Device Mouse` |
| M590 | Yes | `M585/M590 Multi-Device Mouse` |
| T400 | Yes | `Zone Touch Mouse T400` |
| MX Keys | Yes | `MX Keys Wireless Keyboard` |
| M500s | Yes | `Advanced Corded Mouse M500s` |
| Device | Compatible? |
|:------------:|:-----------:|
| MX Master 3 | Yes |
| MX Master 2S | Yes |
| MX Master | Yes |
| MX Vertical | Yes |
| MX Ergo | Yes |
| M720 | Yes |
| T400 | Yes |

View File

@ -1,11 +1,10 @@
devices: (
{
name: "Wireless Mouse MX Master";
name: "MX Master";
smartshift:
{
on: true;
threshold: 30;
torque: 50;
};
hiresscroll:
{

@ -1 +0,0 @@
Subproject commit cd0f9a8cefb5b2545e163fceb249fdbcbaf666aa

View File

@ -1,9 +1,7 @@
cmake_minimum_required(VERSION 3.12)
cmake_minimum_required(VERSION 3.10)
project(logid)
# C++20 is only needed for string literal template parameters
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../CMake")
@ -13,7 +11,6 @@ find_package(PkgConfig REQUIRED)
add_executable(logid
logid.cpp
util/log.cpp
config/config.cpp
InputDevice.cpp
DeviceManager.cpp
Device.cpp
@ -23,8 +20,6 @@ add_executable(logid
features/SmartShift.cpp
features/HiresScroll.cpp
features/RemapButton.cpp
features/DeviceStatus.cpp
features/ThumbWheel.cpp
actions/Action.cpp
actions/NullAction.cpp
actions/KeypressAction.cpp
@ -34,19 +29,17 @@ 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
actions/gesture/IntervalGesture.cpp
actions/gesture/AxisGesture.cpp
actions/gesture/NullGesture.cpp
backend/Error.cpp
backend/raw/DeviceMonitor.cpp
backend/raw/RawDevice.cpp
backend/raw/IOMonitor.cpp
backend/hidpp10/Receiver.cpp
backend/hidpp10/ReceiverMonitor.cpp
backend/dj/Receiver.cpp
backend/dj/ReceiverMonitor.cpp
backend/dj/Error.cpp
backend/hidpp/Device.cpp
backend/hidpp/Report.cpp
backend/hidpp10/Error.cpp
@ -64,9 +57,12 @@ add_executable(logid
backend/hidpp20/features/ReprogControls.cpp
backend/hidpp20/features/HiresScroll.cpp
backend/hidpp20/features/ChangeHost.cpp
backend/hidpp20/features/WirelessDeviceStatus.cpp
backend/hidpp20/features/ThumbWheel.cpp
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})
@ -81,27 +77,20 @@ find_path(EVDEV_INCLUDE_DIR libevdev/libevdev.h
find_library(EVDEV_LIBRARY
NAMES evdev libevdev)
set(IPCGULL_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/../ipcgull/src/include)
message(${IPCGULL_INCLUDE_DIRS})
include_directories(. ${EVDEV_INCLUDE_DIR} ${LIBUDEV_INCLUDE_DIRECTORIES} ${IPCGULL_INCLUDE_DIRS})
include_directories(${EVDEV_INCLUDE_DIR} ${LIBUDEV_INCLUDE_DIRECTORIES})
target_link_libraries(logid ${CMAKE_THREAD_LIBS_INIT} ${EVDEV_LIBRARY} config++
${LIBUDEV_LIBRARIES} ipcgull)
${LIBUDEV_LIBRARIES})
install(TARGETS logid DESTINATION bin)
if (SYSTEMD_FOUND)
if ("${SYSTEMD_SERVICES_INSTALL_DIR}" STREQUAL "")
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}")
endif ()
# Install systemd service
configure_file(logid.service.in ${CMAKE_BINARY_DIR}/logid.service)
configure_file(logid.service.cmake ${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}
@ -109,12 +98,3 @@ if (SYSTEMD_FOUND)
elseif(NOT SYSTEMD_FOUND AND SYSTEMD_SERVICES_INSTALL_DIR)
message(FATAL_ERROR "systemd is not found w/ pkg-config but SYSTEMD_SERVICES_INSTALL_DIR is defined.")
endif()
# Install DBus conf
# TODO: Is there a better way of setting the system policy directory?
set(DBUS_SYSTEM_POLICY_INSTALL_DIR "/usr/share/dbus-1/system.d")
configure_file(logiops-dbus.conf.in ${CMAKE_BINARY_DIR}/pizza.pixl.LogiOps.conf)
message(STATUS "dbus system policy will be installed at ${DBUS_SYSTEM_POLICY_INSTALL_DIR}")
install(FILES ${CMAKE_BINARY_DIR}/pizza.pixl.LogiOps.conf
DESTINATION ${DBUS_SYSTEM_POLICY_INSTALL_DIR}
COMPONENT cp)

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -19,168 +19,87 @@
#ifndef LOGID_DEVICE_H
#define LOGID_DEVICE_H
#include <features/DeviceFeature.h>
#include <backend/hidpp20/Device.h>
#include <backend/hidpp/defs.h>
#include <ipcgull/node.h>
#include <ipcgull/interface.h>
#include <Configuration.h>
namespace logid {
class DeviceManager;
#include "backend/hidpp/defs.h"
#include "backend/hidpp20/Device.h"
#include "backend/hidpp20/Feature.h"
#include "features/DeviceFeature.h"
#include "Configuration.h"
#include "util/log.h"
namespace logid
{
class Device;
class Receiver;
class InputDevice;
class DeviceNickname {
class DeviceConfig
{
public:
explicit DeviceNickname(const std::shared_ptr<DeviceManager>& manager);
DeviceNickname() = delete;
DeviceNickname(const DeviceNickname&) = delete;
~DeviceNickname();
operator std::string() const;
DeviceConfig(const std::shared_ptr<Configuration>& config, Device*
device);
libconfig::Setting& getSetting(const std::string& path);
private:
const int _nickname;
const std::weak_ptr<DeviceManager> _manager;
Device* _device;
std::string _root_setting;
std::shared_ptr<Configuration> _config;
};
/* TODO: Implement HID++ 1.0 support
* Currently, the logid::Device class has a hardcoded requirement
* for an HID++ 2.0 device.
*/
class Device : public ipcgull::object {
class Device
{
public:
std::string name();
Device(std::string path, backend::hidpp::DeviceIndex index);
Device(const std::shared_ptr<backend::raw::RawDevice>& raw_device,
backend::hidpp::DeviceIndex index);
std::string name();
uint16_t pid();
[[nodiscard]] config::Profile& activeProfile();
[[nodiscard]] std::vector<std::string> getProfiles() const;
void setProfile(const std::string& profile);
void setProfileDelayed(const std::string& profile);
void removeProfile(const std::string& profile);
void clearProfile(const std::string& profile);
DeviceConfig& config();
backend::hidpp20::Device& hidpp20();
static std::shared_ptr<Device> make(
std::string path,
backend::hidpp::DeviceIndex index,
std::shared_ptr<DeviceManager> manager);
static std::shared_ptr<Device> make(
std::shared_ptr<backend::raw::RawDevice> raw_device,
backend::hidpp::DeviceIndex index,
std::shared_ptr<DeviceManager> manager);
static std::shared_ptr<Device> make(
Receiver* receiver,
backend::hidpp::DeviceIndex index,
std::shared_ptr<DeviceManager> manager);
void wakeup();
void sleep();
void reconfigure();
void reset();
[[nodiscard]] std::shared_ptr<InputDevice> virtualInput() const;
[[nodiscard]] std::shared_ptr<ipcgull::node> ipcNode() const;
template<typename T>
std::shared_ptr<T> getFeature(const std::string& name) {
std::shared_ptr<T> getFeature(std::string name) {
auto it = _features.find(name);
if(it == _features.end())
return nullptr;
try {
return std::dynamic_pointer_cast<T>(it->second);
} catch(std::bad_cast& e) {
logPrintf(ERROR, "bad_cast while getting device feature %s: %s",
name.c_str(), e.what());
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, features::DeviceFeature::make<T>(this));
} catch (features::UnsupportedFeature& e) {
_features.emplace(name, std::make_shared<T>(this));
} catch (backend::hidpp20::UnsupportedFeature& e) {
}
}
std::shared_ptr<backend::hidpp20::Device> _hidpp20;
backend::hidpp20::Device _hidpp20;
std::string _path;
backend::hidpp::DeviceIndex _index;
std::map<std::string, std::shared_ptr<features::DeviceFeature>> _features;
config::Device& _config;
mutable std::shared_mutex _profile_mutex;
ipcgull::property<std::string> _profile_name;
std::map<std::string, config::Profile>::iterator _profile;
const std::weak_ptr<DeviceManager> _manager;
std::map<std::string, std::shared_ptr<features::DeviceFeature>>
_features;
DeviceConfig _config;
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-2023 PixlOne
* 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
@ -16,305 +16,95 @@
*
*/
#include <DeviceManager.h>
#include <backend/Error.h>
#include <util/log.h>
#include <thread>
#include <sstream>
#include <utility>
#include <ipc_defs.h>
#include "DeviceManager.h"
#include "Receiver.h"
#include "util/log.h"
#include "backend/hidpp10/Error.h"
#include "backend/Error.h"
using namespace logid;
using namespace logid::backend;
DeviceManager::DeviceManager(std::shared_ptr<Configuration> config,
std::shared_ptr<InputDevice> virtual_input,
std::shared_ptr<ipcgull::server> server) :
backend::raw::DeviceMonitor(),
_server(std::move(server)), _config(std::move(config)),
_virtual_input(std::move(virtual_input)),
_root_node(ipcgull::node::make_root("")),
_device_node(ipcgull::node::make_root("devices")),
_receiver_node(ipcgull::node::make_root("receivers")) {
_ipc_devices = _root_node->make_interface<DevicesIPC>(this);
_ipc_receivers = _root_node->make_interface<ReceiversIPC>(this);
_ipc_config = _root_node->make_interface<Configuration::IPC>(_config.get());
_device_node->add_server(_server);
_receiver_node->add_server(_server);
_root_node->add_server(_server);
}
std::shared_ptr<Configuration> DeviceManager::config() const {
return _config;
}
std::shared_ptr<InputDevice> DeviceManager::virtualInput() const {
return _virtual_input;
}
std::shared_ptr<const ipcgull::node> DeviceManager::devicesNode() const {
return _device_node;
}
std::shared_ptr<const ipcgull::node> DeviceManager::receiversNode() const {
return _receiver_node;
}
void DeviceManager::addDevice(std::string path) {
void DeviceManager::addDevice(std::string path)
{
bool defaultExists = true;
bool isReceiver = false;
// Check if device is ignored before continuing
{
auto raw_dev = raw::RawDevice::make(path, self<DeviceManager>().lock());
if (config()->ignore.has_value() &&
config()->ignore.value().contains(raw_dev->productId())) {
raw::RawDevice raw_dev(path);
if(global_config->isIgnored(raw_dev.productId())) {
logPrintf(DEBUG, "%s: Device 0x%04x ignored.",
path.c_str(), raw_dev->productId());
path.c_str(), raw_dev.productId());
return;
}
}
try {
auto device = hidpp::Device::make(
path, hidpp::DefaultDevice, self<DeviceManager>().lock(),
config()->io_timeout.value_or(defaults::io_timeout));
isReceiver = device->version() == std::make_tuple(1, 0);
} catch (hidpp20::Error& e) {
if (e.code() != hidpp20::Error::UnknownDevice)
throw DeviceNotReady();
defaultExists = false;
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 DeviceNotReady();
throw;
} catch(hidpp::Device::InvalidDevice &e) { // Ignore
defaultExists = false;
} catch (hidpp::Device::InvalidDevice& e) {
if (e.code() == hidpp::Device::InvalidDevice::VirtualNode) {
logPrintf(DEBUG, "Ignoring virtual node on %s", path.c_str());
} else if (e.code() == hidpp::Device::InvalidDevice::Asleep) {
/* May be a valid device, wait */
throw DeviceNotReady();
}
return;
} catch(std::system_error &e) {
logPrintf(WARN, "I/O error on %s: %s, skipping device.", path.c_str(), e.what());
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();
logPrintf(WARN, "Device %s timed out.", path.c_str());
defaultExists = false;
}
if(isReceiver) {
logPrintf(INFO, "Detected receiver at %s", path.c_str());
auto receiver = Receiver::make(path, self<DeviceManager>().lock());
std::lock_guard<std::mutex> lock(_map_lock);
auto receiver = std::make_shared<Receiver>(path);
receiver->run();
_receivers.emplace(path, receiver);
_ipc_receivers->receiverAdded(receiver);
} else {
/* TODO: Can non-receivers only contain 1 device?
* If the device exists, it is guaranteed to be an HID++ 2.0 device */
if(defaultExists) {
auto device = Device::make(path, hidpp::DefaultDevice, self<DeviceManager>().lock());
std::lock_guard<std::mutex> lock(_map_lock);
auto device = std::make_shared<Device>(path, hidpp::DefaultDevice);
_devices.emplace(path, device);
_ipc_devices->deviceAdded(device);
} else {
try {
auto device = Device::make(path, hidpp::CordedDevice, self<DeviceManager>().lock());
std::lock_guard<std::mutex> lock(_map_lock);
auto device = std::make_shared<Device>(path,
hidpp::CordedDevice);
_devices.emplace(path, device);
_ipc_devices->deviceAdded(device);
} catch(hidpp10::Error &e) {
if(e.code() != hidpp10::Error::UnknownDevice)
throw DeviceNotReady();
} catch (hidpp20::Error& e) {
if (e.code() != hidpp20::Error::UnknownDevice)
throw DeviceNotReady();
} catch (hidpp::Device::InvalidDevice& e) {
if (e.code() == hidpp::Device::InvalidDevice::Asleep)
throw DeviceNotReady();
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) {
// This error should have been thrown previously
logPrintf(WARN, "I/O error on %s: %s", path.c_str(), e.what());
} catch (TimeoutError& e) {
throw DeviceNotReady();
logPrintf(WARN, "I/O error on %s: %s", path.c_str(),
e.what());
}
}
}
}
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);
void DeviceManager::removeDevice(std::string path)
{
auto receiver = _receivers.find(path);
if(receiver != _receivers.end()) {
_ipc_receivers->receiverRemoved(receiver->second);
_receivers.erase(receiver);
logPrintf(INFO, "Receiver on %s disconnected", path.c_str());
} else {
auto device = _devices.find(path);
if (device != _devices.end()) {
_ipc_devices->deviceRemoved(device->second);
if(device != _devices.find(path)) {
_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-2023 PixlOne
* 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
@ -19,98 +19,32 @@
#ifndef LOGID_DEVICEMANAGER_H
#define LOGID_DEVICEMANAGER_H
#include <backend/raw/DeviceMonitor.h>
#include <Device.h>
#include <Receiver.h>
#include <ipcgull/node.h>
#include <ipcgull/interface.h>
#include <map>
#include <thread>
#include <mutex>
namespace logid {
class InputDevice;
#include "backend/raw/DeviceMonitor.h"
#include "backend/hidpp/Device.h"
#include "Device.h"
#include "Receiver.h"
class DeviceManager : public backend::raw::DeviceMonitor {
namespace logid
{
class DeviceManager : public backend::raw::DeviceMonitor
{
public:
[[nodiscard]] std::shared_ptr<Configuration> config() const;
[[nodiscard]] std::shared_ptr<InputDevice> virtualInput() const;
[[nodiscard]] std::shared_ptr<const ipcgull::node> devicesNode() const;
[[nodiscard]] std::shared_ptr<const ipcgull::node>
receiversNode() const;
void addExternalDevice(const std::shared_ptr<Device>& d);
void removeExternalDevice(const std::shared_ptr<Device>& d);
std::mutex& mutex() const;
DeviceManager() = default;
protected:
DeviceManager(std::shared_ptr<Configuration> config,
std::shared_ptr<InputDevice> virtual_input,
std::shared_ptr<ipcgull::server> server);
void addDevice(std::string path) final;
void removeDevice(std::string path) final;
void addDevice(std::string path) override;
void removeDevice(std::string path) override;
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

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -20,8 +20,6 @@
#define LOGID_INPUTDEVICE_H
#include <memory>
#include <string>
#include <mutex>
extern "C"
{
@ -29,61 +27,38 @@ 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);
~InputDevice();
void registerKey(uint code);
void registerAxis(uint axis);
void moveAxis(uint axis, int movement);
void pressKey(uint code);
void releaseKey(uint code);
static std::string toKeyName(uint code);
static uint toKeyCode(const std::string& name);
static std::string toAxisName(uint code);
static uint toAxisCode(const std::string& name);
static int getLowResAxis(uint axis_code);
private:
void _sendEvent(uint type, uint code, int value);
void _enableEvent(uint type, uint name);
static std::string _toEventName(uint type, uint code);
static uint _toEventCode(uint type, const std::string& name);
bool registered_keys[KEY_CNT]{};
bool registered_axis[REL_CNT]{};
libevdev* device;
libevdev_uinput* ui_device{};
std::mutex _input_mutex;
};
extern std::unique_ptr<InputDevice> virtual_input;
}
#endif //LOGID_INPUTDEVICE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -16,74 +16,26 @@
*
*/
#include <Receiver.h>
#include <DeviceManager.h>
#include <backend/Error.h>
#include <util/log.h>
#include <ipc_defs.h>
#include "Receiver.h"
#include "util/log.h"
#include "backend/hidpp10/Error.h"
#include "backend/hidpp20/Error.h"
#include "backend/Error.h"
using namespace logid;
using namespace logid::backend;
ReceiverNickname::ReceiverNickname(
const std::shared_ptr<DeviceManager>& manager) :
_nickname(manager->newReceiverNickname()), _manager(manager) {
Receiver::Receiver(const std::string& path) :
dj::ReceiverMonitor(path), _path (path)
{
}
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) {
void Receiver::addDevice(hidpp::DeviceConnectionEvent event)
{
std::unique_lock<std::mutex> lock(_devices_change);
auto manager = _manager.lock();
if (!manager) {
logPrintf(ERROR, "Orphan Receiver, missing DeviceManager");
logPrintf(ERROR, "Fatal error, file a bug report. Program will now exit.");
std::terminate();
}
try {
// Check if device is ignored before continuing
if (manager->config()->ignore.value_or(std::set<uint16_t>()).contains(event.pid)) {
if(global_config->isIgnored(event.pid)) {
logPrintf(DEBUG, "%s:%d: Device 0x%04x ignored.",
_path.c_str(), event.index, event.pid);
return;
@ -101,10 +53,9 @@ void Receiver::addDevice(hidpp::DeviceConnectionEvent event) {
if(!event.linkEstablished)
return;
auto hidpp_device = hidpp::Device::make(
receiver(), event, manager->config()->io_timeout.value_or(defaults::io_timeout));
hidpp::Device hidpp_device(receiver(), event);
auto version = hidpp_device->version();
auto version = hidpp_device.version();
if(std::get<0>(version) < 2) {
logPrintf(INFO, "Unsupported HID++ 1.0 device on %s:%d connected.",
@ -112,16 +63,15 @@ void Receiver::addDevice(hidpp::DeviceConnectionEvent event) {
return;
}
hidpp_device.reset();
std::shared_ptr<Device> device = std::make_shared<Device>(
receiver()->rawDevice(), event.index);
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());
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());
@ -133,102 +83,8 @@ void Receiver::addDevice(hidpp::DeviceConnectionEvent event) {
}
}
void Receiver::removeDevice(hidpp::DeviceIndex index) {
void Receiver::removeDevice(hidpp::DeviceIndex index)
{
std::unique_lock<std::mutex> lock(_devices_change);
std::unique_lock<std::mutex> manager_lock;
if (auto manager = _manager.lock())
manager_lock = std::unique_lock<std::mutex>(manager->mutex());
auto device = _devices.find(index);
if (device != _devices.end()) {
if (auto manager = _manager.lock())
manager->removeExternalDevice(device->second);
_devices.erase(device);
}
}
void Receiver::pairReady(const hidpp10::DeviceDiscoveryEvent& event,
const std::string& passkey) {
std::string type;
switch (event.deviceType) {
case hidpp::DeviceUnknown:
type = "unknown";
break;
case hidpp::DeviceKeyboard:
type = "keyboard";
break;
case hidpp::DeviceMouse:
type = "mouse";
break;
case hidpp::DeviceNumpad:
type = "numpad";
break;
case hidpp::DevicePresenter:
type = "presenter";
break;
case hidpp::DeviceTouchpad:
type = "touchpad";
break;
case hidpp::DeviceTrackball:
type = "trackball";
break;
}
_ipc_interface->emit_signal("PairReady", event.name, event.pid, type, passkey);
}
const std::string& Receiver::path() const {
return _path;
}
std::shared_ptr<hidpp10::Receiver> Receiver::rawReceiver() {
return receiver();
}
std::vector<std::tuple<int, uint16_t, std::string, uint32_t>> Receiver::pairedDevices() const {
std::vector<std::tuple<int, uint16_t, std::string, uint32_t>> ret;
for (int i = hidpp::WirelessDevice1; i <= hidpp::WirelessDevice6; ++i) {
try {
auto index(static_cast<hidpp::DeviceIndex>(i));
auto pair_info = receiver()->getPairingInfo(index);
auto extended_pair_info = receiver()->getExtendedPairingInfo(index);
auto name = receiver()->getDeviceName(index);
ret.emplace_back(i, pair_info.pid, name, extended_pair_info.serialNumber);
} catch (hidpp10::Error& e) {
}
}
return ret;
}
void Receiver::startPair(uint8_t timeout) {
_startPair(timeout);
}
void Receiver::stopPair() {
_stopPair();
}
void Receiver::unpair(int device) {
receiver()->disconnect(static_cast<hidpp::DeviceIndex>(device));
}
Receiver::IPC::IPC(Receiver* receiver) :
ipcgull::interface(
SERVICE_ROOT_NAME ".Receiver",
{
{"GetPaired", {receiver, &Receiver::pairedDevices, {"devices"}}},
{"StartPair", {receiver, &Receiver::startPair, {"timeout"}}},
{"StopPair", {receiver, &Receiver::stopPair}},
{"Unpair", {receiver, &Receiver::unpair, {"device"}}}
},
{
{"Bolt", ipcgull::property<bool>(ipcgull::property_readable,
receiver->receiver()->bolt())}
}, {
{"PairReady",
ipcgull::signal::make_signal<std::string, uint16_t, std::string,
std::string>(
{"name", "pid", "type", "passkey"})
}
}) {
_devices.erase(index);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -20,80 +20,23 @@
#define LOGID_RECEIVER_H
#include <string>
#include <Device.h>
#include <backend/hidpp10/ReceiverMonitor.h>
#include "backend/dj/ReceiverMonitor.h"
#include "Device.h"
namespace logid {
class ReceiverNickname {
namespace logid
{
class Receiver : public backend::dj::ReceiverMonitor
{
public:
explicit ReceiverNickname(const std::shared_ptr<DeviceManager>& manager);
ReceiverNickname() = delete;
ReceiverNickname(const ReceiverNickname&) = delete;
~ReceiverNickname();
operator std::string() const;
private:
const int _nickname;
const std::weak_ptr<DeviceManager> _manager;
};
class Receiver : public backend::hidpp10::ReceiverMonitor,
public ipcgull::object {
public:
typedef std::map<backend::hidpp::DeviceIndex, std::shared_ptr<Device>>
DeviceList;
~Receiver() noexcept override;
static std::shared_ptr<Receiver> make(
const std::string& path,
const std::shared_ptr<DeviceManager>& manager);
[[nodiscard]] const std::string& path() const;
std::shared_ptr<backend::hidpp10::Receiver> rawReceiver();
[[nodiscard]] const DeviceList& devices() const;
[[nodiscard]] std::vector<std::tuple<int, uint16_t, std::string, uint32_t>>
pairedDevices() const;
void startPair(uint8_t timeout);
void stopPair();
void unpair(int device);
Receiver(const std::string& path);
protected:
Receiver(const std::string& path,
const std::shared_ptr<DeviceManager>& manager);
void addDevice(backend::hidpp::DeviceConnectionEvent event) override;
void removeDevice(backend::hidpp::DeviceIndex index) override;
void pairReady(const backend::hidpp10::DeviceDiscoveryEvent& event,
const std::string& passkey) override;
private:
std::mutex _devices_change;
DeviceList _devices;
std::map<backend::hidpp::DeviceIndex, std::shared_ptr<Device>> _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-2023 PixlOne
* 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
@ -16,128 +16,64 @@
*
*/
#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>
#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"
using namespace logid;
using namespace logid::actions;
namespace logid::actions {
template<typename T>
struct action_type {
typedef typename T::action type;
};
template<typename T>
struct action_type<const T> : action_type<T> {
};
template<typename T>
struct action_type<T&> : action_type<T> {
};
template<typename T>
std::shared_ptr<Action> _makeAction(
Device* device, T& action,
const std::shared_ptr<ipcgull::node>& parent) {
return parent->make_interface<typename action_type<T>::type>(
device, std::forward<T&>(action), parent);
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();
}
template<typename T>
std::shared_ptr<Action> _makeAction(
Device* device, const std::string& name,
std::optional<T>& config,
const std::shared_ptr<ipcgull::node>& parent) {
if (name == ChangeDPI::interface_name) {
config = config::ChangeDPI();
} else if (name == CycleDPI::interface_name) {
config = config::CycleDPI();
} else if (name == KeypressAction::interface_name) {
config = config::KeypressAction();
} else if (name == NullAction::interface_name) {
config = config::NoAction();
} else if (name == ChangeHostAction::interface_name) {
config = config::ChangeHost();
} else if (name == ToggleHiresScroll::interface_name) {
config = config::ToggleHiresScroll();
} else if (name == ToggleSmartShift::interface_name) {
config = config::ToggleSmartShift();
} else if (name == ChangeProfile::interface_name) {
config = config::ChangeProfile();
} else if (name == "Default") {
config.reset();
return nullptr;
} else {
throw InvalidAction(name);
}
return Action::makeAction(device, config.value(), parent);
}
}
std::shared_ptr<Action> Action::makeAction(
Device* device, const std::string& name,
std::optional<config::BasicAction>& config,
const std::shared_ptr<ipcgull::node>& parent) {
auto ret = _makeAction(device, name, config, parent);
if (ret)
ret->_self = ret;
return ret;
}
std::shared_ptr<Action> Action::makeAction(
Device* device, const std::string& name,
std::optional<config::Action>& config,
const std::shared_ptr<ipcgull::node>& parent) {
try {
auto ret = _makeAction(device, name, config, parent);
if (ret)
ret->_self = ret;
return ret;
} catch (actions::InvalidAction& e) {
if (name == GestureAction::interface_name) {
config = config::GestureAction();
return makeAction(device, config.value(), parent);
}
throw;
}
auto& action_type = setting.lookup("type");
if(action_type.getType() != libconfig::Setting::TypeString) {
logPrintf(WARN, "Line %d: Action type must be a string",
action_type.getSourceLine());
throw InvalidAction();
}
std::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::string type = action_type;
std::transform(type.begin(), type.end(), type.begin(), ::tolower);
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;
}
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);
Action::Action(Device* device, const std::string& name, tables t) :
ipcgull::interface(SERVICE_ROOT_NAME ".Action." + name, std::move(t)),
_device(device), _pressed(false) {
} catch(libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: Action type is missing, ignoring.",
setting.getSourceLine());
throw InvalidAction();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -19,81 +19,65 @@
#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 logid::actions {
class InvalidAction : public std::exception {
namespace actions {
class InvalidAction : public std::exception
{
public:
InvalidAction() = default;
InvalidAction(std::string action) : _action(std::move(action)) {}
[[nodiscard]] const char* what() const noexcept override {
InvalidAction()
{
}
explicit InvalidAction(std::string& action) : _action (action)
{
}
const char* what() const noexcept override
{
return _action.c_str();
}
private:
std::string _action;
};
class Action : public ipcgull::interface {
class Action
{
public:
static std::shared_ptr<Action> makeAction(
Device* device, const std::string& name,
std::optional<config::BasicAction>& config,
const std::shared_ptr<ipcgull::node>& parent);
static std::shared_ptr<Action> makeAction(
Device* device, const std::string& name,
std::optional<config::Action>& config,
const std::shared_ptr<ipcgull::node>& parent);
static std::shared_ptr<Action> makeAction(
Device* device, config::BasicAction& action,
const std::shared_ptr<ipcgull::node>& parent);
static std::shared_ptr<Action> makeAction(
Device* device, config::Action& action,
const std::shared_ptr<ipcgull::node>& parent);
static std::shared_ptr<Action> makeAction(Device* device,
libconfig::Setting& setting);
virtual void press() = 0;
virtual void release() = 0;
virtual void move(int16_t x, int16_t y)
{
// Suppress unused warning
(void)x; (void)y;
}
virtual void move([[maybe_unused]] int16_t x, [[maybe_unused]] int16_t y) { }
virtual bool pressed() {
virtual bool pressed()
{
return _pressed;
}
[[nodiscard]] virtual uint8_t reprogFlags() const = 0;
virtual ~Action() = default;
virtual uint8_t reprogFlags() const = 0;
class Config
{
protected:
Action(Device* device, const std::string& name, tables t = {});
explicit Config(Device* device) : _device (device)
{
}
Device* _device;
};
protected:
explicit Action(Device* device) : _device (device), _pressed (false)
{
}
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-2023 PixlOne
* 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
@ -15,80 +15,95 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <actions/ChangeDPI.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/task.h>
#include <util/log.h>
#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"
using namespace logid::actions;
const char* ChangeDPI::interface_name = "ChangeDPI";
ChangeDPI::ChangeDPI(
Device* device, config::ChangeDPI& config,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(device, interface_name, {
ChangeDPI::ChangeDPI(Device *device, libconfig::Setting &setting) :
Action(device), _config(device, setting)
{
{"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.",
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;
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()) {
if(_dpi) {
task::spawn([this]{
try {
uint16_t last_dpi = self->_dpi->getDPI(sensor);
self->_dpi->setDPI(last_dpi + inc, sensor);
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",
self->_device->hidpp20().devicePath().c_str(),
self->_device->hidpp20().deviceIndex(), sensor);
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;
}
}
});
}
}
void ChangeDPI::release() {
void ChangeDPI::release()
{
_pressed = false;
}
uint8_t ChangeDPI::reprogFlags() const {
uint8_t ChangeDPI::reprogFlags() const
{
return backend::hidpp20::ReprogControls::TemporaryDiverted;
}
std::tuple<int16_t, uint16_t> ChangeDPI::getConfig() const {
std::shared_lock lock(_config_mutex);
return {_config.inc.value_or(0), _config.sensor.value_or(0)};
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;
}
void ChangeDPI::setChange(int16_t change) {
std::unique_lock lock(_config_mutex);
_config.inc = change;
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());
}
void ChangeDPI::setSensor(uint8_t sensor, bool reset) {
std::unique_lock lock(_config_mutex);
if (reset) {
_config.sensor.reset();
} else {
_config.sensor = sensor;
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
}
}
uint16_t ChangeDPI::Config::interval() const
{
return _interval;
}
uint8_t ChangeDPI::Config::sensor() const
{
return _sensor;
}

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -15,36 +15,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <actions/ChangeHostAction.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <algorithm>
#include <util/task.h>
#include <util/log.h>
#include "ChangeHostAction.h"
#include "../Device.h"
#include "../backend/hidpp20/features/ReprogControls.h"
#include "../util/task.h"
using namespace logid::actions;
using namespace logid::backend;
const char* ChangeHostAction::interface_name = "ChangeHost";
ChangeHostAction::ChangeHostAction(
Device* device, config::ChangeHost& config,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent)
: Action(device, interface_name, {
ChangeHostAction::ChangeHostAction(Device *device, libconfig::Setting&
config) : Action(device), _config (device, config)
{
{"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) {
@ -54,59 +36,84 @@ ChangeHostAction::ChangeHostAction(
}
}
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() {
void ChangeHostAction::press()
{
// Do nothing, wait until release
}
void ChangeHostAction::release() {
std::shared_lock lock(_config_mutex);
if (_change_host && _config.host.has_value()) {
run_task([self_weak = self<ChangeHostAction>(), host = _config.host.value()] {
if (auto self = self_weak.lock()) {
auto host_info = self->_change_host->getHostInfo();
int next_host;
if (std::holds_alternative<std::string>(host)) {
const auto& host_str = std::get<std::string>(host);
if (host_str == "next")
next_host = host_info.currentHost + 1;
else if (host_str == "prev" || host_str == "previous")
next_host = host_info.currentHost - 1;
else
next_host = host_info.currentHost;
} else {
next_host = std::get<int>(host) - 1;
}
next_host %= host_info.hostCount;
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)
self->_change_host->setHost(next_host);
}
_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-2023 PixlOne
* 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
@ -18,31 +18,37 @@
#ifndef LOGID_ACTION_CHANGEHOSTACTION_H
#define LOGID_ACTION_CHANGEHOSTACTION_H
#include <actions/Action.h>
#include <backend/hidpp20/features/ChangeHost.h>
#include <libconfig.h++>
#include "Action.h"
#include "../backend/hidpp20/features/ChangeHost.h"
namespace logid::actions {
class ChangeHostAction : public Action {
namespace logid {
namespace actions
{
class ChangeHostAction : public Action
{
public:
static const char* interface_name;
ChangeHostAction(Device* device, libconfig::Setting& config);
ChangeHostAction(Device* device, config::ChangeHost& config,
const std::shared_ptr<ipcgull::node>& parent);
virtual void press();
virtual void release();
void press() final;
virtual uint8_t reprogFlags() const;
void release() final;
[[nodiscard]] std::string getHost() const;
void setHost(std::string host);
[[nodiscard]] uint8_t reprogFlags() const 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;
};
protected:
std::shared_ptr<backend::hidpp20::ChangeHost> _change_host;
config::ChangeHost& _config;
Config _config;
};
}
}}
#endif //LOGID_ACTION_CHANGEHOSTACTION_H

View File

@ -1,67 +0,0 @@
/*
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <actions/ChangeProfile.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <Device.h>
using namespace logid;
using namespace logid::actions;
const char* ChangeProfile::interface_name = "ChangeProfile";
ChangeProfile::ChangeProfile(Device* device, config::ChangeProfile& config,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(device, interface_name, {
{
{"GetProfile", {this, &ChangeProfile::getProfile, {"profile"}}},
{"SetProfile", {this, &ChangeProfile::setProfile, {"profile"}}}
},
{},
{}
}), _config(config) {
}
void ChangeProfile::press() {
}
void ChangeProfile::release() {
std::shared_lock lock(_config_mutex);
if (_config.profile.has_value())
_device->setProfileDelayed(_config.profile.value());
}
uint8_t ChangeProfile::reprogFlags() const {
return backend::hidpp20::ReprogControls::TemporaryDiverted;
}
std::string ChangeProfile::getProfile() {
std::shared_lock lock(_config_mutex);
if (_config.profile.has_value())
return _config.profile.value();
else
return "";
}
void ChangeProfile::setProfile(std::string profile) {
std::unique_lock lock(_config_mutex);
if (profile.empty())
_config.profile->clear();
else
_config.profile = std::move(profile);
}

View File

@ -1,47 +0,0 @@
/*
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_CHANGEPROFILE_H
#define LOGID_CHANGEPROFILE_H
#include <actions/Action.h>
namespace logid::actions {
class ChangeProfile : public Action {
public:
static const char* interface_name;
ChangeProfile(Device* device, config::ChangeProfile& setting,
const std::shared_ptr<ipcgull::node>& parent);
void press() final;
void release() final;
[[nodiscard]] uint8_t reprogFlags() const final;
std::string getProfile();
void setProfile(std::string profile);
private:
config::ChangeProfile& _config;
};
}
#endif //LOGID_CHANGEPROFILE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -15,84 +15,119 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <actions/CycleDPI.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/task.h>
#include <util/log.h>
#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"
using namespace logid::actions;
using namespace libconfig;
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, {
CycleDPI::CycleDPI(Device* device, libconfig::Setting& setting) :
Action (device), _config (device, setting)
{
{"GetDPIs", {this, &CycleDPI::getDPIs, {"dpis"}}},
{"SetDPIs", {this, &CycleDPI::setDPIs, {"dpis"}}}
},
{},
{}
}),
_config(config) {
_dpi = _device->getFeature<features::DPI>("dpi");
if(!_dpi)
logPrintf(WARN, "%s:%d: DPI feature not found, cannot use "
"CycleDPI action.",
_device->hidpp20().devicePath().c_str(),
_device->hidpp20().deviceIndex());
if (_config.dpis.has_value()) {
_current_dpi = _config.dpis.value().begin();
}
}
std::vector<int> CycleDPI::getDPIs() const {
std::shared_lock lock(_config_mutex);
auto dpis = _config.dpis.value_or(std::list<int>());
return {dpis.begin(), dpis.end()};
}
void CycleDPI::setDPIs(const std::vector<int>& dpis) {
std::unique_lock lock(_config_mutex);
std::lock_guard dpi_lock(_dpi_mutex);
_config.dpis.emplace(dpis.begin(), dpis.end());
_current_dpi = _config.dpis->cbegin();
}
void CycleDPI::press() {
void CycleDPI::press()
{
_pressed = true;
std::shared_lock lock(_config_mutex);
std::lock_guard dpi_lock(_dpi_mutex);
if (_dpi && _config.dpis.has_value() && _config.dpis.value().empty()) {
++_current_dpi;
if (_current_dpi == _config.dpis.value().end())
_current_dpi = _config.dpis.value().begin();
run_task([self_weak = self<CycleDPI>(), dpi = *_current_dpi] {
if (auto self = self_weak.lock()) {
if(_dpi && !_config.empty()) {
task::spawn([this](){
uint16_t dpi = _config.nextDPI();
try {
self->_dpi->setDPI(dpi, self->_config.sensor.value_or(0));
_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",
self->_device->hidpp20().devicePath().c_str(),
self->_device->hidpp20().deviceIndex(), dpi,
self->_config.sensor.value_or(0));
"sensor %d", _device->hidpp20().devicePath().c_str(),
_device->hidpp20().deviceIndex(), dpi,
_config.sensor());
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-2023 PixlOne
* 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
@ -18,33 +18,39 @@
#ifndef LOGID_ACTION_CYCLEDPI_H
#define LOGID_ACTION_CYCLEDPI_H
#include <actions/Action.h>
#include <features/DPI.h>
#include <libconfig.h++>
#include "Action.h"
#include "../features/DPI.h"
namespace logid::actions {
class CycleDPI : public Action {
namespace logid {
namespace actions {
class CycleDPI : public Action
{
public:
static const char* interface_name;
explicit CycleDPI(Device* device, libconfig::Setting& setting);
CycleDPI(Device* device, config::CycleDPI& setting,
const std::shared_ptr<ipcgull::node>& parent);
virtual void press();
virtual void release();
void press() final;
virtual uint8_t reprogFlags() const;
void release() final;
[[nodiscard]] std::vector<int> getDPIs() const;
void setDPIs(const std::vector<int>& dpis);
[[nodiscard]] uint8_t reprogFlags() const 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;
};
protected:
std::mutex _dpi_mutex;
config::CycleDPI& _config;
Config _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-2023 PixlOne
* 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
@ -15,18 +15,17 @@
* 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;
const char* GestureAction::interface_name = "Gesture";
GestureAction::Direction GestureAction::toDirection(std::string direction) {
GestureAction::Direction GestureAction::toDirection(std::string direction)
{
std::transform(direction.begin(), direction.end(), direction.begin(),
::tolower);
if(direction == "up")
@ -43,92 +42,45 @@ GestureAction::Direction GestureAction::toDirection(std::string direction) {
throw std::invalid_argument("direction");
}
std::string GestureAction::fromDirection(Direction direction) {
switch (direction) {
case Up:
return "up";
case Down:
return "down";
case Left:
return "left";
case Right:
return "right";
case None:
return "none";
}
// This shouldn't happen
throw InvalidGesture();
}
GestureAction::Direction GestureAction::toDirection(int32_t x, int32_t y) {
GestureAction::Direction GestureAction::toDirection(int16_t x, int16_t y)
{
if(x >= 0 && y >= 0)
return x >= y ? Right : Down;
else if(x < 0 && y >= 0)
return -x <= y ? Down : Left;
else if (x <= 0 /* && y < 0 */)
else if(x <= 0 && y < 0)
return x <= y ? Left : Up;
else
return x <= -y ? Up : Right;
}
GestureAction::GestureAction(Device* dev, config::GestureAction& config,
const std::shared_ptr<ipcgull::node>& parent) :
Action(dev, interface_name,
GestureAction::GestureAction(Device* dev, libconfig::Setting& config) :
Action (dev), _config (dev, config)
{
{
{"SetGesture", {this, &GestureAction::setGesture, {"direction", "type"}}}
},
{},
{}
}),
_node(parent->make_child("gestures")), _config(config) {
if (_config.gestures.has_value()) {
auto& gestures = _config.gestures.value();
for (auto&& x: gestures) {
try {
auto direction = toDirection(x.first);
_gestures.emplace(
direction,Gesture::makeGesture(
dev, x.second,
_node->make_child(fromDirection(direction))));
if (direction == None) {
auto& gesture = x.second;
std::visit([](auto&& x) {
x.threshold.emplace(0);
}, gesture);
}
} catch (std::invalid_argument& e) {
logPrintf(WARN, "%s is not a direction", x.first.c_str());
}
}
}
}
void GestureAction::press() {
std::shared_lock lock(_config_mutex);
void GestureAction::press()
{
_pressed = true;
_x = 0, _y = 0;
for (auto& gesture: _gestures)
gesture.second->press(false);
for(auto& gesture : _config.gestures())
gesture.second->press();
}
void GestureAction::release() {
std::shared_lock lock(_config_mutex);
void GestureAction::release()
{
_pressed = false;
bool threshold_met = false;
auto d = toDirection(_x, _y);
auto primary_gesture = _gestures.find(d);
if (primary_gesture != _gestures.end()) {
auto primary_gesture = _config.gestures().find(d);
if(primary_gesture != _config.gestures().end()) {
threshold_met = primary_gesture->second->metThreshold();
primary_gesture->second->release(true);
}
for (auto& gesture: _gestures) {
if (gesture.first == d || gesture.first == None)
for(auto& gesture : _config.gestures()) {
if(gesture.first == d)
continue;
if(!threshold_met) {
if(gesture.second->metThreshold()) {
@ -136,126 +88,182 @@ void GestureAction::release() {
// secondary one.
threshold_met = true;
gesture.second->release(true);
break;
}
} else {
gesture.second->release(false);
}
}
auto none_gesture = _gestures.find(None);
if (none_gesture != _gestures.end()) {
none_gesture->second->release(!threshold_met);
if(!threshold_met) {
if(_config.noneAction()) {
_config.noneAction()->press();
_config.noneAction()->release();
}
}
}
void GestureAction::move(int16_t x, int16_t y) {
std::shared_lock lock(_config_mutex);
int32_t new_x = _x + x, new_y = _y + y;
void GestureAction::move(int16_t x, int16_t y)
{
auto 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);
auto left = _config.gestures().find(Left);
if(left != _config.gestures().end())
left->second->move(_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);
auto right = _config.gestures().find(Right);
if(right != _config.gestures().end())
right->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);
auto right = _config.gestures().find(Right);
if(right != _config.gestures().end())
right->second->move(-_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);
auto left = _config.gestures().find(Left);
if(left != _config.gestures().end())
left->second->move(-new_x);
}
} else if(new_x < 0) { // Origin/Left to Left
auto left = _gestures.find(Left);
if (left != _gestures.end() && left->second)
left->second->move((int16_t) -x);
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 = _gestures.find(Right);
if (right != _gestures.end() && right->second)
auto right = _config.gestures().find(Right);
if(right != _config.gestures().end())
right->second->move(x);
}
}
if(abs(y) > 0) {
if(_y > 0 && new_y <= 0) { // Up -> Origin/Down
auto up = _gestures.find(Up);
if (up != _gestures.end() && up->second)
up->second->move((int16_t) _y);
auto up = _config.gestures().find(Up);
if(up != _config.gestures().end())
up->second->move(_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);
auto down = _config.gestures().find(Down);
if(down != _config.gestures().end())
down->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);
auto down = _config.gestures().find(Down);
if(down != _config.gestures().end())
down->second->move(-_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);
auto up = _config.gestures().find(Up);
if(up != _config.gestures().end())
up->second->move(-new_y);
}
} else if(new_y < 0) { // Origin/Up to Up
auto up = _gestures.find(Up);
if (up != _gestures.end() && up->second)
up->second->move((int16_t) -y);
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 = _gestures.find(Down);
if (down != _gestures.end() && down->second)
auto down = _config.gestures().find(Down);
if(down != _config.gestures().end())
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);
}
void GestureAction::setGesture(const std::string& direction, const std::string& type) {
std::unique_lock lock(_config_mutex);
Direction d = toDirection(direction);
auto it = _gestures.find(d);
if (it != _gestures.end()) {
if (pressed()) {
auto current = toDirection(_x, _y);
if (it->second)
it->second->release(current == d);
}
}
auto dir_name = fromDirection(d);
auto& gesture = _config.gestures.value()[dir_name];
_gestures[d].reset();
GestureAction::Config::Config(Device* device, libconfig::Setting &root) :
Action::Config(device)
{
try {
_gestures[d] = Gesture::makeGesture(
_device, type, gesture,
_node->make_child(dir_name));
} catch (InvalidGesture& e) {
_gestures[d] = Gesture::makeGesture(
_device, gesture,
_node->make_child(dir_name));
throw std::invalid_argument("Invalid gesture type");
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) {
std::visit([](auto&& x) {
x.threshold = 0;
}, gesture);
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());
}
}
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-2023 PixlOne
* 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
@ -19,48 +19,49 @@
#define LOGID_ACTION_GESTUREACTION_H
#include <map>
#include <actions/Action.h>
#include <actions/gesture/Gesture.h>
#include <libconfig.h++>
#include "Action.h"
#include "gesture/Gesture.h"
namespace logid::actions {
class GestureAction : public Action {
namespace logid {
namespace actions {
class GestureAction : public Action
{
public:
static const char* interface_name;
enum Direction {
enum Direction
{
None,
Up,
Down,
Left,
Right
};
static Direction toDirection(std::string direction);
static Direction toDirection(int16_t x, int16_t y);
static std::string fromDirection(Direction direction);
GestureAction(Device* dev, libconfig::Setting& config);
static Direction toDirection(int32_t x, int32_t y);
virtual void press();
virtual void release();
virtual void move(int16_t x, int16_t y);
GestureAction(Device* dev, config::GestureAction& config,
const std::shared_ptr<ipcgull::node>& parent);
virtual uint8_t reprogFlags() const;
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);
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;
};
protected:
int32_t _x{}, _y{};
std::shared_ptr<ipcgull::node> _node;
std::map<Direction, std::shared_ptr<Gesture>> _gestures;
config::GestureAction& _config;
int16_t _x, _y;
Config _config;
};
}
}}
#endif //LOGID_ACTION_GESTUREACTION_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -15,112 +15,75 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <actions/KeypressAction.h>
#include <Device.h>
#include <InputDevice.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/log.h>
#include "KeypressAction.h"
#include "../util/log.h"
#include "../InputDevice.h"
#include "../backend/hidpp20/features/ReprogControls.h"
using namespace logid::actions;
using namespace logid::backend;
const char* KeypressAction::interface_name = "Keypress";
KeypressAction::KeypressAction(
Device* device, config::KeypressAction& config,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(device, interface_name, {
KeypressAction::KeypressAction(Device *device, libconfig::Setting& config) :
Action(device), _config (device, config)
{
{"GetKeys", {this, &KeypressAction::getKeys, {"keys"}}},
{"SetKeys", {this, &KeypressAction::setKeys, {"keys"}}}
},
{},
{}
}), _config(config) {
_setConfig();
}
void KeypressAction::press() {
std::shared_lock lock(_config_mutex);
void KeypressAction::press()
{
_pressed = true;
for (auto& key: _keys)
_device->virtualInput()->pressKey(key);
for(auto& key : _config.keys())
virtual_input->pressKey(key);
}
void KeypressAction::release() {
std::shared_lock lock(_config_mutex);
void KeypressAction::release()
{
_pressed = false;
for (auto& key: _keys)
_device->virtualInput()->releaseKey(key);
for(auto& key : _config.keys())
virtual_input->releaseKey(key);
}
void KeypressAction::_setConfig() {
_keys.clear();
if (!_config.keys.has_value())
return;
auto& config = _config.keys.value();
if (std::holds_alternative<std::string>(config)) {
const auto& key = std::get<std::string>(config);
try {
auto code = _device->virtualInput()->toKeyCode(key);
_device->virtualInput()->registerKey(code);
_keys.emplace_back(code);
} catch (InputDevice::InvalidEventCode& e) {
logPrintf(WARN, "Invalid keycode %s, skipping.", key.c_str());
}
} else if (std::holds_alternative<uint>(_config.keys.value())) {
const auto& key = std::get<uint>(config);
_device->virtualInput()->registerKey(key);
_keys.emplace_back(key);
} else if (std::holds_alternative<
std::list<std::variant<uint, std::string>>>(config)) {
const auto& keys = std::get<
std::list<std::variant<uint, std::string>>>(config);
for (const auto& key: keys) {
if (std::holds_alternative<std::string>(key)) {
const auto& key_str = std::get<std::string>(key);
try {
auto code = _device->virtualInput()->toKeyCode(key_str);
_device->virtualInput()->registerKey(code);
_keys.emplace_back(code);
} catch (InputDevice::InvalidEventCode& e) {
logPrintf(WARN, "Invalid keycode %s, skipping.",
key_str.c_str());
}
} else if (std::holds_alternative<uint>(key)) {
auto& code = std::get<uint>(key);
_device->virtualInput()->registerKey(code);
_keys.emplace_back(code);
}
}
}
}
uint8_t KeypressAction::reprogFlags() const {
uint8_t KeypressAction::reprogFlags() const
{
return hidpp20::ReprogControls::TemporaryDiverted;
}
std::vector<std::string> KeypressAction::getKeys() const {
std::shared_lock lock(_config_mutex);
std::vector<std::string> ret;
for (auto& x: _keys)
ret.push_back(InputDevice::toKeyName(x));
return ret;
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;
}
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();
try {
auto &keys = config.lookup("keys");
if(keys.isArray() || keys.isList()) {
int key_count = keys.getLength();
for(int i = 0; i < key_count; i++) {
auto& key = keys[i];
if(key.isNumber()) {
_keys.push_back(key);
} else if(key.getType() == libconfig::Setting::TypeString) {
try {
_keys.push_back(virtual_input->toKeyCode(key));
} catch(InputDevice::InvalidEventCode& e) {
logPrintf(WARN, "Line %d: Invalid keycode %s, skipping."
, key.getSourceLine(), key.c_str());
}
} else {
logPrintf(WARN, "Line %d: keycode must be string or int",
key.getSourceLine(), key.c_str());
}
}
}
} catch (libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: keys is a required field, skipping.",
config.getSourceLine());
}
}
std::vector<uint>& KeypressAction::Config::keys()
{
return _keys;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -19,33 +19,32 @@
#define LOGID_ACTION_KEYPRESS_H
#include <vector>
#include <actions/Action.h>
#include <libconfig.h++>
#include "Action.h"
namespace logid::actions {
class KeypressAction : public Action {
namespace logid {
namespace actions {
class KeypressAction : public Action
{
public:
static const char* interface_name;
KeypressAction(Device* dev, libconfig::Setting& config);
KeypressAction(Device* dev,
config::KeypressAction& config,
const std::shared_ptr<ipcgull::node>& parent);
virtual void press();
virtual void release();
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;
virtual uint8_t reprogFlags() const;
class Config : public Action::Config
{
public:
explicit Config(Device* device, libconfig::Setting& root);
std::vector<uint>& keys();
protected:
config::KeypressAction& _config;
std::list<uint> _keys;
void _setConfig();
std::vector<uint> _keys;
};
}
protected:
Config _config;
};
}}
#endif //LOGID_ACTION_KEYPRESS_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -15,27 +15,27 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <actions/NullAction.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include "NullAction.h"
#include "../Device.h"
#include "../backend/hidpp20/features/ReprogControls.h"
using namespace logid::actions;
const char* NullAction::interface_name = "None";
NullAction::NullAction(
Device* device,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(device, interface_name) {
NullAction::NullAction(Device* device) : Action(device)
{
}
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-2023 PixlOne
* 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
@ -18,27 +18,22 @@
#ifndef LOGID_ACTION_NULL_H
#define LOGID_ACTION_NULL_H
#include <actions/Action.h>
#include "Action.h"
namespace logid::actions {
class NullAction : public Action {
namespace logid {
namespace actions
{
class NullAction : public Action
{
public:
static const char* interface_name;
explicit NullAction(Device* device);
NullAction(Device* device,
const std::shared_ptr<ipcgull::node>& parent);
virtual void press();
virtual void release();
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;
virtual uint8_t reprogFlags() const;
};
}
}}
#endif //LOGID_ACTION_NULL_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -15,21 +15,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <actions/ToggleHiresScroll.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/task.h>
#include <util/log.h>
#include "ToggleHiresScroll.h"
#include "../Device.h"
#include "../util/task.h"
#include "../backend/hidpp20/features/ReprogControls.h"
using namespace logid::actions;
using namespace logid::backend;
const char* ToggleHiresScroll::interface_name = "ToggleHiresScroll";
ToggleHiresScroll::ToggleHiresScroll(
Device* dev,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(dev, interface_name) {
ToggleHiresScroll::ToggleHiresScroll(Device *dev) : Action (dev)
{
_hires_scroll = _device->getFeature<features::HiresScroll>("hiresscroll");
if(!_hires_scroll)
logPrintf(WARN, "%s:%d: HiresScroll feature not found, cannot use "
@ -38,23 +33,25 @@ ToggleHiresScroll::ToggleHiresScroll(
_device->hidpp20().devicePath().c_str());
}
void ToggleHiresScroll::press() {
void ToggleHiresScroll::press()
{
_pressed = true;
if (_hires_scroll) {
run_task([self_weak = self<ToggleHiresScroll>()]() {
if (auto self = self_weak.lock()) {
auto mode = self->_hires_scroll->getMode();
if(_hires_scroll)
{
task::spawn([hires=this->_hires_scroll](){
auto mode = hires->getMode();
mode ^= backend::hidpp20::HiresScroll::HiRes;
self->_hires_scroll->setMode(mode);
}
hires->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-2023 PixlOne
* 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
@ -18,30 +18,24 @@
#ifndef LOGID_ACTION_TOGGLEHIRESSCROLL_H
#define LOGID_ACTION_TOGGLEHIRESSCROLL_H
#include <actions/Action.h>
#include <features/HiresScroll.h>
#include "Action.h"
#include "../features/HiresScroll.h"
namespace logid::actions {
class ToggleHiresScroll : public Action {
namespace logid {
namespace actions
{
class ToggleHiresScroll : public Action
{
public:
static const char* interface_name;
explicit ToggleHiresScroll(Device* dev);
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 void press();
virtual void release();
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-2023 PixlOne
* 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
@ -15,22 +15,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <actions/ToggleSmartShift.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/task.h>
#include <util/log.h>
#include "ToggleSmartShift.h"
#include "../Device.h"
#include "../backend/hidpp20/features/ReprogControls.h"
#include "../util/task.h"
using namespace logid::actions;
using namespace logid::backend;
const char* ToggleSmartShift::interface_name = "ToggleSmartShift";
ToggleSmartShift::ToggleSmartShift(
Device* dev,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(dev, interface_name) {
ToggleSmartShift::ToggleSmartShift(Device *dev) : Action (dev)
{
_smartshift = _device->getFeature<features::SmartShift>("smartshift");
if(!_smartshift)
logPrintf(WARN, "%s:%d: SmartShift feature not found, cannot use "
@ -39,24 +33,25 @@ ToggleSmartShift::ToggleSmartShift(
_device->hidpp20().deviceIndex());
}
void ToggleSmartShift::press() {
void ToggleSmartShift::press()
{
_pressed = true;
if(_smartshift) {
run_task([self_weak = self<ToggleSmartShift>()]() {
if (auto self = self_weak.lock()) {
auto status = self->_smartshift->getStatus();
task::spawn([ss=this->_smartshift](){
auto status = ss->getStatus();
status.setActive = true;
status.active = !status.active;
self->_smartshift->setStatus(status);
}
ss->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-2023 PixlOne
* 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
@ -18,31 +18,24 @@
#ifndef LOGID_ACTION_TOGGLESMARTSHIFT_H
#define LOGID_ACTION_TOGGLESMARTSHIFT_H
#include <actions/Action.h>
#include <features/SmartShift.h>
#include <libconfig.h++>
#include "Action.h"
#include "../features/SmartShift.h"
namespace logid::actions {
class ToggleSmartShift : public Action {
namespace logid {
namespace actions {
class ToggleSmartShift : public Action
{
public:
static const char* interface_name;
explicit ToggleSmartShift(Device* dev);
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 void press();
virtual void release();
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-2023 PixlOne
* 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
@ -16,83 +16,41 @@
*
*/
#include <cmath>
#include <actions/gesture/AxisGesture.h>
#include <Device.h>
#include <InputDevice.h>
#include <util/log.h>
#include "AxisGesture.h"
#include "../../InputDevice.h"
#include "../../util/log.h"
using namespace logid::actions;
const char* AxisGesture::interface_name = "Axis";
AxisGesture::AxisGesture(Device* device, config::AxisGesture& config,
const std::shared_ptr<ipcgull::node>& parent) :
Gesture(device, parent, interface_name, {
AxisGesture::AxisGesture(Device *device, libconfig::Setting &root) :
Gesture (device), _config (device, root)
{
{"GetConfig", {this, &AxisGesture::getConfig, {"axis", "multiplier", "threshold"}}},
{"SetThreshold", {this, &AxisGesture::setThreshold, {"threshold"}}},
{"SetMultiplier", {this, &AxisGesture::setMultiplier, {"multiplier"}}},
{"SetAxis", {this, &AxisGesture::setAxis, {"axis"}}}
},
{},
{}
}), _multiplier(1), _config(config) {
if (_config.axis.has_value()) {
if (std::holds_alternative<uint>(_config.axis.value())) {
_input_axis = std::get<uint>(_config.axis.value());
} else {
const auto& axis = std::get<std::string>(_config.axis.value());
try {
_input_axis = _device->virtualInput()->toAxisCode(axis);
} catch (InputDevice::InvalidEventCode& e) {
logPrintf(WARN, "Invalid axis %s.");
}
}
}
if (_input_axis.has_value())
_device->virtualInput()->registerAxis(_input_axis.value());
}
void AxisGesture::press(bool init_threshold) {
std::shared_lock lock(_config_mutex);
if (init_threshold) {
_axis = (int32_t) (_config.threshold.value_or(defaults::gesture_threshold));
} else {
void AxisGesture::press()
{
_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 AxisGesture::move(int16_t axis) {
std::shared_lock lock(_config_mutex);
if (!_input_axis.has_value())
return;
const auto threshold = _config.threshold.value_or(
defaults::gesture_threshold);
int32_t new_axis = _axis + axis;
int low_res_axis = InputDevice::getLowResAxis(axis);
int hires_remainder = _hires_remainder;
if (new_axis > threshold) {
void AxisGesture::move(int16_t axis)
{
int16_t new_axis = _axis + axis;
if(new_axis > _config.threshold()) {
double move = axis;
if (_axis < threshold)
move = new_axis - threshold;
bool negative_multiplier = _config.axis_multiplier.value_or(1) < 0;
if(_axis < _config.threshold())
move = new_axis - _config.threshold();
bool negative_multiplier = _config.multiplier() < 0;
if(negative_multiplier)
move *= -_config.axis_multiplier.value_or(1);
move *= -_config.multiplier();
else
move *= _config.axis_multiplier.value_or(1);
// Handle hi-res multiplier
move *= _multiplier;
move *= _config.multiplier();
double move_floor = floor(move);
_axis_remainder = move - move_floor;
@ -105,81 +63,64 @@ void AxisGesture::move(int16_t axis) {
if(negative_multiplier)
move_floor = -move_floor;
if (low_res_axis != -1) {
int lowres_movement, hires_movement = (int) move_floor;
_device->virtualInput()->moveAxis(_input_axis.value(), hires_movement);
hires_remainder += hires_movement;
if (abs(hires_remainder) >= 60) {
lowres_movement = hires_remainder / 120;
if (lowres_movement == 0)
lowres_movement = hires_remainder > 0 ? 1 : -1;
hires_remainder -= lowres_movement * 120;
_device->virtualInput()->moveAxis(low_res_axis, lowres_movement);
}
_hires_remainder = hires_remainder;
} else {
_device->virtualInput()->moveAxis(_input_axis.value(), (int) move_floor);
}
virtual_input->moveAxis(_config.axis(), move_floor);
}
_axis = new_axis;
}
bool AxisGesture::metThreshold() const {
std::shared_lock lock(_config_mutex);
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
bool AxisGesture::metThreshold() const
{
return _axis >= _config.threshold();
}
bool AxisGesture::wheelCompatibility() const {
return true;
AxisGesture::Config::Config(Device *device, libconfig::Setting &setting) :
Gesture::Config(device, setting, false)
{
try {
auto& axis = setting.lookup("axis");
if(axis.isNumber()) {
_axis = axis;
} else if(axis.getType() == libconfig::Setting::TypeString) {
try {
_axis = virtual_input->toAxisCode(axis);
} catch(InputDevice::InvalidEventCode& e) {
logPrintf(WARN, "Line %d: Invalid axis %s, skipping."
, axis.getSourceLine(), axis.c_str());
}
void AxisGesture::setHiresMultiplier(double multiplier) {
_hires_multiplier = multiplier;
if (_input_axis.has_value()) {
if (InputDevice::getLowResAxis(_input_axis.value()) != -1)
_multiplier = _config.axis_multiplier.value_or(1) * multiplier;
}
}
std::tuple<std::string, double, int> AxisGesture::getConfig() const {
std::shared_lock lock(_config_mutex);
std::string axis;
if (_config.axis.has_value()) {
if (std::holds_alternative<std::string>(_config.axis.value())) {
axis = std::get<std::string>(_config.axis.value());
} else {
axis = _device->virtualInput()->toAxisName(std::get<uint>(_config.axis.value()));
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();
}
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;
try {
auto& multiplier = setting.lookup("axis_multiplier");
if(multiplier.isNumber()) {
if(multiplier.getType() == libconfig::Setting::TypeFloat)
_multiplier = multiplier;
setHiresMultiplier(_hires_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
}
}
void AxisGesture::setThreshold(int threshold) {
std::unique_lock lock(_config_mutex);
if (threshold == 0)
_config.threshold.reset();
else
_config.threshold = threshold;
unsigned int AxisGesture::Config::axis() const
{
return _axis;
}
double AxisGesture::Config::multiplier() const
{
return _multiplier;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -18,45 +18,38 @@
#ifndef LOGID_ACTION_AXISGESTURE_H
#define LOGID_ACTION_AXISGESTURE_H
#include <actions/gesture/Gesture.h>
#include "Gesture.h"
namespace logid::actions {
class AxisGesture : public Gesture {
namespace logid {
namespace actions
{
class AxisGesture : public Gesture
{
public:
static const char* interface_name;
AxisGesture(Device* device, libconfig::Setting& root);
AxisGesture(Device* device, config::AxisGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
virtual void press();
virtual void release(bool primary=false);
virtual void move(int16_t axis);
void press(bool init_threshold) final;
virtual bool metThreshold() const;
void release(bool primary) final;
void move(int16_t axis) final;
[[nodiscard]] bool wheelCompatibility() const final;
[[nodiscard]] bool metThreshold() const final;
void setHiresMultiplier(double multiplier);
[[nodiscard]] std::tuple<std::string, double, int> getConfig() const;
void setAxis(const std::string& axis);
void setMultiplier(double multiplier);
void setThreshold(int threshold);
class Config : public Gesture::Config
{
public:
Config(Device* device, libconfig::Setting& setting);
unsigned int axis() const;
double multiplier() const;
private:
unsigned int _axis;
double _multiplier = 1;
};
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;
int16_t _axis;
double _axis_remainder;
Config _config;
};
}
}}
#endif //LOGID_ACTION_AXISGESTURE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -16,73 +16,103 @@
*
*/
#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>
#include <algorithm>
#include "Gesture.h"
#include "../../util/log.h"
#include "ReleaseGesture.h"
#include "../../backend/hidpp20/features/ReprogControls.h"
#include "IntervalGesture.h"
#include "AxisGesture.h"
#include "NullGesture.h"
using namespace logid;
using namespace logid::actions;
Gesture::Gesture(Device* device,
std::shared_ptr<ipcgull::node> node,
const std::string& name, tables t) :
ipcgull::interface(SERVICE_ROOT_NAME ".Gesture." + name, std::move(t)),
_node(std::move(node)), _device(device) {
Gesture::Gesture(Device *device) : _device (device)
{
}
namespace {
template<typename T>
struct gesture_type {
typedef typename T::gesture type;
};
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");
}
template<typename T>
struct gesture_type<const T> : gesture_type<T> {
};
if(_action->reprogFlags() & backend::hidpp20::ReprogControls::RawXYDiverted)
throw InvalidGesture("gesture cannot require RawXY");
}
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);
_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
}
}
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 {
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());
throw InvalidGesture();
}
return makeGesture(device, config, parent);
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 == "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);
}
}
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-2023 PixlOne
* 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
@ -18,56 +18,56 @@
#ifndef LOGID_ACTION_GESTURE_H
#define LOGID_ACTION_GESTURE_H
#include <utility>
#include <actions/Action.h>
#include "../Action.h"
namespace logid::actions {
class InvalidGesture : public std::exception {
#define LOGID_GESTURE_DEFAULT_THRESHOLD 50
namespace logid {
namespace actions
{
class InvalidGesture : public std::exception
{
public:
explicit InvalidGesture(std::string what = "") : _what(std::move(what)) {
explicit InvalidGesture(std::string what="") : _what (what)
{
}
[[nodiscard]] const char* what() const noexcept override {
virtual const char* what()
{
return _what.c_str();
}
private:
std::string _what;
};
class Gesture : public ipcgull::interface {
class Gesture
{
public:
virtual void press(bool init_threshold) = 0;
virtual void release(bool primary) = 0;
virtual void press() = 0;
virtual void release(bool primary=false) = 0;
virtual void move(int16_t axis) = 0;
[[nodiscard]] virtual bool wheelCompatibility() const = 0;
virtual bool metThreshold() 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,
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);
libconfig::Setting& setting);
protected:
Gesture(Device* device,
std::shared_ptr<ipcgull::node> parent,
const std::string& name, tables t = {});
mutable std::shared_mutex _config_mutex;
const std::shared_ptr<ipcgull::node> _node;
explicit Gesture(Device* device);
Device* _device;
};
}
}}
#endif //LOGID_ACTION_GESTURE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -15,103 +15,77 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <actions/gesture/IntervalGesture.h>
#include <Configuration.h>
#include <util/log.h>
#include "IntervalGesture.h"
#include "../../util/log.h"
using namespace logid::actions;
const char* IntervalGesture::interface_name = "OnInterval";
IntervalGesture::IntervalGesture(
Device* device, config::IntervalGesture& config,
const std::shared_ptr<ipcgull::node>& parent) :
Gesture(device, parent, interface_name, {
IntervalGesture::IntervalGesture(Device *device, libconfig::Setting &root) :
Gesture (device), _config (device, root)
{
{"GetConfig", {this, &IntervalGesture::getConfig, {"interval", "threshold"}}},
{"SetInterval", {this, &IntervalGesture::setInterval, {"interval"}}},
{"SetThreshold", {this, &IntervalGesture::setThreshold, {"interval"}}},
{"SetAction", {this, &IntervalGesture::setAction, {"type"}}}
},
{},
{}
}),
_axis(0), _interval_pass_count(0), _config(config) {
if (config.action) {
try {
_action = Action::makeAction(device, config.action.value(), _node);
} catch (InvalidAction& e) {
logPrintf(WARN, "Mapping gesture to invalid action");
}
}
}
void IntervalGesture::press(bool init_threshold) {
std::shared_lock lock(_config_mutex);
if (init_threshold) {
_axis = (int32_t) _config.threshold.value_or(defaults::gesture_threshold);
} else {
void IntervalGesture::press()
{
_axis = 0;
}
_interval_pass_count = 0;
}
void IntervalGesture::release([[maybe_unused]] bool primary) {
void IntervalGesture::release(bool primary)
{
// Do nothing
(void)primary; // Suppress unused warning
}
void IntervalGesture::move(int16_t axis) {
std::shared_lock lock(_config_mutex);
if (!_config.interval.has_value())
return;
const auto threshold =
_config.threshold.value_or(defaults::gesture_threshold);
void IntervalGesture::move(int16_t axis)
{
_axis += axis;
if (_axis < threshold)
if(_axis < _config.threshold())
return;
int32_t new_interval_count = (_axis - threshold) / _config.interval.value();
int16_t new_interval_count = (_axis - _config.threshold())/
_config.interval();
if(new_interval_count > _interval_pass_count) {
if (_action) {
_action->press();
_action->release();
}
_config.action()->press();
_config.action()->release();
}
_interval_pass_count = new_interval_count;
}
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)};
}
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);
int16_t IntervalGesture::Config::interval() const
{
return _interval;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -18,41 +18,36 @@
#ifndef LOGID_ACTION_INTERVALGESTURE_H
#define LOGID_ACTION_INTERVALGESTURE_H
#include <actions/gesture/Gesture.h>
#include "Gesture.h"
namespace logid::actions {
class IntervalGesture : public Gesture {
namespace logid {
namespace actions
{
class IntervalGesture : public Gesture
{
public:
static const char* interface_name;
IntervalGesture(Device* device, libconfig::Setting& root);
IntervalGesture(Device* device, config::IntervalGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
virtual void press();
virtual void release(bool primary=false);
virtual void move(int16_t axis);
void press(bool init_threshold) final;
virtual bool metThreshold() const;
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);
class Config : public Gesture::Config
{
public:
Config(Device* device, libconfig::Setting& setting);
int16_t interval() const;
private:
int16_t _interval;
};
protected:
int32_t _axis;
int32_t _interval_pass_count;
std::shared_ptr<Action> _action;
config::IntervalGesture& _config;
private:
int16_t _axis;
int16_t _interval_pass_count;
Config _config;
};
}
}}
#endif //LOGID_ACTION_INTERVALGESTURE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -15,37 +15,32 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <actions/gesture/NullGesture.h>
#include <Configuration.h>
#include "NullGesture.h"
using namespace logid::actions;
const char* NullGesture::interface_name = "None";
NullGesture::NullGesture(Device* device,
config::NoGesture& config,
const std::shared_ptr<ipcgull::node>& parent) :
Gesture(device, parent, interface_name), _config(config) {
NullGesture::NullGesture(Device *device, libconfig::Setting& setting) :
Gesture (device), _config (device, setting, false)
{
}
void NullGesture::press(bool init_threshold) {
_axis = init_threshold ? _config.threshold.value_or(
defaults::gesture_threshold) : 0;
void NullGesture::press()
{
_axis = 0;
}
void NullGesture::release(bool primary) {
void NullGesture::release(bool primary)
{
// Do nothing
(void)primary; // Suppress unused warning
}
void NullGesture::move(int16_t axis) {
void NullGesture::move(int16_t axis)
{
_axis += axis;
}
bool NullGesture::wheelCompatibility() const {
return true;
}
bool NullGesture::metThreshold() const {
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
bool NullGesture::metThreshold() const
{
return _axis > _config.threshold();
}

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -18,37 +18,26 @@
#ifndef LOGID_ACTION_RELEASEGESTURE_H
#define LOGID_ACTION_RELEASEGESTURE_H
#include <actions/gesture/Gesture.h>
#include "Gesture.h"
namespace logid::actions {
class ReleaseGesture : public Gesture {
namespace logid {
namespace actions
{
class ReleaseGesture : public Gesture
{
public:
static const char* interface_name;
ReleaseGesture(Device* device, libconfig::Setting& root);
ReleaseGesture(Device* device, config::ReleaseGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
virtual void press();
virtual void release(bool primary=false);
virtual void move(int16_t axis);
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);
virtual bool metThreshold() const;
protected:
int32_t _axis{};
std::shared_ptr<Action> _action;
config::ReleaseGesture& _config;
int16_t _axis;
Gesture::Config _config;
};
}
}}
#endif //LOGID_ACTION_RELEASEGESTURE_H

View File

@ -1,95 +0,0 @@
/*
* Copyright 2019-2023 PixlOne, michtere
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <actions/gesture/ThresholdGesture.h>
#include <Configuration.h>
#include <util/log.h>
using namespace logid::actions;
const char* ThresholdGesture::interface_name = "OnRelease";
ThresholdGesture::ThresholdGesture(
Device* device, config::ThresholdGesture& config,
const std::shared_ptr<ipcgull::node>& parent) :
Gesture(device, parent, interface_name, {
{
{"GetThreshold", {this, &ThresholdGesture::getThreshold, {"threshold"}}},
{"SetThreshold", {this, &ThresholdGesture::setThreshold, {"threshold"}}},
{"SetAction", {this, &ThresholdGesture::setAction, {"type"}}}
},
{},
{}
}), _config(config) {
if (config.action) {
try {
_action = Action::makeAction(device, config.action.value(), _node);
} catch (InvalidAction& e) {
logPrintf(WARN, "Mapping gesture to invalid action");
}
}
}
void ThresholdGesture::press(bool init_threshold) {
std::shared_lock lock(_config_mutex);
_axis = init_threshold ? (int32_t) _config.threshold.value_or(defaults::gesture_threshold) : 0;
this->_executed = false;
}
void ThresholdGesture::release([[maybe_unused]] bool primary) {
this->_executed = false;
}
void ThresholdGesture::move(int16_t axis) {
_axis += axis;
if (!this->_executed && metThreshold()) {
if (_action) {
_action->press();
_action->release();
}
this->_executed = true;
}
}
bool ThresholdGesture::metThreshold() const {
std::shared_lock lock(_config_mutex);
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
}
bool ThresholdGesture::wheelCompatibility() const {
return false;
}
int ThresholdGesture::getThreshold() const {
std::shared_lock lock(_config_mutex);
return _config.threshold.value_or(0);
}
void ThresholdGesture::setThreshold(int threshold) {
std::unique_lock lock(_config_mutex);
if (threshold == 0)
_config.threshold.reset();
else
_config.threshold = threshold;
}
void ThresholdGesture::setAction(const std::string& type) {
std::unique_lock lock(_config_mutex);
_action.reset();
_action = Action::makeAction(_device, type, _config.action, _node);
}

View File

@ -1,57 +0,0 @@
/*
* Copyright 2019-2023 PixlOne, michtere
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_ACTION_THRESHOLDGESTURE_H
#define LOGID_ACTION_THRESHOLDGESTURE_H
#include <actions/gesture/Gesture.h>
namespace logid::actions {
class ThresholdGesture : public Gesture {
public:
static const char* interface_name;
ThresholdGesture(Device* device, config::ThresholdGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
void press(bool init_threshold) final;
void release(bool primary) final;
void move(int16_t axis) final;
[[nodiscard]] bool metThreshold() const final;
[[nodiscard]] bool wheelCompatibility() const final;
[[nodiscard]] int getThreshold() const;
void setThreshold(int threshold);
void setAction(const std::string& type);
protected:
int32_t _axis{};
std::shared_ptr<actions::Action> _action;
config::ThresholdGesture& _config;
private:
bool _executed = false;
};
}
#endif //LOGID_ACTION_THRESHOLDGESTURE_H

View File

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

View File

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

View File

@ -1,137 +0,0 @@
/*
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_EVENTHANDLERLIST_H
#define LOGID_EVENTHANDLERLIST_H
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <list>
#include <atomic>
template <class T>
class EventHandlerLock;
template <class T>
class EventHandlerList {
public:
typedef std::list<std::pair<typename T::EventHandler, std::atomic_bool>> list_t;
typedef typename list_t::iterator iterator_t;
private:
list_t list;
std::shared_mutex mutex;
std::shared_mutex add_mutex;
void cleanup() {
std::unique_lock lock(mutex, std::try_to_lock);
if (lock.owns_lock()) {
std::list<iterator_t> to_remove;
for (auto it = list.begin(); it != list.end(); ++it) {
if (!it->second)
to_remove.push_back(it);
}
for(auto& it : to_remove)
list.erase(it);
}
}
public:
iterator_t add(typename T::EventHandler handler) {
std::unique_lock add_lock(add_mutex);
list.emplace_front(std::move(handler), true);
return list.begin();
}
void remove(iterator_t iterator) {
std::unique_lock lock(mutex, std::try_to_lock);
if (lock.owns_lock()) {
std::unique_lock add_lock(add_mutex);
list.erase(iterator);
} else {
iterator->second = false;
}
}
template <typename Arg>
void run_all(Arg arg) {
cleanup();
std::shared_lock lock(mutex);
std::shared_lock add_lock(add_mutex);
for (auto& handler : list) {
add_lock.unlock();
if (handler.second) {
if (handler.first.condition(arg))
handler.first.callback(arg);
}
add_lock.lock();
}
}
};
template <class T>
class EventHandlerLock {
typedef EventHandlerList<T> list_t;
typedef typename list_t::iterator_t iterator_t;
friend T;
std::weak_ptr<list_t> _list;
iterator_t _iterator;
EventHandlerLock(const std::shared_ptr<list_t>& list, iterator_t iterator) :
_list (list), _iterator (iterator) {
}
public:
EventHandlerLock() = default;
EventHandlerLock(const EventHandlerLock&) = delete;
EventHandlerLock(EventHandlerLock&& o) noexcept : _list (o._list), _iterator (o._iterator) {
o._list.reset();
}
EventHandlerLock& operator=(const EventHandlerLock&) = delete;
EventHandlerLock& operator=(EventHandlerLock&& o) noexcept {
if (this != &o) {
if (auto list = _list.lock()) {
this->_list.reset();
list->remove(_iterator);
}
this->_list = o._list;
o._list.reset();
this->_iterator = o._iterator;
}
return *this;
}
~EventHandlerLock() {
if(auto list = _list.lock())
list->remove(_iterator);
}
[[nodiscard]] bool empty() const noexcept {
return _list.expired();
}
};
#endif //LOGID_EVENTHANDLERLIST_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -16,15 +16,27 @@
*
*/
#include <config/schema.h>
#include <util/log.h>
#include "Error.h"
using namespace logid;
using namespace logid::backend::dj;
const char config::keys::name[] = "name";
const char config::keys::cid[] = "cid";
const char config::keys::direction[] = "direction";
void config::logError(const libconfig::Setting& setting, std::exception& e) {
logPrintf(WARN, "Error at line %d: %s", setting.getSourceLine(), e.what());
Error::Error(uint8_t code) : _code (code)
{
}
const char* Error::what() const noexcept
{
switch(_code) {
case Unknown:
return "Unknown";
case KeepAliveTimeout:
return "Keep-alive timeout";
default:
return "Reserved";
}
}
uint8_t Error::code() const noexcept
{
return _code;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -15,21 +15,34 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <backend/hidpp20/features/WirelessDeviceStatus.h>
#include <cassert>
using namespace logid::backend::hidpp20;
#ifndef LOGID_BACKEND_DJ_ERROR_H
#define LOGID_BACKEND_DJ_ERROR_H
WirelessDeviceStatus::WirelessDeviceStatus(Device* dev) : Feature(dev, ID) {
}
#include <cstdint>
#include <stdexcept>
WirelessDeviceStatus::Status WirelessDeviceStatus::statusBroadcastEvent(
const hidpp::Report& report) {
assert(report.function() == StatusBroadcast);
Status status = {};
auto params = report.paramBegin();
status.reconnection = params[0];
status.reconfNeeded = params[1];
status.powerSwitch = params[2];
return status;
}
namespace logid {
namespace backend {
namespace dj
{
class Error : public std::exception
{
public:
enum ErrorCode : uint8_t
{
Unknown = 0x00,
KeepAliveTimeout = 0x01
};
explicit Error(uint8_t code);
const char* what() const noexcept override;
uint8_t code() const noexcept;
private:
uint8_t _code;
};
}}}
#endif //LOGID_BACKEND_DJ_ERROR_H

View File

@ -0,0 +1,381 @@
/*
* 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

@ -0,0 +1,213 @@
/*
* 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
{
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

@ -0,0 +1,137 @@
/*
* 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

@ -0,0 +1,61 @@
/*
* 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);
~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

@ -0,0 +1,135 @@
/*
* 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

@ -0,0 +1,58 @@
/*
* Copyright 2019-2020 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#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

@ -0,0 +1,59 @@
/*
* 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-2023 PixlOne
* 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
@ -16,21 +16,22 @@
*
*/
#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;
using namespace std::chrono;
const char* Device::InvalidDevice::what() const noexcept {
const char* Device::InvalidDevice::what() const noexcept
{
switch(_reason) {
case NoHIDPPReport:
return "Invalid HID++ device";
@ -38,39 +39,34 @@ const char* Device::InvalidDevice::what() const noexcept {
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,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) :
io_timeout(duration_cast<milliseconds>(
duration<double, std::milli>(timeout))),
_raw_device(raw::RawDevice::make(path, monitor)),
_receiver(nullptr), _path(path), _index(index) {
Device::Device(const std::string& path, DeviceIndex index):
_raw_device (std::make_shared<raw::RawDevice>(path)), _receiver (nullptr),
_path (path), _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))),
Device::Device(std::shared_ptr<raw::RawDevice> raw_device, DeviceIndex index) :
_raw_device (std::move(raw_device)), _receiver (nullptr),
_path(_raw_device->rawPath()), _index(index) {
_path (_raw_device->hidrawPath()), _index (index)
{
_init();
}
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::Device(std::shared_ptr<dj::Receiver> receiver,
hidpp::DeviceConnectionEvent event) :
_raw_device (receiver->rawDevice()), _index (event.index)
{
// Device will throw an error soon, just do it now
if(!event.linkEstablished)
throw InvalidDevice(InvalidDevice::Asleep);
@ -79,63 +75,33 @@ Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
_pid = event.pid;
else
_pid = receiver->getPairingInfo(_index).pid;
}
Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
DeviceIndex index, double timeout) :
io_timeout(duration_cast<milliseconds>(
duration<double, std::milli>(timeout))),
_raw_device(receiver->rawDevice()),
_receiver(receiver), _path(receiver->rawDevice()->rawPath()),
_index(index) {
_pid = receiver->getPairingInfo(_index).pid;
}
const std::string& Device::devicePath() const {
return _path;
}
DeviceIndex Device::deviceIndex() const {
return _index;
}
const std::tuple<uint8_t, uint8_t>& Device::version() const {
return _version;
}
void Device::_setupReportsAndInit() {
_event_handlers = std::make_shared<EventHandlerList<Device>>();
supported_reports = getSupportedReports(_raw_device->reportDescriptor());
if (!supported_reports)
throw InvalidDevice(InvalidDevice::NoHIDPPReport);
/* hid_logitech_dj creates virtual /dev/hidraw nodes for receiver
* devices. We should ignore these devices.
*/
if (_raw_device->isSubDevice())
throw InvalidDevice(InvalidDevice::VirtualNode);
_raw_handler = _raw_device->addEventHandler(
{[index = _index](
const std::vector<uint8_t>& report) -> bool {
return (report[Offset::Type] == Report::Type::Short ||
report[Offset::Type] == Report::Type::Long) &&
(report[Offset::DeviceIndex] == index);
},
[self_weak = _self](const std::vector<uint8_t>& report) -> void {
Report _report(report);
if(auto self = self_weak.lock())
self->handleEvent(_report);
}});
_init();
}
void Device::_init() {
std::string Device::devicePath() const
{
return _path;
}
DeviceIndex Device::deviceIndex() const
{
return _index;
}
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)
throw InvalidDevice(InvalidDevice::NoHIDPPReport);
try {
hidpp20::Root root(this);
hidpp20::EssentialRoot root(this);
_version = root.getVersion();
} catch(hidpp10::Error &e) {
// Valid HID++ 1.0 devices should send an InvalidSubID error
@ -144,27 +110,13 @@ void Device::_init() {
// HID++ 2.0 is not supported, assume HID++ 1.0
_version = std::make_tuple(1, 0);
} catch (hidpp20::Error& e) {
/* Should never happen, device not ready? */
throw DeviceNotReady();
}
/* Do a stability test before going further */
if (std::get<0>(_version) >= 2) {
if (!isStable20()) {
throw DeviceNotReady();
}
} else {
if (!isStable10()) {
throw DeviceNotReady();
}
}
if(!_receiver) {
_pid = _raw_device->productId();
if(std::get<0>(_version) >= 2) {
try {
hidpp20::DeviceName deviceName(this);
hidpp20::EssentialDeviceName deviceName(this);
_name = deviceName.getName();
} catch(hidpp20::UnsupportedFeature &e) {
_name = _raw_device->name();
@ -172,149 +124,126 @@ void Device::_init() {
} else {
_name = _raw_device->name();
}
} else {
if (std::get<0>(_version) >= 2) {
try {
hidpp20::DeviceName deviceName(this);
_name = deviceName.getName();
} catch (hidpp20::UnsupportedFeature& e) {
_name = _receiver->getDeviceName(_index);
}
} else {
_name = _receiver->getDeviceName(_index);
}
}
Device::~Device()
{
if(_listening)
_raw_device->removeEventHandler("DEV_" + std::to_string(_index));
}
EventHandlerLock<Device> Device::addEventHandler(EventHandler handler) {
return {_event_handlers, _event_handlers->add(std::move(handler))};
void Device::addEventHandler(const std::string& nickname,
const std::shared_ptr<EventHandler>& handler)
{
assert(_event_handlers.find(nickname) == _event_handlers.end());
_event_handlers.emplace(nickname, handler);
}
void Device::handleEvent(Report& report) {
if (responseReport(report))
return;
_event_handlers->run_all(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();
_sent_address = report.address();
std::unique_lock lock(_response_mutex);
_sendReport(report);
bool valid = _response_cv.wait_for(
lock, io_timeout, [this]() {
return _response.has_value();
});
if (!valid) {
_sent_sub_id.reset();
throw TimeoutError();
const std::map<std::string, std::shared_ptr<EventHandler>>&
Device::eventHandlers()
{
return _event_handlers;
}
Response response = _response.value();
_response.reset();
_sent_sub_id.reset();
_sent_address.reset();
if (std::holds_alternative<Report>(response)) {
return std::get<Report>(response);
} else if (std::holds_alternative<Report::Hidpp10Error>(response)) {
auto error = std::get<Report::Hidpp10Error>(response);
throw hidpp10::Error(error.error_code, error.device_index);
} else if (std::holds_alternative<Report::Hidpp20Error>(response)) {
auto error = std::get<Report::Hidpp20Error>(response);
throw hidpp20::Error(error.error_code, error.device_index);
void Device::handleEvent(Report& report)
{
for(auto& handler : _event_handlers)
if(handler.second->condition(report))
handler.second->callback(report);
}
// Should not be reached
throw std::runtime_error("unhandled variant type");
}
bool Device::responseReport(const Report& report) {
std::lock_guard lock(_response_mutex);
Response response = report;
uint8_t sub_id;
uint8_t address;
Report::Hidpp10Error hidpp10_error{};
Report::Hidpp20Error hidpp20_error{};
if (report.isError10(hidpp10_error)) {
sub_id = hidpp10_error.sub_id;
address = hidpp10_error.address;
response = hidpp10_error;
} else if (report.isError20(hidpp20_error)) {
sub_id = hidpp20_error.feature_index;
address = (hidpp20_error.function << 4) | (hidpp20_error.software_id & 0x0f);
} else {
sub_id = report.subId();
address = report.address();
}
if (sub_id == _sent_sub_id && address == _sent_address) {
_response = response;
_response_cv.notify_all();
return true;
} else {
return false;
}
}
const std::shared_ptr<raw::RawDevice>& Device::rawDevice() const {
return _raw_device;
}
void Device::_sendReport(Report report) {
reportFixup(report);
_raw_device->sendReport(report.rawReport());
}
void Device::sendReportNoACK(const Report& report) {
std::lock_guard lock(_send_mutex);
_sendReport(report);
}
bool Device::isStable10() {
return true;
}
bool Device::isStable20() {
static const std::string ping_seq = "hello";
hidpp20::Root root(this);
try {
for (auto c: ping_seq) {
if (root.ping(c) != c)
return false;
}
} catch (std::exception& e) {
return false;
}
return true;
}
void Device::reportFixup(Report& report) const {
switch (report.type()) {
Report Device::sendReport(Report& report)
{
switch(report.type())
{
case Report::Type::Short:
if (!(supported_reports & ShortReportSupported))
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 & LongReportSupported);
}
assert(_supported_reports & HIDPP_REPORT_LONG_SUPPORTED);
}
const std::string& Device::name() const {
auto raw_response = _raw_device->sendReport(report.rawReport());
Report response(raw_response);
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);
}
_raw_device->sendReportNoResponse(report.rawReport());
}
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-2023 PixlOne
* 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
@ -19,168 +19,92 @@
#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::backend::hidpp10 {
namespace logid {
namespace backend {
namespace dj
{
// Need to define here for a constructor
class Receiver;
}
namespace logid::backend::hidpp {
namespace hidpp
{
struct DeviceConnectionEvent;
template<typename T>
class _deviceWrapper : public T {
friend class Device;
public:
template<typename... Args>
explicit _deviceWrapper(Args... args) : T(std::forward<Args>(args)...) {}
template<typename... Args>
static std::shared_ptr<T> make(Args... args) {
return std::make_shared<_deviceWrapper>(std::forward<Args>(args)...);
}
};
class Device {
template<typename T>
friend
class _deviceWrapper;
public:
struct EventHandler {
struct EventHandler
{
std::function<bool(Report&)> condition;
std::function<void(Report&)> callback;
};
class InvalidDevice : std::exception {
class Device
{
public:
enum Reason {
class InvalidDevice : std::exception
{
public:
enum Reason
{
NoHIDPPReport,
InvalidRawDevice,
Asleep,
VirtualNode
Asleep
};
explicit InvalidDevice(Reason reason) : _reason(reason) {}
[[nodiscard]] const char* what() const noexcept override;
[[nodiscard]] virtual Reason code() const noexcept;
InvalidDevice(Reason reason) : _reason (reason) {}
virtual const char* what() const noexcept;
virtual Reason code() const noexcept;
private:
Reason _reason;
};
[[nodiscard]] const std::string& devicePath() const;
explicit Device(const std::string& path, DeviceIndex index);
explicit Device(std::shared_ptr<raw::RawDevice> raw_device,
DeviceIndex index);
explicit Device(std::shared_ptr<dj::Receiver> receiver,
hidpp::DeviceConnectionEvent event);
~Device();
[[nodiscard]] DeviceIndex deviceIndex() const;
std::string devicePath() const;
DeviceIndex deviceIndex() const;
std::tuple<uint8_t, uint8_t> version() const;
[[nodiscard]] const std::tuple<uint8_t, uint8_t>& version() const;
std::string name() const;
uint16_t pid() const;
[[nodiscard]] const std::string& name() const;
void listen(); // Runs asynchronously
void stopListening();
[[nodiscard]] uint16_t pid() 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();
EventHandlerLock<Device> addEventHandler(EventHandler handler);
virtual Report sendReport(const Report& report);
virtual void sendReportNoACK(const Report& report);
Report sendReport(Report& report);
void sendReportNoResponse(Report& report);
void handleEvent(Report& report);
[[nodiscard]] const std::shared_ptr<raw::RawDevice>& rawDevice() const;
Device(const Device&) = delete;
Device(Device&&) = delete;
virtual ~Device() = default;
protected:
Device(const std::string& path, DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout);
Device(std::shared_ptr<raw::RawDevice> raw_device, DeviceIndex index,
double timeout);
Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceConnectionEvent event, double timeout);
Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
DeviceIndex index, double timeout);
// Returns whether the report is a response
virtual bool responseReport(const Report& report);
bool isStable20();
bool isStable10();
void _sendReport(Report report);
void reportFixup(Report& report) const;
const std::chrono::milliseconds io_timeout;
uint8_t supported_reports{};
std::mutex _response_mutex;
std::condition_variable _response_cv;
private:
void _setupReportsAndInit();
void _init();
std::shared_ptr<raw::RawDevice> _raw_device;
EventHandlerLock<raw::RawDevice> _raw_handler;
std::shared_ptr<hidpp10::Receiver> _receiver;
std::shared_ptr<dj::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::mutex _send_mutex;
std::atomic<bool> _listening;
typedef std::variant<Report, Report::Hidpp10Error, Report::Hidpp20Error> Response;
std::optional<Response> _response;
std::optional<uint8_t> _sent_sub_id{};
std::optional<uint8_t> _sent_address{};
std::shared_ptr<EventHandlerList<Device>> _event_handlers;
std::weak_ptr<Device> _self;
protected:
template<typename T, typename... Args>
static std::shared_ptr<T> makeDerived(Args... args) {
auto device = _deviceWrapper<T>::make(std::forward<Args>(args)...);
device->_self = device;
device->_setupReportsAndInit();
return device;
}
public:
template<typename... Args>
static std::shared_ptr<Device> make(Args... args) {
return makeDerived<Device>(std::forward<Args>(args)...);
}
std::map<std::string, std::shared_ptr<EventHandler>> _event_handlers;
};
typedef Device::EventHandler EventHandler;
}
} } }
#endif //LOGID_BACKEND_HIDPP_DEVICE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -16,12 +16,12 @@
*
*/
#include <backend/hidpp/Report.h>
#include <array>
#include <algorithm>
#include <cassert>
#include <backend/hidpp10/Error.h>
#include <backend/hidpp20/Error.h>
#include "Report.h"
#include "../hidpp10/Error.h"
#include "../hidpp20/Error.h"
using namespace logid::backend::hidpp;
using namespace logid::backend;
@ -84,38 +84,42 @@ static const std::array<uint8_t, 22> LongReportDesc2 = {
0xC0 // End Collection
};
uint8_t hidpp::getSupportedReports(const std::vector<uint8_t>& report_desc) {
uint8_t hidpp::getSupportedReports(std::vector<uint8_t>&& rdesc)
{
uint8_t ret = 0;
auto it = std::search(report_desc.begin(), report_desc.end(),
auto it = std::search(rdesc.begin(), rdesc.end(),
ShortReportDesc.begin(), ShortReportDesc.end());
if (it == report_desc.end())
it = std::search(report_desc.begin(), report_desc.end(),
if(it == rdesc.end())
it = std::search(rdesc.begin(), rdesc.end(),
ShortReportDesc2.begin(), ShortReportDesc2.end());
if (it != report_desc.end())
ret |= ShortReportSupported;
if(it != rdesc.end())
ret |= HIDPP_REPORT_SHORT_SUPPORTED;
it = std::search(report_desc.begin(), report_desc.end(),
it = std::search(rdesc.begin(), rdesc.end(),
LongReportDesc.begin(), LongReportDesc.end());
if (it == report_desc.end())
it = std::search(report_desc.begin(), report_desc.end(),
if(it == rdesc.end())
it = std::search(rdesc.begin(), rdesc.end(),
LongReportDesc2.begin(), LongReportDesc2.end());
if (it != report_desc.end())
ret |= LongReportSupported;
if(it != rdesc.end())
ret |= HIDPP_REPORT_LONG_SUPPORTED;
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) {
uint8_t sub_id, uint8_t address)
{
switch(type) {
case Type::Short:
_data.resize(HeaderLength + ShortParamLength);
@ -134,7 +138,8 @@ 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);
@ -157,7 +162,8 @@ Report::Report(Report::Type type, DeviceIndex device_index,
}
Report::Report(const std::vector<uint8_t>& data) :
_data(data) {
_data (data)
{
_data.resize(HeaderLength + LongParamLength);
// Truncating data is entirely valid here.
@ -173,11 +179,13 @@ Report::Report(const std::vector<uint8_t>& data) :
}
}
Report::Type Report::type() const {
Report::Type Report::type() const
{
return static_cast<Report::Type>(_data[Offset::Type]);
}
void Report::setType(Report::Type type) {
void Report::setType(Report::Type type)
{
switch(type) {
case Type::Short:
_data.resize(HeaderLength + ShortParamLength);
@ -192,106 +200,123 @@ void Report::setType(Report::Type type) {
_data[Offset::Type] = type;
}
hidpp::DeviceIndex Report::deviceIndex() const {
hidpp::DeviceIndex Report::deviceIndex() const
{
return static_cast<hidpp::DeviceIndex>(_data[Offset::DeviceIndex]);
}
[[maybe_unused]] void Report::setDeviceIndex(hidpp::DeviceIndex index) {
void Report::setDeviceIndex(hidpp::DeviceIndex index)
{
_data[Offset::DeviceIndex] = index;
}
uint8_t Report::feature() const {
uint8_t Report::feature() const
{
return _data[Offset::Feature];
}
[[maybe_unused]] void Report::setFeature(uint8_t feature) {
void Report::setFeature(uint8_t feature)
{
_data[Offset::Parameters] = feature;
}
uint8_t Report::subId() const {
uint8_t Report::subId() const
{
return _data[Offset::SubID];
}
[[maybe_unused]] void Report::setSubId(uint8_t sub_id) {
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;
}
[[maybe_unused]] void Report::setFunction(uint8_t function) {
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 sw_id) {
_data[Offset::Function] &= 0xf0;
_data[Offset::Function] |= sw_id & 0x0f;
void Report::setSwId(uint8_t sub_id)
{
_data[Offset::Function] &= 0x0f;
_data[Offset::Function] |= sub_id & 0x0f;
}
uint8_t Report::address() const {
uint8_t Report::address() const
{
return _data[Offset::Address];
}
[[maybe_unused]] void Report::setAddress(uint8_t address) {
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) {
void Report::setParams(const std::vector<uint8_t>& _params)
{
assert(_params.size() <= _data.size()-HeaderLength);
for(std::size_t i = 0; i < _params.size(); i++)
_data[Offset::Parameters + i] = _params[i];
}
bool Report::isError10(Report::Hidpp10Error& error) const {
bool Report::isError10(Report::Hidpp10Error *error)
{
assert(error != nullptr);
if(_data[Offset::Type] != Type::Short ||
_data[Offset::SubID] != hidpp10::ErrorID)
return false;
error.device_index = deviceIndex();
error.sub_id = _data[3];
error.address = _data[4];
error.error_code = _data[5];
error->sub_id = _data[3];
error->address = _data[4];
error->error_code = _data[5];
return true;
}
bool Report::isError20(Report::Hidpp20Error& error) const {
bool Report::isError20(Report::Hidpp20Error* error)
{
assert(error != nullptr);
if(_data[Offset::Type] != Type::Long ||
_data[Offset::Feature] != hidpp20::ErrorID)
return false;
error.device_index = deviceIndex();
error.feature_index = _data[3];
error.function = (_data[4] >> 4) & 0x0f;
error.software_id = _data[4] & 0x0f;
error.error_code = _data[5];
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-2023 PixlOne
* 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
@ -19,19 +19,23 @@
#ifndef LOGID_BACKEND_HIDPP_REPORT_H
#define LOGID_BACKEND_HIDPP_REPORT_H
#include <backend/raw/RawDevice.h>
#include <backend/hidpp/defs.h>
#include <cstdint>
namespace logid::backend::hidpp {
uint8_t getSupportedReports(const std::vector<uint8_t>& report_desc);
#include "../raw/RawDevice.h"
#include "defs.h"
/* Some devices only support a subset of these reports */
static constexpr uint8_t ShortReportSupported = 1U;
static constexpr uint8_t LongReportSupported = (1U<<1);
#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 Offset {
namespace logid {
namespace backend {
namespace hidpp
{
uint8_t getSupportedReports(std::vector<uint8_t>&& rdesc);
namespace Offset
{
static constexpr uint8_t Type = 0;
static constexpr uint8_t DeviceIndex = 1;
static constexpr uint8_t SubID = 2;
@ -41,22 +45,23 @@ namespace logid::backend::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;
[[nodiscard]] const char* what() const noexcept override;
const char* what() const noexcept override;
};
class InvalidReportLength : public std::exception {
class InvalidReportLength: public std::exception
{
public:
InvalidReportLength() = default;
[[nodiscard]] const char* what() const noexcept override;
InvalidReportLength() = default;;
const char* what() const noexcept override;
};
static constexpr std::size_t MaxDataLength = 20;
@ -64,72 +69,57 @@ namespace logid::backend::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);
explicit Report(const std::vector<uint8_t>& data);
[[nodiscard]] Report::Type type() const;
Report::Type type() const;
void setType(Report::Type type);
[[nodiscard]] DeviceIndex deviceIndex() const;
logid::backend::hidpp::DeviceIndex deviceIndex() const;
void setDeviceIndex(hidpp::DeviceIndex index);
[[maybe_unused]] void setDeviceIndex(DeviceIndex index);
uint8_t feature() const;
void setFeature(uint8_t feature);
[[nodiscard]] uint8_t feature() const;
uint8_t subId() const;
void setSubId(uint8_t sub_id);
[[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 function() const;
void setFunction(uint8_t function);
uint8_t swId() const;
void setSwId(uint8_t sw_id);
[[nodiscard]] uint8_t address() const;
[[maybe_unused]] void setAddress(uint8_t address);
[[nodiscard]] std::vector<uint8_t>::iterator paramBegin();
[[nodiscard]] std::vector<uint8_t>::iterator paramEnd();
[[nodiscard]] std::vector<uint8_t>::const_iterator paramBegin() const;
[[nodiscard]] std::vector<uint8_t>::const_iterator paramEnd() const;
uint8_t address() const;
void setAddress(uint8_t address);
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 {
hidpp::DeviceIndex device_index;
struct Hidpp10Error
{
uint8_t sub_id, address, error_code;
};
bool isError10(Hidpp10Error* error);
bool isError10(Hidpp10Error& error) const;
struct Hidpp20Error {
hidpp::DeviceIndex device_index;
struct Hidpp20Error
{
uint8_t feature_index, function, software_id, error_code;
};
bool isError20(Hidpp20Error* error);
bool isError20(Hidpp20Error& error) const;
[[nodiscard]] const std::vector<uint8_t>& rawReport() const;
std::vector<uint8_t> rawReport () const { return _data; }
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-2023 PixlOne
* 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
@ -19,33 +19,37 @@
#ifndef LOGID_BACKEND_HIDPP_DEFS_H
#define LOGID_BACKEND_HIDPP_DEFS_H
#define LOGID_HIDPP_SOFTWARE_ID 0
#include <cstdint>
namespace logid::backend::hidpp {
namespace ReportType {
enum ReportType : uint8_t {
namespace logid {
namespace backend {
namespace 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 [[maybe_unused]] = 2,
WirelessDevice3 [[maybe_unused]] = 3,
WirelessDevice4 [[maybe_unused]] = 4,
WirelessDevice5 [[maybe_unused]] = 5,
WirelessDevice2 = 2,
WirelessDevice3 = 3,
WirelessDevice4 = 4,
WirelessDevice5 = 5,
WirelessDevice6 = 6,
};
static constexpr uint8_t softwareID = 2;
/* For sending reports with no response, use a different SW ID */
static constexpr uint8_t noAckSoftwareID = 3;
static constexpr std::size_t ShortParamLength = 3;
static constexpr std::size_t LongParamLength = 16;
}
} } }
#endif //LOGID_BACKEND_HIDPP_DEFS_H

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -16,17 +16,19 @@
*
*/
#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, hidpp::DeviceIndex index) : _code(code), _index(index) {
Error::Error(uint8_t code): _code(code)
{
assert(code != Success);
}
const char* Error::what() const noexcept {
const char* Error::what() const noexcept
{
switch(_code) {
case Success:
return "Success";
@ -59,10 +61,7 @@ const char* Error::what() const noexcept {
}
}
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-2023 PixlOne
* 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
@ -19,16 +19,18 @@
#ifndef LOGID_BACKEND_HIDPP10_ERROR_H
#define LOGID_BACKEND_HIDPP10_ERROR_H
#include <backend/hidpp/defs.h>
#include <cstdint>
#include <exception>
namespace logid::backend::hidpp10 {
namespace logid {
namespace backend {
namespace 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,
@ -44,18 +46,14 @@ namespace logid::backend::hidpp10 {
WrongPINCode = 0x0C
};
Error(uint8_t code, hidpp::DeviceIndex index);
explicit Error(uint8_t code);
[[nodiscard]] const char* what() const noexcept override;
[[nodiscard]] uint8_t code() const noexcept;
[[nodiscard]] hidpp::DeviceIndex deviceIndex() const noexcept;
const char* what() const noexcept override;
uint8_t code() const noexcept;
private:
uint8_t _code;
hidpp::DeviceIndex _index;
};
}
}}}
#endif //LOGID_BACKEND_HIDPP10_ERROR_H

View File

@ -1,380 +0,0 @@
/*
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <backend/hidpp10/Receiver.h>
#include <cassert>
using namespace logid::backend::hidpp10;
using namespace logid::backend;
const char* InvalidReceiver::what() const noexcept {
return "Not a receiver";
}
Receiver::Receiver(const std::string& path, const std::shared_ptr<raw::DeviceMonitor>& monitor,
double timeout) : Device(path, hidpp::DefaultDevice, monitor, timeout) {
}
void Receiver::_receiverCheck() {
// Check if the device is a receiver
try {
getNotificationFlags();
} catch (hidpp10::Error& e) {
if (e.code() == Error::InvalidAddress)
throw InvalidReceiver();
}
// TODO: is there a better way of checking if this is a bolt receiver?
_is_bolt = pid() == 0xc548;
}
Receiver::NotificationFlags Receiver::getNotificationFlags() {
auto response = getRegister(EnableHidppNotifications, {}, hidpp::ReportType::Short);
NotificationFlags flags{};
flags.deviceBatteryStatus = response[0] & (1 << 4);
flags.receiverWirelessNotifications = response[1] & (1 << 0);
flags.receiverSoftwarePresent = response[1] & (1 << 3);
return flags;
}
void Receiver::setNotifications(NotificationFlags flags) {
std::vector<uint8_t> request(3);
if (flags.deviceBatteryStatus)
request[0] |= (1 << 4);
if (flags.receiverWirelessNotifications)
request[1] |= 1;
if (flags.receiverSoftwarePresent)
request[1] |= (1 << 3);
setRegister(EnableHidppNotifications, request, hidpp::ReportType::Short);
}
void Receiver::enumerate() {
setRegisterNoResponse(ConnectionState, {2}, hidpp::ReportType::Short);
}
///TODO: Investigate usage
uint8_t Receiver::getConnectionState(hidpp::DeviceIndex index) {
auto response = getRegister(ConnectionState, {index}, hidpp::ReportType::Short);
return response[0];
}
void Receiver::startPairing(uint8_t timeout) {
std::vector<uint8_t> request(3);
if (_is_bolt)
throw std::invalid_argument("unifying pairing on bolt");
request[0] = 1;
request[1] = hidpp::DefaultDevice;
request[2] = timeout;
if (_is_bolt) {
setRegister(BoltDevicePairing, request, hidpp::ReportType::Long);
} else {
setRegister(DevicePairing, request, hidpp::ReportType::Short);
}
}
// bolt pairing request from solaar
void Receiver::startBoltPairing(const DeviceDiscoveryEvent& discovery) {
std::vector<uint8_t> request(10);
request[0] = 1; // start pair
request[1] = 0; // slot, from solaar. what does this mean?
for(int i = 0; i < 6; ++i)
request[2 + i] = (discovery.address >> (i*8)) & 0xff;
request[8] = discovery.authentication;
// TODO: what does entropy do?
request[9] = (discovery.deviceType == hidpp::DeviceKeyboard) ? 10 : 20;
setRegister(BoltDevicePairing, request, hidpp::ReportType::Long);
}
void Receiver::stopPairing() {
std::vector<uint8_t> request(3);
request[0] = 2;
request[1] = hidpp::DefaultDevice;
if (_is_bolt)
setRegister(BoltDevicePairing, request, hidpp::ReportType::Long);
else
setRegister(DevicePairing, request, hidpp::ReportType::Short);
}
void Receiver::startDiscover(uint8_t timeout) {
std::vector<uint8_t> request = {timeout, 1};
if (!_is_bolt)
throw std::invalid_argument("not a bolt receiver");
setRegister(BoltDeviceDiscovery, request, hidpp::ReportType::Short);
}
void Receiver::stopDiscover() {
std::vector<uint8_t> request = {0, 2};
if (!_is_bolt)
throw std::invalid_argument("not a bolt receiver");
setRegister(BoltDeviceDiscovery, request, hidpp::ReportType::Short);
}
void Receiver::disconnect(hidpp::DeviceIndex index) {
std::vector<uint8_t> request(2);
request[0] = _is_bolt ? 3 : 2;
request[1] = index;
if (_is_bolt)
setRegister(BoltDevicePairing, request, hidpp::ReportType::Long);
else
setRegister(DevicePairing, request, hidpp::ReportType::Short);
}
std::map<hidpp::DeviceIndex, uint8_t> Receiver::getDeviceActivity() {
auto response = getRegister(DeviceActivity, {}, hidpp::ReportType::Long);
std::map<hidpp::DeviceIndex, uint8_t> device_activity;
for (uint8_t i = hidpp::WirelessDevice1; i <= hidpp::WirelessDevice6; i++)
device_activity[static_cast<hidpp::DeviceIndex>(i)] = response[i];
return device_activity;
}
struct Receiver::PairingInfo
Receiver::getPairingInfo(hidpp::DeviceIndex index) {
std::vector<uint8_t> request(1);
request[0] = index;
if (_is_bolt)
request[0] += 0x50;
else
request[0] += 0x1f;
auto response = getRegister(PairingInfo, request, hidpp::ReportType::Long);
struct PairingInfo info{};
if (_is_bolt) {
info = {
.destinationId = 0, // no longer given?
.reportInterval = 0, // no longer given?
.pid = (uint16_t) ((response[3] << 8) | response[2]),
.deviceType = static_cast<hidpp::DeviceType>(response[1])
};
} else {
info = {
.destinationId = response[1],
.reportInterval = response[2],
.pid = (uint16_t) ((response[3] << 8) | response[4]),
.deviceType = static_cast<hidpp::DeviceType>(response[7])
};
}
return info;
}
struct Receiver::ExtendedPairingInfo
Receiver::getExtendedPairingInfo(hidpp::DeviceIndex index) {
const int device_num_offset = _is_bolt ? 0x50 : 0x2f;
const int serial_num_offset = _is_bolt ? 4 : 1;
const int report_offset = _is_bolt ? 8 : 5;
const int psl_offset = _is_bolt ? 12 : 8;
std::vector<uint8_t> request(1, index + device_num_offset);
auto response = getRegister(PairingInfo, request, hidpp::ReportType::Long);
ExtendedPairingInfo info{};
info.serialNumber = 0;
for (uint8_t i = 0; i < 4; i++)
info.serialNumber |= (response[i + serial_num_offset] << 8 * i);
for (uint8_t i = 0; i < 4; i++)
info.reportTypes[i] = response[i + report_offset];
uint8_t psl = response[psl_offset] & 0xf;
if (psl > 0xc)
info.powerSwitchLocation = PowerSwitchLocation::Reserved;
else
info.powerSwitchLocation = static_cast<PowerSwitchLocation>(psl);
return info;
}
std::string Receiver::getDeviceName(hidpp::DeviceIndex index) {
std::vector<uint8_t> request(2);
std::string name;
request[0] = index;
if (_is_bolt) {
/* Undocumented, deduced the following
* param 1 refers to part of string, 1-indexed
*
* response at 0x01 is [reg] [param 1] [size] [str...]
* response at 0x02-... is [next part of str...]
*/
request[0] += 0x60;
request[1] = 0x01;
auto resp = getRegister(PairingInfo, request, hidpp::ReportType::Long);
const uint8_t size = resp[2];
const uint8_t chunk_size = resp.size() - 3;
const uint8_t chunks = size / chunk_size + (size % chunk_size ? 1 : 0);
name.resize(size, ' ');
for (int i = 0; i < chunks; ++i) {
for (int j = 0; j < chunk_size; ++j) {
name[i * chunk_size + j] = (char) resp[j + 3];
}
if (i < chunks - 1) {
request[1] = i + 1;
resp = getRegister(PairingInfo, request, hidpp::ReportType::Long);
}
}
} else {
request[0] += 0x3f;
auto response = getRegister(PairingInfo, request, hidpp::ReportType::Long);
const uint8_t size = response[1];
name.resize(size, ' ');
for (std::size_t i = 0; i < size && i + 2 < response.size(); i++)
name[i] = (char) (response[i + 2]);
}
return name;
}
hidpp::DeviceIndex Receiver::deviceDisconnectionEvent(const hidpp::Report& report) {
assert(report.subId() == DeviceDisconnection);
return report.deviceIndex();
}
hidpp::DeviceConnectionEvent Receiver::deviceConnectionEvent(const hidpp::Report& report) {
assert(report.subId() == DeviceConnection);
auto data = report.paramBegin();
return {
.index = report.deviceIndex(),
.pid = (uint16_t) ((data[2] << 8) | data[1]),
.deviceType = static_cast<hidpp::DeviceType>(data[0] & 0x0f),
.unifying = ((report.address() & 0b111) == 0x04),
.softwarePresent = bool(data[0] & (1 << 4)),
.encrypted = (bool) (data[0] & (1 << 5)),
.linkEstablished = !(data[0] & (1 << 6)),
.withPayload = (bool) (data[0] & (1 << 7)),
.fromTimeoutCheck = false,
};
}
bool Receiver::fillDeviceDiscoveryEvent(DeviceDiscoveryEvent& event,
const hidpp::Report& report) {
assert(report.subId() == DeviceDiscovered);
auto data = report.paramBegin();
if (data[1] == 0) {
// device discovery event
uint64_t address = 0 ;
for (int i = 0; i < 6; ++i)
address |= ((uint64_t)(data[6 + i]) << (8*i));
event.deviceType = static_cast<hidpp::DeviceType>(data[3]);
event.pid = (data[5] << 8) | data[4];
event.address = address;
event.authentication = data[14];
event.seq_num = report.address();
event.name = "";
return false;
} else {
/* bad sequence, do not continue */
if (event.seq_num != report.address())
return false;
const int block_size = hidpp::LongParamLength - 3;
if (data[1] == 1) {
event.name.resize(data[2], ' ');
}
for(int i = 0; i < block_size; ++i) {
const size_t j = (data[1]-1)*block_size + i;
if (j < event.name.size()) {
event.name[j] = (char)data[i + 3];
} else {
return true;
}
}
return false;
}
}
PairStatusEvent Receiver::pairStatusEvent(const hidpp::Report& report) {
assert(report.subId() == PairStatus);
return {
.pairing = (bool)(report.paramBegin()[0] & 1),
.error = static_cast<PairingError>(report.paramBegin()[1])
};
}
BoltPairStatusEvent Receiver::boltPairStatusEvent(const hidpp::Report& report) {
assert(report.subId() == BoltPairStatus);
return {
.pairing = report.address() == 0,
.error = report.paramBegin()[1]
};
}
DiscoveryStatusEvent Receiver::discoveryStatusEvent(const hidpp::Report& report) {
assert(report.subId() == DiscoveryStatus);
return {
.discovering = report.address() == 0,
.error = report.paramBegin()[1]
};
}
std::string Receiver::passkeyEvent(const hidpp::Report& report) {
assert(report.subId() == PasskeyRequest);
return {report.paramBegin(), report.paramBegin() + 6};
}
bool Receiver::bolt() const {
return _is_bolt;
}

View File

@ -1,221 +0,0 @@
/*
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_DJ_RECEIVER_H
#define LOGID_BACKEND_DJ_RECEIVER_H
#include <cstdint>
#include <backend/hidpp10/Device.h>
namespace logid::backend::hidpp {
enum DeviceType : uint8_t {
DeviceUnknown = 0x00,
DeviceKeyboard = 0x01,
DeviceMouse = 0x02,
DeviceNumpad = 0x03,
DevicePresenter = 0x04,
/* 0x05-0x07 is reserved */
DeviceTrackball = 0x08,
DeviceTouchpad = 0x09
};
struct DeviceConnectionEvent {
DeviceIndex index{};
uint16_t pid{};
DeviceType deviceType = DeviceUnknown;
bool unifying{};
bool softwarePresent{};
bool encrypted{};
bool linkEstablished{};
bool withPayload{};
bool fromTimeoutCheck = false; // Fake field
};
}
namespace logid::backend::hidpp10 {
struct DeviceDiscoveryEvent {
hidpp::DeviceType deviceType = hidpp::DeviceUnknown;
uint8_t seq_num{};
uint64_t address{};
uint16_t pid{};
uint8_t authentication{};
std::string name;
};
enum PairingError : uint8_t {
NoPairingError = 0x00,
Timeout = 0x01,
UnsupportedDevice = 0x02,
TooManyDevices = 0x03,
/* Errors 0x04-0x05 are reserved */
ConnectionSeqTimeout = 0x06,
};
struct PairStatusEvent {
bool pairing{};
PairingError error = NoPairingError;
};
struct BoltPairStatusEvent {
bool pairing{};
uint8_t error;
};
struct DiscoveryStatusEvent {
bool discovering{};
uint8_t error{}; // don't know the error codes
};
class InvalidReceiver : public std::exception {
public:
[[nodiscard]] const char* what() const noexcept override;
};
class Receiver : public Device {
public:
/* The following functions deal with HID++ 1.0 features.
* While these are not technically DJ functions, it is redundant
* to have a separate hidpp10::Receiver class for these functions.
*/
enum Events : uint8_t {
// These events are identical to their DJ counterparts
DeviceDisconnection = 0x40,
DeviceConnection = 0x41,
PairStatus = 0x4a,
PasskeyRequest = 0x4d,
DeviceDiscovered = 0x4f,
DiscoveryStatus = 0x53,
BoltPairStatus = 0x54,
};
enum Registers : uint8_t {
EnableHidppNotifications = 0x00,
ConnectionState = 0x02,
DevicePairing = 0xb2,
DeviceActivity = 0xb3,
PairingInfo = 0xb5,
BoltDeviceDiscovery = 0xc0,
BoltDevicePairing = 0xc1,
};
struct NotificationFlags {
bool deviceBatteryStatus;
bool receiverWirelessNotifications;
bool receiverSoftwarePresent;
};
NotificationFlags getNotificationFlags();
void setNotifications(NotificationFlags flags);
void enumerate();
uint8_t getConnectionState(hidpp::DeviceIndex index);
void startPairing(uint8_t timeout = 0);
void startBoltPairing(const DeviceDiscoveryEvent& discovery);
void stopPairing();
void startDiscover(uint8_t timeout = 0);
void stopDiscover();
void disconnect(hidpp::DeviceIndex index);
std::map<hidpp::DeviceIndex, uint8_t> getDeviceActivity();
[[nodiscard]] bool bolt() const;
struct PairingInfo {
uint8_t destinationId;
uint8_t reportInterval;
uint16_t pid;
hidpp::DeviceType deviceType;
};
enum class PowerSwitchLocation : uint8_t {
Reserved = 0x0,
Base = 0x1,
TopCase = 0x2,
TopRightEdge = 0x3,
Other = 0x4,
TopLeft = 0x5,
BottomLeft = 0x6,
TopRight = 0x7,
BottomRight = 0x8,
TopEdge = 0x9,
RightEdge = 0xa,
LeftEdge = 0xb,
BottomEdge = 0xc
};
struct ExtendedPairingInfo {
uint32_t serialNumber;
uint8_t reportTypes[4];
PowerSwitchLocation powerSwitchLocation;
};
struct PairingInfo getPairingInfo(hidpp::DeviceIndex index);
struct ExtendedPairingInfo getExtendedPairingInfo(hidpp::DeviceIndex index);
std::string getDeviceName(hidpp::DeviceIndex index);
static hidpp::DeviceIndex deviceDisconnectionEvent(const hidpp::Report& report);
static hidpp::DeviceConnectionEvent deviceConnectionEvent(const hidpp::Report& report);
static PairStatusEvent pairStatusEvent(const hidpp::Report& report);
static BoltPairStatusEvent boltPairStatusEvent(const hidpp::Report& report);
static DiscoveryStatusEvent discoveryStatusEvent(const hidpp::Report& report);
static bool fillDeviceDiscoveryEvent(DeviceDiscoveryEvent& event,
const hidpp::Report& report);
static std::string passkeyEvent(const hidpp::Report& report);
protected:
Receiver(const std::string& path,
const std::shared_ptr<raw::DeviceMonitor>& monitor,
double timeout);
private:
void _receiverCheck();
bool _is_bolt = false;
public:
template <typename... Args>
static std::shared_ptr<Receiver> make(Args... args) {
auto receiver = makeDerived<Receiver>(std::forward<Args>(args)...);
receiver->_receiverCheck();
return receiver;
}
};
}
#endif //LOGID_BACKEND_DJ_RECEIVER_H

View File

@ -1,252 +0,0 @@
/*
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <backend/hidpp10/ReceiverMonitor.h>
#include <backend/Error.h>
#include <util/task.h>
#include <util/log.h>
using namespace logid::backend::hidpp10;
using namespace logid::backend::hidpp;
ReceiverMonitor::ReceiverMonitor(const std::string& path,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout)
: _receiver(Receiver::make(path, monitor, timeout)) {
Receiver::NotificationFlags notification_flags{true, true, true};
_receiver->setNotifications(notification_flags);
}
void ReceiverMonitor::_ready() {
if (_connect_ev_handler.empty()) {
_connect_ev_handler = _receiver->rawDevice()->addEventHandler(
{[](const std::vector<uint8_t>& report) -> bool {
if (report[Offset::Type] == Report::Type::Short ||
report[Offset::Type] == Report::Type::Long) {
uint8_t sub_id = report[Offset::SubID];
return (sub_id == Receiver::DeviceConnection ||
sub_id == Receiver::DeviceDisconnection);
}
return false;
}, [self_weak = _self](const std::vector<uint8_t>& raw) -> void {
/* Running in a new thread prevents deadlocks since the
* receiver may be enumerating.
*/
hidpp::Report report(raw);
if (auto self = self_weak.lock()) {
run_task([self_weak, report]() {
auto self = self_weak.lock();
if (!self)
return;
if (report.subId() == Receiver::DeviceConnection) {
self->_addHandler(Receiver::deviceConnectionEvent(report));
} else if (report.subId() == Receiver::DeviceDisconnection) {
self->_removeHandler(Receiver::deviceDisconnectionEvent(report));
}
});
}
}
});
}
if (_discover_ev_handler.empty()) {
_discover_ev_handler = _receiver->addEventHandler(
{[](const hidpp::Report& report) -> bool {
return (report.subId() == Receiver::DeviceDiscovered) &&
(report.type() == Report::Type::Long);
},
[self_weak = _self](const hidpp::Report& report) {
auto self = self_weak.lock();
if (!self)
return;
std::lock_guard lock(self->_pair_mutex);
if (self->_pair_state == Discovering) {
bool filled = Receiver::fillDeviceDiscoveryEvent(
self->_discovery_event, report);
if (filled) {
self->_pair_state = FindingPasskey;
run_task([self_weak, event = self->_discovery_event]() {
if (auto self = self_weak.lock())
self->receiver()->startBoltPairing(event);
});
}
}
}
});
}
if (_passkey_ev_handler.empty()) {
_passkey_ev_handler = _receiver->addEventHandler(
{[](const hidpp::Report& report) -> bool {
return report.subId() == Receiver::PasskeyRequest &&
report.type() == hidpp::Report::Type::Long;
},
[self_weak = _self](const hidpp::Report& report) {
if (auto self = self_weak.lock()) {
std::lock_guard lock(self->_pair_mutex);
if (self->_pair_state == FindingPasskey) {
auto passkey = Receiver::passkeyEvent(report);
self->_pair_state = Pairing;
self->pairReady(self->_discovery_event, passkey);
}
}
}
});
}
if (_pair_status_handler.empty()) {
_pair_status_handler = _receiver->addEventHandler(
{[](const hidpp::Report& report) -> bool {
return report.subId() == Receiver::DiscoveryStatus ||
report.subId() == Receiver::PairStatus ||
report.subId() == Receiver::BoltPairStatus;
},
[self_weak = _self](const hidpp::Report& report) {
auto self = self_weak.lock();
if (!self)
return;
std::lock_guard lock(self->_pair_mutex);
// TODO: forward status to user
if (report.subId() == Receiver::DiscoveryStatus) {
auto event = Receiver::discoveryStatusEvent(report);
if (self->_pair_state == Discovering && !event.discovering)
self->_pair_state = NotPairing;
} else if (report.subId() == Receiver::PairStatus) {
auto event = Receiver::pairStatusEvent(report);
if ((self->_pair_state == FindingPasskey ||
self->_pair_state == Pairing) && !event.pairing)
self->_pair_state = NotPairing;
} else if (report.subId() == Receiver::BoltPairStatus) {
auto event = Receiver::boltPairStatusEvent(report);
if ((self->_pair_state == FindingPasskey ||
self->_pair_state == Pairing) && !event.pairing)
self->_pair_state = NotPairing;
}
}
});
}
enumerate();
}
void ReceiverMonitor::enumerate() {
_receiver->enumerate();
}
void ReceiverMonitor::waitForDevice(hidpp::DeviceIndex index) {
const std::lock_guard lock(_wait_mutex);
if (!_waiters.count(index)) {
_waiters.emplace(index, _receiver->rawDevice()->addEventHandler(
{[index](const std::vector<uint8_t>& report) -> bool {
/* Connection events should be handled by connect_ev_handler */
auto sub_id = report[Offset::SubID];
return report[Offset::DeviceIndex] == index &&
sub_id != Receiver::DeviceConnection &&
sub_id != Receiver::DeviceDisconnection;
},
[self_weak = _self, index](
[[maybe_unused]] const std::vector<uint8_t>& report) {
hidpp::DeviceConnectionEvent event{};
event.withPayload = false;
event.linkEstablished = true;
event.index = index;
event.fromTimeoutCheck = true;
run_task([self_weak, event]() {
if (auto self = self_weak.lock())
self->_addHandler(event);
});
}
}));
}
}
std::shared_ptr<Receiver> ReceiverMonitor::receiver() const {
return _receiver;
}
void ReceiverMonitor::_startPair(uint8_t timeout) {
{
std::lock_guard lock(_pair_mutex);
_pair_state = _receiver->bolt() ? Discovering : Pairing;
_discovery_event = {};
}
if (_receiver->bolt())
receiver()->startDiscover(timeout);
else
receiver()->startPairing(timeout);
}
void ReceiverMonitor::_stopPair() {
PairState last_state;
{
std::lock_guard lock(_pair_mutex);
last_state = _pair_state;
_pair_state = NotPairing;
}
if (last_state == Discovering)
receiver()->stopDiscover();
else if (last_state == Pairing || last_state == FindingPasskey)
receiver()->stopPairing();
}
void ReceiverMonitor::_addHandler(const hidpp::DeviceConnectionEvent& event, int tries) {
auto device_path = _receiver->devicePath();
try {
addDevice(event);
const std::lock_guard lock(_wait_mutex);
_waiters.erase(event.index);
} catch (DeviceNotReady& e) {
if (tries == max_tries) {
logPrintf(WARN, "Failed to add device %s:%d after %d tries."
"Treating as failure.", device_path.c_str(), event.index, max_tries);
} else {
/* Do exponential backoff for 2^tries * backoff ms. */
std::chrono::milliseconds wait((1 << tries) * ready_backoff);
logPrintf(DEBUG, "Failed to add device %s:%d on try %d, backing off for %dms",
device_path.c_str(), event.index, tries + 1, wait.count());
run_task_after([self_weak = _self, event, tries]() {
if (auto self = self_weak.lock())
self->_addHandler(event, tries + 1);
}, wait);
}
} catch (std::exception& e) {
logPrintf(ERROR, "Failed to add device %d to receiver on %s: %s",
event.index, device_path.c_str(), e.what());
}
}
void ReceiverMonitor::_removeHandler(hidpp::DeviceIndex index) {
try {
removeDevice(index);
} catch (std::exception& e) {
logPrintf(ERROR, "Failed to remove device %d from receiver on %s: %s",
index, _receiver->devicePath().c_str(), e.what());
}
}

View File

@ -1,119 +0,0 @@
/*
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_DJ_RECEIVERMONITOR_H
#define LOGID_BACKEND_DJ_RECEIVERMONITOR_H
#include <backend/hidpp10/Receiver.h>
#include <backend/hidpp/defs.h>
#include <cstdint>
#include <string>
namespace logid::backend::hidpp10 {
template<typename T>
class _receiverMonitorWrapper : public T {
friend class ReceiverMonitor;
public:
template<typename... Args>
explicit _receiverMonitorWrapper(Args... args) : T(std::forward<Args>(args)...) {}
template<typename... Args>
static std::shared_ptr<T> make(Args... args) {
return std::make_shared<_receiverMonitorWrapper>(std::forward<Args>(args)...);
}
};
static constexpr int max_tries = 5;
static constexpr int ready_backoff = 250;
// This class will run on the RawDevice thread,
class ReceiverMonitor {
public:
void enumerate();
ReceiverMonitor(const ReceiverMonitor&) = delete;
ReceiverMonitor(ReceiverMonitor&&) = delete;
protected:
ReceiverMonitor(const std::string& path,
const std::shared_ptr<raw::DeviceMonitor>& monitor,
double timeout);
virtual void addDevice(hidpp::DeviceConnectionEvent event) = 0;
virtual void removeDevice(hidpp::DeviceIndex index) = 0;
virtual void pairReady(const hidpp10::DeviceDiscoveryEvent& event,
const std::string& passkey) = 0;
void _startPair(uint8_t timeout = 0);
void _stopPair();
void waitForDevice(hidpp::DeviceIndex index);
[[nodiscard]] std::shared_ptr<Receiver> receiver() const;
private:
void _ready();
void _addHandler(const hidpp::DeviceConnectionEvent& event, int tries = 0);
void _removeHandler(hidpp::DeviceIndex index);
std::shared_ptr<Receiver> _receiver;
enum PairState {
NotPairing,
Discovering,
FindingPasskey,
Pairing,
};
std::mutex _pair_mutex;
DeviceDiscoveryEvent _discovery_event;
PairState _pair_state = NotPairing;
EventHandlerLock<raw::RawDevice> _connect_ev_handler;
EventHandlerLock<hidpp::Device> _discover_ev_handler;
EventHandlerLock<hidpp::Device> _passkey_ev_handler;
EventHandlerLock<hidpp::Device> _pair_status_handler;
std::weak_ptr<ReceiverMonitor> _self;
std::mutex _wait_mutex;
std::map<hidpp::DeviceIndex, EventHandlerLock<raw::RawDevice>> _waiters;
public:
template<typename T, typename... Args>
static std::shared_ptr<T> make(Args... args) {
auto receiver_monitor = _receiverMonitorWrapper<T>::make(std::forward<Args>(args)...);
receiver_monitor->_self = receiver_monitor;
receiver_monitor->_ready();
return receiver_monitor;
}
};
}
#endif //LOGID_BACKEND_DJ_RECEIVERMONITOR_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -19,15 +19,17 @@
#ifndef LOGID_BACKEND_HIDPP10_DEFS_H
#define LOGID_BACKEND_HIDPP10_DEFS_H
namespace logid::backend::hidpp10 {
enum SubID : uint8_t {
namespace logid {
namespace backend {
namespace 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-2023 PixlOne
* 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
@ -17,35 +17,27 @@
*/
#include <cassert>
#include <backend/hidpp20/Device.h>
#include <backend/Error.h>
#include <backend/hidpp10/Receiver.h>
using namespace logid::backend;
#include "Device.h"
#include "../hidpp/defs.h"
using namespace logid::backend::hidpp20;
Device::Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) :
hidpp::Device(path, index, monitor, timeout) {
Device::Device(std::string path, hidpp::DeviceIndex index)
: hidpp::Device(path, 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(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) {
Device::Device(std::shared_ptr<raw::RawDevice> raw_device, hidpp::DeviceIndex index)
: hidpp::Device(raw_device, index)
{
assert(std::get<0>(version()) >= 2);
}
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);
@ -57,15 +49,16 @@ std::vector<uint8_t> Device::callFunction(uint8_t feature_index,
throw hidpp::Report::InvalidReportID();
hidpp::Report request(type, deviceIndex(), feature_index, function,
hidpp::softwareID);
LOGID_HIDPP_SOFTWARE_ID);
std::copy(params.begin(), params.end(), request.paramBegin());
auto response = this->sendReport(request);
return {response.paramBegin(), response.paramEnd()};
return std::vector<uint8_t>(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);
@ -76,87 +69,9 @@ void Device::callFunctionNoResponse(uint8_t feature_index, uint8_t function,
else
throw hidpp::Report::InvalidReportID();
hidpp::Report request(type, deviceIndex(), feature_index, function, hidpp::softwareID);
hidpp::Report request(type, deviceIndex(), feature_index, function,
LOGID_HIDPP_SOFTWARE_ID);
std::copy(params.begin(), params.end(), request.paramBegin());
this->sendReportNoACK(request);
}
hidpp::Report Device::sendReport(const hidpp::Report& report) {
auto& response_slot = _responses[report.feature() % _responses.size()];
std::unique_lock<std::mutex> response_lock(_response_mutex);
_response_cv.wait(response_lock, [&response_slot]() {
return !response_slot.feature.has_value();
});
response_slot.feature = report.feature();
_sendReport(report);
bool valid = _response_cv.wait_for(
response_lock, io_timeout,
[&response_slot]() {
return response_slot.response.has_value();
});
if (!valid) {
response_slot.reset();
throw TimeoutError();
}
assert(response_slot.response.has_value());
auto response = response_slot.response.value();
response_slot.reset();
if (std::holds_alternative<hidpp::Report>(response)) {
return std::get<hidpp::Report>(response);
} else { // if(std::holds_alternative<Error::ErrorCode>(response))
auto error = std::get<hidpp::Report::Hidpp20Error>(response);
throw Error(error.error_code, error.device_index);
}
}
void Device::sendReportNoACK(const hidpp::Report& report) {
hidpp::Report no_ack_report(report);
no_ack_report.setSwId(hidpp::noAckSoftwareID);
_sendReport(std::move(no_ack_report));
}
bool Device::responseReport(const hidpp::Report& report) {
auto& response_slot = _responses[report.feature() % _responses.size()];
std::lock_guard<std::mutex> lock(_response_mutex);
uint8_t sw_id, feature;
bool is_error = false;
hidpp::Report::Hidpp20Error hidpp20_error{};
if (report.isError20(hidpp20_error)) {
is_error = true;
sw_id = hidpp20_error.software_id;
feature = hidpp20_error.feature_index;
} else {
sw_id = report.swId();
feature = report.feature();
}
if (sw_id != hidpp::softwareID)
return false;
if (!response_slot.feature || response_slot.feature.value() != feature) {
return false;
}
if (is_error) {
response_slot.response = hidpp20_error;
} else {
response_slot.response = report;
}
_response_cv.notify_all();
return true;
}
void Device::ResponseSlot::reset() {
response.reset();
feature.reset();
this->sendReportNoResponse(request);
}

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -16,17 +16,18 @@
*
*/
#include <backend/hidpp20/Error.h>
#include <cassert>
#include "Error.h"
using namespace logid::backend;
using namespace logid::backend::hidpp20;
Error::Error(uint8_t code, hidpp::DeviceIndex index) : _code(code), _index (index) {
Error::Error(uint8_t code) : _code (code)
{
assert(_code != NoError);
}
const char* Error::what() const noexcept {
const char* Error::what() const noexcept
{
switch(_code) {
case NoError:
return "No error";
@ -55,10 +56,7 @@ const char* Error::what() const noexcept {
}
}
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-2023 PixlOne
* 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
@ -19,14 +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::backend::hidpp20 {
namespace logid {
namespace backend {
namespace hidpp20 {
static constexpr uint8_t ErrorID = 0xFF;
class Error : public std::exception {
class Error: public std::exception
{
public:
enum ErrorCode: uint8_t {
NoError = 0,
@ -42,18 +44,14 @@ namespace logid::backend::hidpp20 {
UnknownDevice = 10
};
Error(uint8_t code, hidpp::DeviceIndex index);
explicit Error(uint8_t code);
[[nodiscard]] const char* what() const noexcept override;
[[nodiscard]] uint8_t code() const noexcept;
[[nodiscard]] hidpp::DeviceIndex deviceIndex() const noexcept;
const char* what() const noexcept override;
uint8_t code() const noexcept;
private:
uint8_t _code;
hidpp::DeviceIndex _index;
};
}
}}}
#endif //LOGID_BACKEND_HIDPP20_ERROR_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -16,16 +16,17 @@
*
*/
#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);
@ -36,23 +37,28 @@ std::vector<uint8_t> EssentialFeature::callFunction(uint8_t function_id,
else
throw hidpp::Report::InvalidReportID();
hidpp::Report request(type, _device->deviceIndex(), _index, function_id, hidpp::softwareID);
hidpp::Report request(type, _device->deviceIndex(), _index, function_id,
LOGID_HIDPP_SOFTWARE_ID);
std::copy(params.begin(), params.end(), request.paramBegin());
auto response = _device->sendReport(request);
return {response.paramBegin(), response.paramEnd()};
return std::vector<uint8_t>(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 {
_index = this->callFunction(Root::GetFeature, getFunc_req).at(0);
auto getFunc_resp = this->callFunction(Root::GetFeature,
getFunc_req);
_index = getFunc_resp[0];
} catch(Error& e) {
if(e.code() == Error::InvalidFeatureIndex)
throw UnsupportedFeature(_id);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -25,24 +25,26 @@
* hidpp::Device class. No version checks are provided here
*/
#include <backend/hidpp/Device.h>
#include "Device.h"
namespace logid::backend::hidpp20 {
class EssentialFeature {
namespace logid {
namespace backend {
namespace hidpp20
{
class EssentialFeature
{
public:
static const uint16_t ID;
virtual uint16_t getID() = 0;
protected:
EssentialFeature(hidpp::Device* dev, uint16_t _id);
std::vector<uint8_t> callFunction(uint8_t function_id,
std::vector<uint8_t>& params);
hidpp::Device* const _device;
private:
hidpp::Device* _device;
uint8_t _index;
};
}
}}}
#endif //LOGID_BACKEND_HIDPP20_ESSENTIAL_FEATURE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -16,40 +16,48 @@
*
*/
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/Device.h>
#include <backend/hidpp20/features/Root.h>
#include "Error.h"
#include "Feature.h"
#include "feature_defs.h"
#include "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)
@ -63,6 +71,7 @@ Feature::Feature(Device* dev, uint16_t _id) : _device(dev) {
}
}
uint8_t Feature::featureIndex() const {
uint8_t Feature::featureIndex()
{
return _index;
}

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -21,7 +21,9 @@
#include <cstdint>
namespace logid::backend::hidpp20 {
namespace logid {
namespace backend {
namespace hidpp20 {
struct feature_info {
uint16_t feature_id;
bool obsolete;
@ -29,8 +31,10 @@ namespace logid::backend::hidpp20 {
bool hidden;
};
namespace FeatureID {
enum FeatureID : uint16_t {
namespace FeatureID
{
enum FeatureID : uint16_t
{
ROOT = 0x0000,
FEATURE_SET = 0x0001,
FEATURE_INFO = 0x0002,
@ -74,11 +78,9 @@ namespace logid::backend::hidpp20 {
POINTER_AXES_ORIENTATION = 0x2006,
VERTICAL_SCROLLING = 0x2100,
SMART_SHIFT = 0x2110,
SMART_SHIFT_V2 = 0x2111,
HIRES_SCROLLING = 0x2120,
HIRES_SCROLLING_V2 = 0x2121, // Referred to as Hi-res wheel in cvuchener/hidpp, seems to be V2?
LORES_SCROLLING = 0x2130,
THUMB_WHEEL = 0x2150,
MOUSE_POINTER = 0x2200, // Possibly predecessor to 0x2201?
ADJUSTABLE_DPI = 0x2201,
ANGLE_SNAPPING = 0x2230,
@ -124,6 +126,6 @@ namespace logid::backend::hidpp20 {
};
}
}
}}}
#endif //LOGID_BACKEND_HIDPP20_FEATUREDEFS

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -15,20 +15,23 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <backend/hidpp20/features/AdjustableDPI.h>
#include "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;
@ -51,7 +54,8 @@ 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);
@ -62,7 +66,8 @@ 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);
@ -73,7 +78,8 @@ 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-2023 PixlOne
* 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
@ -18,15 +18,18 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_ADJUSTABLEDPI_H
#define LOGID_BACKEND_HIDPP20_FEATURE_ADJUSTABLEDPI_H
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
#include "../feature_defs.h"
#include "../Feature.h"
namespace logid::backend::hidpp20 {
class AdjustableDPI : public Feature {
namespace logid {
namespace backend {
namespace hidpp20
{
class AdjustableDPI : public Feature
{
public:
static const uint16_t ID = FeatureID::ADJUSTABLE_DPI;
[[nodiscard]] uint16_t getID() final { return ID; }
virtual uint16_t getID() { return ID; }
enum Function {
GetSensorCount = 0,
@ -35,24 +38,23 @@ namespace logid::backend::hidpp20 {
SetSensorDPI = 3
};
explicit AdjustableDPI(Device* dev);
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-2023 PixlOne
* 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
@ -15,15 +15,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <backend/hidpp20/features/ChangeHost.h>
#include <backend/hidpp20/Device.h>
#include "ChangeHost.h"
#include "../Error.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);
@ -38,7 +40,8 @@ ChangeHost::HostInfo ChangeHost::getHostInfo() {
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
@ -48,15 +51,15 @@ void ChangeHost::setHost(uint8_t host) {
getHostInfo();
if(host >= _host_count)
throw Error(hidpp20::Error::InvalidArgument, _device->deviceIndex());
throw hidpp20::Error(hidpp20::Error::InvalidArgument);
std::vector<uint8_t> params = {host};
callFunctionNoResponse(SetCurrentHost, params);
}
[[maybe_unused]]
std::vector<uint8_t> ChangeHost::getCookies() {
std::vector<uint8_t> ChangeHost::getCookies()
{
if(!_host_count)
getHostInfo();
@ -68,8 +71,8 @@ std::vector<uint8_t> ChangeHost::getCookies() {
return response;
}
[[maybe_unused]]
void ChangeHost::setCookie(uint8_t host, uint8_t cookie) {
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-2023 PixlOne
* 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
@ -18,17 +18,20 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_CHANGEHOST_H
#define LOGID_BACKEND_HIDPP20_FEATURE_CHANGEHOST_H
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp20/Feature.h>
#include "../feature_defs.h"
#include "../Feature.h"
namespace logid::backend::hidpp20 {
class ChangeHost : public Feature {
namespace logid {
namespace backend {
namespace hidpp20
{
class ChangeHost : public Feature
{
public:
static const uint16_t ID = FeatureID::CHANGE_HOST;
virtual uint16_t getID() { return ID; }
[[nodiscard]] uint16_t getID() final { return ID; }
explicit ChangeHost(Device* dev);
ChangeHost(Device* dev);
enum Function {
GetHostInfo = 0,
@ -37,23 +40,22 @@ namespace logid::backend::hidpp20 {
SetCookie = 3
};
struct HostInfo {
struct HostInfo
{
uint8_t hostCount;
uint8_t currentHost;
bool enhancedHostSwitch;
};
HostInfo getHostInfo();
void setHost(uint8_t host);
[[maybe_unused]] [[maybe_unused]] std::vector<uint8_t> getCookies();
[[maybe_unused]] [[maybe_unused]] void setCookie(uint8_t host, uint8_t cookie);
std::vector<uint8_t> getCookies();
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-2023 PixlOne
* 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
@ -16,14 +16,27 @@
*
*/
#include <backend/hidpp20/features/DeviceName.h>
#include <cmath>
#include "DeviceName.h"
using namespace logid::backend;
using namespace logid::backend::hidpp20;
namespace {
DeviceName::DeviceName(Device* dev) : Feature(dev, ID)
{
}
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) {
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++;
@ -36,27 +49,36 @@ namespace {
for(std::size_t j = 0; j < hidpp::LongParamLength; j++) {
if(params[0] + j >= length)
return name;
name += (char) name_section[j];
name += name_section[j];
}
}
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 DeviceName::getNameLength() {
uint8_t EssentialDeviceName::getNameLength()
{
std::vector<uint8_t> params(0);
auto response = this->callFunction(DeviceName::Function::GetLength, params);
return response[0];
}
std::string DeviceName::getName() {
std::string EssentialDeviceName::getName()
{
return _getName(getNameLength(), [this]
(std::vector<uint8_t> params)->std::vector<uint8_t> {
return this->callFunction(DeviceName::Function::GetDeviceName, params);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -19,27 +19,43 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_DEVICENAME_H
#define LOGID_BACKEND_HIDPP20_FEATURE_DEVICENAME_H
#include <backend/hidpp20/EssentialFeature.h>
#include <backend/hidpp20/feature_defs.h>
#include "../Feature.h"
#include "../feature_defs.h"
#include "../EssentialFeature.h"
namespace logid::backend::hidpp20 {
class DeviceName : public EssentialFeature {
namespace logid {
namespace backend {
namespace hidpp20
{
class DeviceName : public Feature
{
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
};
[[nodiscard]] uint16_t getID() final { return ID; }
explicit DeviceName(Device* device);
explicit DeviceName(hidpp::Device* device);
[[nodiscard]] uint8_t getNameLength();
[[nodiscard]] std::string getName();
uint8_t getNameLength();
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-2023 PixlOne
* 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
@ -15,21 +15,23 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <backend/hidpp20/features/FeatureSet.h>
#include "FeatureSet.h"
using namespace logid::backend::hidpp20;
[[maybe_unused]]
FeatureSet::FeatureSet(Device* device) : Feature(device, ID) {
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);
@ -39,8 +41,8 @@ uint16_t FeatureSet::getFeature(uint8_t feature_index) {
return feature_id;
}
[[maybe_unused]]
std::map<uint8_t, uint16_t> FeatureSet::getFeatures() {
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++)

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -15,15 +15,17 @@
* 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);
@ -33,36 +35,42 @@ 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);
}
[[maybe_unused]] bool HiresScroll::getRatchetState() {
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.periods = report.paramBegin()[0] & 0x0F;
status.deltaV = (int16_t) (report.paramBegin()[1] << 8 | report.paramBegin()[2]);
status.deltaV = report.paramBegin()[1] << 8 | report.paramBegin()[2];
return status;
}
[[maybe_unused]]
HiresScroll::RatchetState HiresScroll::ratchetSwitchEvent(const hidpp::Report& report) {
assert(report.function() == RatchetSwitch);
HiresScroll::RatchetState HiresScroll::ratchetSwitchEvent(const hidpp::Report
&report)
{
assert(report.function() == WheelMovement);
// Possible bad cast
return static_cast<RatchetState>(report.paramBegin()[0]);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -18,73 +18,76 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H
#define LOGID_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp/Report.h>
#include "../Feature.h"
#include "../feature_defs.h"
namespace logid::backend::hidpp20 {
class HiresScroll : public Feature {
namespace logid {
namespace backend {
namespace 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; }
uint16_t getID() final { return ID; }
enum Function : uint8_t {
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 {
Invertible = 1 << 3,
enum Capability : uint8_t
{
Invertable = 1<<3,
HasRatchet = 1<<2
};
enum Mode : uint8_t {
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;
uint16_t deltaV;
};
explicit HiresScroll(Device* device);
Capabilities getCapabilities();
uint8_t getMode();
void setMode(uint8_t mode);
[[maybe_unused]]
bool getRatchetState();
static WheelStatus wheelMovementEvent(const hidpp::Report& report);
[[maybe_unused]]
static RatchetState ratchetSwitchEvent(const hidpp::Report& report);
};
}
}}}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 PixlOne
* 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
@ -15,58 +15,53 @@
* 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 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 DEFINE_REPROG(x, base) \
x::x(Device* dev) : base(dev, ID) \
{ \
} \
x::x(Device* dev, uint16_t _id) : base(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)
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;
}
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)
// 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;
@ -84,7 +79,8 @@ 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)
return;
@ -97,22 +93,25 @@ 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) {
ReprogControls::ControlInfo ReprogControls::getControlIdInfo(uint16_t cid)
{
if(!_cids_initialized)
initCidMap();
auto it = _cids.find(cid);
if(it == _cids.end())
throw Error(Error::InvalidArgument, _device->deviceIndex());
throw Error(Error::InvalidArgument);
else
return it->second;
}
[[maybe_unused]] ReprogControls::ControlInfo ReprogControls::getControlReporting(uint16_t cid) {
ReprogControls::ControlInfo ReprogControls::getControlReporting(uint16_t cid)
{
// Emulate this function, only Reprog controls v4 supports this
auto info = getControlIdInfo(cid);
@ -121,7 +120,7 @@ ReprogControls::ControlInfo ReprogControls::getControlIdInfo(uint16_t cid) {
report.flags = 0;
if(info.flags & TemporaryDivertable)
report.flags |= TemporaryDiverted;
if (info.flags & PersistentlyDivertable)
if(info.flags & PersisentlyDivertable)
report.flags |= PersistentlyDiverted;
if(info.additionalFlags & RawXY)
report.flags |= RawXYDiverted;
@ -129,14 +128,15 @@ ReprogControls::ControlInfo ReprogControls::getControlIdInfo(uint16_t cid) {
return report;
}
void ReprogControls::setControlReporting(uint16_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;
@ -152,15 +152,19 @@ std::set<uint16_t> ReprogControls::divertedButtonEvent(
}
ReprogControls::Move ReprogControls::divertedRawXYEvent(const hidpp::Report
& report) {
&report)
{
assert(report.function() == DivertedRawXYEvent);
Move move{};
move.x = (int16_t) ((report.paramBegin()[0] << 8) | report.paramBegin()[1]);
move.y = (int16_t) ((report.paramBegin()[2] << 8) | report.paramBegin()[3]);
move.x = report.paramBegin()[1];
move.x |= report.paramBegin()[0] << 8;
move.y = report.paramBegin()[3];
move.y |= report.paramBegin()[2] << 8;
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;
@ -173,7 +177,8 @@ ReprogControls::ControlInfo ReprogControlsV4::getControlReporting(uint16_t cid)
return info;
}
void ReprogControlsV4::setControlReporting(uint16_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-2023 PixlOne
* 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
@ -18,15 +18,17 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H
#define LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp/Report.h>
#include <map>
#include <set>
#include <memory>
namespace logid::backend::hidpp20 {
class ReprogControls : public Feature {
#include "../feature_defs.h"
#include "../Feature.h"
namespace logid {
namespace backend {
namespace hidpp20
{
class ReprogControls : public Feature
{
public:
enum Function {
GetControlCount = 0,
@ -39,7 +41,8 @@ namespace logid::backend::hidpp20 {
DivertedRawXYEvent = 1
};
struct ControlInfo {
struct ControlInfo
{
uint16_t controlID;
uint16_t taskID;
uint8_t flags;
@ -49,14 +52,15 @@ namespace logid::backend::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,
PersistentlyDivertable = 1 << 6,
PersisentlyDivertable = 1<<6,
Virtual = 1<<7
};
enum ControlInfoAdditionalFlags: uint8_t {
@ -67,109 +71,102 @@ namespace logid::backend::hidpp20 {
TemporaryDiverted = 1<<0,
ChangeTemporaryDivert = 1<<1,
PersistentlyDiverted = 1<<2,
ChangePersistentDivert [[maybe_unused]] = 1 << 3,
ChangePersistentDivert = 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; }
[[nodiscard]] uint16_t getID() override { return ID; }
[[nodiscard]] virtual bool supportsRawXY() { return false; }
virtual bool supportsRawXY() { return false; }
explicit ReprogControls(Device* dev);
[[nodiscard]] virtual uint8_t getControlCount();
virtual uint8_t getControlCount();
[[nodiscard]] virtual ControlInfo getControlInfo(uint8_t cid);
virtual ControlInfo getControlInfo(uint8_t cid);
[[nodiscard]] virtual ControlInfo getControlIdInfo(uint16_t cid);
virtual ControlInfo getControlIdInfo(uint16_t cid);
virtual void initCidMap();
[[nodiscard]] const std::map<uint16_t, ControlInfo>& getControls() const;
const std::map<uint16_t, ControlInfo>& getControls() const;
// Only controlId and flags will be set
[[maybe_unused]]
[[nodiscard]] virtual ControlInfo getControlReporting(uint16_t cid);
// Onlu controlId and flags will be set
virtual ControlInfo getControlReporting(uint16_t cid);
// Only controlId (for remap) and flags will be read
virtual void setControlReporting(uint16_t cid, ControlInfo info);
virtual void setControlReporting(uint8_t cid, ControlInfo info);
[[nodiscard]] static std::set<uint16_t> divertedButtonEvent(const hidpp::Report& report);
static std::set<uint16_t> divertedButtonEvent(const hidpp::Report&
report);
[[nodiscard]] static Move divertedRawXYEvent(const hidpp::Report& report);
[[nodiscard]] static std::shared_ptr<ReprogControls> autoVersion(Device* dev);
static Move divertedRawXYEvent(const hidpp::Report& report);
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;
[[nodiscard]] uint16_t getID() override { return ID; }
virtual uint16_t getID() { 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;
[[nodiscard]] uint16_t getID() override { return ID; }
virtual uint16_t getID() { 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;
[[nodiscard]] uint16_t getID() override { return ID; }
virtual uint16_t getID() { 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() { return ID; }
[[nodiscard]] uint16_t getID() final { return ID; }
bool supportsRawXY() override { return true; }
[[nodiscard]] bool supportsRawXY() override { return true; }
ControlInfo getControlReporting(uint16_t cid) override;
[[nodiscard]] ControlInfo getControlReporting(uint16_t cid) override;
void setControlReporting(uint16_t cid, ControlInfo info) 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

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