diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml new file mode 100644 index 0000000..86b6d61 --- /dev/null +++ b/.github/workflows/build-test.yml @@ -0,0 +1,55 @@ +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) diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml new file mode 100644 index 0000000..3249407 --- /dev/null +++ b/.github/workflows/make-release.yml @@ -0,0 +1,35 @@ +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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index c2c32ea..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,37 +0,0 @@ -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/' diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..273d67b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/ipcgull"] + path = src/ipcgull + url = https://github.com/PixlOne/ipcgull.git diff --git a/CMakeLists.txt b/CMakeLists.txt index b147ab7..8d7f83b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.12) set(CMAKE_INSTALL_PREFIX /usr) @@ -8,16 +8,19 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -Wall -Wextra") -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +option(USE_USER_BUS "Uses user bus" OFF) + find_package(Git) -# Set version number -if(GIT_FOUND AND EXISTS ${CMAKE_SOURCE_DIR}/.git) - execute_process(COMMAND ${GIT_EXECUTABLE} describe +# Set version number and update submodules +if(EXISTS ${PROJECT_SOURCE_DIR}/.git AND GIT_FOUND) + execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags 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} @@ -25,10 +28,20 @@ if(GIT_FOUND AND EXISTS ${CMAKE_SOURCE_DIR}/.git) OUTPUT_VARIABLE LOGIOPS_COMMIT_HASH) set(LOGIOPS_VERSION git-${LOGIOPS_COMMIT_HASH}) endif() + + execute_process(COMMAND ${GIT_EXECUTABLE} + submodule update --init --recursive + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) + string(REGEX REPLACE "\n$" "" LOGIOPS_VERSION ${LOGIOPS_VERSION}) -elseif(EXISTS ${CMAKE_SOURCE_DIR}/version.txt) +elseif(EXISTS ${PROJECT_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) @@ -38,4 +51,9 @@ message("LogiOps Version Number: ${LOGIOPS_VERSION}") add_definitions( -DLOGIOPS_VERSION="${LOGIOPS_VERSION}") +if(USE_USER_BUS) + add_definitions(-DUSE_USER_BUS) +endif() + +add_subdirectory(src/ipcgull) add_subdirectory(src/logid) diff --git a/README.md b/README.md index 7aacdf5..cedf017 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# logiops +# LogiOps + +![Build Status](https://github.com/PixlOne/logiops/actions/workflows/build-test.yml/badge.svg) This is an unofficial driver for Logitech mice and keyboard. @@ -13,19 +15,20 @@ Default location for the configuration file is /etc/logid.cfg, but another can b ## Dependencies -This project requires a C++14 compiler, `cmake`, `libevdev`, `libudev`, and `libconfig`. For popular distributions, I've included commands below. +This project requires a C++20 compiler, `cmake`, `libevdev`, `libudev`, `glib`, and `libconfig`. +For popular distributions, I've included commands below. -**Arch Linux:** `sudo pacman -S cmake libevdev libconfig pkgconf` +**Arch Linux:** `sudo pacman -S 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++` +**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-util/cmake virtual/libudev` +**Gentoo Linux:** `sudo emerge dev-libs/libconfig dev-libs/libevdev dev-libs/glib dev-util/cmake virtual/libudev` -**Solus:** `sudo eopkg install libevdev-devel libconfig-devel libgudev-devel` +**Solus:** `sudo eopkg install 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` +**openSUSE:** `sudo zypper install cmake libevdev-devel systemd-devel libconfig-devel gcc-c++ libconfig++-devel libudev-devel glib2-devel` ## Building @@ -34,12 +37,18 @@ To build this project, run: ```bash mkdir build cd build -cmake .. +cmake -DCMAKE_BUILD_TYPE=Release .. make ``` To install, run `sudo make install` after building. You can set the daemon to start at boot by running `sudo systemctl enable logid` or `sudo systemctl enable --now logid` if you want to enable and start the daemon. +## Development + +The project may only run as root, but for development purposes, you may find it +convenient to run as non-root on the user bus. You must compile with the CMake +flag `-DUSE_USER_BUS=ON` to use the user bus. + ## Donate This program is (and will always be) provided free of charge. If you would like to support the development of this project by donating, you can donate to my Ko-Fi below. diff --git a/TESTED.md b/TESTED.md index e29fb7d..9623bc4 100644 --- a/TESTED.md +++ b/TESTED.md @@ -2,18 +2,20 @@ 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 3 | Yes | `Wireless Mouse MX Master 3` | -| MX Master 3 for Business | Yes | `MX Master 3 for Business` | -| 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 ` | -| 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? | Config Name | +| :----------------------: | :---------: | :------------------------------------: | +| MX Master 3 | Yes | `Wireless Mouse MX Master 3` | +| MX Master 3 for Business | Yes | `MX Master 3 for Business` | +| 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` | diff --git a/logid.example.cfg b/logid.example.cfg index e49d7b5..5b64118 100644 --- a/logid.example.cfg +++ b/logid.example.cfg @@ -5,6 +5,7 @@ devices: ( { on: true; threshold: 30; + torque: 50; }; hiresscroll: { diff --git a/src/ipcgull b/src/ipcgull new file mode 160000 index 0000000..4f22a43 --- /dev/null +++ b/src/ipcgull @@ -0,0 +1 @@ +Subproject commit 4f22a43e3380dc1e9c0a490201f4d745390b623f diff --git a/src/logid/CMakeLists.txt b/src/logid/CMakeLists.txt index 00ee796..d41b7c3 100644 --- a/src/logid/CMakeLists.txt +++ b/src/logid/CMakeLists.txt @@ -1,7 +1,9 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.12) project(logid) -set(CMAKE_CXX_STANDARD 14) +# C++20 is only needed for string literal template parameters +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../CMake") @@ -11,6 +13,7 @@ find_package(PkgConfig REQUIRED) add_executable(logid logid.cpp util/log.cpp + config/config.cpp InputDevice.cpp DeviceManager.cpp Device.cpp @@ -31,6 +34,7 @@ add_executable(logid actions/ChangeDPI.cpp actions/GestureAction.cpp actions/ChangeHostAction.cpp + actions/ChangeProfile.cpp actions/gesture/Gesture.cpp actions/gesture/ReleaseGesture.cpp actions/gesture/ThresholdGesture.cpp @@ -40,9 +44,9 @@ add_executable(logid backend/Error.cpp backend/raw/DeviceMonitor.cpp backend/raw/RawDevice.cpp - backend/dj/Receiver.cpp - backend/dj/ReceiverMonitor.cpp - backend/dj/Error.cpp + backend/raw/IOMonitor.cpp + backend/hidpp10/Receiver.cpp + backend/hidpp10/ReceiverMonitor.cpp backend/hidpp/Device.cpp backend/hidpp/Report.cpp backend/hidpp10/Error.cpp @@ -62,44 +66,58 @@ add_executable(logid backend/hidpp20/features/ChangeHost.cpp backend/hidpp20/features/WirelessDeviceStatus.cpp backend/hidpp20/features/ThumbWheel.cpp - backend/dj/Report.cpp - util/mutex_queue.h - util/workqueue.cpp - util/worker_thread.cpp util/task.cpp - util/thread.cpp util/ExceptionHandler.cpp) set_target_properties(logid PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) pkg_check_modules(PC_EVDEV libevdev REQUIRED) +pkg_check_modules(DBUS "dbus-1") pkg_check_modules(SYSTEMD "systemd") pkg_check_modules(LIBCONFIG libconfig REQUIRED) pkg_check_modules(LIBUDEV libudev REQUIRED) find_path(EVDEV_INCLUDE_DIR libevdev/libevdev.h - HINTS ${PC_EVDEV_INCLUDE_DIRS} ${PC_EVDEV_INCLUDEDIR}) + HINTS ${PC_EVDEV_INCLUDE_DIRS} ${PC_EVDEV_INCLUDEDIR}) find_library(EVDEV_LIBRARY NAMES evdev libevdev) -include_directories(${EVDEV_INCLUDE_DIR} ${LIBUDEV_INCLUDE_DIRECTORIES}) +set(IPCGULL_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/../ipcgull/src/include) +message(${IPCGULL_INCLUDE_DIRS}) + +include_directories(. ${EVDEV_INCLUDE_DIR} ${LIBUDEV_INCLUDE_DIRECTORIES} ${IPCGULL_INCLUDE_DIRS}) target_link_libraries(logid ${CMAKE_THREAD_LIBS_INIT} ${EVDEV_LIBRARY} config++ - ${LIBUDEV_LIBRARIES}) + ${LIBUDEV_LIBRARIES} ipcgull) install(TARGETS logid DESTINATION bin) -if (SYSTEMD_FOUND AND "${SYSTEMD_SERVICES_INSTALL_DIR}" STREQUAL "") - execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} - --variable=systemdsystemunitdir systemd - OUTPUT_VARIABLE SYSTEMD_SERVICES_INSTALL_DIR) - string(REGEX REPLACE "[ \t\n]+" "" SYSTEMD_SERVICES_INSTALL_DIR - "${SYSTEMD_SERVICES_INSTALL_DIR}") - configure_file(logid.service.cmake ${CMAKE_BINARY_DIR}/logid.service) +if (SYSTEMD_FOUND) + if ("${SYSTEMD_SERVICES_INSTALL_DIR}" STREQUAL "") + execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} + --variable=systemdsystemunitdir systemd + OUTPUT_VARIABLE SYSTEMD_SERVICES_INSTALL_DIR) + string(REGEX REPLACE "[ \t\n]+" "" SYSTEMD_SERVICES_INSTALL_DIR + "${SYSTEMD_SERVICES_INSTALL_DIR}") + endif () + + # Install systemd service + configure_file(logid.service.in ${CMAKE_BINARY_DIR}/logid.service) message(STATUS "systemd units will be installed at ${SYSTEMD_SERVICES_INSTALL_DIR}") install(FILES ${CMAKE_BINARY_DIR}/logid.service DESTINATION ${SYSTEMD_SERVICES_INSTALL_DIR} COMPONENT cp) -elseif(NOT SYSTEMD_FOUND AND SYSTEMD_SERVICES_INSTALL_DIR) +elseif (NOT SYSTEMD_FOUND AND SYSTEMD_SERVICES_INSTALL_DIR) message(FATAL_ERROR "systemd is not found w/ pkg-config but SYSTEMD_SERVICES_INSTALL_DIR is defined.") -endif() +endif () + +if(DBUS_FOUND) + # Install DBus conf + # TODO: Is there a better way of setting the system policy directory? + set(DBUS_SYSTEM_POLICY_INSTALL_DIR "/usr/share/dbus-1/system.d") + configure_file(logiops-dbus.conf.in ${CMAKE_BINARY_DIR}/pizza.pixl.LogiOps.conf) + message(STATUS "dbus system policy will be installed at ${DBUS_SYSTEM_POLICY_INSTALL_DIR}") + install(FILES ${CMAKE_BINARY_DIR}/pizza.pixl.LogiOps.conf + DESTINATION ${DBUS_SYSTEM_POLICY_INSTALL_DIR} + COMPONENT cp) +endif() \ No newline at end of file diff --git a/src/logid/Configuration.cpp b/src/logid/Configuration.cpp index a5fad09..e49b5ea 100644 --- a/src/logid/Configuration.cpp +++ b/src/logid/Configuration.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,163 +16,61 @@ * */ +#include +#include #include -#include -#include - -#include "Configuration.h" -#include "util/log.h" +#include +#include using namespace logid; using namespace libconfig; -using namespace std::chrono; +using namespace logid::config; -Configuration::Configuration(const std::string& config_file) -{ - try { - _config.readFile(config_file.c_str()); - } catch(const FileIOException &e) { - logPrintf(ERROR, "I/O Error while reading %s: %s", config_file.c_str(), - e.what()); - throw e; - } catch(const ParseException &e) { - logPrintf(ERROR, "Parse error in %s, line %d: %s", e.getFile(), - e.getLine(), e.getError()); - throw e; - } - - const Setting &root = _config.getRoot(); - - try { - auto& worker_count = root["workers"]; - if(worker_count.getType() == Setting::TypeInt) { - _worker_threads = worker_count; - if(_worker_threads < 0) - logPrintf(WARN, "Line %d: workers cannot be negative.", - worker_count.getSourceLine()); - } else { - logPrintf(WARN, "Line %d: workers must be an integer.", - worker_count.getSourceLine()); - } - } catch(const SettingNotFoundException& e) { - // Ignore - } - - try { - auto& timeout = root["io_timeout"]; - if(timeout.isNumber()) { - if(timeout.getType() == Setting::TypeFloat) - _io_timeout = duration_cast( - duration(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 +Configuration::Configuration(std::string config_file) : + _config_file(std::move(config_file)) { + if (std::filesystem::exists(_config_file)) { try { - auto& ignore = root.lookup("blacklist"); - if(ignore.getType() == libconfig::Setting::TypeInt) { - _ignore_list.insert((int)ignore); - } else if(ignore.isList() || ignore.isArray()) { - int ignore_count = ignore.getLength(); - for(int i = 0; i < ignore_count; i++) { - if(ignore[i].getType() != libconfig::Setting::TypeInt) { - logPrintf(WARN, "Line %d: blacklist must refer to " - "device PIDs", - ignore[i].getSourceLine()); - if(ignore.isArray()) - break; - } else - _ignore_list.insert((int)ignore[i]); - } - } - } catch(const SettingNotFoundException& e) { - // Ignore + _config.readFile(_config_file.c_str()); + } catch (const FileIOException& e) { + logPrintf(ERROR, "I/O Error while reading %s: %s", _config_file.c_str(), + e.what()); + throw; + } catch (const ParseException& e) { + logPrintf(ERROR, "Parse error in %s, line %d: %s", e.getFile(), + e.getLine(), e.getError()); + throw; } + + Config::operator=(get(_config.getRoot())); + } else { + logPrintf(INFO, "Config file does not exist, using empty config."); + } + + if (!devices.has_value()) + devices.emplace(); +} + +Configuration::Configuration() { + devices.emplace(); +} + +void Configuration::save() { + config::set(_config.getRoot(), *this); + try { + _config.writeFile(_config_file.c_str()); + } catch (const FileIOException& e) { + logPrintf(ERROR, "I/O Error while writing %s: %s", + _config_file.c_str(), e.what()); + throw; + } catch (const std::exception& e) { + logPrintf(ERROR, "Error while writing %s: %s", + _config_file.c_str(), e.what()); + throw; } } -libconfig::Setting& Configuration::getSetting(const std::string& path) -{ - return _config.lookup(path); -} - -std::string Configuration::getDevice(const std::string& name) -{ - auto it = _device_paths.find(name); - if(it == _device_paths.end()) - throw DeviceNotFound(name); - else - return it->second; -} - -bool Configuration::isIgnored(uint16_t pid) const -{ - return _ignore_list.find(pid) != _ignore_list.end(); -} - -Configuration::DeviceNotFound::DeviceNotFound(std::string name) : - _name (std::move(name)) -{ -} - -const char * Configuration::DeviceNotFound::what() const noexcept -{ - return _name.c_str(); -} - -int Configuration::workerCount() const -{ - return _worker_threads; -} - -std::chrono::milliseconds Configuration::ioTimeout() const -{ - return _io_timeout; +Configuration::IPC::IPC(Configuration* config) : + ipcgull::interface(SERVICE_ROOT_NAME ".Config", { + {"Save", {config, &Configuration::save}} + }, {}, {}) { } diff --git a/src/logid/Configuration.h b/src/logid/Configuration.h index 03eb62d..3a6a1e6 100644 --- a/src/logid/Configuration.h +++ b/src/logid/Configuration.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,46 +19,40 @@ #ifndef LOGID_CONFIGURATION_H #define LOGID_CONFIGURATION_H -#include +#include +#include #include #include #include #include -#define LOGID_DEFAULT_IO_TIMEOUT std::chrono::seconds(2) -#define LOGID_DEFAULT_WORKER_COUNT 4 +namespace logid { + namespace defaults { + static constexpr double io_timeout = 500; + static constexpr int workers = 4; + static constexpr int gesture_threshold = 50; + } -namespace logid -{ - class Configuration - { + class Configuration : public config::Config { public: - explicit Configuration(const std::string& config_file); - Configuration() = default; - libconfig::Setting& getSetting(const std::string& path); - std::string getDevice(const std::string& name); - bool isIgnored(uint16_t pid) const; + explicit Configuration(std::string config_file); - class DeviceNotFound : public std::exception - { + Configuration(); + + // Reloading is not safe, references will be invalidated + //void reload(); + void save(); + + class IPC : public ipcgull::interface { public: - explicit DeviceNotFound(std::string name); - const char* what() const noexcept override; - private: - std::string _name; + explicit IPC(Configuration* config); }; - std::chrono::milliseconds ioTimeout() const; - int workerCount() const; private: - std::map _device_paths; - std::set _ignore_list; - std::chrono::milliseconds _io_timeout = LOGID_DEFAULT_IO_TIMEOUT; - int _worker_threads = LOGID_DEFAULT_WORKER_COUNT; + std::string _config_file; libconfig::Config _config; }; - extern std::shared_ptr global_config; } #endif //LOGID_CONFIGURATION_H diff --git a/src/logid/Device.cpp b/src/logid/Device.cpp index 6f34d6b..73ecfee 100644 --- a/src/logid/Device.cpp +++ b/src/logid/Device.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,47 +16,138 @@ * */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include "util/log.h" -#include "features/DPI.h" -#include "Device.h" -#include "Receiver.h" -#include "features/SmartShift.h" -#include "features/RemapButton.h" -#include "backend/hidpp20/features/Reset.h" -#include "features/HiresScroll.h" -#include "features/DeviceStatus.h" -#include "features/ThumbWheel.h" +#include +#include using namespace logid; using namespace logid::backend; -Device::Device(std::string path, backend::hidpp::DeviceIndex index) : - _hidpp20 (path, index), _path (std::move(path)), _index (index), - _config (global_config, this), _receiver (nullptr) -{ +DeviceNickname::DeviceNickname(const std::shared_ptr& 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 lock(manager->_nick_lock); + manager->_device_nicknames.erase(_nickname); + } +} + +namespace logid { + class DeviceWrapper : public Device { + public: + template + explicit DeviceWrapper(Args... args) : Device(std::forward(args)...) {} + }; +} + +std::shared_ptr Device::make( + std::string path, backend::hidpp::DeviceIndex index, + std::shared_ptr manager) { + auto ret = std::make_shared(std::move(path), + index, + std::move(manager)); + ret->_self = ret; + ret->_ipc_node->manage(ret); + ret->_ipc_interface = ret->_ipc_node->make_interface(ret.get()); + return ret; +} + +std::shared_ptr Device::make( + std::shared_ptr raw_device, + backend::hidpp::DeviceIndex index, + std::shared_ptr manager) { + auto ret = std::make_shared(std::move(raw_device), + index, + std::move(manager)); + ret->_self = ret; + ret->_ipc_node->manage(ret); + ret->_ipc_interface = ret->_ipc_node->make_interface(ret.get()); + return ret; +} + +std::shared_ptr Device::make( + Receiver* receiver, backend::hidpp::DeviceIndex index, + std::shared_ptr manager) { + auto ret = std::make_shared(receiver, index, std::move(manager)); + ret->_self = ret; + ret->_ipc_node->manage(ret); + ret->_ipc_interface = ret->_ipc_node->make_interface(ret.get()); + return ret; +} + +Device::Device(std::string path, backend::hidpp::DeviceIndex index, + const std::shared_ptr& manager) : + _hidpp20(hidpp20::Device::make(path, index, manager, + manager->config()->io_timeout.value_or( + defaults::io_timeout))), + _path(std::move(path)), _index(index), + _config(_getConfig(manager, _hidpp20->name())), + _profile_name(ipcgull::property_readable, ""), + _manager(manager), + _nickname(manager), + _ipc_node(manager->devicesNode()->make_child(_nickname)), + _awake(ipcgull::property_readable, true) { _init(); } -Device::Device(const std::shared_ptr& raw_device, - hidpp::DeviceIndex index) : _hidpp20(raw_device, index), _path - (raw_device->hidrawPath()), _index (index), - _config (global_config, this), _receiver (nullptr) -{ +Device::Device(std::shared_ptr raw_device, + hidpp::DeviceIndex index, const std::shared_ptr& manager) : + _hidpp20(hidpp20::Device::make( + std::move(raw_device), index, + manager->config()->io_timeout.value_or(defaults::io_timeout))), + _path(raw_device->rawPath()), _index(index), + _config(_getConfig(manager, _hidpp20->name())), + _profile_name(ipcgull::property_readable, ""), + _manager(manager), + _nickname(manager), + _ipc_node(manager->devicesNode()->make_child(_nickname)), + _awake(ipcgull::property_readable, true) { _init(); } -Device::Device(Receiver* receiver, hidpp::DeviceIndex index) : _hidpp20 - (receiver->rawReceiver(), index), _path (receiver->path()), _index (index), - _config (global_config, this), _receiver (receiver) -{ +Device::Device(Receiver* receiver, hidpp::DeviceIndex index, + const std::shared_ptr& 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); + 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("dpi"); _addFeature("smartshift"); @@ -68,85 +159,207 @@ void Device::_init() _makeResetMechanism(); reset(); - for(auto& feature: _features) { + for (auto& feature: _features) { feature.second->configure(); feature.second->listen(); } - - _hidpp20.listen(); } -std::string Device::name() -{ - return _hidpp20.name(); +std::string Device::name() { + return _hidpp20->name(); } -uint16_t Device::pid() -{ - return _hidpp20.pid(); +uint16_t Device::pid() { + return _hidpp20->pid(); } -void Device::sleep() -{ - logPrintf(INFO, "%s:%d fell asleep.", _path.c_str(), _index); +void Device::sleep() { + std::lock_guard lock(_state_lock); + if (_awake) { + logPrintf(INFO, "%s:%d fell asleep.", _path.c_str(), _index); + _awake = false; + _ipc_interface->notifyStatus(); + } } -void Device::wakeup() -{ +void Device::wakeup() { + std::lock_guard lock(_state_lock); logPrintf(INFO, "%s:%d woke up.", _path.c_str(), _index); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + reconfigure(); + + if (!_awake) { + _awake = true; + _ipc_interface->notifyStatus(); + } +} + +void Device::reconfigure() { reset(); - for(auto& feature: _features) + for (auto& feature: _features) feature.second->configure(); } -void Device::reset() -{ - if(_reset_mechanism) +void Device::reset() { + if (_reset_mechanism) (*_reset_mechanism)(); else logPrintf(DEBUG, "%s:%d tried to reset, but no reset mechanism was " "available.", _path.c_str(), _index); } -DeviceConfig& Device::config() -{ - return _config; +std::shared_ptr Device::virtualInput() const { + if (auto manager = _manager.lock()) { + return manager->virtualInput(); + } else { + logPrintf(ERROR, "Device manager lost"); + logPrintf(ERROR, + "Fatal error occurred, file a bug report," + " the program will now exit."); + std::terminate(); + } } -hidpp20::Device& Device::hidpp20() -{ - return _hidpp20; +std::shared_ptr Device::ipcNode() const { + return _ipc_node; } -void Device::_makeResetMechanism() -{ +std::vector Device::getProfiles() const { + std::shared_lock lock(_profile_mutex); + + std::vector ret; + for (auto& profile : _config.profiles) { + ret.push_back(profile.first); + } + + return ret; +} + +void Device::setProfile(const std::string& profile) { + std::unique_lock lock(_profile_mutex); + + _profile = _config.profiles.find(profile); + if (_profile == _config.profiles.end()) + _profile = _config.profiles.insert({profile, {}}).first; + _profile_name = profile; + + for (auto& feature : _features) + feature.second->setProfile(_profile->second); + + reconfigure(); +} + +void Device::setProfileDelayed(const std::string& profile) { + run_task([self_weak = _self, profile](){ + if (auto self = self_weak.lock()) + self->setProfile(profile); + }); +} + +void Device::removeProfile(const std::string& profile) { + std::unique_lock lock(_profile_mutex); + + if (profile == (std::string)_profile_name) + throw std::invalid_argument("cannot remove active profile"); + else if (profile == (std::string)_config.default_profile) + throw std::invalid_argument("cannot remove default profile"); + + _config.profiles.erase(profile); +} + +void Device::clearProfile(const std::string& profile) { + std::unique_lock lock(_profile_mutex); + + if (profile == (std::string)_profile_name) { + _profile->second = config::Profile(); + + for (auto& feature : _features) + feature.second->setProfile(_profile->second); + + reconfigure(); + } else { + auto it = _config.profiles.find(profile); + if (it != _config.profiles.end()) { + it->second = config::Profile(); + } else { + throw std::invalid_argument("unknown profile"); + } + } +} + +config::Profile& Device::activeProfile() { + std::shared_lock lock(_profile_mutex); + return _profile->second; +} + +hidpp20::Device& Device::hidpp20() { + return *_hidpp20; +} + +void Device::_makeResetMechanism() { try { - hidpp20::Reset reset(&_hidpp20); + hidpp20::Reset reset(_hidpp20.get()); _reset_mechanism = std::make_unique>( - [dev=&this->_hidpp20]{ - hidpp20::Reset reset(dev); - reset.reset(reset.getProfile()); + [dev = _hidpp20] { + hidpp20::Reset reset(dev.get()); + reset.reset(reset.getProfile()); }); - } catch(hidpp20::UnsupportedFeature& e) { + } catch (hidpp20::UnsupportedFeature& e) { // Reset unsupported, ignore. } } -DeviceConfig::DeviceConfig(const std::shared_ptr& config, Device* - device) : _device (device), _config (config) -{ - try { - _root_setting = config->getDevice(device->name()); - } catch(Configuration::DeviceNotFound& e) { - logPrintf(INFO, "Device %s not configured, using default config.", - device->name().c_str()); - } +Device::IPC::IPC(Device* device) : + ipcgull::interface( + SERVICE_ROOT_NAME ".Device", + { + {"GetProfiles", {device, &Device::getProfiles, {"profiles"}}}, + {"SetProfile", {device, &Device::setProfile, {"profile"}}}, + {"RemoveProfile", {device, &Device::removeProfile, {"profile"}}}, + {"ClearProfile", {device, &Device::clearProfile, {"profile"}}} + }, + { + {"Name", ipcgull::property( + ipcgull::property_readable, device->name())}, + {"ProductID", ipcgull::property( + ipcgull::property_readable, device->pid())}, + {"Active", device->_awake}, + {"DefaultProfile", device->_config.default_profile}, + {"ActiveProfile", device->_profile_name} + }, { + {"StatusChanged", ipcgull::signal::make_signal({"active"})} + }), _device(*device) { } -libconfig::Setting& DeviceConfig::getSetting(const std::string& path) -{ - return _config->getSetting(_root_setting + '/' + path); +void Device::IPC::notifyStatus() const { + emit_signal("StatusChanged", (bool) (_device._awake)); +} + +config::Device& Device::_getConfig( + const std::shared_ptr& manager, + const std::string& name) { + static std::mutex config_mutex; + std::lock_guard 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(device)) { + config::Device d; + d.profiles["default"] = std::get(device); + d.default_profile = "default"; + device = std::move(d); + } + + auto& conf = std::get(device); + if (conf.profiles.empty()) { + conf.profiles["default"] = {}; + conf.default_profile = "default"; + } + + return conf; } diff --git a/src/logid/Device.h b/src/logid/Device.h index f1ddd1f..99da6dc 100644 --- a/src/logid/Device.h +++ b/src/logid/Device.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,90 +19,168 @@ #ifndef LOGID_DEVICE_H #define LOGID_DEVICE_H -#include "backend/hidpp/defs.h" -#include "backend/hidpp20/Device.h" -#include "features/DeviceFeature.h" -#include "Configuration.h" -#include "util/log.h" +#include +#include +#include +#include +#include +#include + +namespace logid { + class DeviceManager; -namespace logid -{ class Device; + class Receiver; - class DeviceConfig - { + class InputDevice; + + class DeviceNickname { public: - DeviceConfig(const std::shared_ptr& config, Device* - device); - libconfig::Setting& getSetting(const std::string& path); + explicit DeviceNickname(const std::shared_ptr& manager); + + DeviceNickname() = delete; + + DeviceNickname(const DeviceNickname&) = delete; + + ~DeviceNickname(); + + operator std::string() const; + private: - Device* _device; - std::string _root_setting; - std::shared_ptr _config; + const int _nickname; + const std::weak_ptr _manager; }; /* TODO: Implement HID++ 1.0 support * Currently, the logid::Device class has a hardcoded requirement * for an HID++ 2.0 device. */ - class Device - { + class Device : public ipcgull::object { public: - Device(std::string path, backend::hidpp::DeviceIndex index); - Device(const std::shared_ptr& raw_device, - backend::hidpp::DeviceIndex index); - Device(Receiver* receiver, backend::hidpp::DeviceIndex index); - std::string name(); + uint16_t pid(); - DeviceConfig& config(); + [[nodiscard]] config::Profile& activeProfile(); + + [[nodiscard]] std::vector getProfiles() const; + + void setProfile(const std::string& profile); + + void setProfileDelayed(const std::string& profile); + + void removeProfile(const std::string& profile); + + void clearProfile(const std::string& profile); + backend::hidpp20::Device& hidpp20(); + static std::shared_ptr make( + std::string path, + backend::hidpp::DeviceIndex index, + std::shared_ptr manager); + + static std::shared_ptr make( + std::shared_ptr raw_device, + backend::hidpp::DeviceIndex index, + std::shared_ptr manager); + + static std::shared_ptr make( + Receiver* receiver, + backend::hidpp::DeviceIndex index, + std::shared_ptr manager); + void wakeup(); + void sleep(); + void reconfigure(); + void reset(); + [[nodiscard]] std::shared_ptr virtualInput() const; + + [[nodiscard]] std::shared_ptr ipcNode() const; + template - std::shared_ptr getFeature(std::string name) { + std::shared_ptr getFeature(const std::string& name) { auto it = _features.find(name); - if(it == _features.end()) + if (it == _features.end()) return nullptr; try { return std::dynamic_pointer_cast(it->second); - } catch(std::bad_cast& e) { - logPrintf(ERROR, "bad_cast while getting device feature %s: %s", - name.c_str(), e.what()); + } catch (std::bad_cast& e) { return nullptr; } } + Device(const Device&) = delete; + + Device(Device&&) = delete; + private: + friend class DeviceWrapper; + + Device(std::string path, backend::hidpp::DeviceIndex index, + const std::shared_ptr& manager); + + Device(std::shared_ptr raw_device, + backend::hidpp::DeviceIndex index, + const std::shared_ptr& manager); + + Device(Receiver* receiver, backend::hidpp::DeviceIndex index, + const std::shared_ptr& manager); + + static config::Device& _getConfig( + const std::shared_ptr& manager, + const std::string& name); + void _init(); /* Adds a feature without calling an error if unsupported */ template - void _addFeature(std::string name) - { + void _addFeature(std::string name) { try { - _features.emplace(name, std::make_shared(this)); + _features.emplace(name, features::DeviceFeature::make(this)); } catch (features::UnsupportedFeature& e) { } } - backend::hidpp20::Device _hidpp20; + std::shared_ptr _hidpp20; std::string _path; backend::hidpp::DeviceIndex _index; - std::map> - _features; - DeviceConfig _config; + std::map> _features; - Receiver* _receiver; + config::Device& _config; + mutable std::shared_mutex _profile_mutex; + ipcgull::property _profile_name; + std::map::iterator _profile; + + const std::weak_ptr _manager; void _makeResetMechanism(); + std::unique_ptr> _reset_mechanism; + + const DeviceNickname _nickname; + std::shared_ptr _ipc_node; + + class IPC : public ipcgull::interface { + private: + Device& _device; + public: + explicit IPC(Device* device); + + void notifyStatus() const; + }; + + ipcgull::property _awake; + std::mutex _state_lock; + + std::weak_ptr _self; + + std::shared_ptr _ipc_interface; }; } diff --git a/src/logid/DeviceManager.cpp b/src/logid/DeviceManager.cpp index 924c499..71cb924 100644 --- a/src/logid/DeviceManager.cpp +++ b/src/logid/DeviceManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,95 +16,305 @@ * */ +#include +#include +#include #include #include - -#include "DeviceManager.h" -#include "Receiver.h" -#include "util/log.h" -#include "backend/hidpp10/Error.h" -#include "backend/Error.h" +#include +#include using namespace logid; using namespace logid::backend; -void DeviceManager::addDevice(std::string path) -{ +DeviceManager::DeviceManager(std::shared_ptr config, + std::shared_ptr virtual_input, + std::shared_ptr 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(this); + _ipc_receivers = _root_node->make_interface(this); + _ipc_config = _root_node->make_interface(_config.get()); + _device_node->add_server(_server); + _receiver_node->add_server(_server); + _root_node->add_server(_server); +} + +std::shared_ptr DeviceManager::config() const { + return _config; +} + +std::shared_ptr DeviceManager::virtualInput() const { + return _virtual_input; +} + +std::shared_ptr DeviceManager::devicesNode() const { + return _device_node; +} + +std::shared_ptr DeviceManager::receiversNode() const { + return _receiver_node; +} + +void DeviceManager::addDevice(std::string path) { bool defaultExists = true; bool isReceiver = false; // Check if device is ignored before continuing { - raw::RawDevice raw_dev(path); - if(global_config->isIgnored(raw_dev.productId())) { + raw::RawDevice raw_dev(path, self().lock()); + if (config()->ignore.has_value() && + config()->ignore.value().contains(raw_dev.productId())) { logPrintf(DEBUG, "%s: Device 0x%04x ignored.", - path.c_str(), raw_dev.productId()); + path.c_str(), raw_dev.productId()); return; } } try { - hidpp::Device device(path, hidpp::DefaultDevice); - isReceiver = device.version() == std::make_tuple(1, 0); - } catch(hidpp10::Error &e) { - if(e.code() != hidpp10::Error::UnknownDevice) - throw; - } catch(hidpp::Device::InvalidDevice &e) { // Ignore + auto device = hidpp::Device::make( + path, hidpp::DefaultDevice, self().lock(), + config()->io_timeout.value_or(defaults::io_timeout)); + isReceiver = device->version() == std::make_tuple(1, 0); + } catch (hidpp20::Error& e) { + if (e.code() != hidpp20::Error::UnknownDevice) + throw DeviceNotReady(); defaultExists = false; - } catch(std::system_error &e) { - logPrintf(WARN, "I/O error on %s: %s, skipping device.", - path.c_str(), e.what()); + } catch (hidpp10::Error& e) { + if (e.code() != hidpp10::Error::UnknownDevice) + throw DeviceNotReady(); + defaultExists = false; + } catch (hidpp::Device::InvalidDevice& e) { + if (e.code() == hidpp::Device::InvalidDevice::VirtualNode) { + logPrintf(DEBUG, "Ignoring virtual node on %s", path.c_str()); + } else if (e.code() == hidpp::Device::InvalidDevice::Asleep) { + /* May be a valid device, wait */ + throw DeviceNotReady(); + } + return; - } catch (TimeoutError &e) { - logPrintf(WARN, "Device %s timed out.", path.c_str()); - defaultExists = false; + } catch (std::system_error& e) { + logPrintf(WARN, "I/O error on %s: %s, skipping device.", path.c_str(), e.what()); + return; + } catch (TimeoutError& e) { + /* Ready and valid non-default devices should throw an UnknownDevice error */ + throw DeviceNotReady(); } - if(isReceiver) { + if (isReceiver) { logPrintf(INFO, "Detected receiver at %s", path.c_str()); - auto receiver = std::make_shared(path); - receiver->run(); + auto receiver = Receiver::make(path, self().lock()); + std::lock_guard lock(_map_lock); _receivers.emplace(path, receiver); + _ipc_receivers->receiverAdded(receiver); } else { - /* TODO: Can non-receivers only contain 1 device? - * If the device exists, it is guaranteed to be an HID++ 2.0 device */ - if(defaultExists) { - auto device = std::make_shared(path, hidpp::DefaultDevice); - _devices.emplace(path, device); + /* TODO: Can non-receivers only contain 1 device? + * If the device exists, it is guaranteed to be an HID++ 2.0 device */ + if (defaultExists) { + auto device = Device::make(path, hidpp::DefaultDevice, self().lock()); + std::lock_guard lock(_map_lock); + _devices.emplace(path, device); + _ipc_devices->deviceAdded(device); } else { try { - auto device = std::make_shared(path, - hidpp::CordedDevice); + auto device = Device::make(path, hidpp::CordedDevice, self().lock()); + std::lock_guard lock(_map_lock); _devices.emplace(path, device); - } catch(hidpp10::Error &e) { - if(e.code() != hidpp10::Error::UnknownDevice) - throw; - else - logPrintf(WARN, - "HID++ 1.0 error while trying to initialize %s:" - "%s", path.c_str(), e.what()); - } catch(hidpp::Device::InvalidDevice &e) { // Ignore - } catch(std::system_error &e) { + _ipc_devices->deviceAdded(device); + } catch (hidpp10::Error& e) { + if (e.code() != hidpp10::Error::UnknownDevice) + throw DeviceNotReady(); + } catch (hidpp20::Error& e) { + if (e.code() != hidpp20::Error::UnknownDevice) + throw DeviceNotReady(); + } catch (hidpp::Device::InvalidDevice& e) { + if (e.code() == hidpp::Device::InvalidDevice::Asleep) + throw DeviceNotReady(); + } catch (std::system_error& e) { // This error should have been thrown previously - logPrintf(WARN, "I/O error on %s: %s", path.c_str(), - e.what()); + logPrintf(WARN, "I/O error on %s: %s", path.c_str(), e.what()); + } catch (TimeoutError& e) { + throw DeviceNotReady(); } } } } -void DeviceManager::removeDevice(std::string path) -{ +void DeviceManager::addExternalDevice(const std::shared_ptr& d) { + _ipc_devices->deviceAdded(d); +} + +void DeviceManager::removeExternalDevice(const std::shared_ptr& d) { + _ipc_devices->deviceRemoved(d); +} + +std::mutex& DeviceManager::mutex() const { + return _map_lock; +} + +void DeviceManager::removeDevice(std::string path) { + std::lock_guard lock(_map_lock); auto receiver = _receivers.find(path); - if(receiver != _receivers.end()) { + if (receiver != _receivers.end()) { + _ipc_receivers->receiverRemoved(receiver->second); _receivers.erase(receiver); logPrintf(INFO, "Receiver on %s disconnected", path.c_str()); } else { auto device = _devices.find(path); - if(device != _devices.end()) { + if (device != _devices.end()) { + _ipc_devices->deviceRemoved(device->second); _devices.erase(device); logPrintf(INFO, "Device on %s disconnected", path.c_str()); } } } + +DeviceManager::DevicesIPC::DevicesIPC(DeviceManager* manager) : + ipcgull::interface( + SERVICE_ROOT_NAME ".Devices", + { + {"Enumerate", {manager, &DeviceManager::listDevices, {"devices"}}} + }, + {}, + { + {"DeviceAdded", + ipcgull::make_signal>( + {"device"})}, + {"DeviceRemoved", + ipcgull::make_signal>( + {"device"})} + }) { +} + +std::vector> DeviceManager::listDevices() const { + std::lock_guard lock(_map_lock); + std::vector> 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> DeviceManager::listReceivers() const { + std::lock_guard lock(_map_lock); + std::vector> receivers; + for (auto& x: _receivers) + receivers.emplace_back(x.second); + return receivers; +} + +void DeviceManager::DevicesIPC::deviceAdded( + const std::shared_ptr& d) { + emit_signal("DeviceAdded", d); +} + +void DeviceManager::DevicesIPC::deviceRemoved( + const std::shared_ptr& 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>( + {"receiver"})}, + {"ReceiverRemoved", + ipcgull::make_signal>( + {"receiver"})} + }) { +} + +void DeviceManager::ReceiversIPC::receiverAdded( + const std::shared_ptr& r) { + emit_signal("ReceiverAdded", r); +} + +void DeviceManager::ReceiversIPC::receiverRemoved( + const std::shared_ptr& r) { + emit_signal("ReceiverRemoved", r); +} + +int DeviceManager::newDeviceNickname() { + std::lock_guard 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 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; +} diff --git a/src/logid/DeviceManager.h b/src/logid/DeviceManager.h index 6305963..77105da 100644 --- a/src/logid/DeviceManager.h +++ b/src/logid/DeviceManager.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,32 +19,98 @@ #ifndef LOGID_DEVICEMANAGER_H #define LOGID_DEVICEMANAGER_H -#include -#include -#include +#include +#include +#include +#include +#include -#include "backend/raw/DeviceMonitor.h" -#include "backend/hidpp/Device.h" -#include "Device.h" -#include "Receiver.h" +namespace logid { + class InputDevice; -namespace logid -{ - - class DeviceManager : public backend::raw::DeviceMonitor - { + class DeviceManager : public backend::raw::DeviceMonitor { public: - DeviceManager() = default; + + [[nodiscard]] std::shared_ptr config() const; + + [[nodiscard]] std::shared_ptr virtualInput() const; + + [[nodiscard]] std::shared_ptr devicesNode() const; + + [[nodiscard]] std::shared_ptr + receiversNode() const; + + void addExternalDevice(const std::shared_ptr& d); + + void removeExternalDevice(const std::shared_ptr& d); + + std::mutex& mutex() const; + protected: - void addDevice(std::string path) override; - void removeDevice(std::string path) override; + DeviceManager(std::shared_ptr config, + std::shared_ptr virtual_input, + std::shared_ptr server); + + void addDevice(std::string path) final; + + void removeDevice(std::string path) final; + private: + class DevicesIPC : public ipcgull::interface { + public: + explicit DevicesIPC(DeviceManager* manager); + + void deviceAdded(const std::shared_ptr& d); + + void deviceRemoved(const std::shared_ptr& d); + }; + + [[nodiscard]] + std::vector> listDevices() const; + + class ReceiversIPC : public ipcgull::interface { + public: + explicit ReceiversIPC(DeviceManager* manager); + + void receiverAdded(const std::shared_ptr& r); + + void receiverRemoved(const std::shared_ptr& r); + }; + + [[nodiscard]] + std::vector> listReceivers() const; + + std::shared_ptr _server; + std::shared_ptr _config; + std::shared_ptr _virtual_input; + + std::shared_ptr _root_node; + + std::shared_ptr _device_node; + std::shared_ptr _receiver_node; + + std::shared_ptr _ipc_config; + std::shared_ptr _ipc_devices; + std::shared_ptr _ipc_receivers; std::map> _devices; std::map> _receivers; + + mutable std::mutex _map_lock; + + friend class DeviceNickname; + + friend class ReceiverNickname; + + [[nodiscard]] int newDeviceNickname(); + + [[nodiscard]] int newReceiverNickname(); + + std::mutex _nick_lock; + std::set _device_nicknames; + std::set _receiver_nicknames; }; - extern std::unique_ptr device_manager; } -#endif //LOGID_DEVICEMANAGER_H \ No newline at end of file +#endif //LOGID_DEVICEMANAGER_H diff --git a/src/logid/InputDevice.cpp b/src/logid/InputDevice.cpp index 8bc93dc..9d3169c 100644 --- a/src/logid/InputDevice.cpp +++ b/src/logid/InputDevice.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,9 +16,9 @@ * */ +#include #include - -#include "InputDevice.h" +#include extern "C" { @@ -29,17 +29,18 @@ extern "C" using namespace logid; InputDevice::InvalidEventCode::InvalidEventCode(const std::string& name) : - _what ("Invalid event code " + name) -{ + _what("Invalid event code " + name) { } -const char* InputDevice::InvalidEventCode::what() const noexcept -{ +InputDevice::InvalidEventCode::InvalidEventCode(uint code) : + _what("Invalid event code " + std::to_string(code)) { +} + +const char* InputDevice::InvalidEventCode::what() const noexcept { return _what.c_str(); } -InputDevice::InputDevice(const char* name) -{ +InputDevice::InputDevice(const char* name) { device = libevdev_new(); libevdev_set_name(device, name); @@ -56,27 +57,28 @@ InputDevice::InputDevice(const char* name) } } + for (bool& axis: registered_axis) + axis = false; + libevdev_enable_event_type(device, EV_REL); int err = libevdev_uinput_create_from_device(device, - LIBEVDEV_UINPUT_OPEN_MANAGED, &ui_device); + LIBEVDEV_UINPUT_OPEN_MANAGED, &ui_device); - if(err != 0) { + if (err != 0) { libevdev_free(device); throw std::system_error(-err, std::generic_category()); } } -InputDevice::~InputDevice() -{ +InputDevice::~InputDevice() { libevdev_uinput_destroy(ui_device); libevdev_free(device); } -void InputDevice::registerKey(uint code) -{ +void InputDevice::registerKey(uint code) { // TODO: Maybe print error message, if wrong code is passed? - if(registered_keys[code] || code > KEY_CNT) { + if (code >= KEY_CNT || registered_keys[code]) { return; } @@ -85,10 +87,9 @@ void InputDevice::registerKey(uint code) registered_keys[code] = true; } -void InputDevice::registerAxis(uint axis) -{ +void InputDevice::registerAxis(uint axis) { // TODO: Maybe print error message, if wrong code is passed? - if(registered_axis[axis] || axis > REL_CNT) { + if (axis >= REL_CNT || registered_axis[axis]) { return; } @@ -97,67 +98,77 @@ void InputDevice::registerAxis(uint axis) registered_axis[axis] = true; } -void InputDevice::moveAxis(uint axis, int movement) -{ +void InputDevice::moveAxis(uint axis, int movement) { _sendEvent(EV_REL, axis, movement); } -void InputDevice::pressKey(uint code) -{ +void InputDevice::pressKey(uint code) { _sendEvent(EV_KEY, code, 1); } -void InputDevice::releaseKey(uint code) -{ +void InputDevice::releaseKey(uint code) { _sendEvent(EV_KEY, code, 0); } -uint InputDevice::toKeyCode(const std::string& name) -{ +std::string InputDevice::toKeyName(uint code) { + return _toEventName(EV_KEY, code); +} + +uint InputDevice::toKeyCode(const std::string& name) { return _toEventCode(EV_KEY, name); } -uint InputDevice::toAxisCode(const std::string& name) -{ +std::string InputDevice::toAxisName(uint code) { + return _toEventName(EV_REL, code); +} + +uint InputDevice::toAxisCode(const std::string& name) { return _toEventCode(EV_REL, name); } /* Returns -1 if axis_code is not hi-res */ -int InputDevice::getLowResAxis(const uint axis_code) -{ +int InputDevice::getLowResAxis(const uint axis_code) { /* Some systems don't have these hi-res axes */ #ifdef REL_WHEEL_HI_RES - if(axis_code == REL_WHEEL_HI_RES) + if (axis_code == REL_WHEEL_HI_RES) return REL_WHEEL; #endif #ifdef REL_HWHEEL_HI_RES - if(axis_code == REL_HWHEEL_HI_RES) + if (axis_code == REL_HWHEEL_HI_RES) return REL_HWHEEL; #endif return -1; } -uint InputDevice::_toEventCode(uint type, const std::string& name) -{ +std::string InputDevice::_toEventName(uint type, uint code) { + const char* ret = libevdev_event_code_get_name(type, code); + + if (!ret) + throw InvalidEventCode(code); + + return {ret}; +} + +uint InputDevice::_toEventCode(uint type, const std::string& name) { int code = libevdev_event_code_from_name(type, name.c_str()); - if(code == -1) + if (code == -1) throw InvalidEventCode(name); return code; } -void InputDevice::_enableEvent(const uint type, const uint code) -{ +void InputDevice::_enableEvent(const uint type, const uint code) { + std::unique_lock lock(_input_mutex); libevdev_uinput_destroy(ui_device); libevdev_enable_event_code(device, type, code, nullptr); int err = libevdev_uinput_create_from_device(device, - LIBEVDEV_UINPUT_OPEN_MANAGED, &ui_device); + LIBEVDEV_UINPUT_OPEN_MANAGED, &ui_device); - if(err != 0) { + if (err != 0) { libevdev_free(device); device = nullptr; ui_device = nullptr; @@ -165,8 +176,8 @@ void InputDevice::_enableEvent(const uint type, const uint code) } } -void InputDevice::_sendEvent(uint type, uint code, int value) -{ +void InputDevice::_sendEvent(uint type, uint code, int value) { + std::unique_lock lock(_input_mutex); libevdev_uinput_write_event(ui_device, type, code, value); libevdev_uinput_write_event(ui_device, EV_SYN, SYN_REPORT, 0); } diff --git a/src/logid/InputDevice.h b/src/logid/InputDevice.h index c3c803d..939d59f 100644 --- a/src/logid/InputDevice.h +++ b/src/logid/InputDevice.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,6 +21,7 @@ #include #include +#include extern "C" { @@ -28,45 +29,61 @@ extern "C" #include } -namespace logid -{ - class InputDevice - { +namespace logid { + class InputDevice { public: - class InvalidEventCode : public std::exception - { + class InvalidEventCode : public std::exception { public: explicit InvalidEventCode(const std::string& name); + + explicit InvalidEventCode(uint code); + const char* what() const noexcept override; + private: const std::string _what; }; - explicit InputDevice(const char *name); + + explicit InputDevice(const char* name); + ~InputDevice(); void registerKey(uint code); + void registerAxis(uint axis); + void moveAxis(uint axis, int movement); + void pressKey(uint code); + void releaseKey(uint code); + static std::string toKeyName(uint code); + static uint toKeyCode(const std::string& name); + + static std::string toAxisName(uint code); + static uint toAxisCode(const std::string& name); + static int getLowResAxis(uint axis_code); private: void _sendEvent(uint type, uint code, int value); + void _enableEvent(uint type, uint name); + static std::string _toEventName(uint type, uint code); + static uint _toEventCode(uint type, const std::string& name); - bool registered_keys[KEY_CNT]; - bool registered_axis[REL_CNT]; + bool registered_keys[KEY_CNT]{}; + bool registered_axis[REL_CNT]{}; libevdev* device; libevdev_uinput* ui_device{}; - }; - extern std::unique_ptr virtual_input; + std::mutex _input_mutex; + }; } #endif //LOGID_INPUTDEVICE_H diff --git a/src/logid/Receiver.cpp b/src/logid/Receiver.cpp index 49c053d..692e1ba 100644 --- a/src/logid/Receiver.cpp +++ b/src/logid/Receiver.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,85 +16,219 @@ * */ -#include "Receiver.h" -#include "util/log.h" -#include "backend/hidpp10/Error.h" -#include "backend/hidpp20/Error.h" -#include "backend/Error.h" +#include +#include +#include +#include +#include using namespace logid; using namespace logid::backend; -Receiver::Receiver(const std::string& path) : - dj::ReceiverMonitor(path), _path (path) -{ +ReceiverNickname::ReceiverNickname( + const std::shared_ptr& manager) : + _nickname(manager->newReceiverNickname()), _manager(manager) { } -void Receiver::addDevice(hidpp::DeviceConnectionEvent event) -{ +ReceiverNickname::operator std::string() const { + return std::to_string(_nickname); +} + +ReceiverNickname::~ReceiverNickname() { + if (auto manager = _manager.lock()) { + std::lock_guard lock(manager->_nick_lock); + manager->_receiver_nicknames.erase(_nickname); + } +} + +std::shared_ptr Receiver::make( + const std::string& path, + const std::shared_ptr& manager) { + auto ret = ReceiverMonitor::make(path, manager); + ret->_ipc_node->manage(ret); + return ret; +} + + +Receiver::Receiver(const std::string& path, + const std::shared_ptr& 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(this)) { +} + +const Receiver::DeviceList& Receiver::devices() const { + return _devices; +} + +Receiver::~Receiver() noexcept { + if (auto manager = _manager.lock()) { + for (auto& d: _devices) + manager->removeExternalDevice(d.second); + } +} + +void Receiver::addDevice(hidpp::DeviceConnectionEvent event) { std::unique_lock lock(_devices_change); + + auto manager = _manager.lock(); + if (!manager) { + logPrintf(ERROR, "Orphan Receiver, missing DeviceManager"); + logPrintf(ERROR, "Fatal error, file a bug report. Program will now exit."); + std::terminate(); + } + try { // Check if device is ignored before continuing - if(global_config->isIgnored(event.pid)) { + if (manager->config()->ignore.value_or(std::set()).contains(event.pid)) { logPrintf(DEBUG, "%s:%d: Device 0x%04x ignored.", _path.c_str(), event.index, event.pid); return; } auto dev = _devices.find(event.index); - if(dev != _devices.end()) { - if(event.linkEstablished) + if (dev != _devices.end()) { + if (event.linkEstablished) dev->second->wakeup(); else dev->second->sleep(); return; } - if(!event.linkEstablished) + if (!event.linkEstablished) return; - hidpp::Device hidpp_device(receiver(), event); + auto hidpp_device = hidpp::Device::make( + receiver(), event, manager->config()->io_timeout.value_or(defaults::io_timeout)); - auto version = hidpp_device.version(); + auto version = hidpp_device->version(); - if(std::get<0>(version) < 2) { + if (std::get<0>(version) < 2) { logPrintf(INFO, "Unsupported HID++ 1.0 device on %s:%d connected.", - _path.c_str(), event.index); + _path.c_str(), event.index); return; } - std::shared_ptr device = std::make_shared(this, - event.index); + hidpp_device.reset(); + auto device = Device::make(this, event.index, manager); + std::lock_guard manager_lock(manager->mutex()); _devices.emplace(event.index, device); + manager->addExternalDevice(device); - } catch(hidpp10::Error &e) { - logPrintf(ERROR, - "Caught HID++ 1.0 error while trying to initialize " - "%s:%d: %s", _path.c_str(), event.index, e.what()); - } catch(hidpp20::Error &e) { + } catch (hidpp10::Error& e) { + logPrintf(ERROR, "Caught HID++ 1.0 error while trying to initialize %s:%d: %s", + _path.c_str(), event.index, e.what()); + } catch (hidpp20::Error& e) { logPrintf(ERROR, "Caught HID++ 2.0 error while trying to initialize " - "%s:%d: %s", _path.c_str(), event.index, e.what()); - } catch(TimeoutError &e) { - if(!event.fromTimeoutCheck) + "%s:%d: %s", _path.c_str(), event.index, e.what()); + } catch (TimeoutError& e) { + if (!event.fromTimeoutCheck) logPrintf(DEBUG, "%s:%d timed out, waiting for input from device to" " initialize.", _path.c_str(), event.index); waitForDevice(event.index); } } -void Receiver::removeDevice(hidpp::DeviceIndex index) -{ +void Receiver::removeDevice(hidpp::DeviceIndex index) { std::unique_lock lock(_devices_change); - _devices.erase(index); + std::unique_lock manager_lock; + if (auto manager = _manager.lock()) + manager_lock = std::unique_lock(manager->mutex()); + auto device = _devices.find(index); + if (device != _devices.end()) { + if (auto manager = _manager.lock()) + manager->removeExternalDevice(device->second); + _devices.erase(device); + } } -const std::string& Receiver::path() const -{ +void Receiver::pairReady(const hidpp10::DeviceDiscoveryEvent& event, + const std::string& passkey) { + std::string type; + switch (event.deviceType) { + case hidpp::DeviceUnknown: + type = "unknown"; + break; + case hidpp::DeviceKeyboard: + type = "keyboard"; + break; + case hidpp::DeviceMouse: + type = "mouse"; + break; + case hidpp::DeviceNumpad: + type = "numpad"; + break; + case hidpp::DevicePresenter: + type = "presenter"; + break; + case hidpp::DeviceTouchpad: + type = "touchpad"; + break; + case hidpp::DeviceTrackball: + type = "trackball"; + break; + } + _ipc_interface->emit_signal("PairReady", event.name, event.pid, type, passkey); +} + +const std::string& Receiver::path() const { return _path; } -std::shared_ptr Receiver::rawReceiver() -{ +std::shared_ptr Receiver::rawReceiver() { return receiver(); -} \ No newline at end of file +} + +std::vector> Receiver::pairedDevices() const { + std::vector> ret; + for (int i = hidpp::WirelessDevice1; i <= hidpp::WirelessDevice6; ++i) { + try { + auto index(static_cast(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(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(ipcgull::property_readable, + receiver->receiver()->bolt())} + }, { + {"PairReady", + ipcgull::signal::make_signal( + {"name", "pid", "type", "passkey"}) + } + }) { +} diff --git a/src/logid/Receiver.h b/src/logid/Receiver.h index e055008..89e7e68 100644 --- a/src/logid/Receiver.h +++ b/src/logid/Receiver.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,24 +20,80 @@ #define LOGID_RECEIVER_H #include -#include "backend/dj/ReceiverMonitor.h" -#include "Device.h" +#include +#include -namespace logid -{ - class Receiver : public backend::dj::ReceiverMonitor - { +namespace logid { + class ReceiverNickname { public: - explicit Receiver(const std::string& path); - const std::string& path() const; - std::shared_ptr rawReceiver(); + explicit ReceiverNickname(const std::shared_ptr& manager); + + ReceiverNickname() = delete; + + ReceiverNickname(const ReceiverNickname&) = delete; + + ~ReceiverNickname(); + + operator std::string() const; + + private: + const int _nickname; + const std::weak_ptr _manager; + }; + + class Receiver : public backend::hidpp10::ReceiverMonitor, + public ipcgull::object { + public: + typedef std::map> + DeviceList; + + ~Receiver() noexcept override; + + static std::shared_ptr make( + const std::string& path, + const std::shared_ptr& manager); + + [[nodiscard]] const std::string& path() const; + + std::shared_ptr rawReceiver(); + + [[nodiscard]] const DeviceList& devices() const; + + [[nodiscard]] std::vector> + pairedDevices() const; + + void startPair(uint8_t timeout); + + void stopPair(); + + void unpair(int device); + protected: + Receiver(const std::string& path, + const std::shared_ptr& manager); + void addDevice(backend::hidpp::DeviceConnectionEvent event) override; + void removeDevice(backend::hidpp::DeviceIndex index) override; + + void pairReady(const backend::hidpp10::DeviceDiscoveryEvent& event, + const std::string& passkey) override; + private: std::mutex _devices_change; - std::map> _devices; + DeviceList _devices; std::string _path; + std::weak_ptr _manager; + + const ReceiverNickname _nickname; + std::shared_ptr _ipc_node; + + class IPC : public ipcgull::interface { + public: + explicit IPC(Receiver* receiver); + }; + + std::shared_ptr _ipc_interface; }; } diff --git a/src/logid/actions/Action.cpp b/src/logid/actions/Action.cpp index ecac552..317a2dc 100644 --- a/src/logid/actions/Action.cpp +++ b/src/logid/actions/Action.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,64 +16,128 @@ * */ -#include -#include "Action.h" -#include "../util/log.h" -#include "KeypressAction.h" -#include "ToggleSmartShift.h" -#include "ToggleHiresScroll.h" -#include "GestureAction.h" -#include "NullAction.h" -#include "CycleDPI.h" -#include "ChangeDPI.h" -#include "ChangeHostAction.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include using namespace logid; using namespace logid::actions; -std::shared_ptr Action::makeAction(Device *device, libconfig::Setting - &setting) -{ - if(!setting.isGroup()) { - logPrintf(WARN, "Line %d: Action is not a group, ignoring.", - setting.getSourceLine()); - throw InvalidAction(); +namespace logid::actions { + template + struct action_type { + typedef typename T::action type; + }; + + template + struct action_type : action_type { + }; + + template + struct action_type : action_type { + }; + + template + std::shared_ptr _makeAction( + Device* device, T& action, + const std::shared_ptr& parent) { + return parent->make_interface::type>( + device, std::forward(action), parent); } - try { - auto& action_type = setting.lookup("type"); - - if(action_type.getType() != libconfig::Setting::TypeString) { - logPrintf(WARN, "Line %d: Action type must be a string", - action_type.getSourceLine()); - throw InvalidAction(); + template + std::shared_ptr _makeAction( + Device* device, const std::string& name, + std::optional& config, + const std::shared_ptr& parent) { + if (name == ChangeDPI::interface_name) { + config = config::ChangeDPI(); + } else if (name == CycleDPI::interface_name) { + config = config::CycleDPI(); + } else if (name == KeypressAction::interface_name) { + config = config::KeypressAction(); + } else if (name == NullAction::interface_name) { + config = config::NoAction(); + } else if (name == ChangeHostAction::interface_name) { + config = config::ChangeHost(); + } else if (name == ToggleHiresScroll::interface_name) { + config = config::ToggleHiresScroll(); + } else if (name == ToggleSmartShift::interface_name) { + config = config::ToggleSmartShift(); + } else if (name == ChangeProfile::interface_name) { + config = config::ChangeProfile(); + } else if (name == "Default") { + config.reset(); + return nullptr; + } else { + throw InvalidAction(name); } - std::string type = action_type; - std::transform(type.begin(), type.end(), type.begin(), ::tolower); - - if(type == "keypress") - return std::make_shared(device, setting); - else if(type == "togglesmartshift") - return std::make_shared(device); - else if(type == "togglehiresscroll") - return std::make_shared(device); - else if(type == "gestures") - return std::make_shared(device, setting); - else if(type == "cycledpi") - return std::make_shared(device, setting); - else if(type == "changedpi") - return std::make_shared(device, setting); - else if(type == "none") - return std::make_shared(device); - else if(type == "changehost") - return std::make_shared(device, setting); - else - throw InvalidAction(type); - - } catch(libconfig::SettingNotFoundException& e) { - logPrintf(WARN, "Line %d: Action type is missing, ignoring.", - setting.getSourceLine()); - throw InvalidAction(); + return Action::makeAction(device, config.value(), parent); } -} \ No newline at end of file +} + +std::shared_ptr Action::makeAction( + Device* device, const std::string& name, + std::optional& config, + const std::shared_ptr& parent) { + auto ret = _makeAction(device, name, config, parent); + if (ret) + ret->_self = ret; + return ret; +} + +std::shared_ptr Action::makeAction( + Device* device, const std::string& name, + std::optional& config, + const std::shared_ptr& parent) { + try { + auto ret = _makeAction(device, name, config, parent); + if (ret) + ret->_self = ret; + return ret; + } catch (actions::InvalidAction& e) { + if (name == GestureAction::interface_name) { + config = config::GestureAction(); + return makeAction(device, config.value(), parent); + } + throw; + } +} + +std::shared_ptr Action::makeAction( + Device* device, config::BasicAction& action, + const std::shared_ptr& parent) { + std::shared_ptr ret; + std::visit([&device, &ret, &parent](auto&& x) { + ret = _makeAction(device, x, parent); + }, action); + if (ret) + ret->_self = ret; + return ret; +} + +std::shared_ptr Action::makeAction( + Device* device, config::Action& action, + const std::shared_ptr& parent) { + std::shared_ptr ret; + std::visit([&device, &ret, &parent](auto&& x) { + ret = _makeAction(device, x, parent); + }, action); + if (ret) + ret->_self = ret; + return ret; +} + +Action::Action(Device* device, const std::string& name, tables t) : + ipcgull::interface(SERVICE_ROOT_NAME ".Action." + name, std::move(t)), + _device(device), _pressed(false) { +} diff --git a/src/logid/actions/Action.h b/src/logid/actions/Action.h index 46024ee..f00979b 100644 --- a/src/logid/actions/Action.h +++ b/src/logid/actions/Action.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,67 +19,81 @@ #define LOGID_ACTION_H #include -#include #include +#include +#include +#include +#include +#include namespace logid { class Device; -namespace actions { - class InvalidAction : public std::exception - { +} + +namespace logid::actions { + class InvalidAction : public std::exception { public: - InvalidAction() - { - } - explicit InvalidAction(std::string& action) : _action (action) - { - } - const char* what() const noexcept override - { + InvalidAction() = default; + + InvalidAction(std::string action) : _action(std::move(action)) {} + + [[nodiscard]] const char* what() const noexcept override { return _action.c_str(); } + private: std::string _action; }; - class Action - { + class Action : public ipcgull::interface { public: - static std::shared_ptr makeAction(Device* device, - libconfig::Setting& setting); + static std::shared_ptr makeAction( + Device* device, const std::string& name, + std::optional& config, + const std::shared_ptr& parent); + + static std::shared_ptr makeAction( + Device* device, const std::string& name, + std::optional& config, + const std::shared_ptr& parent); + + static std::shared_ptr makeAction( + Device* device, config::BasicAction& action, + const std::shared_ptr& parent); + + static std::shared_ptr makeAction( + Device* device, config::Action& action, + const std::shared_ptr& parent); virtual void press() = 0; - virtual void release() = 0; - virtual void move(int16_t x, int16_t y) - { - // Suppress unused warning - (void)x; (void)y; - } - virtual bool pressed() - { + virtual void release() = 0; + + virtual void move([[maybe_unused]] int16_t x, [[maybe_unused]] int16_t y) { } + + virtual bool pressed() { return _pressed; } - virtual uint8_t reprogFlags() const = 0; + [[nodiscard]] virtual uint8_t reprogFlags() const = 0; virtual ~Action() = default; - class Config - { - protected: - explicit Config(Device* device) : _device (device) - { - } - Device* _device; - }; protected: - explicit Action(Device* device) : _device (device), _pressed (false) - { - } + Action(Device* device, const std::string& name, tables t = {}); + Device* _device; std::atomic _pressed; + mutable std::shared_mutex _config_mutex; + + template + [[nodiscard]] std::weak_ptr self() const { + return std::dynamic_pointer_cast(_self.lock()); + } + + private: + std::weak_ptr _self; }; -}} +} #endif //LOGID_ACTION_H diff --git a/src/logid/actions/ChangeDPI.cpp b/src/logid/actions/ChangeDPI.cpp index faab6b9..4ac58c0 100644 --- a/src/logid/actions/ChangeDPI.cpp +++ b/src/logid/actions/ChangeDPI.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,95 +15,80 @@ * along with this program. If not, see . * */ -#include "ChangeDPI.h" -#include "../Device.h" -#include "../util/task.h" -#include "../util/log.h" -#include "../backend/hidpp20/Error.h" -#include "../backend/hidpp20/features/ReprogControls.h" +#include +#include +#include +#include +#include using namespace logid::actions; -ChangeDPI::ChangeDPI(Device *device, libconfig::Setting &setting) : - Action(device), _config(device, setting) -{ +const char* ChangeDPI::interface_name = "ChangeDPI"; + +ChangeDPI::ChangeDPI( + Device* device, config::ChangeDPI& config, + [[maybe_unused]] const std::shared_ptr& parent) : + Action(device, interface_name, { + { + {"GetConfig", {this, &ChangeDPI::getConfig, {"change", "sensor"}}}, + {"SetChange", {this, &ChangeDPI::setChange, {"change"}}}, + {"SetSensor", {this, &ChangeDPI::setSensor, {"sensor", "reset"}}}, + }, + {}, + {}}), _config(config) { _dpi = _device->getFeature("dpi"); - if(!_dpi) - logPrintf(WARN, "%s:%d: DPI feature not found, cannot use " - "ChangeDPI action.", + if (!_dpi) + logPrintf(WARN, "%s:%d: DPI feature not found, cannot use ChangeDPI action.", _device->hidpp20().devicePath().c_str(), _device->hidpp20().deviceIndex()); } -void ChangeDPI::press() -{ +void ChangeDPI::press() { _pressed = true; - if(_dpi) { - task::spawn([this]{ - try { - uint16_t last_dpi = _dpi->getDPI(_config.sensor()); - _dpi->setDPI(last_dpi + _config.interval(), _config.sensor()); - } catch (backend::hidpp20::Error& e) { - if(e.code() == backend::hidpp20::Error::InvalidArgument) - logPrintf(WARN, "%s:%d: Could not get/set DPI for sensor " - "%d", - _device->hidpp20().devicePath().c_str(), - _device->hidpp20().deviceIndex(), - _config.sensor()); - else - throw e; + std::shared_lock lock(_config_mutex); + if (_dpi && _config.inc.has_value()) { + run_task([self_weak = self(), + sensor = _config.sensor.value_or(0), inc = _config.inc.value()] { + if (auto self = self_weak.lock()) { + try { + uint16_t last_dpi = self->_dpi->getDPI(sensor); + self->_dpi->setDPI(last_dpi + inc, sensor); + } catch (backend::hidpp20::Error& e) { + if (e.code() == backend::hidpp20::Error::InvalidArgument) + logPrintf(WARN, "%s:%d: Could not get/set DPI for sensor %d", + self->_device->hidpp20().devicePath().c_str(), + self->_device->hidpp20().deviceIndex(), sensor); + else + throw e; + } } }); } } -void ChangeDPI::release() -{ +void ChangeDPI::release() { _pressed = false; } -uint8_t ChangeDPI::reprogFlags() const -{ +uint8_t ChangeDPI::reprogFlags() const { return backend::hidpp20::ReprogControls::TemporaryDiverted; } -ChangeDPI::Config::Config(Device *device, libconfig::Setting &config) : - Action::Config(device), _interval (0), _sensor (0) -{ - if(!config.isGroup()) { - logPrintf(WARN, "Line %d: action must be an object, skipping.", - config.getSourceLine()); - return; - } - - try { - auto& inc = config.lookup("inc"); - if(inc.getType() != libconfig::Setting::TypeInt) - logPrintf(WARN, "Line %d: inc must be an integer", - inc.getSourceLine()); - _interval = (int)inc; - } catch(libconfig::SettingNotFoundException& e) { - logPrintf(WARN, "Line %d: inc is a required field, skipping.", - config.getSourceLine()); - } - - try { - auto& sensor = config.lookup("sensor"); - if(sensor.getType() != libconfig::Setting::TypeInt) - logPrintf(WARN, "Line %d: sensor must be an integer", - sensor.getSourceLine()); - _sensor = (int)sensor; - } catch(libconfig::SettingNotFoundException& e) { - // Ignore - } +std::tuple ChangeDPI::getConfig() const { + std::shared_lock lock(_config_mutex); + return {_config.inc.value_or(0), _config.sensor.value_or(0)}; } -uint16_t ChangeDPI::Config::interval() const -{ - return _interval; +void ChangeDPI::setChange(int16_t change) { + std::unique_lock lock(_config_mutex); + _config.inc = change; } -uint8_t ChangeDPI::Config::sensor() const -{ - return _sensor; -} \ No newline at end of file +void ChangeDPI::setSensor(uint8_t sensor, bool reset) { + std::unique_lock lock(_config_mutex); + if (reset) { + _config.sensor.reset(); + } else { + _config.sensor = sensor; + } +} diff --git a/src/logid/actions/ChangeDPI.h b/src/logid/actions/ChangeDPI.h index 4d9e04f..2a3a23f 100644 --- a/src/logid/actions/ChangeDPI.h +++ b/src/logid/actions/ChangeDPI.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,37 +18,33 @@ #ifndef LOGID_ACTION_CHANGEDPI_H #define LOGID_ACTION_CHANGEDPI_H -#include -#include "Action.h" -#include "../features/DPI.h" +#include +#include -namespace logid { - namespace actions { - class ChangeDPI : public Action - { - public: - explicit ChangeDPI(Device* device, libconfig::Setting& setting); +namespace logid::actions { + class ChangeDPI : public Action { + public: + static const char* interface_name; - virtual void press(); - virtual void release(); + ChangeDPI(Device* device, config::ChangeDPI& setting, + const std::shared_ptr& parent); - virtual uint8_t reprogFlags() const; + void press() final; - class Config : public Action::Config - { - public: - Config(Device* device, libconfig::Setting& setting); - uint16_t interval() const; - uint8_t sensor() const; - private: - uint16_t _interval; - uint8_t _sensor; - }; + void release() final; - protected: - Config _config; - std::shared_ptr _dpi; - }; - }} + [[nodiscard]] std::tuple getConfig() const; + + void setChange(int16_t change); + + void setSensor(uint8_t sensor, bool reset); + + [[nodiscard]] uint8_t reprogFlags() const final; + + protected: + config::ChangeDPI& _config; + std::shared_ptr _dpi; + }; +} #endif //LOGID_ACTION_CHANGEDPI_H \ No newline at end of file diff --git a/src/logid/actions/ChangeHostAction.cpp b/src/logid/actions/ChangeHostAction.cpp index 411b8ac..0da9138 100644 --- a/src/logid/actions/ChangeHostAction.cpp +++ b/src/logid/actions/ChangeHostAction.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,105 +15,98 @@ * along with this program. If not, see . * */ +#include +#include +#include #include -#include "ChangeHostAction.h" -#include "../Device.h" -#include "../backend/hidpp20/features/ReprogControls.h" -#include "../util/task.h" +#include +#include using namespace logid::actions; using namespace logid::backend; -ChangeHostAction::ChangeHostAction(Device *device, libconfig::Setting& -config) : Action(device), _config (device, config) -{ +const char* ChangeHostAction::interface_name = "ChangeHost"; + +ChangeHostAction::ChangeHostAction( + Device* device, config::ChangeHost& config, + [[maybe_unused]] const std::shared_ptr& parent) + : Action(device, interface_name, { + { + {"GetHost", {this, &ChangeHostAction::getHost, {"host"}}}, + {"SetHost", {this, &ChangeHostAction::setHost, {"host"}}} + }, + {}, + {} +}), _config(config) { + if (_config.host.has_value()) { + if (std::holds_alternative(_config.host.value())) { + auto& host = std::get(_config.host.value()); + std::transform(host.begin(), host.end(), + host.begin(), ::tolower); + } + } try { _change_host = std::make_shared(&device->hidpp20()); } catch (hidpp20::UnsupportedFeature& e) { logPrintf(WARN, "%s:%d: ChangeHost feature not supported, " "ChangeHostAction will not work.", device->hidpp20() - .devicePath().c_str(), device->hidpp20().deviceIndex()); + .devicePath().c_str(), device->hidpp20().deviceIndex()); } } -void ChangeHostAction::press() -{ +std::string ChangeHostAction::getHost() const { + std::shared_lock lock(_config_mutex); + if (_config.host.has_value()) { + if (std::holds_alternative(_config.host.value())) + return std::get(_config.host.value()); + else + return std::to_string(std::get(_config.host.value())); + } else { + return ""; + } +} + +void ChangeHostAction::setHost(std::string host) { + std::transform(host.begin(), host.end(), + host.begin(), ::tolower); + std::unique_lock lock(_config_mutex); + if (host == "next" || host == "prev" || host == "previous") { + _config.host = std::move(host); + } else { + _config.host = std::stoi(host); + } +} + +void ChangeHostAction::press() { // Do nothing, wait until release } -void ChangeHostAction::release() -{ - if(_change_host) { - task::spawn([this] { - auto host_info = _change_host->getHostInfo(); - auto next_host = _config.nextHost(host_info); - if(next_host != host_info.currentHost) - _change_host->setHost(next_host); +void ChangeHostAction::release() { + std::shared_lock lock(_config_mutex); + if (_change_host && _config.host.has_value()) { + run_task([self_weak = self(), host = _config.host.value()] { + if (auto self = self_weak.lock()) { + auto host_info = self->_change_host->getHostInfo(); + int next_host; + if (std::holds_alternative(host)) { + const auto& host_str = std::get(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(host) - 1; + } + next_host %= host_info.hostCount; + if (next_host != host_info.currentHost) + self->_change_host->setHost(next_host); + } }); } } -uint8_t ChangeHostAction::reprogFlags() const -{ +uint8_t ChangeHostAction::reprogFlags() const { return hidpp20::ReprogControls::TemporaryDiverted; } - -ChangeHostAction::Config::Config(Device *device, libconfig::Setting& config) - : Action::Config(device) -{ - try { - auto& host = config.lookup("host"); - if(host.getType() == libconfig::Setting::TypeInt) { - _offset = false; - _host = host; - _host--; // hosts are one-indexed in config - - if(_host < 0) { - logPrintf(WARN, "Line %d: host must be positive.", - host.getSourceLine()); - _offset = true; - _host = 0; - } - - } else if(host.getType() == libconfig::Setting::TypeString) { - _offset = true; - std::string hostmode = host; - std::transform(hostmode.begin(), hostmode.end(), - hostmode.begin(), ::tolower); - - if(hostmode == "next") - _host = 1; - else if(hostmode == "prev" || hostmode == "previous") - _host = -1; - else { - logPrintf(WARN, "Line %d: host must equal an integer, 'next'," - "or 'prev'.", host.getSourceLine()); - _host = 0; - } - } else { - logPrintf(WARN, "Line %d: host must equal an integer, 'next'," - "or 'prev'.", host.getSourceLine()); - _offset = true; - _host = 0; - } - } catch (libconfig::SettingNotFoundException& e) { - logPrintf(WARN, "Line %d: host is a required field, skipping.", - config.getSourceLine()); - _offset = true; - _host = 0; - } -} - -uint8_t ChangeHostAction::Config::nextHost(hidpp20::ChangeHost::HostInfo info) -{ - if(_offset) { - return (info.currentHost + _host) % info.hostCount; - } else { - if(_host >= info.hostCount || _host < 0) { - logPrintf(WARN, "No such host %d, defaulting to current.", - _host+1); - return info.currentHost; - } else - return _host; - } -} \ No newline at end of file diff --git a/src/logid/actions/ChangeHostAction.h b/src/logid/actions/ChangeHostAction.h index 4466a62..2d13384 100644 --- a/src/logid/actions/ChangeHostAction.h +++ b/src/logid/actions/ChangeHostAction.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,37 +18,31 @@ #ifndef LOGID_ACTION_CHANGEHOSTACTION_H #define LOGID_ACTION_CHANGEHOSTACTION_H -#include -#include "Action.h" -#include "../backend/hidpp20/features/ChangeHost.h" +#include +#include -namespace logid { -namespace actions -{ - class ChangeHostAction : public Action - { +namespace logid::actions { + class ChangeHostAction : public Action { public: - ChangeHostAction(Device* device, libconfig::Setting& config); + static const char* interface_name; - virtual void press(); - virtual void release(); + ChangeHostAction(Device* device, config::ChangeHost& config, + const std::shared_ptr& parent); - virtual uint8_t reprogFlags() const; + void press() final; - class Config : public Action::Config - { - public: - Config(Device* device, libconfig::Setting& setting); - uint8_t nextHost(backend::hidpp20::ChangeHost::HostInfo info); - private: - bool _offset; - int _host; - }; + void release() final; + + [[nodiscard]] std::string getHost() const; + + void setHost(std::string host); + + [[nodiscard]] uint8_t reprogFlags() const final; protected: std::shared_ptr _change_host; - Config _config; + config::ChangeHost& _config; }; -}} +} #endif //LOGID_ACTION_CHANGEHOSTACTION_H diff --git a/src/logid/actions/ChangeProfile.cpp b/src/logid/actions/ChangeProfile.cpp new file mode 100644 index 0000000..4e0f7b0 --- /dev/null +++ b/src/logid/actions/ChangeProfile.cpp @@ -0,0 +1,67 @@ +/* + * Copyright 2019-2023 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include + +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& 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); +} diff --git a/src/logid/backend/dj/Error.h b/src/logid/actions/ChangeProfile.h similarity index 52% rename from src/logid/backend/dj/Error.h rename to src/logid/actions/ChangeProfile.h index dc44067..0cbdc91 100644 --- a/src/logid/backend/dj/Error.h +++ b/src/logid/actions/ChangeProfile.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,34 +15,33 @@ * along with this program. If not, see . * */ +#ifndef LOGID_CHANGEPROFILE_H +#define LOGID_CHANGEPROFILE_H -#ifndef LOGID_BACKEND_DJ_ERROR_H -#define LOGID_BACKEND_DJ_ERROR_H +#include -#include -#include - -namespace logid { -namespace backend { -namespace dj -{ - class Error : public std::exception - { +namespace logid::actions { + class ChangeProfile : public Action { public: - enum ErrorCode : uint8_t - { - Unknown = 0x00, - KeepAliveTimeout = 0x01 - }; + static const char* interface_name; - explicit Error(uint8_t code); + ChangeProfile(Device* device, config::ChangeProfile& setting, + const std::shared_ptr& parent); - const char* what() const noexcept override; - uint8_t code() const noexcept; + void press() final; + + void release() final; + + [[nodiscard]] uint8_t reprogFlags() const final; + + std::string getProfile(); + + void setProfile(std::string profile); private: - uint8_t _code; + config::ChangeProfile& _config; }; -}}} +} -#endif //LOGID_BACKEND_DJ_ERROR_H \ No newline at end of file + +#endif //LOGID_CHANGEPROFILE_H diff --git a/src/logid/actions/CycleDPI.cpp b/src/logid/actions/CycleDPI.cpp index 19b729c..ee23252 100644 --- a/src/logid/actions/CycleDPI.cpp +++ b/src/logid/actions/CycleDPI.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,119 +15,84 @@ * along with this program. If not, see . * */ -#include "CycleDPI.h" -#include "../Device.h" -#include "../util/task.h" -#include "../util/log.h" -#include "../backend/hidpp20/Error.h" -#include "../backend/hidpp20/features/ReprogControls.h" +#include +#include +#include +#include +#include using namespace logid::actions; -using namespace libconfig; -CycleDPI::CycleDPI(Device* device, libconfig::Setting& setting) : - Action (device), _config (device, setting) -{ +const char* CycleDPI::interface_name = "CycleDPI"; + +CycleDPI::CycleDPI(Device* device, config::CycleDPI& config, + [[maybe_unused]] const std::shared_ptr& parent) : + Action(device, interface_name, { + { + {"GetDPIs", {this, &CycleDPI::getDPIs, {"dpis"}}}, + {"SetDPIs", {this, &CycleDPI::setDPIs, {"dpis"}}} + }, + {}, + {} + }), + _config(config) { _dpi = _device->getFeature("dpi"); - if(!_dpi) + if (!_dpi) logPrintf(WARN, "%s:%d: DPI feature not found, cannot use " "CycleDPI action.", _device->hidpp20().devicePath().c_str(), _device->hidpp20().deviceIndex()); + + if (_config.dpis.has_value()) { + _current_dpi = _config.dpis.value().begin(); + } } -void CycleDPI::press() -{ +std::vector CycleDPI::getDPIs() const { + std::shared_lock lock(_config_mutex); + auto dpis = _config.dpis.value_or(std::list()); + return {dpis.begin(), dpis.end()}; +} + +void CycleDPI::setDPIs(const std::vector& dpis) { + std::unique_lock lock(_config_mutex); + std::lock_guard dpi_lock(_dpi_mutex); + _config.dpis.emplace(dpis.begin(), dpis.end()); + _current_dpi = _config.dpis->cbegin(); +} + +void CycleDPI::press() { _pressed = true; - if(_dpi && !_config.empty()) { - task::spawn([this](){ - uint16_t dpi = _config.nextDPI(); - try { - _dpi->setDPI(dpi, _config.sensor()); - } catch (backend::hidpp20::Error& e) { - if(e.code() == backend::hidpp20::Error::InvalidArgument) - logPrintf(WARN, "%s:%d: Could not set DPI to %d for " - "sensor %d", _device->hidpp20().devicePath().c_str(), - _device->hidpp20().deviceIndex(), dpi, - _config.sensor()); - else - throw e; + std::shared_lock lock(_config_mutex); + std::lock_guard dpi_lock(_dpi_mutex); + if (_dpi && _config.dpis.has_value() && _config.dpis.value().empty()) { + ++_current_dpi; + if (_current_dpi == _config.dpis.value().end()) + _current_dpi = _config.dpis.value().begin(); + + run_task([self_weak = self(), dpi = *_current_dpi] { + if (auto self = self_weak.lock()) { + try { + self->_dpi->setDPI(dpi, self->_config.sensor.value_or(0)); + } catch (backend::hidpp20::Error& e) { + if (e.code() == backend::hidpp20::Error::InvalidArgument) + logPrintf(WARN, "%s:%d: Could not set DPI to %d for " + "sensor %d", + self->_device->hidpp20().devicePath().c_str(), + self->_device->hidpp20().deviceIndex(), dpi, + self->_config.sensor.value_or(0)); + else + throw e; + } } }); } } -void CycleDPI::release() -{ +void CycleDPI::release() { _pressed = false; } -uint8_t CycleDPI::reprogFlags() const -{ +uint8_t CycleDPI::reprogFlags() const { return backend::hidpp20::ReprogControls::TemporaryDiverted; } - -CycleDPI::Config::Config(Device *device, libconfig::Setting &config) : - Action::Config(device), _current_index (0), _sensor (0) -{ - if(!config.isGroup()) { - logPrintf(WARN, "Line %d: action must be an object, skipping.", - config.getSourceLine()); - return; - } - - try { - auto& sensor = config.lookup("sensor"); - if(sensor.getType() != Setting::TypeInt) - logPrintf(WARN, "Line %d: sensor must be an integer", - sensor.getSourceLine()); - _sensor = (int)sensor; - } catch(libconfig::SettingNotFoundException& e) { - // Ignore - } - - try { - auto& dpis = config.lookup("dpis"); - if(!dpis.isList() && !dpis.isArray()) { - logPrintf(WARN, "Line %d: dpis must be a list or array, skipping.", - dpis.getSourceLine()); - return; - } - - int dpi_count = dpis.getLength(); - for(int i = 0; i < dpi_count; i++) { - if(dpis[i].getType() != Setting::TypeInt) { - logPrintf(WARN, "Line %d: dpis must be integers, skipping.", - dpis[i].getSourceLine()); - if(dpis.isList()) - continue; - else - break; - } - - _dpis.push_back((int)(dpis[i])); - } - - } catch (libconfig::SettingNotFoundException& e) { - logPrintf(WARN, "Line %d: dpis is a required field, skipping.", - config.getSourceLine()); - } -} - -uint16_t CycleDPI::Config::nextDPI() -{ - uint16_t dpi = _dpis[_current_index++]; - if(_current_index >= _dpis.size()) - _current_index = 0; - return dpi; -} - -bool CycleDPI::Config::empty() const -{ - return _dpis.empty(); -} - -uint8_t CycleDPI::Config::sensor() const -{ - return _sensor; -} \ No newline at end of file diff --git a/src/logid/actions/CycleDPI.h b/src/logid/actions/CycleDPI.h index b6a5017..828f513 100644 --- a/src/logid/actions/CycleDPI.h +++ b/src/logid/actions/CycleDPI.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,39 +18,33 @@ #ifndef LOGID_ACTION_CYCLEDPI_H #define LOGID_ACTION_CYCLEDPI_H -#include -#include "Action.h" -#include "../features/DPI.h" +#include +#include -namespace logid { -namespace actions { - class CycleDPI : public Action - { +namespace logid::actions { + class CycleDPI : public Action { public: - explicit CycleDPI(Device* device, libconfig::Setting& setting); + static const char* interface_name; - virtual void press(); - virtual void release(); + CycleDPI(Device* device, config::CycleDPI& setting, + const std::shared_ptr& parent); - virtual uint8_t reprogFlags() const; + void press() final; - class Config : public Action::Config - { - public: - Config(Device* device, libconfig::Setting& setting); - uint16_t nextDPI(); - bool empty() const; - uint8_t sensor() const; - private: - std::size_t _current_index; - std::vector _dpis; - uint8_t _sensor; - }; + void release() final; + + [[nodiscard]] std::vector getDPIs() const; + + void setDPIs(const std::vector& dpis); + + [[nodiscard]] uint8_t reprogFlags() const final; protected: - Config _config; + std::mutex _dpi_mutex; + config::CycleDPI& _config; std::shared_ptr _dpi; + std::list::const_iterator _current_dpi; }; -}} +} #endif //LOGID_ACTION_CYCLEDPI_H \ No newline at end of file diff --git a/src/logid/actions/GestureAction.cpp b/src/logid/actions/GestureAction.cpp index f364a32..8afceb6 100644 --- a/src/logid/actions/GestureAction.cpp +++ b/src/logid/actions/GestureAction.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,277 +15,247 @@ * along with this program. If not, see . * */ +#include +#include +#include #include -#include "GestureAction.h" -#include "../Device.h" -#include "../backend/hidpp20/features/ReprogControls.h" using namespace logid::actions; using namespace logid; using namespace logid::backend; -GestureAction::Direction GestureAction::toDirection(std::string direction) -{ +const char* GestureAction::interface_name = "Gesture"; + +GestureAction::Direction GestureAction::toDirection(std::string direction) { std::transform(direction.begin(), direction.end(), direction.begin(), - ::tolower); - if(direction == "up") + ::tolower); + if (direction == "up") return Up; - else if(direction == "down") + else if (direction == "down") return Down; - else if(direction == "left") + else if (direction == "left") return Left; - else if(direction == "right") + else if (direction == "right") return Right; - else if(direction == "none") + else if (direction == "none") return None; else throw std::invalid_argument("direction"); } -GestureAction::Direction GestureAction::toDirection(int16_t x, int16_t y) -{ - if(x >= 0 && y >= 0) +std::string GestureAction::fromDirection(Direction direction) { + switch (direction) { + case Up: + return "up"; + case Down: + return "down"; + case Left: + return "left"; + case Right: + return "right"; + case None: + return "none"; + } + + // This shouldn't happen + throw InvalidGesture(); +} + +GestureAction::Direction GestureAction::toDirection(int32_t x, int32_t y) { + if (x >= 0 && y >= 0) return x >= y ? Right : Down; - else if(x < 0 && y >= 0) + else if (x < 0 && y >= 0) return -x <= y ? Down : Left; - else if(x <= 0 && y < 0) + else if (x <= 0 /* && y < 0 */) return x <= y ? Left : Up; else return x <= -y ? Up : Right; } -GestureAction::GestureAction(Device* dev, libconfig::Setting& config) : - Action (dev), _config (dev, config) -{ +GestureAction::GestureAction(Device* dev, config::GestureAction& config, + const std::shared_ptr& parent) : + Action(dev, interface_name, + { + { + {"SetGesture", {this, &GestureAction::setGesture, {"direction", "type"}}} + }, + {}, + {} + }), + _node(parent->make_child("gestures")), _config(config) { + if (_config.gestures.has_value()) { + auto& gestures = _config.gestures.value(); + for (auto&& x: gestures) { + try { + auto direction = toDirection(x.first); + _gestures.emplace( + direction,Gesture::makeGesture( + dev, x.second, + _node->make_child(fromDirection(direction)))); + if (direction == None) { + auto& gesture = x.second; + std::visit([](auto&& x) { + x.threshold.emplace(0); + }, gesture); + } + } catch (std::invalid_argument& e) { + logPrintf(WARN, "%s is not a direction", x.first.c_str()); + } + } + } } -void GestureAction::press() -{ +void GestureAction::press() { + std::shared_lock lock(_config_mutex); + _pressed = true; _x = 0, _y = 0; - for(auto& gesture : _config.gestures()) - gesture.second->press(); + for (auto& gesture: _gestures) + gesture.second->press(false); } -void GestureAction::release() -{ +void GestureAction::release() { + std::shared_lock lock(_config_mutex); + _pressed = false; bool threshold_met = false; auto d = toDirection(_x, _y); - auto primary_gesture = _config.gestures().find(d); - if(primary_gesture != _config.gestures().end()) { + auto primary_gesture = _gestures.find(d); + if (primary_gesture != _gestures.end()) { threshold_met = primary_gesture->second->metThreshold(); primary_gesture->second->release(true); } - for(auto& gesture : _config.gestures()) { - if(gesture.first == d) + for (auto& gesture: _gestures) { + if (gesture.first == d || gesture.first == None) continue; - if(!threshold_met) { - if(gesture.second->metThreshold()) { + if (!threshold_met) { + if (gesture.second->metThreshold()) { // If the primary gesture did not meet its threshold, use the // secondary one. threshold_met = true; gesture.second->release(true); - break; } } else { gesture.second->release(false); } } - if(!threshold_met) { - if(_config.noneAction()) { - _config.noneAction()->press(); - _config.noneAction()->release(); - } + auto none_gesture = _gestures.find(None); + if (none_gesture != _gestures.end()) { + none_gesture->second->release(!threshold_met); } } -void GestureAction::move(int16_t x, int16_t y) -{ - auto new_x = _x + x, new_y = _y + y; +void GestureAction::move(int16_t x, int16_t y) { + std::shared_lock lock(_config_mutex); - if(abs(x) > 0) { - if(_x < 0 && new_x >= 0) { // Left -> Origin/Right - auto left = _config.gestures().find(Left); - if(left != _config.gestures().end()) - left->second->move(_x); - if(new_x) { // Ignore to origin - auto right = _config.gestures().find(Right); - if(right != _config.gestures().end()) - right->second->move(new_x); + int32_t new_x = _x + x, new_y = _y + y; + + if (abs(x) > 0) { + if (_x < 0 && new_x >= 0) { // Left -> Origin/Right + auto left = _gestures.find(Left); + if (left != _gestures.end() && left->second) + left->second->move((int16_t) _x); + if (new_x) { // Ignore to origin + auto right = _gestures.find(Right); + if (right != _gestures.end() && right->second) + right->second->move((int16_t) new_x); } - } else if(_x > 0 && new_x <= 0) { // Right -> Origin/Left - auto right = _config.gestures().find(Right); - if(right != _config.gestures().end()) - right->second->move(-_x); - if(new_x) { // Ignore to origin - auto left = _config.gestures().find(Left); - if(left != _config.gestures().end()) - left->second->move(-new_x); + } else if (_x > 0 && new_x <= 0) { // Right -> Origin/Left + auto right = _gestures.find(Right); + if (right != _gestures.end() && right->second) + right->second->move((int16_t) -_x); + if (new_x) { // Ignore to origin + auto left = _gestures.find(Left); + if (left != _gestures.end() && left->second) + left->second->move((int16_t) -new_x); } - } else if(new_x < 0) { // Origin/Left to Left - auto left = _config.gestures().find(Left); - if(left != _config.gestures().end()) - left->second->move(-x); - } else if(new_x > 0) { // Origin/Right to Right - auto right = _config.gestures().find(Right); - if(right != _config.gestures().end()) + } else if (new_x < 0) { // Origin/Left to Left + auto left = _gestures.find(Left); + if (left != _gestures.end() && left->second) + left->second->move((int16_t) -x); + } else if (new_x > 0) { // Origin/Right to Right + auto right = _gestures.find(Right); + if (right != _gestures.end() && right->second) right->second->move(x); } } - if(abs(y) > 0) { - if(_y > 0 && new_y <= 0) { // Up -> Origin/Down - auto up = _config.gestures().find(Up); - if(up != _config.gestures().end()) - up->second->move(_y); - if(new_y) { // Ignore to origin - auto down = _config.gestures().find(Down); - if(down != _config.gestures().end()) - down->second->move(new_y); + if (abs(y) > 0) { + if (_y > 0 && new_y <= 0) { // Up -> Origin/Down + auto up = _gestures.find(Up); + if (up != _gestures.end() && up->second) + up->second->move((int16_t) _y); + if (new_y) { // Ignore to origin + auto down = _gestures.find(Down); + if (down != _gestures.end() && down->second) + down->second->move((int16_t) new_y); } - } else if(_y < 0 && new_y >= 0) { // Down -> Origin/Up - auto down = _config.gestures().find(Down); - if(down != _config.gestures().end()) - down->second->move(-_y); - if(new_y) { // Ignore to origin - auto up = _config.gestures().find(Up); - if(up != _config.gestures().end()) - up->second->move(-new_y); + } else if (_y < 0 && new_y >= 0) { // Down -> Origin/Up + auto down = _gestures.find(Down); + if (down != _gestures.end() && down->second) + down->second->move((int16_t) -_y); + if (new_y) { // Ignore to origin + auto up = _gestures.find(Up); + if (up != _gestures.end() && up->second) + up->second->move((int16_t) -new_y); } - } else if(new_y < 0) { // Origin/Up to Up - auto up = _config.gestures().find(Up); - if(up != _config.gestures().end()) - up->second->move(-y); - } else if(new_y > 0) {// Origin/Down to Down - auto down = _config.gestures().find(Down); - if(down != _config.gestures().end()) + } else if (new_y < 0) { // Origin/Up to Up + auto up = _gestures.find(Up); + if (up != _gestures.end() && up->second) + up->second->move((int16_t) -y); + } else if (new_y > 0) {// Origin/Down to Down + auto down = _gestures.find(Down); + if (down != _gestures.end() && down->second) down->second->move(y); } } - _x = new_x; _y = new_y; + _x = new_x; + _y = new_y; } -uint8_t GestureAction::reprogFlags() const -{ +uint8_t GestureAction::reprogFlags() const { return (hidpp20::ReprogControls::TemporaryDiverted | - hidpp20::ReprogControls::RawXYDiverted); + hidpp20::ReprogControls::RawXYDiverted); } -GestureAction::Config::Config(Device* device, libconfig::Setting &root) : - Action::Config(device) -{ +void GestureAction::setGesture(const std::string& direction, const std::string& type) { + std::unique_lock lock(_config_mutex); + + Direction d = toDirection(direction); + + auto it = _gestures.find(d); + + if (it != _gestures.end()) { + if (pressed()) { + auto current = toDirection(_x, _y); + if (it->second) + it->second->release(current == d); + } + } + + auto dir_name = fromDirection(d); + + auto& gesture = _config.gestures.value()[dir_name]; + + _gestures[d].reset(); try { - auto& gestures = root.lookup("gestures"); + _gestures[d] = Gesture::makeGesture( + _device, type, gesture, + _node->make_child(dir_name)); + } catch (InvalidGesture& e) { + _gestures[d] = Gesture::makeGesture( + _device, gesture, + _node->make_child(dir_name)); + throw std::invalid_argument("Invalid gesture type"); + } - if(!gestures.isList()) { - logPrintf(WARN, "Line %d: gestures must be a list, ignoring.", - gestures.getSourceLine()); - return; - } - - int gesture_count = gestures.getLength(); - - for(int i = 0; i < gesture_count; i++) { - if(!gestures[i].isGroup()) { - logPrintf(WARN, "Line %d: gesture must be a group, skipping.", - gestures[i].getSourceLine()); - continue; - } - - Direction d; - try { - auto& direction = gestures[i].lookup("direction"); - if(direction.getType() != libconfig::Setting::TypeString) { - logPrintf(WARN, "Line %d: direction must be a string, " - "skipping.", direction.getSourceLine()); - continue; - } - - try { - d = toDirection(direction); - } catch(std::invalid_argument& e) { - logPrintf(WARN, "Line %d: Invalid direction %s", - direction.getSourceLine(), (const char*)direction); - continue; - } - } catch(libconfig::SettingNotFoundException& e) { - logPrintf(WARN, "Line %d: direction is a required field, " - "skipping.", gestures[i].getSourceLine()); - continue; - } - - if(_gestures.find(d) != _gestures.end() || (d == None && _none_action)) { - logPrintf(WARN, "Line %d: Gesture is already defined for " - "this direction, duplicate ignored.", - gestures[i].getSourceLine()); - continue; - } - - if(d == None) { - try { - auto& mode = gestures[i].lookup("mode"); - if(mode.getType() == libconfig::Setting::TypeString) { - std::string mode_str = mode; - std::transform(mode_str.begin(), mode_str.end(), - mode_str.begin(), ::tolower); - if(mode_str == "nopress") // No action - continue; - else if(mode_str != "onrelease") - logPrintf(WARN, "Line %d: Only NoPress and " - "OnRelease are supported for the " - "None direction, defaulting to " - "OnRelease.", mode.getSourceLine()); - } else { - logPrintf(WARN, "Line %d: mode must be a string, " - "defaulting to OnRelease", - mode.getSourceLine()); - } - } catch(libconfig::SettingNotFoundException& e) { - // Default to OnRelease - } - - try { - _none_action = Action::makeAction(_device, - gestures[i].lookup("action")); - } catch (InvalidAction& e) { - logPrintf(WARN, "Line %d: %s is not a valid action, " - "skipping.", gestures[i].lookup("action") - .getSourceLine(), e.what()); - } catch (libconfig::SettingNotFoundException& e) { - logPrintf(WARN, "Line %d: action is a required field, " - "skipping.", gestures[i].getSourceLine(), - e.what()); - } - continue; - } - - try { - _gestures.emplace(d, Gesture::makeGesture(_device, - gestures[i])); - } catch(InvalidGesture& e) { - logPrintf(WARN, "Line %d: Invalid gesture: %s", - gestures[i].getSourceLine(), e.what()); - } - } - - } catch(libconfig::SettingNotFoundException& e) { - logPrintf(WARN, "Line %d: gestures is a required field, ignoring.", - root.getSourceLine()); + if (d == None) { + std::visit([](auto&& x) { + x.threshold = 0; + }, gesture); } } - -std::map>& - GestureAction::Config::gestures() -{ - return _gestures; -} - -std::shared_ptr GestureAction::Config::noneAction() -{ - return _none_action; -} \ No newline at end of file diff --git a/src/logid/actions/GestureAction.h b/src/logid/actions/GestureAction.h index 3928f75..7ef8f3a 100644 --- a/src/logid/actions/GestureAction.h +++ b/src/logid/actions/GestureAction.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,49 +19,48 @@ #define LOGID_ACTION_GESTUREACTION_H #include -#include -#include "Action.h" -#include "gesture/Gesture.h" +#include +#include -namespace logid { -namespace actions { - class GestureAction : public Action - { +namespace logid::actions { + class GestureAction : public Action { public: - enum Direction - { + static const char* interface_name; + + enum Direction { None, Up, Down, Left, Right }; + static Direction toDirection(std::string direction); - static Direction toDirection(int16_t x, int16_t y); - GestureAction(Device* dev, libconfig::Setting& config); + static std::string fromDirection(Direction direction); - virtual void press(); - virtual void release(); - virtual void move(int16_t x, int16_t y); + static Direction toDirection(int32_t x, int32_t y); - virtual uint8_t reprogFlags() const; + GestureAction(Device* dev, config::GestureAction& config, + const std::shared_ptr& parent); - class Config : public Action::Config - { - public: - Config(Device* device, libconfig::Setting& root); - std::map>& gestures(); - std::shared_ptr noneAction(); - protected: - std::map> _gestures; - std::shared_ptr _none_action; - }; + void press() final; + + void release() final; + + void move(int16_t x, int16_t y) final; + + uint8_t reprogFlags() const final; + + void setGesture(const std::string& direction, + const std::string& type); protected: - int16_t _x, _y; - Config _config; + int32_t _x{}, _y{}; + std::shared_ptr _node; + std::map> _gestures; + config::GestureAction& _config; }; -}} +} #endif //LOGID_ACTION_GESTUREACTION_H diff --git a/src/logid/actions/KeypressAction.cpp b/src/logid/actions/KeypressAction.cpp index a71546b..ee8d1de 100644 --- a/src/logid/actions/KeypressAction.cpp +++ b/src/logid/actions/KeypressAction.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,77 +15,112 @@ * along with this program. If not, see . * */ -#include "KeypressAction.h" -#include "../util/log.h" -#include "../InputDevice.h" -#include "../backend/hidpp20/features/ReprogControls.h" +#include +#include +#include +#include +#include using namespace logid::actions; using namespace logid::backend; -KeypressAction::KeypressAction(Device *device, libconfig::Setting& config) : - Action(device), _config (device, config) -{ +const char* KeypressAction::interface_name = "Keypress"; + +KeypressAction::KeypressAction( + Device* device, config::KeypressAction& config, + [[maybe_unused]] const std::shared_ptr& parent) : + Action(device, interface_name, { + { + {"GetKeys", {this, &KeypressAction::getKeys, {"keys"}}}, + {"SetKeys", {this, &KeypressAction::setKeys, {"keys"}}} + }, + {}, + {} + }), _config(config) { + _setConfig(); } -void KeypressAction::press() -{ +void KeypressAction::press() { + std::shared_lock lock(_config_mutex); _pressed = true; - for(auto& key : _config.keys()) - virtual_input->pressKey(key); + for (auto& key: _keys) + _device->virtualInput()->pressKey(key); } -void KeypressAction::release() -{ +void KeypressAction::release() { + std::shared_lock lock(_config_mutex); _pressed = false; - for(auto& key : _config.keys()) - virtual_input->releaseKey(key); + for (auto& key: _keys) + _device->virtualInput()->releaseKey(key); } -uint8_t KeypressAction::reprogFlags() const -{ +void KeypressAction::_setConfig() { + _keys.clear(); + + if (!_config.keys.has_value()) + return; + + auto& config = _config.keys.value(); + + if (std::holds_alternative(config)) { + const auto& key = std::get(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(_config.keys.value())) { + const auto& key = std::get(config); + _device->virtualInput()->registerKey(key); + _keys.emplace_back(key); + } else if (std::holds_alternative< + std::list>>(config)) { + const auto& keys = std::get< + std::list>>(config); + for (const auto& key: keys) { + if (std::holds_alternative(key)) { + const auto& key_str = std::get(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(key)) { + auto& code = std::get(key); + _device->virtualInput()->registerKey(code); + _keys.emplace_back(code); + } + } + } +} + +uint8_t KeypressAction::reprogFlags() const { return hidpp20::ReprogControls::TemporaryDiverted; } -KeypressAction::Config::Config(Device* device, libconfig::Setting& config) : - Action::Config(device) -{ - if(!config.isGroup()) { - logPrintf(WARN, "Line %d: action must be an object, skipping.", - config.getSourceLine()); - return; - } +std::vector KeypressAction::getKeys() const { + std::shared_lock lock(_config_mutex); + std::vector ret; + for (auto& x: _keys) + ret.push_back(InputDevice::toKeyName(x)); - try { - auto &keys = config.lookup("keys"); - if(keys.isArray() || keys.isList()) { - int key_count = keys.getLength(); - for(int i = 0; i < key_count; i++) { - auto& key = keys[i]; - if(key.isNumber()) { - _keys.push_back(key); - virtual_input->registerKey(key); - } else if(key.getType() == libconfig::Setting::TypeString) { - try { - _keys.push_back(virtual_input->toKeyCode(key)); - virtual_input->registerKey(virtual_input->toKeyCode(key)); - } catch(InputDevice::InvalidEventCode& e) { - logPrintf(WARN, "Line %d: Invalid keycode %s, skipping." - , key.getSourceLine(), key.c_str()); - } - } else { - logPrintf(WARN, "Line %d: keycode must be string or int", - key.getSourceLine(), key.c_str()); - } - } - } - } catch (libconfig::SettingNotFoundException& e) { - logPrintf(WARN, "Line %d: keys is a required field, skipping.", - config.getSourceLine()); - } + return ret; } -std::vector& KeypressAction::Config::keys() -{ - return _keys; -} \ No newline at end of file +void KeypressAction::setKeys(const std::vector& keys) { + std::unique_lock lock(_config_mutex); + if (_pressed) + for (auto& key: _keys) + _device->virtualInput()->releaseKey(key); + _config.keys = std::list>(); + auto& config = std::get>>( + _config.keys.value()); + for (auto& x: keys) + config.emplace_back(x); + _setConfig(); +} diff --git a/src/logid/actions/KeypressAction.h b/src/logid/actions/KeypressAction.h index 4385faf..226db94 100644 --- a/src/logid/actions/KeypressAction.h +++ b/src/logid/actions/KeypressAction.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,32 +19,33 @@ #define LOGID_ACTION_KEYPRESS_H #include -#include -#include "Action.h" +#include -namespace logid { -namespace actions { - class KeypressAction : public Action - { +namespace logid::actions { + class KeypressAction : public Action { public: - KeypressAction(Device* dev, libconfig::Setting& config); + static const char* interface_name; - virtual void press(); - virtual void release(); + KeypressAction(Device* dev, + config::KeypressAction& config, + const std::shared_ptr& parent); - virtual uint8_t reprogFlags() const; + void press() final; + + void release() final; + + [[nodiscard]] std::vector getKeys() const; + + void setKeys(const std::vector& keys); + + [[nodiscard]] uint8_t reprogFlags() const final; - class Config : public Action::Config - { - public: - explicit Config(Device* device, libconfig::Setting& root); - std::vector& keys(); - protected: - std::vector _keys; - }; protected: - Config _config; + config::KeypressAction& _config; + std::list _keys; + + void _setConfig(); }; -}} +} #endif //LOGID_ACTION_KEYPRESS_H diff --git a/src/logid/actions/NullAction.cpp b/src/logid/actions/NullAction.cpp index 8cf875f..0734aa6 100644 --- a/src/logid/actions/NullAction.cpp +++ b/src/logid/actions/NullAction.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,27 +15,27 @@ * along with this program. If not, see . * */ -#include "NullAction.h" -#include "../Device.h" -#include "../backend/hidpp20/features/ReprogControls.h" +#include +#include using namespace logid::actions; -NullAction::NullAction(Device* device) : Action(device) -{ +const char* NullAction::interface_name = "None"; + +NullAction::NullAction( + Device* device, + [[maybe_unused]] const std::shared_ptr& parent) : + Action(device, interface_name) { } -void NullAction::press() -{ +void NullAction::press() { _pressed = true; } -void NullAction::release() -{ +void NullAction::release() { _pressed = false; } -uint8_t NullAction::reprogFlags() const -{ +uint8_t NullAction::reprogFlags() const { return backend::hidpp20::ReprogControls::TemporaryDiverted; } \ No newline at end of file diff --git a/src/logid/actions/NullAction.h b/src/logid/actions/NullAction.h index 2d2d0ef..1aeca57 100644 --- a/src/logid/actions/NullAction.h +++ b/src/logid/actions/NullAction.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,22 +18,27 @@ #ifndef LOGID_ACTION_NULL_H #define LOGID_ACTION_NULL_H -#include "Action.h" +#include -namespace logid { -namespace actions -{ - class NullAction : public Action - { +namespace logid::actions { + class NullAction : public Action { public: - explicit NullAction(Device* device); + static const char* interface_name; - virtual void press(); - virtual void release(); + NullAction(Device* device, + const std::shared_ptr& parent); - virtual uint8_t reprogFlags() const; + NullAction(Device* device, [[maybe_unused]] config::NoAction& config, + const std::shared_ptr& parent) : + NullAction(device, parent) {} + + void press() final; + + void release() final; + + [[nodiscard]] uint8_t reprogFlags() const final; }; -}} +} #endif //LOGID_ACTION_NULL_H diff --git a/src/logid/actions/ToggleHiresScroll.cpp b/src/logid/actions/ToggleHiresScroll.cpp index 0c57d54..cf8eb4d 100644 --- a/src/logid/actions/ToggleHiresScroll.cpp +++ b/src/logid/actions/ToggleHiresScroll.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,43 +15,46 @@ * along with this program. If not, see . * */ -#include "ToggleHiresScroll.h" -#include "../Device.h" -#include "../util/task.h" -#include "../backend/hidpp20/features/ReprogControls.h" +#include +#include +#include +#include +#include using namespace logid::actions; using namespace logid::backend; -ToggleHiresScroll::ToggleHiresScroll(Device *dev) : Action (dev) -{ +const char* ToggleHiresScroll::interface_name = "ToggleHiresScroll"; + +ToggleHiresScroll::ToggleHiresScroll( + Device* dev, + [[maybe_unused]] const std::shared_ptr& parent) : + Action(dev, interface_name) { _hires_scroll = _device->getFeature("hiresscroll"); - if(!_hires_scroll) + if (!_hires_scroll) logPrintf(WARN, "%s:%d: HiresScroll feature not found, cannot use " "ToggleHiresScroll action.", _device->hidpp20().devicePath().c_str(), _device->hidpp20().devicePath().c_str()); } -void ToggleHiresScroll::press() -{ +void ToggleHiresScroll::press() { _pressed = true; - if(_hires_scroll) - { - task::spawn([hires=this->_hires_scroll](){ - auto mode = hires->getMode(); - mode ^= backend::hidpp20::HiresScroll::HiRes; - hires->setMode(mode); + if (_hires_scroll) { + run_task([self_weak = self()]() { + if (auto self = self_weak.lock()) { + auto mode = self->_hires_scroll->getMode(); + mode ^= backend::hidpp20::HiresScroll::HiRes; + self->_hires_scroll->setMode(mode); + } }); } } -void ToggleHiresScroll::release() -{ +void ToggleHiresScroll::release() { _pressed = false; } -uint8_t ToggleHiresScroll::reprogFlags() const -{ +uint8_t ToggleHiresScroll::reprogFlags() const { return hidpp20::ReprogControls::TemporaryDiverted; -} \ No newline at end of file +} diff --git a/src/logid/actions/ToggleHiresScroll.h b/src/logid/actions/ToggleHiresScroll.h index bd60f71..3376c4f 100644 --- a/src/logid/actions/ToggleHiresScroll.h +++ b/src/logid/actions/ToggleHiresScroll.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,24 +18,30 @@ #ifndef LOGID_ACTION_TOGGLEHIRESSCROLL_H #define LOGID_ACTION_TOGGLEHIRESSCROLL_H -#include "Action.h" -#include "../features/HiresScroll.h" +#include +#include -namespace logid { -namespace actions -{ - class ToggleHiresScroll : public Action - { +namespace logid::actions { + class ToggleHiresScroll : public Action { public: - explicit ToggleHiresScroll(Device* dev); + static const char* interface_name; - virtual void press(); - virtual void release(); + ToggleHiresScroll(Device* dev, const std::shared_ptr& parent); + + ToggleHiresScroll(Device* device, + [[maybe_unused]] config::ToggleHiresScroll& action, + const std::shared_ptr& parent) : + ToggleHiresScroll(device, parent) {} + + void press() final; + + void release() final; + + [[nodiscard]] uint8_t reprogFlags() const final; - virtual uint8_t reprogFlags() const; protected: std::shared_ptr _hires_scroll; }; -}} +} #endif //LOGID_ACTION_TOGGLEHIRESSCROLL_H diff --git a/src/logid/actions/ToggleSmartShift.cpp b/src/logid/actions/ToggleSmartShift.cpp index 5e4fd6e..598cbff 100644 --- a/src/logid/actions/ToggleSmartShift.cpp +++ b/src/logid/actions/ToggleSmartShift.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,43 +15,48 @@ * along with this program. If not, see . * */ -#include "ToggleSmartShift.h" -#include "../Device.h" -#include "../backend/hidpp20/features/ReprogControls.h" -#include "../util/task.h" + +#include +#include +#include +#include +#include using namespace logid::actions; using namespace logid::backend; -ToggleSmartShift::ToggleSmartShift(Device *dev) : Action (dev) -{ +const char* ToggleSmartShift::interface_name = "ToggleSmartShift"; + +ToggleSmartShift::ToggleSmartShift( + Device* dev, + [[maybe_unused]] const std::shared_ptr& parent) : + Action(dev, interface_name) { _smartshift = _device->getFeature("smartshift"); - if(!_smartshift) + if (!_smartshift) logPrintf(WARN, "%s:%d: SmartShift feature not found, cannot use " "ToggleSmartShift action.", - _device->hidpp20().devicePath().c_str(), - _device->hidpp20().deviceIndex()); + _device->hidpp20().devicePath().c_str(), + _device->hidpp20().deviceIndex()); } -void ToggleSmartShift::press() -{ +void ToggleSmartShift::press() { _pressed = true; - if(_smartshift) { - task::spawn([ss=this->_smartshift](){ - auto status = ss->getStatus(); - status.setActive = true; - status.active = !status.active; - ss->setStatus(status); + if (_smartshift) { + run_task([self_weak = self()]() { + if (auto self = self_weak.lock()) { + auto status = self->_smartshift->getStatus(); + status.setActive = true; + status.active = !status.active; + self->_smartshift->setStatus(status); + } }); } } -void ToggleSmartShift::release() -{ +void ToggleSmartShift::release() { _pressed = false; } -uint8_t ToggleSmartShift::reprogFlags() const -{ +uint8_t ToggleSmartShift::reprogFlags() const { return hidpp20::ReprogControls::TemporaryDiverted; -} \ No newline at end of file +} diff --git a/src/logid/actions/ToggleSmartShift.h b/src/logid/actions/ToggleSmartShift.h index 7884f48..e987ccd 100644 --- a/src/logid/actions/ToggleSmartShift.h +++ b/src/logid/actions/ToggleSmartShift.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,24 +18,31 @@ #ifndef LOGID_ACTION_TOGGLESMARTSHIFT_H #define LOGID_ACTION_TOGGLESMARTSHIFT_H -#include -#include "Action.h" -#include "../features/SmartShift.h" +#include +#include -namespace logid { -namespace actions { - class ToggleSmartShift : public Action - { +namespace logid::actions { + class ToggleSmartShift : public Action { public: - explicit ToggleSmartShift(Device* dev); + static const char* interface_name; - virtual void press(); - virtual void release(); + ToggleSmartShift(Device* dev, + const std::shared_ptr& parent); + + ToggleSmartShift(Device* device, + [[maybe_unused]] config::ToggleSmartShift& action, + const std::shared_ptr& parent) : + ToggleSmartShift(device, parent) {} + + void press() final; + + void release() final; + + [[nodiscard]] uint8_t reprogFlags() const final; - virtual uint8_t reprogFlags() const; protected: std::shared_ptr _smartshift; }; -}} +} #endif //LOGID_ACTION_TOGGLESMARTSHIFT_H \ No newline at end of file diff --git a/src/logid/actions/gesture/AxisGesture.cpp b/src/logid/actions/gesture/AxisGesture.cpp index 8ec26f5..ac4a810 100644 --- a/src/logid/actions/gesture/AxisGesture.cpp +++ b/src/logid/actions/gesture/AxisGesture.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,161 +16,170 @@ * */ #include -#include "AxisGesture.h" -#include "../../InputDevice.h" -#include "../../util/log.h" +#include +#include +#include +#include using namespace logid::actions; -AxisGesture::AxisGesture(Device *device, libconfig::Setting &root) : - Gesture (device), _config (device, root) -{ +const char* AxisGesture::interface_name = "Axis"; + +AxisGesture::AxisGesture(Device* device, config::AxisGesture& config, + const std::shared_ptr& parent) : + Gesture(device, parent, interface_name, { + { + {"GetConfig", {this, &AxisGesture::getConfig, {"axis", "multiplier", "threshold"}}}, + {"SetThreshold", {this, &AxisGesture::setThreshold, {"threshold"}}}, + {"SetMultiplier", {this, &AxisGesture::setMultiplier, {"multiplier"}}}, + {"SetAxis", {this, &AxisGesture::setAxis, {"axis"}}} + }, + {}, + {} + }), _multiplier(1), _config(config) { + if (_config.axis.has_value()) { + if (std::holds_alternative(_config.axis.value())) { + _input_axis = std::get(_config.axis.value()); + } else { + const auto& axis = std::get(_config.axis.value()); + try { + _input_axis = _device->virtualInput()->toAxisCode(axis); + } catch (InputDevice::InvalidEventCode& e) { + logPrintf(WARN, "Invalid axis %s."); + } + } + + } + + if (_input_axis.has_value()) + _device->virtualInput()->registerAxis(_input_axis.value()); } -void AxisGesture::press(bool init_threshold) -{ - _axis = init_threshold ? _config.threshold() : 0; +void AxisGesture::press(bool init_threshold) { + std::shared_lock lock(_config_mutex); + if (init_threshold) { + _axis = (int32_t) (_config.threshold.value_or(defaults::gesture_threshold)); + } else { + _axis = 0; + } _axis_remainder = 0; _hires_remainder = 0; } -void AxisGesture::release(bool primary) -{ +void AxisGesture::release(bool primary) { // Do nothing - (void)primary; // Suppress unused warning + (void) primary; // Suppress unused warning } -void AxisGesture::move(int16_t axis) -{ - int16_t new_axis = _axis+axis; - int low_res_axis = InputDevice::getLowResAxis(_config.axis()); +void AxisGesture::move(int16_t axis) { + std::shared_lock lock(_config_mutex); + if (!_input_axis.has_value()) + return; + + const auto threshold = _config.threshold.value_or( + defaults::gesture_threshold); + int32_t new_axis = _axis + axis; + int low_res_axis = InputDevice::getLowResAxis(axis); int hires_remainder = _hires_remainder; - if(new_axis > _config.threshold()) { + if (new_axis > threshold) { double move = axis; - if(_axis < _config.threshold()) - move = new_axis - _config.threshold(); - bool negative_multiplier = _config.multiplier() < 0; - if(negative_multiplier) - move *= -_config.multiplier(); + if (_axis < threshold) + move = new_axis - threshold; + bool negative_multiplier = _config.axis_multiplier.value_or(1) < 0; + if (negative_multiplier) + move *= -_config.axis_multiplier.value_or(1); else - move *= _config.multiplier(); + move *= _config.axis_multiplier.value_or(1); + // Handle hi-res multiplier + move *= _multiplier; double move_floor = floor(move); _axis_remainder = move - move_floor; - if(_axis_remainder >= 1) { + if (_axis_remainder >= 1) { double int_remainder = floor(_axis_remainder); move_floor += int_remainder; _axis_remainder -= int_remainder; } - if(negative_multiplier) + if (negative_multiplier) move_floor = -move_floor; - if(low_res_axis != -1) { - int lowres_movement = 0, hires_movement = move_floor; - virtual_input->moveAxis(_config.axis(), hires_movement); + if (low_res_axis != -1) { + int lowres_movement, hires_movement = (int) move_floor; + _device->virtualInput()->moveAxis(_input_axis.value(), hires_movement); hires_remainder += hires_movement; - if(abs(hires_remainder) >= 60) { - lowres_movement = hires_remainder/120; - if(lowres_movement == 0) + if (abs(hires_remainder) >= 60) { + lowres_movement = hires_remainder / 120; + if (lowres_movement == 0) lowres_movement = hires_remainder > 0 ? 1 : -1; - hires_remainder -= lowres_movement*120; - virtual_input->moveAxis(low_res_axis, lowres_movement); + hires_remainder -= lowres_movement * 120; + _device->virtualInput()->moveAxis(low_res_axis, lowres_movement); } _hires_remainder = hires_remainder; } else { - virtual_input->moveAxis(_config.axis(), move_floor); + _device->virtualInput()->moveAxis(_input_axis.value(), (int) move_floor); } } _axis = new_axis; } -bool AxisGesture::metThreshold() const -{ - return _axis >= _config.threshold(); +bool AxisGesture::metThreshold() const { + std::shared_lock lock(_config_mutex); + return _axis >= _config.threshold.value_or(defaults::gesture_threshold); } -void AxisGesture::setHiresMultiplier(double multiplier) -{ - _config.setHiresMultiplier(multiplier); -} - -AxisGesture::Config::Config(Device *device, libconfig::Setting &setting) : - Gesture::Config(device, setting, false) -{ - try { - auto& axis = setting.lookup("axis"); - if(axis.isNumber()) { - _axis = axis; - virtual_input->registerAxis(_axis); - } else if(axis.getType() == libconfig::Setting::TypeString) { - try { - _axis = virtual_input->toAxisCode(axis); - virtual_input->registerAxis(_axis); - } catch(InputDevice::InvalidEventCode& e) { - logPrintf(WARN, "Line %d: Invalid axis %s, skipping." - , axis.getSourceLine(), axis.c_str()); - } - } else { - logPrintf(WARN, "Line %d: axis must be string or int, skipping.", - axis.getSourceLine(), axis.c_str()); - throw InvalidGesture(); - } - } catch(libconfig::SettingNotFoundException& e) { - logPrintf(WARN, "Line %d: axis is a required field, skippimg.", - setting.getSourceLine()); - throw InvalidGesture(); - } - - try { - auto& multiplier = setting.lookup("axis_multiplier"); - if(multiplier.isNumber()) { - if(multiplier.getType() == libconfig::Setting::TypeFloat) - _multiplier = multiplier; - else - _multiplier = (int)multiplier; - } else { - logPrintf(WARN, "Line %d: axis_multiplier must be a number, " - "setting to default (1).", - multiplier.getSourceLine()); - } - } catch(libconfig::SettingNotFoundException& e) { - // Ignore - } - - int low_res_axis = InputDevice::getLowResAxis(_axis); - if(low_res_axis != -1) { - _multiplier *= 120; - virtual_input->registerAxis(low_res_axis); - } -} - -unsigned int AxisGesture::Config::axis() const -{ - return _axis; -} - -double AxisGesture::Config::multiplier() const -{ - return _multiplier; -} - -bool AxisGesture::wheelCompatibility() const -{ +bool AxisGesture::wheelCompatibility() const { return true; } -void AxisGesture::Config::setHiresMultiplier(double multiplier) -{ - if(_hires_multiplier == multiplier || multiplier == 0) - return; +void AxisGesture::setHiresMultiplier(double multiplier) { + _hires_multiplier = multiplier; + if (_input_axis.has_value()) { + if (InputDevice::getLowResAxis(_input_axis.value()) != -1) + _multiplier = _config.axis_multiplier.value_or(1) * multiplier; + } +} - if(InputDevice::getLowResAxis(_axis) != -1) { - _multiplier *= _hires_multiplier; - _multiplier /= multiplier; +std::tuple AxisGesture::getConfig() const { + std::shared_lock lock(_config_mutex); + std::string axis; + if (_config.axis.has_value()) { + if (std::holds_alternative(_config.axis.value())) { + axis = std::get(_config.axis.value()); + } else { + axis = _device->virtualInput()->toAxisName(std::get(_config.axis.value())); + } } - _hires_multiplier = multiplier; + return {axis, _config.axis_multiplier.value_or(1), _config.threshold.value_or(0)}; +} + +void AxisGesture::setAxis(const std::string& axis) { + std::unique_lock lock(_config_mutex); + if (axis.empty()) { + _config.axis.reset(); + _input_axis.reset(); + } else { + _input_axis = _device->virtualInput()->toAxisCode(axis); + _config.axis = axis; + _device->virtualInput()->registerAxis(_input_axis.value()); + } + setHiresMultiplier(_hires_multiplier); +} + +void AxisGesture::setMultiplier(double multiplier) { + std::unique_lock lock(_config_mutex); + _config.axis_multiplier = multiplier; + _multiplier = multiplier; + setHiresMultiplier(_hires_multiplier); +} + +void AxisGesture::setThreshold(int threshold) { + std::unique_lock lock(_config_mutex); + if (threshold == 0) + _config.threshold.reset(); + else + _config.threshold = threshold; } diff --git a/src/logid/actions/gesture/AxisGesture.h b/src/logid/actions/gesture/AxisGesture.h index 5352f9f..eccb5fb 100644 --- a/src/logid/actions/gesture/AxisGesture.h +++ b/src/logid/actions/gesture/AxisGesture.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,44 +18,45 @@ #ifndef LOGID_ACTION_AXISGESTURE_H #define LOGID_ACTION_AXISGESTURE_H -#include "Gesture.h" +#include -namespace logid { - namespace actions - { - class AxisGesture : public Gesture - { - public: - AxisGesture(Device* device, libconfig::Setting& root); +namespace logid::actions { + class AxisGesture : public Gesture { + public: + static const char* interface_name; - virtual void press(bool init_threshold=false); - virtual void release(bool primary=false); - virtual void move(int16_t axis); + AxisGesture(Device* device, config::AxisGesture& config, + const std::shared_ptr& parent); - virtual bool wheelCompatibility() const; - virtual bool metThreshold() const; + void press(bool init_threshold) final; - void setHiresMultiplier(double multiplier); + void release(bool primary) final; - class Config : public Gesture::Config - { - public: - Config(Device* device, libconfig::Setting& setting); - unsigned int axis() const; - double multiplier() const; - void setHiresMultiplier(double multiplier); - private: - unsigned int _axis; - double _multiplier = 1; - double _hires_multiplier = 1; - }; + void move(int16_t axis) final; - protected: - int16_t _axis; - double _axis_remainder; - int _hires_remainder; - Config _config; - }; - }} + [[nodiscard]] bool wheelCompatibility() const final; + + [[nodiscard]] bool metThreshold() const final; + + void setHiresMultiplier(double multiplier); + + [[nodiscard]] std::tuple getConfig() const; + + void setAxis(const std::string& axis); + + void setMultiplier(double multiplier); + + void setThreshold(int threshold); + + protected: + int32_t _axis{}; + double _axis_remainder{}; + int _hires_remainder{}; + std::optional _input_axis; + double _multiplier; + double _hires_multiplier = 1.0; + config::AxisGesture& _config; + }; +} #endif //LOGID_ACTION_AXISGESTURE_H diff --git a/src/logid/actions/gesture/Gesture.cpp b/src/logid/actions/gesture/Gesture.cpp index eb3bcfe..53ec4ba 100644 --- a/src/logid/actions/gesture/Gesture.cpp +++ b/src/logid/actions/gesture/Gesture.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,106 +16,73 @@ * */ -#include -#include "Gesture.h" -#include "../../util/log.h" -#include "ReleaseGesture.h" -#include "ThresholdGesture.h" -#include "../../backend/hidpp20/features/ReprogControls.h" -#include "IntervalGesture.h" -#include "AxisGesture.h" -#include "NullGesture.h" +#include +#include +#include +#include +#include +#include +#include +#include +using namespace logid; using namespace logid::actions; -Gesture::Gesture(Device *device) : _device (device) -{ +Gesture::Gesture(Device* device, + std::shared_ptr node, + const std::string& name, tables t) : + ipcgull::interface(SERVICE_ROOT_NAME ".Gesture." + name, std::move(t)), + _node(std::move(node)), _device(device) { } -Gesture::Config::Config(Device* device, libconfig::Setting& root, - bool action_required) : _device (device) -{ - if(action_required) { - try { - _action = Action::makeAction(_device, - root.lookup("action")); - } catch (libconfig::SettingNotFoundException &e) { - throw InvalidGesture("action is missing"); - } +namespace { + template + struct gesture_type { + typedef typename T::gesture type; + }; - if(_action->reprogFlags() & backend::hidpp20::ReprogControls::RawXYDiverted) - throw InvalidGesture("gesture cannot require RawXY"); - } + template + struct gesture_type : gesture_type { + }; - _threshold = LOGID_GESTURE_DEFAULT_THRESHOLD; - try { - auto& threshold = root.lookup("threshold"); - if(threshold.getType() == libconfig::Setting::TypeInt) { - _threshold = (int)threshold; - if(_threshold <= 0) { - _threshold = LOGID_GESTURE_DEFAULT_THRESHOLD; - logPrintf(WARN, "Line %d: threshold must be positive, setting " - "to default (%d)", threshold.getSourceLine(), - _threshold); - } - } else - logPrintf(WARN, "Line %d: threshold must be an integer, setting " - "to default (%d).", threshold.getSourceLine()); - } catch(libconfig::SettingNotFoundException& e) { - // Ignore + template + struct gesture_type : gesture_type { + }; + + template + std::shared_ptr _makeGesture( + Device* device, T& gesture, + const std::shared_ptr& parent) { + return parent->make_interface::type>( + device, std::forward(gesture), parent); } } -std::shared_ptr Gesture::makeGesture(Device *device, - libconfig::Setting &setting) -{ - if(!setting.isGroup()) { - logPrintf(WARN, "Line %d: Gesture is not a group, ignoring.", - setting.getSourceLine()); +std::shared_ptr Gesture::makeGesture( + Device* device, config::Gesture& gesture, + const std::shared_ptr& parent) { + return std::visit([&device, &parent](auto&& x) { + return _makeGesture(device, x, parent); + }, gesture); +} + +std::shared_ptr Gesture::makeGesture( + Device* device, const std::string& type, + config::Gesture& config, + const std::shared_ptr& parent) { + if (type == AxisGesture::interface_name) { + config = config::AxisGesture(); + } else if (type == IntervalGesture::interface_name) { + config = config::IntervalGesture(); + } else if (type == ReleaseGesture::interface_name) { + config = config::ReleaseGesture(); + } else if (type == ThresholdGesture::interface_name) { + config = config::ThresholdGesture(); + } else if (type == NullGesture::interface_name) { + config = config::NoGesture(); + } else { throw InvalidGesture(); } - try { - auto& gesture_mode = setting.lookup("mode"); - - if(gesture_mode.getType() != libconfig::Setting::TypeString) { - logPrintf(WARN, "Line %d: Gesture mode must be a string," - "defaulting to OnRelease.", - gesture_mode.getSourceLine()); - return std::make_shared(device, setting); - } - - std::string type = gesture_mode; - std::transform(type.begin(), type.end(), type.begin(), ::tolower); - - if(type == "onrelease") - return std::make_shared(device, setting); - else if(type == "onthreshold") - return std::make_shared(device, setting); - else if(type == "oninterval" || type == "onfewpixels") - return std::make_shared(device, setting); - else if(type == "axis") - return std::make_shared(device, setting); - else if(type == "nopress") - return std::make_shared(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(device, setting); - } - - } catch(libconfig::SettingNotFoundException& e) { - return std::make_shared(device, setting); - } + return makeGesture(device, config, parent); } - -int16_t Gesture::Config::threshold() const -{ - return _threshold; -} - -std::shared_ptr Gesture::Config::action() -{ - return _action; -} \ No newline at end of file diff --git a/src/logid/actions/gesture/Gesture.h b/src/logid/actions/gesture/Gesture.h index dcb84ea..13822b6 100644 --- a/src/logid/actions/gesture/Gesture.h +++ b/src/logid/actions/gesture/Gesture.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,59 +18,56 @@ #ifndef LOGID_ACTION_GESTURE_H #define LOGID_ACTION_GESTURE_H -#include "../Action.h" +#include +#include -#define LOGID_GESTURE_DEFAULT_THRESHOLD 50 - -namespace logid { -namespace actions -{ - class InvalidGesture : public std::exception - { +namespace logid::actions { + class InvalidGesture : public std::exception { public: - explicit InvalidGesture(std::string what="") : _what (what) - { + explicit InvalidGesture(std::string what = "") : _what(std::move(what)) { } - virtual const char* what() const noexcept - { + + [[nodiscard]] const char* what() const noexcept override { return _what.c_str(); } + private: std::string _what; }; - class Gesture - { + class Gesture : public ipcgull::interface { public: - virtual void press(bool init_threshold=false) = 0; - virtual void release(bool primary=false) = 0; + virtual void press(bool init_threshold) = 0; + + virtual void release(bool primary) = 0; + virtual void move(int16_t axis) = 0; - virtual bool wheelCompatibility() const = 0; - virtual bool metThreshold() const = 0; + [[nodiscard]] virtual bool wheelCompatibility() const = 0; + + [[nodiscard]] virtual bool metThreshold() const = 0; virtual ~Gesture() = default; - class Config - { - public: - Config(Device* device, libconfig::Setting& root, - bool action_required=true); - virtual int16_t threshold() const; - virtual std::shared_ptr action(); - protected: - Device* _device; - std::shared_ptr _action; - int16_t _threshold; - }; - static std::shared_ptr makeGesture(Device* device, - libconfig::Setting& setting); + config::Gesture& gesture, + const std::shared_ptr& parent); + + static std::shared_ptr makeGesture( + Device* device, const std::string& type, + config::Gesture& gesture, + const std::shared_ptr& parent); protected: - explicit Gesture(Device* device); + Gesture(Device* device, + std::shared_ptr parent, + const std::string& name, tables t = {}); + + mutable std::shared_mutex _config_mutex; + + const std::shared_ptr _node; Device* _device; }; -}} +} #endif //LOGID_ACTION_GESTURE_H diff --git a/src/logid/actions/gesture/IntervalGesture.cpp b/src/logid/actions/gesture/IntervalGesture.cpp index a3d8438..48c61aa 100644 --- a/src/logid/actions/gesture/IntervalGesture.cpp +++ b/src/logid/actions/gesture/IntervalGesture.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,82 +15,103 @@ * along with this program. If not, see . * */ -#include "IntervalGesture.h" -#include "../../util/log.h" +#include +#include +#include using namespace logid::actions; -IntervalGesture::IntervalGesture(Device *device, libconfig::Setting &root) : - Gesture (device), _config (device, root) -{ +const char* IntervalGesture::interface_name = "OnInterval"; + +IntervalGesture::IntervalGesture( + Device* device, config::IntervalGesture& config, + const std::shared_ptr& parent) : + Gesture(device, parent, interface_name, { + { + {"GetConfig", {this, &IntervalGesture::getConfig, {"interval", "threshold"}}}, + {"SetInterval", {this, &IntervalGesture::setInterval, {"interval"}}}, + {"SetThreshold", {this, &IntervalGesture::setThreshold, {"interval"}}}, + {"SetAction", {this, &IntervalGesture::setAction, {"type"}}} + }, + {}, + {} + }), + _axis(0), _interval_pass_count(0), _config(config) { + if (config.action) { + try { + _action = Action::makeAction(device, config.action.value(), _node); + } catch (InvalidAction& e) { + logPrintf(WARN, "Mapping gesture to invalid action"); + } + } } -void IntervalGesture::press(bool init_threshold) -{ - _axis = init_threshold ? _config.threshold() : 0; +void IntervalGesture::press(bool init_threshold) { + std::shared_lock lock(_config_mutex); + if (init_threshold) { + _axis = (int32_t) _config.threshold.value_or(defaults::gesture_threshold); + } else { + _axis = 0; + } _interval_pass_count = 0; } -void IntervalGesture::release(bool primary) -{ - // Do nothing - (void)primary; // Suppress unused warning +void IntervalGesture::release([[maybe_unused]] bool primary) { } -void IntervalGesture::move(int16_t axis) -{ - _axis += axis; - if(_axis < _config.threshold()) +void IntervalGesture::move(int16_t axis) { + std::shared_lock lock(_config_mutex); + if (!_config.interval.has_value()) return; - int16_t new_interval_count = (_axis - _config.threshold())/ - _config.interval(); - if(new_interval_count > _interval_pass_count) { - _config.action()->press(); - _config.action()->release(); + const auto threshold = + _config.threshold.value_or(defaults::gesture_threshold); + _axis += axis; + if (_axis < threshold) + return; + + int32_t new_interval_count = (_axis - threshold) / _config.interval.value(); + if (new_interval_count > _interval_pass_count) { + if (_action) { + _action->press(); + _action->release(); + } } _interval_pass_count = new_interval_count; } -bool IntervalGesture::wheelCompatibility() const -{ +bool IntervalGesture::wheelCompatibility() const { return true; } -bool IntervalGesture::metThreshold() const -{ - return _axis >= _config.threshold(); +bool IntervalGesture::metThreshold() const { + std::shared_lock lock(_config_mutex); + return _axis >= _config.threshold.value_or(defaults::gesture_threshold); } -IntervalGesture::Config::Config(Device *device, libconfig::Setting &setting) : - Gesture::Config(device, setting) -{ - try { - auto& interval = setting.lookup("interval"); - if(interval.getType() != libconfig::Setting::TypeInt) { - logPrintf(WARN, "Line %d: interval must be an integer, skipping.", - interval.getSourceLine()); - throw InvalidGesture(); - } - _interval = (int)interval; - } catch(libconfig::SettingNotFoundException& e) { - try { - // pixels is an alias for interval - auto& interval = setting.lookup("pixels"); - if(interval.getType() != libconfig::Setting::TypeInt) { - logPrintf(WARN, "Line %d: pixels must be an integer, skipping.", - interval.getSourceLine()); - throw InvalidGesture(); - } - _interval = (int)interval; - } catch(libconfig::SettingNotFoundException& e) { - logPrintf(WARN, "Line %d: interval is a required field, skipping.", - setting.getSourceLine()); - } - } +std::tuple IntervalGesture::getConfig() const { + std::shared_lock lock(_config_mutex); + return {_config.interval.value_or(0), _config.threshold.value_or(0)}; } -int16_t IntervalGesture::Config::interval() const -{ - return _interval; -} \ No newline at end of file +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); +} diff --git a/src/logid/actions/gesture/IntervalGesture.h b/src/logid/actions/gesture/IntervalGesture.h index 268822d..434e192 100644 --- a/src/logid/actions/gesture/IntervalGesture.h +++ b/src/logid/actions/gesture/IntervalGesture.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,37 +18,41 @@ #ifndef LOGID_ACTION_INTERVALGESTURE_H #define LOGID_ACTION_INTERVALGESTURE_H -#include "Gesture.h" +#include -namespace logid { -namespace actions -{ - class IntervalGesture : public Gesture - { +namespace logid::actions { + class IntervalGesture : public Gesture { public: - IntervalGesture(Device* device, libconfig::Setting& root); + static const char* interface_name; - virtual void press(bool init_threshold=false); - virtual void release(bool primary=false); - virtual void move(int16_t axis); + IntervalGesture(Device* device, config::IntervalGesture& config, + const std::shared_ptr& parent); - virtual bool wheelCompatibility() const; - virtual bool metThreshold() const; + void press(bool init_threshold) final; - class Config : public Gesture::Config - { - public: - Config(Device* device, libconfig::Setting& setting); - int16_t interval() const; - private: - int16_t _interval; - }; + void release(bool primary) final; + + void move(int16_t axis) final; + + [[nodiscard]] bool wheelCompatibility() const final; + + [[nodiscard]] bool metThreshold() const final; + + [[nodiscard]] std::tuple getConfig() const; + + void setInterval(int interval); + + void setThreshold(int threshold); + + void setAction(const std::string& type); protected: - int16_t _axis; - int16_t _interval_pass_count; - Config _config; + int32_t _axis; + int32_t _interval_pass_count; + std::shared_ptr _action; + config::IntervalGesture& _config; + private: }; -}} +} #endif //LOGID_ACTION_INTERVALGESTURE_H diff --git a/src/logid/actions/gesture/NullGesture.cpp b/src/logid/actions/gesture/NullGesture.cpp index dbb7e57..9cdb39b 100644 --- a/src/logid/actions/gesture/NullGesture.cpp +++ b/src/logid/actions/gesture/NullGesture.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,37 +15,37 @@ * along with this program. If not, see . * */ -#include "NullGesture.h" +#include +#include using namespace logid::actions; -NullGesture::NullGesture(Device *device, libconfig::Setting& setting) : - Gesture (device), _config (device, setting, false) -{ +const char* NullGesture::interface_name = "None"; + +NullGesture::NullGesture(Device* device, + config::NoGesture& config, + const std::shared_ptr& parent) : + Gesture(device, parent, interface_name), _config(config) { } -void NullGesture::press(bool init_threshold) -{ - _axis = init_threshold ? _config.threshold() : 0; +void NullGesture::press(bool init_threshold) { + _axis = init_threshold ? _config.threshold.value_or( + defaults::gesture_threshold) : 0; } -void NullGesture::release(bool primary) -{ +void NullGesture::release(bool primary) { // Do nothing - (void)primary; // Suppress unused warning + (void) primary; // Suppress unused warning } -void NullGesture::move(int16_t axis) -{ +void NullGesture::move(int16_t axis) { _axis += axis; } -bool NullGesture::wheelCompatibility() const -{ +bool NullGesture::wheelCompatibility() const { return true; } -bool NullGesture::metThreshold() const -{ - return _axis > _config.threshold(); +bool NullGesture::metThreshold() const { + return _axis >= _config.threshold.value_or(defaults::gesture_threshold); } \ No newline at end of file diff --git a/src/logid/actions/gesture/NullGesture.h b/src/logid/actions/gesture/NullGesture.h index 6b0f980..181ff37 100644 --- a/src/logid/actions/gesture/NullGesture.h +++ b/src/logid/actions/gesture/NullGesture.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,26 +18,31 @@ #ifndef LOGID_ACTION_NULLGESTURE_H #define LOGID_ACTION_NULLGESTURE_H -#include "Gesture.h" +#include -namespace logid { -namespace actions -{ - class NullGesture : public Gesture - { +namespace logid::actions { + class NullGesture : public Gesture { public: - NullGesture(Device* device, libconfig::Setting& setting); + static const char* interface_name; - virtual void press(bool init_threshold=false); - virtual void release(bool primary=false); - virtual void move(int16_t axis); + NullGesture(Device* device, + config::NoGesture& config, + const std::shared_ptr& parent); + + void press(bool init_threshold) final; + + void release(bool primary) final; + + void move(int16_t axis) final; + + [[nodiscard]] bool wheelCompatibility() const final; + + [[nodiscard]] bool metThreshold() const final; - virtual bool wheelCompatibility() const; - virtual bool metThreshold() const; protected: - int16_t _axis; - Gesture::Config _config; + int32_t _axis{}; + config::NoGesture& _config; }; -}} +} #endif //LOGID_ACTION_NULLGESTURE_H diff --git a/src/logid/actions/gesture/ReleaseGesture.cpp b/src/logid/actions/gesture/ReleaseGesture.cpp index 613412b..1d0b54c 100644 --- a/src/logid/actions/gesture/ReleaseGesture.cpp +++ b/src/logid/actions/gesture/ReleaseGesture.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,39 +15,75 @@ * along with this program. If not, see . * */ -#include "ReleaseGesture.h" +#include +#include using namespace logid::actions; -ReleaseGesture::ReleaseGesture(Device *device, libconfig::Setting &root) : - Gesture (device), _config (device, root) -{ +const char* ReleaseGesture::interface_name = "OnRelease"; + +ReleaseGesture::ReleaseGesture(Device* device, config::ReleaseGesture& config, + const std::shared_ptr& parent) : + Gesture(device, parent, interface_name, { + { + {"GetThreshold", {this, &ReleaseGesture::getThreshold, {"threshold"}}}, + {"SetThreshold", {this, &ReleaseGesture::setThreshold, {"threshold"}}}, + {"SetAction", {this, &ReleaseGesture::setAction, {"type"}}} + }, + {}, + {} + }), _config(config) { + if (_config.action.has_value()) + _action = Action::makeAction(device, _config.action.value(), _node); } -void ReleaseGesture::press(bool init_threshold) -{ - _axis = init_threshold ? _config.threshold() : 0; -} - -void ReleaseGesture::release(bool primary) -{ - if(metThreshold() && primary) { - _config.action()->press(); - _config.action()->release(); +void ReleaseGesture::press(bool init_threshold) { + std::shared_lock lock(_config_mutex); + if (init_threshold) { + _axis = (int32_t) (_config.threshold.value_or(defaults::gesture_threshold)); + } else { + _axis = 0; } } -void ReleaseGesture::move(int16_t axis) -{ +void ReleaseGesture::release(bool primary) { + if (metThreshold() && primary) { + if (_action) { + _action->press(); + _action->release(); + } + } +} + +void ReleaseGesture::move(int16_t axis) { _axis += axis; } -bool ReleaseGesture::wheelCompatibility() const -{ +bool ReleaseGesture::wheelCompatibility() const { return false; } -bool ReleaseGesture::metThreshold() const -{ - return _axis >= _config.threshold(); -} \ No newline at end of file +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); +} diff --git a/src/logid/actions/gesture/ReleaseGesture.h b/src/logid/actions/gesture/ReleaseGesture.h index 59cc528..f417d83 100644 --- a/src/logid/actions/gesture/ReleaseGesture.h +++ b/src/logid/actions/gesture/ReleaseGesture.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,27 +18,37 @@ #ifndef LOGID_ACTION_RELEASEGESTURE_H #define LOGID_ACTION_RELEASEGESTURE_H -#include "Gesture.h" +#include -namespace logid { -namespace actions -{ - class ReleaseGesture : public Gesture - { +namespace logid::actions { + class ReleaseGesture : public Gesture { public: - ReleaseGesture(Device* device, libconfig::Setting& root); + static const char* interface_name; - virtual void press(bool init_threshold=false); - virtual void release(bool primary=false); - virtual void move(int16_t axis); + ReleaseGesture(Device* device, config::ReleaseGesture& config, + const std::shared_ptr& parent); - virtual bool wheelCompatibility() const; - virtual bool metThreshold() const; + void press(bool init_threshold) final; + + void release(bool primary) final; + + void move(int16_t axis) final; + + [[nodiscard]] bool wheelCompatibility() const final; + + [[nodiscard]] bool metThreshold() const final; + + [[nodiscard]] int getThreshold() const; + + void setThreshold(int threshold); + + void setAction(const std::string& type); protected: - int16_t _axis; - Gesture::Config _config; + int32_t _axis{}; + std::shared_ptr _action; + config::ReleaseGesture& _config; }; -}} +} #endif //LOGID_ACTION_RELEASEGESTURE_H diff --git a/src/logid/actions/gesture/ThresholdGesture.cpp b/src/logid/actions/gesture/ThresholdGesture.cpp index 5e1ebd9..5dbaba8 100644 --- a/src/logid/actions/gesture/ThresholdGesture.cpp +++ b/src/logid/actions/gesture/ThresholdGesture.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne, michtere + * Copyright 2019-2023 PixlOne, michtere * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,45 +15,81 @@ * along with this program. If not, see . * */ -#include "ThresholdGesture.h" +#include +#include +#include using namespace logid::actions; -ThresholdGesture::ThresholdGesture(Device *device, libconfig::Setting &root) : - Gesture (device), _config (device, root) -{ +const char* ThresholdGesture::interface_name = "OnRelease"; + +ThresholdGesture::ThresholdGesture( + Device* device, config::ThresholdGesture& config, + const std::shared_ptr& parent) : + Gesture(device, parent, interface_name, { + { + {"GetThreshold", {this, &ThresholdGesture::getThreshold, {"threshold"}}}, + {"SetThreshold", {this, &ThresholdGesture::setThreshold, {"threshold"}}}, + {"SetAction", {this, &ThresholdGesture::setAction, {"type"}}} + }, + {}, + {} + }), _config(config) { + if (config.action) { + try { + _action = Action::makeAction(device, config.action.value(), _node); + } catch (InvalidAction& e) { + logPrintf(WARN, "Mapping gesture to invalid action"); + } + } } -void ThresholdGesture::press(bool init_threshold) -{ - _axis = init_threshold ? _config.threshold() : 0; +void ThresholdGesture::press(bool init_threshold) { + std::shared_lock lock(_config_mutex); + _axis = init_threshold ? (int32_t) _config.threshold.value_or(defaults::gesture_threshold) : 0; this->_executed = false; } -void ThresholdGesture::release(bool primary) -{ - (void)primary; // Suppress unused warning - +void ThresholdGesture::release([[maybe_unused]] bool primary) { this->_executed = false; } -void ThresholdGesture::move(int16_t axis) -{ +void ThresholdGesture::move(int16_t axis) { _axis += axis; - if(!this->_executed && metThreshold()) { - _config.action()->press(); - _config.action()->release(); + if (!this->_executed && metThreshold()) { + if (_action) { + _action->press(); + _action->release(); + } this->_executed = true; } } -bool ThresholdGesture::metThreshold() const -{ - return _axis >= _config.threshold(); +bool ThresholdGesture::metThreshold() const { + std::shared_lock lock(_config_mutex); + return _axis >= _config.threshold.value_or(defaults::gesture_threshold); } -bool ThresholdGesture::wheelCompatibility() const -{ +bool ThresholdGesture::wheelCompatibility() const { return false; -} \ No newline at end of file +} + +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); +} diff --git a/src/logid/actions/gesture/ThresholdGesture.h b/src/logid/actions/gesture/ThresholdGesture.h index 0a38b96..c5a90f1 100644 --- a/src/logid/actions/gesture/ThresholdGesture.h +++ b/src/logid/actions/gesture/ThresholdGesture.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne, michtere + * Copyright 2019-2023 PixlOne, michtere * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,31 +18,40 @@ #ifndef LOGID_ACTION_THRESHOLDGESTURE_H #define LOGID_ACTION_THRESHOLDGESTURE_H -#include "Gesture.h" +#include -namespace logid { -namespace actions -{ - class ThresholdGesture : public Gesture - { +namespace logid::actions { + class ThresholdGesture : public Gesture { public: - ThresholdGesture(Device* device, libconfig::Setting& root); + static const char* interface_name; - virtual void press(bool init_threshold=false); - virtual void release(bool primary=false); - virtual void move(int16_t axis); + ThresholdGesture(Device* device, config::ThresholdGesture& config, + const std::shared_ptr& parent); - virtual bool metThreshold() const; + void press(bool init_threshold) final; - virtual bool wheelCompatibility() const; + void release(bool primary) final; + + void move(int16_t axis) final; + + [[nodiscard]] bool metThreshold() const final; + + [[nodiscard]] bool wheelCompatibility() const final; + + [[nodiscard]] int getThreshold() const; + + void setThreshold(int threshold); + + void setAction(const std::string& type); protected: - int16_t _axis; - Gesture::Config _config; + int32_t _axis{}; + std::shared_ptr _action; + config::ThresholdGesture& _config; private: bool _executed = false; }; -}} +} #endif //LOGID_ACTION_THRESHOLDGESTURE_H diff --git a/src/logid/backend/Error.cpp b/src/logid/backend/Error.cpp index f15fc0e..6623e30 100644 --- a/src/logid/backend/Error.cpp +++ b/src/logid/backend/Error.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,9 +16,14 @@ * */ -#include "Error.h" +#include -const char *logid::backend::TimeoutError::what() const noexcept -{ +using namespace logid::backend; + +const char* DeviceNotReady::what() const noexcept { + return "device not ready"; +} + +const char* TimeoutError::what() const noexcept { return "Device timed out"; } diff --git a/src/logid/backend/Error.h b/src/logid/backend/Error.h index d281a3b..520923e 100644 --- a/src/logid/backend/Error.h +++ b/src/logid/backend/Error.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,14 +21,18 @@ #include -namespace logid { -namespace backend { -class TimeoutError: public std::exception -{ -public: - TimeoutError() = default; - const char* what() const noexcept override; -}; -}} +namespace logid::backend { + class DeviceNotReady : public std::exception { + public: + [[nodiscard]] const char* what() const noexcept override; + }; + + class TimeoutError : public std::exception { + public: + TimeoutError() = default; + + [[nodiscard]] const char* what() const noexcept override; + }; +} #endif //LOGID_BACKEND_ERROR_H \ No newline at end of file diff --git a/src/logid/backend/EventHandlerList.h b/src/logid/backend/EventHandlerList.h new file mode 100644 index 0000000..edad88c --- /dev/null +++ b/src/logid/backend/EventHandlerList.h @@ -0,0 +1,94 @@ +/* + * Copyright 2019-2023 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + + +#ifndef LOGID_EVENTHANDLERLIST_H +#define LOGID_EVENTHANDLERLIST_H + +#include +#include +#include +#include + +template +class EventHandlerLock; + +template +struct EventHandlerList { + typedef std::list list_t; + typedef typename list_t::const_iterator iterator_t; + + std::list list; + mutable std::shared_mutex mutex; + + void remove(iterator_t iterator) { + std::unique_lock lock(mutex); + list.erase(iterator); + } +}; + +template +class EventHandlerLock { + typedef EventHandlerList list_t; + typedef typename list_t::iterator_t iterator_t; + + friend T; + + std::weak_ptr _list; + iterator_t _iterator; + + EventHandlerLock(const std::shared_ptr& 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 diff --git a/src/logid/backend/dj/Receiver.cpp b/src/logid/backend/dj/Receiver.cpp deleted file mode 100644 index 40cbbbd..0000000 --- a/src/logid/backend/dj/Receiver.cpp +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright 2019-2020 PixlOne - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#include -#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(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 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 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 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 request(3); - - request[0] = 3; - request[1] = index; - - _hidpp10_device.setRegister(DevicePairing, request, - hidpp::ReportType::Short); -} - -std::map Receiver::getDeviceActivity() -{ - auto response = _hidpp10_device.getRegister(DeviceActivity, {}, - hidpp::ReportType::Long); - - std::map device_activity; - for(uint8_t i = hidpp::WirelessDevice1; i <= hidpp::WirelessDevice6; i++) - device_activity[static_cast(i)] = response[i]; - - return device_activity; -} - -struct Receiver::PairingInfo - Receiver::getPairingInfo(hidpp::DeviceIndex index) -{ - std::vector 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(response[7]); - - return info; -} - -struct Receiver::ExtendedPairingInfo - Receiver::getExtendedPairingInfo(hidpp::DeviceIndex index) -{ - std::vector 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(psl); - - return info; -} - -std::string Receiver::getDeviceName(hidpp::DeviceIndex index) -{ - std::vector 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( - 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& 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>& -Receiver::djEventHandlers() -{ - return _dj_event_handlers; -} - -void Receiver::addHidppEventHandler(const std::string& nickname, - const std::shared_ptr& 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>& -Receiver::hidppEventHandlers() -{ - return _hidpp_event_handlers; -} - -void Receiver::_sendDjRequest(hidpp::DeviceIndex index, uint8_t function, - const std::vector&& 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 hidpp_handler = - std::make_shared(); - hidpp_handler->condition = [](std::vector& 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& 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 dj_handler = - std::make_shared(); - dj_handler->condition = [](std::vector& report)->bool - { - return (report[Offset::Type] == Report::Type::Short || - report[Offset::Type] == Report::Type::Long); - }; - dj_handler->callback = [this](std::vector& 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 Receiver::rawDevice() const -{ - return _raw_device; -} \ No newline at end of file diff --git a/src/logid/backend/dj/Receiver.h b/src/logid/backend/dj/Receiver.h deleted file mode 100644 index 767689a..0000000 --- a/src/logid/backend/dj/Receiver.h +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright 2019-2020 PixlOne - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#ifndef LOGID_BACKEND_DJ_RECEIVER_H -#define LOGID_BACKEND_DJ_RECEIVER_H - -#include -#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 condition; - std::function callback; - }; - - class InvalidReceiver : public std::exception - { - public: - enum Reason - { - NoDJReports - }; - explicit InvalidReceiver(Reason reason); - const char* what() const noexcept override; - Reason code() const noexcept; - private: - Reason _reason; - }; - - class Receiver final - { - public: - explicit Receiver(std::string path); - - enum DjEvents : uint8_t - { - DeviceDisconnection = 0x40, - DeviceConnection = 0x41, - ConnectionStatus = 0x42 - }; - - enum DjCommands : uint8_t - { - SwitchAndKeepAlive = 0x80, - GetPairedDevices = 0x81 - }; - - void enumerateDj(); - - struct ConnectionStatusEvent - { - hidpp::DeviceIndex index; - bool linkLost; - }; - - ConnectionStatusEvent connectionStatusEvent(dj::Report& report); - - /* The following functions deal with HID++ 1.0 features. - * While these are not technically DJ functions, it is redundant - * to have a separate hidpp10::Receiver class for these functions. - */ - - enum HidppEvents : uint8_t - { - // These events are identical to their DJ counterparts - // DeviceDisconnection = 0x40, - // DeviceConnection = 0x41, - LockingChange = 0x4a - }; - - enum HidppRegisters : uint8_t - { - EnableHidppNotifications = 0x00, - ConnectionState = 0x02, - DevicePairing = 0xb2, - DeviceActivity = 0xb3, - PairingInfo = 0xb5 - }; - - struct NotificationFlags - { - bool deviceBatteryStatus; - bool receiverWirelessNotifications; - bool receiverSoftwarePresent; - }; - - NotificationFlags getHidppNotifications(); - void enableHidppNotifications(NotificationFlags flags); - - void enumerateHidpp(); - uint8_t getConnectionState(hidpp::DeviceIndex index); - - void startPairing(uint8_t timeout = 0); - void stopPairing(); - void disconnect(hidpp::DeviceIndex index); - - std::map 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& handler); - void removeDjEventHandler(const std::string& nickname); - const std::map>& - djEventHandlers(); - - void addHidppEventHandler(const std::string& nickname, - const std::shared_ptr& handler); - void removeHidppEventHandler(const std::string& nickname); - const std::map>& - hidppEventHandlers(); - - std::shared_ptr rawDevice() const; - private: - void _sendDjRequest(hidpp::DeviceIndex index, uint8_t function, - const std::vector&& params); - - void _handleDjEvent(dj::Report& report); - void _handleHidppEvent(hidpp::Report& report); - - std::map> - _dj_event_handlers; - std::map> - _hidpp_event_handlers; - - std::shared_ptr _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 \ No newline at end of file diff --git a/src/logid/backend/dj/ReceiverMonitor.cpp b/src/logid/backend/dj/ReceiverMonitor.cpp deleted file mode 100644 index 80a9283..0000000 --- a/src/logid/backend/dj/ReceiverMonitor.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2019-2020 PixlOne - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#include "ReceiverMonitor.h" -#include "../../util/task.h" -#include "../../util/log.h" - -#include -#include - -using namespace logid::backend::dj; - -ReceiverMonitor::ReceiverMonitor(std::string path) : _receiver ( - std::make_shared(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 event_handler = - std::make_shared(); - 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(); - handler->condition = [index](std::vector& report)->bool { - return report[Offset::DeviceIndex] == index; - }; - - handler->callback = [this, index, nickname](std::vector& 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 ReceiverMonitor::receiver() const -{ - return _receiver; -} \ No newline at end of file diff --git a/src/logid/backend/dj/ReceiverMonitor.h b/src/logid/backend/dj/ReceiverMonitor.h deleted file mode 100644 index 88d7686..0000000 --- a/src/logid/backend/dj/ReceiverMonitor.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2019-2020 PixlOne - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#ifndef LOGID_BACKEND_DJ_RECEIVERMONITOR_H -#define LOGID_BACKEND_DJ_RECEIVERMONITOR_H - -#include -#include -#include "Receiver.h" -#include "../hidpp/defs.h" - -namespace logid { -namespace backend { -namespace dj -{ - // This class will run on the RawDevice thread, - class ReceiverMonitor - { - public: - explicit ReceiverMonitor(std::string path); - virtual ~ReceiverMonitor(); - - void enumerate(); - void run(); - void stop(); - - protected: - virtual void addDevice(hidpp::DeviceConnectionEvent event) = 0; - virtual void removeDevice(hidpp::DeviceIndex index) = 0; - - void waitForDevice(hidpp::DeviceIndex index); - - // Internal methods for derived class - void _pair(uint8_t timeout = 0); - void _stopPairing(); - - void _unpair(); - - std::shared_ptr receiver() const; - private: - std::shared_ptr _receiver; - }; - -}}} - -#endif //LOGID_BACKEND_DJ_RECEIVERMONITOR_H \ No newline at end of file diff --git a/src/logid/backend/dj/Report.cpp b/src/logid/backend/dj/Report.cpp deleted file mode 100644 index dff4040..0000000 --- a/src/logid/backend/dj/Report.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2019-2020 PixlOne - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include "Report.h" - -using namespace logid::backend::dj; -using namespace logid::backend; - -static const std::array 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 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&& 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& 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(_data[Offset::Type]); -} - -hidpp::DeviceIndex Report::index() const -{ - return static_cast(_data[Offset::DeviceIndex]); -} - -uint8_t Report::feature() const -{ - return _data[Offset::Feature]; -} - -std::vector::iterator Report::paramBegin() -{ - return _data.begin() + Offset::Parameters; -} - -std::vector Report::rawData() const -{ - return _data; -} diff --git a/src/logid/backend/dj/Report.h b/src/logid/backend/dj/Report.h deleted file mode 100644 index 76439a5..0000000 --- a/src/logid/backend/dj/Report.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2019-2020 PixlOne - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#ifndef LOGID_BACKEND_DJ_REPORT_H -#define LOGID_BACKEND_DJ_REPORT_H - -#include -#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&& rdesc); - class Report - { - public: - typedef ReportType::ReportType Type; - - explicit Report(std::vector& data); - Report(Type type, hidpp::DeviceIndex index, uint8_t feature); - - Type type() const; - hidpp::DeviceIndex index() const; - uint8_t feature() const; - std::vector::iterator paramBegin(); - std::vector rawData() const; - private: - std::vector _data; - }; -}}} - -#endif //LOGID_BACKEND_DJ_REPORT_H diff --git a/src/logid/backend/dj/defs.h b/src/logid/backend/dj/defs.h deleted file mode 100644 index d527d9b..0000000 --- a/src/logid/backend/dj/defs.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2019-2020 PixlOne - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#ifndef LOGID_BACKEND_DJ_DEFS_H -#define LOGID_BACKEND_DJ_DEFS_H - -#include - -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 \ No newline at end of file diff --git a/src/logid/backend/hidpp/Device.cpp b/src/logid/backend/hidpp/Device.cpp index cc9db25..f0da17c 100644 --- a/src/logid/backend/hidpp/Device.cpp +++ b/src/logid/backend/hidpp/Device.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,130 +16,195 @@ * */ +#include +#include +#include +#include +#include +#include #include #include -#include "../../util/thread.h" -#include "Device.h" -#include "Report.h" -#include "../hidpp20/features/Root.h" -#include "../hidpp20/features/DeviceName.h" -#include "../hidpp20/Error.h" -#include "../hidpp10/Error.h" -#include "../dj/Receiver.h" using namespace logid::backend; using namespace logid::backend::hidpp; -const char* Device::InvalidDevice::what() const noexcept -{ - switch(_reason) { - case NoHIDPPReport: - return "Invalid HID++ device"; - case InvalidRawDevice: - return "Invalid raw device"; - case Asleep: - return "Device asleep"; - default: - return "Invalid device"; +using namespace std::chrono; + +const char* Device::InvalidDevice::what() const noexcept { + switch (_reason) { + case NoHIDPPReport: + return "Invalid HID++ device"; + case InvalidRawDevice: + return "Invalid raw device"; + case Asleep: + return "Device asleep"; + case VirtualNode: + return "Virtual device"; + default: + return "Invalid device"; } } -Device::InvalidDevice::Reason Device::InvalidDevice::code() const noexcept -{ +Device::InvalidDevice::Reason Device::InvalidDevice::code() const noexcept { return _reason; } -Device::Device(const std::string& path, DeviceIndex index): - _raw_device (std::make_shared(path)), _receiver (nullptr), - _path (path), _index (index) -{ - _init(); +Device::Device(const std::string& path, DeviceIndex index, + const std::shared_ptr& monitor, double timeout) : + io_timeout(duration_cast( + duration(timeout))), + _raw_device(std::make_shared(path, monitor)), + _receiver(nullptr), _path(path), _index(index) { } -Device::Device(std::shared_ptr raw_device, DeviceIndex index) : - _raw_device (std::move(raw_device)), _receiver (nullptr), - _path (_raw_device->hidrawPath()), _index (index) -{ - _init(); +Device::Device(std::shared_ptr raw_device, DeviceIndex index, + double timeout) : + io_timeout(duration_cast( + duration(timeout))), + _raw_device(std::move(raw_device)), _receiver(nullptr), + _path(_raw_device->rawPath()), _index(index) { } -Device::Device(std::shared_ptr receiver, - hidpp::DeviceConnectionEvent event) : - _raw_device (receiver->rawDevice()), _receiver (receiver), - _path (receiver->rawDevice()->hidrawPath()), _index (event.index) -{ +Device::Device(const std::shared_ptr& receiver, + hidpp::DeviceConnectionEvent event, double timeout) : + io_timeout(duration_cast( + duration(timeout))), + _raw_device(receiver->rawDevice()), _receiver(receiver), + _path(receiver->rawDevice()->rawPath()), _index(event.index) { // Device will throw an error soon, just do it now - if(!event.linkEstablished) + if (!event.linkEstablished) throw InvalidDevice(InvalidDevice::Asleep); - if(!event.fromTimeoutCheck) + if (!event.fromTimeoutCheck) _pid = event.pid; else _pid = receiver->getPairingInfo(_index).pid; - _init(); } -Device::Device(std::shared_ptr receiver, - DeviceIndex index) : _raw_device (receiver->rawDevice()), - _receiver (receiver), _path (receiver->rawDevice()->hidrawPath()), - _index (index) -{ +Device::Device(const std::shared_ptr& receiver, + DeviceIndex index, double timeout) : + io_timeout(duration_cast( + duration(timeout))), + _raw_device(receiver->rawDevice()), + _receiver(receiver), _path(receiver->rawDevice()->rawPath()), + _index(index) { _pid = receiver->getPairingInfo(_index).pid; - _init(); } -std::string Device::devicePath() const -{ +const std::string& Device::devicePath() const { return _path; } -DeviceIndex Device::deviceIndex() const -{ +DeviceIndex Device::deviceIndex() const { return _index; } -std::tuple Device::version() const -{ +const std::tuple& Device::version() const { return _version; } -void Device::_init() -{ - _listening = false; - _supported_reports = getSupportedReports(_raw_device->reportDescriptor()); - if(!_supported_reports) + +void Device::_setupReportsAndInit() { + _event_handlers = std::make_shared>(); + + supported_reports = getSupportedReports(_raw_device->reportDescriptor()); + if (!supported_reports) throw InvalidDevice(InvalidDevice::NoHIDPPReport); + /* hid_logitech_dj creates virtual /dev/hidraw nodes for receiver + * devices. We should ignore these devices. + */ + if (_index == hidpp::DefaultDevice) { + _raw_handler = _raw_device->addEventHandler( + {[](const std::vector& report) -> bool { + return (report[Offset::Type] == Report::Type::Short || + report[Offset::Type] == Report::Type::Long); + }, + [self_weak = _self](const std::vector& report) -> void { + Report _report(report); + if(auto self = self_weak.lock()) + self->handleEvent(_report); + }}); + + try { + auto rsp = sendReport({ReportType::Short, _index, + hidpp20::FeatureID::ROOT, hidpp20::Root::Ping, + hidpp::softwareID}); + if (rsp.deviceIndex() != _index) + throw InvalidDevice(InvalidDevice::VirtualNode); + } catch (hidpp10::Error& e) { + if (e.deviceIndex() != _index) + throw InvalidDevice(InvalidDevice::VirtualNode); + } catch (hidpp20::Error& e) { + /* This shouldn't happen, the device must not be ready */ + if (e.deviceIndex() != _index) + throw InvalidDevice(InvalidDevice::VirtualNode); + else + throw DeviceNotReady(); + } + } + + _raw_handler = _raw_device->addEventHandler( + {[index = _index]( + const std::vector& 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& report) -> void { + Report _report(report); + if(auto self = self_weak.lock()) + self->handleEvent(_report); + }}); + + _init(); +} + +void Device::_init() { try { - hidpp20::EssentialRoot root(this); + hidpp20::Root root(this); _version = root.getVersion(); - } catch(hidpp10::Error &e) { + } catch (hidpp10::Error& e) { // Valid HID++ 1.0 devices should send an InvalidSubID error - if(e.code() != hidpp10::Error::InvalidSubID) + if (e.code() != hidpp10::Error::InvalidSubID) throw; // HID++ 2.0 is not supported, assume HID++ 1.0 _version = std::make_tuple(1, 0); + } catch (hidpp20::Error& e) { + /* Should never happen, device not ready? */ + throw DeviceNotReady(); } - if(!_receiver) { + /* Do a stability test before going further */ + if (std::get<0>(_version) >= 2) { + if (!isStable20()) { + throw DeviceNotReady(); + } + } else { + if (!isStable10()) { + throw DeviceNotReady(); + } + } + + if (!_receiver) { _pid = _raw_device->productId(); - if(std::get<0>(_version) >= 2) { + if (std::get<0>(_version) >= 2) { try { - hidpp20::EssentialDeviceName deviceName(this); + hidpp20::DeviceName deviceName(this); _name = deviceName.getName(); - } catch(hidpp20::UnsupportedFeature &e) { + } catch (hidpp20::UnsupportedFeature& e) { _name = _raw_device->name(); } } else { _name = _raw_device->name(); } } else { - if(std::get<0>(_version) >= 2) { + if (std::get<0>(_version) >= 2) { try { - hidpp20::EssentialDeviceName deviceName(this); + hidpp20::DeviceName deviceName(this); _name = deviceName.getName(); - } catch(hidpp20::UnsupportedFeature &e) { + } catch (hidpp20::UnsupportedFeature& e) { _name = _receiver->getDeviceName(_index); } } else { @@ -148,121 +213,135 @@ void Device::_init() } } -Device::~Device() -{ - if(_listening) - _raw_device->removeEventHandler("DEV_" + std::to_string(_index)); +EventHandlerLock Device::addEventHandler(EventHandler handler) { + std::unique_lock lock(_event_handlers->mutex); + _event_handlers->list.emplace_front(std::move(handler)); + return {_event_handlers, _event_handlers->list.cbegin()}; } -void Device::addEventHandler(const std::string& nickname, - const std::shared_ptr& handler) -{ - assert(_event_handlers.find(nickname) == _event_handlers.end()); +void Device::handleEvent(Report& report) { + if (responseReport(report)) + return; - _event_handlers.emplace(nickname, handler); + std::shared_lock lock(_event_handlers->mutex); + for (auto& handler: _event_handlers->list) + if (handler.condition(report)) + handler.callback(report); } -void Device::removeEventHandler(const std::string& nickname) -{ - _event_handlers.erase(nickname); -} +Report Device::sendReport(const Report& report) { + /* Must complete transaction before next send */ + std::lock_guard send_lock(_send_mutex); + _sent_sub_id = report.subId(); + std::unique_lock lock(_response_mutex); + _sendReport(report); -const std::map>& - Device::eventHandlers() -{ - return _event_handlers; -} + bool valid = _response_cv.wait_for( + lock, io_timeout, [this]() { + return _response.has_value(); + }); -void Device::handleEvent(Report& report) -{ - for(auto& handler : _event_handlers) - if(handler.second->condition(report)) - handler.second->callback(report); -} - -Report Device::sendReport(Report& report) -{ - switch(report.type()) - { - case Report::Type::Short: - if(!(_supported_reports & HIDPP_REPORT_SHORT_SUPPORTED)) - report.setType(Report::Type::Long); - break; - case Report::Type::Long: - /* Report can be truncated, but that isn't a good idea. */ - assert(_supported_reports & HIDPP_REPORT_LONG_SUPPORTED); + if (!valid) { + _sent_sub_id.reset(); + throw TimeoutError(); } - auto raw_response = _raw_device->sendReport(report.rawReport()); + Response response = _response.value(); + _response.reset(); + _sent_sub_id.reset(); - Report response(raw_response); + if (std::holds_alternative(response)) { + return std::get(response); + } else if (std::holds_alternative(response)) { + auto error = std::get(response); + throw hidpp10::Error(error.error_code, error.device_index); + } else if (std::holds_alternative(response)) { + auto error = std::get(response); + throw hidpp20::Error(error.error_code, error.device_index); + } + + // Should not be reached + throw std::runtime_error("unhandled variant type"); +} + +bool Device::responseReport(const Report& report) { + std::lock_guard lock(_response_mutex); + Response response = report; + uint8_t sub_id; Report::Hidpp10Error hidpp10_error{}; - if(response.isError10(&hidpp10_error)) - throw hidpp10::Error(hidpp10_error.error_code); - Report::Hidpp20Error hidpp20_error{}; - if(response.isError20(&hidpp20_error)) - throw hidpp20::Error(hidpp20_error.error_code); - - return response; -} - -void Device::sendReportNoResponse(Report &report) -{ - switch(report.type()) - { - case Report::Type::Short: - if(!(_supported_reports & HIDPP_REPORT_SHORT_SUPPORTED)) - report.setType(Report::Type::Long); - break; - case Report::Type::Long: - /* Report can be truncated, but that isn't a good idea. */ - assert(_supported_reports & HIDPP_REPORT_LONG_SUPPORTED); + if (report.isError10(hidpp10_error)) { + sub_id = hidpp10_error.sub_id; + response = hidpp10_error; + } else if (report.isError20(hidpp20_error)) { + sub_id = hidpp20_error.feature_index; + response = hidpp20_error; + } else { + sub_id = report.subId(); + } + + if (sub_id == _sent_sub_id) { + _response = response; + _response_cv.notify_all(); + return true; + } else { + return false; } - _raw_device->sendReportNoResponse(report.rawReport()); } -std::string Device::name() const -{ +const std::shared_ptr& Device::rawDevice() const { + return _raw_device; +} + +void Device::_sendReport(Report report) { + reportFixup(report); + _raw_device->sendReport(report.rawReport()); +} + +void Device::sendReportNoACK(const Report& report) { + std::lock_guard lock(_send_mutex); + _sendReport(report); +} + +bool Device::isStable10() { + return true; +} + +bool Device::isStable20() { + static const std::string ping_seq = "hello"; + + hidpp20::Root root(this); + + try { + for (auto c: ping_seq) { + if (root.ping(c) != c) + return false; + } + } catch (std::exception& e) { + return false; + } + + return true; +} + +void Device::reportFixup(Report& report) const { + switch (report.type()) { + case Report::Type::Short: + if (!(supported_reports & ShortReportSupported)) + report.setType(Report::Type::Long); + break; + case Report::Type::Long: + /* Report can be truncated, but that isn't a good idea. */ + assert(supported_reports & LongReportSupported); + } +} + +const std::string& Device::name() const { return _name; } -uint16_t Device::pid() const -{ +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(); - handler->condition = [index=this->_index](std::vector& report) - ->bool { - return (report[Offset::Type] == Report::Type::Short || - report[Offset::Type] == Report::Type::Long) && - (report[Offset::DeviceIndex] == index); - }; - handler->callback = [this](std::vector& 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(); -} \ No newline at end of file diff --git a/src/logid/backend/hidpp/Device.h b/src/logid/backend/hidpp/Device.h index 612318b..838b9d6 100644 --- a/src/logid/backend/hidpp/Device.h +++ b/src/logid/backend/hidpp/Device.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,94 +19,167 @@ #ifndef LOGID_BACKEND_HIDPP_DEVICE_H #define LOGID_BACKEND_HIDPP_DEVICE_H +#include +#include +#include +#include +#include +#include #include #include #include #include -#include "../raw/RawDevice.h" -#include "Report.h" -#include "defs.h" -namespace logid { -namespace backend { -namespace dj -{ +namespace logid::backend::hidpp10 { // Need to define here for a constructor class Receiver; } -namespace hidpp -{ + +namespace logid::backend::hidpp { struct DeviceConnectionEvent; - struct EventHandler - { - std::function condition; - std::function callback; - }; - class Device - { + + template + class _deviceWrapper : public T { + friend class Device; + public: - class InvalidDevice : std::exception - { + template + explicit _deviceWrapper(Args... args) : T(std::forward(args)...) {} + + template + static std::shared_ptr make(Args... args) { + return std::make_shared<_deviceWrapper>(std::forward(args)...); + } + }; + + class Device { + template + friend + class _deviceWrapper; + + public: + struct EventHandler { + std::function condition; + std::function callback; + }; + + class InvalidDevice : std::exception { public: - enum Reason - { + enum Reason { NoHIDPPReport, InvalidRawDevice, - Asleep + Asleep, + VirtualNode }; - InvalidDevice(Reason reason) : _reason (reason) {} - virtual const char* what() const noexcept; - virtual Reason code() const noexcept; + + explicit InvalidDevice(Reason reason) : _reason(reason) {} + + [[nodiscard]] const char* what() const noexcept override; + + [[nodiscard]] virtual Reason code() const noexcept; + private: Reason _reason; }; - explicit Device(const std::string& path, DeviceIndex index); - Device(std::shared_ptr raw_device, - DeviceIndex index); - Device(std::shared_ptr receiver, - hidpp::DeviceConnectionEvent event); - Device(std::shared_ptr receiver, - DeviceIndex index); - ~Device(); + [[nodiscard]] const std::string& devicePath() const; - std::string devicePath() const; - DeviceIndex deviceIndex() const; - std::tuple version() const; + [[nodiscard]] DeviceIndex deviceIndex() const; - std::string name() const; - uint16_t pid() const; + [[nodiscard]] const std::tuple& version() const; - void listen(); // Runs asynchronously - void stopListening(); + [[nodiscard]] const std::string& name() const; - void addEventHandler(const std::string& nickname, - const std::shared_ptr& handler); - void removeEventHandler(const std::string& nickname); - const std::map>& - eventHandlers(); + [[nodiscard]] uint16_t pid() const; - Report sendReport(Report& report); - void sendReportNoResponse(Report& report); + EventHandlerLock addEventHandler(EventHandler handler); + + virtual Report sendReport(const Report& report); + + virtual void sendReportNoACK(const Report& report); void handleEvent(Report& report); + + [[nodiscard]] const std::shared_ptr& rawDevice() const; + + Device(const Device&) = delete; + + Device(Device&&) = delete; + + virtual ~Device() = default; + + protected: + Device(const std::string& path, DeviceIndex index, + const std::shared_ptr& monitor, double timeout); + + Device(std::shared_ptr raw_device, DeviceIndex index, + double timeout); + + Device(const std::shared_ptr& receiver, + hidpp::DeviceConnectionEvent event, double timeout); + + Device(const std::shared_ptr& 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_device; - std::shared_ptr _receiver; + EventHandlerLock _raw_handler; + std::shared_ptr _receiver; std::string _path; DeviceIndex _index; - uint8_t _supported_reports; std::tuple _version; - uint16_t _pid; + uint16_t _pid{}; std::string _name; - std::atomic _listening; + std::mutex _send_mutex; - std::map> _event_handlers; + typedef std::variant Response; + + std::optional _response; + std::optional _sent_sub_id{}; + + std::shared_ptr> _event_handlers; + + std::weak_ptr _self; + + protected: + template + static std::shared_ptr makeDerived(Args... args) { + auto device = _deviceWrapper::make(std::forward(args)...); + device->_self = device; + device->_setupReportsAndInit(); + return device; + } + + public: + template + static std::shared_ptr make(Args... args) { + return makeDerived(std::forward(args)...); + } }; -} } } + + typedef Device::EventHandler EventHandler; +} #endif //LOGID_BACKEND_HIDPP_DEVICE_H \ No newline at end of file diff --git a/src/logid/backend/hidpp/Report.cpp b/src/logid/backend/hidpp/Report.cpp index c5683a7..9fdb3c1 100644 --- a/src/logid/backend/hidpp/Report.cpp +++ b/src/logid/backend/hidpp/Report.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,119 +16,115 @@ * */ +#include #include #include #include -#include "Report.h" -#include "../hidpp10/Error.h" -#include "../hidpp20/Error.h" +#include +#include using namespace logid::backend::hidpp; using namespace logid::backend; /* Report descriptors were sourced from cvuchener/hidpp */ static const std::array ShortReportDesc = { - 0xA1, 0x01, // Collection (Application) - 0x85, 0x10, // Report ID (16) - 0x75, 0x08, // Report Size (8) - 0x95, 0x06, // Report Count (6) - 0x15, 0x00, // Logical Minimum (0) - 0x26, 0xFF, 0x00, // Logical Maximum (255) - 0x09, 0x01, // Usage (0001 - Vendor) - 0x81, 0x00, // Input (Data, Array, Absolute) - 0x09, 0x01, // Usage (0001 - Vendor) - 0x91, 0x00, // Output (Data, Array, Absolute) - 0xC0 // End Collection + 0xA1, 0x01, // Collection (Application) + 0x85, 0x10, // Report ID (16) + 0x75, 0x08, // Report Size (8) + 0x95, 0x06, // Report Count (6) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x09, 0x01, // Usage (0001 - Vendor) + 0x81, 0x00, // Input (Data, Array, Absolute) + 0x09, 0x01, // Usage (0001 - Vendor) + 0x91, 0x00, // Output (Data, Array, Absolute) + 0xC0 // End Collection }; static const std::array LongReportDesc = { - 0xA1, 0x01, // Collection (Application) - 0x85, 0x11, // Report ID (17) - 0x75, 0x08, // Report Size (8) - 0x95, 0x13, // Report Count (19) - 0x15, 0x00, // Logical Minimum (0) - 0x26, 0xFF, 0x00, // Logical Maximum (255) - 0x09, 0x02, // Usage (0002 - Vendor) - 0x81, 0x00, // Input (Data, Array, Absolute) - 0x09, 0x02, // Usage (0002 - Vendor) - 0x91, 0x00, // Output (Data, Array, Absolute) - 0xC0 // End Collection + 0xA1, 0x01, // Collection (Application) + 0x85, 0x11, // Report ID (17) + 0x75, 0x08, // Report Size (8) + 0x95, 0x13, // Report Count (19) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x09, 0x02, // Usage (0002 - Vendor) + 0x81, 0x00, // Input (Data, Array, Absolute) + 0x09, 0x02, // Usage (0002 - Vendor) + 0x91, 0x00, // Output (Data, Array, Absolute) + 0xC0 // End Collection }; /* Alternative versions from the G602 */ static const std::array ShortReportDesc2 = { - 0xA1, 0x01, // Collection (Application) - 0x85, 0x10, // Report ID (16) - 0x95, 0x06, // Report Count (6) - 0x75, 0x08, // Report Size (8) - 0x15, 0x00, // Logical Minimum (0) - 0x26, 0xFF, 0x00, // Logical Maximum (255) - 0x09, 0x01, // Usage (0001 - Vendor) - 0x81, 0x00, // Input (Data, Array, Absolute) - 0x09, 0x01, // Usage (0001 - Vendor) - 0x91, 0x00, // Output (Data, Array, Absolute) - 0xC0 // End Collection + 0xA1, 0x01, // Collection (Application) + 0x85, 0x10, // Report ID (16) + 0x95, 0x06, // Report Count (6) + 0x75, 0x08, // Report Size (8) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x09, 0x01, // Usage (0001 - Vendor) + 0x81, 0x00, // Input (Data, Array, Absolute) + 0x09, 0x01, // Usage (0001 - Vendor) + 0x91, 0x00, // Output (Data, Array, Absolute) + 0xC0 // End Collection }; static const std::array LongReportDesc2 = { - 0xA1, 0x01, // Collection (Application) - 0x85, 0x11, // Report ID (17) - 0x95, 0x13, // Report Count (19) - 0x75, 0x08, // Report Size (8) - 0x15, 0x00, // Logical Minimum (0) - 0x26, 0xFF, 0x00, // Logical Maximum (255) - 0x09, 0x02, // Usage (0002 - Vendor) - 0x81, 0x00, // Input (Data, Array, Absolute) - 0x09, 0x02, // Usage (0002 - Vendor) - 0x91, 0x00, // Output (Data, Array, Absolute) - 0xC0 // End Collection + 0xA1, 0x01, // Collection (Application) + 0x85, 0x11, // Report ID (17) + 0x95, 0x13, // Report Count (19) + 0x75, 0x08, // Report Size (8) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x09, 0x02, // Usage (0002 - Vendor) + 0x81, 0x00, // Input (Data, Array, Absolute) + 0x09, 0x02, // Usage (0002 - Vendor) + 0x91, 0x00, // Output (Data, Array, Absolute) + 0xC0 // End Collection }; -uint8_t hidpp::getSupportedReports(std::vector&& rdesc) -{ +uint8_t hidpp::getSupportedReports(const std::vector& report_desc) { uint8_t ret = 0; - auto it = std::search(rdesc.begin(), rdesc.end(), - ShortReportDesc.begin(), ShortReportDesc.end()); - if(it == rdesc.end()) - it = std::search(rdesc.begin(), rdesc.end(), - ShortReportDesc2.begin(), ShortReportDesc2.end()); - if(it != rdesc.end()) - ret |= HIDPP_REPORT_SHORT_SUPPORTED; + auto it = std::search(report_desc.begin(), report_desc.end(), + ShortReportDesc.begin(), ShortReportDesc.end()); + if (it == report_desc.end()) + it = std::search(report_desc.begin(), report_desc.end(), + ShortReportDesc2.begin(), ShortReportDesc2.end()); + if (it != report_desc.end()) + ret |= ShortReportSupported; - it = std::search(rdesc.begin(), rdesc.end(), - LongReportDesc.begin(), LongReportDesc.end()); - if(it == rdesc.end()) - it = std::search(rdesc.begin(), rdesc.end(), - LongReportDesc2.begin(), LongReportDesc2.end()); - if(it != rdesc.end()) - ret |= HIDPP_REPORT_LONG_SUPPORTED; + it = std::search(report_desc.begin(), report_desc.end(), + LongReportDesc.begin(), LongReportDesc.end()); + if (it == report_desc.end()) + it = std::search(report_desc.begin(), report_desc.end(), + LongReportDesc2.begin(), LongReportDesc2.end()); + if (it != report_desc.end()) + ret |= LongReportSupported; return ret; } -const char *Report::InvalidReportID::what() const noexcept -{ +const char* Report::InvalidReportID::what() const noexcept { return "Invalid report ID"; } -const char *Report::InvalidReportLength::what() const noexcept -{ +const char* Report::InvalidReportLength::what() const noexcept { return "Invalid report length"; } Report::Report(Report::Type type, DeviceIndex device_index, - uint8_t sub_id, uint8_t address) -{ - switch(type) { - case Type::Short: - _data.resize(HeaderLength + ShortParamLength); - break; - case Type::Long: - _data.resize(HeaderLength + LongParamLength); - break; - default: - throw InvalidReportID(); + uint8_t sub_id, uint8_t address) { + switch (type) { + case Type::Short: + _data.resize(HeaderLength + ShortParamLength); + break; + case Type::Long: + _data.resize(HeaderLength + LongParamLength); + break; + default: + throw InvalidReportID(); } _data[Offset::Type] = type; @@ -138,185 +134,164 @@ Report::Report(Report::Type type, DeviceIndex device_index, } Report::Report(Report::Type type, DeviceIndex device_index, - uint8_t feature_index, uint8_t function, uint8_t sw_id) -{ + uint8_t feature_index, uint8_t function, uint8_t sw_id) { assert(function <= 0x0f); assert(sw_id <= 0x0f); - switch(type) { - case Type::Short: - _data.resize(HeaderLength + ShortParamLength); - break; - case Type::Long: - _data.resize(HeaderLength + LongParamLength); - break; - default: - throw InvalidReportID(); + switch (type) { + case Type::Short: + _data.resize(HeaderLength + ShortParamLength); + break; + case Type::Long: + _data.resize(HeaderLength + LongParamLength); + break; + default: + throw InvalidReportID(); } _data[Offset::Type] = type; _data[Offset::DeviceIndex] = device_index; _data[Offset::Feature] = feature_index; _data[Offset::Function] = (function & 0x0f) << 4 | - (sw_id & 0x0f); + (sw_id & 0x0f); } Report::Report(const std::vector& data) : - _data (data) -{ + _data(data) { _data.resize(HeaderLength + LongParamLength); // Truncating data is entirely valid here. - switch(_data[Offset::Type]) { - case Type::Short: - _data.resize(HeaderLength + ShortParamLength); - break; - case Type::Long: - _data.resize(HeaderLength + LongParamLength); - break; - default: - throw InvalidReportID(); + switch (_data[Offset::Type]) { + case Type::Short: + _data.resize(HeaderLength + ShortParamLength); + break; + case Type::Long: + _data.resize(HeaderLength + LongParamLength); + break; + default: + throw InvalidReportID(); } } -Report::Type Report::type() const -{ +Report::Type Report::type() const { return static_cast(_data[Offset::Type]); } -void Report::setType(Report::Type type) -{ - switch(type) { - case Type::Short: - _data.resize(HeaderLength + ShortParamLength); - break; - case Type::Long: - _data.resize(HeaderLength + LongParamLength); - break; - default: - throw InvalidReportID(); +void Report::setType(Report::Type type) { + switch (type) { + case Type::Short: + _data.resize(HeaderLength + ShortParamLength); + break; + case Type::Long: + _data.resize(HeaderLength + LongParamLength); + break; + default: + throw InvalidReportID(); } _data[Offset::Type] = type; } -hidpp::DeviceIndex Report::deviceIndex() const -{ +hidpp::DeviceIndex Report::deviceIndex() const { return static_cast(_data[Offset::DeviceIndex]); } -void Report::setDeviceIndex(hidpp::DeviceIndex index) -{ +[[maybe_unused]] void Report::setDeviceIndex(hidpp::DeviceIndex index) { _data[Offset::DeviceIndex] = index; } -uint8_t Report::feature() const -{ +uint8_t Report::feature() const { return _data[Offset::Feature]; } -void Report::setFeature(uint8_t feature) -{ +[[maybe_unused]] void Report::setFeature(uint8_t feature) { _data[Offset::Parameters] = feature; } -uint8_t Report::subId() const -{ +uint8_t Report::subId() const { return _data[Offset::SubID]; } -void Report::setSubId(uint8_t sub_id) -{ +[[maybe_unused]] void Report::setSubId(uint8_t sub_id) { _data[Offset::SubID] = sub_id; } -uint8_t Report::function() const -{ +uint8_t Report::function() const { return (_data[Offset::Function] >> 4) & 0x0f; } -void Report::setFunction(uint8_t function) -{ +[[maybe_unused]] void Report::setFunction(uint8_t function) { _data[Offset::Function] &= 0x0f; _data[Offset::Function] |= (function & 0x0f) << 4; } -uint8_t Report::swId() const -{ +uint8_t Report::swId() const { return _data[Offset::Function] & 0x0f; } -void Report::setSwId(uint8_t sub_id) -{ - _data[Offset::Function] &= 0x0f; - _data[Offset::Function] |= sub_id & 0x0f; +void Report::setSwId(uint8_t sw_id) { + _data[Offset::Function] &= 0xf0; + _data[Offset::Function] |= sw_id & 0x0f; } -uint8_t Report::address() const -{ +uint8_t Report::address() const { return _data[Offset::Address]; } -void Report::setAddress(uint8_t address) -{ +[[maybe_unused]] void Report::setAddress(uint8_t address) { _data[Offset::Address] = address; } -std::vector::iterator Report::paramBegin() -{ +std::vector::iterator Report::paramBegin() { return _data.begin() + Offset::Parameters; } -std::vector::iterator Report::paramEnd() -{ +std::vector::iterator Report::paramEnd() { return _data.end(); } -std::vector::const_iterator Report::paramBegin() const -{ +std::vector::const_iterator Report::paramBegin() const { return _data.begin() + Offset::Parameters; } -std::vector::const_iterator Report::paramEnd() const -{ +std::vector::const_iterator Report::paramEnd() const { return _data.end(); } -void Report::setParams(const std::vector& _params) -{ - assert(_params.size() <= _data.size()-HeaderLength); +void Report::setParams(const std::vector& _params) { + assert(_params.size() <= _data.size() - HeaderLength); - for(std::size_t i = 0; i < _params.size(); i++) + for (std::size_t i = 0; i < _params.size(); i++) _data[Offset::Parameters + i] = _params[i]; } -bool Report::isError10(Report::Hidpp10Error *error) -{ - assert(error != nullptr); - - if(_data[Offset::Type] != Type::Short || +bool Report::isError10(Report::Hidpp10Error& error) const { + if (_data[Offset::Type] != Type::Short || _data[Offset::SubID] != hidpp10::ErrorID) return false; - error->sub_id = _data[3]; - error->address = _data[4]; - error->error_code = _data[5]; + error.device_index = deviceIndex(); + error.sub_id = _data[3]; + error.address = _data[4]; + error.error_code = _data[5]; return true; } -bool Report::isError20(Report::Hidpp20Error* error) -{ - assert(error != nullptr); - - if(_data[Offset::Type] != Type::Long || +bool Report::isError20(Report::Hidpp20Error& error) const { + if (_data[Offset::Type] != Type::Long || _data[Offset::Feature] != hidpp20::ErrorID) return false; - error->feature_index= _data[3]; - error->function = (_data[4] >> 4) & 0x0f; - error->software_id = _data[4] & 0x0f; - error->error_code = _data[5]; + error.device_index = deviceIndex(); + error.feature_index = _data[3]; + error.function = (_data[4] >> 4) & 0x0f; + error.software_id = _data[4] & 0x0f; + error.error_code = _data[5]; return true; -} \ No newline at end of file +} + +const std::vector& Report::rawReport() const { + return _data; +} diff --git a/src/logid/backend/hidpp/Report.h b/src/logid/backend/hidpp/Report.h index e406a6d..305e928 100644 --- a/src/logid/backend/hidpp/Report.h +++ b/src/logid/backend/hidpp/Report.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,23 +19,19 @@ #ifndef LOGID_BACKEND_HIDPP_REPORT_H #define LOGID_BACKEND_HIDPP_REPORT_H +#include +#include #include -#include "../raw/RawDevice.h" -#include "defs.h" -/* Some devices only support a subset of these reports */ -#define HIDPP_REPORT_SHORT_SUPPORTED 1U -#define HIDPP_REPORT_LONG_SUPPORTED 1U<<1U -/* Very long reports exist, however they have not been encountered so far */ +namespace logid::backend::hidpp { + uint8_t getSupportedReports(const std::vector& report_desc); -namespace logid { -namespace backend { -namespace hidpp -{ - uint8_t getSupportedReports(std::vector&& rdesc); + /* Some devices only support a subset of these reports */ + static constexpr uint8_t ShortReportSupported = 1U; + static constexpr uint8_t LongReportSupported = (1U<<1); + /* Very long reports exist, however they have not been encountered so far */ - namespace Offset - { + namespace Offset { static constexpr uint8_t Type = 0; static constexpr uint8_t DeviceIndex = 1; static constexpr uint8_t SubID = 2; @@ -45,23 +41,22 @@ namespace hidpp static constexpr uint8_t Parameters = 4; } - class Report - { + class Report { public: typedef ReportType::ReportType Type; - class InvalidReportID: public std::exception - { + class InvalidReportID : public std::exception { public: InvalidReportID() = default; - const char* what() const noexcept override; + + [[nodiscard]] const char* what() const noexcept override; }; - class InvalidReportLength: public std::exception - { + class InvalidReportLength : public std::exception { public: - InvalidReportLength() = default;; - const char* what() const noexcept override; + InvalidReportLength() = default; + + [[nodiscard]] const char* what() const noexcept override; }; static constexpr std::size_t MaxDataLength = 20; @@ -69,57 +64,72 @@ namespace hidpp Report(Report::Type type, DeviceIndex device_index, uint8_t sub_id, uint8_t address); + Report(Report::Type type, DeviceIndex device_index, - uint8_t feature_index, - uint8_t function, - uint8_t sw_id); + uint8_t feature_index, + uint8_t function, + uint8_t sw_id); + explicit Report(const std::vector& data); - Report::Type type() const; + [[nodiscard]] Report::Type type() const; + void setType(Report::Type type); - logid::backend::hidpp::DeviceIndex deviceIndex() const; - void setDeviceIndex(hidpp::DeviceIndex index); + [[nodiscard]] DeviceIndex deviceIndex() const; - uint8_t feature() const; - void setFeature(uint8_t feature); + [[maybe_unused]] void setDeviceIndex(DeviceIndex index); - uint8_t subId() const; - void setSubId(uint8_t sub_id); + [[nodiscard]] uint8_t feature() const; - uint8_t function() const; - void setFunction(uint8_t function); + [[maybe_unused]] void setFeature(uint8_t feature); + + [[nodiscard]] uint8_t subId() const; + + [[maybe_unused]] void setSubId(uint8_t sub_id); + + [[nodiscard]] uint8_t function() const; + + [[maybe_unused]] void setFunction(uint8_t function); + + [[nodiscard]] uint8_t swId() const; - uint8_t swId() const; void setSwId(uint8_t sw_id); - uint8_t address() const; - void setAddress(uint8_t address); + [[nodiscard]] uint8_t address() const; + + [[maybe_unused]] void setAddress(uint8_t address); + + [[nodiscard]] std::vector::iterator paramBegin(); + + [[nodiscard]] std::vector::iterator paramEnd(); + + [[nodiscard]] std::vector::const_iterator paramBegin() const; + + [[nodiscard]] std::vector::const_iterator paramEnd() const; - std::vector::iterator paramBegin(); - std::vector::iterator paramEnd(); - std::vector::const_iterator paramBegin() const; - std::vector::const_iterator paramEnd() const; void setParams(const std::vector& _params); - struct Hidpp10Error - { + struct Hidpp10Error { + hidpp::DeviceIndex device_index; uint8_t sub_id, address, error_code; }; - bool isError10(Hidpp10Error* error); - struct Hidpp20Error - { + bool isError10(Hidpp10Error& error) const; + + struct Hidpp20Error { + hidpp::DeviceIndex device_index; uint8_t feature_index, function, software_id, error_code; }; - bool isError20(Hidpp20Error* error); - std::vector rawReport () const { return _data; } + bool isError20(Hidpp20Error& error) const; + + [[nodiscard]] const std::vector& rawReport() const; static constexpr std::size_t HeaderLength = 4; private: std::vector _data; }; -}}} +} #endif //LOGID_BACKEND_HIDPP_REPORT_H \ No newline at end of file diff --git a/src/logid/backend/hidpp/defs.h b/src/logid/backend/hidpp/defs.h index 3a85470..253f3c0 100644 --- a/src/logid/backend/hidpp/defs.h +++ b/src/logid/backend/hidpp/defs.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,37 +19,33 @@ #ifndef LOGID_BACKEND_HIDPP_DEFS_H #define LOGID_BACKEND_HIDPP_DEFS_H -#define LOGID_HIDPP_SOFTWARE_ID 0 - #include -namespace logid { -namespace backend { -namespace hidpp -{ - namespace ReportType - { - enum ReportType : uint8_t - { +namespace logid::backend::hidpp { + namespace ReportType { + enum ReportType : uint8_t { Short = 0x10, Long = 0x11 }; } - enum DeviceIndex: uint8_t - { + enum DeviceIndex : uint8_t { DefaultDevice = 0xff, CordedDevice = 0, WirelessDevice1 = 1, - WirelessDevice2 = 2, - WirelessDevice3 = 3, - WirelessDevice4 = 4, - WirelessDevice5 = 5, + WirelessDevice2 [[maybe_unused]] = 2, + WirelessDevice3 [[maybe_unused]] = 3, + WirelessDevice4 [[maybe_unused]] = 4, + WirelessDevice5 [[maybe_unused]] = 5, WirelessDevice6 = 6, }; + static constexpr uint8_t softwareID = 2; + /* For sending reports with no response, use a different SW ID */ + static constexpr uint8_t noAckSoftwareID = 1; + static constexpr std::size_t ShortParamLength = 3; static constexpr std::size_t LongParamLength = 16; -} } } +} #endif //LOGID_BACKEND_HIDPP_DEFS_H \ No newline at end of file diff --git a/src/logid/backend/hidpp10/Device.cpp b/src/logid/backend/hidpp10/Device.cpp index c56e407..f2972c9 100644 --- a/src/logid/backend/hidpp10/Device.cpp +++ b/src/logid/backend/hidpp10/Device.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,47 +16,106 @@ * */ +#include +#include #include #include -#include "Device.h" -#include "defs.h" using namespace logid::backend; using namespace logid::backend::hidpp10; -Device::Device(const std::string &path, hidpp::DeviceIndex index) : - hidpp::Device(path, index) -{ - assert(version() == std::make_tuple(1, 0)); +Device::Device(const std::string& path, hidpp::DeviceIndex index, + const std::shared_ptr& monitor, double timeout) : + hidpp::Device(path, index, monitor, timeout) { } -Device::Device(std::shared_ptr raw_dev, - hidpp::DeviceIndex index) : hidpp::Device(std::move(raw_dev), index) -{ - assert(version() == std::make_tuple(1, 0)); +Device::Device(std::shared_ptr raw_dev, hidpp::DeviceIndex index, + double timeout) : hidpp::Device(std::move(raw_dev), index, timeout) { } -Device::Device(std::shared_ptr receiver, hidpp::DeviceIndex index) - : hidpp::Device(receiver, index) -{ - assert(version() == std::make_tuple(1, 0)); +Device::Device(const std::shared_ptr& 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 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(response)) { + return std::get(response); + } else { // if(std::holds_alternative(response)) + auto error = std::get(response); + throw Error(error.error_code, error.device_index); + } +} + +bool Device::responseReport(const hidpp::Report& report) { + std::lock_guard lock(_response_mutex); + uint8_t sub_id; + + bool is_error = false; + hidpp::Report::Hidpp10Error hidpp10_error{}; + if (report.isError10(hidpp10_error)) { + sub_id = hidpp10_error.sub_id; + is_error = true; + } else { + sub_id = report.subId(); + } + + auto& response_slot = _responses[sub_id % SubIDCount]; + + if (!response_slot.sub_id.has_value() || response_slot.sub_id.value() != sub_id) + return false; + + if (is_error) { + response_slot.response = hidpp10_error; + } else { + response_slot.response = report; + } + + _response_cv.notify_all(); + return true; } std::vector Device::getRegister(uint8_t address, - const std::vector& params, hidpp::Report::Type type) -{ + const std::vector& params, + hidpp::Report::Type type) { assert(params.size() <= hidpp::LongParamLength); uint8_t sub_id = type == hidpp::Report::Type::Short ? - GetRegisterShort : GetRegisterLong; + GetRegisterShort : GetRegisterLong; return accessRegister(sub_id, address, params); } std::vector Device::setRegister(uint8_t address, const std::vector& params, - hidpp::Report::Type type) -{ + hidpp::Report::Type type) { assert(params.size() <= hidpp::LongParamLength); uint8_t sub_id = type == hidpp::Report::Type::Short ? @@ -66,16 +125,20 @@ std::vector Device::setRegister(uint8_t address, } std::vector Device::accessRegister(uint8_t sub_id, uint8_t address, - const std::vector ¶ms) -{ + const std::vector& params) { hidpp::Report::Type type = params.size() <= hidpp::ShortParamLength ? - hidpp::Report::Type::Short : hidpp::Report::Type::Long; + hidpp::Report::Type::Short : hidpp::Report::Type::Long; + + if (sub_id == SetRegisterLong) { + // When setting a long register, the report must be long. + type = hidpp::Report::Type::Long; + } hidpp::Report request(type, deviceIndex(), sub_id, address); std::copy(params.begin(), params.end(), request.paramBegin()); auto response = sendReport(request); - return std::vector(response.paramBegin(), response.paramEnd()); + return {response.paramBegin(), response.paramEnd()}; } diff --git a/src/logid/backend/hidpp10/Device.h b/src/logid/backend/hidpp10/Device.h index a86d0e5..5ae3fd6 100644 --- a/src/logid/backend/hidpp10/Device.h +++ b/src/logid/backend/hidpp10/Device.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,30 +19,66 @@ #ifndef LOGID_BACKEND_HIDPP10_DEVICE_H #define LOGID_BACKEND_HIDPP10_DEVICE_H -#include "../hidpp/Device.h" +#include +#include +#include +#include +#include -namespace logid { -namespace backend { -namespace hidpp10 -{ - class Device : public hidpp::Device - { +namespace logid::backend::hidpp10 { + class Device : public hidpp::Device { public: - Device(const std::string& path, hidpp::DeviceIndex index); - Device(std::shared_ptr raw_dev, - hidpp::DeviceIndex index); - Device(std::shared_ptr receiver, - hidpp::DeviceIndex index); + + hidpp::Report sendReport(const hidpp::Report& report) final; std::vector getRegister(uint8_t address, - const std::vector& params, hidpp::Report::Type type); + const std::vector& params, + hidpp::Report::Type type); std::vector setRegister(uint8_t address, - const std::vector& params, hidpp::Report::Type type); + const std::vector& params, + hidpp::Report::Type type); + + protected: + Device(const std::string& path, hidpp::DeviceIndex index, + const std::shared_ptr& monitor, double timeout); + + Device(std::shared_ptr raw_dev, + hidpp::DeviceIndex index, double timeout); + + Device(const std::shared_ptr& receiver, + hidpp::DeviceIndex index, double timeout); + + bool responseReport(const hidpp::Report& report) final; + private: - std::vector accessRegister(uint8_t sub_id, - uint8_t address, const std::vector& params); + typedef std::variant Response; + struct ResponseSlot { + std::optional response; + std::optional sub_id; + void reset(); + }; + std::array _responses; + + std::vector accessRegister( + uint8_t sub_id, uint8_t address, const std::vector& params); + + protected: + template + static std::shared_ptr makeDerived(Args... args) { + auto device = hidpp::Device::makeDerived(std::forward(args)...); + + if (std::get<0>(device->version()) != 1) + throw std::invalid_argument("not a hid++ 1.0 device"); + + return device; + } + public: + template + static std::shared_ptr make(Args... args) { + return makeDerived(std::forward(args)...); + } }; -}}} +} #endif //LOGID_BACKEND_HIDPP10_DEVICE_H \ No newline at end of file diff --git a/src/logid/backend/hidpp10/Error.cpp b/src/logid/backend/hidpp10/Error.cpp index 76c8415..fd145e2 100644 --- a/src/logid/backend/hidpp10/Error.cpp +++ b/src/logid/backend/hidpp10/Error.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,52 +16,53 @@ * */ +#include #include -#include -#include "Error.h" +using namespace logid::backend; using namespace logid::backend::hidpp10; -Error::Error(uint8_t code): _code(code) -{ +Error::Error(uint8_t code, hidpp::DeviceIndex index) : _code(code), _index(index) { assert(code != Success); } -const char* Error::what() const noexcept -{ - switch(_code) { - case Success: - return "Success"; - case InvalidSubID: - return "Invalid sub ID"; - case InvalidAddress: - return "Invalid address"; - case InvalidValue: - return "Invalid value"; - case ConnectFail: - return "Connection failure"; - case TooManyDevices: - return "Too many devices"; - case AlreadyExists: - return "Already exists"; - case Busy: - return "Busy"; - case UnknownDevice: - return "Unknown device"; - case ResourceError: - return "Resource error"; - case RequestUnavailable: - return "Request unavailable"; - case InvalidParameterValue: - return "Invalid parameter value"; - case WrongPINCode: - return "Wrong PIN code"; - default: - return "Unknown error code"; +const char* Error::what() const noexcept { + switch (_code) { + case Success: + return "Success"; + case InvalidSubID: + return "Invalid sub ID"; + case InvalidAddress: + return "Invalid address"; + case InvalidValue: + return "Invalid value"; + case ConnectFail: + return "Connection failure"; + case TooManyDevices: + return "Too many devices"; + case AlreadyExists: + return "Already exists"; + case Busy: + return "Busy"; + case UnknownDevice: + return "Unknown device"; + case ResourceError: + return "Resource error"; + case RequestUnavailable: + return "Request unavailable"; + case InvalidParameterValue: + return "Invalid parameter value"; + case WrongPINCode: + return "Wrong PIN code"; + default: + return "Unknown error code"; } } -uint8_t Error::code() const noexcept -{ +uint8_t Error::code() const noexcept { return _code; -} \ No newline at end of file +} + +hidpp::DeviceIndex Error::deviceIndex() const noexcept { + return _index; +} diff --git a/src/logid/backend/hidpp10/Error.h b/src/logid/backend/hidpp10/Error.h index f5fffae..d172061 100644 --- a/src/logid/backend/hidpp10/Error.h +++ b/src/logid/backend/hidpp10/Error.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,18 +19,16 @@ #ifndef LOGID_BACKEND_HIDPP10_ERROR_H #define LOGID_BACKEND_HIDPP10_ERROR_H +#include #include +#include -namespace logid { -namespace backend { -namespace hidpp10 { +namespace logid::backend::hidpp10 { static constexpr uint8_t ErrorID = 0x8f; - class Error: public std::exception - { + class Error : public std::exception { public: - enum ErrorCode: uint8_t - { + enum ErrorCode : uint8_t { Success = 0x00, InvalidSubID = 0x01, InvalidAddress = 0x02, @@ -46,14 +44,18 @@ namespace hidpp10 { WrongPINCode = 0x0C }; - explicit Error(uint8_t code); + Error(uint8_t code, hidpp::DeviceIndex index); - const char* what() const noexcept override; - uint8_t code() const noexcept; + [[nodiscard]] const char* what() const noexcept override; + + [[nodiscard]] uint8_t code() const noexcept; + + [[nodiscard]] hidpp::DeviceIndex deviceIndex() const noexcept; private: uint8_t _code; + hidpp::DeviceIndex _index; }; -}}} +} #endif //LOGID_BACKEND_HIDPP10_ERROR_H \ No newline at end of file diff --git a/src/logid/backend/hidpp10/Receiver.cpp b/src/logid/backend/hidpp10/Receiver.cpp new file mode 100644 index 0000000..65ee05c --- /dev/null +++ b/src/logid/backend/hidpp10/Receiver.cpp @@ -0,0 +1,380 @@ +/* + * Copyright 2019-2023 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +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& 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 request(3); + + if (flags.deviceBatteryStatus) + request[0] |= (1 << 4); + if (flags.receiverWirelessNotifications) + request[1] |= 1; + if (flags.receiverSoftwarePresent) + request[1] |= (1 << 3); + + setRegister(EnableHidppNotifications, request, hidpp::ReportType::Short); +} + +void Receiver::enumerate() { + setRegister(ConnectionState, {2}, hidpp::ReportType::Short); +} + +///TODO: Investigate usage +uint8_t Receiver::getConnectionState(hidpp::DeviceIndex index) { + auto response = getRegister(ConnectionState, {index}, hidpp::ReportType::Short); + + return response[0]; +} + +void Receiver::startPairing(uint8_t timeout) { + std::vector 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 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 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 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 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 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 Receiver::getDeviceActivity() { + auto response = getRegister(DeviceActivity, {}, hidpp::ReportType::Long); + + std::map device_activity; + for (uint8_t i = hidpp::WirelessDevice1; i <= hidpp::WirelessDevice6; i++) + device_activity[static_cast(i)] = response[i]; + + return device_activity; +} + +struct Receiver::PairingInfo +Receiver::getPairingInfo(hidpp::DeviceIndex index) { + std::vector 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(response[1]) + }; + } else { + info = { + .destinationId = response[1], + .reportInterval = response[2], + .pid = (uint16_t) ((response[3] << 8) | response[4]), + .deviceType = static_cast(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 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(psl); + + return info; +} + +std::string Receiver::getDeviceName(hidpp::DeviceIndex index) { + std::vector 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(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(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(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; +} diff --git a/src/logid/backend/hidpp10/Receiver.h b/src/logid/backend/hidpp10/Receiver.h new file mode 100644 index 0000000..91a7038 --- /dev/null +++ b/src/logid/backend/hidpp10/Receiver.h @@ -0,0 +1,221 @@ +/* + * Copyright 2019-2023 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_DJ_RECEIVER_H +#define LOGID_BACKEND_DJ_RECEIVER_H + +#include +#include + +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 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& monitor, + double timeout); + + private: + void _receiverCheck(); + + bool _is_bolt = false; + + public: + template + static std::shared_ptr make(Args... args) { + auto receiver = makeDerived(std::forward(args)...); + + receiver->_receiverCheck(); + + return receiver; + } + }; +} + +#endif //LOGID_BACKEND_DJ_RECEIVER_H \ No newline at end of file diff --git a/src/logid/backend/hidpp10/ReceiverMonitor.cpp b/src/logid/backend/hidpp10/ReceiverMonitor.cpp new file mode 100644 index 0000000..9faef84 --- /dev/null +++ b/src/logid/backend/hidpp10/ReceiverMonitor.cpp @@ -0,0 +1,247 @@ +/* + * Copyright 2019-2023 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +using namespace logid::backend::hidpp10; +using namespace logid::backend::hidpp; + +ReceiverMonitor::ReceiverMonitor(const std::string& path, + const std::shared_ptr& 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& 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& raw) -> void { + /* Running in a new thread prevents deadlocks since the + * receiver may be enumerating. + */ + hidpp::Report report(raw); + + if (auto self = self_weak.lock()) { + run_task([self_weak, report]() { + auto self = self_weak.lock(); + if (!self) + return; + + if (report.subId() == Receiver::DeviceConnection) { + self->_addHandler(Receiver::deviceConnectionEvent(report)); + } else if (report.subId() == Receiver::DeviceDisconnection) { + self->_removeHandler(Receiver::deviceDisconnectionEvent(report)); + } + }); + } + + } + }); + } + + if (_discover_ev_handler.empty()) { + _discover_ev_handler = _receiver->addEventHandler( + {[](const hidpp::Report& report) -> bool { + return (report.subId() == Receiver::DeviceDiscovered) && + (report.type() == Report::Type::Long); + }, + [self_weak = _self](const hidpp::Report& report) { + auto self = self_weak.lock(); + if (!self) + return; + std::lock_guard lock(self->_pair_mutex); + if (self->_pair_state == Discovering) { + bool filled = Receiver::fillDeviceDiscoveryEvent( + self->_discovery_event, report); + + if (filled) { + self->_pair_state = FindingPasskey; + run_task([self_weak, event = self->_discovery_event]() { + if (auto self = self_weak.lock()) + self->receiver()->startBoltPairing(event); + }); + } + } + } + }); + } + + if (_passkey_ev_handler.empty()) { + _passkey_ev_handler = _receiver->addEventHandler( + {[](const hidpp::Report& report) -> bool { + return report.subId() == Receiver::PasskeyRequest && + report.type() == hidpp::Report::Type::Long; + }, + [self_weak = _self](const hidpp::Report& report) { + if (auto self = self_weak.lock()) { + std::lock_guard lock(self->_pair_mutex); + if (self->_pair_state == FindingPasskey) { + auto passkey = Receiver::passkeyEvent(report); + + self->_pair_state = Pairing; + self->pairReady(self->_discovery_event, passkey); + } + } + } + }); + } + + if (_pair_status_handler.empty()) { + _pair_status_handler = _receiver->addEventHandler( + {[](const hidpp::Report& report) -> bool { + return report.subId() == Receiver::DiscoveryStatus || + report.subId() == Receiver::PairStatus || + report.subId() == Receiver::BoltPairStatus; + }, + [self_weak = _self](const hidpp::Report& report) { + auto self = self_weak.lock(); + if (!self) + return; + + std::lock_guard lock(self->_pair_mutex); + // TODO: forward status to user + if (report.subId() == Receiver::DiscoveryStatus) { + auto event = Receiver::discoveryStatusEvent(report); + + if (self->_pair_state == Discovering && !event.discovering) + self->_pair_state = NotPairing; + } else if (report.subId() == Receiver::PairStatus) { + auto event = Receiver::pairStatusEvent(report); + + if ((self->_pair_state == FindingPasskey || + self->_pair_state == Pairing) && !event.pairing) + self->_pair_state = NotPairing; + } else if (report.subId() == Receiver::BoltPairStatus) { + auto event = Receiver::boltPairStatusEvent(report); + + if ((self->_pair_state == FindingPasskey || + self->_pair_state == Pairing) && !event.pairing) + self->_pair_state = NotPairing; + } + } + }); + } + + enumerate(); +} + +void ReceiverMonitor::enumerate() { + _receiver->enumerate(); +} + +void ReceiverMonitor::waitForDevice(hidpp::DeviceIndex index) { + auto handler_id = std::make_shared>(); + + *handler_id = _receiver->rawDevice()->addEventHandler( + {[index](const std::vector& report) -> bool { + return report[Offset::DeviceIndex] == index; + }, + [self_weak = _self, index, handler_id]( + [[maybe_unused]] const std::vector& report) { + hidpp::DeviceConnectionEvent event{}; + event.withPayload = false; + event.linkEstablished = true; + event.index = index; + event.fromTimeoutCheck = true; + + run_task([self_weak, event, handler_id]() { + *handler_id = {}; + if (auto self = self_weak.lock()) { + self->_addHandler(event); + } + }); + } + }); +} + +std::shared_ptr ReceiverMonitor::receiver() const { + return _receiver; +} + +void ReceiverMonitor::_startPair(uint8_t timeout) { + { + std::lock_guard lock(_pair_mutex); + _pair_state = _receiver->bolt() ? Discovering : Pairing; + _discovery_event = {}; + } + + if (_receiver->bolt()) + receiver()->startDiscover(timeout); + else + receiver()->startPairing(timeout); +} + +void ReceiverMonitor::_stopPair() { + PairState last_state; + { + std::lock_guard lock(_pair_mutex); + last_state = _pair_state; + _pair_state = NotPairing; + } + + if (last_state == Discovering) + receiver()->stopDiscover(); + else if (last_state == Pairing || last_state == FindingPasskey) + receiver()->stopPairing(); +} + +void ReceiverMonitor::_addHandler(const hidpp::DeviceConnectionEvent& event, int tries) { + auto device_path = _receiver->devicePath(); + try { + addDevice(event); + } catch (DeviceNotReady& e) { + if (tries == max_tries) { + logPrintf(WARN, "Failed to add device %s:%d after %d tries." + "Treating as failure.", device_path.c_str(), event.index, max_tries); + } else { + /* Do exponential backoff for 2^tries * backoff ms. */ + std::chrono::milliseconds wait((1 << tries) * ready_backoff); + logPrintf(DEBUG, "Failed to add device %s:%d on try %d, backing off for %dms", + device_path.c_str(), event.index, tries + 1, wait.count()); + run_task_after([self_weak = _self, event, tries]() { + if (auto self = self_weak.lock()) + self->_addHandler(event, tries + 1); + }, wait); + } + } catch (std::exception& e) { + logPrintf(ERROR, "Failed to add device %d to receiver on %s: %s", + event.index, device_path.c_str(), e.what()); + } +} + +void ReceiverMonitor::_removeHandler(hidpp::DeviceIndex index) { + try { + removeDevice(index); + } catch (std::exception& e) { + logPrintf(ERROR, "Failed to remove device %d from receiver on %s: %s", + index, _receiver->devicePath().c_str(), e.what()); + } +} diff --git a/src/logid/backend/hidpp10/ReceiverMonitor.h b/src/logid/backend/hidpp10/ReceiverMonitor.h new file mode 100644 index 0000000..32b6df6 --- /dev/null +++ b/src/logid/backend/hidpp10/ReceiverMonitor.h @@ -0,0 +1,116 @@ +/* + * Copyright 2019-2023 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_DJ_RECEIVERMONITOR_H +#define LOGID_BACKEND_DJ_RECEIVERMONITOR_H + +#include +#include +#include +#include + +namespace logid::backend::hidpp10 { + + template + class _receiverMonitorWrapper : public T { + friend class ReceiverMonitor; + + public: + template + explicit _receiverMonitorWrapper(Args... args) : T(std::forward(args)...) {} + + template + static std::shared_ptr make(Args... args) { + return std::make_shared<_receiverMonitorWrapper>(std::forward(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& 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() const; + + private: + void _ready(); + + void _addHandler(const hidpp::DeviceConnectionEvent& event, int tries = 0); + + void _removeHandler(hidpp::DeviceIndex index); + + std::shared_ptr _receiver; + + enum PairState { + NotPairing, + Discovering, + FindingPasskey, + Pairing, + }; + + std::mutex _pair_mutex; + DeviceDiscoveryEvent _discovery_event; + PairState _pair_state = NotPairing; + + EventHandlerLock _connect_ev_handler; + + EventHandlerLock _discover_ev_handler; + EventHandlerLock _passkey_ev_handler; + EventHandlerLock _pair_status_handler; + + std::weak_ptr _self; + + public: + template + static std::shared_ptr make(Args... args) { + auto receiver_monitor = _receiverMonitorWrapper::make(std::forward(args)...); + receiver_monitor->_self = receiver_monitor; + receiver_monitor->_ready(); + return receiver_monitor; + } + }; + +} + +#endif //LOGID_BACKEND_DJ_RECEIVERMONITOR_H \ No newline at end of file diff --git a/src/logid/backend/hidpp10/defs.h b/src/logid/backend/hidpp10/defs.h index d3c06b8..ff18ed4 100644 --- a/src/logid/backend/hidpp10/defs.h +++ b/src/logid/backend/hidpp10/defs.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,17 +19,15 @@ #ifndef LOGID_BACKEND_HIDPP10_DEFS_H #define LOGID_BACKEND_HIDPP10_DEFS_H -namespace logid { -namespace backend { -namespace hidpp10 -{ - enum SubID: uint8_t - { +namespace logid::backend::hidpp10 { + enum SubID : uint8_t { SetRegisterShort = 0x80, GetRegisterShort = 0x81, SetRegisterLong = 0x82, - GetRegisterLong = 0x83 + GetRegisterLong = 0x83, }; -}}} + + static constexpr size_t SubIDCount = 4; +} #endif //LOGID_BACKEND_HIDPP10_DEFS_H \ No newline at end of file diff --git a/src/logid/backend/hidpp20/Device.cpp b/src/logid/backend/hidpp20/Device.cpp index d32bf41..adbcc27 100644 --- a/src/logid/backend/hidpp20/Device.cpp +++ b/src/logid/backend/hidpp20/Device.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,67 +17,146 @@ */ #include +#include +#include +#include -#include "Device.h" -#include "../hidpp/defs.h" - +using namespace logid::backend; using namespace logid::backend::hidpp20; -Device::Device(std::string path, hidpp::DeviceIndex index) - : hidpp::Device(path, index) -{ - assert(std::get<0>(version()) >= 2); +Device::Device(const std::string& path, hidpp::DeviceIndex index, + const std::shared_ptr& monitor, double timeout) : + hidpp::Device(path, index, monitor, timeout) { } -Device::Device(std::shared_ptr raw_device, hidpp::DeviceIndex index) - : hidpp::Device(raw_device, index) -{ - assert(std::get<0>(version()) >= 2); +Device::Device(std::shared_ptr raw_device, + hidpp::DeviceIndex index, double timeout) : + hidpp::Device(std::move(raw_device), index, timeout) { } -Device::Device(std::shared_ptr receiver, hidpp::DeviceIndex index) - : hidpp::Device(receiver, index) -{ - assert(std::get<0>(version()) >= 2); +Device::Device(const std::shared_ptr& receiver, + hidpp::DeviceConnectionEvent event, double timeout) : + hidpp::Device(receiver, event, timeout) { +} + +Device::Device(const std::shared_ptr& receiver, + hidpp::DeviceIndex index, double timeout) + : hidpp::Device(receiver, index, timeout) { } std::vector Device::callFunction(uint8_t feature_index, - uint8_t function, std::vector& params) -{ + uint8_t function, std::vector& params) { hidpp::Report::Type type; assert(params.size() <= hidpp::LongParamLength); - if(params.size() <= hidpp::ShortParamLength) + if (params.size() <= hidpp::ShortParamLength) type = hidpp::Report::Type::Short; - else if(params.size() <= hidpp::LongParamLength) + else if (params.size() <= hidpp::LongParamLength) type = hidpp::Report::Type::Long; else throw hidpp::Report::InvalidReportID(); hidpp::Report request(type, deviceIndex(), feature_index, function, - LOGID_HIDPP_SOFTWARE_ID); + hidpp::softwareID); std::copy(params.begin(), params.end(), request.paramBegin()); auto response = this->sendReport(request); - return std::vector(response.paramBegin(), response.paramEnd()); + return {response.paramBegin(), response.paramEnd()}; } void Device::callFunctionNoResponse(uint8_t feature_index, uint8_t function, - std::vector ¶ms) -{ + std::vector& params) { hidpp::Report::Type type; assert(params.size() <= hidpp::LongParamLength); - if(params.size() <= hidpp::ShortParamLength) + if (params.size() <= hidpp::ShortParamLength) type = hidpp::Report::Type::Short; - else if(params.size() <= hidpp::LongParamLength) + else if (params.size() <= hidpp::LongParamLength) type = hidpp::Report::Type::Long; else throw hidpp::Report::InvalidReportID(); - hidpp::Report request(type, deviceIndex(), feature_index, function, - LOGID_HIDPP_SOFTWARE_ID); + hidpp::Report request(type, deviceIndex(), feature_index, function, hidpp::softwareID); std::copy(params.begin(), params.end(), request.paramBegin()); - this->sendReportNoResponse(request); -} \ No newline at end of file + this->sendReportNoACK(request); +} + +hidpp::Report Device::sendReport(const hidpp::Report& report) { + auto& response_slot = _responses[report.feature() % _responses.size()]; + + std::unique_lock 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(response)) { + return std::get(response); + } else { // if(std::holds_alternative(response)) + auto error = std::get(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 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(); +} diff --git a/src/logid/backend/hidpp20/Device.h b/src/logid/backend/hidpp20/Device.h index 2628016..a3e3103 100644 --- a/src/logid/backend/hidpp20/Device.h +++ b/src/logid/backend/hidpp20/Device.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,28 +19,64 @@ #ifndef LOGID_BACKEND_HIDPP20_DEVICE_H #define LOGID_BACKEND_HIDPP20_DEVICE_H -#include "../hidpp/Device.h" #include +#include +#include +#include +#include -namespace logid { -namespace backend { -namespace hidpp20 { - class Device : public hidpp::Device - { +namespace logid::backend::hidpp20 { + class Device : public hidpp::Device { public: - Device(std::string path, hidpp::DeviceIndex index); - Device(std::shared_ptr raw_device, hidpp::DeviceIndex index); - Device(std::shared_ptr receiver, hidpp::DeviceIndex - index); - std::vector callFunction(uint8_t feature_index, - uint8_t function, - std::vector& params); + uint8_t function, + std::vector& params); void callFunctionNoResponse(uint8_t feature_index, - uint8_t function, - std::vector& params); + uint8_t function, + std::vector& 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& monitor, double timeout); + + Device(std::shared_ptr raw_device, + hidpp::DeviceIndex index, double timeout); + + Device(const std::shared_ptr& receiver, + hidpp::DeviceConnectionEvent event, double timeout); + + Device(const std::shared_ptr& receiver, + hidpp::DeviceIndex index, double timeout); + + bool responseReport(const hidpp::Report& report) final; + + private: + typedef std::variant Response; + struct ResponseSlot { + std::optional response; + std::optional feature; + void reset(); + }; + + /* Multiplex responses on lower nibble of SubID, ignore upper nibble for space */ + std::array _responses; + + public: + template + static std::shared_ptr make(Args... args) { + auto device = makeDerived(std::forward(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 \ No newline at end of file diff --git a/src/logid/backend/hidpp20/Error.cpp b/src/logid/backend/hidpp20/Error.cpp index eea11a9..4eb0772 100644 --- a/src/logid/backend/hidpp20/Error.cpp +++ b/src/logid/backend/hidpp20/Error.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,47 +16,49 @@ * */ +#include #include -#include "Error.h" +using namespace logid::backend; using namespace logid::backend::hidpp20; -Error::Error(uint8_t code) : _code (code) -{ +Error::Error(uint8_t code, hidpp::DeviceIndex index) : _code(code), _index (index) { assert(_code != NoError); } -const char* Error::what() const noexcept -{ - switch(_code) { - case NoError: - return "No error"; - case Unknown: - return "Unknown"; - case InvalidArgument: - return "Invalid argument"; - case OutOfRange: - return "Out of range"; - case HardwareError: - return "Hardware error"; - case LogitechInternal: - return "Logitech internal feature"; - case InvalidFeatureIndex: - return "Invalid feature index"; - case InvalidFunctionID: - return "Invalid function ID"; - case Busy: - return "Busy"; - case Unsupported: - return "Unsupported"; - case UnknownDevice: - return "Unknown device"; - default: - return "Unknown error code"; +const char* Error::what() const noexcept { + switch (_code) { + case NoError: + return "No error"; + case Unknown: + return "Unknown"; + case InvalidArgument: + return "Invalid argument"; + case OutOfRange: + return "Out of range"; + case HardwareError: + return "Hardware error"; + case LogitechInternal: + return "Logitech internal feature"; + case InvalidFeatureIndex: + return "Invalid feature index"; + case InvalidFunctionID: + return "Invalid function ID"; + case Busy: + return "Busy"; + case Unsupported: + return "Unsupported"; + case UnknownDevice: + return "Unknown device"; + default: + return "Unknown error code"; } } -uint8_t Error::code() const noexcept -{ +uint8_t Error::code() const noexcept { return _code; +} + +hidpp::DeviceIndex Error::deviceIndex() const noexcept { + return _index; } \ No newline at end of file diff --git a/src/logid/backend/hidpp20/Error.h b/src/logid/backend/hidpp20/Error.h index 06b5132..94e48eb 100644 --- a/src/logid/backend/hidpp20/Error.h +++ b/src/logid/backend/hidpp20/Error.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,18 +19,16 @@ #ifndef LOGID_BACKEND_HIDPP20_ERROR_H #define LOGID_BACKEND_HIDPP20_ERROR_H +#include #include #include -namespace logid { -namespace backend { -namespace hidpp20 { +namespace logid::backend::hidpp20 { static constexpr uint8_t ErrorID = 0xFF; - class Error: public std::exception - { + class Error : public std::exception { public: - enum ErrorCode: uint8_t { + enum ErrorCode : uint8_t { NoError = 0, Unknown = 1, InvalidArgument = 2, @@ -44,14 +42,18 @@ namespace hidpp20 { UnknownDevice = 10 }; - explicit Error(uint8_t code); + Error(uint8_t code, hidpp::DeviceIndex index); - const char* what() const noexcept override; - uint8_t code() const noexcept; + [[nodiscard]] const char* what() const noexcept override; + + [[nodiscard]] uint8_t code() const noexcept; + + [[nodiscard]] hidpp::DeviceIndex deviceIndex() const noexcept; private: uint8_t _code; + hidpp::DeviceIndex _index; }; -}}} +} #endif //LOGID_BACKEND_HIDPP20_ERROR_H \ No newline at end of file diff --git a/src/logid/backend/hidpp20/EssentialFeature.cpp b/src/logid/backend/hidpp20/EssentialFeature.cpp index 11b9751..1d836bf 100644 --- a/src/logid/backend/hidpp20/EssentialFeature.cpp +++ b/src/logid/backend/hidpp20/EssentialFeature.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,57 +16,51 @@ * */ +#include +#include +#include +#include #include -#include "EssentialFeature.h" -#include "feature_defs.h" -#include "features/Root.h" -#include "Error.h" using namespace logid::backend::hidpp20; std::vector EssentialFeature::callFunction(uint8_t function_id, - std::vector& params) -{ + std::vector& params) { hidpp::Report::Type type; assert(params.size() <= hidpp::LongParamLength); - if(params.size() <= hidpp::ShortParamLength) + if (params.size() <= hidpp::ShortParamLength) type = hidpp::Report::Type::Short; - else if(params.size() <= hidpp::LongParamLength) + else if (params.size() <= hidpp::LongParamLength) type = hidpp::Report::Type::Long; else throw hidpp::Report::InvalidReportID(); - hidpp::Report request(type, _device->deviceIndex(), _index, function_id, - LOGID_HIDPP_SOFTWARE_ID); + hidpp::Report request(type, _device->deviceIndex(), _index, function_id, hidpp::softwareID); std::copy(params.begin(), params.end(), request.paramBegin()); auto response = _device->sendReport(request); - return std::vector(response.paramBegin(), response.paramEnd()); + return {response.paramBegin(), response.paramEnd()}; } EssentialFeature::EssentialFeature(hidpp::Device* dev, uint16_t _id) : - _device (dev) -{ + _device(dev) { _index = hidpp20::FeatureID::ROOT; - if(_id) - { + if (_id) { std::vector getFunc_req(2); getFunc_req[0] = (_id >> 8) & 0xff; getFunc_req[1] = _id & 0xff; try { - auto getFunc_resp = this->callFunction(Root::GetFeature, - getFunc_req); - _index = getFunc_resp[0]; - } catch(Error& e) { - if(e.code() == Error::InvalidFeatureIndex) + _index = this->callFunction(Root::GetFeature, getFunc_req).at(0); + } catch (Error& e) { + if (e.code() == Error::InvalidFeatureIndex) throw UnsupportedFeature(_id); throw e; } // 0 if not found - if(!_index) + if (!_index) throw UnsupportedFeature(_id); } } diff --git a/src/logid/backend/hidpp20/EssentialFeature.h b/src/logid/backend/hidpp20/EssentialFeature.h index bfc578f..7e3f995 100644 --- a/src/logid/backend/hidpp20/EssentialFeature.h +++ b/src/logid/backend/hidpp20/EssentialFeature.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,26 +25,24 @@ * hidpp::Device class. No version checks are provided here */ -#include "Device.h" +#include -namespace logid { -namespace backend { -namespace hidpp20 -{ - class EssentialFeature - { +namespace logid::backend::hidpp20 { + class EssentialFeature { public: static const uint16_t ID; + virtual uint16_t getID() = 0; protected: EssentialFeature(hidpp::Device* dev, uint16_t _id); + std::vector callFunction(uint8_t function_id, - std::vector& params); - private: - hidpp::Device* _device; + std::vector& params); + + hidpp::Device* const _device; uint8_t _index; }; -}}} +} #endif //LOGID_BACKEND_HIDPP20_ESSENTIAL_FEATURE_H \ No newline at end of file diff --git a/src/logid/backend/hidpp20/Feature.cpp b/src/logid/backend/hidpp20/Feature.cpp index 1fc62b5..a810c6d 100644 --- a/src/logid/backend/hidpp20/Feature.cpp +++ b/src/logid/backend/hidpp20/Feature.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,62 +16,53 @@ * */ -#include "Error.h" -#include "Feature.h" -#include "feature_defs.h" -#include "features/Root.h" +#include +#include +#include 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 Feature::callFunction(uint8_t function_id, - std::vector& params) -{ + std::vector& params) { return _device->callFunction(_index, function_id, params); } void Feature::callFunctionNoResponse(uint8_t function_id, - std::vector& params) -{ + std::vector& 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 getFunc_req(2); getFunc_req[0] = (_id >> 8) & 0xff; getFunc_req[1] = _id & 0xff; try { - auto getFunc_resp = this->callFunction(Root::GetFeature, - getFunc_req); + auto getFunc_resp = this->callFunction(Root::GetFeature, getFunc_req); _index = getFunc_resp[0]; - } catch(Error& e) { - if(e.code() == Error::InvalidFeatureIndex) + } catch (Error& e) { + if (e.code() == Error::InvalidFeatureIndex) throw UnsupportedFeature(_id); throw e; } // 0 if not found - if(!_index) + if (!_index) throw UnsupportedFeature(_id); } } -uint8_t Feature::featureIndex() -{ +uint8_t Feature::featureIndex() const { return _index; } \ No newline at end of file diff --git a/src/logid/backend/hidpp20/Feature.h b/src/logid/backend/hidpp20/Feature.h index 3469ce6..1e269e2 100644 --- a/src/logid/backend/hidpp20/Feature.h +++ b/src/logid/backend/hidpp20/Feature.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,38 +20,44 @@ #define LOGID_BACKEND_HIDPP20_FEATURE_H #include -#include "Device.h" +#include +#include -namespace logid { -namespace backend { -namespace hidpp20 { - class UnsupportedFeature : public std::exception - { +namespace logid::backend::hidpp20 { + class Device; + + class UnsupportedFeature : public std::exception { public: - explicit UnsupportedFeature(uint16_t ID) : _f_id (ID) {} - const char* what() const noexcept override; - uint16_t code() const noexcept; + explicit UnsupportedFeature(uint16_t ID) : _f_id(ID) {} + + [[nodiscard]] const char* what() const noexcept override; + + [[nodiscard]] uint16_t code() const noexcept; + private: uint16_t _f_id; }; - class Feature - { + class Feature { public: static const uint16_t ID; + virtual uint16_t getID() = 0; - uint8_t featureIndex(); + + [[nodiscard]] uint8_t featureIndex() const; + virtual ~Feature() = default; + protected: explicit Feature(Device* dev, uint16_t _id); - std::vector callFunction(uint8_t function_id, - std::vector& params); - void callFunctionNoResponse(uint8_t function_id, - std::vector& params); - private: - Device* _device; + + std::vector callFunction(uint8_t function_id, std::vector& params); + + void callFunctionNoResponse(uint8_t function_id, std::vector& params); + + Device* const _device; uint8_t _index; }; -}}} +} #endif //LOGID_BACKEND_HIDPP20_FEATURE_H \ No newline at end of file diff --git a/src/logid/backend/hidpp20/feature_defs.h b/src/logid/backend/hidpp20/feature_defs.h index 687dcc4..3eebe42 100644 --- a/src/logid/backend/hidpp20/feature_defs.h +++ b/src/logid/backend/hidpp20/feature_defs.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,9 +21,7 @@ #include -namespace logid { -namespace backend { -namespace hidpp20 { +namespace logid::backend::hidpp20 { struct feature_info { uint16_t feature_id; bool obsolete; @@ -31,10 +29,8 @@ namespace hidpp20 { bool hidden; }; - namespace FeatureID - { - enum FeatureID : uint16_t - { + namespace FeatureID { + enum FeatureID : uint16_t { ROOT = 0x0000, FEATURE_SET = 0x0001, FEATURE_INFO = 0x0002, @@ -78,6 +74,7 @@ namespace 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, @@ -127,6 +124,6 @@ namespace hidpp20 { }; } -}}} +} #endif //LOGID_BACKEND_HIDPP20_FEATUREDEFS \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/AdjustableDPI.cpp b/src/logid/backend/hidpp20/features/AdjustableDPI.cpp index 722d6cb..15dcd2b 100644 --- a/src/logid/backend/hidpp20/features/AdjustableDPI.cpp +++ b/src/logid/backend/hidpp20/features/AdjustableDPI.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,35 +15,32 @@ * along with this program. If not, see . * */ -#include "AdjustableDPI.h" +#include 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 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 params(1); params[0] = sensor; auto response = callFunction(GetSensorDPIList, params); dpi_list.dpiStep = false; - for(std::size_t i = 1; i < response.size(); i+=2) { + for (std::size_t i = 1; i < response.size(); i += 2) { uint16_t dpi = response[i + 1]; dpi |= (response[i] << 8); - if(!dpi) + if (!dpi) break; - if(dpi >= 0xe000) { + if (dpi >= 0xe000) { dpi_list.isRange = true; dpi_list.dpiStep = dpi - 0xe000; } else { @@ -54,8 +51,7 @@ AdjustableDPI::SensorDPIList AdjustableDPI::getSensorDPIList(uint8_t sensor) return dpi_list; } -uint16_t AdjustableDPI::getDefaultSensorDPI(uint8_t sensor) -{ +uint16_t AdjustableDPI::getDefaultSensorDPI(uint8_t sensor) { std::vector params(1); params[0] = sensor; auto response = callFunction(GetSensorDPI, params); @@ -66,8 +62,7 @@ uint16_t AdjustableDPI::getDefaultSensorDPI(uint8_t sensor) return default_dpi; } -uint16_t AdjustableDPI::getSensorDPI(uint8_t sensor) -{ +uint16_t AdjustableDPI::getSensorDPI(uint8_t sensor) { std::vector params(1); params[0] = sensor; auto response = callFunction(GetSensorDPI, params); @@ -78,8 +73,7 @@ uint16_t AdjustableDPI::getSensorDPI(uint8_t sensor) return dpi; } -void AdjustableDPI::setSensorDPI(uint8_t sensor, uint16_t dpi) -{ +void AdjustableDPI::setSensorDPI(uint8_t sensor, uint16_t dpi) { std::vector params(3); params[0] = sensor; params[1] = (dpi >> 8); diff --git a/src/logid/backend/hidpp20/features/AdjustableDPI.h b/src/logid/backend/hidpp20/features/AdjustableDPI.h index 9a76d0f..a7a7fdb 100644 --- a/src/logid/backend/hidpp20/features/AdjustableDPI.h +++ b/src/logid/backend/hidpp20/features/AdjustableDPI.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,18 +18,15 @@ #ifndef LOGID_BACKEND_HIDPP20_FEATURE_ADJUSTABLEDPI_H #define LOGID_BACKEND_HIDPP20_FEATURE_ADJUSTABLEDPI_H -#include "../feature_defs.h" -#include "../Feature.h" +#include +#include -namespace logid { -namespace backend { -namespace hidpp20 -{ - class AdjustableDPI : public Feature - { +namespace logid::backend::hidpp20 { + class AdjustableDPI : public Feature { public: static const uint16_t ID = FeatureID::ADJUSTABLE_DPI; - virtual uint16_t getID() { return ID; } + + [[nodiscard]] uint16_t getID() final { return ID; } enum Function { GetSensorCount = 0, @@ -38,23 +35,24 @@ namespace hidpp20 SetSensorDPI = 3 }; - AdjustableDPI(Device* dev); + explicit AdjustableDPI(Device* dev); uint8_t getSensorCount(); - struct SensorDPIList - { + struct SensorDPIList { std::vector 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 diff --git a/src/logid/backend/hidpp20/features/ChangeHost.cpp b/src/logid/backend/hidpp20/features/ChangeHost.cpp index 3083718..f6ef377 100644 --- a/src/logid/backend/hidpp20/features/ChangeHost.cpp +++ b/src/logid/backend/hidpp20/features/ChangeHost.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,17 +15,15 @@ * along with this program. If not, see . * */ -#include "ChangeHost.h" -#include "../Error.h" +#include +#include 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 params(0); auto response = callFunction(GetHostInfo, params); @@ -34,33 +32,32 @@ ChangeHost::HostInfo ChangeHost::getHostInfo() info.currentHost = response[1]; info.enhancedHostSwitch = response[2] & 1; - if(!_host_count) + if (!_host_count) _host_count = info.hostCount; return info; } -void ChangeHost::setHost(uint8_t host) -{ +void ChangeHost::setHost(uint8_t host) { /* Expect connection to be severed here, send without response * * Since there is no response, we have to emulate any kind of * error that may be returned (i.e. InvalidArgument as per the docs) */ - if(!_host_count) + if (!_host_count) getHostInfo(); - if(host >= _host_count) - throw hidpp20::Error(hidpp20::Error::InvalidArgument); + if (host >= _host_count) + throw Error(hidpp20::Error::InvalidArgument, _device->deviceIndex()); std::vector params = {host}; callFunctionNoResponse(SetCurrentHost, params); } -std::vector ChangeHost::getCookies() -{ - if(!_host_count) +[[maybe_unused]] +std::vector ChangeHost::getCookies() { + if (!_host_count) getHostInfo(); std::vector params(0); @@ -71,8 +68,8 @@ std::vector ChangeHost::getCookies() return response; } -void ChangeHost::setCookie(uint8_t host, uint8_t cookie) -{ +[[maybe_unused]] +void ChangeHost::setCookie(uint8_t host, uint8_t cookie) { std::vector params = {host, cookie}; callFunction(SetCookie, params); } \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/ChangeHost.h b/src/logid/backend/hidpp20/features/ChangeHost.h index 094cfb1..14e5b33 100644 --- a/src/logid/backend/hidpp20/features/ChangeHost.h +++ b/src/logid/backend/hidpp20/features/ChangeHost.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,20 +18,17 @@ #ifndef LOGID_BACKEND_HIDPP20_FEATURE_CHANGEHOST_H #define LOGID_BACKEND_HIDPP20_FEATURE_CHANGEHOST_H -#include "../feature_defs.h" -#include "../Feature.h" +#include +#include -namespace logid { -namespace backend { -namespace hidpp20 -{ - class ChangeHost : public Feature - { +namespace logid::backend::hidpp20 { + class ChangeHost : public Feature { public: static const uint16_t ID = FeatureID::CHANGE_HOST; - virtual uint16_t getID() { return ID; } - ChangeHost(Device* dev); + [[nodiscard]] uint16_t getID() final { return ID; } + + explicit ChangeHost(Device* dev); enum Function { GetHostInfo = 0, @@ -40,22 +37,23 @@ namespace hidpp20 SetCookie = 3 }; - struct HostInfo - { + struct HostInfo { uint8_t hostCount; uint8_t currentHost; bool enhancedHostSwitch; }; HostInfo getHostInfo(); + void setHost(uint8_t host); - std::vector getCookies(); - void setCookie(uint8_t host, uint8_t cookie); + [[maybe_unused]] [[maybe_unused]] std::vector getCookies(); + + [[maybe_unused]] [[maybe_unused]] void setCookie(uint8_t host, uint8_t cookie); + private: uint8_t _host_count; }; -}}} - +} #endif //LOGID_BACKEND_HIDPP20_FEATURE_CHANGEHOST_H diff --git a/src/logid/backend/hidpp20/features/DeviceName.cpp b/src/logid/backend/hidpp20/features/DeviceName.cpp index d6c44bc..3cf94f8 100644 --- a/src/logid/backend/hidpp20/features/DeviceName.cpp +++ b/src/logid/backend/hidpp20/features/DeviceName.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,71 +16,49 @@ * */ -#include -#include "DeviceName.h" +#include using namespace logid::backend; using namespace logid::backend::hidpp20; -DeviceName::DeviceName(Device* dev) : Feature(dev, ID) -{ -} +namespace { + std::string _getName(uint8_t length, + const std::function(std::vector)>& fcall) { + uint8_t function_calls = length / hidpp::LongParamLength; + if (length % hidpp::LongParamLength) + function_calls++; + std::vector params(1); + std::string name; -uint8_t DeviceName::getNameLength() -{ - std::vector params(0); - - auto response = this->callFunction(Function::GetLength, params); - return response[0]; -} - -std::string _getName(uint8_t length, - const std::function(std::vector)>& fcall) -{ - uint8_t function_calls = length/hidpp::LongParamLength; - if(length % hidpp::LongParamLength) - function_calls++; - std::vector params(1); - std::string name; - - for(uint8_t i = 0; i < function_calls; i++) { - params[0] = i*hidpp::LongParamLength; - auto name_section = fcall(params); - for(std::size_t j = 0; j < hidpp::LongParamLength; j++) { - if(params[0] + j >= length) - return name; - name += name_section[j]; + for (uint8_t i = 0; i < function_calls; i++) { + params[0] = i * hidpp::LongParamLength; + auto name_section = fcall(params); + for (std::size_t j = 0; j < hidpp::LongParamLength; j++) { + if (params[0] + j >= length) + return name; + name += (char) name_section[j]; + } } + + return name; } - - return name; } -std::string DeviceName::getName() -{ - return _getName(getNameLength(), [this] - (std::vector params)->std::vector { - return this->callFunction(Function::GetDeviceName, params); - }); +DeviceName::DeviceName(hidpp::Device* dev) : + EssentialFeature(dev, ID) { } -EssentialDeviceName::EssentialDeviceName(hidpp::Device* dev) : - EssentialFeature(dev, ID) -{ -} - -uint8_t EssentialDeviceName::getNameLength() -{ +uint8_t DeviceName::getNameLength() { std::vector params(0); auto response = this->callFunction(DeviceName::Function::GetLength, params); + return response[0]; } -std::string EssentialDeviceName::getName() -{ +std::string DeviceName::getName() { return _getName(getNameLength(), [this] - (std::vector params)->std::vector { + (std::vector params) -> std::vector { return this->callFunction(DeviceName::Function::GetDeviceName, params); }); } \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/DeviceName.h b/src/logid/backend/hidpp20/features/DeviceName.h index e21fe27..71a8c11 100644 --- a/src/logid/backend/hidpp20/features/DeviceName.h +++ b/src/logid/backend/hidpp20/features/DeviceName.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,43 +19,27 @@ #ifndef LOGID_BACKEND_HIDPP20_FEATURE_DEVICENAME_H #define LOGID_BACKEND_HIDPP20_FEATURE_DEVICENAME_H -#include "../Feature.h" -#include "../feature_defs.h" -#include "../EssentialFeature.h" +#include +#include -namespace logid { -namespace backend { -namespace hidpp20 -{ - class DeviceName : public Feature - { +namespace logid::backend::hidpp20 { + class DeviceName : public EssentialFeature { public: static const uint16_t ID = FeatureID::DEVICE_NAME; - virtual uint16_t getID() { return ID; } - enum Function : uint8_t - { + enum Function : uint8_t { GetLength = 0, GetDeviceName = 1 }; - explicit DeviceName(Device* device); + [[nodiscard]] uint16_t getID() final { return ID; } - uint8_t getNameLength(); - std::string getName(); + explicit DeviceName(hidpp::Device* device); + + [[nodiscard]] uint8_t getNameLength(); + + [[nodiscard]] std::string getName(); }; - - class EssentialDeviceName : public EssentialFeature - { - public: - static const uint16_t ID = FeatureID::DEVICE_NAME; - virtual uint16_t getID() { return ID; } - - explicit EssentialDeviceName(hidpp::Device* device); - - uint8_t getNameLength(); - std::string getName(); - }; -}}} +} #endif //LOGID_BACKEND_HIDPP20_FEATURE_DEVICENAME_H \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/FeatureSet.cpp b/src/logid/backend/hidpp20/features/FeatureSet.cpp index 092b809..f626bbb 100644 --- a/src/logid/backend/hidpp20/features/FeatureSet.cpp +++ b/src/logid/backend/hidpp20/features/FeatureSet.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,23 +15,21 @@ * along with this program. If not, see . * */ -#include "FeatureSet.h" +#include using namespace logid::backend::hidpp20; -FeatureSet::FeatureSet(Device *device) : Feature(device, ID) -{ +[[maybe_unused]] +FeatureSet::FeatureSet(Device* device) : Feature(device, ID) { } -uint8_t FeatureSet::getFeatureCount() -{ +uint8_t FeatureSet::getFeatureCount() { std::vector 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 params(1); params[0] = feature_index; auto response = callFunction(GetFeature, params); @@ -41,11 +39,11 @@ uint16_t FeatureSet::getFeature(uint8_t feature_index) return feature_id; } -std::map FeatureSet::getFeatures() -{ +[[maybe_unused]] +std::map FeatureSet::getFeatures() { uint8_t feature_count = getFeatureCount(); std::map features; - for(uint8_t i = 0; i < feature_count; i++) + for (uint8_t i = 0; i < feature_count; i++) features[i] = getFeature(i); return features; } \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/FeatureSet.h b/src/logid/backend/hidpp20/features/FeatureSet.h index 27a814e..d8692a3 100644 --- a/src/logid/backend/hidpp20/features/FeatureSet.h +++ b/src/logid/backend/hidpp20/features/FeatureSet.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,31 +18,32 @@ #ifndef LOGID_BACKEND_HIDPP20_FEATURE_FEATURESET_H #define LOGID_BACKEND_HIDPP20_FEATURE_FEATURESET_H -#include "../Feature.h" -#include "../feature_defs.h" +#include +#include +#include -namespace logid { -namespace backend { -namespace hidpp20 -{ - class FeatureSet : public Feature - { +namespace logid::backend::hidpp20 { + class FeatureSet : public Feature { public: static const uint16_t ID = FeatureID::FEATURE_SET; - virtual uint16_t getID() { return ID; } - enum Function : uint8_t - { + [[nodiscard]] uint16_t getID() final { return ID; } + + enum Function : uint8_t { GetFeatureCount = 0, GetFeature = 1 }; + [[maybe_unused]] [[maybe_unused]] explicit FeatureSet(Device* device); - uint8_t getFeatureCount(); - uint16_t getFeature(uint8_t feature_index); - std::map getFeatures(); + [[nodiscard]] uint8_t getFeatureCount(); + + [[nodiscard]] uint16_t getFeature(uint8_t feature_index); + + [[maybe_unused]] + [[nodiscard]] std::map getFeatures(); }; -}}} +} #endif //LOGID_BACKEND_HIDPP20_FEATURE_FEATURESET_H diff --git a/src/logid/backend/hidpp20/features/HiresScroll.cpp b/src/logid/backend/hidpp20/features/HiresScroll.cpp index d8f64e8..2a03190 100644 --- a/src/logid/backend/hidpp20/features/HiresScroll.cpp +++ b/src/logid/backend/hidpp20/features/HiresScroll.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,17 +15,15 @@ * along with this program. If not, see . * */ +#include #include -#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 params(0); auto response = callFunction(GetCapabilities, params); @@ -35,41 +33,35 @@ HiresScroll::Capabilities HiresScroll::getCapabilities() return capabilities; } -uint8_t HiresScroll::getMode() -{ +uint8_t HiresScroll::getMode() { std::vector params(0); auto response = callFunction(GetMode, params); return response[0]; } -void HiresScroll::setMode(uint8_t mode) -{ +void HiresScroll::setMode(uint8_t mode) { std::vector params(1); params[0] = mode; callFunction(SetMode, params); } -bool HiresScroll::getRatchetState() -{ +[[maybe_unused]] bool HiresScroll::getRatchetState() { std::vector params(0); auto response = callFunction(GetRatchetState, params); return params[0]; } -HiresScroll::WheelStatus HiresScroll::wheelMovementEvent(const hidpp::Report - &report) -{ +HiresScroll::WheelStatus HiresScroll::wheelMovementEvent(const hidpp::Report& report) { assert(report.function() == WheelMovement); WheelStatus status{}; - status.hiRes = report.paramBegin()[0] & 1<<4; + status.hiRes = report.paramBegin()[0] & 1 << 4; status.periods = report.paramBegin()[0] & 0x0F; - status.deltaV = report.paramBegin()[1] << 8 | report.paramBegin()[2]; + status.deltaV = (int16_t) (report.paramBegin()[1] << 8 | report.paramBegin()[2]); return status; } -HiresScroll::RatchetState HiresScroll::ratchetSwitchEvent(const hidpp::Report - &report) -{ +[[maybe_unused]] +HiresScroll::RatchetState HiresScroll::ratchetSwitchEvent(const hidpp::Report& report) { assert(report.function() == RatchetSwitch); // Possible bad cast return static_cast(report.paramBegin()[0]); diff --git a/src/logid/backend/hidpp20/features/HiresScroll.h b/src/logid/backend/hidpp20/features/HiresScroll.h index e580f82..56eb0d4 100644 --- a/src/logid/backend/hidpp20/features/HiresScroll.h +++ b/src/logid/backend/hidpp20/features/HiresScroll.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,61 +18,52 @@ #ifndef LOGID_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H #define LOGID_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H -#include "../Feature.h" -#include "../feature_defs.h" +#include +#include +#include -namespace logid { -namespace backend { -namespace hidpp20 -{ - class HiresScroll : public Feature - { +namespace logid::backend::hidpp20 { + class HiresScroll : public Feature { public: ///TODO: Hires scroll V1? static const uint16_t ID = FeatureID::HIRES_SCROLLING_V2; - virtual uint16_t getID() { return ID; } - enum Function : uint8_t - { + uint16_t getID() final { return ID; } + + enum Function : uint8_t { GetCapabilities = 0, GetMode = 1, SetMode = 2, GetRatchetState = 3 }; - enum Event : uint8_t - { + enum Event : uint8_t { WheelMovement = 0, RatchetSwitch = 1, }; - enum Capability : uint8_t - { - Invertable = 1<<3, - HasRatchet = 1<<2 + enum Capability : uint8_t { + Invertible = 1 << 3, + HasRatchet = 1 << 2 }; - enum Mode : uint8_t - { - Inverted = 1<<2, - HiRes = 1<<1, + enum Mode : uint8_t { + Inverted = 1 << 2, + HiRes = 1 << 1, Target = 1 }; - enum RatchetState : uint8_t - { + enum RatchetState : uint8_t { FreeWheel = 0, Ratchet = 1 }; - struct Capabilities - { + struct Capabilities { uint8_t multiplier; uint8_t flags; }; - struct WheelStatus - { + struct WheelStatus { bool hiRes; uint8_t periods; int16_t deltaV; @@ -81,13 +72,19 @@ namespace hidpp20 explicit HiresScroll(Device* device); Capabilities getCapabilities(); + uint8_t getMode(); + void setMode(uint8_t mode); + + [[maybe_unused]] bool getRatchetState(); static WheelStatus wheelMovementEvent(const hidpp::Report& report); + + [[maybe_unused]] static RatchetState ratchetSwitchEvent(const hidpp::Report& report); }; -}}} +} #endif //LOGID_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H diff --git a/src/logid/backend/hidpp20/features/ReprogControls.cpp b/src/logid/backend/hidpp20/features/ReprogControls.cpp index ce10bb0..6858a75 100644 --- a/src/logid/backend/hidpp20/features/ReprogControls.cpp +++ b/src/logid/backend/hidpp20/features/ReprogControls.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,53 +15,58 @@ * along with this program. If not, see . * */ +#include +#include +#include #include -#include "../Error.h" -#include "ReprogControls.h" using namespace logid::backend::hidpp20; -#define DEFINE_REPROG(x, base) \ -x::x(Device* dev) : base(dev, ID) \ -{ \ -} \ -x::x(Device* dev, uint16_t _id) : base(dev, _id) \ -{ \ -} +// Define all the ReprogControls versions +#define DEFINE_REPROG(T, Base) \ + T::T(Device* dev, uint16_t _id) : Base(dev, _id) { } \ + T::T(Device* dev) : T(dev, ID) { } -#define MAKE_REPROG(x, dev) \ -try { \ - return std::make_shared(dev); \ -} catch(UnsupportedFeature &e) {\ -} - -// Define all of the ReprogControls versions DEFINE_REPROG(ReprogControls, Feature) + DEFINE_REPROG(ReprogControlsV2, ReprogControls) + DEFINE_REPROG(ReprogControlsV2_2, ReprogControlsV2) + DEFINE_REPROG(ReprogControlsV3, ReprogControlsV2_2) + DEFINE_REPROG(ReprogControlsV4, ReprogControlsV3) -std::shared_ptr ReprogControls::autoVersion(Device *dev) -{ - MAKE_REPROG(ReprogControlsV4, dev) - MAKE_REPROG(ReprogControlsV3, dev) - MAKE_REPROG(ReprogControlsV2_2, dev) - MAKE_REPROG(ReprogControlsV2, dev) +template +std::shared_ptr make_reprog(Device* dev) { + try { + return std::make_shared(dev); + } catch (UnsupportedFeature& e) { + return {}; + } +} + +std::shared_ptr ReprogControls::autoVersion(Device* dev) { + if (auto v4 = make_reprog(dev)) { + return v4; + } else if (auto v3 = make_reprog(dev)) { + return v3; + } else if (auto v2_2 = make_reprog(dev)) { + return v2_2; + } else if (auto v2 = make_reprog(dev)) { + return v2; + } - // If base version cannot be made, throw error return std::make_shared(dev); } -uint8_t ReprogControls::getControlCount() -{ +uint8_t ReprogControls::getControlCount() { std::vector 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 params(1); ControlInfo info{}; params[0] = index; @@ -79,13 +84,12 @@ ReprogControls::ControlInfo ReprogControls::getControlInfo(uint8_t index) return info; } -void ReprogControls::initCidMap() -{ +void ReprogControls::initCidMap() { std::unique_lock lock(_cids_populating); - if(_cids_initialized) + if (_cids_initialized) return; uint8_t controls = getControlCount(); - for(uint8_t i = 0; i < controls; i++) { + for (uint8_t i = 0; i < controls; i++) { auto info = getControlInfo(i); _cids.emplace(info.controlID, info); } @@ -93,57 +97,53 @@ void ReprogControls::initCidMap() } const std::map& - ReprogControls::getControls() const -{ +ReprogControls::getControls() const { return _cids; } -ReprogControls::ControlInfo ReprogControls::getControlIdInfo(uint16_t cid) -{ - if(!_cids_initialized) +ReprogControls::ControlInfo ReprogControls::getControlIdInfo(uint16_t cid) { + if (!_cids_initialized) initCidMap(); auto it = _cids.find(cid); - if(it == _cids.end()) - throw Error(Error::InvalidArgument); + if (it == _cids.end()) + throw Error(Error::InvalidArgument, _device->deviceIndex()); else return it->second; } -ReprogControls::ControlInfo ReprogControls::getControlReporting(uint16_t cid) -{ +[[maybe_unused]] ReprogControls::ControlInfo ReprogControls::getControlReporting(uint16_t cid) { // Emulate this function, only Reprog controls v4 supports this auto info = getControlIdInfo(cid); ControlInfo report{}; report.controlID = cid; report.flags = 0; - if(info.flags & TemporaryDivertable) + if (info.flags & TemporaryDivertable) report.flags |= TemporaryDiverted; - if(info.flags & PersisentlyDivertable) + if (info.flags & PersistentlyDivertable) report.flags |= PersistentlyDiverted; - if(info.additionalFlags & RawXY) + if (info.additionalFlags & RawXY) report.flags |= RawXYDiverted; return report; } -void ReprogControls::setControlReporting(uint8_t cid, ControlInfo info) -{ +void ReprogControls::setControlReporting(uint8_t cid, ControlInfo info) { // This function does not exist pre-v4 and cannot be emulated, ignore. - (void)cid; (void)info; // Suppress unused warnings + (void) cid; + (void) info; // Suppress unused warnings } std::set ReprogControls::divertedButtonEvent( - const hidpp::Report& report) -{ + const hidpp::Report& report) { assert(report.function() == DivertedButtonEvent); std::set buttons; - uint8_t cids = std::distance(report.paramBegin(), report.paramEnd())/2; - for(uint8_t i = 0; i < cids; i++) { - uint16_t cid = report.paramBegin()[2*i + 1]; - cid |= report.paramBegin()[2*i] << 8; - if(cid) + uint8_t cids = std::distance(report.paramBegin(), report.paramEnd()) / 2; + for (uint8_t i = 0; i < cids; i++) { + uint16_t cid = report.paramBegin()[2 * i + 1]; + cid |= report.paramBegin()[2 * i] << 8; + if (cid) buttons.insert(cid); else break; @@ -152,19 +152,15 @@ std::set ReprogControls::divertedButtonEvent( } ReprogControls::Move ReprogControls::divertedRawXYEvent(const hidpp::Report - &report) -{ + & report) { assert(report.function() == DivertedRawXYEvent); Move move{}; - move.x = report.paramBegin()[1]; - move.x |= report.paramBegin()[0] << 8; - move.y = report.paramBegin()[3]; - move.y |= report.paramBegin()[2] << 8; + move.x = (int16_t) ((report.paramBegin()[0] << 8) | report.paramBegin()[1]); + move.y = (int16_t) ((report.paramBegin()[2] << 8) | report.paramBegin()[3]); return move; } -ReprogControls::ControlInfo ReprogControlsV4::getControlReporting(uint16_t cid) -{ +ReprogControls::ControlInfo ReprogControlsV4::getControlReporting(uint16_t cid) { std::vector params(2); ControlInfo info{}; params[0] = (cid >> 8) & 0xff; @@ -177,8 +173,7 @@ ReprogControls::ControlInfo ReprogControlsV4::getControlReporting(uint16_t cid) return info; } -void ReprogControlsV4::setControlReporting(uint8_t cid, ControlInfo info) -{ +void ReprogControlsV4::setControlReporting(uint8_t cid, ControlInfo info) { std::vector params(5); params[0] = (cid >> 8) & 0xff; params[1] = cid & 0xff; diff --git a/src/logid/backend/hidpp20/features/ReprogControls.h b/src/logid/backend/hidpp20/features/ReprogControls.h index 9f0d4d5..d464110 100644 --- a/src/logid/backend/hidpp20/features/ReprogControls.h +++ b/src/logid/backend/hidpp20/features/ReprogControls.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,17 +18,15 @@ #ifndef LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H #define LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H +#include +#include +#include #include +#include +#include -#include "../feature_defs.h" -#include "../Feature.h" - -namespace logid { -namespace backend { -namespace hidpp20 -{ - class ReprogControls : public Feature - { +namespace logid::backend::hidpp20 { + class ReprogControls : public Feature { public: enum Function { GetControlCount = 0, @@ -41,8 +39,7 @@ namespace hidpp20 DivertedRawXYEvent = 1 }; - struct ControlInfo - { + struct ControlInfo { uint16_t controlID; uint16_t taskID; uint8_t flags; @@ -52,121 +49,127 @@ namespace hidpp20 uint8_t additionalFlags; }; - enum ControlInfoFlags: uint8_t - { + enum ControlInfoFlags : uint8_t { MouseButton = 1, //Mouse button - FKey = 1<<1, //Fx key - Hotkey = 1<<2, - FnToggle = 1<<3, - ReprogHint = 1<<4, - TemporaryDivertable = 1<<5, - PersisentlyDivertable = 1<<6, - Virtual = 1<<7 + FKey = 1 << 1, //Fx key + Hotkey = 1 << 2, + FnToggle = 1 << 3, + ReprogHint = 1 << 4, + TemporaryDivertable = 1 << 5, + PersistentlyDivertable = 1 << 6, + Virtual = 1 << 7 }; - enum ControlInfoAdditionalFlags: uint8_t { - RawXY = 1<<0 + enum ControlInfoAdditionalFlags : uint8_t { + RawXY = 1 << 0 }; - enum ControlReportingFlags: uint8_t { - TemporaryDiverted = 1<<0, - ChangeTemporaryDivert = 1<<1, - PersistentlyDiverted = 1<<2, - ChangePersistentDivert = 1<<3, - RawXYDiverted = 1<<4, - ChangeRawXYDivert = 1<<5 + enum ControlReportingFlags : uint8_t { + TemporaryDiverted = 1 << 0, + ChangeTemporaryDivert = 1 << 1, + PersistentlyDiverted = 1 << 2, + ChangePersistentDivert [[maybe_unused]] = 1 << 3, + RawXYDiverted = 1 << 4, + ChangeRawXYDivert = 1 << 5 }; - struct Move - { + struct Move { int16_t x; int16_t y; }; static const uint16_t ID = FeatureID::REPROG_CONTROLS; - virtual uint16_t getID() { return ID; } - virtual bool supportsRawXY() { return false; } + [[nodiscard]] uint16_t getID() override { return ID; } + + [[nodiscard]] virtual bool supportsRawXY() { return false; } explicit ReprogControls(Device* dev); - virtual uint8_t getControlCount(); + [[nodiscard]] virtual uint8_t getControlCount(); - virtual ControlInfo getControlInfo(uint8_t cid); + [[nodiscard]] virtual ControlInfo getControlInfo(uint8_t cid); - virtual ControlInfo getControlIdInfo(uint16_t cid); + [[nodiscard]] virtual ControlInfo getControlIdInfo(uint16_t cid); virtual void initCidMap(); - const std::map& getControls() const; + [[nodiscard]] const std::map& getControls() const; - // Onlu controlId and flags will be set - virtual ControlInfo getControlReporting(uint16_t cid); + // Only controlId and flags will be set + [[maybe_unused]] + [[nodiscard]] virtual ControlInfo getControlReporting(uint16_t cid); // Only controlId (for remap) and flags will be read virtual void setControlReporting(uint8_t cid, ControlInfo info); - static std::set divertedButtonEvent(const hidpp::Report& - report); + [[nodiscard]] static std::set divertedButtonEvent(const hidpp::Report& report); - static Move divertedRawXYEvent(const hidpp::Report& report); + [[nodiscard]] static Move divertedRawXYEvent(const hidpp::Report& report); + + [[nodiscard]] static std::shared_ptr autoVersion(Device* dev); - static std::shared_ptr autoVersion(Device *dev); protected: ReprogControls(Device* dev, uint16_t _id); + std::map _cids; bool _cids_initialized = false; std::mutex _cids_populating; }; - class ReprogControlsV2 : public ReprogControls - { + class ReprogControlsV2 : public ReprogControls { public: static const uint16_t ID = FeatureID::REPROG_CONTROLS_V2; - virtual uint16_t getID() override { return ID; } + + [[nodiscard]] uint16_t getID() override { return ID; } explicit ReprogControlsV2(Device* dev); + protected: ReprogControlsV2(Device* dev, uint16_t _id); }; - class ReprogControlsV2_2 : public ReprogControlsV2 - { + class ReprogControlsV2_2 : public ReprogControlsV2 { public: static const uint16_t ID = FeatureID::REPROG_CONTROLS_V2_2; - virtual uint16_t getID() override { return ID; } + + [[nodiscard]] uint16_t getID() override { return ID; } explicit ReprogControlsV2_2(Device* dev); + protected: ReprogControlsV2_2(Device* dev, uint16_t _id); }; - class ReprogControlsV3 : public ReprogControlsV2_2 - { + class ReprogControlsV3 : public ReprogControlsV2_2 { public: static const uint16_t ID = FeatureID::REPROG_CONTROLS_V3; - virtual uint16_t getID() override { return ID; } + + [[nodiscard]] uint16_t getID() override { return ID; } explicit ReprogControlsV3(Device* dev); + protected: ReprogControlsV3(Device* dev, uint16_t _id); }; - class ReprogControlsV4 : public ReprogControlsV3 - { + class ReprogControlsV4 : public ReprogControlsV3 { public: static const uint16_t ID = FeatureID::REPROG_CONTROLS_V4; - virtual uint16_t getID() override { return ID; } - bool supportsRawXY() override { return true; } + [[nodiscard]] uint16_t getID() final { return ID; } - ControlInfo getControlReporting(uint16_t cid) override; + [[nodiscard]] bool supportsRawXY() override { return true; } + + [[nodiscard]] ControlInfo getControlReporting(uint16_t cid) override; void setControlReporting(uint8_t cid, ControlInfo info) override; explicit ReprogControlsV4(Device* dev); + protected: + [[maybe_unused]] ReprogControlsV4(Device* dev, uint16_t _id); }; -}}} +} #endif //LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H diff --git a/src/logid/backend/hidpp20/features/Reset.cpp b/src/logid/backend/hidpp20/features/Reset.cpp index 3512055..7cc0447 100644 --- a/src/logid/backend/hidpp20/features/Reset.cpp +++ b/src/logid/backend/hidpp20/features/Reset.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,16 +15,14 @@ * along with this program. If not, see . * */ -#include "Reset.h" +#include using namespace logid::backend::hidpp20; -Reset::Reset(Device *device) : Feature(device, ID) -{ +Reset::Reset(Device* device) : Feature(device, ID) { } -uint16_t Reset::getProfile() -{ +uint16_t Reset::getProfile() { std::vector params(0); auto results = callFunction(GetProfile, params); @@ -33,8 +31,7 @@ uint16_t Reset::getProfile() return profile; } -void Reset::reset(uint16_t profile) -{ +void Reset::reset(uint16_t profile) { std::vector params(2); params[0] = (profile >> 8) & 0xff; params[1] = profile & 0xff; diff --git a/src/logid/backend/hidpp20/features/Reset.h b/src/logid/backend/hidpp20/features/Reset.h index a3b6e14..4bdb848 100644 --- a/src/logid/backend/hidpp20/features/Reset.h +++ b/src/logid/backend/hidpp20/features/Reset.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,30 +18,27 @@ #ifndef LOGID_BACKEND_HIDPP20_FEATURE_RESET_H #define LOGID_BACKEND_HIDPP20_FEATURE_RESET_H -#include "../Feature.h" -#include "../feature_defs.h" +#include +#include -namespace logid { -namespace backend { -namespace hidpp20 -{ - class Reset : public Feature - { +namespace logid::backend::hidpp20 { + class Reset : public Feature { public: static const uint16_t ID = FeatureID::RESET; - virtual uint16_t getID() { return ID; } - enum Function : uint8_t - { - GetProfile = 0, - ResetToProfile = 1 + [[nodiscard]] uint16_t getID() final { return ID; } + + enum Function : uint8_t { + GetProfile = 0, + ResetToProfile = 1 }; explicit Reset(Device* device); uint16_t getProfile(); + void reset(uint16_t profile = 0); }; -}}} +} #endif //LOGID_BACKEND_HIDPP20_FEATURE_RESET_H diff --git a/src/logid/backend/hidpp20/features/Root.cpp b/src/logid/backend/hidpp20/features/Root.cpp index f1aa724..0cc3e36 100644 --- a/src/logid/backend/hidpp20/features/Root.cpp +++ b/src/logid/backend/hidpp20/features/Root.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,81 +16,66 @@ * */ -#include "Root.h" -#include "../Error.h" +#include +#include +#include using namespace logid::backend::hidpp20; -Root::Root(Device* dev) : Feature(dev, ID) -{ +namespace { + std::vector _genGetFeatureParams(uint16_t feature_id) { + std::vector params(2); + params[0] = feature_id & 0xff; + params[1] = (feature_id >> 8) & 0xff; + return params; + } + + feature_info _genGetFeatureInfo(uint16_t feature_id, + std::vector response) { + feature_info info{}; + info.feature_id = response[0]; + + if (!info.feature_id) + throw UnsupportedFeature(feature_id); + + info.hidden = response[1] & Root::FeatureFlag::Hidden; + info.obsolete = response[1] & Root::FeatureFlag::Obsolete; + info.internal = response[1] & Root::FeatureFlag::Internal; + + return info; + } } -std::vector _genGetFeatureParams(uint16_t feature_id) -{ - std::vector params(2); - params[0] = feature_id & 0xff; - params[1] = (feature_id >> 8) & 0xff; - return params; +Root::Root(hidpp::Device* dev) : EssentialFeature(dev, ID) { } -feature_info _genGetFeatureInfo(uint16_t feature_id, - std::vector response) -{ - feature_info info{}; - info.feature_id = response[0]; - - if(!info.feature_id) - throw UnsupportedFeature(feature_id); - - info.hidden = response[1] & Root::FeatureFlag::Hidden; - info.obsolete = response[1] & Root::FeatureFlag::Obsolete; - info.internal = response[1] & Root::FeatureFlag::Internal; - - return info; -} - -feature_info Root::getFeature(uint16_t feature_id) -{ +feature_info Root::getFeature(uint16_t feature_id) { auto params = _genGetFeatureParams(feature_id); try { auto response = this->callFunction(Root::Function::GetFeature, params); return _genGetFeatureInfo(feature_id, response); - } catch(Error& e) { - if(e.code() == Error::InvalidFeatureIndex) + } catch (Error& e) { + if (e.code() == Error::InvalidFeatureIndex) throw UnsupportedFeature(feature_id); throw e; } } -std::tuple Root::getVersion() -{ - std::vector params(0); - auto response = this->callFunction(Function::Ping, params); +uint8_t Root::ping(uint8_t byte) { + std::vector params(3); + params[2] = byte; - return std::make_tuple(response[0], response[1]); + auto response = this->callFunction(Root::Function::Ping, params); + + return response[2]; } -EssentialRoot::EssentialRoot(hidpp::Device* dev) : EssentialFeature(dev, ID) -{ -} - -feature_info EssentialRoot::getFeature(uint16_t feature_id) -{ - auto params = _genGetFeatureParams(feature_id); - try { - auto response = this->callFunction(Root::Function::GetFeature, params); - return _genGetFeatureInfo(feature_id, response); - } catch(Error& e) { - if(e.code() == Error::InvalidFeatureIndex) - throw UnsupportedFeature(feature_id); - throw e; - } -} - -std::tuple EssentialRoot::getVersion() -{ +std::tuple Root::getVersion() { std::vector params(0); auto response = this->callFunction(Root::Function::Ping, params); + if (response[0] == 0x11) + return std::make_tuple(1, 0); + return std::make_tuple(response[0], response[1]); } \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/Root.h b/src/logid/backend/hidpp20/features/Root.h index 6d699aa..89017c4 100644 --- a/src/logid/backend/hidpp20/features/Root.h +++ b/src/logid/backend/hidpp20/features/Root.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,50 +19,36 @@ #ifndef LOGID_BACKEND_HIDPP20_FEATURE_ROOT_H #define LOGID_BACKEND_HIDPP20_FEATURE_ROOT_H -#include "../Feature.h" -#include "../EssentialFeature.h" -#include "../feature_defs.h" +#include +#include -namespace logid { -namespace backend { -namespace hidpp20 -{ - class Root : public Feature - { +namespace logid::backend::hidpp20 { + + class Root : public EssentialFeature { public: static const uint16_t ID = FeatureID::ROOT; - virtual uint16_t getID() { return ID; } - enum Function : uint8_t - { + uint16_t getID() final { return ID; } + + explicit Root(hidpp::Device* device); + + enum Function : uint8_t { GetFeature = 0, Ping = 1 }; - explicit Root(Device* device); + feature_info getFeature(uint16_t feature_id); + + uint8_t ping(uint8_t byte); - feature_info getFeature (uint16_t feature_id); std::tuple getVersion(); - enum FeatureFlag : uint8_t - { - Obsolete = 1<<7, - Hidden = 1<<6, - Internal = 1<<5 + enum FeatureFlag : uint8_t { + Obsolete = 1 << 7, + Hidden = 1 << 6, + Internal = 1 << 5 }; }; - - class EssentialRoot : public EssentialFeature - { - public: - static const uint16_t ID = FeatureID::ROOT; - virtual uint16_t getID() { return ID; } - - explicit EssentialRoot(hidpp::Device* device); - - feature_info getFeature(uint16_t feature_id); - std::tuple getVersion(); - }; -}}} +} #endif //LOGID_BACKEND_HIDPP20_FEATURE_ROOT_H \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/SmartShift.cpp b/src/logid/backend/hidpp20/features/SmartShift.cpp index 2170f10..5237582 100644 --- a/src/logid/backend/hidpp20/features/SmartShift.cpp +++ b/src/logid/backend/hidpp20/features/SmartShift.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,33 +15,111 @@ * along with this program. If not, see . * */ -#include "SmartShift.h" +#include using namespace logid::backend::hidpp20; -SmartShift::SmartShift(Device* dev) : Feature(dev, ID) -{ +SmartShift::SmartShift(Device* dev) : SmartShift(dev, ID) { } -SmartShift::SmartshiftStatus SmartShift::getStatus() -{ +SmartShift::SmartShift(Device* dev, uint16_t feature_id) : + Feature(dev, feature_id) { +} + +SmartShiftV2::SmartShiftV2(Device* dev) : SmartShift(dev, ID) { +} + +template +std::shared_ptr make_smartshift(Device* dev) { + try { + return std::make_shared(dev); + } catch (UnsupportedFeature& e) { + return {}; + } +} + +std::shared_ptr SmartShift::autoVersion(Device* dev) { + if (auto v2 = make_smartshift(dev)) + return v2; + + return std::make_shared(dev); +} + +SmartShift::Status SmartShift::getStatus() { std::vector params(0); - SmartshiftStatus status{}; + auto response = callFunction(GetStatus, params); - status.active = response[0]-1; - status.autoDisengage = response[1]; - status.defaultAutoDisengage = response[2]; - return status; + + return { + .active = static_cast(response[0] - 1), + .autoDisengage = response[1], + .torque = 0, + .setActive = false, + .setAutoDisengage = false, + .setTorque = false + }; } -void SmartShift::setStatus(SmartshiftStatus status) -{ +SmartShift::Defaults SmartShift::getDefaults() { + std::vector params(0); + + auto response = callFunction(GetStatus, params); + + return { + .autoDisengage = response[2], + .torque = 0, + .maxForce = 0, + }; +} + +void SmartShift::setStatus(Status status) { std::vector params(3); - if(status.setActive) + if (status.setActive) params[0] = status.active + 1; - if(status.setAutoDisengage) + if (status.setAutoDisengage) params[1] = status.autoDisengage; - if(status.setDefaultAutoDisengage) - params[2] = status.defaultAutoDisengage; callFunction(SetStatus, params); -} \ No newline at end of file +} + +SmartShift::Defaults SmartShiftV2::getDefaults() { + std::vector params(0); + auto response = callFunction(GetCapabilities, params); + + return { + .autoDisengage = response[1], + .torque = response[2], + .maxForce = response[3], + }; +} + +SmartShift::Status SmartShiftV2::getStatus() { + std::vector params(0); + auto response = callFunction(GetStatus, params); + + return { + .active = static_cast(response[0] - 1), + .autoDisengage = response[1], + .torque = response[2], + .setActive = false, .setAutoDisengage = false, .setTorque = false, + }; +} + +void SmartShiftV2::setStatus(Status status) { + std::vector params(3); + if (status.setActive) + params[0] = status.active + 1; + if (status.setAutoDisengage) + params[1] = status.autoDisengage; + if (status.setTorque) + params[2] = status.torque; + + callFunction(SetStatus, params); +} + +bool SmartShiftV2::supportsTorque() { + std::vector params(0); + auto response = callFunction(GetCapabilities, params); + + return static_cast(response[0] & 1); +} + diff --git a/src/logid/backend/hidpp20/features/SmartShift.h b/src/logid/backend/hidpp20/features/SmartShift.h index 92f4fb1..70e3357 100644 --- a/src/logid/backend/hidpp20/features/SmartShift.h +++ b/src/logid/backend/hidpp20/features/SmartShift.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,18 +18,16 @@ #ifndef LOGID_BACKEND_HIDPP20_FEATURE_SMARTSHIFT_H #define LOGID_BACKEND_HIDPP20_FEATURE_SMARTSHIFT_H -#include "../feature_defs.h" -#include "../Feature.h" +#include +#include +#include -namespace logid { -namespace backend { -namespace hidpp20 -{ - class SmartShift : public Feature - { +namespace logid::backend::hidpp20 { + class SmartShift : public Feature { public: static const uint16_t ID = FeatureID::SMART_SHIFT; - virtual uint16_t getID() { return ID; } + + uint16_t getID() override { return ID; } enum Function { GetStatus = 0, @@ -38,17 +36,55 @@ namespace hidpp20 explicit SmartShift(Device* dev); - struct SmartshiftStatus - { - bool active; + struct Defaults { uint8_t autoDisengage; - uint8_t defaultAutoDisengage; - bool setActive, setAutoDisengage, setDefaultAutoDisengage; + uint8_t torque; + uint8_t maxForce; }; - SmartshiftStatus getStatus(); - void setStatus(SmartshiftStatus status); + struct Status { + bool active; + uint8_t autoDisengage; + uint8_t torque; + bool setActive, setAutoDisengage, setTorque; + }; + + [[nodiscard]] virtual bool supportsTorque() { return false; } + + [[nodiscard]] virtual Defaults getDefaults(); + + [[nodiscard]] virtual Status getStatus(); + + virtual void setStatus(Status status); + + [[nodiscard]] static std::shared_ptr autoVersion(Device* dev); + + protected: + SmartShift(Device* dev, uint16_t feature_id); }; -}}} + + class SmartShiftV2 : public SmartShift + { + public: + static const uint16_t ID = FeatureID::SMART_SHIFT_V2; + uint16_t getID() final { return ID; } + + enum Function { + GetCapabilities = 0, + GetStatus = 1, + SetStatus = 2 + }; + + explicit SmartShiftV2(Device* dev); + + [[nodiscard]] bool supportsTorque() final; + + [[nodiscard]] Defaults getDefaults() final; + + [[nodiscard]] Status getStatus() final; + + void setStatus(Status status) final; + }; +} #endif //LOGID_BACKEND_HIDPP20_FEATURE_SMARTSHIFT_H diff --git a/src/logid/backend/hidpp20/features/ThumbWheel.cpp b/src/logid/backend/hidpp20/features/ThumbWheel.cpp index a2d6c60..fe0c3ba 100644 --- a/src/logid/backend/hidpp20/features/ThumbWheel.cpp +++ b/src/logid/backend/hidpp20/features/ThumbWheel.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,17 +16,15 @@ * */ +#include #include -#include "ThumbWheel.h" using namespace logid::backend::hidpp20; -ThumbWheel::ThumbWheel(Device *dev) : Feature(dev, ID) -{ +ThumbWheel::ThumbWheel(Device* dev) : Feature(dev, ID) { } -ThumbWheel::ThumbwheelInfo ThumbWheel::getInfo() -{ +ThumbWheel::ThumbwheelInfo ThumbWheel::getInfo() { std::vector params(0), response; ThumbwheelInfo info{}; response = callFunction(GetInfo, params); @@ -43,20 +41,20 @@ ThumbWheel::ThumbwheelInfo ThumbWheel::getInfo() return info; } -ThumbWheel::ThumbwheelStatus ThumbWheel::getStatus() -{ +ThumbWheel::ThumbwheelStatus ThumbWheel::getStatus() { std::vector params(0), response; ThumbwheelStatus status{}; response = callFunction(GetStatus, params); status.diverted = response[0]; status.inverted = response[1] & 1; + status.touch = response[1] & (1 << 1); + status.proxy = response[1] & (1 << 2); return status; } -ThumbWheel::ThumbwheelStatus ThumbWheel::setStatus(bool divert, bool invert) -{ +ThumbWheel::ThumbwheelStatus ThumbWheel::setStatus(bool divert, bool invert) { std::vector params(2), response; ThumbwheelStatus status{}; params[0] = divert; @@ -69,12 +67,10 @@ ThumbWheel::ThumbwheelStatus ThumbWheel::setStatus(bool divert, bool invert) return status; } -ThumbWheel::ThumbwheelEvent ThumbWheel::thumbwheelEvent(hidpp::Report& report) -{ +ThumbWheel::ThumbwheelEvent ThumbWheel::thumbwheelEvent(const hidpp::Report& report) { assert(report.function() == Event); ThumbwheelEvent event{}; - event.rotation = report.paramBegin()[1]; - event.rotation |= report.paramBegin()[0] << 8; + event.rotation = (int16_t) ((report.paramBegin()[0] << 8) | report.paramBegin()[1]); event.timestamp = report.paramBegin()[3]; event.timestamp |= report.paramBegin()[2] << 8; event.rotationStatus = static_cast(report.paramBegin()[4]); diff --git a/src/logid/backend/hidpp20/features/ThumbWheel.h b/src/logid/backend/hidpp20/features/ThumbWheel.h index 7079f76..12b95a1 100644 --- a/src/logid/backend/hidpp20/features/ThumbWheel.h +++ b/src/logid/backend/hidpp20/features/ThumbWheel.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,18 +18,16 @@ #ifndef LOGID_BACKEND_HIDPP20_FEATURE_THUMBWHEEL_H #define LOGID_BACKEND_HIDPP20_FEATURE_THUMBWHEEL_H -#include "../feature_defs.h" -#include "../Feature.h" +#include +#include +#include -namespace logid { -namespace backend { -namespace hidpp20 -{ - class ThumbWheel : public Feature - { +namespace logid::backend::hidpp20 { + class ThumbWheel : public Feature { public: static const uint16_t ID = FeatureID::THUMB_WHEEL; - virtual uint16_t getID() { return ID; } + + uint16_t getID() final { return ID; } enum Function { GetInfo = 0, @@ -43,12 +41,11 @@ namespace hidpp20 explicit ThumbWheel(Device* dev); - enum Capabilities : uint8_t - { + enum Capabilities : uint8_t { Timestamp = 1, - Touch = 1<<1, - Proxy = 1<<2, - SingleTap = 1<<3 + Touch = 1 << 1, + Proxy = 1 << 2, + SingleTap = 1 << 3 }; struct ThumbwheelInfo { @@ -66,8 +63,7 @@ namespace hidpp20 bool proxy; }; - enum RotationStatus : uint8_t - { + enum RotationStatus : uint8_t { Inactive = 0, Start = 1, Active = 2, @@ -81,13 +77,14 @@ namespace hidpp20 uint8_t flags; }; - ThumbwheelInfo getInfo(); - ThumbwheelStatus getStatus(); + [[nodiscard]] ThumbwheelInfo getInfo(); + + [[nodiscard]] ThumbwheelStatus getStatus(); ThumbwheelStatus setStatus(bool divert, bool invert); - ThumbwheelEvent thumbwheelEvent(hidpp::Report& report); + [[nodiscard]] static ThumbwheelEvent thumbwheelEvent(const hidpp::Report& report); }; -}}} +} #endif //LOGID_BACKEND_HIDPP20_FEATURE_THUMBWHEEL_H diff --git a/src/logid/backend/hidpp20/features/WirelessDeviceStatus.cpp b/src/logid/backend/hidpp20/features/WirelessDeviceStatus.cpp index d8c526b..42c36a9 100644 --- a/src/logid/backend/hidpp20/features/WirelessDeviceStatus.cpp +++ b/src/logid/backend/hidpp20/features/WirelessDeviceStatus.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,18 +15,16 @@ * along with this program. If not, see . * */ +#include #include -#include "WirelessDeviceStatus.h" using namespace logid::backend::hidpp20; -WirelessDeviceStatus::WirelessDeviceStatus(Device* dev) : Feature(dev, ID) -{ +WirelessDeviceStatus::WirelessDeviceStatus(Device* dev) : Feature(dev, ID) { } WirelessDeviceStatus::Status WirelessDeviceStatus::statusBroadcastEvent( - const hidpp::Report &report) -{ + const hidpp::Report& report) { assert(report.function() == StatusBroadcast); Status status = {}; auto params = report.paramBegin(); diff --git a/src/logid/backend/hidpp20/features/WirelessDeviceStatus.h b/src/logid/backend/hidpp20/features/WirelessDeviceStatus.h index 77d6f15..74ec8c9 100644 --- a/src/logid/backend/hidpp20/features/WirelessDeviceStatus.h +++ b/src/logid/backend/hidpp20/features/WirelessDeviceStatus.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,35 +18,31 @@ #ifndef LOGID_BACKEND_HIDPP20_FEATURE_WIRELESSDEVICESTATUS_H #define LOGID_BACKEND_HIDPP20_FEATURE_WIRELESSDEVICESTATUS_H -#include "../Feature.h" -#include "../feature_defs.h" +#include +#include +#include -namespace logid { -namespace backend { -namespace hidpp20 -{ - class WirelessDeviceStatus : public Feature - { +namespace logid::backend::hidpp20 { + class WirelessDeviceStatus : public Feature { public: static constexpr uint16_t ID = FeatureID::WIRELESS_DEVICE_STATUS; - virtual uint16_t getID() { return ID; } - WirelessDeviceStatus(Device* dev); + [[nodiscard]] uint16_t getID() final { return ID; } - enum Event : uint8_t - { + explicit WirelessDeviceStatus(Device* dev); + + enum Event : uint8_t { StatusBroadcast = 0 }; - struct Status - { + struct Status { bool reconnection; bool reconfNeeded; bool powerSwitch; }; - static Status statusBroadcastEvent(const hidpp::Report &report); + static Status statusBroadcastEvent(const hidpp::Report& report); }; -}}} +} #endif //LOGID_BACKEND_HIDPP20_FEATURE_WIRELESSDEVICESTATUS_H diff --git a/src/logid/backend/raw/DeviceMonitor.cpp b/src/logid/backend/raw/DeviceMonitor.cpp index f98ed48..0e5cb99 100644 --- a/src/logid/backend/raw/DeviceMonitor.cpp +++ b/src/logid/backend/raw/DeviceMonitor.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,170 +16,177 @@ * */ -#include "DeviceMonitor.h" -#include "../../util/task.h" -#include "../../util/log.h" -#include "RawDevice.h" -#include "../hidpp/Device.h" - -#include +#include +#include +#include +#include +#include +#include +#include #include extern "C" { -#include #include } +using namespace logid; using namespace logid::backend::raw; -DeviceMonitor::DeviceMonitor() -{ - if(-1 == pipe(_pipe)) - throw std::system_error(errno, std::system_category(), - "pipe creation failed"); - - _udev_context = udev_new(); - if(!_udev_context) - throw std::runtime_error("udev_new failed"); -} - -DeviceMonitor::~DeviceMonitor() -{ - this->stop(); - - udev_unref(_udev_context); - - for(int i : _pipe) - close(i); -} - -void DeviceMonitor::run() -{ +DeviceMonitor::DeviceMonitor() : _io_monitor(std::make_shared()), + _ready(false) { int ret; - std::lock_guard lock(_running); + _udev_context = udev_new(); + if (!_udev_context) + throw std::runtime_error("udev_new failed"); - struct udev_monitor* monitor = udev_monitor_new_from_netlink(_udev_context, - "udev"); - if(!monitor) + _udev_monitor = udev_monitor_new_from_netlink(_udev_context, + "udev"); + if (!_udev_monitor) { + if (_udev_context) + udev_unref(_udev_context); throw std::runtime_error("udev_monitor_new_from_netlink failed"); - - ret = udev_monitor_filter_add_match_subsystem_devtype(monitor, "hidraw", - nullptr); - if (0 != ret) - throw std::system_error (-ret, std::system_category(), - "udev_monitor_filter_add_match_subsystem_devtype"); - - ret = udev_monitor_enable_receiving(monitor); - if(0 != ret) - throw std::system_error(-ret, std::system_category(), - "udev_moniotr_enable_receiving"); - - this->enumerate(); - - int fd = udev_monitor_get_fd(monitor); - - _run_monitor = true; - while (_run_monitor) { - fd_set fds; - FD_ZERO(&fds); - FD_SET(_pipe[0], &fds); - FD_SET(fd, &fds); - - if (-1 == select (std::max (_pipe[0], fd)+1, &fds, nullptr, - nullptr, nullptr)) { - if (errno == EINTR) - continue; - throw std::system_error (errno, std::system_category(), - "udev_monitor select"); - } - - if (FD_ISSET(fd, &fds)) { - struct udev_device *device = udev_monitor_receive_device(monitor); - std::string action = udev_device_get_action(device); - std::string devnode = udev_device_get_devnode(device); - - if (action == "add") - task::spawn([this, name=devnode]() { - // Wait for device to initialise - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - auto supported_reports = backend::hidpp::getSupportedReports( - RawDevice::getReportDescriptor(name)); - if(supported_reports) - this->addDevice(name); - else - logPrintf(DEBUG, "Unsupported device %s ignored", - name.c_str()); - }, [name=devnode](std::exception& e){ - logPrintf(WARN, "Error adding device %s: %s", - name.c_str(), e.what()); - }); - else if (action == "remove") - task::spawn([this, name=devnode]() { - this->removeDevice(name); - }, [name=devnode](std::exception& e){ - logPrintf(WARN, "Error removing device %s: %s", - name.c_str(), e.what()); - }); - - udev_device_unref (device); - } - if (FD_ISSET(_pipe[0], &fds)) { - char c; - if (-1 == read(_pipe[0], &c, sizeof (char))) - throw std::system_error (errno, std::system_category(), - "read pipe"); - break; - } } + + ret = udev_monitor_filter_add_match_subsystem_devtype( + _udev_monitor, "hidraw", nullptr); + if (0 != ret) { + if (_udev_monitor) + udev_monitor_unref(_udev_monitor); + if (_udev_context) + udev_unref(_udev_context); + throw std::system_error( + -ret, std::system_category(), + "udev_monitor_filter_add_match_subsystem_devtype"); + } + + ret = udev_monitor_enable_receiving(_udev_monitor); + if (0 != ret) { + if (_udev_monitor) + udev_monitor_unref(_udev_monitor); + if (_udev_context) + udev_unref(_udev_context); + throw std::system_error(-ret, std::system_category(), + "udev_monitor_enable_receiving"); + } + + _fd = udev_monitor_get_fd(_udev_monitor); } -void DeviceMonitor::stop() -{ - _run_monitor = false; - std::lock_guard lock(_running); +DeviceMonitor::~DeviceMonitor() { + if (_ready) + _io_monitor->remove(_fd); + + if (_udev_monitor) + udev_monitor_unref(_udev_monitor); + if (_udev_context) + udev_unref(_udev_context); } -void DeviceMonitor::enumerate() -{ +void DeviceMonitor::ready() { + if (_ready) + return; + _ready = true; + + _io_monitor->add(_fd, { + [this]() { + struct udev_device* device = udev_monitor_receive_device(_udev_monitor); + std::string action = udev_device_get_action(device); + std::string dev_node = udev_device_get_devnode(device); + + if (action == "add") + run_task([self_weak = _self, dev_node]() { + if (auto self = self_weak.lock()) + self->_addHandler(dev_node); + }); + else if (action == "remove") + run_task([self_weak = _self, dev_node]() { + if (auto self = self_weak.lock()) + self->_removeHandler(dev_node); + }); + + udev_device_unref(device); + }, + []() { + throw std::runtime_error("udev hangup"); + }, + []() { + throw std::runtime_error("udev error"); + } + }); +} + +void DeviceMonitor::enumerate() { int ret; struct udev_enumerate* udev_enum = udev_enumerate_new(_udev_context); ret = udev_enumerate_add_match_subsystem(udev_enum, "hidraw"); - if(0 != ret) + if (0 != ret) throw std::system_error(-ret, std::system_category(), - "udev_enumerate_add_match_subsystem"); + "udev_enumerate_add_match_subsystem"); ret = udev_enumerate_scan_devices(udev_enum); - if(0 != ret) + if (0 != ret) throw std::system_error(-ret, std::system_category(), "udev_enumerate_scan_devices"); struct udev_list_entry* udev_enum_entry; udev_list_entry_foreach(udev_enum_entry, - udev_enumerate_get_list_entry(udev_enum)) { + udev_enumerate_get_list_entry(udev_enum)) { const char* name = udev_list_entry_get_name(udev_enum_entry); - struct udev_device* device = udev_device_new_from_syspath(_udev_context, - name); - if(!device) - throw std::runtime_error("udev_device_new_from_syspath failed"); + struct udev_device* device = udev_device_new_from_syspath(_udev_context, name); + if (device) { + const char* dev_node_cstr = udev_device_get_devnode(device); + if (dev_node_cstr) { + const std::string dev_node {dev_node_cstr}; + udev_device_unref(device); - std::string devnode = udev_device_get_devnode(device); - udev_device_unref(device); - - task::spawn([this, name=devnode]() { - auto supported_reports = backend::hidpp::getSupportedReports( - RawDevice::getReportDescriptor(name)); - if(supported_reports) - this->addDevice(name); - else - logPrintf(DEBUG, "Unsupported device %s ignored", - name.c_str()); - }, [name=devnode](std::exception& e){ - logPrintf(WARN, "Error adding device %s: %s", - name.c_str(), e.what()); - }); + _addHandler(dev_node); + } else { + udev_device_unref(device); + } + } } udev_enumerate_unref(udev_enum); -} \ No newline at end of file +} + +void DeviceMonitor::_addHandler(const std::string& device, int tries) { + try { + auto supported_reports = backend::hidpp::getSupportedReports( + RawDevice::getReportDescriptor(device)); + if (supported_reports) + addDevice(device); + else + logPrintf(DEBUG, "Unsupported device %s ignored", device.c_str()); + } catch (backend::DeviceNotReady& e) { + if (tries == max_tries) { + logPrintf(WARN, "Failed to add device %s after %d tries. Treating as failure.", + device.c_str(), max_tries); + } else { + /* Do exponential backoff for 2^tries * backoff ms. */ + std::chrono::milliseconds wait((1 << tries) * ready_backoff); + logPrintf(DEBUG, "Failed to add device %s on try %d, backing off for %dms", + device.c_str(), tries + 1, wait.count()); + run_task_after([self_weak = _self, device, tries]() { + if (auto self = self_weak.lock()) + self->_addHandler(device, tries + 1); + }, wait); + } + } catch (std::exception& e) { + logPrintf(WARN, "Error adding device %s: %s", device.c_str(), e.what()); + } +} + +void DeviceMonitor::_removeHandler(const std::string& device) { + try { + removeDevice(device); + } catch (std::exception& e) { + logPrintf(WARN, "Error removing device %s: %s", + device.c_str(), e.what()); + } +} + +std::shared_ptr DeviceMonitor::ioMonitor() const { + return _io_monitor; +} diff --git a/src/logid/backend/raw/DeviceMonitor.h b/src/logid/backend/raw/DeviceMonitor.h index d795019..851a004 100644 --- a/src/logid/backend/raw/DeviceMonitor.h +++ b/src/logid/backend/raw/DeviceMonitor.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,33 +22,80 @@ #include #include #include +#include extern "C" { -#include +struct udev; +struct udev_monitor; } -namespace logid { -namespace backend { -namespace raw -{ - class DeviceMonitor - { +namespace logid::backend::raw { + class IOMonitor; + + static constexpr int max_tries = 5; + static constexpr int ready_backoff = 500; + + template + class _deviceMonitorWrapper : public T { + friend class Device; + public: + template + explicit _deviceMonitorWrapper(Args... args) : T(std::forward(args)...) {} + + template + static std::shared_ptr make(Args... args) { + return std::make_shared<_deviceMonitorWrapper>(std::forward(args)...); + } + }; + + class DeviceMonitor { + public: + virtual ~DeviceMonitor(); + void enumerate(); - void run(); - void stop(); + + [[nodiscard]] std::shared_ptr ioMonitor() const; + + template + static std::shared_ptr make(Args... args) { + auto device_monitor = _deviceMonitorWrapper::make(std::forward(args)...); + device_monitor->_self = device_monitor; + device_monitor->ready(); + + return device_monitor; + } + protected: DeviceMonitor(); - virtual ~DeviceMonitor(); + + // This should be run once the derived class is ready + void ready(); + virtual void addDevice(std::string device) = 0; + virtual void removeDevice(std::string device) = 0; + + template + [[nodiscard]] std::weak_ptr self() const { + return std::dynamic_pointer_cast(_self.lock()); + } + private: + void _addHandler(const std::string& device, int tries = 0); + + void _removeHandler(const std::string& device); + + std::shared_ptr _io_monitor; + struct udev* _udev_context; - int _pipe[2]; - std::atomic _run_monitor; - std::mutex _running; + struct udev_monitor* _udev_monitor; + int _fd; + bool _ready; + + std::weak_ptr _self; }; -}}} +} #endif //LOGID_BACKEND_RAW_DEVICEMONITOR_H \ No newline at end of file diff --git a/src/logid/backend/raw/defs.h b/src/logid/backend/raw/EventHandler.h similarity index 63% rename from src/logid/backend/raw/defs.h rename to src/logid/backend/raw/EventHandler.h index 388d9c6..2d136ec 100644 --- a/src/logid/backend/raw/defs.h +++ b/src/logid/backend/raw/EventHandler.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,15 +23,16 @@ #include #include -namespace logid { -namespace backend { -namespace raw -{ - struct RawEventHandler - { - std::function& )> condition; - std::function& )> callback; +namespace logid::backend::raw { + struct RawEventHandler { + std::function&)> condition; + std::function&)> callback; + + RawEventHandler(std::function&)> cond, + std::function&)> call) : + condition(std::move(cond)), callback(std::move(call)) { + } }; -}}} +} #endif //LOGID_BACKEND_RAW_DEFS_H \ No newline at end of file diff --git a/src/logid/backend/raw/IOMonitor.cpp b/src/logid/backend/raw/IOMonitor.cpp new file mode 100644 index 0000000..5c5d8fd --- /dev/null +++ b/src/logid/backend/raw/IOMonitor.cpp @@ -0,0 +1,188 @@ +/* + * Copyright 2022 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include +#include + +extern "C" +{ +#include +#include +#include +} + +using namespace logid::backend::raw; + +IOHandler::IOHandler(std::function r, + std::function hup, + std::function err) : + read(std::move(r)), + hangup(std::move(hup)), + error(std::move(err)) { +} + +IOMonitor::IOMonitor() : _epoll_fd(epoll_create1(0)), + _event_fd(eventfd(0, EFD_NONBLOCK)) { + if (_epoll_fd < 0) { + if (_event_fd >= 0) + close(_event_fd); + throw std::runtime_error("failed to create epoll fd"); + } + + if (_event_fd < 0) { + close(_epoll_fd); + throw std::runtime_error("failed to create event fd"); + } + + struct epoll_event event{}; + event.events = EPOLLIN; + event.data.fd = _event_fd; + + if (::epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, _event_fd, &event)) { + throw std::system_error(errno, std::generic_category()); + } + + _fds.emplace(std::piecewise_construct, std::forward_as_tuple(_event_fd), + std::forward_as_tuple([]() {}, []() { + throw std::runtime_error("event_fd hangup"); + }, []() { + throw std::runtime_error("event_fd error"); + })); + + _io_thread = std::make_unique([this]() { + _listen(); + }); +} + +IOMonitor::~IOMonitor() noexcept { + std::lock_guard ctl_lock(_ctl_lock); + _stop(); + + if (_event_fd >= 0) + close(_event_fd); + + if (_epoll_fd >= 0) + close(_epoll_fd); +} + +void IOMonitor::_listen() { + std::lock_guard run_lock(_run_lock); + std::vector events; + + _is_running = true; + + while (_is_running) { + if (_interrupting) { + std::unique_lock lock(_interrupt_mutex); + _interrupt_cv.wait(lock, [this]() { + return !(bool) _interrupting; + }); + + if (!_is_running) + break; + } + + std::lock_guard io_lock(_io_lock); + if (events.size() != _fds.size()) + events.resize(_fds.size()); + int ev_count = ::epoll_wait(_epoll_fd, events.data(), (int) events.size(), -1); + for (int i = 0; i < ev_count; ++i) { + const auto& handler = _fds.at(events[i].data.fd); + if (events[i].events & EPOLLIN) + handler.read(); + if (events[i].events & EPOLLHUP) + handler.hangup(); + if (events[i].events & EPOLLERR) + handler.error(); + } + } +} + +void IOMonitor::_stop() noexcept { + _interrupt(); + _is_running = false; + _continue(); + _io_thread->join(); +} + +[[maybe_unused]] +bool IOMonitor::_running() const { + std::unique_lock run_lock(_run_lock, std::try_to_lock); + return !run_lock.owns_lock() || _is_running; +} + +void IOMonitor::add(int fd, IOHandler handler) { + std::lock_guard lock(_ctl_lock); + _interrupt(); + + struct epoll_event event{}; + event.events = EPOLLIN | EPOLLHUP | EPOLLERR; + event.data.fd = fd; + + // TODO: EPOLL_CTL_MOD + if (_fds.contains(fd)) { + _continue(); + throw std::runtime_error("duplicate io fd"); + } + + if (::epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, fd, &event)) { + _continue(); + throw std::system_error(errno, std::generic_category()); + } + _fds.emplace(fd, std::move(handler)); + + _continue(); +} + +void IOMonitor::remove(int fd) noexcept { + std::lock_guard lock(_ctl_lock); + _interrupt(); + std::lock_guard io_lock(_io_lock); + + ::epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, fd, nullptr); + _fds.erase(fd); + + _continue(); +} + +void IOMonitor::_interrupt() noexcept { + std::unique_lock run_lock(_run_lock, std::try_to_lock); + + _interrupting = true; + + uint64_t counter = 1; + [[maybe_unused]] ssize_t ret = ::write(_event_fd, &counter, sizeof(counter)); + assert(ret == sizeof(counter)); + + // Wait for the IO monitor to _stop + std::lock_guard io_lock(_io_lock); + +} + +void IOMonitor::_continue() noexcept { + std::unique_lock run_lock(_run_lock, std::try_to_lock); + + uint64_t counter; + [[maybe_unused]] ssize_t ret = ::read(_event_fd, &counter, sizeof(counter)); + + assert(ret != -1); + + if (counter == 1) { + _interrupting = false; + _interrupt_cv.notify_all(); + } +} diff --git a/src/logid/backend/raw/IOMonitor.h b/src/logid/backend/raw/IOMonitor.h new file mode 100644 index 0000000..7fd8670 --- /dev/null +++ b/src/logid/backend/raw/IOMonitor.h @@ -0,0 +1,76 @@ +/* + * Copyright 2019-2023 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_BACKEND_RAW_IOMONITOR_H +#define LOGID_BACKEND_RAW_IOMONITOR_H + +#include +#include +#include +#include +#include +#include + +namespace logid::backend::raw { + struct IOHandler { + std::function read; + std::function hangup; + std::function error; + + IOHandler(std::function r, + std::function hup, + std::function err); + }; + + class IOMonitor { + public: + IOMonitor(); + + ~IOMonitor() noexcept; + + void add(int fd, IOHandler handler); + + void remove(int fd) noexcept; + + private: + void _listen(); // This is a blocking call + void _stop() noexcept; + + [[maybe_unused]] + [[nodiscard]] bool _running() const; + + void _interrupt() noexcept; + + void _continue() noexcept; + + std::unique_ptr _io_thread; + + std::map _fds; + std::mutex _io_lock, _ctl_lock; + mutable std::mutex _run_lock; + std::atomic_bool _is_running; + + std::atomic_bool _interrupting; + std::mutex _interrupt_mutex; + std::condition_variable _interrupt_cv; + + const int _epoll_fd; + const int _event_fd; + }; +} + +#endif //LOGID_BACKEND_RAW_IOMONITOR_H diff --git a/src/logid/backend/raw/RawDevice.cpp b/src/logid/backend/raw/RawDevice.cpp index 0493862..213b546 100644 --- a/src/logid/backend/raw/RawDevice.cpp +++ b/src/logid/backend/raw/RawDevice.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,23 +16,15 @@ * */ -#include "RawDevice.h" -#include "../Error.h" -#include "../hidpp/defs.h" -#include "../dj/defs.h" -#include "../../util/log.h" -#include "../hidpp/Report.h" -#include "../../Configuration.h" -#include "../../util/thread.h" -#include "../../util/task.h" -#include "../../util/workqueue.h" +#include +#include +#include +#include #include #include #include -#define MAX_DATA_LENGTH 32 - extern "C" { #include @@ -46,453 +38,151 @@ using namespace logid::backend::raw; using namespace logid::backend; using namespace std::chrono; -bool RawDevice::supportedReport(uint8_t id, uint8_t length) -{ - switch(id) { - case hidpp::ReportType::Short: - return length == (hidpp::ShortParamLength + - hidpp::Report::HeaderLength); - case hidpp::ReportType::Long: - return length == (hidpp::LongParamLength + - hidpp::Report::HeaderLength); - case dj::ReportType::Short: - return length == (dj::ShortParamLength + dj::HeaderLength); - case dj::ReportType::Long: - return length == (dj::LongParamLength + dj::HeaderLength); - default: - return false; - } -} - -RawDevice::RawDevice(std::string path) : _path (std::move(path)), - _continue_listen (false), _continue_respond (false) -{ - int ret; - - _fd = ::open(_path.c_str(), O_RDWR); - if (_fd == -1) +int get_fd(const std::string& path) { + int fd = ::open(path.c_str(), O_RDWR | O_NONBLOCK); + if (fd == -1) throw std::system_error(errno, std::system_category(), - "RawDevice open failed"); + "RawDevice open failed"); - hidraw_devinfo devinfo{}; - if (-1 == ::ioctl(_fd, HIDIOCGRAWINFO, &devinfo)) { + return fd; +} + +RawDevice::dev_info get_dev_info(int fd) { + hidraw_devinfo dev_info{}; + if (-1 == ::ioctl(fd, HIDIOCGRAWINFO, &dev_info)) { int err = errno; - ::close(_fd); + ::close(fd); throw std::system_error(err, std::system_category(), - "RawDevice HIDIOCGRAWINFO failed"); + "RawDevice HIDIOCGRAWINFO failed"); } - _vid = devinfo.vendor; - _pid = devinfo.product; + return {dev_info.vendor, dev_info.product}; +} + +std::string get_name(int fd) { + ssize_t len; char name_buf[256]; - if (-1 == (ret = ::ioctl(_fd, HIDIOCGRAWNAME(sizeof(name_buf)), name_buf) - )) { + if (-1 == (len = ::ioctl(fd, HIDIOCGRAWNAME(sizeof(name_buf)), name_buf))) { int err = errno; - ::close(_fd); + ::close(fd); throw std::system_error(err, std::system_category(), - "RawDevice HIDIOCGRAWNAME failed"); + "RawDevice HIDIOCGRAWNAME failed"); } - _name.assign(name_buf, ret - 1); - - _rdesc = getReportDescriptor(_fd); - - if (-1 == ::pipe(_pipe)) { - int err = errno; - close(_fd); - throw std::system_error(err, std::system_category(), - "RawDevice pipe open failed"); - } - - _continue_listen = false; + return {name_buf, static_cast(len)}; } -RawDevice::~RawDevice() -{ - if(_fd != -1) - { - ::close(_fd); - ::close(_pipe[0]); - ::close(_pipe[1]); - } +RawDevice::RawDevice(std::string path, const std::shared_ptr& monitor) : + _valid(true), _path(std::move(path)), _fd(get_fd(_path)), + _dev_info(get_dev_info(_fd)), _name(get_name(_fd)), + _report_desc(getReportDescriptor(_fd)), _io_monitor(monitor->ioMonitor()), + _event_handlers(std::make_shared>()) { + _io_monitor->add(_fd, { + [this]() { _readReports(); }, + [this]() { _valid = false; }, + [this]() { _valid = false; } + }); } -std::string RawDevice::hidrawPath() const -{ + +RawDevice::~RawDevice() noexcept { + _io_monitor->remove(_fd); + ::close(_fd); +} + +const std::string& RawDevice::rawPath() const { return _path; } -std::string RawDevice::name() const -{ +const std::string& RawDevice::name() const { return _name; } -uint16_t RawDevice::vendorId() const -{ - return _vid; +[[maybe_unused]] +int16_t RawDevice::vendorId() const { + return _dev_info.vid; } -uint16_t RawDevice::productId() const -{ - return _pid; +int16_t RawDevice::productId() const { + return _dev_info.pid; } -std::vector RawDevice::getReportDescriptor(std::string path) -{ - int fd = ::open(path.c_str(), O_RDWR); +std::vector RawDevice::getReportDescriptor(const std::string& path) { + int fd = ::open(path.c_str(), O_RDWR | O_NONBLOCK); if (fd == -1) throw std::system_error(errno, std::system_category(), "open failed"); - auto rdesc = getReportDescriptor(fd); + auto report_desc = getReportDescriptor(fd); ::close(fd); - return rdesc; + return report_desc; } -std::vector RawDevice::getReportDescriptor(int fd) -{ - hidraw_report_descriptor rdesc{}; - if (-1 == ::ioctl(fd, HIDIOCGRDESCSIZE, &rdesc.size)) { +std::vector RawDevice::getReportDescriptor(int fd) { + hidraw_report_descriptor report_desc{}; + if (-1 == ::ioctl(fd, HIDIOCGRDESCSIZE, &report_desc.size)) { int err = errno; ::close(fd); throw std::system_error(err, std::system_category(), "RawDevice HIDIOCGRDESCSIZE failed"); } - if (-1 == ::ioctl(fd, HIDIOCGRDESC, &rdesc)) { + if (-1 == ::ioctl(fd, HIDIOCGRDESC, &report_desc)) { int err = errno; ::close(fd); throw std::system_error(err, std::system_category(), "RawDevice HIDIOCGRDESC failed"); } - return std::vector(rdesc.value, rdesc.value + rdesc.size); + return {report_desc.value, report_desc.value + report_desc.size}; } -std::vector RawDevice::reportDescriptor() const -{ - return _rdesc; +const std::vector& RawDevice::reportDescriptor() const { + return _report_desc; } -std::vector RawDevice::sendReport(const std::vector& report) -{ - /* If the listener will stop, handle I/O manually. - * Otherwise, push to queue and wait for result. */ - if(_continue_listen) { - std::mutex send_report; - std::unique_lock lock(send_report); - std::condition_variable cv; - bool top_of_queue = false; - auto task = std::make_shared()>> - ( [this, report, &cv, &top_of_queue] () { - top_of_queue = true; - cv.notify_all(); - return this->_respondToReport(report); - }); - auto f = task->get_future(); - _io_queue.push(task); - interruptRead(false); // Alert listener to prioritise - cv.wait(lock, [&top_of_queue]{ return top_of_queue; }); - auto status = f.wait_for(global_config->ioTimeout()); - if(status == std::future_status::timeout) { - _continue_respond = false; - interruptRead(); - return f.get(); // Expecting an error, but it could work - } - return f.get(); - } - else { - std::vector response; - std::exception_ptr _exception; - std::shared_ptr t = std::make_shared( - [this, report, &response]() { - response = _respondToReport(report); - }, [&_exception](std::exception& e) { - try { - throw e; - } catch(std::exception& e) { - _exception = std::make_exception_ptr(e); - } - }); - global_workqueue->queue(t); - t->waitStart(); - auto status = t->waitFor(global_config->ioTimeout()); - if(_exception) - std::rethrow_exception(_exception); - if(status == std::future_status::timeout) { - _continue_respond = false; - interruptRead(); - t->wait(); - if(_exception) - std::rethrow_exception(_exception); - throw TimeoutError(); - } else - return response; - } -} - -// DJ commands are not systematically acknowledged, do not expect a result. -void RawDevice::sendReportNoResponse(const std::vector& report) -{ - /* If the listener will stop, handle I/O manually. - * Otherwise, push to queue and wait for result. */ - if(_continue_listen) { - auto task = std::make_shared()>> - ([this, report]() { - this->_sendReport(report); - return std::vector(); - }); - auto f = task->get_future(); - _io_queue.push(task); - f.get(); - } - else - _sendReport(report); -} - -std::vector RawDevice::_respondToReport - (const std::vector& request) -{ - _sendReport(request); - _continue_respond = true; - - auto start_point = std::chrono::steady_clock::now(); - - while(_continue_respond) { - std::vector response; - auto current_point = std::chrono::steady_clock::now(); - auto timeout = global_config->ioTimeout() - std::chrono::duration_cast - (current_point - start_point); - if(timeout.count() <= 0) - throw TimeoutError(); - _readReport(response, MAX_DATA_LENGTH, timeout); - - if(!_continue_respond) - throw TimeoutError(); - - // All reports have the device index at byte 2 - if(response[1] != request[1]) { - if(_continue_listen) - this->_handleEvent(response); - continue; - } - - if(hidpp::ReportType::Short == request[0] || - hidpp::ReportType::Long == request[0]) { - if(hidpp::ReportType::Short != response[0] && - hidpp::ReportType::Long != response[0]) { - if(_continue_listen) - this->_handleEvent(response); - continue; - } - - // Error; leave to device to handle - if(response[2] == 0x8f || response[2] == 0xff) - return response; - - bool others_match = true; - for(int i = 2; i < 4; i++) - if(response[i] != request[i]) - others_match = false; - - if(others_match) - return response; - } else if(dj::ReportType::Short == request[0] || - dj::ReportType::Long == request[0]) { - //Error; leave to device ot handle - if(0x7f == response[2]) - return response; - else if(response[2] == request[2]) - return response; - } - - if(_continue_listen) - this->_handleEvent(response); +void RawDevice::sendReport(const std::vector& report) { + if (!_valid) { + // We could throw an error here, but this will likely be closed soon. + return; } - return {}; -} - -int RawDevice::_sendReport(const std::vector& report) -{ - std::lock_guard lock(_dev_io); - if(logid::global_loglevel <= LogLevel::RAWREPORT) { + if (logid::global_loglevel <= LogLevel::RAWREPORT) { printf("[RAWREPORT] %s OUT: ", _path.c_str()); - for(auto &i : report) + for (auto& i: report) printf("%02x ", i); printf("\n"); } - assert(supportedReport(report[0], report.size())); - - int ret = ::write(_fd, report.data(), report.size()); - if(ret == -1) { - ///TODO: This seems like a hacky solution - // Try again before failing - ret = ::write(_fd, report.data(), report.size()); - if(ret == -1) - throw std::system_error(errno, std::system_category(), - "_sendReport write failed"); - } - - return ret; -} - -int RawDevice::_readReport(std::vector &report, - std::size_t maxDataLength) -{ - return _readReport(report, maxDataLength, global_config->ioTimeout()); -} - -int RawDevice::_readReport(std::vector &report, - std::size_t maxDataLength, std::chrono::milliseconds timeout) -{ - std::lock_guard lock(_dev_io); - int ret; - report.resize(maxDataLength); - - timeval timeout_tv{}; - timeout_tv.tv_sec = duration_cast(global_config->ioTimeout()) - .count(); - timeout_tv.tv_usec = duration_cast( - global_config->ioTimeout()).count() % - duration_cast(seconds(1)).count(); - - auto timeout_ms = duration_cast(timeout).count(); - - fd_set fds; - do { - FD_ZERO(&fds); - FD_SET(_fd, &fds); - FD_SET(_pipe[0], &fds); - - ret = select(std::max(_fd, _pipe[0]) + 1, - &fds, nullptr, nullptr, - (timeout_ms > 0 ? nullptr : &timeout_tv)); - } while(ret == -1 && errno == EINTR); - - if(ret == -1) + if (write(_fd, report.data(), report.size()) == -1) throw std::system_error(errno, std::system_category(), - "_readReport select failed"); - - if(FD_ISSET(_fd, &fds)) { - ret = read(_fd, report.data(), report.size()); - if(ret == -1) - throw std::system_error(errno, std::system_category(), - "_readReport read failed"); - report.resize(ret); - } - - if(FD_ISSET(_pipe[0], &fds)) { - char c; - ret = read(_pipe[0], &c, sizeof(char)); - if(ret == -1) - throw std::system_error(errno, std::system_category(), - "_readReport read pipe failed"); - } - - if(0 == ret) - throw backend::TimeoutError(); - - if(logid::global_loglevel <= LogLevel::RAWREPORT) { - printf("[RAWREPORT] %s IN: ", _path.c_str()); - for(auto &i : report) - printf("%02x ", i); - printf("\n"); - } - - return ret; + "sendReport write failed"); } -void RawDevice::interruptRead(bool wait_for_halt) -{ - char c = 0; - if(-1 == write(_pipe[1], &c, sizeof(char))) - throw std::system_error(errno, std::system_category(), - "interruptRead write pipe failed"); - - // Ensure I/O has halted - if(wait_for_halt) - std::lock_guard lock(_dev_io); +EventHandlerLock RawDevice::addEventHandler(RawEventHandler handler) { + std::unique_lock lock(_event_handlers->mutex); + _event_handlers->list.emplace_front(std::move(handler)); + return {_event_handlers, _event_handlers->list.cbegin()}; } -void RawDevice::listen() -{ - std::lock_guard lock(_listening); - _continue_listen = true; - _listen_condition.notify_all(); - while(_continue_listen) { - while(!_io_queue.empty()) { - auto task = _io_queue.front(); - (*task)(); - _io_queue.pop(); +void RawDevice::_readReports() { + uint8_t buf[max_data_length]; + ssize_t len; + + while (-1 != (len = ::read(_fd, buf, max_data_length))) { + assert(len <= max_data_length); + std::vector report(buf, buf + len); + + if (logid::global_loglevel <= LogLevel::RAWREPORT) { + printf("[RAWREPORT] %s IN: ", _path.c_str()); + for (auto& i: report) + printf("%02x ", i); + printf("\n"); } - std::vector report; - _readReport(report, MAX_DATA_LENGTH); - this->_handleEvent(report); + _handleEvent(report); } - - // Listener is stopped, handle I/O queue - while(!_io_queue.empty()) { - auto task = _io_queue.front(); - (*task)(); - _io_queue.pop(); - } - - _continue_listen = false; } -void RawDevice::listenAsync() -{ - std::mutex listen_check; - std::unique_lock check_lock(listen_check); - thread::spawn({[this]() { listen(); }}); - - // Block until RawDevice is listening - _listen_condition.wait(check_lock, [this](){ - return (bool)_continue_listen; - }); -} - -void RawDevice::stopListener() -{ - _continue_listen = false; - interruptRead(); -} - -void RawDevice::addEventHandler(const std::string& nickname, - const std::shared_ptr& handler) -{ - std::unique_lock lock(_event_handler_lock); - assert(_event_handlers.find(nickname) == _event_handlers.end()); - assert(handler); - _event_handlers.emplace(nickname, handler); -} - -void RawDevice::removeEventHandler(const std::string &nickname) -{ - std::unique_lock lock(_event_handler_lock); - _event_handlers.erase(nickname); -} - -const std::map>& -RawDevice::eventHandlers() -{ - std::unique_lock lock(_event_handler_lock); - return _event_handlers; -} - -void RawDevice::_handleEvent(std::vector &report) -{ - std::unique_lock lock(_event_handler_lock); - for(auto& handler : _event_handlers) - if(handler.second->condition(report)) - handler.second->callback(report); -} - -bool RawDevice::isListening() -{ - bool ret = _listening.try_lock(); - - if(ret) - _listening.unlock(); - - return !ret; +void RawDevice::_handleEvent(const std::vector& report) { + std::shared_lock lock(_event_handlers->mutex); + for (auto& handler : _event_handlers->list) + if (handler.condition(report)) + handler.callback(report); } diff --git a/src/logid/backend/raw/RawDevice.h b/src/logid/backend/raw/RawDevice.h index 9ec935a..222cd12 100644 --- a/src/logid/backend/raw/RawDevice.h +++ b/src/logid/backend/raw/RawDevice.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,84 +19,71 @@ #ifndef LOGID_BACKEND_RAWDEVICE_H #define LOGID_BACKEND_RAWDEVICE_H +#include +#include #include #include -#include -#include +#include #include #include #include +#include -#include "defs.h" -#include "../../util/mutex_queue.h" +namespace logid::backend::raw { + class DeviceMonitor; -namespace logid { -namespace backend { -namespace raw -{ - class RawDevice - { + class IOMonitor; + + class RawDevice { public: - static bool supportedReport(uint8_t id, uint8_t length); + static constexpr int max_data_length = 32; + typedef RawEventHandler EventHandler; - explicit RawDevice(std::string path); - ~RawDevice(); - std::string hidrawPath() const; + struct dev_info { + int16_t vid; + int16_t pid; + }; - std::string name() const; - uint16_t vendorId() const; - uint16_t productId() const; + RawDevice(std::string path, const std::shared_ptr& monitor); + + ~RawDevice() noexcept; + + [[nodiscard]] const std::string& rawPath() const; + + [[nodiscard]] const std::string& name() const; + + [[maybe_unused]] + [[nodiscard]] int16_t vendorId() const; + + [[nodiscard]] int16_t productId() const; + + static std::vector getReportDescriptor(const std::string& path); - static std::vector getReportDescriptor(std::string path); static std::vector getReportDescriptor(int fd); - std::vector reportDescriptor() const; - std::vector sendReport(const std::vector& report); - void sendReportNoResponse(const std::vector& report); - void interruptRead(bool wait_for_halt=true); + [[nodiscard]] const std::vector& reportDescriptor() const; - void listen(); - void listenAsync(); - void stopListener(); - bool isListening(); + void sendReport(const std::vector& report); - void addEventHandler(const std::string& nickname, - const std::shared_ptr& handler); - void removeEventHandler(const std::string& nickname); - const std::map>& - eventHandlers(); + [[nodiscard]] EventHandlerLock addEventHandler(RawEventHandler handler); private: - std::mutex _dev_io, _listening; - std::string _path; - int _fd; - int _pipe[2]; - uint16_t _vid; - uint16_t _pid; - std::string _name; - std::vector _rdesc; + void _readReports(); - std::atomic _continue_listen; - std::atomic _continue_respond; - std::condition_variable _listen_condition; + std::atomic_bool _valid; - std::map> - _event_handlers; - std::mutex _event_handler_lock; - void _handleEvent(std::vector& report); + const std::string _path; + const int _fd; + const dev_info _dev_info; + const std::string _name; + const std::vector _report_desc; - /* These will only be used internally and processed with a queue */ - int _sendReport(const std::vector& report); - int _readReport(std::vector& report, std::size_t maxDataLength); - int _readReport(std::vector& report, std::size_t maxDataLength, - std::chrono::milliseconds timeout); + std::shared_ptr _io_monitor; - std::vector _respondToReport(const std::vector& - request); + std::shared_ptr> _event_handlers; - mutex_queue()>>> - _io_queue; + void _handleEvent(const std::vector& report); }; -}}} +} -#endif //LOGID_BACKEND_RAWDEVICE_H \ No newline at end of file +#endif //LOGID_BACKEND_RAWDEVICE_H diff --git a/src/logid/backend/dj/Error.cpp b/src/logid/config/config.cpp similarity index 61% rename from src/logid/backend/dj/Error.cpp rename to src/logid/config/config.cpp index 4277cf6..1e4f130 100644 --- a/src/logid/backend/dj/Error.cpp +++ b/src/logid/config/config.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,27 +16,15 @@ * */ -#include "Error.h" +#include +#include -using namespace logid::backend::dj; +using namespace logid; -Error::Error(uint8_t code) : _code (code) -{ -} +const char config::keys::name[] = "name"; +const char config::keys::cid[] = "cid"; +const char config::keys::direction[] = "direction"; -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; +void config::logError(const libconfig::Setting& setting, std::exception& e) { + logPrintf(WARN, "Error at line %d: %s", setting.getSourceLine(), e.what()); } \ No newline at end of file diff --git a/src/logid/config/group.h b/src/logid/config/group.h new file mode 100644 index 0000000..e7b301a --- /dev/null +++ b/src/logid/config/group.h @@ -0,0 +1,202 @@ +/* + * Copyright 2022 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_CONFIG_GROUP_H +#define LOGID_CONFIG_GROUP_H + +#include +#include +#include +#include + +namespace logid::config { + template + void set(libconfig::Setting& parent, + const std::string& name, + const T& t); + + template + void set(libconfig::Setting& parent, const T& t); + + template + auto get(const libconfig::Setting& parent, const std::string& name); + + template + void append(libconfig::Setting& list, const T& t); + + template + struct group_io { + }; + + template + struct group_io { + static void get(const libconfig::Setting&, T*, + const std::vector&, const std::size_t) {} + + static void set(libconfig::Setting&, const T*, + const std::vector&, const std::size_t) {} + }; + + template + struct group_io { + static void get(const libconfig::Setting& s, T* t, + const std::vector& names, + const std::size_t index, A T::* arg, M T::*... rest) { + auto& x = t->*(arg); + A old{x}; + try { + x = config::get(s, names[index]); + group_io::get(s, t, names, index + 1, rest...); + } catch (libconfig::SettingTypeException& e) { + x = old; + throw; + } catch (libconfig::SettingException& e) { + x = old; + throw libconfig::SettingTypeException(s); + } + } + + static void set(libconfig::Setting& s, const T* t, + const std::vector& names, + const std::size_t index, A T::* arg, M T::*... rest) { + config::set(s, names[index], t->*(arg)); + group_io::set(s, t, names, index + 1, rest...); + } + }; + + template + struct signed_group; + + struct group { + private: + const std::vector _names; + const std::function&)> _getter; + const std::function&)> _setter; + + template + friend + struct signed_group; + protected: + template + explicit group(const std::array& names, + M T::*... args) : + _names(names.begin(), names.end()), + _getter([args...](const libconfig::Setting& s, group* g, + const std::vector& names) { + T* t = dynamic_cast(g); + group_io::get(s, t, names, 0, args...); + }), + _setter([args...](libconfig::Setting& s, const group* g, + const std::vector& names) { + const T* t = dynamic_cast(g); + group_io::set(s, t, names, 0, args...); + }) { + static_assert(std::is_base_of::value); + } + + group() : _getter([](const libconfig::Setting&, group*, + const std::vector&) {}), + _setter([](libconfig::Setting&, const group*, + const std::vector&) {}) {} + + public: + group(const group& o) = default; + + group(group&& o) noexcept = default; + + group& operator=(const group&) { + return *this; + } + + group& operator=(group&&) noexcept { + return *this; + } + + virtual ~group() = default; + + virtual void _save(libconfig::Setting& setting) const { + _setter(setting, this, _names); + } + + virtual void _load(const libconfig::Setting& setting) { + _getter(setting, this, _names); + } + }; + + template + struct normalize_signature { + static const T& make(const T& ret) { return ret; } + }; + + template<> + struct normalize_signature { + static std::string make(const std::string& data) { + std::string ret = data; + std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower); + return ret; + } + }; + + template + struct signed_group : public group { + private: + const std::string _sig_field; + const Sign _signature; + protected: + signed_group(std::string sign_name, const Sign& sign_data) : + group(), _sig_field(std::move(sign_name)), + _signature(normalize_signature::make(sign_data)) {} + + template + signed_group( + std::string sign_name, const Sign& sign_data, + const std::array& names, + M T::*... args) : group(names, args...), + _sig_field(std::move(sign_name)), + _signature(normalize_signature::make(sign_data)) {} + + public: + signed_group(const signed_group& o) = default; + + signed_group(signed_group&& o) noexcept = default; + + signed_group& operator=(const signed_group&) { + return *this; + } + + signed_group& operator=(signed_group&&) noexcept { + return *this; + } + + void _save(libconfig::Setting& setting) const override { + set(setting, _sig_field, _signature); + _setter(setting, this, _names); + } + + void _load(const libconfig::Setting& setting) override { + if (normalize_signature::make(get(setting, _sig_field)) + != _signature) + throw libconfig::SettingTypeException(setting); + _getter(setting, this, _names); + } + }; + +} + +#endif //LOGID_CONFIG_GROUP_H diff --git a/src/logid/config/map.h b/src/logid/config/map.h new file mode 100644 index 0000000..13d1751 --- /dev/null +++ b/src/logid/config/map.h @@ -0,0 +1,60 @@ +/* + * Copyright 2022 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_CONFIG_MAP_H +#define LOGID_CONFIG_MAP_H + +#include +#include +#include +#include + +namespace logid::config { + struct string_literal { }; + + template + struct string_literal_of : public string_literal { + constexpr static const char* value = str; + }; + + template + struct less_caseless { + constexpr bool operator()(const T& a, const T& b) const noexcept { + auto a_it = a.begin(), b_it = b.begin(); + for (; a_it != a.end() && b_it != b.end(); ++a_it, ++b_it) { + if (tolower(*a_it) != tolower(*b_it)) + return tolower(*a_it) < tolower(*b_it); + } + return b_it != b.end(); + } + }; + + // Warning: map must be a variant of groups or a group + template::key_compare, + typename Allocator=typename std::map::allocator_type> + class map : public std::map { + static_assert(std::is_base_of::value, + "KeyName must be a string_literal"); + public: + template + explicit map(Args... args) : + std::map(std::forward(args)...) {} + }; +} + +#endif //LOGID_CONFIG_MAP_H diff --git a/src/logid/config/schema.h b/src/logid/config/schema.h new file mode 100644 index 0000000..6f16a8a --- /dev/null +++ b/src/logid/config/schema.h @@ -0,0 +1,335 @@ +/* + * Copyright 2022 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_CONFIG_SCHEMA_H +#define LOGID_CONFIG_SCHEMA_H + +#include + +namespace logid::actions { + class ChangeDPI; + + class ChangeHostAction; + + class ChangeProfile; + + class CycleDPI; + + class GestureAction; + + class KeypressAction; + + class NullAction; + + class ToggleHiresScroll; + + class ToggleSmartShift; + + class AxisGesture; + + class IntervalGesture; + + class NullGesture; + + class ReleaseGesture; + + class ThresholdGesture; +} + +namespace logid::config { + struct keys { + static const char name[]; + static const char cid[]; + static const char direction[]; + }; + + struct NoAction : public signed_group { + typedef actions::NullAction action; + + NoAction() : signed_group("type", "None") {} + }; + + struct KeypressAction : public signed_group { + typedef actions::KeypressAction action; + std::optional< + std::variant>>> keys; + + KeypressAction() : signed_group( + "type", "Keypress", + {"keys"}, &KeypressAction::keys) { + } + }; + + struct ToggleSmartShift : public signed_group { + typedef actions::ToggleSmartShift action; + + ToggleSmartShift() : + signed_group("type", "ToggleSmartShift") {} + }; + + struct ToggleHiresScroll : public signed_group { + typedef actions::ToggleHiresScroll action; + + ToggleHiresScroll() : + signed_group("type", "ToggleHiresScroll") {} + }; + + struct CycleDPI : public signed_group { + typedef actions::CycleDPI action; + std::optional> dpis; + std::optional sensor; + + CycleDPI() : signed_group( + "type", "CycleDPI", + {"dpis", "sensor"}, + &CycleDPI::dpis, + &CycleDPI::sensor) {} + }; + + struct ChangeDPI : public signed_group { + typedef actions::ChangeDPI action; + std::optional inc; + std::optional sensor; + + ChangeDPI() : signed_group( + "type", "ChangeDPI", + {"inc", "sensor"}, + &ChangeDPI::inc, + &ChangeDPI::sensor) {} + }; + + struct ChangeHost : public signed_group { + typedef actions::ChangeHostAction action; + std::optional> host; + + ChangeHost() : signed_group( + "type", "ChangeHost", + {"host"}, &ChangeHost::host) {} + }; + + struct ChangeProfile : public signed_group { + typedef actions::ChangeProfile action; + std::optional profile; + + ChangeProfile() : signed_group("type", "ChangeProfile", + {"profile"}, &ChangeProfile::profile) {} + }; + + typedef std::variant< + NoAction, + KeypressAction, + ToggleSmartShift, + ToggleHiresScroll, + CycleDPI, + ChangeDPI, + ChangeHost, + ChangeProfile + > BasicAction; + + struct AxisGesture : public signed_group { + typedef actions::AxisGesture gesture; + std::optional threshold; + std::optional> axis; + std::optional axis_multiplier; + + AxisGesture() : signed_group("mode", "Axis", + {"threshold", "axis", "axis_multiplier"}, + &AxisGesture::threshold, + &AxisGesture::axis, + &AxisGesture::axis_multiplier) {} + }; + + struct IntervalGesture : public signed_group { + typedef actions::IntervalGesture gesture; + std::optional threshold; + std::optional action; + std::optional interval; + protected: + explicit IntervalGesture(const std::string& name) : signed_group( + "mode", name, + {"threshold", "action", "interval"}, + &IntervalGesture::threshold, + &IntervalGesture::action, + &IntervalGesture::interval) {} + + public: + IntervalGesture() : IntervalGesture("OnInterval") {} + }; + + struct FewPixelsGesture : public IntervalGesture { + FewPixelsGesture() : IntervalGesture("OnFewPixels") {} + }; + + struct ReleaseGesture : public signed_group { + typedef actions::ReleaseGesture gesture; + std::optional threshold; + std::optional action; + + ReleaseGesture() : signed_group("mode", "OnRelease", + {"threshold", "action"}, + &ReleaseGesture::threshold, + &ReleaseGesture::action) {} + }; + + struct ThresholdGesture : public signed_group { + typedef actions::ThresholdGesture gesture; + std::optional threshold; + std::optional action; + + ThresholdGesture() : signed_group("mode", "OnThreshold", + {"threshold", "action"}, + &ThresholdGesture::threshold, + &ThresholdGesture::action) {} + }; + + struct NoGesture : public signed_group { + typedef actions::NullGesture gesture; + std::optional threshold; + + NoGesture() : signed_group("mode", "NoPress", + {"threshold"}, + &NoGesture::threshold) {} + }; + + typedef std::variant< + NoGesture, + AxisGesture, + IntervalGesture, + FewPixelsGesture, + ReleaseGesture, + ThresholdGesture + > Gesture; + + + struct GestureAction : public signed_group { + typedef actions::GestureAction action; + std::optional, + less_caseless>> gestures; + + GestureAction() : signed_group( + "type", "Gestures", + {"gestures"}, + &GestureAction::gestures) {} + }; + + typedef std::variant< + NoAction, + KeypressAction, + ToggleSmartShift, + ToggleHiresScroll, + CycleDPI, + ChangeDPI, + ChangeHost, + ChangeProfile, + GestureAction + > Action; + + struct Button : public group { + std::optional action; + + Button() : group({"action"}, + &Button::action) {} + }; + + struct SmartShift : public group { + std::optional on; + std::optional threshold; + std::optional torque; + + SmartShift() : group({"on", "threshold", "torque"}, + &SmartShift::on, &SmartShift::threshold, &SmartShift::torque) {} + }; + + + struct HiresScroll : public group { + std::optional hires; + std::optional invert; + std::optional target; + std::optional up; + std::optional down; + + HiresScroll() : group({"hires", "invert", "target", "up", "down"}, + &HiresScroll::hires, + &HiresScroll::invert, + &HiresScroll::target, + &HiresScroll::up, + &HiresScroll::down) {} + }; + + typedef std::variant> DPI; + + struct ThumbWheel : public group { + std::optional divert; + std::optional invert; + std::optional left; + std::optional right; + std::optional proxy; + std::optional touch; + std::optional tap; + + ThumbWheel() : group({"divert", "invert", "left", "right", + "proxy", "touch", "tap"}, + &ThumbWheel::divert, &ThumbWheel::invert, + &ThumbWheel::left, &ThumbWheel::right, + &ThumbWheel::proxy, &ThumbWheel::touch, + &ThumbWheel::tap) {} + }; + + typedef map> RemapButton; + + struct Profile : public group { + std::optional dpi; + std::optional smartshift; + std::optional> hiresscroll; + std::optional thumbwheel; + std::optional buttons; + + Profile() : group({"dpi", "smartshift", "hiresscroll", + "buttons", "thumbwheel"}, + &Profile::dpi, &Profile::smartshift, + &Profile::hiresscroll, &Profile::buttons, + &Profile::thumbwheel) {} + }; + + struct Device : public group { + ipcgull::property default_profile; + map> profiles; + + Device() : group({"default_profile", "profiles"}, + &Device::default_profile, + &Device::profiles), + default_profile(ipcgull::property_full_permissions, "") { + } + }; + + struct Config : public group { + std::optional, string_literal_of>> devices; + std::optional> ignore; + std::optional io_timeout; + std::optional workers; + + Config() : group({"devices", "ignore", "io_timeout", "workers"}, + &Config::devices, + &Config::ignore, + &Config::io_timeout, + &Config::workers) {} + }; +} + +#endif //LOGID_CONFIG_SCHEMA_H diff --git a/src/logid/config/types.h b/src/logid/config/types.h new file mode 100644 index 0000000..ef17a75 --- /dev/null +++ b/src/logid/config/types.h @@ -0,0 +1,462 @@ +/* + * Copyright 2022 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_CONFIG_PRIMITIVE_H +#define LOGID_CONFIG_PRIMITIVE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Containers are chosen specifically so that no iterator is invalidated. + +namespace logid::config { + void logError(const libconfig::Setting& setting, std::exception& e); + + template + struct config_io { + static_assert(std::is_base_of::value); + + static T get(const libconfig::Setting& parent, + const std::string& name) { + T t{}; + t._load(parent.lookup(name)); + return t; + } + + static T get(const libconfig::Setting& setting) { + T t{}; + t._load(setting); + return t; + } + + static void set(libconfig::Setting& parent, + const std::string& name, + const T& t) { + if (!parent.exists(name)) { + parent.add(name, libconfig::Setting::TypeGroup); + } else if (parent.lookup(name).getType() + != libconfig::Setting::TypeGroup) { + parent.remove(name); + parent.add(name, libconfig::Setting::TypeGroup); + } + t._save(parent.lookup(name)); + } + + static void set(libconfig::Setting& setting, const T& t) { + t._save(setting); + } + + static void append(libconfig::Setting& list, const T& t) { + auto& x = list.add(libconfig::Setting::TypeGroup); + set(x, t); + } + }; + + template + struct config_io> : public config_io { + }; + + template + struct primitive_io { + static T get(const libconfig::Setting& parent, + const std::string& name) { + return parent.lookup(name); + } + + static T get(const libconfig::Setting& setting) { + return setting; + } + + static void set(libconfig::Setting& parent, + const std::string& name, + const T& t) { + if (!parent.exists(name)) { + parent.add(name, TypeEnum); + } else if (parent.lookup(name).getType() != TypeEnum) { + parent.remove(name); + parent.add(name, TypeEnum); + } + set(parent.lookup(name), t); + } + + static void set(libconfig::Setting& setting, const T& t) { + setting = t; + } + + static void append(libconfig::Setting& list, const T& t) { + auto& x = list.add(TypeEnum); + set(x, t); + } + }; + + template + struct reinterpret_io { + static T get(const libconfig::Setting& parent, + const std::string& name) { + return static_cast(primitive_io::get(parent, name)); + } + + static T get(const libconfig::Setting& setting) { + return static_cast(primitive_io::get(setting)); + } + + static void set(libconfig::Setting& parent, + const std::string& name, + const T& t) { + primitive_io::set(parent, name, + static_cast(t)); + } + + static void set(libconfig::Setting& setting, const T& t) { + primitive_io::set(setting, + static_cast(t)); + } + + [[maybe_unused]] + static void append(libconfig::Setting& list, const T& t) { + primitive_io::append(list, + static_cast(t)); + } + }; + + template<> + struct config_io : public primitive_io { + }; + template<> + struct config_io : public reinterpret_io { + }; + template<> + struct config_io : public reinterpret_io { + }; + template<> + struct config_io : public reinterpret_io { + }; + template<> + struct config_io : public reinterpret_io { + }; + template<> + struct config_io : public primitive_io { + }; + template<> + struct config_io : public reinterpret_io { + }; + template<> + struct config_io : public reinterpret_io { + }; + template<> + struct config_io : public reinterpret_io { + }; + template<> + struct config_io : public primitive_io { + }; + template<> + struct config_io : + public reinterpret_io { + }; + template<> + struct config_io : public primitive_io { + }; + template<> + struct config_io : public primitive_io { + }; + template<> + struct config_io : public primitive_io { + }; + + template + struct config_io> { + private: + template + static std::variant try_each(const libconfig::Setting& setting) { + return config_io::get(setting); + } + + template + static std::variant try_each(const libconfig::Setting& setting) { + try { + return config_io::get(setting); + } catch (libconfig::SettingException& e) { + return try_each(setting); + } + } + + public: + static std::variant get(const libconfig::Setting& setting) { + return try_each(setting); + } + + static std::variant get(const libconfig::Setting& parent, + const std::string& name) { + return get(parent.lookup(name)); + } + + static void set(libconfig::Setting& setting, + const std::variant& t) { + std::visit([&setting](auto&& arg) { + config::set(setting, arg); + }, t); + } + + static void set(libconfig::Setting& parent, + const std::string& name, + const std::variant& t) { + std::visit([&parent, &name](auto&& arg) { + config::set(parent, name, arg); + }, t); + } + + [[maybe_unused]] + static void append(libconfig::Setting& list, const std::variant& t) { + std::visit([&list](auto&& arg) { + config::append(list, arg); + }, t); + } + }; + + template + struct config_io> { + static std::list get(const libconfig::Setting& setting) { + const auto size = setting.getLength(); + std::list t{}; + for (int i = 0; i < size; ++i) { + try { + t.emplace_back(config_io::get(setting[i])); + } catch (libconfig::SettingException& e) {} + } + return t; + } + + static std::list get(const libconfig::Setting& parent, const std::string& name) { + return get(parent.lookup(name)); + } + + static void set(libconfig::Setting& setting, const std::list& t) { + while (setting.getLength() != 0) + setting.remove((int) 0); + for (auto& x: t) { + config_io::append(setting, x); + } + } + + static void set(libconfig::Setting& parent, + const std::string& name, + const std::list& t) { + if (!parent.exists(name)) { + parent.add(name, libconfig::Setting::TypeList); + } else if (!parent.lookup(name).isList()) { + parent.remove(name); + parent.add(name, libconfig::Setting::TypeList); + } + set(parent.lookup(name), t); + } + + [[maybe_unused]] + static void append(libconfig::Setting& list, const std::list& t) { + auto& s = list.add(libconfig::Setting::TypeList); + set(s, t); + } + }; + + template + struct config_io> { + static std::set get(const libconfig::Setting& setting) { + const auto size = setting.getLength(); + std::set t; + for (int i = 0; i < size; ++i) { + try { + t.emplace(config_io::get(setting[i])); + } catch (libconfig::SettingException& e) {} + } + return t; + } + + static std::set get(const libconfig::Setting& parent, const std::string& name) { + return get(parent.lookup(name)); + } + + static void set(libconfig::Setting& setting, const std::set& t) { + while (setting.getLength() != 0) + setting.remove((int) 0); + for (auto& x: t) { + auto& s = setting.add(libconfig::Setting::TypeGroup); + config_io::set(s, x); + } + } + + static void set(libconfig::Setting& parent, + const std::string& name, + const std::set& t) { + if (!parent.exists(name)) { + parent.add(name, libconfig::Setting::TypeList); + } else if (!parent.lookup(name).isArray()) { + parent.remove(name); + parent.add(name, libconfig::Setting::TypeList); + } + set(parent.lookup(name), t); + } + + [[maybe_unused]] + static void append(libconfig::Setting& list, + const std::set& t) { + auto& s = list.add(libconfig::Setting::TypeList); + set(s, t); + } + }; + + template + struct config_io> { + static map get(const libconfig::Setting& setting) { + const auto size = setting.getLength(); + map t; + for (int i = 0; i < size; ++i) { + auto& s = setting[i]; + try { + t.emplace(config_io::get(s.lookup(KeyName::value)), + config_io::get(s)); + } catch (libconfig::SettingException& e) {} + } + return t; + } + + static map get( + const libconfig::Setting& parent, const std::string& name) { + return get(parent.lookup(name)); + } + + static void set(libconfig::Setting& setting, + const map& t) { + while (setting.getLength() != 0) + setting.remove((int) 0); + for (auto& x: t) { + auto& s = setting.add(libconfig::Setting::TypeGroup); + config_io::set(s, x.second); + config_io::set(s, KeyName::value, x.first); + } + } + + static void set(libconfig::Setting& parent, + const std::string& name, + const map& t) { + if (!parent.exists(name)) { + parent.add(name, libconfig::Setting::TypeList); + } else if (!parent.lookup(name).isArray()) { + parent.remove(name); + parent.add(name, libconfig::Setting::TypeList); + } + set(parent.lookup(name), t); + } + + [[maybe_unused]] + static void append(libconfig::Setting& list, const map& t) { + auto& s = list.add(libconfig::Setting::TypeList); + set(s, t); + } + }; + + template + struct config_io> { + static std::optional get(const libconfig::Setting& parent, + const std::string& name) { + if (parent.exists(name)) { + auto& setting = parent.lookup(name); + try { + return config_io::get(setting); + } catch (libconfig::SettingException& e) { + logError(setting, e); + return std::nullopt; + } + } else { + return std::nullopt; + } + } + + static void set(libconfig::Setting& parent, + const std::string& name, + const std::optional& t) { + if (t.has_value()) + config_io::set(parent, name, t.value()); + } + }; + + // Optionals may not appear as part of a list or array + template + struct config_io, Rest...>> { + static_assert(!sizeof(std::optional), "Invalid type"); + }; + + template + struct config_io>> { + static_assert(!sizeof(std::optional), "Invalid type"); + }; + + template + struct config_io>> { + static_assert(!sizeof(std::optional), "Invalid type"); + }; + + template + void set(libconfig::Setting& parent, + const std::string& name, + const T& t) { + config_io::set(parent, name, t); + } + + template + void set(libconfig::Setting& setting, const T& t) { + config_io::set(setting, t); + } + + + template + void append(libconfig::Setting& list, const T& t) { + config_io::append(list, t); + } + + template + auto get(const libconfig::Setting& setting) { + return config_io::get(setting); + } + + template + auto get(const libconfig::Setting& parent, const std::string& name) { + return config_io::get(parent, name); + } +} + +#endif //LOGID_CONFIG_PRIMITIVE_H diff --git a/src/logid/features/DPI.cpp b/src/logid/features/DPI.cpp index 82093a1..fdbac5c 100644 --- a/src/logid/features/DPI.cpp +++ b/src/logid/features/DPI.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,42 +15,39 @@ * along with this program. If not, see . * */ +#include +#include #include #include -#include "DPI.h" -#include "../Device.h" -#include "../util/log.h" +#include using namespace logid::features; using namespace logid::backend; -uint16_t getClosestDPI(hidpp20::AdjustableDPI::SensorDPIList& dpi_list, - uint16_t dpi) -{ - if(dpi_list.isRange) { - const uint16_t min = *std::min_element(dpi_list.dpis.begin(), - dpi_list.dpis.end()); - const uint16_t max = *std::max_element(dpi_list.dpis.begin(), - dpi_list.dpis.end()); - if(!((dpi-min) % dpi_list.dpiStep) && dpi >= min && dpi <= max) +uint16_t getClosestDPI(const hidpp20::AdjustableDPI::SensorDPIList& dpi_list, + uint16_t dpi) { + if (dpi_list.isRange) { + const uint16_t min = *std::min_element(dpi_list.dpis.begin(), dpi_list.dpis.end()); + const uint16_t max = *std::max_element(dpi_list.dpis.begin(), dpi_list.dpis.end()); + if (!((dpi - min) % dpi_list.dpiStep) && dpi >= min && dpi <= max) return dpi; - else if(dpi > max) + else if (dpi > max) return max; - else if(dpi < min) + else if (dpi < min) return min; else - return min + round((double)(dpi-min)/dpi_list.dpiStep)*dpi_list - .dpiStep; + return (uint16_t) (min + round((double) (dpi - min) / dpi_list.dpiStep) * + dpi_list.dpiStep); } else { - if(std::find(dpi_list.dpis.begin(), dpi_list.dpis.end(), dpi) - != dpi_list.dpis.end()) + if (std::find(dpi_list.dpis.begin(), dpi_list.dpis.end(), dpi) + != dpi_list.dpis.end()) return dpi; else { - auto it = std::min_element(dpi_list.dpis.begin(), dpi_list.dpis - .end(), [dpi](uint16_t a, uint16_t b) { - return (dpi - a) < (dpi - b); - }); - if(it == dpi_list.dpis.end()) + auto it = std::min_element(dpi_list.dpis.begin(), dpi_list.dpis.end(), + [dpi](uint16_t a, uint16_t b) { + return (dpi - a) < (dpi - b); + }); + if (it == dpi_list.dpis.end()) return 0; else return *it; @@ -58,89 +55,155 @@ uint16_t getClosestDPI(hidpp20::AdjustableDPI::SensorDPIList& dpi_list, } } -DPI::DPI(Device* device) : DeviceFeature(device), _config (device) -{ +DPI::DPI(Device* device) : DeviceFeature(device), _config(device->activeProfile().dpi) { try { _adjustable_dpi = std::make_shared (&device->hidpp20()); } catch (hidpp20::UnsupportedFeature& e) { throw UnsupportedFeature(); } + + _ipc_interface = _device->ipcNode()->make_interface(this); } -void DPI::configure() -{ - const uint8_t sensors = _adjustable_dpi->getSensorCount(); - for(uint8_t i = 0; i < _config.getSensorCount(); i++) { - hidpp20::AdjustableDPI::SensorDPIList dpi_list; - if(_dpi_lists.size() <= i) { - dpi_list = _adjustable_dpi->getSensorDPIList(i); - _dpi_lists.push_back(dpi_list); +void DPI::configure() { + std::shared_lock lock(_config_mutex); + + if (_config.get().has_value()) { + const auto& config = _config.get().value(); + if (std::holds_alternative(config)) { + const auto& dpi = std::get(config); + _fillDPILists(0); + std::shared_lock dpi_lock(_dpi_list_mutex); + if (dpi != 0) { + _adjustable_dpi->setSensorDPI(0, getClosestDPI(_dpi_lists.at(0), dpi)); + } } else { - dpi_list = _dpi_lists[i]; - } - if(i < sensors) { - auto dpi = _config.getDPI(i); - if(dpi) { - _adjustable_dpi->setSensorDPI(i, getClosestDPI(dpi_list, - dpi)); + const auto& dpis = std::get>(config); + int i = 0; + _fillDPILists(dpis.size() - 1); + std::shared_lock dpi_lock(_dpi_list_mutex); + for (const auto& dpi: dpis) { + if (dpi != 0) { + _adjustable_dpi->setSensorDPI(i, getClosestDPI(_dpi_lists.at(i), dpi)); + ++i; + } } } } } -void DPI::listen() -{ +void DPI::listen() { } -uint16_t DPI::getDPI(uint8_t sensor) -{ +void DPI::setProfile(config::Profile& profile) { + std::unique_lock lock(_config_mutex); + _config = profile.dpi; +} + +uint16_t DPI::getDPI(uint8_t sensor) { return _adjustable_dpi->getSensorDPI(sensor); } -void DPI::setDPI(uint16_t dpi, uint8_t sensor) -{ - hidpp20::AdjustableDPI::SensorDPIList dpi_list; - if(_dpi_lists.size() <= sensor) { - dpi_list = _adjustable_dpi->getSensorDPIList(sensor); - for(std::size_t i = _dpi_lists.size(); i < sensor; i++) { - _dpi_lists.push_back(_adjustable_dpi->getSensorDPIList(i)); - } - _dpi_lists.push_back(dpi_list); - } - dpi_list = _dpi_lists[sensor]; +void DPI::setDPI(uint16_t dpi, uint8_t sensor) { + if (dpi == 0) + return; + _fillDPILists(sensor); + std::shared_lock lock(_dpi_list_mutex); + auto dpi_list = _dpi_lists.at(sensor); _adjustable_dpi->setSensorDPI(sensor, getClosestDPI(dpi_list, dpi)); } -/* Some devices have multiple sensors, but an older config format - * only supports a single DPI. The dpi setting can be an array or - * an integer. - */ -DPI::Config::Config(Device *dev) : DeviceFeature::Config(dev) -{ - try { - auto& config_root = dev->config().getSetting("dpi"); - if(config_root.isNumber()) { - int dpi = config_root; - _dpis.push_back(dpi); - } else if(config_root.isArray()) { - for(int i = 0; i < config_root.getLength(); i++) - _dpis.push_back((int)config_root[i]); - } else { - logPrintf(WARN, "Line %d: dpi is improperly formatted", - config_root.getSourceLine()); +void DPI::_fillDPILists(uint8_t sensor) { + bool needs_fill; + { + std::shared_lock lock(_dpi_list_mutex); + needs_fill = _dpi_lists.size() <= sensor; + } + if (needs_fill) { + std::unique_lock lock(_dpi_list_mutex); + for (std::size_t i = _dpi_lists.size(); i <= sensor; i++) { + _dpi_lists.push_back(_adjustable_dpi->getSensorDPIList(i)); } - } catch(libconfig::SettingNotFoundException& e) { - // DPI not configured, use default } } -uint8_t DPI::Config::getSensorCount() -{ - return _dpis.size(); +DPI::IPC::IPC(DPI* parent) : ipcgull::interface( + SERVICE_ROOT_NAME ".DPI", { + {"GetSensors", {this, &IPC::getSensors, {"sensors"}}}, + {"GetDPIs", {this, &IPC::getDPIs, {"sensor"}, {"dpis", "dpiStep", "range"}}}, + {"GetDPI", {this, &IPC::getDPI, {"sensor"}, {"dpi"}}}, + {"SetDPI", {this, &IPC::setDPI, {"dpi", "sensor"}}} + }, {}, {}), _parent(*parent) { } -uint16_t DPI::Config::getDPI(uint8_t sensor) -{ - return _dpis[sensor]; +uint8_t DPI::IPC::getSensors() const { + return _parent._dpi_lists.size(); +} + +std::tuple, uint16_t, bool> DPI::IPC::getDPIs(uint8_t sensor) const { + _parent._fillDPILists(sensor); + std::shared_lock lock(_parent._dpi_list_mutex); + auto& dpi_list = _parent._dpi_lists.at(sensor); + return {dpi_list.dpis, dpi_list.dpiStep, dpi_list.isRange}; +} + +uint16_t DPI::IPC::getDPI(uint8_t sensor) const { + std::shared_lock lock(_parent._config_mutex); + auto& config = _parent._config.get(); + + if (!config.has_value()) + return _parent.getDPI(sensor); + + if (std::holds_alternative(config.value())) { + if (sensor == 0) + return std::get(config.value()); + else + return _parent.getDPI(sensor); + } + + const auto& list = std::get>(config.value()); + + if (list.size() > sensor) { + auto it = list.begin(); + std::advance(it, sensor); + return *it; + } else { + return _parent.getDPI(sensor); + } +} + +void DPI::IPC::setDPI(uint16_t dpi, uint8_t sensor) { + std::unique_lock lock(_parent._config_mutex); + auto& config = _parent._config.get(); + + if (!config.has_value()) + config.emplace(std::list()); + + if (std::holds_alternative(config.value())) { + if (sensor == 0) { + config.value() = dpi; + } else { + auto list = std::list(sensor + 1, 0); + *list.rbegin() = dpi; + *list.begin() = dpi; + config.value() = list; + } + } else { + auto& list = std::get>(config.value()); + + while (list.size() <= sensor) { + list.emplace_back(0); + } + + if (list.size() == (size_t) (sensor + 1)) { + *list.rbegin() = dpi; + } else { + auto it = list.begin(); + std::advance(it, sensor); + *it = dpi; + } + } + + _parent.setDPI(dpi, sensor); } diff --git a/src/logid/features/DPI.h b/src/logid/features/DPI.h index f2f6c33..6fff8d8 100644 --- a/src/logid/features/DPI.h +++ b/src/logid/features/DPI.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,36 +18,56 @@ #ifndef LOGID_FEATURE_DPI_H #define LOGID_FEATURE_DPI_H -#include "../backend/hidpp20/features/AdjustableDPI.h" -#include "DeviceFeature.h" +#include +#include +#include +#include +#include -namespace logid { -namespace features -{ - class DPI : public DeviceFeature - { +namespace logid::features { + class DPI : public DeviceFeature { public: + void configure() final; + + void listen() final; + + void setProfile(config::Profile& profile) final; + + uint16_t getDPI(uint8_t sensor = 0); + + void setDPI(uint16_t dpi, uint8_t sensor = 0); + + protected: explicit DPI(Device* dev); - virtual void configure(); - virtual void listen(); - uint16_t getDPI(uint8_t sensor=0); - void setDPI(uint16_t dpi, uint8_t sensor=0); - - class Config : public DeviceFeature::Config - { - public: - explicit Config(Device* dev); - uint16_t getDPI(uint8_t sensor); - uint8_t getSensorCount(); - protected: - std::vector _dpis; - }; private: - Config _config; + void _fillDPILists(uint8_t sensor); + + class IPC : public ipcgull::interface { + public: + explicit IPC(DPI* parent); + + [[nodiscard]] uint8_t getSensors() const; + + [[nodiscard]] std::tuple, uint16_t, bool> getDPIs( + uint8_t sensor) const; + + [[nodiscard]] uint16_t getDPI(uint8_t sensor) const; + + void setDPI(uint16_t dpi, uint8_t sensor); + + private: + DPI& _parent; + }; + + mutable std::shared_mutex _config_mutex; + std::reference_wrapper> _config; std::shared_ptr _adjustable_dpi; + mutable std::shared_mutex _dpi_list_mutex; std::vector _dpi_lists; + + std::shared_ptr _ipc_interface; }; - }} +} #endif //LOGID_FEATURE_DPI_H diff --git a/src/logid/features/DeviceFeature.h b/src/logid/features/DeviceFeature.h index b5d60ee..f41bdf9 100644 --- a/src/logid/features/DeviceFeature.h +++ b/src/logid/features/DeviceFeature.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,44 +19,75 @@ #ifndef LOGID_FEATURES_DEVICEFEATURE_H #define LOGID_FEATURES_DEVICEFEATURE_H +#include #include namespace logid { class Device; -namespace features -{ - class UnsupportedFeature : public std::exception - { +} + +namespace logid::config { + struct Profile; +} + +namespace logid::features { + class UnsupportedFeature : public std::exception { public: UnsupportedFeature() = default; - virtual const char* what() const noexcept - { + + [[nodiscard]] const char* what() const noexcept override { return "Unsupported feature"; } }; - class DeviceFeature - { + template + class _featureWrapper : public T { + friend class DeviceFeature; + public: - explicit DeviceFeature(Device* dev) : _device (dev) - { + template + explicit _featureWrapper(Args... args) : T(std::forward(args)...) {} + + template + static std::shared_ptr make(Args... args) { + return std::make_shared<_featureWrapper>(std::forward(args)...); } + }; + + class DeviceFeature { + std::weak_ptr _self; + public: virtual void configure() = 0; + virtual void listen() = 0; + + virtual void setProfile(config::Profile& profile) = 0; + virtual ~DeviceFeature() = default; - class Config - { - public: - explicit Config(Device* dev) : _device (dev) - { - } - protected: - Device* _device; - }; + + DeviceFeature(const DeviceFeature&) = delete; + + DeviceFeature(DeviceFeature&&) = delete; protected: + explicit DeviceFeature(Device* dev) : _device(dev) {} + Device* _device; + + template + [[nodiscard]] std::weak_ptr self() const { + return std::dynamic_pointer_cast(_self.lock()); + } + + public: + template + static std::shared_ptr make(Args... args) { + auto feature = _featureWrapper::make(std::forward(args)...); + feature->_self = feature; + + return feature; + } }; -}} +} #endif //LOGID_FEATURES_DEVICEFEATURE_H diff --git a/src/logid/features/DeviceStatus.cpp b/src/logid/features/DeviceStatus.cpp index 7ff8a01..c9a0544 100644 --- a/src/logid/features/DeviceStatus.cpp +++ b/src/logid/features/DeviceStatus.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,56 +15,53 @@ * along with this program. If not, see . * */ -#include "DeviceStatus.h" -#include "../util/task.h" - -#define EVENTHANDLER_NAME "devicestatus" +#include +#include using namespace logid::features; using namespace logid::backend; -DeviceStatus::DeviceStatus(logid::Device *dev) : DeviceFeature(dev) -{ +DeviceStatus::DeviceStatus(logid::Device* dev) : DeviceFeature(dev) { /* This feature is redundant on receivers since the receiver * handles wakeup/sleep events. If the device is connected on a * receiver, pretend this feature is unsupported. */ - if(dev->hidpp20().deviceIndex() >= hidpp::WirelessDevice1 && - dev->hidpp20().deviceIndex() <= hidpp::WirelessDevice6) + if (dev->hidpp20().deviceIndex() >= hidpp::WirelessDevice1 && + dev->hidpp20().deviceIndex() <= hidpp::WirelessDevice6) throw UnsupportedFeature(); try { - _wireless_device_status =std::make_shared< - hidpp20::WirelessDeviceStatus>(&dev->hidpp20()); - } catch(hidpp20::UnsupportedFeature& e) { + _wireless_device_status = std::make_shared(&dev->hidpp20()); + } catch (hidpp20::UnsupportedFeature& e) { throw UnsupportedFeature(); } } -void DeviceStatus::configure() -{ +void DeviceStatus::configure() { // Do nothing } -void DeviceStatus::listen() -{ - if(_device->hidpp20().eventHandlers().find(EVENTHANDLER_NAME) == - _device->hidpp20().eventHandlers().end()) { - auto handler = std::make_shared(); - handler->condition = [index=_wireless_device_status->featureIndex()]( - const hidpp::Report& report)->bool { - return report.feature() == index && report.function() == - hidpp20::WirelessDeviceStatus::StatusBroadcast; - }; - - handler->callback = [dev=this->_device]( - const hidpp::Report& report)->void { - auto event = hidpp20::WirelessDeviceStatus::statusBroadcastEvent( - report); - if(event.reconfNeeded) - task::spawn([dev](){ dev->wakeup(); }); - }; - - _device->hidpp20().addEventHandler(EVENTHANDLER_NAME, handler); +void DeviceStatus::listen() { + if (_ev_handler.empty()) { + _ev_handler = _device->hidpp20().addEventHandler( + {[index = _wireless_device_status->featureIndex()]( + const hidpp::Report& report) -> bool { + return report.feature() == index && + report.function() == + hidpp20::WirelessDeviceStatus::StatusBroadcast; + }, + [self_weak = self()]( + const hidpp::Report& report) { + auto event = hidpp20::WirelessDeviceStatus::statusBroadcastEvent(report); + if (event.reconfNeeded) + run_task_after([self_weak]() { + if (auto self = self_weak.lock()) + self->_device->wakeup(); + }, std::chrono::milliseconds(100)); + } + }); } -} \ No newline at end of file +} + +void DeviceStatus::setProfile(config::Profile&) { +} diff --git a/src/logid/features/DeviceStatus.h b/src/logid/features/DeviceStatus.h index 985f8fd..acd98fa 100644 --- a/src/logid/features/DeviceStatus.h +++ b/src/logid/features/DeviceStatus.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,23 +19,26 @@ #define LOGID_FEATURE_DEVICESTATUS_H -#include "DeviceFeature.h" -#include "../Device.h" -#include "../backend/hidpp20/features/WirelessDeviceStatus.h" +#include +#include +#include -namespace logid { -namespace features -{ - class DeviceStatus : public DeviceFeature - { +namespace logid::features { + class DeviceStatus : public DeviceFeature { public: + void configure() final; + + void listen() final; + + void setProfile(config::Profile& profile) final; + + protected: explicit DeviceStatus(Device* dev); - virtual void configure(); - virtual void listen(); + private: - std::shared_ptr - _wireless_device_status; + EventHandlerLock _ev_handler; + std::shared_ptr _wireless_device_status; }; -}} +} #endif //LOGID_FEATURE_DEVICESTATUS_H diff --git a/src/logid/features/HiresScroll.cpp b/src/logid/features/HiresScroll.cpp index 720f994..c1c4587 100644 --- a/src/logid/features/HiresScroll.cpp +++ b/src/logid/features/HiresScroll.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,247 +15,304 @@ * along with this program. If not, see . * */ -#include "HiresScroll.h" -#include "../Device.h" -#include "../InputDevice.h" -#include "../actions/gesture/Gesture.h" -#include "../actions/gesture/AxisGesture.h" +#include +#include +#include +#include +#include +using namespace logid; using namespace logid::features; using namespace logid::backend; -#define MOVE_EVENTHANDLER_NAME "HIRES_SCROLL" +HiresScroll::HiresScroll(Device* dev) : + DeviceFeature(dev), + _config(dev->activeProfile().hiresscroll), _mode(0), + _mask(0), + _node(dev->ipcNode()->make_child("hires_scroll")), + _up_node(_node->make_child("up")), + _down_node(_node->make_child("down")) { -HiresScroll::HiresScroll(Device *dev) : DeviceFeature(dev), _config(dev) -{ try { _hires_scroll = std::make_shared(&dev->hidpp20()); - } catch(hidpp20::UnsupportedFeature& e) { + } catch (hidpp20::UnsupportedFeature& e) { throw UnsupportedFeature(); } - if(_config.upAction()) { - try { - auto up_axis = std::dynamic_pointer_cast( - _config.upAction()); - if(up_axis) - up_axis->setHiresMultiplier( - _hires_scroll->getCapabilities().multiplier); - } catch(std::bad_cast& e) { } - - _config.upAction()->press(true); - } - - if(_config.downAction()) { - try { - auto down_axis = std::dynamic_pointer_cast( - _config.downAction()); - if(down_axis) - down_axis->setHiresMultiplier( - _hires_scroll->getCapabilities().multiplier); - } catch(std::bad_cast& e) { } - - _config.downAction()->press(true); - } + _makeConfig(); _last_scroll = std::chrono::system_clock::now(); + + _ipc_interface = dev->ipcNode()->make_interface(this); } -HiresScroll::~HiresScroll() -{ - _device->hidpp20().removeEventHandler(MOVE_EVENTHANDLER_NAME); +void HiresScroll::_makeConfig() { + auto& config = _config.get(); + _mode = 0; + _mask = 0; + + if (config.has_value()) { + if (std::holds_alternative(config.value())) { + config::HiresScroll conf{}; + conf.hires = std::get(config.value()); + conf.invert = false; + conf.target = false; + _mask |= hidpp20::HiresScroll::Mode::HiRes; + config.value() = conf; + } + auto& conf = std::get(config.value()); + if (conf.hires.has_value()) { + _mask |= hidpp20::HiresScroll::Mode::HiRes; + if (conf.hires.value()) + _mode |= hidpp20::HiresScroll::Mode::HiRes; + } + if (conf.invert.has_value()) { + _mask |= hidpp20::HiresScroll::Mode::Inverted; + if (conf.invert.value()) + _mode |= hidpp20::HiresScroll::Mode::Inverted; + } + if (conf.target.has_value()) { + _mask |= hidpp20::HiresScroll::Mode::Target; + if (conf.target.value()) + _mode |= hidpp20::HiresScroll::Mode::Target; + } + + _makeGesture(_up_gesture, conf.up, "up"); + _makeGesture(_down_gesture, conf.down, "down"); + } } -void HiresScroll::configure() -{ +void HiresScroll::configure() { + std::shared_lock lock(_config_mutex); + _configure(); +} + +void HiresScroll::_configure() { auto mode = _hires_scroll->getMode(); - mode &= ~_config.getMask(); - mode |= (_config.getMode() & _config.getMask()); + mode &= ~_mask; + mode |= (_mode & _mask); _hires_scroll->setMode(mode); } -void HiresScroll::listen() -{ - if(_device->hidpp20().eventHandlers().find(MOVE_EVENTHANDLER_NAME) == - _device->hidpp20().eventHandlers().end()) { - auto handler = std::make_shared(); - handler->condition = [index=_hires_scroll->featureIndex()] - (hidpp::Report& report)->bool { - return (report.feature() == index) && (report.function() == - hidpp20::HiresScroll::WheelMovement); - }; - - handler->callback = [this](hidpp::Report& report)->void { - this->_handleScroll(_hires_scroll->wheelMovementEvent(report)); - }; - - _device->hidpp20().addEventHandler(MOVE_EVENTHANDLER_NAME, handler); +void HiresScroll::listen() { + std::shared_lock lock(_config_mutex); + if (_ev_handler.empty()) { + _ev_handler = _device->hidpp20().addEventHandler( + {[index = _hires_scroll->featureIndex()]( + const hidpp::Report& report) -> bool { + return (report.feature() == index) && + (report.function() == hidpp20::HiresScroll::WheelMovement); + }, + [self_weak = self()](const hidpp::Report& report) { + if (auto self = self_weak.lock()) + self->_handleScroll(self->_hires_scroll->wheelMovementEvent(report)); + } + }); } } -uint8_t HiresScroll::getMode() -{ +void HiresScroll::setProfile(config::Profile& profile) { + std::unique_lock lock(_config_mutex); + + _up_gesture.reset(); + _down_gesture.reset(); + _config = profile.hiresscroll; + _makeConfig(); +} + +uint8_t HiresScroll::getMode() { return _hires_scroll->getMode(); } -void HiresScroll::setMode(uint8_t mode) -{ +void HiresScroll::setMode(uint8_t mode) { _hires_scroll->setMode(mode); } -void HiresScroll::_handleScroll(hidpp20::HiresScroll::WheelStatus event) -{ +void HiresScroll::_makeGesture(std::shared_ptr& gesture, + std::optional& config, + const std::string& direction) { + if (config.has_value()) { + gesture = actions::Gesture::makeGesture(_device, config.value(), + _node->make_child(direction)); + + _fixGesture(gesture); + } else { + gesture.reset(); + } +} + +void HiresScroll::_fixGesture(const std::shared_ptr& gesture) { + try { + auto axis = std::dynamic_pointer_cast(gesture); + if (axis) + axis->setHiresMultiplier(_hires_scroll->getCapabilities().multiplier); + } catch (std::bad_cast& e) {} + if (gesture) + gesture->press(true); +} + +void HiresScroll::_handleScroll(hidpp20::HiresScroll::WheelStatus event) { + std::shared_lock lock(_config_mutex); auto now = std::chrono::system_clock::now(); - if(std::chrono::duration_cast( - now - _last_scroll).count() >= 1) { - if(_config.upAction()) { - _config.upAction()->release(); - _config.upAction()->press(true); + if (std::chrono::duration_cast(now - _last_scroll).count() >= 1) { + if (_up_gesture) { + _up_gesture->release(false); + _up_gesture->press(true); } - if(_config.downAction()) { - _config.downAction()->release(); - _config.downAction()->press(true); + if (_down_gesture) { + _down_gesture->release(false); + _down_gesture->press(true); } _last_direction = 0; } - if(event.deltaV > 0) { - if(_last_direction == -1) { - if(_config.downAction()){ - _config.downAction()->release(); - _config.downAction()->press(true); + if (event.deltaV > 0) { + if (_last_direction == -1) { + if (_down_gesture) { + _down_gesture->release(false); + _down_gesture->press(true); } } - if(_config.upAction()) - _config.upAction()->move(event.deltaV); + if (_up_gesture) + _up_gesture->move(event.deltaV); _last_direction = 1; - } else if(event.deltaV < 0) { - if(_last_direction == 1) { - if(_config.upAction()){ - _config.upAction()->release(); - _config.upAction()->press(true); + } else if (event.deltaV < 0) { + if (_last_direction == 1) { + if (_up_gesture) { + _up_gesture->release(false); + _up_gesture->press(true); } } - if(_config.downAction()) - _config.downAction()->move(-event.deltaV); + if (_down_gesture) + _down_gesture->move((int16_t) -event.deltaV); _last_direction = -1; } _last_scroll = now; } -HiresScroll::Config::Config(Device *dev) : DeviceFeature::Config(dev) -{ - try { - auto& config_root = dev->config().getSetting("hiresscroll"); - if(!config_root.isGroup()) { - logPrintf(WARN, "Line %d: hiresscroll must be a group", - config_root.getSourceLine()); - return; +HiresScroll::IPC::IPC(HiresScroll* parent) : ipcgull::interface( + SERVICE_ROOT_NAME ".HiresScroll", { + {"GetConfig", {this, &IPC::getConfig, {"hires", "invert", "target"}}}, + {"SetHires", {this, &IPC::setHires, {"hires"}}}, + {"SetInvert", {this, &IPC::setInvert, {"invert"}}}, + {"SetTarget", {this, &IPC::setTarget, {"target"}}}, + {"SetUp", {this, &IPC::setUp, {"type"}}}, + {"SetDown", {this, &IPC::setDown, {"type"}}}, + }, {}, {}), _parent(*parent) { +} + +std::tuple HiresScroll::IPC::getConfig() const { + std::shared_lock lock(_parent._config_mutex); + + auto& config = _parent._config.get(); + + if (config.has_value()) { + if (std::holds_alternative(config.value())) { + return {std::get(config.value()), false, false}; + } else { + const auto& config_obj = std::get(config.value()); + return { + config_obj.hires.value_or(true), + config_obj.invert.value_or(false), + config_obj.target.value_or(false) + }; } - _mode = 0; - _mask = 0; - try { - auto& hires = config_root.lookup("hires"); - if(hires.getType() == libconfig::Setting::TypeBoolean) { - _mask |= hidpp20::HiresScroll::Mode::HiRes; - if(hires) - _mode |= hidpp20::HiresScroll::Mode::HiRes; - } else { - logPrintf(WARN, "Line %d: hires must be a boolean", - hires.getSourceLine()); - } - } catch(libconfig::SettingNotFoundException& e) { } - - try { - auto& invert = config_root.lookup("invert"); - if(invert.getType() == libconfig::Setting::TypeBoolean) { - _mask |= hidpp20::HiresScroll::Mode::Inverted; - if(invert) - _mode |= hidpp20::HiresScroll::Mode::Inverted; - } else { - logPrintf(WARN, "Line %d: invert must be a boolean, ignoring.", - invert.getSourceLine()); - } - } catch(libconfig::SettingNotFoundException& e) { } - - try { - auto& target = config_root.lookup("target"); - if(target.getType() == libconfig::Setting::TypeBoolean) { - _mask |= hidpp20::HiresScroll::Mode::Target; - if(target) - _mode |= hidpp20::HiresScroll::Mode::Target; - } else { - logPrintf(WARN, "Line %d: target must be a boolean, ignoring.", - target.getSourceLine()); - } - } catch(libconfig::SettingNotFoundException& e) { } - - if(_mode & hidpp20::HiresScroll::Mode::Target) { - try { - auto& up = config_root.lookup("up"); - try { - auto g = actions::Gesture::makeGesture(dev, up); - if(g->wheelCompatibility()) { - _up_action = g; - } else { - logPrintf(WARN, "Line %d: This gesture cannot be used" - " as a scroll action.", - up.getSourceLine()); - } - } catch(actions::InvalidGesture& e) { - logPrintf(WARN, "Line %d: Invalid scroll action", - up.getSourceLine()); - } - } catch(libconfig::SettingNotFoundException&) { - logPrintf(WARN, "Line %d: target is true but no up action was" - " set", config_root.getSourceLine()); - } - - try { - auto& down = config_root.lookup("down"); - try { - auto g = actions::Gesture::makeGesture(dev, down); - if(g->wheelCompatibility()) { - _down_action = g; - } else { - logPrintf(WARN, "Line %d: This gesture cannot be used" - " as a scroll action.", - down.getSourceLine()); - } - } catch(actions::InvalidGesture& e) { - logPrintf(WARN, "Line %d: Invalid scroll action", - down.getSourceLine()); - } - } catch(libconfig::SettingNotFoundException&) { - logPrintf(WARN, "Line %d: target is true but no down action was" - " set", config_root.getSourceLine()); - } - } - } catch(libconfig::SettingNotFoundException& e) { - // HiresScroll not configured, use default + } else { + return {true, false, false}; } } -uint8_t HiresScroll::Config::getMode() const -{ - return _mode; +config::HiresScroll& HiresScroll::IPC::_parentConfig() { + auto& config = _parent._config.get(); + if (!config.has_value()) { + config = config::HiresScroll(); + } else if (std::holds_alternative(config.value())) { + bool hires = std::get(config.value()); + auto new_config = config::HiresScroll(); + new_config.hires = hires; + config = new_config; + } + + return std::get(config.value()); } -uint8_t HiresScroll::Config::getMask() const -{ - return _mask; +void HiresScroll::IPC::setHires(bool hires) { + std::unique_lock lock(_parent._config_mutex); + _parentConfig().hires = hires; + + _parent._mask |= hidpp20::HiresScroll::Mode::HiRes; + if (hires) + _parent._mode |= hidpp20::HiresScroll::Mode::HiRes; + else + _parent._mode &= ~hidpp20::HiresScroll::Mode::HiRes; + + _parent._configure(); } -const std::shared_ptr& - HiresScroll::Config::upAction() const -{ - return _up_action; +void HiresScroll::IPC::setInvert(bool invert) { + std::unique_lock lock(_parent._config_mutex); + _parentConfig().invert = invert; + + _parent._mask |= hidpp20::HiresScroll::Mode::Inverted; + if (invert) + _parent._mode |= hidpp20::HiresScroll::Mode::Inverted; + else + _parent._mode &= ~hidpp20::HiresScroll::Mode::Inverted; + + _parent._configure(); } -const std::shared_ptr& - HiresScroll::Config::downAction() const -{ - return _down_action; -} \ No newline at end of file +void HiresScroll::IPC::setTarget(bool target) { + std::unique_lock lock(_parent._config_mutex); + _parentConfig().target = target; + + _parent._mask |= hidpp20::HiresScroll::Mode::Target; + if (target) + _parent._mode |= hidpp20::HiresScroll::Mode::Target; + else + _parent._mode &= ~hidpp20::HiresScroll::Mode::Target; + + _parent._configure(); +} + +void HiresScroll::IPC::setUp(const std::string& type) { + std::unique_lock lock(_parent._config_mutex); + + auto& config = _parentConfig(); + + if (!config.up.has_value()) { + config.up = config::NoGesture(); + } + _parent._up_gesture = actions::Gesture::makeGesture( + _parent._device, type, config.up.value(), _parent._up_node); + if (!_parent._up_gesture->wheelCompatibility()) { + _parent._up_node.reset(); + config.up.reset(); + + throw std::invalid_argument("incompatible gesture"); + } else { + _parent._fixGesture(_parent._up_gesture); + } +} + +void HiresScroll::IPC::setDown(const std::string& type) { + std::unique_lock lock(_parent._config_mutex); + + auto& config = _parentConfig(); + + if (!config.down.has_value()) { + config.down = config::NoGesture(); + } + _parent._down_gesture = actions::Gesture::makeGesture( + _parent._device, type, config.down.value(), _parent._down_node); + if (!_parent._down_gesture->wheelCompatibility()) { + _parent._down_node.reset(); + config.down.reset(); + + throw std::invalid_argument("incompatible gesture"); + } else { + _parent._fixGesture(_parent._down_gesture); + } +} diff --git a/src/logid/features/HiresScroll.h b/src/logid/features/HiresScroll.h index 7cc2f67..29b8caa 100644 --- a/src/logid/features/HiresScroll.h +++ b/src/logid/features/HiresScroll.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,47 +18,87 @@ #ifndef LOGID_FEATURE_HIRESSCROLL_H #define LOGID_FEATURE_HIRESSCROLL_H -#include "../backend/hidpp20/features/HiresScroll.h" -#include "DeviceFeature.h" -#include "../actions/gesture/Gesture.h" +#include +#include +#include +#include +#include +#include +#include +#include -namespace logid { -namespace features -{ - class HiresScroll : public DeviceFeature - { +namespace logid::features { + class HiresScroll : public DeviceFeature { public: - explicit HiresScroll(Device* dev); - ~HiresScroll(); - virtual void configure(); - virtual void listen(); + void configure() final; + + void listen() final; + + void setProfile(config::Profile& profile) final; + + [[nodiscard]] uint8_t getMode(); - uint8_t getMode(); void setMode(uint8_t mode); - class Config : public DeviceFeature::Config - { - public: - explicit Config(Device* dev); - uint8_t getMode() const; - uint8_t getMask() const; + protected: + explicit HiresScroll(Device* dev); - const std::shared_ptr& upAction() const; - const std::shared_ptr& downAction() const; - protected: - uint8_t _mode; - uint8_t _mask; - - std::shared_ptr _up_action; - std::shared_ptr _down_action; - }; private: + void _makeConfig(); + + EventHandlerLock _ev_handler; + + void _makeGesture(std::shared_ptr& gesture, + std::optional& config, + const std::string& direction); + + void _configure(); + + void _fixGesture(const std::shared_ptr& gesture); + void _handleScroll(backend::hidpp20::HiresScroll::WheelStatus event); + + class IPC : public ipcgull::interface { + public: + explicit IPC(HiresScroll* parent); + + [[nodiscard]] std::tuple getConfig() const; + + void setHires(bool hires); + + void setInvert(bool invert); + + void setTarget(bool target); + + void setUp(const std::string& type); + + void setDown(const std::string& type); + + private: + config::HiresScroll& _parentConfig(); + + HiresScroll& _parent; + }; + std::shared_ptr _hires_scroll; std::chrono::time_point _last_scroll; int16_t _last_direction = 0; - Config _config; + + mutable std::shared_mutex _config_mutex; + std::reference_wrapper>> _config; + + uint8_t _mode; + uint8_t _mask; + + std::shared_ptr _up_gesture; + std::shared_ptr _down_gesture; + + std::shared_ptr _node; + std::shared_ptr _up_node; + std::shared_ptr _down_node; + + std::shared_ptr _ipc_interface; }; -}} +} #endif //LOGID_FEATURE_HIRESSCROLL_H diff --git a/src/logid/features/RemapButton.cpp b/src/logid/features/RemapButton.cpp index a72fb4f..d1f885e 100644 --- a/src/logid/features/RemapButton.cpp +++ b/src/logid/features/RemapButton.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 PixlOne + * Copyright 2019-2023 PixlOne * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,195 +15,302 @@ * along with this program. If not, see . * */ +#include +#include +#include #include -#include "../Device.h" -#include "RemapButton.h" -#include "../backend/hidpp20/Error.h" +#include +#include using namespace logid::features; using namespace logid::backend; using namespace logid::actions; -#define HIDPP20_REPROG_REBIND (hidpp20::ReprogControls::ChangeTemporaryDivert \ -| hidpp20::ReprogControls::ChangeRawXYDivert) +#define REPROG_FLAG(x) (control.second.flags & hidpp20::ReprogControls::x ? "YES" : "") +#define REPROG_FLAG_ADDITIONAL(x) (control.second.additionalFlags & \ + hidpp20::ReprogControls::x ? "YES" : "") -#define EVENTHANDLER_NAME "REMAP_BUTTON" +static constexpr auto hidpp20_reprog_rebind = + (hidpp20::ReprogControls::ChangeTemporaryDivert | + hidpp20::ReprogControls::ChangeRawXYDivert); -RemapButton::RemapButton(Device *dev): DeviceFeature(dev), _config (dev) -{ +RemapButton::RemapButton(Device* dev) : DeviceFeature(dev), + _config(dev->activeProfile().buttons), + _ipc_node(dev->ipcNode()->make_child("buttons")) { try { _reprog_controls = hidpp20::ReprogControls::autoVersion( &dev->hidpp20()); - } catch(hidpp20::UnsupportedFeature& e) { + } catch (hidpp20::UnsupportedFeature& e) { throw UnsupportedFeature(); } _reprog_controls->initCidMap(); - if(global_loglevel <= DEBUG) { - #define FLAG(x) control.second.flags & hidpp20::ReprogControls::x ? \ - "YES" : "" - #define ADDITIONAL_FLAG(x) control.second.additionalFlags & \ - hidpp20::ReprogControls::x ? "YES" : "" + auto& config = _config.get(); + if (!config.has_value()) + config = config::RemapButton(); + + for (const auto& control: _reprog_controls->getControls()) { + const auto i = _buttons.size(); + Button::ConfigFunction func = [this, info = control.second]( + const std::shared_ptr& action) { + hidpp20::ReprogControls::ControlInfo report{}; + report.controlID = info.controlID; + report.flags = hidpp20_reprog_rebind; + + if (action) { + if ((action->reprogFlags() & hidpp20::ReprogControls::RawXYDiverted) && + (!_reprog_controls->supportsRawXY() || + !(info.additionalFlags & hidpp20::ReprogControls::RawXY))) + logPrintf(WARN, "%s: Cannot divert raw XY movements for CID 0x%02x", + _device->name().c_str(), info.controlID); + + report.flags |= action->reprogFlags(); + } + _reprog_controls->setControlReporting(info.controlID, report); + }; + _buttons.emplace(control.second.controlID, + Button::make(control.second, (int) i, + _device, func, _ipc_node, + config.value()[control.first])); + } + + _ipc_interface = _device->ipcNode()->make_interface(this); + + if (global_loglevel <= DEBUG) { // Print CIDs, originally by zv0n - logPrintf(DEBUG, "%s:%d remappable buttons:", - dev->hidpp20().devicePath().c_str(), - dev->hidpp20().deviceIndex()); + logPrintf(DEBUG, "%s:%d remappable buttons:", + dev->hidpp20().devicePath().c_str(), + dev->hidpp20().deviceIndex()); logPrintf(DEBUG, "CID | reprog? | fn key? | mouse key? | " "gesture support?"); - for(const auto & control : _reprog_controls->getControls()) - logPrintf(DEBUG, "0x%02x | %-7s | %-7s | %-10s | %s", - control.first, FLAG(TemporaryDivertable), FLAG(FKey), - FLAG(MouseButton), ADDITIONAL_FLAG(RawXY)); - #undef FLAG + for (const auto& control: _reprog_controls->getControls()) + logPrintf(DEBUG, "0x%02x | %-7s | %-7s | %-10s | %s", + control.first, REPROG_FLAG(TemporaryDivertable), REPROG_FLAG(FKey), + REPROG_FLAG(MouseButton), REPROG_FLAG_ADDITIONAL(RawXY)); } } -RemapButton::~RemapButton() -{ - _device->hidpp20().removeEventHandler(EVENTHANDLER_NAME); +void RemapButton::configure() { + for (const auto& button: _buttons) + button.second->configure(); } -void RemapButton::configure() -{ - ///TODO: DJ reporting trickery if cannot be remapped - for(const auto& i : _config.buttons()) { - hidpp20::ReprogControls::ControlInfo info{}; - try { - info = _reprog_controls->getControlIdInfo(i.first); - } catch(hidpp20::Error& e) { - if(e.code() == hidpp20::Error::InvalidArgument) { - logPrintf(WARN, "%s: CID 0x%02x does not exist.", - _device->name().c_str(), i.first); - continue; - } - throw e; - } +void RemapButton::listen() { + if (_ev_handler.empty()) { + _ev_handler = _device->hidpp20().addEventHandler( + {[index = _reprog_controls->featureIndex()]( + const hidpp::Report& report) -> bool { + if (report.feature() == index) { + switch (report.function()) { + case hidpp20::ReprogControls::DivertedButtonEvent: + case hidpp20::ReprogControls::DivertedRawXYEvent: + return true; + default: + return false; + } + } + return false; + }, + [self_weak = self()](const hidpp::Report& report) -> void { + auto self = self_weak.lock(); + if (!self) + return; - if((i.second->reprogFlags() & hidpp20::ReprogControls::RawXYDiverted) && - (!_reprog_controls->supportsRawXY() || !(info.additionalFlags & - hidpp20::ReprogControls::RawXY))) - logPrintf(WARN, "%s: Cannot divert raw XY movements for CID " - "0x%02x", _device->name().c_str(), i.first); - - hidpp20::ReprogControls::ControlInfo report{}; - report.controlID = i.first; - report.flags = HIDPP20_REPROG_REBIND; - report.flags |= i.second->reprogFlags(); - _reprog_controls->setControlReporting(i.first, report); + switch (report.function()) { + case hidpp20::ReprogControls::DivertedButtonEvent: + self->_buttonEvent( + self->_reprog_controls->divertedButtonEvent(report)); + break; + case hidpp20::ReprogControls::DivertedRawXYEvent: { + auto divertedXY = self->_reprog_controls->divertedRawXYEvent(report); + for (const auto& button: self->_buttons) + if (button.second->pressed()) + button.second->move(divertedXY.x, divertedXY.y); + break; + } + default: + break; + } + } + }); } } -void RemapButton::listen() -{ - if(_device->hidpp20().eventHandlers().find(EVENTHANDLER_NAME) == - _device->hidpp20().eventHandlers().end()) { - auto handler = std::make_shared(); - handler->condition = [index=_reprog_controls->featureIndex()] - (hidpp::Report& report)->bool { - return (report.feature() == index) && ((report.function() == - hidpp20::ReprogControls::DivertedButtonEvent) || (report - .function() == hidpp20::ReprogControls::DivertedRawXYEvent)); - }; +void RemapButton::setProfile(config::Profile& profile) { + std::lock_guard lock(_button_lock); - handler->callback = [this](hidpp::Report& report)->void { - if(report.function() == - hidpp20::ReprogControls::DivertedButtonEvent) - this->_buttonEvent(_reprog_controls->divertedButtonEvent( - report)); - else { // RawXY - auto divertedXY = _reprog_controls->divertedRawXYEvent(report); - for(const auto& button : this->_config.buttons()) - if(button.second->pressed()) - button.second->move(divertedXY.x, divertedXY.y); - } - }; + _config = profile.buttons; + if (!_config.get().has_value()) + _config.get().emplace(); + auto& config = _config.get().value(); - _device->hidpp20().addEventHandler(EVENTHANDLER_NAME, handler); - } + for(auto& button : _buttons) + button.second->setProfile(config[button.first]); } -void RemapButton::_buttonEvent(const std::set& new_state) -{ +void RemapButton::_buttonEvent(const std::set& new_state) { // Ensure I/O doesn't occur while updating button state std::lock_guard lock(_button_lock); // Press all added buttons - for(const auto& i : new_state) { + for (const auto& i: new_state) { auto old_i = _pressed_buttons.find(i); - if(old_i != _pressed_buttons.end()) { + if (old_i != _pressed_buttons.end()) { _pressed_buttons.erase(old_i); } else { - auto action = _config.buttons().find(i); - if(action != _config.buttons().end()) + auto action = _buttons.find(i); + if (action != _buttons.end()) action->second->press(); } } // Release all removed buttons - for(auto& i : _pressed_buttons) { - auto action = _config.buttons().find(i); - if(action != _config.buttons().end()) + for (auto& i: _pressed_buttons) { + auto action = _buttons.find(i); + if (action != _buttons.end()) action->second->release(); } _pressed_buttons = new_state; } -RemapButton::Config::Config(Device *dev) : DeviceFeature::Config(dev) -{ - try { - auto& config_root = dev->config().getSetting("buttons"); - if(!config_root.isList()) { - logPrintf(WARN, "Line %d: buttons must be a list.", - config_root.getSourceLine()); - return; +namespace logid::features { + class ButtonWrapper : public Button { + public: + template + explicit ButtonWrapper(Args&& ... args) : Button(std::forward(args)...) { + } + }; +} + +std::shared_ptr