Compare commits

...

53 Commits
v0.3.0 ... main

Author SHA1 Message Date
pixl
628ab937a2
Merge pull request #476 from PixlOne/fix-cve-2024-45752
Fix CVE-2024-45752
2024-09-27 20:46:28 -04:00
pixl
9495516e0c
Fix CVE-2024-45752
Prevents arbitrary users from accessing d-bus interface. Fixes #473.
This change now requires any application using the LogiOps D-Bus
interface to run as root.
2024-09-27 20:43:01 -04:00
pixl
237fa5fbd3
Merge pull request #414 from wprzytula/fix-uint8_t-cid
fix bug: represent cid as `uint16_t`, not `uint8_t`
2024-05-27 19:55:13 -07:00
pixl
456efb4db0
Merge pull request #441 from pasanflo/patch-1
Update TESTED.md
2024-05-27 19:54:49 -07:00
pixl
d79d050bf4
Merge pull request #415 from kostadinsh/gcc-14
Add include <algorithm> to fix building with gcc 14
2024-05-27 19:50:21 -07:00
Pablo Sánchez Flores
e9f8072a0c
Update TESTED.md
I've tested my Logitech MX Master 3S with LogiOps, and I can see that it's working with the config name as shown on the PR.
2024-03-04 13:17:30 +01:00
Kostadin Shishmanov
da742af3a5
Add include <algorithm> to fix building with gcc 14
With gcc 14 some C++ Standard Library headers have been changed to no longer include other headers that were being used internally by the library. In logiops's case it is the <algorithm> header.

Downstream Gentoo bug:
https://bugs.gentoo.org/917002

GCC 14 porting guide:
https://gcc.gnu.org/gcc-14/porting_to.html

Signed-off-by: Kostadin Shishmanov <kocelfc@tutanota.com>
2023-11-07 19:10:29 +02:00
Wojciech Przytuła
700070c651 fix bug: represent cid as uint16_t
The function ReprogControlsV4::setControlReporting() erroneously took
cid as uint8_t. Because of that, reports contained only lower byte
of any cid, so any ReprogControls request for such cid resulted
in error response. Lack of error response handling in this library
led to timeout.

E.g.:
```
[DEBUG] Configuring button: 0x103
[RAWREPORT] /dev/hidraw1 OUT: 11 ff 08 32 00 03 22 01 03 00 00 00 00 00 00 00 00 00 00 00
[RAWREPORT] /dev/hidraw1 IN:  11 ff ff 08 32 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00
```
We can see only lower byte (03) of `cid` present. Thus we get an error
response (with feature id `ff`), but the logiops keeps waiting for
response with feature id `08` and hence eventually timeouts.
2023-11-07 16:22:50 +01:00
pixl
94f6dbab53
Merge pull request #399 from PixlOne/cmake-fixes
CMake fixes: NDEBUG in None build type and allow shared ipcgull library
2023-07-12 17:39:21 -04:00
pixl
f2680bdf5f
Use NDEBUG for None build type 2023-07-12 14:08:59 -04:00
pixl
d51fd5c344
Update ipcgull to allow for shared library options 2023-07-12 14:03:08 -04:00
pixl
31d1955faf
Merge pull request #390 from PixlOne/fix-iomon-locks
Resolve deadlocking when adding to IOMonitor
2023-07-12 13:59:18 -04:00
pixl
cb7a2dad7c
Resolve deadlocking when adding to IOMonitor
Do not lock run_mutex while running an I/O handler.
2023-05-20 20:55:01 -04:00
pixl
5767aac362
Merge pull request #380 from PixlOne/fix-wakeup-memleak 2023-05-18 21:15:53 -04:00
pixl
64962a8ed3
Merge pull request #378 from PixlOne/remove-dbus-check
Remove check if DBus is found
2023-05-16 15:42:28 -04:00
pixl
b4a9d4460e
Remove check if DBus is found
Broken on Ubuntu and leads to DBus policy not being installed, DBus
is required anyways right now so remove this check. Fixes #371.
2023-05-16 15:37:24 -04:00
pixl
b81f935bcd
Only create one waiter per device on receiver 2023-05-16 15:34:11 -04:00
pixl
5e32120b2c
Fix broken pipes and improve receiver handling
Retry before failing on broken pipe error (fixes Nano receiver),
and test for virtual devices without I/O.
2023-05-16 15:16:11 -04:00
pixl
be5ee9f793
Exit gracefully upon return
Stop all worker threads when main thread exits.
2023-05-15 16:38:49 -04:00
pixl
a361f206ff
Only call wakeup test once
Ensures that wakeup check does not occur multiple times and possibly
create new event handlers on every HID packet.
2023-05-15 16:38:49 -04:00
pixl
f85cd5ba62
Avoid event handler data races 2023-05-15 16:38:01 -04:00
pixl
4ae58b81a3
Use RAII for IOMonitor locks
May solve faulty IO monitor locking and solve #374.
2023-05-15 16:37:54 -04:00
pixl
30ade71edf
Merge pull request #375 from PixlOne/udev-enum-fix
Do not terminate on bad udev device
2023-05-10 22:30:04 -04:00
pixl
eb5b3ca481
Do not terminate on bad udev device
Skip the device in this case since this could occur if the device is
removed. Fixes #373
2023-05-10 22:23:10 -04:00
pixl
be840b333a
Merge pull request #324 from wooparadog/master
Set 0 buffer for stdout
2023-05-08 15:33:14 -04:00
WooParadog
99716cbd99
Set 0 buffer for stdout
So that journald can follow its logs
2023-05-08 15:29:36 -04:00
pixl
3fb18a7d5f
Merge pull request #261 from leios76/master
support smartshift(0x2111) on mx anywhere3
2023-05-08 15:12:10 -04:00
Minsoo Kim
1503a1b2ca
support smartshift(0x2111) on mx anywhere3 2023-05-08 15:02:29 -04:00
pixl
a77b328b35
Use PROJECT_SOURCE_DIR across CMakeLists 2023-05-04 01:31:54 -04:00
pixl
4e70095281
Fix building outside out of source dir 2023-05-04 01:19:51 -04:00
pixl
1d7ff5e034
Fix make-release github action 2023-05-04 01:13:04 -04:00
pixl
1267db027c
Fix build test status in README 2023-05-04 00:20:39 -04:00
pixl
bb8e0b4a78
Add GitHub Actions for build test and releases
Adds a build test for ubuntu (latest, 20.04), archlinux, and fedora
and makes a tarball including submodules.
2023-05-04 00:16:07 -04:00
pixl
a96036c97d
Fix compiler warnings on Ubuntu and code cleanup
Fixes some compiler warnings, avoids using anonymous namespaces as much
and removes manual inlining for config.
2023-05-03 21:31:05 -04:00
pixl
5e436a2bdf
Fix none gesture not working
Also fixes gestures on MX Master 3S by ignoring the first movement
event. Fixes #366 and #339.
2023-05-03 18:38:46 -04:00
pixl
5547f52cad
Do not restart logid on failure
logid is not as buggy as before and should not randomly crash.
Generally failures will repeat and the user should investigate
why it fails before restarting.
2023-05-03 17:51:14 -04:00
pixl
2815fc50f8
Update ipcgull to fix stdexcept errors
Fixes #368
2023-05-03 17:49:54 -04:00
pixl
2df4351ff8
Merge pull request #369 from PixlOne/cxx2a-fixes
Fix c++2a compatibility
2023-05-03 17:40:36 -04:00
pixl
0945fa1fe8
Fix c++2a compatibility
Adds gcc 9 support.
2023-05-03 17:35:56 -04:00
pixl
4c406c7363
Fix compiler errors and warnings on Ubuntu 20.04
Also fixes some warnings and errors seen on clang.
2023-05-03 15:14:51 -04:00
pixl
7147825539
Call libconfig read/write file with C strings
Older libconfig versions don't support calling these functions with
std::string, fixes #364.
2023-05-03 14:28:06 -04:00
pixl
c27a6edae8
Merge pull request #365 from jw910731/master
Fix #363 by changing ipcgull submodule upstream url to http protocol
2023-05-03 14:23:04 -04:00
jw910731
9b020a9f9c
Change ipcgull submodule upstream url to http protocol 2023-05-04 01:07:36 +08:00
pixl
9cf7e438cd
Merge pull request #359 from vitek/m575
Add Ergo m575 to the list of tested devices
2023-05-02 22:41:22 -04:00
pixl
e238dce6f1
Merge branch 'master' into m575 2023-05-02 22:41:08 -04:00
pixl
44e319d770
Do not throw error if config file does not exist 2023-05-02 22:09:58 -04:00
pixl
c5a9c1d0a4
Merge pull request #358 from kalenpw/master
Confirm MX Master 3 for Mac support
2023-05-02 21:16:16 -04:00
pixl
c88f8b9b53
Update TESTED.md 2023-05-02 21:16:00 -04:00
pixl
fbd915e201
Merge pull request #346 from ckiee/config-invalid-enforce
logid: Fail on invalid Configurations
2023-05-02 21:08:38 -04:00
pixl
cdca3e8312
Merge branch 'master' into config-invalid-enforce 2023-05-02 21:07:56 -04:00
Victor Makarov
0ca528a91f Add Ergo m575 to the list of tested devices 2023-04-18 08:15:03 +03:00
Kalen Williams
cbee607902
Confirm MX Master 3 for Mac support 2023-04-07 20:54:45 -06:00
ckie
5741a9a323
logid: Fail on invalid Configurations
NixOS will soon have a runtime test for logiops which uses an
invalid configuration as a baseline, expecting it to fail.

Silent errors are not nice for users; systemd does not inform users
of stderr messages in nominally running services.

https://github.com/NixOS/nixpkgs/pull/167388
2022-12-11 00:15:39 +02:00
53 changed files with 1376 additions and 980 deletions

55
.github/workflows/build-test.yml vendored Normal file
View File

@ -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)

35
.github/workflows/make-release.yml vendored Normal file
View File

@ -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

View File

@ -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/'

2
.gitmodules vendored
View File

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

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.12)
set(CMAKE_INSTALL_PREFIX /usr)
@ -6,6 +6,7 @@ project(logiops)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
set(CMAKE_CXX_FLAGS_NONE "${CMAKE_CXX_FLAGS_NONE} -DNDEBUG")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -Wall -Wextra")
set(CMAKE_CXX_STANDARD 20)
@ -13,13 +14,14 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
option(USE_USER_BUS "Uses user bus" OFF)
find_package(Git REQUIRED)
find_package(Git)
# Set version number and update submodules
if(EXISTS ${CMAKE_SOURCE_DIR}/.git)
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}
@ -29,16 +31,18 @@ if(EXISTS ${CMAKE_SOURCE_DIR}/.git)
endif()
execute_process(COMMAND ${GIT_EXECUTABLE}
submodule update --init --recursive)
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})
IF(NOT EXISTS src/ipcgull)
message(FATAL_ERROR "Missing ipcgull submodule")
endif()
endif()
IF(NOT EXISTS ${PROJECT_SOURCE_DIR}/src/ipcgull)
message(FATAL_ERROR "Missing ipcgull submodule")
endif()
if(NOT LOGIOPS_VERSION)

View File

@ -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,20 +15,20 @@ Default location for the configuration file is /etc/logid.cfg, but another can b
## Dependencies
This project requires a C++17 compiler, `cmake`, `libevdev`, `libudev`, `glib`, and `libconfig`.
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 glib2`
**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 libglib2.0`
**Debian/Ubuntu:** `sudo apt install build-essential cmake pkg-config libevdev-dev libudev-dev libconfig++-dev libglib2.0-dev`
**Fedora:** `sudo dnf install cmake libevdev-devel systemd-devel libconfig-devel gcc-c++ glib2`
**Fedora:** `sudo dnf install cmake libevdev-devel systemd-devel libconfig-devel gcc-c++ glib2-devel`
**Gentoo Linux:** `sudo emerge dev-libs/libconfig dev-libs/libevdev dev-libs/glib dev-util/cmake virtual/libudev`
**Solus:** `sudo eopkg install libevdev-devel libconfig-devel libgudev-devel glib2`
**Solus:** `sudo eopkg install cmake libevdev-devel libconfig-devel libgudev-devel glib2-devel`
**openSUSE:** `sudo zypper install cmake libevdev-devel systemd-devel libconfig-devel gcc-c++ libconfig++-devel libudev-devel glib2`
**openSUSE:** `sudo zypper install cmake libevdev-devel systemd-devel libconfig-devel gcc-c++ libconfig++-devel libudev-devel glib2-devel`
## Building

View File

@ -2,17 +2,21 @@
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 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 3S | Yes | `MX Master 3S` |
| MX Master 3 | Yes | `Wireless Mouse MX Master 3` |
| MX Master 3 for Mac | Yes | `MX Master 3 for Mac` |
| MX Master 2S | Yes | `Wireless Mouse MX Master 2S` |
| MX Master | Yes | `Wireless Mouse MX Master` |
| MX Anywhere S2 | Yes | `Wireless Mobile Mouse MX Anywhere 2S` |
| MX Anywhere 3 | Yes | `MX Anywhere 3` |
| MX Vertical | Yes | `MX Vertical Advanced Ergonomic Mouse` |
| MX Ergo | Yes | `MX Ergo Multi-Device Trackball ` |
| MX Ergo M575 | Yes | `ERGO M575 Trackball` |
| M720 | Yes | `M720 Triathlon Multi-Device Mouse` |
| M590 | Yes | `M585/M590 Multi-Device Mouse` |
| T400 | Yes | `Zone Touch Mouse T400` |
| MX Keys | Yes | `MX Keys Wireless Keyboard` |
| M500s | Yes | `Advanced Corded Mouse M500s` |

View File

@ -5,6 +5,7 @@ devices: (
{
on: true;
threshold: 30;
torque: 50;
};
hiresscroll:
{

@ -1 +1 @@
Subproject commit 3b19a11e8adf5608c9facd6a4e93d8833cbc4e3f
Subproject commit cd0f9a8cefb5b2545e163fceb249fdbcbaf666aa

View File

@ -1,8 +1,9 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.12)
project(logid)
# C++20 is only needed for string literal template parameters
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../CMake")
@ -12,7 +13,7 @@ find_package(PkgConfig REQUIRED)
add_executable(logid
logid.cpp
util/log.cpp
config/util.cpp
config/config.cpp
InputDevice.cpp
DeviceManager.cpp
Device.cpp
@ -71,7 +72,6 @@ add_executable(logid
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)
@ -110,13 +110,11 @@ 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 ()
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()
# Install DBus conf
# TODO: Is there a better way of setting the system policy directory?
set(DBUS_SYSTEM_POLICY_INSTALL_DIR "/usr/share/dbus-1/system.d")
configure_file(logiops-dbus.conf.in ${CMAKE_BINARY_DIR}/pizza.pixl.LogiOps.conf)
message(STATUS "dbus system policy will be installed at ${DBUS_SYSTEM_POLICY_INSTALL_DIR}")
install(FILES ${CMAKE_BINARY_DIR}/pizza.pixl.LogiOps.conf
DESTINATION ${DBUS_SYSTEM_POLICY_INSTALL_DIR}
COMPONENT cp)

View File

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

View File

@ -100,7 +100,6 @@ Device::Device(std::string path, backend::hidpp::DeviceIndex index,
_path(std::move(path)), _index(index),
_config(_getConfig(manager, _hidpp20->name())),
_profile_name(ipcgull::property_readable, ""),
_receiver(nullptr),
_manager(manager),
_nickname(manager),
_ipc_node(manager->devicesNode()->make_child(_nickname)),
@ -116,7 +115,6 @@ Device::Device(std::shared_ptr<backend::raw::RawDevice> raw_device,
_path(raw_device->rawPath()), _index(index),
_config(_getConfig(manager, _hidpp20->name())),
_profile_name(ipcgull::property_readable, ""),
_receiver(nullptr),
_manager(manager),
_nickname(manager),
_ipc_node(manager->devicesNode()->make_child(_nickname)),
@ -132,7 +130,6 @@ Device::Device(Receiver* receiver, hidpp::DeviceIndex index,
_path(receiver->path()), _index(index),
_config(_getConfig(manager, _hidpp20->name())),
_profile_name(ipcgull::property_readable, ""),
_receiver(receiver),
_manager(manager),
_nickname(manager),
_ipc_node(manager->devicesNode()->make_child(_nickname)),
@ -187,7 +184,6 @@ void Device::sleep() {
void Device::wakeup() {
std::lock_guard<std::mutex> lock(_state_lock);
logPrintf(INFO, "%s:%d woke up.", _path.c_str(), _index);
reconfigure();
@ -195,6 +191,8 @@ void Device::wakeup() {
_awake = true;
_ipc_interface->notifyStatus();
}
logPrintf(INFO, "%s:%d woke up.", _path.c_str(), _index);
}
void Device::reconfigure() {

View File

@ -157,7 +157,6 @@ namespace logid {
ipcgull::property<std::string> _profile_name;
std::map<std::string, config::Profile>::iterator _profile;
Receiver* _receiver;
const std::weak_ptr<DeviceManager> _manager;
void _makeResetMechanism();

View File

@ -66,11 +66,11 @@ void DeviceManager::addDevice(std::string path) {
// Check if device is ignored before continuing
{
raw::RawDevice raw_dev(path, self<DeviceManager>().lock());
auto raw_dev = raw::RawDevice::make(path, self<DeviceManager>().lock());
if (config()->ignore.has_value() &&
config()->ignore.value().contains(raw_dev.productId())) {
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;
}
}

View File

@ -21,6 +21,7 @@
#include <memory>
#include <string>
#include <mutex>
extern "C"
{

View File

@ -92,6 +92,12 @@ GestureAction::GestureAction(Device* dev, config::GestureAction& config,
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());
}
@ -122,7 +128,7 @@ void GestureAction::release() {
}
for (auto& gesture: _gestures) {
if (gesture.first == d)
if (gesture.first == d || gesture.first == None)
continue;
if (!threshold_met) {
if (gesture.second->metThreshold()) {
@ -130,21 +136,15 @@ void GestureAction::release() {
// secondary one.
threshold_met = true;
gesture.second->release(true);
break;
}
} else {
gesture.second->release(false);
}
}
if (!threshold_met) {
try {
auto none = _gestures.at(None);
if (none) {
none->press(false);
none->release(false);
}
} catch (std::out_of_range& e) {}
auto none_gesture = _gestures.find(None);
if (none_gesture != _gestures.end()) {
none_gesture->second->release(!threshold_met);
}
}
@ -239,15 +239,23 @@ void GestureAction::setGesture(const std::string& direction, const std::string&
auto dir_name = fromDirection(d);
auto& gesture = _config.gestures.value()[dir_name];
_gestures[d].reset();
try {
_gestures[d] = Gesture::makeGesture(
_device, type, _config.gestures.value()[dir_name],
_device, type, gesture,
_node->make_child(dir_name));
} catch (InvalidGesture& e) {
_gestures[d] = Gesture::makeGesture(
_device, _config.gestures.value()[dir_name],
_device, gesture,
_node->make_child(dir_name));
throw std::invalid_argument("Invalid gesture type");
}
if (d == None) {
std::visit([](auto&& x) {
x.threshold = 0;
}, gesture);
}
}

View File

@ -47,5 +47,5 @@ bool NullGesture::wheelCompatibility() const {
}
bool NullGesture::metThreshold() const {
return _axis > _config.threshold.value_or(defaults::gesture_threshold);
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
}

View File

@ -24,21 +24,64 @@
#include <mutex>
#include <shared_mutex>
#include <list>
#include <atomic>
template <class T>
class EventHandlerLock;
template <class T>
struct EventHandlerList {
typedef std::list<typename T::EventHandler> list_t;
typedef list_t::const_iterator iterator_t;
class EventHandlerList {
public:
typedef std::list<std::pair<typename T::EventHandler, std::atomic_bool>> list_t;
typedef typename list_t::iterator iterator_t;
private:
list_t list;
std::shared_mutex mutex;
std::shared_mutex add_mutex;
std::list<typename T::EventHandler> list;
mutable std::shared_mutex mutex;
void cleanup() {
std::unique_lock lock(mutex, std::try_to_lock);
if (lock.owns_lock()) {
std::list<iterator_t> to_remove;
for (auto it = list.begin(); it != list.end(); ++it) {
if (!it->second)
to_remove.push_back(it);
}
for(auto& it : to_remove)
list.erase(it);
}
}
public:
iterator_t add(typename T::EventHandler handler) {
std::unique_lock add_lock(add_mutex);
list.emplace_front(std::move(handler), true);
return list.begin();
}
void remove(iterator_t iterator) {
std::unique_lock lock(mutex);
list.erase(iterator);
std::unique_lock lock(mutex, std::try_to_lock);
if (lock.owns_lock()) {
std::unique_lock add_lock(add_mutex);
list.erase(iterator);
} else {
iterator->second = false;
}
}
template <typename Arg>
void run_all(Arg arg) {
cleanup();
std::shared_lock lock(mutex);
std::shared_lock add_lock(add_mutex);
for (auto& handler : list) {
add_lock.unlock();
if (handler.second) {
if (handler.first.condition(arg))
handler.first.callback(arg);
}
add_lock.lock();
}
}
};

View File

@ -53,7 +53,7 @@ Device::Device(const std::string& path, DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) :
io_timeout(duration_cast<milliseconds>(
duration<double, std::milli>(timeout))),
_raw_device(std::make_shared<raw::RawDevice>(path, monitor)),
_raw_device(raw::RawDevice::make(path, monitor)),
_receiver(nullptr), _path(path), _index(index) {
}
@ -114,35 +114,8 @@ void Device::_setupReportsAndInit() {
/* hid_logitech_dj creates virtual /dev/hidraw nodes for receiver
* devices. We should ignore these devices.
*/
if (_index == hidpp::DefaultDevice) {
_raw_handler = _raw_device->addEventHandler(
{[](const std::vector<uint8_t>& report) -> bool {
return (report[Offset::Type] == Report::Type::Short ||
report[Offset::Type] == Report::Type::Long);
},
[self_weak = _self](const std::vector<uint8_t>& report) -> void {
Report _report(report);
if(auto self = self_weak.lock())
self->handleEvent(_report);
}});
try {
auto rsp = sendReport({ReportType::Short, _index,
hidpp20::FeatureID::ROOT, hidpp20::Root::Ping,
hidpp::softwareID});
if (rsp.deviceIndex() != _index)
throw InvalidDevice(InvalidDevice::VirtualNode);
} catch (hidpp10::Error& e) {
if (e.deviceIndex() != _index)
throw InvalidDevice(InvalidDevice::VirtualNode);
} catch (hidpp20::Error& e) {
/* This shouldn't happen, the device must not be ready */
if (e.deviceIndex() != _index)
throw InvalidDevice(InvalidDevice::VirtualNode);
else
throw DeviceNotReady();
}
}
if (_raw_device->isSubDevice())
throw InvalidDevice(InvalidDevice::VirtualNode);
_raw_handler = _raw_device->addEventHandler(
{[index = _index](
@ -214,25 +187,21 @@ void Device::_init() {
}
EventHandlerLock<Device> Device::addEventHandler(EventHandler handler) {
std::unique_lock lock(_event_handlers->mutex);
_event_handlers->list.emplace_front(std::move(handler));
return {_event_handlers, _event_handlers->list.cbegin()};
return {_event_handlers, _event_handlers->add(std::move(handler))};
}
void Device::handleEvent(Report& report) {
if (responseReport(report))
return;
std::shared_lock lock(_event_handlers->mutex);
for (auto& handler: _event_handlers->list)
if (handler.condition(report))
handler.callback(report);
_event_handlers->run_all(report);
}
Report Device::sendReport(const Report& report) {
/* Must complete transaction before next send */
std::lock_guard send_lock(_send_mutex);
_sent_sub_id = report.subId();
_sent_address = report.address();
std::unique_lock lock(_response_mutex);
_sendReport(report);
@ -249,6 +218,7 @@ Report Device::sendReport(const Report& report) {
Response response = _response.value();
_response.reset();
_sent_sub_id.reset();
_sent_address.reset();
if (std::holds_alternative<Report>(response)) {
return std::get<Report>(response);
@ -268,20 +238,23 @@ bool Device::responseReport(const Report& report) {
std::lock_guard lock(_response_mutex);
Response response = report;
uint8_t sub_id;
uint8_t address;
Report::Hidpp10Error hidpp10_error{};
Report::Hidpp20Error hidpp20_error{};
if (report.isError10(hidpp10_error)) {
sub_id = hidpp10_error.sub_id;
address = hidpp10_error.address;
response = hidpp10_error;
} else if (report.isError20(hidpp20_error)) {
sub_id = hidpp20_error.feature_index;
response = hidpp20_error;
address = (hidpp20_error.function << 4) | (hidpp20_error.software_id & 0x0f);
} else {
sub_id = report.subId();
address = report.address();
}
if (sub_id == _sent_sub_id) {
if (sub_id == _sent_sub_id && address == _sent_address) {
_response = response;
_response_cv.notify_all();
return true;
@ -310,7 +283,7 @@ bool Device::isStable10() {
}
bool Device::isStable20() {
static constexpr std::string ping_seq = "hello";
static const std::string ping_seq = "hello";
hidpp20::Root root(this);

View File

@ -38,24 +38,25 @@ namespace logid::backend::hidpp10 {
namespace logid::backend::hidpp {
struct DeviceConnectionEvent;
namespace {
template <typename T>
class DeviceWrapper : public T {
friend class Device;
public:
template <typename... Args>
explicit DeviceWrapper(Args... args) : T(std::forward<Args>(args)...) { }
template<typename T>
class _deviceWrapper : public T {
friend class Device;
template <typename... Args>
static std::shared_ptr<T> make(Args... args) {
return std::make_shared<DeviceWrapper>(std::forward<Args>(args)...);
}
};
}
public:
template<typename... Args>
explicit _deviceWrapper(Args... args) : T(std::forward<Args>(args)...) {}
template<typename... Args>
static std::shared_ptr<T> make(Args... args) {
return std::make_shared<_deviceWrapper>(std::forward<Args>(args)...);
}
};
class Device {
template <typename T>
friend class DeviceWrapper;
template<typename T>
friend
class _deviceWrapper;
public:
struct EventHandler {
std::function<bool(Report&)> condition;
@ -102,7 +103,9 @@ namespace logid::backend::hidpp {
[[nodiscard]] const std::shared_ptr<raw::RawDevice>& rawDevice() const;
Device(const Device&) = delete;
Device(Device&&) = delete;
virtual ~Device() = default;
protected:
@ -155,22 +158,23 @@ namespace logid::backend::hidpp {
std::optional<Response> _response;
std::optional<uint8_t> _sent_sub_id{};
std::optional<uint8_t> _sent_address{};
std::shared_ptr<EventHandlerList<Device>> _event_handlers;
std::weak_ptr<Device> _self;
protected:
template <typename T, typename... Args>
template<typename T, typename... Args>
static std::shared_ptr<T> makeDerived(Args... args) {
auto device = DeviceWrapper<T>::make(std::forward<Args>(args)...);
auto device = _deviceWrapper<T>::make(std::forward<Args>(args)...);
device->_self = device;
device->_setupReportsAndInit();
return device;
}
public:
template <typename... Args>
template<typename... Args>
static std::shared_ptr<Device> make(Args... args) {
return makeDerived<Device>(std::forward<Args>(args)...);
}

View File

@ -42,7 +42,7 @@ namespace logid::backend::hidpp {
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 uint8_t noAckSoftwareID = 3;
static constexpr std::size_t ShortParamLength = 3;
static constexpr std::size_t LongParamLength = 16;

View File

@ -24,6 +24,23 @@
using namespace logid::backend;
using namespace logid::backend::hidpp10;
hidpp::Report setupRegReport(hidpp::DeviceIndex index,
uint8_t sub_id, uint8_t address,
const std::vector<uint8_t>& params) {
hidpp::Report::Type type = params.size() <= hidpp::ShortParamLength ?
hidpp::Report::Type::Short : hidpp::Report::Type::Long;
if (sub_id == SetRegisterLong) {
// When setting a long register, the report must be long.
type = hidpp::Report::Type::Long;
}
hidpp::Report request(type, index, sub_id, address);
std::copy(params.begin(), params.end(), request.paramBegin());
return request;
}
Device::Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) :
hidpp::Device(path, index, monitor, timeout) {
@ -124,22 +141,24 @@ std::vector<uint8_t> Device::setRegister(uint8_t address,
return accessRegister(sub_id, address, params);
}
void Device::setRegisterNoResponse(uint8_t address,
const std::vector<uint8_t>& params,
hidpp::Report::Type type) {
assert(params.size() <= hidpp::LongParamLength);
uint8_t sub_id = type == hidpp::Report::Type::Short ?
SetRegisterShort : SetRegisterLong;
return accessRegisterNoResponse(sub_id, address, params);
}
std::vector<uint8_t> Device::accessRegister(uint8_t sub_id, uint8_t address,
const std::vector<uint8_t>& params) {
hidpp::Report::Type type = params.size() <= hidpp::ShortParamLength ?
hidpp::Report::Type::Short : hidpp::Report::Type::Long;
if (sub_id == SetRegisterLong) {
// When setting a long register, the report must be long.
type = hidpp::Report::Type::Long;
}
hidpp::Report request(type, deviceIndex(), sub_id, address);
std::copy(params.begin(), params.end(), request.paramBegin());
auto response = sendReport(request);
auto response = sendReport(setupRegReport(deviceIndex(), sub_id, address, params));
return {response.paramBegin(), response.paramEnd()};
}
void Device::accessRegisterNoResponse(uint8_t sub_id, uint8_t address,
const std::vector<uint8_t>& params) {
sendReportNoACK(setupRegReport(deviceIndex(), sub_id, address, params));
}

View File

@ -39,6 +39,9 @@ namespace logid::backend::hidpp10 {
const std::vector<uint8_t>& params,
hidpp::Report::Type type);
void setRegisterNoResponse(uint8_t address, const std::vector<uint8_t>& params,
hidpp::Report::Type type);
protected:
Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout);
@ -53,18 +56,24 @@ namespace logid::backend::hidpp10 {
private:
typedef std::variant<hidpp::Report, hidpp::Report::Hidpp10Error> Response;
struct ResponseSlot {
std::optional<Response> response;
std::optional<uint8_t> sub_id;
void reset();
};
std::array<ResponseSlot, SubIDCount> _responses;
std::vector<uint8_t> accessRegister(
uint8_t sub_id, uint8_t address, const std::vector<uint8_t>& params);
void accessRegisterNoResponse(
uint8_t sub_id, uint8_t address, const std::vector<uint8_t>& params);
protected:
template <typename T, typename... Args>
template<typename T, typename... Args>
static std::shared_ptr<T> makeDerived(Args... args) {
auto device = hidpp::Device::makeDerived<T>(std::forward<Args>(args)...);
@ -73,8 +82,9 @@ namespace logid::backend::hidpp10 {
return device;
}
public:
template <typename... Args>
template<typename... Args>
static std::shared_ptr<Device> make(Args... args) {
return makeDerived<Device>(std::forward<Args>(args)...);
}

View File

@ -68,7 +68,7 @@ void Receiver::setNotifications(NotificationFlags flags) {
}
void Receiver::enumerate() {
setRegister(ConnectionState, {2}, hidpp::ReportType::Short);
setRegisterNoResponse(ConnectionState, {2}, hidpp::ReportType::Short);
}
///TODO: Investigate usage

View File

@ -158,28 +158,31 @@ void ReceiverMonitor::enumerate() {
}
void ReceiverMonitor::waitForDevice(hidpp::DeviceIndex index) {
auto handler_id = std::make_shared<EventHandlerLock<raw::RawDevice>>();
const std::lock_guard lock(_wait_mutex);
if (!_waiters.count(index)) {
_waiters.emplace(index, _receiver->rawDevice()->addEventHandler(
{[index](const std::vector<uint8_t>& report) -> bool {
/* Connection events should be handled by connect_ev_handler */
auto sub_id = report[Offset::SubID];
return report[Offset::DeviceIndex] == index &&
sub_id != Receiver::DeviceConnection &&
sub_id != Receiver::DeviceDisconnection;
},
[self_weak = _self, index](
[[maybe_unused]] const std::vector<uint8_t>& report) {
hidpp::DeviceConnectionEvent event{};
event.withPayload = false;
event.linkEstablished = true;
event.index = index;
event.fromTimeoutCheck = true;
*handler_id = _receiver->rawDevice()->addEventHandler(
{[index](const std::vector<uint8_t>& report) -> bool {
return report[Offset::DeviceIndex] == index;
},
[self_weak = _self, index, handler_id](
[[maybe_unused]] const std::vector<uint8_t>& report) {
hidpp::DeviceConnectionEvent event{};
event.withPayload = false;
event.linkEstablished = true;
event.index = index;
event.fromTimeoutCheck = true;
run_task([self_weak, event, handler_id]() {
*handler_id = {};
if (auto self = self_weak.lock()) {
self->_addHandler(event);
}
});
}
});
run_task([self_weak, event]() {
if (auto self = self_weak.lock())
self->_addHandler(event);
});
}
}));
}
}
std::shared_ptr<Receiver> ReceiverMonitor::receiver() const {
@ -217,6 +220,8 @@ void ReceiverMonitor::_addHandler(const hidpp::DeviceConnectionEvent& event, int
auto device_path = _receiver->devicePath();
try {
addDevice(event);
const std::lock_guard lock(_wait_mutex);
_waiters.erase(event.index);
} catch (DeviceNotReady& e) {
if (tries == max_tries) {
logPrintf(WARN, "Failed to add device %s:%d after %d tries."

View File

@ -26,20 +26,19 @@
namespace logid::backend::hidpp10 {
namespace {
template <typename T>
class ReceiverMonitorWrapper : public T {
friend class ReceiverMonitor;
public:
template <typename... Args>
explicit ReceiverMonitorWrapper(Args... args) : T(std::forward<Args>(args)...) { }
template<typename T>
class _receiverMonitorWrapper : public T {
friend class ReceiverMonitor;
template <typename... Args>
static std::shared_ptr<T> make(Args... args) {
return std::make_shared<ReceiverMonitorWrapper>(std::forward<Args>(args)...);
}
};
}
public:
template<typename... Args>
explicit _receiverMonitorWrapper(Args... args) : T(std::forward<Args>(args)...) {}
template<typename... Args>
static std::shared_ptr<T> make(Args... args) {
return std::make_shared<_receiverMonitorWrapper>(std::forward<Args>(args)...);
}
};
static constexpr int max_tries = 5;
static constexpr int ready_backoff = 250;
@ -50,7 +49,9 @@ namespace logid::backend::hidpp10 {
void enumerate();
ReceiverMonitor(const ReceiverMonitor&) = delete;
ReceiverMonitor(ReceiverMonitor&&) = delete;
protected:
ReceiverMonitor(const std::string& path,
const std::shared_ptr<raw::DeviceMonitor>& monitor,
@ -100,10 +101,13 @@ namespace logid::backend::hidpp10 {
std::weak_ptr<ReceiverMonitor> _self;
std::mutex _wait_mutex;
std::map<hidpp::DeviceIndex, EventHandlerLock<raw::RawDevice>> _waiters;
public:
template <typename T, typename... Args>
template<typename T, typename... Args>
static std::shared_ptr<T> make(Args... args) {
auto receiver_monitor = ReceiverMonitorWrapper<T>::make(std::forward<Args>(args)...);
auto receiver_monitor = _receiverMonitorWrapper<T>::make(std::forward<Args>(args)...);
receiver_monitor->_self = receiver_monitor;
receiver_monitor->_ready();
return receiver_monitor;

View File

@ -74,6 +74,7 @@ namespace logid::backend::hidpp20 {
POINTER_AXES_ORIENTATION = 0x2006,
VERTICAL_SCROLLING = 0x2100,
SMART_SHIFT = 0x2110,
SMART_SHIFT_V2 = 0x2111,
HIRES_SCROLLING = 0x2120,
HIRES_SCROLLING_V2 = 0x2121, // Referred to as Hi-res wheel in cvuchener/hidpp, seems to be V2?
LORES_SCROLLING = 0x2130,

View File

@ -40,7 +40,7 @@ namespace logid::backend::hidpp20 {
struct HostInfo {
uint8_t hostCount;
uint8_t currentHost;
[[maybe_unused]] bool enhancedHostSwitch;
bool enhancedHostSwitch;
};
HostInfo getHostInfo();

View File

@ -129,7 +129,7 @@ ReprogControls::ControlInfo ReprogControls::getControlIdInfo(uint16_t cid) {
return report;
}
void ReprogControls::setControlReporting(uint8_t cid, ControlInfo info) {
void ReprogControls::setControlReporting(uint16_t cid, ControlInfo info) {
// This function does not exist pre-v4 and cannot be emulated, ignore.
(void) cid;
(void) info; // Suppress unused warnings
@ -173,7 +173,7 @@ ReprogControls::ControlInfo ReprogControlsV4::getControlReporting(uint16_t cid)
return info;
}
void ReprogControlsV4::setControlReporting(uint8_t cid, ControlInfo info) {
void ReprogControlsV4::setControlReporting(uint16_t cid, ControlInfo info) {
std::vector<uint8_t> params(5);
params[0] = (cid >> 8) & 0xff;
params[1] = cid & 0xff;

View File

@ -100,7 +100,7 @@ namespace logid::backend::hidpp20 {
[[nodiscard]] virtual ControlInfo getControlReporting(uint16_t cid);
// Only controlId (for remap) and flags will be read
virtual void setControlReporting(uint8_t cid, ControlInfo info);
virtual void setControlReporting(uint16_t cid, ControlInfo info);
[[nodiscard]] static std::set<uint16_t> divertedButtonEvent(const hidpp::Report& report);
@ -162,7 +162,7 @@ namespace logid::backend::hidpp20 {
[[nodiscard]] ControlInfo getControlReporting(uint16_t cid) override;
void setControlReporting(uint8_t cid, ControlInfo info) override;
void setControlReporting(uint16_t cid, ControlInfo info) override;
explicit ReprogControlsV4(Device* dev);

View File

@ -19,26 +19,107 @@
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<typename T>
std::shared_ptr<T> make_smartshift(Device* dev) {
try {
return std::make_shared<T>(dev);
} catch (UnsupportedFeature& e) {
return {};
}
}
std::shared_ptr<SmartShift> SmartShift::autoVersion(Device* dev) {
if (auto v2 = make_smartshift<SmartShiftV2>(dev))
return v2;
return std::make_shared<SmartShift>(dev);
}
SmartShift::Status SmartShift::getStatus() {
std::vector<uint8_t> params(0);
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<bool>(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<uint8_t> params(0);
auto response = callFunction(GetStatus, params);
return {
.autoDisengage = response[2],
.torque = 0,
.maxForce = 0,
};
}
void SmartShift::setStatus(Status status) {
std::vector<uint8_t> params(3);
if (status.setActive)
params[0] = status.active + 1;
if (status.setAutoDisengage)
params[1] = status.autoDisengage;
if (status.setDefaultAutoDisengage)
params[2] = status.defaultAutoDisengage;
callFunction(SetStatus, params);
}
}
SmartShift::Defaults SmartShiftV2::getDefaults() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetCapabilities, params);
return {
.autoDisengage = response[1],
.torque = response[2],
.maxForce = response[3],
};
}
SmartShift::Status SmartShiftV2::getStatus() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetStatus, params);
return {
.active = static_cast<bool>(response[0] - 1),
.autoDisengage = response[1],
.torque = response[2],
.setActive = false, .setAutoDisengage = false, .setTorque = false,
};
}
void SmartShiftV2::setStatus(Status status) {
std::vector<uint8_t> params(3);
if (status.setActive)
params[0] = status.active + 1;
if (status.setAutoDisengage)
params[1] = status.autoDisengage;
if (status.setTorque)
params[2] = status.torque;
callFunction(SetStatus, params);
}
bool SmartShiftV2::supportsTorque() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetCapabilities, params);
return static_cast<bool>(response[0] & 1);
}

View File

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

View File

@ -89,23 +89,25 @@ void DeviceMonitor::ready() {
_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);
[self_weak = _self]() {
if (auto self = self_weak.lock()) {
struct udev_device* device = udev_monitor_receive_device(self->_udev_monitor);
std::string action = udev_device_get_action(device);
std::string dev_node = udev_device_get_devnode(device);
if (action == "add")
run_task([self_weak = _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);
});
if (action == "add")
run_task([self_weak, dev_node]() {
if (auto self = self_weak.lock())
self->_addHandler(dev_node);
});
else if (action == "remove")
run_task([self_weak, dev_node]() {
if (auto self = self_weak.lock())
self->_removeHandler(dev_node);
});
udev_device_unref(device);
udev_device_unref(device);
}
},
[]() {
throw std::runtime_error("udev hangup");
@ -135,13 +137,17 @@ void DeviceMonitor::enumerate() {
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");
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 dev_node = udev_device_get_devnode(device);
udev_device_unref(device);
_addHandler(dev_node);
_addHandler(dev_node);
} else {
udev_device_unref(device);
}
}
}
udev_enumerate_unref(udev_enum);

View File

@ -36,21 +36,19 @@ namespace logid::backend::raw {
static constexpr int max_tries = 5;
static constexpr int ready_backoff = 500;
namespace {
template<typename T>
class DeviceMonitorWrapper : public T {
friend class Device;
template<typename T>
class _deviceMonitorWrapper : public T {
friend class Device;
public:
template<typename... Args>
explicit DeviceMonitorWrapper(Args... args) : T(std::forward<Args>(args)...) {}
public:
template<typename... Args>
explicit _deviceMonitorWrapper(Args... args) : T(std::forward<Args>(args)...) {}
template<typename... Args>
static std::shared_ptr<T> make(Args... args) {
return std::make_shared<DeviceMonitorWrapper>(std::forward<Args>(args)...);
}
};
}
template<typename... Args>
static std::shared_ptr<T> make(Args... args) {
return std::make_shared<_deviceMonitorWrapper>(std::forward<Args>(args)...);
}
};
class DeviceMonitor {
public:
@ -62,7 +60,7 @@ namespace logid::backend::raw {
template<typename T, typename... Args>
static std::shared_ptr<T> make(Args... args) {
auto device_monitor = DeviceMonitorWrapper<T>::make(std::forward<Args>(args)...);
auto device_monitor = _deviceMonitorWrapper<T>::make(std::forward<Args>(args)...);
device_monitor->_self = device_monitor;
device_monitor->ready();
@ -79,7 +77,7 @@ namespace logid::backend::raw {
virtual void removeDevice(std::string device) = 0;
template <typename T>
template<typename T>
[[nodiscard]] std::weak_ptr<T> self() const {
return std::dynamic_pointer_cast<T>(_self.lock());
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2022 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,7 +16,8 @@
*
*/
#include <backend/raw/IOMonitor.h>
#include <cassert>
#include <util/log.h>
#include <optional>
extern "C"
{
@ -56,12 +57,7 @@ IOMonitor::IOMonitor() : _epoll_fd(epoll_create1(0)),
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");
}));
_fds.emplace(_event_fd, nullptr);
_io_thread = std::make_unique<std::thread>([this]() {
_listen();
@ -69,120 +65,103 @@ IOMonitor::IOMonitor() : _epoll_fd(epoll_create1(0)),
}
IOMonitor::~IOMonitor() noexcept {
std::lock_guard<std::mutex> ctl_lock(_ctl_lock);
_stop();
if (_event_fd >= 0)
close(_event_fd);
::close(_event_fd);
if (_epoll_fd >= 0)
close(_epoll_fd);
::close(_epoll_fd);
}
void IOMonitor::_listen() {
std::lock_guard<std::mutex> run_lock(_run_lock);
std::unique_lock lock(_run_mutex);
std::vector<struct epoll_event> events;
if (_is_running)
throw std::runtime_error("IOMonitor double run");
_is_running = true;
while (_is_running) {
if (_interrupting) {
std::unique_lock<std::mutex> lock(_interrupt_mutex);
_interrupt_cv.wait(lock, [this]() {
return !(bool) _interrupting;
});
if (!_is_running)
break;
}
std::lock_guard<std::mutex> 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();
std::shared_ptr<IOHandler> handler;
if (events[i].data.fd == _event_fd) {
if (events[i].events & EPOLLIN) {
lock.unlock();
/* Wait until done yielding */
const std::lock_guard yield_lock(_yield_mutex);
uint64_t event;
while (-1 != ::eventfd_read(_event_fd, &event)) { }
lock.lock();
}
} else {
try {
handler = _fds.at(events[i].data.fd);
} catch (std::out_of_range& e) {
continue;
}
lock.unlock();
try {
if (events[i].events & EPOLLIN)
handler->read();
if (events[i].events & EPOLLHUP)
handler->hangup();
if (events[i].events & EPOLLERR)
handler->error();
} catch (std::exception& e) {
logPrintf(ERROR, "Unhandled I/O handler error: %s", e.what());
}
lock.lock();
}
}
}
}
void IOMonitor::_stop() noexcept {
_interrupt();
_is_running = false;
_continue();
_yield();
_io_thread->join();
}
[[maybe_unused]]
bool IOMonitor::_running() const {
std::unique_lock<std::mutex> run_lock(_run_lock, std::try_to_lock);
return !run_lock.owns_lock() || _is_running;
std::unique_lock<std::mutex> IOMonitor::_yield() noexcept {
/* Prevent listener thread from grabbing lock during yielding */
std::unique_lock yield_lock(_yield_mutex);
std::unique_lock run_lock(_run_mutex, std::try_to_lock);
if (!run_lock.owns_lock()) {
::eventfd_write(_event_fd, 1);
run_lock = std::unique_lock<std::mutex>(_run_mutex);
}
return run_lock;
}
void IOMonitor::add(int fd, IOHandler handler) {
std::lock_guard<std::mutex> lock(_ctl_lock);
_interrupt();
const auto lock = _yield();
struct epoll_event event{};
event.events = EPOLLIN | EPOLLHUP | EPOLLERR;
event.data.fd = fd;
// TODO: EPOLL_CTL_MOD
if (_fds.contains(fd)) {
_continue();
if (!_fds.contains(fd)) {
if (::epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, fd, &event))
throw std::system_error(errno, std::generic_category());
_fds.emplace(fd, std::make_shared<IOHandler>(std::move(handler)));
} else {
throw std::runtime_error("duplicate io fd");
}
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<std::mutex> lock(_ctl_lock);
_interrupt();
std::lock_guard<std::mutex> io_lock(_io_lock);
const auto lock = _yield();
::epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, fd, nullptr);
_fds.erase(fd);
_continue();
}
void IOMonitor::_interrupt() noexcept {
std::unique_lock<std::mutex> 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<std::mutex> io_lock(_io_lock);
}
void IOMonitor::_continue() noexcept {
std::unique_lock<std::mutex> 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();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2022 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,8 +21,10 @@
#include <atomic>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <condition_variable>
#include <thread>
namespace logid::backend::raw {
struct IOHandler {
@ -39,33 +41,31 @@ namespace logid::backend::raw {
public:
IOMonitor();
IOMonitor(IOMonitor&&) = delete;
IOMonitor(const IOMonitor&) = delete;
IOMonitor& operator=(IOMonitor&&) = delete;
IOMonitor& operator=(const IOMonitor&) = delete;
~IOMonitor() noexcept;
void add(int fd, IOHandler handler);
void remove(int fd) noexcept;
private:
void _listen(); // This is a blocking call
void _stop() noexcept;
[[maybe_unused]]
[[nodiscard]] bool _running() const;
void _interrupt() noexcept;
void _continue() noexcept;
std::unique_lock<std::mutex> _yield() noexcept;
std::unique_ptr<std::thread> _io_thread;
std::map<int, IOHandler> _fds;
std::mutex _io_lock, _ctl_lock;
mutable std::mutex _run_lock;
std::atomic_bool _is_running;
std::mutex _run_mutex;
std::mutex _yield_mutex;
std::atomic_bool _interrupting;
std::mutex _interrupt_mutex;
std::condition_variable _interrupt_cv;
std::map<int, std::shared_ptr<IOHandler>> _fds;
std::atomic_bool _is_running;
const int _epoll_fd;
const int _event_fd;

View File

@ -24,6 +24,7 @@
#include <string>
#include <system_error>
#include <utility>
#include <regex>
extern "C"
{
@ -32,12 +33,17 @@ extern "C"
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/hidraw.h>
#include <linux/input.h>
}
using namespace logid::backend::raw;
using namespace logid::backend;
using namespace std::chrono;
static constexpr int max_write_tries = 8;
static const std::regex virtual_path_regex(R"~((.*\/)(.*:)([0-9]+))~");
int get_fd(const std::string& path) {
int fd = ::open(path.c_str(), O_RDWR | O_NONBLOCK);
if (fd == -1)
@ -56,7 +62,37 @@ RawDevice::dev_info get_dev_info(int fd) {
"RawDevice HIDIOCGRAWINFO failed");
}
return {dev_info.vendor, dev_info.product};
RawDevice::BusType type = RawDevice::OtherBus;
switch (dev_info.bustype) {
case BUS_USB:
type = RawDevice::USB;
break;
case BUS_BLUETOOTH:
type = RawDevice::Bluetooth;
break;
default:;
}
return {
.vid = dev_info.vendor,
.pid = dev_info.product,
.bus_type = type
};
}
std::string get_phys(int fd) {
ssize_t len;
char buf[256];
if (-1 == (len = ::ioctl(fd, HIDIOCGRAWPHYS(sizeof(buf)), buf))) {
int err = errno;
::close(fd);
throw std::system_error(err, std::system_category(),
"RawDevice HIDIOCGRAWPHYS failed");
}
return {buf, static_cast<size_t>(len) - 1};
}
std::string get_name(int fd) {
@ -68,7 +104,7 @@ std::string get_name(int fd) {
throw std::system_error(err, std::system_category(),
"RawDevice HIDIOCGRAWNAME failed");
}
return {name_buf, static_cast<size_t>(len)};
return {name_buf, static_cast<size_t>(len) - 1};
}
RawDevice::RawDevice(std::string path, const std::shared_ptr<DeviceMonitor>& monitor) :
@ -76,10 +112,27 @@ RawDevice::RawDevice(std::string path, const std::shared_ptr<DeviceMonitor>& mon
_dev_info(get_dev_info(_fd)), _name(get_name(_fd)),
_report_desc(getReportDescriptor(_fd)), _io_monitor(monitor->ioMonitor()),
_event_handlers(std::make_shared<EventHandlerList<RawDevice>>()) {
if (busType() == USB) {
auto phys = get_phys(_fd);
_sub_device = std::regex_match(phys, virtual_path_regex);
}
}
void RawDevice::_ready() {
_io_monitor->add(_fd, {
[this]() { _readReports(); },
[this]() { _valid = false; },
[this]() { _valid = false; }
[self_weak = _self]() {
if (auto self = self_weak.lock())
self->_readReports();
},
[self_weak = _self]() {
if (auto self = self_weak.lock())
self->_valid = false;
},
[self_weak = _self]() {
if (auto self = self_weak.lock())
self->_valid = false;
}
});
}
@ -105,6 +158,14 @@ int16_t RawDevice::productId() const {
return _dev_info.pid;
}
RawDevice::BusType RawDevice::busType() const {
return _dev_info.bus_type;
}
bool RawDevice::isSubDevice() const {
return _sub_device;
}
std::vector<uint8_t> RawDevice::getReportDescriptor(const std::string& path) {
int fd = ::open(path.c_str(), O_RDWR | O_NONBLOCK);
if (fd == -1)
@ -150,15 +211,17 @@ void RawDevice::sendReport(const std::vector<uint8_t>& report) {
printf("\n");
}
if (write(_fd, report.data(), report.size()) == -1)
throw std::system_error(errno, std::system_category(),
"sendReport write failed");
for (int i = 0; i < max_write_tries && write(_fd, report.data(), report.size()) == -1; ++i) {
auto err = errno;
if (err != EPIPE)
throw std::system_error(err, std::system_category(),
"sendReport write failed");
}
}
EventHandlerLock<RawDevice> RawDevice::addEventHandler(RawEventHandler handler) {
std::unique_lock<std::shared_mutex> lock(_event_handlers->mutex);
_event_handlers->list.emplace_front(std::move(handler));
return {_event_handlers, _event_handlers->list.cbegin()};
return {_event_handlers, _event_handlers->add(std::forward<RawEventHandler>(handler))};
}
void RawDevice::_readReports() {
@ -181,8 +244,5 @@ void RawDevice::_readReports() {
}
void RawDevice::_handleEvent(const std::vector<uint8_t>& report) {
std::shared_lock<std::shared_mutex> lock(_event_handlers->mutex);
for (auto& handler : _event_handlers->list)
if (handler.condition(report))
handler.callback(report);
_event_handlers->run_all(report);
}

View File

@ -34,17 +34,40 @@ namespace logid::backend::raw {
class IOMonitor;
template <typename T>
class RawDeviceWrapper : public T {
public:
template <typename... Args>
RawDeviceWrapper(Args... args) : T(std::forward<Args>(args)...) { }
};
class RawDevice {
template <typename>
friend class RawDeviceWrapper;
public:
static constexpr int max_data_length = 32;
typedef RawEventHandler EventHandler;
enum BusType {
USB,
Bluetooth,
OtherBus
};
struct dev_info {
int16_t vid;
int16_t pid;
BusType bus_type;
};
RawDevice(std::string path, const std::shared_ptr<DeviceMonitor>& monitor);
template <typename... Args>
static std::shared_ptr<RawDevice> make(Args... args) {
auto raw_dev = std::make_shared<RawDeviceWrapper<RawDevice>>(
std::forward<Args>(args)...);
raw_dev->_self = raw_dev;
raw_dev->_ready();
return raw_dev;
}
~RawDevice() noexcept;
@ -57,6 +80,10 @@ namespace logid::backend::raw {
[[nodiscard]] int16_t productId() const;
[[nodiscard]] BusType busType() const;
[[nodiscard]] bool isSubDevice() const;
static std::vector<uint8_t> getReportDescriptor(const std::string& path);
static std::vector<uint8_t> getReportDescriptor(int fd);
@ -68,6 +95,10 @@ namespace logid::backend::raw {
[[nodiscard]] EventHandlerLock<RawDevice> addEventHandler(RawEventHandler handler);
private:
RawDevice(std::string path, const std::shared_ptr<DeviceMonitor>& monitor);
void _ready();
void _readReports();
std::atomic_bool _valid;
@ -80,6 +111,10 @@ namespace logid::backend::raw {
std::shared_ptr<IOMonitor> _io_monitor;
std::weak_ptr<RawDevice> _self;
bool _sub_device = false;
std::shared_ptr<EventHandlerList<RawDevice>> _event_handlers;
void _handleEvent(const std::vector<uint8_t>& report);

View File

@ -16,11 +16,15 @@
*
*/
#include <config/types.h>
#include <config/schema.h>
#include <util/log.h>
using namespace logid;
const char config::keys::name[] = "name";
const char config::keys::cid[] = "cid";
const char config::keys::direction[] = "direction";
void config::logError(const libconfig::Setting& setting, std::exception& e) {
logPrintf(WARN, "Error at line %d: %s", setting.getSourceLine(), e.what());
}

View File

@ -22,6 +22,7 @@
#include <type_traits>
#include <functional>
#include <utility>
#include <algorithm>
namespace logid::config {
template<typename T>
@ -38,55 +39,45 @@ namespace logid::config {
template<typename T>
void append(libconfig::Setting& list, const T& t);
namespace {
template<typename T, typename... M>
struct group_io {
};
template<typename T, typename... M>
struct group_io {
};
template<typename T>
struct group_io<T> {
static inline void get(
[[maybe_unused]] const libconfig::Setting& s,
[[maybe_unused]] T* t,
[[maybe_unused]] const std::vector<std::string>& names,
[[maybe_unused]] const std::size_t index) {}
template<typename T>
struct group_io<T> {
static void get(const libconfig::Setting&, T*,
const std::vector<std::string>&, const std::size_t) {}
static inline void set(
[[maybe_unused]] libconfig::Setting& s,
[[maybe_unused]] const T* t,
[[maybe_unused]] const std::vector<std::string>& names,
[[maybe_unused]] const std::size_t index) {}
};
static void set(libconfig::Setting&, const T*,
const std::vector<std::string>&, const std::size_t) {}
};
template<typename T, typename A, typename... M>
struct group_io<T, A, M...> {
static inline void get(
const libconfig::Setting& s, T* t,
const std::vector<std::string>& names,
const std::size_t index, A T::* arg, M T::*... rest) {
auto& x = t->*(arg);
A old{x};
try {
x = config::get<A>(s, names[index]);
group_io<T, M...>::get(s, t, names, index + 1, rest...);
} catch (libconfig::SettingTypeException& e) {
x = old;
throw;
} catch (libconfig::SettingException& e) {
x = old;
throw libconfig::SettingTypeException(s);
}
template<typename T, typename A, typename... M>
struct group_io<T, A, M...> {
static void get(const libconfig::Setting& s, T* t,
const std::vector<std::string>& names,
const std::size_t index, A T::* arg, M T::*... rest) {
auto& x = t->*(arg);
A old{x};
try {
x = config::get<A>(s, names[index]);
group_io<T, M...>::get(s, t, names, index + 1, rest...);
} catch (libconfig::SettingTypeException& e) {
x = old;
throw;
} catch (libconfig::SettingException& e) {
x = old;
throw libconfig::SettingTypeException(s);
}
}
static inline void set(
libconfig::Setting& s, const T* t,
const std::vector<std::string>& names,
const std::size_t index, A T::* arg, M T::*... rest) {
config::set(s, names[index], t->*(arg));
group_io<T, M...>::set(s, t, names, index + 1, rest...);
}
};
}
static void set(libconfig::Setting& s, const T* t,
const std::vector<std::string>& names,
const std::size_t index, A T::* arg, M T::*... rest) {
config::set(s, names[index], t->*(arg));
group_io<T, M...>::set(s, t, names, index + 1, rest...);
}
};
template<typename Sign>
struct signed_group;
@ -149,22 +140,19 @@ namespace logid::config {
}
};
namespace {
template<typename T>
struct normalize_signature {
static inline const T& make(const T& ret) { return ret; }
};
template<typename T>
struct normalize_signature {
static const T& make(const T& ret) { return ret; }
};
template<>
struct normalize_signature<std::string> {
static inline std::string make(const std::string& data) {
std::string ret = data;
std::transform(ret.begin(), ret.end(),
ret.begin(), ::tolower);
return ret;
}
};
}
template<>
struct normalize_signature<std::string> {
static std::string make(const std::string& data) {
std::string ret = data;
std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower);
return ret;
}
};
template<typename Sign>
struct signed_group : public group {

View File

@ -24,13 +24,11 @@
#include <utility>
namespace logid::config {
template<size_t N>
struct string_literal {
constexpr string_literal(const char (& str)[N]) {
std::copy_n(str, N, value);
}
struct string_literal { };
char value[N]{};
template<const char* str>
struct string_literal_of : public string_literal {
constexpr static const char* value = str;
};
template<class T>
@ -46,10 +44,12 @@ namespace logid::config {
};
// Warning: map must be a variant of groups or a group
template<typename K, typename V, string_literal KeyName,
template<typename K, typename V, typename KeyName,
typename Compare=typename std::map<K, V>::key_compare,
typename Allocator=typename std::map<K, V>::allocator_type>
class map : public std::map<K, V, Compare, Allocator> {
static_assert(std::is_base_of<string_literal, KeyName>::value,
"KeyName must be a string_literal");
public:
template<typename... Args>
explicit map(Args... args) :

View File

@ -51,6 +51,11 @@ namespace logid::actions {
}
namespace logid::config {
struct keys {
static const char name[];
static const char cid[];
static const char direction[];
};
struct NoAction : public signed_group<std::string> {
typedef actions::NullAction action;
@ -213,7 +218,7 @@ namespace logid::config {
struct GestureAction : public signed_group<std::string> {
typedef actions::GestureAction action;
std::optional<map<std::string, Gesture, "direction",
std::optional<map<std::string, Gesture, string_literal_of<keys::direction>,
less_caseless<std::string>>> gestures;
GestureAction() : signed_group<std::string>(
@ -244,9 +249,10 @@ namespace logid::config {
struct SmartShift : public group {
std::optional<bool> on;
std::optional<unsigned int> threshold;
std::optional<unsigned int> torque;
SmartShift() : group({"on", "threshold"},
&SmartShift::on, &SmartShift::threshold) {}
SmartShift() : group({"on", "threshold", "torque"},
&SmartShift::on, &SmartShift::threshold, &SmartShift::torque) {}
};
@ -284,7 +290,7 @@ namespace logid::config {
&ThumbWheel::tap) {}
};
typedef map<uint16_t, Button, "cid"> RemapButton;
typedef map<uint16_t, Button, string_literal_of<keys::cid>> RemapButton;
struct Profile : public group {
std::optional<DPI> dpi;
@ -302,7 +308,7 @@ namespace logid::config {
struct Device : public group {
ipcgull::property<std::string> default_profile;
map<std::string, Profile, "name"> profiles;
map<std::string, Profile, string_literal_of<keys::name>> profiles;
Device() : group({"default_profile", "profiles"},
&Device::default_profile,
@ -313,7 +319,7 @@ namespace logid::config {
struct Config : public group {
std::optional<map<std::string,
std::variant<Device, Profile>, "name">> devices;
std::variant<Device, Profile>, string_literal_of<keys::name>>> devices;
std::optional<std::set<uint16_t>> ignore;
std::optional<double> io_timeout;
std::optional<int> workers;

View File

@ -33,416 +33,402 @@
namespace logid::config {
void logError(const libconfig::Setting& setting, std::exception& e);
namespace {
template<typename T>
struct config_io {
static_assert(std::is_base_of<group, T>::value);
template<typename T>
struct config_io {
static_assert(std::is_base_of<group, T>::value);
static inline 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& 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 inline T get(const libconfig::Setting& setting) {
T t{};
t._load(setting);
return t;
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<typename T>
struct config_io<ipcgull::property<T>> : public config_io<T> {
};
template<typename T, libconfig::Setting::Type TypeEnum>
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 inline 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) {
setting = t;
}
static void append(libconfig::Setting& list, const T& t) {
auto& x = list.add(TypeEnum);
set(x, t);
}
};
template<typename T, typename O, libconfig::Setting::Type TypeEnum>
struct reinterpret_io {
static T get(const libconfig::Setting& parent,
const std::string& name) {
return static_cast<T>(primitive_io<O, TypeEnum>::get(parent, name));
}
static T get(const libconfig::Setting& setting) {
return static_cast<T>(primitive_io<O, TypeEnum>::get(setting));
}
static void set(libconfig::Setting& parent,
const std::string& name,
const T& t) {
primitive_io<O, TypeEnum>::set(parent, name,
static_cast<O>(t));
}
static void set(libconfig::Setting& setting, const T& t) {
primitive_io<O, TypeEnum>::set(setting,
static_cast<O>(t));
}
[[maybe_unused]]
static void append(libconfig::Setting& list, const T& t) {
primitive_io<O, TypeEnum>::append(list,
static_cast<O>(t));
}
};
template<>
struct config_io<bool> : public primitive_io<bool,
libconfig::Setting::TypeBoolean> {
};
template<>
struct config_io<int8_t> : public reinterpret_io<int8_t, int,
libconfig::Setting::TypeInt> {
};
template<>
struct config_io<uint8_t> : public reinterpret_io<uint8_t, int,
libconfig::Setting::TypeInt> {
};
template<>
struct config_io<short> : public reinterpret_io<short, int,
libconfig::Setting::TypeInt> {
};
template<>
struct config_io<unsigned short> : public reinterpret_io<unsigned short,
int, libconfig::Setting::TypeInt> {
};
template<>
struct config_io<int> : public primitive_io<int,
libconfig::Setting::TypeInt> {
};
template<>
struct config_io<unsigned int> : public reinterpret_io<unsigned int,
int, libconfig::Setting::TypeInt> {
};
template<>
struct config_io<long> : public reinterpret_io<long, long long,
libconfig::Setting::TypeInt64> {
};
template<>
struct config_io<unsigned long> : public reinterpret_io<unsigned long,
long long, libconfig::Setting::TypeInt64> {
};
template<>
struct config_io<long long> : public primitive_io<long long,
libconfig::Setting::TypeInt64> {
};
template<>
struct config_io<unsigned long long> :
public reinterpret_io<unsigned long long, long long,
libconfig::Setting::TypeInt64> {
};
template<>
struct config_io<float> : public primitive_io<float,
libconfig::Setting::TypeFloat> {
};
template<>
struct config_io<double> : public primitive_io<double,
libconfig::Setting::TypeFloat> {
};
template<>
struct config_io<std::string> : public primitive_io<std::string,
libconfig::Setting::TypeString> {
};
template<typename... T>
struct config_io<std::variant<T...>> {
private:
template<typename Singleton>
static std::variant<T...> try_each(const libconfig::Setting& setting) {
return config_io<Singleton>::get(setting);
}
template<typename First, typename Next, typename... Rest>
static std::variant<T...> try_each(const libconfig::Setting& setting) {
try {
return config_io<First>::get(setting);
} catch (libconfig::SettingException& e) {
return try_each<Next, Rest...>(setting);
}
}
static inline void set(libconfig::Setting& setting, const T& t) {
t._save(setting);
}
public:
static std::variant<T...> get(const libconfig::Setting& setting) {
return try_each<T...>(setting);
}
static inline void append(libconfig::Setting& list, const T& t) {
auto& x = list.add(libconfig::Setting::TypeGroup);
set(x, t);
}
};
static std::variant<T...> get(const libconfig::Setting& parent,
const std::string& name) {
return get(parent.lookup(name));
}
template<typename T>
struct config_io<ipcgull::property<T>> : public config_io<T> {
};
static void set(libconfig::Setting& setting,
const std::variant<T...>& t) {
std::visit([&setting](auto&& arg) {
config::set(setting, arg);
}, t);
}
template<typename T, libconfig::Setting::Type TypeEnum>
struct primitive_io {
static inline T get(const libconfig::Setting& parent,
const std::string& name) {
return parent.lookup(name);
}
static void set(libconfig::Setting& parent,
const std::string& name,
const std::variant<T...>& t) {
std::visit([&parent, &name](auto&& arg) {
config::set(parent, name, arg);
}, t);
}
static inline T get(const libconfig::Setting& setting) {
return setting;
}
[[maybe_unused]]
static void append(libconfig::Setting& list, const std::variant<T...>& t) {
std::visit([&list](auto&& arg) {
config::append(list, arg);
}, t);
}
};
static inline 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 inline void set(libconfig::Setting& setting, const T& t) {
setting = t;
}
static inline void append(libconfig::Setting& list, const T& t) {
auto& x = list.add(TypeEnum);
set(x, t);
}
};
template<typename T, typename O, libconfig::Setting::Type TypeEnum>
struct reinterpret_io {
static inline T get(const libconfig::Setting& parent,
const std::string& name) {
return static_cast<T>(primitive_io<O, TypeEnum>::get(parent, name));
}
static inline T get(const libconfig::Setting& setting) {
return static_cast<T>(primitive_io<O, TypeEnum>::get(setting));
}
static inline void set(libconfig::Setting& parent,
const std::string& name,
const T& t) {
primitive_io<O, TypeEnum>::set(parent, name,
static_cast<O>(t));
}
static inline void set(libconfig::Setting& setting, const T& t) {
primitive_io<O, TypeEnum>::set(setting,
static_cast<O>(t));
}
[[maybe_unused]]
static inline void append(libconfig::Setting& list, const T& t) {
primitive_io<O, TypeEnum>::append(list,
static_cast<O>(t));
}
};
template<>
struct config_io<bool> : public primitive_io<bool,
libconfig::Setting::TypeBoolean> {
};
template<>
struct config_io<int8_t> : public reinterpret_io<int8_t, int,
libconfig::Setting::TypeInt> {
};
template<>
struct config_io<uint8_t> : public reinterpret_io<uint8_t, int,
libconfig::Setting::TypeInt> {
};
template<>
struct config_io<short> : public reinterpret_io<short, int,
libconfig::Setting::TypeInt> {
};
template<>
struct config_io<unsigned short> : public reinterpret_io<unsigned short,
int, libconfig::Setting::TypeInt> {
};
template<>
struct config_io<int> : public primitive_io<int,
libconfig::Setting::TypeInt> {
};
template<>
struct config_io<unsigned int> : public reinterpret_io<unsigned int,
int, libconfig::Setting::TypeInt> {
};
template<>
struct config_io<long> : public reinterpret_io<long, long long,
libconfig::Setting::TypeInt64> {
};
template<>
struct config_io<unsigned long> : public reinterpret_io<unsigned long,
long long, libconfig::Setting::TypeInt64> {
};
template<>
struct config_io<long long> : public primitive_io<long long,
libconfig::Setting::TypeInt64> {
};
template<>
struct config_io<unsigned long long> :
public reinterpret_io<unsigned long long, long long,
libconfig::Setting::TypeInt64> {
};
template<>
struct config_io<float> : public primitive_io<float,
libconfig::Setting::TypeFloat> {
};
template<>
struct config_io<double> : public primitive_io<double,
libconfig::Setting::TypeFloat> {
};
template<>
struct config_io<std::string> : public primitive_io<std::string,
libconfig::Setting::TypeString> {
};
template<typename... T>
struct config_io<std::variant<T...>> {
private:
template<typename Singleton>
static inline std::variant<T...> try_each(
const libconfig::Setting& setting) {
return config_io<Singleton>::get(setting);
}
template<typename First, typename Next, typename... Rest>
static inline std::variant<T...> try_each(
const libconfig::Setting& setting) {
template<typename T>
struct config_io<std::list<T>> {
static std::list<T> get(const libconfig::Setting& setting) {
const auto size = setting.getLength();
std::list<T> t{};
for (int i = 0; i < size; ++i) {
try {
return config_io<First>::get(setting);
t.emplace_back(config_io<T>::get(setting[i]));
} catch (libconfig::SettingException& e) {}
}
return t;
}
static std::list<T> get(const libconfig::Setting& parent, const std::string& name) {
return get(parent.lookup(name));
}
static void set(libconfig::Setting& setting, const std::list<T>& t) {
while (setting.getLength() != 0)
setting.remove((int) 0);
for (auto& x: t) {
config_io<T>::append(setting, x);
}
}
static void set(libconfig::Setting& parent,
const std::string& name,
const std::list<T>& 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>& t) {
auto& s = list.add(libconfig::Setting::TypeList);
set(s, t);
}
};
template<typename T>
struct config_io<std::set<T>> {
static std::set<T> get(const libconfig::Setting& setting) {
const auto size = setting.getLength();
std::set<T> t;
for (int i = 0; i < size; ++i) {
try {
t.emplace(config_io<T>::get(setting[i]));
} catch (libconfig::SettingException& e) {}
}
return t;
}
static std::set<T> get(const libconfig::Setting& parent, const std::string& name) {
return get(parent.lookup(name));
}
static void set(libconfig::Setting& setting, const std::set<T>& t) {
while (setting.getLength() != 0)
setting.remove((int) 0);
for (auto& x: t) {
auto& s = setting.add(libconfig::Setting::TypeGroup);
config_io<T>::set(s, x);
}
}
static void set(libconfig::Setting& parent,
const std::string& name,
const std::set<T>& 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>& t) {
auto& s = list.add(libconfig::Setting::TypeList);
set(s, t);
}
};
template<typename K, typename V, typename KeyName,
typename Cmp, typename Alloc>
struct config_io<map<K, V, KeyName, Cmp, Alloc>> {
static map<K, V, KeyName, Cmp, Alloc> get(const libconfig::Setting& setting) {
const auto size = setting.getLength();
map<K, V, KeyName, Cmp, Alloc> t;
for (int i = 0; i < size; ++i) {
auto& s = setting[i];
try {
t.emplace(config_io<K>::get(s.lookup(KeyName::value)),
config_io<V>::get(s));
} catch (libconfig::SettingException& e) {}
}
return t;
}
static map<K, V, KeyName, Cmp, Alloc> get(
const libconfig::Setting& parent, const std::string& name) {
return get(parent.lookup(name));
}
static void set(libconfig::Setting& setting,
const map<K, V, KeyName, Cmp, Alloc>& t) {
while (setting.getLength() != 0)
setting.remove((int) 0);
for (auto& x: t) {
auto& s = setting.add(libconfig::Setting::TypeGroup);
config_io<V>::set(s, x.second);
config_io<K>::set(s, KeyName::value, x.first);
}
}
static void set(libconfig::Setting& parent,
const std::string& name,
const map<K, V, KeyName, Cmp, Alloc>& 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<K, V, KeyName, Cmp, Alloc>& t) {
auto& s = list.add(libconfig::Setting::TypeList);
set(s, t);
}
};
template<typename T>
struct config_io<std::optional<T>> {
static std::optional<T> get(const libconfig::Setting& parent,
const std::string& name) {
if (parent.exists(name)) {
auto& setting = parent.lookup(name);
try {
return config_io<T>::get(setting);
} catch (libconfig::SettingException& e) {
return try_each<Next, Rest...>(setting);
logError(setting, e);
return std::nullopt;
}
} else {
return std::nullopt;
}
}
public:
static inline std::variant<T...> get(
const libconfig::Setting& setting) {
return try_each<T...>(setting);
}
static void set(libconfig::Setting& parent,
const std::string& name,
const std::optional<T>& t) {
if (t.has_value())
config_io<T>::set(parent, name, t.value());
}
};
static inline std::variant<T...> get(
const libconfig::Setting& parent,
const std::string& name) {
return get(parent.lookup(name));
}
// Optionals may not appear as part of a list or array
template<typename T, typename... Rest>
struct config_io<std::variant<std::optional<T>, Rest...>> {
static_assert(!sizeof(std::optional<T>), "Invalid type");
};
static inline void set(libconfig::Setting& setting,
const std::variant<T...>& t) {
std::visit([&setting](auto&& arg) {
config::set(setting, arg);
}, t);
}
template<typename T>
struct config_io<std::list<std::optional<T>>> {
static_assert(!sizeof(std::optional<T>), "Invalid type");
};
static inline void set(libconfig::Setting& parent,
const std::string& name,
const std::variant<T...>& t) {
std::visit([&parent, &name](auto&& arg) {
config::set(parent, name, arg);
}, t);
}
[[maybe_unused]]
static inline void append(libconfig::Setting& list,
const std::variant<T...>& t) {
std::visit([&list](auto&& arg) {
config::append(list, arg);
}, t);
}
};
template<typename T>
struct config_io<std::list<T>> {
static inline std::list<T> get(const libconfig::Setting& setting) {
const auto size = setting.getLength();
std::list<T> t{};
for (int i = 0; i < size; ++i) {
try {
t.emplace_back(config_io<T>::get(setting[i]));
} catch (libconfig::SettingException& e) {}
}
return t;
}
static inline std::list<T> get(const libconfig::Setting& parent,
const std::string& name) {
return get(parent.lookup(name));
}
static inline void set(libconfig::Setting& setting,
const std::list<T>& t) {
while (setting.getLength() != 0)
setting.remove((int) 0);
for (auto& x: t) {
config_io<T>::append(setting, x);
}
}
static inline void set(libconfig::Setting& parent,
const std::string& name,
const std::list<T>& 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 inline void append(libconfig::Setting& list,
const std::list<T>& t) {
auto& s = list.add(libconfig::Setting::TypeList);
set(s, t);
}
};
template<typename T>
struct config_io<std::set<T>> {
static inline std::set<T> get(const libconfig::Setting& setting) {
const auto size = setting.getLength();
std::set<T> t;
for (int i = 0; i < size; ++i) {
try {
t.emplace(config_io<T>::get(setting[i]));
} catch (libconfig::SettingException& e) {}
}
return t;
}
static inline std::set<T> get(const libconfig::Setting& parent,
const std::string& name) {
return get(parent.lookup(name));
}
static inline void set(libconfig::Setting& setting,
const std::set<T>& t) {
while (setting.getLength() != 0)
setting.remove((int) 0);
for (auto& x: t) {
auto& s = setting.add(libconfig::Setting::TypeGroup);
config_io<T>::set(s, x);
}
}
static inline void set(libconfig::Setting& parent,
const std::string& name,
const std::set<T>& 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 inline void append(libconfig::Setting& list,
const std::set<T>& t) {
auto& s = list.add(libconfig::Setting::TypeList);
set(s, t);
}
};
template<typename K, typename V, string_literal KeyName,
typename Cmp, typename Alloc>
struct config_io<map<K, V, KeyName, Cmp, Alloc>> {
static inline map<K, V, KeyName, Cmp, Alloc> get(
const libconfig::Setting& setting) {
const auto size = setting.getLength();
map<K, V, KeyName, Cmp, Alloc> t;
for (int i = 0; i < size; ++i) {
auto& s = setting[i];
try {
t.emplace(config_io<K>::get(s.lookup(KeyName.value)),
config_io<V>::get(s));
} catch (libconfig::SettingException& e) {}
}
return t;
}
static inline map<K, V, KeyName, Cmp, Alloc> get(
const libconfig::Setting& parent, const std::string& name) {
return get(parent.lookup(name));
}
static inline void set(libconfig::Setting& setting,
const map<K, V, KeyName, Cmp, Alloc>& t) {
while (setting.getLength() != 0)
setting.remove((int) 0);
for (auto& x: t) {
auto& s = setting.add(libconfig::Setting::TypeGroup);
config_io<V>::set(s, x.second);
config_io<K>::set(s, KeyName.value, x.first);
}
}
static inline void set(libconfig::Setting& parent,
const std::string& name,
const map<K, V, KeyName, Cmp, Alloc>& 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 inline void append(libconfig::Setting& list,
const map<K, V, KeyName, Cmp, Alloc>& t) {
auto& s = list.add(libconfig::Setting::TypeList);
set(s, t);
}
};
template<typename T>
struct config_io<std::optional<T>> {
static inline std::optional<T> get(const libconfig::Setting& parent,
const std::string& name) {
if (parent.exists(name)) {
auto& setting = parent.lookup(name);
try {
return config_io<T>::get(setting);
} catch (libconfig::SettingException& e) {
logError(setting, e);
return {};
}
} else {
return {};
}
}
static inline void set(libconfig::Setting& parent,
const std::string& name,
const std::optional<T>& t) {
if (t.has_value())
config_io<T>::set(parent, name, t.value());
}
};
// Optionals may not appear as part of a list or array
template<typename T, typename... Rest>
struct config_io<std::variant<std::optional<T>, Rest...>> {
static_assert(!sizeof(std::optional<T>), "Invalid type");
};
template<typename T>
struct config_io<std::list<std::optional<T>>> {
static_assert(!sizeof(std::optional<T>), "Invalid type");
};
template<typename T>
struct config_io<std::optional<std::optional<T>>> {
static_assert(!sizeof(std::optional<T>), "Invalid type");
};
}
template<typename T>
struct config_io<std::optional<std::optional<T>>> {
static_assert(!sizeof(std::optional<T>), "Invalid type");
};
template<typename T>
void set(libconfig::Setting& parent,

View File

@ -40,21 +40,19 @@ namespace logid::features {
}
};
namespace {
template<typename T>
class FeatureWrapper : public T {
friend class DeviceFeature;
template<typename T>
class _featureWrapper : public T {
friend class DeviceFeature;
public:
template<typename... Args>
explicit FeatureWrapper(Args... args) : T(std::forward<Args>(args)...) {}
public:
template<typename... Args>
explicit _featureWrapper(Args... args) : T(std::forward<Args>(args)...) {}
template<typename... Args>
static std::shared_ptr<T> make(Args... args) {
return std::make_shared<FeatureWrapper>(std::forward<Args>(args)...);
}
};
}
template<typename... Args>
static std::shared_ptr<T> make(Args... args) {
return std::make_shared<_featureWrapper>(std::forward<Args>(args)...);
}
};
class DeviceFeature {
std::weak_ptr<DeviceFeature> _self;
@ -68,7 +66,9 @@ namespace logid::features {
virtual ~DeviceFeature() = default;
DeviceFeature(const DeviceFeature&) = delete;
DeviceFeature(DeviceFeature&&) = delete;
protected:
explicit DeviceFeature(Device* dev) : _device(dev) {}
@ -82,7 +82,7 @@ namespace logid::features {
public:
template<typename T, typename... Args>
static std::shared_ptr<T> make(Args... args) {
auto feature = FeatureWrapper<T>::make(std::forward<Args>(args)...);
auto feature = _featureWrapper<T>::make(std::forward<Args>(args)...);
feature->_self = feature;
return feature;

View File

@ -63,7 +63,7 @@ RemapButton::RemapButton(Device* dev) : DeviceFeature(dev),
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",
logPrintf(WARN, "%s: 'Cannot divert raw XY movements for CID 0x%02x",
_device->name().c_str(), info.controlID);
report.flags |= action->reprogFlags();
@ -220,8 +220,9 @@ void Button::_makeConfig() {
}
}
void Button::press() const {
void Button::press() {
std::shared_lock lock(_action_lock);
_first_move = true;
if (_action)
_action->press();
}
@ -232,10 +233,12 @@ void Button::release() const {
_action->release();
}
void Button::move(int16_t x, int16_t y) const {
void Button::move(int16_t x, int16_t y) {
std::shared_lock lock(_action_lock);
if (_action)
if (_action && !_first_move)
_action->move(x, y);
else if (_first_move)
_first_move = false;
}
bool Button::pressed() const {

View File

@ -36,11 +36,11 @@ namespace logid::features {
Info info, int index, Device* device, ConfigFunction conf_func,
const std::shared_ptr<ipcgull::node>& root, config::Button& config);
void press() const;
void press();
void release() const;
void move(int16_t x, int16_t y) const;
void move(int16_t x, int16_t y);
void setProfile(config::Button& config);
@ -82,6 +82,8 @@ namespace logid::features {
std::shared_ptr<actions::Action> _action;
const Info _info;
bool _first_move{};
std::weak_ptr<Button> _self;
std::shared_ptr<IPC> _ipc_interface;

View File

@ -25,11 +25,35 @@ using namespace logid::backend;
SmartShift::SmartShift(Device* device) : DeviceFeature(device),
_config(device->activeProfile().smartshift) {
try {
_smartshift = std::make_shared<hidpp20::SmartShift>(&device->hidpp20());
_smartshift = hidpp20::SmartShift::autoVersion(&device->hidpp20());
} catch (hidpp20::UnsupportedFeature& e) {
throw UnsupportedFeature();
}
_torque_support = _smartshift->supportsTorque();
_defaults = _smartshift->getDefaults();
if (_config.get().has_value()) {
auto& config = _config.get().value();
if (config.threshold.has_value()) {
auto& threshold = config.threshold.value();
/* 0 means no change, clip to 1. */
if (threshold == 0)
threshold = 1;
}
if (config.torque.has_value()) {
auto& torque = config.torque.value();
/* torque is a percentage, clip between 1-100 */
if (torque == 0)
torque = 1;
else if (torque > 100)
torque = 100;
}
}
_ipc_interface = _device->ipcNode()->make_interface<IPC>(this);
}
@ -45,6 +69,9 @@ void SmartShift::configure() {
settings.setAutoDisengage = conf.threshold.has_value();
if (settings.setAutoDisengage)
settings.autoDisengage = conf.threshold.value();
settings.setTorque = conf.torque.has_value();
if (settings.setTorque)
settings.torque = conf.torque.value();
_smartshift->setStatus(settings);
}
@ -66,86 +93,108 @@ void SmartShift::setStatus(Status status) {
_smartshift->setStatus(status);
}
const hidpp20::SmartShift::Defaults& SmartShift::getDefaults() const {
return _defaults;
}
bool SmartShift::supportsTorque() const {
return _torque_support;
}
SmartShift::IPC::IPC(SmartShift* parent) :
ipcgull::interface(
SERVICE_ROOT_NAME ".SmartShift", {
{"GetStatus", {this, &IPC::getStatus, {"active", "threshold"}}},
{"SetActive", {this, &IPC::setActive, {"active"}}},
{"SetThreshold", {this, &IPC::setThreshold, {"threshold"}}},
{"GetDefault", {this, &IPC::getDefault, {"setActive", "active", "setThreshold", "threshold"}}},
{"ClearDefaultActive", {this, &IPC::clearDefaultActive}},
{"SetDefaultActive", {this, &IPC::setDefaultActive, {"active"}}},
{"ClearDefaultThreshold", {this, &IPC::clearDefaultThreshold}},
{"SetDefaultThreshold", {this, &IPC::setDefaultThreshold, {"threshold"}}}
}, {}, {}),
{"GetConfig", {this, &IPC::getConfig, {"active", "threshold", "torque"}}},
{"SetActive", {this, &IPC::setActive, {"active", "clear"}}},
{"SetThreshold", {this, &IPC::setThreshold, {"threshold", "clear"}}},
{"SetTorque", {this, &IPC::setTorque, {"torque", "clear"}}},
},
{
{"TorqueSupport", ipcgull::property<bool>(
ipcgull::property_readable, parent->supportsTorque())},
}, {}),
_parent(*parent) {
}
std::tuple<bool, uint8_t> SmartShift::IPC::getStatus() const {
auto ret = _parent.getStatus();
return std::make_tuple(ret.active, ret.autoDisengage);
std::tuple<uint8_t, uint8_t, uint8_t> SmartShift::IPC::getConfig() const {
std::shared_lock lock(_parent._config_mutex);
auto& config = _parent._config.get();
auto& defaults = _parent.getDefaults();
if (config.has_value()) {
auto& conf_value = config.value();
return {
conf_value.on.has_value() ? (conf_value.on.value() ? 2 : 1) : 0,
conf_value.threshold.value_or(defaults.autoDisengage),
conf_value.torque.value_or(defaults.torque),
};
} else {
return {0, 0, 0};
}
}
void SmartShift::IPC::setActive(bool active) {
Status status{};
status.setActive = true;
status.active = active;
_parent.setStatus(status);
void SmartShift::IPC::setActive(bool active, bool clear) {
std::unique_lock lock(_parent._config_mutex);
auto& config = _parent._config.get();
if (clear) {
if (config.has_value())
config.value().on.reset();
} else {
if (!config.has_value())
config = config::SmartShift{};
config.value().on = active;
Status status{};
status.active = active, status.setActive = true;
_parent.setStatus(status);
}
}
void SmartShift::IPC::setThreshold(uint8_t threshold) {
void SmartShift::IPC::setThreshold(uint8_t threshold, bool clear) {
std::unique_lock lock(_parent._config_mutex);
auto& config = _parent._config.get();
Status status{};
status.setAutoDisengage = true;
status.autoDisengage = threshold;
/* clip threshold */
if (threshold == 0)
threshold = 1;
if (clear) {
if (config.has_value())
config.value().threshold.reset();
status.autoDisengage = _parent.getDefaults().autoDisengage;
} else {
if (!config.has_value())
config = config::SmartShift{};
config.value().threshold = threshold;
status.autoDisengage = threshold;
}
_parent.setStatus(status);
}
std::tuple<bool, bool, bool, uint8_t> SmartShift::IPC::getDefault() const {
std::shared_lock lock(_parent._config_mutex);
auto& config = _parent._config.get();
if (!config.has_value())
return {false, false, false, 0};
std::tuple<bool, bool, bool, uint8_t> ret;
std::get<0>(ret) = config.value().on.has_value();
if (std::get<0>(ret))
std::get<1>(ret) = config.value().on.value();
std::get<2>(ret) = config.value().threshold.has_value();
if (std::get<2>(ret))
std::get<3>(ret) = config.value().threshold.value();
return ret;
}
void SmartShift::IPC::clearDefaultActive() {
void SmartShift::IPC::setTorque(uint8_t torque, bool clear) {
std::unique_lock lock(_parent._config_mutex);
auto& config = _parent._config.get();
if (config.has_value())
config.value().on.reset();
}
Status status{};
status.setTorque = true;
void SmartShift::IPC::setDefaultActive(bool active) {
std::unique_lock lock(_parent._config_mutex);
auto& config = _parent._config.get();
if (!config.has_value())
config = config::SmartShift{};
config.value().on = active;
}
/* clip torque */
if (torque == 0)
torque = 1;
else if (torque > 100)
torque = 100;
if (!_parent.supportsTorque())
throw std::invalid_argument("torque unsupported");
void SmartShift::IPC::clearDefaultThreshold() {
std::unique_lock lock(_parent._config_mutex);
auto& config = _parent._config.get();
if (config.has_value())
config.value().threshold.reset();
}
void SmartShift::IPC::setDefaultThreshold(uint8_t threshold) {
std::unique_lock lock(_parent._config_mutex);
auto& config = _parent._config.get();
if (!config.has_value())
config = config::SmartShift{};
config.value().threshold = threshold;
if (clear) {
if (config.has_value())
config.value().torque.reset();
status.torque = _parent.getDefaults().torque;
} else {
if (!config.has_value())
config = config::SmartShift{};
config.value().torque = torque;
status.torque = torque;
}
_parent.setStatus(status);
}

View File

@ -27,19 +27,22 @@
namespace logid::features {
class SmartShift : public DeviceFeature {
public:
void configure() final;
void listen() final;
void setProfile(config::Profile& profile) final;
typedef backend::hidpp20::SmartShift::SmartshiftStatus Status;
typedef backend::hidpp20::SmartShift::Status Status;
[[nodiscard]] Status getStatus() const;
void setStatus(Status status);
[[nodiscard]] const backend::hidpp20::SmartShift::Defaults& getDefaults() const;
[[nodiscard]] bool supportsTorque() const;
protected:
explicit SmartShift(Device* dev);
@ -48,25 +51,20 @@ namespace logid::features {
std::reference_wrapper<std::optional<config::SmartShift>> _config;
std::shared_ptr<backend::hidpp20::SmartShift> _smartshift;
backend::hidpp20::SmartShift::Defaults _defaults{};
bool _torque_support = false;
class IPC : public ipcgull::interface {
public:
explicit IPC(SmartShift* parent);
[[nodiscard]] std::tuple<bool, uint8_t> getStatus() const;;
[[nodiscard]] std::tuple<uint8_t, uint8_t, uint8_t> getConfig() const;
void setActive(bool active);
void setActive(bool active, bool clear);
void setThreshold(uint8_t threshold);
void setThreshold(uint8_t threshold, bool clear);
[[nodiscard]] std::tuple<bool, bool, bool, uint8_t> getDefault() const;
void clearDefaultActive();
void setDefaultActive(bool active);
void clearDefaultThreshold();
void setDefaultThreshold(uint8_t threshold);
void setTorque(uint8_t torque, bool clear);
private:
SmartShift& _parent;

View File

@ -135,12 +135,18 @@ int main(int argc, char** argv) {
std::shared_ptr<Configuration> config;
std::shared_ptr<InputDevice> virtual_input;
/* Set stdout buff to Null so that loging system like journal
* can actually read it.
*/
setbuf(stdout, NULL);
// Read config
try {
config = std::make_shared<Configuration>(options.config_file);
}
catch (std::exception& e) {
config = std::make_shared<Configuration>();
} catch (std::exception &e) {
logPrintf(ERROR, "%s", e.what());
return EXIT_FAILURE;
}
init_workers(config->workers.value_or(defaults::workers));

View File

@ -8,7 +8,6 @@ Wants=multi-user.target
Type=simple
ExecStart=${CMAKE_INSTALL_PREFIX}/bin/logid
User=root
Restart=on-failure
[Install]
WantedBy=graphical.target

View File

@ -3,11 +3,12 @@
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow own="pizza.pixl.LogiOps"/>
<policy context="default">
<deny receive_sender="pizza.pixl.LogiOps"/>
</policy>
<policy context="default">
<policy user="root">
<allow own="pizza.pixl.LogiOps"/>
<allow send_destination="pizza.pixl.LogiOps"/>
<allow receive_sender="pizza.pixl.LogiOps"/>
</policy>

View File

@ -18,6 +18,7 @@
#include <util/task.h>
#include <queue>
#include <optional>
#include <cassert>
using namespace logid;
using namespace std::chrono;
@ -35,18 +36,38 @@ static std::priority_queue<task, std::vector<task>, task_less> tasks {};
static std::mutex task_mutex {};
static std::condition_variable task_cv {};
static std::atomic_bool workers_init = false;
static std::atomic_bool workers_run = false;
[[noreturn]] static void worker() {
void stop_workers() {
std::unique_lock lock(task_mutex);
while (true) {
task_cv.wait(lock, []() { return !tasks.empty(); });
if (workers_init) {
workers_run = false;
lock.unlock();
task_cv.notify_all();
/* Wait for all workers to end */
lock.lock();
}
}
void worker() {
std::unique_lock lock(task_mutex);
while (workers_run) {
task_cv.wait(lock, []() { return !tasks.empty() || !workers_run; });
if (!workers_run)
break;
/* top task is in the future, wait */
if (tasks.top().time >= system_clock::now()) {
auto wait = tasks.top().time - system_clock::now();
task_cv.wait_for(lock, wait, []() {
return !tasks.empty() && (tasks.top().time < system_clock::now());
return (!tasks.empty() && (tasks.top().time < system_clock::now())) ||
!workers_run;
});
if (!workers_run)
break;
}
if (!tasks.empty()) {
@ -67,11 +88,15 @@ static std::atomic_bool workers_init = false;
void logid::init_workers(int worker_count) {
std::lock_guard lock(task_mutex);
assert(!workers_init);
for (int i = 0; i < worker_count; ++i)
std::thread(&worker).detach();
workers_init = true;
workers_run = true;
atexit(&stop_workers);
}
void logid::run_task(std::function<void()> function) {

View File

@ -1 +0,0 @@
v0.3.0