Compare commits

...

184 Commits
v0.2.2 ... 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
pixl
bd8b905e27
Fix versioning 2023-05-02 19:23:04 -04:00
pixl
0072093424
Update OpenSUSE dependencies 2023-05-02 19:22:15 -04:00
pixl
dde9c0380d
Merge pull request #362 from PixlOne/feature/ipcgull
Add IPC support and Logi Bolt support
2023-05-02 19:15:02 -04:00
pixl
f614bce4ce
Add ChangeProfile action 2023-05-02 19:01:48 -04:00
pixl
5e47bc6ae4
Handle race condition in InputDevice
Lock mutex when sending events and changing input device.
2023-05-02 18:08:49 -04:00
pixl
27b6a2fd8f
Add full profile support 2023-05-02 18:06:25 -04:00
pixl
c0e532b1de
Add exponential backoff for receivers 2023-05-02 16:13:40 -04:00
pixl
21ae951f3f
Bind delayed lambda lifetimes to owners
Prevents race conditions when objects are being destructed.
2023-05-02 16:01:09 -04:00
pixl
a578493dba
Require HID++ devices to be made as shared_ptrs
Binds event handlers and tasks to lifetime of object. Ensures no race
conditions occur during destruction of HID++ devices.
2023-05-02 14:30:47 -04:00
pixl
a468861f7d
Fix release warnings
Also possibly fixes some undefined behaviour in release builds?
2023-05-02 01:15:46 -04:00
pixl
8a4e2cce81
Reduce thread footprint by using a work queue
Also allows tasks on queue to be run after a certain amount of time.
2023-05-02 00:39:03 -04:00
pixl
1ff386a4bf
Fix EventHandlerList move assignment 2023-05-01 22:40:37 -04:00
pixl
47e4ba2b44
Add exponential backoff to device monitor 2023-05-01 21:51:08 -04:00
pixl
ffd5e054a1
Bind EventHandler lifetimes to owner 2023-05-01 21:44:01 -04:00
pixl
0b9a9f1bf4
Add Logi Bolt pairing support
Used the bolt pairing process from solaar. Currently, no proper
interface exists (aside from poking around d-bus interfaces).
2023-05-01 14:58:53 -04:00
pixl
455f2cd6c4
Fix entire config being invalidated by bad value 2023-04-29 21:23:43 -04:00
pixl
fed7e0cacc
Add support for using system bus 2023-04-29 20:43:51 -04:00
pixl
e50e566f20
Add Logi Bolt support 2023-04-29 19:34:22 -04:00
pixl
f2fd967865
Fix receivers not seeing device connection events 2023-04-29 18:57:09 -04:00
pixl
fc96bb7b40
Remove DJ support requirement from receivers
Allows Logi Bolt receivers to be detected by logid but not yet used.
2023-04-29 16:49:38 -04:00
pixl
d13d1feb4b
Added DPI IPC interface 2023-04-29 15:52:49 -04:00
pixl
7a4a7f0573
Make HiresScroll and ThumbWheel IPC consistent
Features should be at device node.
2023-04-29 15:03:48 -04:00
pixl
0b515feb17
Add HiresScroll IPC support 2023-04-29 14:59:00 -04:00
pixl
485788a74e
Ensure wheel compaitiblity with gestures
Also uses a definition for the root service name pizza.pixl.LogiOps.
2023-04-29 14:18:29 -04:00
pixl
35b4dc03bf
Limit use of C-style definitions 2023-04-29 13:31:44 -04:00
pixl
c15419a234
Add ThumbWheel IPC support 2023-04-29 13:14:09 -04:00
pixl
d7bc4958ba
Clean up includes 2023-04-29 00:24:35 -04:00
pixl
53ce93c6a5
Add gesture IPC interfaces
Also fixes concurrency issues when changing action configs.
2023-04-28 22:39:24 -04:00
pixl
0115aaa804
Wait for devices to be ready before adding them. 2023-04-28 21:18:33 -04:00
pixl
adcb173e4c
I/O fixes, ensure sync and responses are proper 2023-04-28 21:18:33 -04:00
pixl
9d15610596
Update GPL copyright year 2023-04-28 21:18:33 -04:00
pixl
6b2207d412
Fix memory error with gesture configs 2023-04-28 21:18:33 -04:00
pixl
9efd121387
Code cleanup and reformatting 2023-04-28 21:18:33 -04:00
pixl
9af666f863
Add ChangeHostAction IPC interface 2023-04-28 21:18:33 -04:00
pixl
56dee076ea
Add DPI action IPC bindings 2023-04-28 21:18:33 -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
pixl
f76d9dfaf5
Use event handler ids for hidpp::Device 2022-03-05 21:32:12 -05:00
pixl
a23f0ea725
Inline config functions 2022-02-14 20:39:25 -05:00
pixl
605ccdc07d
Add several IPC interfaces 2022-02-14 01:03:56 -05:00
pixl
dbe2b28a53
Add Logitech trademarks to README 2022-01-31 14:52:17 -05:00
pixl
ed3b0ee067
Merge pull request #293 from tekq/patch-1
Update Dependencies to also include openSUSE
2022-01-30 16:06:35 -05:00
pixl
c69ae325d3
Remove old multi-threading code 2022-01-29 00:48:16 -05:00
pixl
d9c64892be
Fix device monitor deadlock 2022-01-29 00:46:26 -05:00
pixl
87fb4371a4
Initial action and gesture ipc interfaces 2022-01-29 00:07:22 -05:00
pixl
6e8c24b2f9
Fix a race condition caused by virtual hid nodes
When a virtual hidraw node is being tested while the real one is tested,
this causes the devices to misidentify reports. This solves that by
making the device monitor synchronous.
2022-01-29 00:06:00 -05:00
pixl
3808f0a6cf
Use action interface names as static vars 2022-01-28 20:50:16 -05:00
pixl
f25a1e4657
Ignore hidpp responses to kernel driver
Also fixes a bug where unconfigured devices would error.
2022-01-28 18:28:58 -05:00
tekq
8c88335838
Update Dependencies to also include openSUSE now! 2022-01-28 23:32:35 +02:00
pixl
7862c85d71
Use epoll for I/O multiplexing 2022-01-28 15:48:00 -05:00
pixl
dbe24f9350
Phase out workqueue 2022-01-21 23:24:47 -05:00
pixl
1dd6dbfe02
Move Buttons into their own class 2022-01-20 22:02:29 -05:00
pixl
6614702929
Merge pull request #292 from PixlOne/config_rework
Rework config interface
2022-01-20 01:33:09 -05:00
pixl
9b94fad0a6
Use new config interface 2022-01-20 01:29:18 -05:00
pixl
c421affe25
Add config schema 2022-01-11 20:33:26 -05:00
pixl
fbc3a1a472
Added refined config struct templates 2022-01-11 20:33:03 -05:00
pixl
62095a3e37
Require C++20 2022-01-11 18:01:14 -05:00
pixl
3c723dc3cf
Expand Device and RemapButton interface 2022-01-09 21:49:09 -05:00
pixl
f93a8b694d
Implement profile support
(cherry picked from commit 1aa6cd0224ebe4dc9ca72171728f7b500c624b65)
2022-01-08 22:49:27 -05:00
pixl
f23326c660
Complete DeviceManager interface 2022-01-08 22:32:49 -05:00
pixl
9f4979ba59
Updated ipcgull version 2022-01-08 21:04:13 -05:00
pixl
436729b07b
Properly handle full workqueue 2022-01-08 16:29:17 -05:00
pixl
918ea63755
Add Device and Receiver signals to DeviceManager 2022-01-08 00:05:32 -05:00
pixl
daa8c37c7d
Change default I/O timeout to 400ms 2022-01-07 23:40:29 -05:00
pixl
65fc252607
Eliminate global variables
global_loglevel is kept for the time being and will be replaced
alongside the current logger.
2022-01-07 19:43:41 -05:00
pixl
23291ec382
Add ipcgull submodule and require glib 2022-01-07 02:37:17 -05:00
pixl
8e87b73d7d
Merge pull request #284 from damageboy/master
Resolve majority of clang+libstdc++ warnings
2022-01-06 16:26:39 -05:00
damageboy
7424c4fe94 Resolve majority of clang+libstdc++ warnings 2022-01-05 13:01:00 +02:00
damageboy
aa4c895c1e add .editorconfig to ease contribution by external devs 2022-01-05 12:53:55 +02:00
pixl
7f70dc7ffa
Merge pull request #272 from lazarin/patch-1
Add M500s to the list of supported devices
2022-01-02 02:56:03 -05:00
pixl
fe72e51155
Do not register EV_REL axis by default
Fixes #244, #245, #281.
2022-01-02 02:54:41 -05:00
pixl
53c9c0a5e4
Merge pull request #277 from mellolucas/patch-1
Added MX Ergo Config Name
2022-01-02 02:06:39 -05:00
Lucas Mello
02c0d03c99
Added MX Ergo Config Name
The space at the end was not placed by mistake. It is necessary to have its config recognized. As per https://github.com/PixlOne/logiops/issues/65#issuecomment-723559895
Config example here: https://github.com/TobiasDev/dot-files/blob/master/logiops/logid.cfg
2021-11-22 16:41:44 -03:00
lazarin
f9bcf5b143
Add M500s to the list of supported devices
Add M500s to the list of supported devices
2021-09-28 12:09:51 +03:00
pixl
6bb4700009
Merge pull request #246 from wooparadog/master
Fix systemd denepdency
2021-05-27 10:02:15 -04:00
Haochuan Guo
5947cc939e
Fix systemd denepdency
Let logiops start after multi-user.target is reached, and change it as a dependency to `graphical.target`. Fix #204
2021-05-16 13:30:48 +08:00
Naman Sood
7b297fc49f
Fix index underflow causing segfault on DPI change (#241)
* Fix index underflow causing segfault on DPI change

* Stop over-allocating sensor lists
2021-05-08 18:06:17 -04:00
pixl
990f923e7a
Add colon to Gentoo in README.md 2021-04-24 01:49:27 -04:00
pixl
febbe3b5de
Merge pull request #218 from ConiKost/gentoo
Add Gentoo Linux to README.md
2021-04-24 01:47:34 -04:00
Conrad Kostecki
0a79f571b9 Sort Linux 2021-03-22 11:35:39 +01:00
Conrad Kostecki
d299853c96 Added Gentoo Linux 2021-03-22 11:34:16 +01:00
pixl
0a26579d5a
Merge pull request #200 from deamn/master
Add <thread> to Device.cpp, fixes issue #199
2021-03-13 20:24:54 -05:00
pixl
214af3d6bb
Merge pull request #215 from PixlOne/fix/thumbwheel
Fix ThumbWheel feature direction bug
2021-03-13 19:30:30 -05:00
pixl
916fa8692c
Merge pull request #209 from BarrensZeppelin/master
Fix missing release of ThumbWheel touch
2021-03-13 18:03:26 -05:00
pixl
48399a1dd4
Fix ThumbWheel feature direction bug
Should fix #145 for the MX Master 3
2021-03-13 17:55:53 -05:00
Oskar Haarklou Veileborg
f0de58e76e Fix missing release of ThumbWheel touch 2021-03-02 12:20:01 +01:00
Nicolas De Amicis
7462a11a5a
Update Device.cpp 2021-02-03 16:38:43 +01:00
Alex Reece
a0687c8f18
Added Fedora dependancy install commands (#155)
* Added Fedora 33 dependancy install commands

Tested on a relatively fresh install of Fedora 33, gcc-c++ included as no c++ compiler by default, feel free to remove if it fits better with the pattern of not including one in the previous instructions, however it did trip me up for a few minutes so may be worth leaving in.

* Remove Fedora Version

* Update README.md

Changed systemd-devel1 to systemd-devel (typo)
2021-01-07 18:38:12 -05:00
pixl
9514f4ed73
Merge pull request #168 from Ashark/patch-1
Filled in config name for MX Vertical
2021-01-06 20:59:58 -05:00
pixl
1f1a306334
Merge pull request #185 from aragon999/feature/only-register-axes-if-used
Enable axis in virtual_input device only when needed
2021-01-06 20:58:46 -05:00
max
1f02882971 Enable axis in virtual_input device only when needed 2020-12-21 11:50:04 +01:00
pixl
ffcfb3da82
Merge pull request #183 from aragon999/fix/keypress-systemd-issue
Register keys for keypress only when needed.

TODO: Apply this for axes too?
2020-12-20 19:46:44 -05:00
max
15de10344a In error case set device and ui_device to nullptr 2020-12-15 13:59:06 +01:00
max
8280bc2505 Enable some keys by default and enable more on request
if the user chooses to use such keys in the config.
2020-12-14 22:10:07 +01:00
max
f8eb77cad7 Register keys for keypress only when needed
there seems to be an issue that with too many registered events some window
manager cannot recognize the device as input device with systemd 247.

PixlOne#166
2020-12-14 20:51:47 +01:00
Kristóf Marussy
911e91eeeb
Enable fewer EV_KEY events in InputDevice
Fixes #166

Starting with systemd 247 (?), desktop environments fail to recognize
the virtual input device if it has too many enabled libinput events.

It seems like KEY_ROTATE_LOCK_TOGGLE as the highest enabled event is a
good limit. This is the highest evdev event mapped by xkb, so the
usefulness higher events is limited, anyways.
2020-12-14 19:49:51 +01:00
Kristóf Marussy
c261b6582c
Cleanup in InputDevice 2020-12-14 19:49:28 +01:00
Andrew Shark
fa020e5f20
Filled in config name for MX Vertical 2020-11-30 11:05:21 +03:00
pixl
1c209edaad
Merge pull request #159 from PixlOne/revert-157-master
Revert "Enable compatibility with libconfig older than v1.5"
2020-11-23 15:43:04 -05:00
pixl
ec8115634d
Revert "Enable compatibility with libconfig older than v1.5" 2020-11-23 15:42:29 -05:00
pixl
1d6a89881f
Merge pull request #157 from abraha2d/master
Enable compatibility with libconfig older than v1.5
2020-11-22 21:02:41 -05:00
Kevin Abraham
28a93b2df4 Enable compatibility with older versions of libconfig (such as the one shipped with RHEL/CentOS 7) 2020-11-22 12:12:29 -05:00
pixl
4c582241d5
Merge pull request #137 from gavinhungry/tested-mx-anywhere-3
Add MX Anywhere 3 to TESTED.md
2020-10-08 23:33:49 -04:00
Gavin Lloyd
132ae7025a
Add MX Anywhere 3 to TESTED.md 2020-10-08 16:54:39 -07:00
pixl
d1d8c49021
Merge pull request #136 from AvdN/patch-1
package install instructions for Solus
2020-09-28 01:43:19 -04:00
Anthon van der Neut
b728f36ff8
package install instructions for Solus 2020-09-27 09:54:08 +02:00
pixl
951661dc62
Merge pull request #127 from KnowYourselves/master
Add MX Anywhere S2 to TESTED.md
2020-09-06 03:05:45 -04:00
Raúl Álvarez
cfbf7cd55a
🎉 Added MX Anywhere S2 2020-09-06 02:02:42 -03:00
pixl
3b4b90dac0
Support NoPress gesture mode on none direction
Fix #122, makes logid.example.cfg accurate.
2020-09-05 18:12:56 -04:00
pixl
4cfc2515d5
Fix ThumbWheel setStatus arg alignment 2020-09-05 18:00:18 -04:00
pixl
a577b74c33
Merge pull request #125 from leo-ventura/master
docs: Update logid.example.cfg and fix daemon instruction on readme
2020-09-01 19:00:43 -04:00
Leonardo Ventura
0614f27c62 docs: Fix instruction to enable daemon
Change `start` to `enable` on systemctl command instruction to enable
daemon at boot. Also add instruction to enable and start at the same
time.
A few other minor improvements on readability.
2020-09-01 10:58:08 -03:00
Leonardo Ventura
eeefa30b0a docs: Update config example to match hidpp20 name 2020-09-01 10:56:53 -03:00
pixl
8348782f27
Implement ThumbWheel feature
This feature has not been tested as it only works on devices with the
0x2150 Thumb wheel feature (e.g. MX Master 3).
2020-08-22 16:59:10 -04:00
pixl
a8e2ecbcd7
Add HiresScroll destructor 2020-08-21 21:46:33 -04:00
pixl
b554d32cf3
Fix InputDevice on distros without hires axes 2020-08-21 21:02:24 -04:00
pixl
cce1275385
Implement HID++ 2.0 ThumbWheel (0x2150) 2020-08-21 20:29:06 -04:00
pixl
63ecbc411e
Fix InputDevice missing header on some systems 2020-08-21 20:09:02 -04:00
pixl
2dfe139f6c
Merge pull request #114 from andy-shi88/logi-m590-tested
Add m590 TESTED.md:ConfigName
2020-08-21 16:58:35 -04:00
pixl
41903992ef
Fix ThresholdGesture
Style fixes, allow to compile, add copyright notice.
2020-08-21 16:57:40 -04:00
pixl
e570e7b91e
Merge pull request #115 from michtere/master
Add ThresholdGesture.
2020-08-21 16:54:07 -04:00
pixl
c1423e345e
Support HiresScroll gesture remapping
This commit allows HiresScroll (when target is true) to map the up and
down events to gestures that support it (i.e.AxisGesture/
IntervalGesture). This check is done by checking if wheelCompatibility()
is true.

This also allows hires scroll events to send low-res scroll events as
well.

TODO: Fix bug w/ Chromium (and some other programs?) where mapping
scroll wheel to REL_WHEEL_HI_RES will cause the program to skip events
occassionally. I have literally been stuck on this bug for a week and I
still don't know what causes it. evtest shows proper scroll events,
Firefox works fine, and libinput test-gui reports proper scrolling.
2020-08-21 16:05:20 -04:00
Michail Terezakis
3561b1f487 Add ThresholdGesture. 2020-08-16 19:57:31 +03:00
andy-shi88
5800b5b954
add m590 logitect device name 2020-08-16 12:57:47 +08:00
pixl
6ea65601f3
Fix typo in HiresScroll 2020-08-02 02:34:58 -04:00
pixl
c3419b9468
Align columns in TESTED.md 2020-07-25 15:09:14 -04:00
pixl
e644eb779b
Add other known device config names to TESTED.md 2020-07-25 15:06:04 -04:00
pixl
62e9863ffc
Merge pull request #110 from lambdageek/patch-1
(TESTED.md) Add M720 config name
2020-07-25 15:03:43 -04:00
Aleksey Kliger (λgeek)
a3987fc4d1
(TESTED.md) Add M720 config name 2020-07-25 14:54:46 -04:00
Jef LeCompte
9064ee3c72
docs: update code for config (#105)
* docs: update code for config

* docs: update config name

* fix: associated config
2020-07-24 14:25:44 -04:00
pixl
34b8047360
Refer to receiver from receiver devices 2020-07-20 00:25:04 -04:00
pixl
1056dfa032
Don't use DeviceStatus on unifying devices 2020-07-20 00:07:35 -04:00
pixl
78c0788be6
Fix bad removeDevice map check 2020-07-19 23:48:24 -04:00
151 changed files with 9658 additions and 5929 deletions

25
.editorconfig Normal file
View File

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

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

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "src/ipcgull"]
path = src/ipcgull
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,18 +6,22 @@ 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 14)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
option(USE_USER_BUS "Uses user bus" OFF)
find_package(Git)
# Set version number
if(GIT_FOUND AND EXISTS ${CMAKE_SOURCE_DIR}/.git)
execute_process(COMMAND ${GIT_EXECUTABLE} describe
# Set version number and update submodules
if(EXISTS ${PROJECT_SOURCE_DIR}/.git AND GIT_FOUND)
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags
OUTPUT_VARIABLE LOGIOPS_VERSION
RESULT_VARIABLE LOGIOPS_VERSION_RET
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
ERROR_QUIET)
if(NOT LOGIOPS_VERSION_RET EQUAL "0")
execute_process(COMMAND ${GIT_EXECUTABLE}
@ -25,10 +29,20 @@ if(GIT_FOUND AND EXISTS ${CMAKE_SOURCE_DIR}/.git)
OUTPUT_VARIABLE LOGIOPS_COMMIT_HASH)
set(LOGIOPS_VERSION git-${LOGIOPS_COMMIT_HASH})
endif()
execute_process(COMMAND ${GIT_EXECUTABLE}
submodule update --init --recursive
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
string(REGEX REPLACE "\n$" "" LOGIOPS_VERSION ${LOGIOPS_VERSION})
elseif(EXISTS ${CMAKE_SOURCE_DIR}/version.txt)
elseif(EXISTS ${PROJECT_SOURCE_DIR}/version.txt)
file(READ version.txt LOGIOPS_VERSION)
string(REGEX REPLACE "\n$" "" LOGIOPS_VERSION ${LOGIOPS_VERSION})
endif()
IF(NOT EXISTS ${PROJECT_SOURCE_DIR}/src/ipcgull)
message(FATAL_ERROR "Missing ipcgull submodule")
endif()
if(NOT LOGIOPS_VERSION)
@ -38,4 +52,9 @@ message("LogiOps Version Number: ${LOGIOPS_VERSION}")
add_definitions( -DLOGIOPS_VERSION="${LOGIOPS_VERSION}")
if(USE_USER_BUS)
add_definitions(-DUSE_USER_BUS)
endif()
add_subdirectory(src/ipcgull)
add_subdirectory(src/logid)

View File

@ -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.
@ -7,30 +9,45 @@ This is currently only compatible with HID++ \>2.0 devices.
## Configuration
[Refer to the wiki for details.](https://github.com/PixlOne/logiops/wiki/Configuration)
You may also refer to logid.example.cfg for an example.
You may also refer to [logid.example.cfg](./logid.example.cfg) for an example.
Default location for the configuration file is /etc/logid.cfg.
Default location for the configuration file is /etc/logid.cfg, but another can be specified using the `-c` flag.
## Dependencies
This project requires a C++14 compiler, cmake, libevdev, libudev, and libconfig. For popular distributions, I've included commands below.
This project requires a C++20 compiler, `cmake`, `libevdev`, `libudev`, `glib`, and `libconfig`.
For popular distributions, I've included commands below.
**Debian/Ubuntu:** `sudo apt install cmake libevdev-dev libudev-dev libconfig++-dev`
**Arch Linux:** `sudo pacman -S base-devel cmake libevdev libconfig systemd-libs glib2`
**Arch Linux:** `sudo pacman -S cmake libevdev libconfig pkgconf`
**Debian/Ubuntu:** `sudo apt install build-essential cmake pkg-config libevdev-dev libudev-dev libconfig++-dev libglib2.0-dev`
**Fedora:** `sudo dnf install cmake libevdev-devel systemd-devel libconfig-devel gcc-c++ glib2-devel`
**Gentoo Linux:** `sudo emerge dev-libs/libconfig dev-libs/libevdev dev-libs/glib dev-util/cmake virtual/libudev`
**Solus:** `sudo eopkg install cmake libevdev-devel libconfig-devel libgudev-devel glib2-devel`
**openSUSE:** `sudo zypper install cmake libevdev-devel systemd-devel libconfig-devel gcc-c++ libconfig++-devel libudev-devel glib2-devel`
## Building
To build this project, run:
```
```bash
mkdir build
cd build
cmake ..
cmake -DCMAKE_BUILD_TYPE=Release ..
make
```
To install, run `sudo make install` after building. You can set the daemon to start at boot by running `sudo systemctl start logid`.
To install, run `sudo make install` after building. You can set the daemon to start at boot by running `sudo systemctl enable logid` or `sudo systemctl enable --now logid` if you want to enable and start the daemon.
## Development
The project may only run as root, but for development purposes, you may find it
convenient to run as non-root on the user bus. You must compile with the CMake
flag `-DUSE_USER_BUS=ON` to use the user bus.
## Donate
This program is (and will always be) provided free of charge. If you would like to support the development of this project by donating, you can donate to my Ko-Fi below.
@ -43,7 +60,10 @@ I'm also looking for contributors to help in my project; feel free to submit a p
[For a list of tested devices, check TESTED.md](TESTED.md)
## Special Thanks
## Credits
Logitech, Logi, and their logos are trademarks or registered trademarks of Logitech Europe S.A. and/or its affiliates in the United States and/or other countries. This software is an independent product that is not endorsed or created by Logitech.
Thanks to the following people for contributing to this repository.
- [Clément Vuchener & contributors for creating the old HID++ library](https://github.com/cvuchener/hidpp)

View File

@ -2,13 +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? |
|:------------:|:-----------:|
| MX Master 3 | Yes |
| MX Master 2S | Yes |
| MX Master | Yes |
| MX Vertical | Yes |
| MX Ergo | Yes |
| M720 | Yes |
| M590 | Yes |
| T400 | Yes |
| 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

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

1
src/ipcgull Submodule

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,163 +16,61 @@
*
*/
#include <Configuration.h>
#include <util/log.h>
#include <utility>
#include <vector>
#include <map>
#include "Configuration.h"
#include "util/log.h"
#include <filesystem>
#include <ipc_defs.h>
using namespace logid;
using namespace libconfig;
using namespace std::chrono;
using namespace logid::config;
Configuration::Configuration(const std::string& config_file)
{
try {
_config.readFile(config_file.c_str());
} catch(const FileIOException &e) {
logPrintf(ERROR, "I/O Error while reading %s: %s", config_file.c_str(),
e.what());
throw e;
} catch(const ParseException &e) {
logPrintf(ERROR, "Parse error in %s, line %d: %s", e.getFile(),
e.getLine(), e.getError());
throw e;
}
const Setting &root = _config.getRoot();
try {
auto& worker_count = root["workers"];
if(worker_count.getType() == Setting::TypeInt) {
_worker_threads = worker_count;
if(_worker_threads < 0)
logPrintf(WARN, "Line %d: workers cannot be negative.",
worker_count.getSourceLine());
} else {
logPrintf(WARN, "Line %d: workers must be an integer.",
worker_count.getSourceLine());
}
} catch(const SettingNotFoundException& e) {
// Ignore
}
try {
auto& timeout = root["io_timeout"];
if(timeout.isNumber()) {
if(timeout.getType() == Setting::TypeFloat)
_io_timeout = duration_cast<milliseconds>(
duration<double, std::milli>(timeout));
else
_io_timeout = milliseconds((int)timeout);
} else
logPrintf(WARN, "Line %d: io_timeout must be a number.",
timeout.getSourceLine());
} catch(const SettingNotFoundException& e) {
// Ignore
}
try {
auto& devices = root["devices"];
for(int i = 0; i < devices.getLength(); i++) {
const Setting& device = devices[i];
std::string name;
try {
if(!device.lookupValue("name", name)) {
logPrintf(WARN, "Line %d: 'name' must be a string, skipping"
" device.", device["name"].getSourceLine());
continue;
}
} catch(SettingNotFoundException &e) {
logPrintf(WARN, "Line %d: Missing name field, skipping device."
, device.getSourceLine());
continue;
}
_device_paths.insert({name, device.getPath()});
}
}
catch(const SettingNotFoundException &e) {
logPrintf(WARN, "No devices listed in config file.");
}
try {
auto& ignore = root.lookup("ignore");
if(ignore.getType() == libconfig::Setting::TypeInt) {
_ignore_list.insert((int)ignore);
} else if(ignore.isList() || ignore.isArray()) {
int ignore_count = ignore.getLength();
for(int i = 0; i < ignore_count; i++) {
if(ignore[i].getType() != libconfig::Setting::TypeInt) {
logPrintf(WARN, "Line %d: ignore must refer to device PIDs",
ignore[i].getSourceLine());
if(ignore.isArray())
break;
} else
_ignore_list.insert((int)ignore[i]);
}
}
} catch(const SettingNotFoundException& e) {
// May be called blacklist
Configuration::Configuration(std::string config_file) :
_config_file(std::move(config_file)) {
if (std::filesystem::exists(_config_file)) {
try {
auto& ignore = root.lookup("blacklist");
if(ignore.getType() == libconfig::Setting::TypeInt) {
_ignore_list.insert((int)ignore);
} else if(ignore.isList() || ignore.isArray()) {
int ignore_count = ignore.getLength();
for(int i = 0; i < ignore_count; i++) {
if(ignore[i].getType() != libconfig::Setting::TypeInt) {
logPrintf(WARN, "Line %d: blacklist must refer to "
"device PIDs",
ignore[i].getSourceLine());
if(ignore.isArray())
break;
} else
_ignore_list.insert((int)ignore[i]);
}
}
} catch(const SettingNotFoundException& e) {
// Ignore
_config.readFile(_config_file.c_str());
} catch (const FileIOException& e) {
logPrintf(ERROR, "I/O Error while reading %s: %s", _config_file.c_str(),
e.what());
throw;
} catch (const ParseException& e) {
logPrintf(ERROR, "Parse error in %s, line %d: %s", e.getFile(),
e.getLine(), e.getError());
throw;
}
Config::operator=(get<Config>(_config.getRoot()));
} else {
logPrintf(INFO, "Config file does not exist, using empty config.");
}
if (!devices.has_value())
devices.emplace();
}
Configuration::Configuration() {
devices.emplace();
}
void Configuration::save() {
config::set(_config.getRoot(), *this);
try {
_config.writeFile(_config_file.c_str());
} catch (const FileIOException& e) {
logPrintf(ERROR, "I/O Error while writing %s: %s",
_config_file.c_str(), e.what());
throw;
} catch (const std::exception& e) {
logPrintf(ERROR, "Error while writing %s: %s",
_config_file.c_str(), e.what());
throw;
}
}
libconfig::Setting& Configuration::getSetting(const std::string& path)
{
return _config.lookup(path);
}
std::string Configuration::getDevice(const std::string& name)
{
auto it = _device_paths.find(name);
if(it == _device_paths.end())
throw DeviceNotFound(name);
else
return it->second;
}
bool Configuration::isIgnored(uint16_t pid) const
{
return _ignore_list.find(pid) != _ignore_list.end();
}
Configuration::DeviceNotFound::DeviceNotFound(std::string name) :
_name (std::move(name))
{
}
const char * Configuration::DeviceNotFound::what() const noexcept
{
return _name.c_str();
}
int Configuration::workerCount() const
{
return _worker_threads;
}
std::chrono::milliseconds Configuration::ioTimeout() const
{
return _io_timeout;
Configuration::IPC::IPC(Configuration* config) :
ipcgull::interface(SERVICE_ROOT_NAME ".Config", {
{"Save", {config, &Configuration::save}}
}, {}, {}) {
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,46 +19,40 @@
#ifndef LOGID_CONFIGURATION_H
#define LOGID_CONFIGURATION_H
#include <map>
#include <config/schema.h>
#include <ipcgull/interface.h>
#include <libconfig.h++>
#include <memory>
#include <chrono>
#include <set>
#define LOGID_DEFAULT_IO_TIMEOUT std::chrono::seconds(2)
#define LOGID_DEFAULT_WORKER_COUNT 4
namespace logid {
namespace defaults {
static constexpr double io_timeout = 500;
static constexpr int workers = 4;
static constexpr int gesture_threshold = 50;
}
namespace logid
{
class Configuration
{
class Configuration : public config::Config {
public:
explicit Configuration(const std::string& config_file);
Configuration() = default;
libconfig::Setting& getSetting(const std::string& path);
std::string getDevice(const std::string& name);
bool isIgnored(uint16_t pid) const;
explicit Configuration(std::string config_file);
class DeviceNotFound : public std::exception
{
Configuration();
// Reloading is not safe, references will be invalidated
//void reload();
void save();
class IPC : public ipcgull::interface {
public:
explicit DeviceNotFound(std::string name);
const char* what() const noexcept override;
private:
std::string _name;
explicit IPC(Configuration* config);
};
std::chrono::milliseconds ioTimeout() const;
int workerCount() const;
private:
std::map<std::string, std::string> _device_paths;
std::set<uint16_t> _ignore_list;
std::chrono::milliseconds _io_timeout = LOGID_DEFAULT_IO_TIMEOUT;
int _worker_threads = LOGID_DEFAULT_WORKER_COUNT;
std::string _config_file;
libconfig::Config _config;
};
extern std::shared_ptr<Configuration> global_config;
}
#endif //LOGID_CONFIGURATION_H

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,86 +19,168 @@
#ifndef LOGID_DEVICE_H
#define LOGID_DEVICE_H
#include "backend/hidpp/defs.h"
#include "backend/hidpp20/Device.h"
#include "features/DeviceFeature.h"
#include "Configuration.h"
#include "util/log.h"
#include <features/DeviceFeature.h>
#include <backend/hidpp20/Device.h>
#include <backend/hidpp/defs.h>
#include <ipcgull/node.h>
#include <ipcgull/interface.h>
#include <Configuration.h>
namespace logid {
class DeviceManager;
namespace logid
{
class Device;
class DeviceConfig
{
class Receiver;
class InputDevice;
class DeviceNickname {
public:
DeviceConfig(const std::shared_ptr<Configuration>& config, Device*
device);
libconfig::Setting& getSetting(const std::string& path);
explicit DeviceNickname(const std::shared_ptr<DeviceManager>& manager);
DeviceNickname() = delete;
DeviceNickname(const DeviceNickname&) = delete;
~DeviceNickname();
operator std::string() const;
private:
Device* _device;
std::string _root_setting;
std::shared_ptr<Configuration> _config;
const int _nickname;
const std::weak_ptr<DeviceManager> _manager;
};
/* TODO: Implement HID++ 1.0 support
* Currently, the logid::Device class has a hardcoded requirement
* for an HID++ 2.0 device.
*/
class Device
{
class Device : public ipcgull::object {
public:
Device(std::string path, backend::hidpp::DeviceIndex index);
Device(const std::shared_ptr<backend::raw::RawDevice>& raw_device,
backend::hidpp::DeviceIndex index);
std::string name();
uint16_t pid();
DeviceConfig& config();
[[nodiscard]] config::Profile& activeProfile();
[[nodiscard]] std::vector<std::string> getProfiles() const;
void setProfile(const std::string& profile);
void setProfileDelayed(const std::string& profile);
void removeProfile(const std::string& profile);
void clearProfile(const std::string& profile);
backend::hidpp20::Device& hidpp20();
static std::shared_ptr<Device> make(
std::string path,
backend::hidpp::DeviceIndex index,
std::shared_ptr<DeviceManager> manager);
static std::shared_ptr<Device> make(
std::shared_ptr<backend::raw::RawDevice> raw_device,
backend::hidpp::DeviceIndex index,
std::shared_ptr<DeviceManager> manager);
static std::shared_ptr<Device> make(
Receiver* receiver,
backend::hidpp::DeviceIndex index,
std::shared_ptr<DeviceManager> manager);
void wakeup();
void sleep();
void reconfigure();
void reset();
[[nodiscard]] std::shared_ptr<InputDevice> virtualInput() const;
[[nodiscard]] std::shared_ptr<ipcgull::node> ipcNode() const;
template<typename T>
std::shared_ptr<T> getFeature(std::string name) {
std::shared_ptr<T> getFeature(const std::string& name) {
auto it = _features.find(name);
if(it == _features.end())
if (it == _features.end())
return nullptr;
try {
return std::dynamic_pointer_cast<T>(it->second);
} catch(std::bad_cast& e) {
logPrintf(ERROR, "bad_cast while getting device feature %s: %s",
name.c_str(), e.what());
} catch (std::bad_cast& e) {
return nullptr;
}
}
Device(const Device&) = delete;
Device(Device&&) = delete;
private:
friend class DeviceWrapper;
Device(std::string path, backend::hidpp::DeviceIndex index,
const std::shared_ptr<DeviceManager>& manager);
Device(std::shared_ptr<backend::raw::RawDevice> raw_device,
backend::hidpp::DeviceIndex index,
const std::shared_ptr<DeviceManager>& manager);
Device(Receiver* receiver, backend::hidpp::DeviceIndex index,
const std::shared_ptr<DeviceManager>& manager);
static config::Device& _getConfig(
const std::shared_ptr<DeviceManager>& manager,
const std::string& name);
void _init();
/* Adds a feature without calling an error if unsupported */
template<typename T>
void _addFeature(std::string name)
{
void _addFeature(std::string name) {
try {
_features.emplace(name, std::make_shared<T>(this));
_features.emplace(name, features::DeviceFeature::make<T>(this));
} catch (features::UnsupportedFeature& e) {
}
}
backend::hidpp20::Device _hidpp20;
std::shared_ptr<backend::hidpp20::Device> _hidpp20;
std::string _path;
backend::hidpp::DeviceIndex _index;
std::map<std::string, std::shared_ptr<features::DeviceFeature>>
_features;
DeviceConfig _config;
std::map<std::string, std::shared_ptr<features::DeviceFeature>> _features;
config::Device& _config;
mutable std::shared_mutex _profile_mutex;
ipcgull::property<std::string> _profile_name;
std::map<std::string, config::Profile>::iterator _profile;
const std::weak_ptr<DeviceManager> _manager;
void _makeResetMechanism();
std::unique_ptr<std::function<void()>> _reset_mechanism;
const DeviceNickname _nickname;
std::shared_ptr<ipcgull::node> _ipc_node;
class IPC : public ipcgull::interface {
private:
Device& _device;
public:
explicit IPC(Device* device);
void notifyStatus() const;
};
ipcgull::property<bool> _awake;
std::mutex _state_lock;
std::weak_ptr<Device> _self;
std::shared_ptr<IPC> _ipc_interface;
};
}

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,32 +19,98 @@
#ifndef LOGID_DEVICEMANAGER_H
#define LOGID_DEVICEMANAGER_H
#include <map>
#include <thread>
#include <mutex>
#include <backend/raw/DeviceMonitor.h>
#include <Device.h>
#include <Receiver.h>
#include <ipcgull/node.h>
#include <ipcgull/interface.h>
#include "backend/raw/DeviceMonitor.h"
#include "backend/hidpp/Device.h"
#include "Device.h"
#include "Receiver.h"
namespace logid {
class InputDevice;
namespace logid
{
class DeviceManager : public backend::raw::DeviceMonitor
{
class DeviceManager : public backend::raw::DeviceMonitor {
public:
DeviceManager() = default;
[[nodiscard]] std::shared_ptr<Configuration> config() const;
[[nodiscard]] std::shared_ptr<InputDevice> virtualInput() const;
[[nodiscard]] std::shared_ptr<const ipcgull::node> devicesNode() const;
[[nodiscard]] std::shared_ptr<const ipcgull::node>
receiversNode() const;
void addExternalDevice(const std::shared_ptr<Device>& d);
void removeExternalDevice(const std::shared_ptr<Device>& d);
std::mutex& mutex() const;
protected:
void addDevice(std::string path) override;
void removeDevice(std::string path) override;
DeviceManager(std::shared_ptr<Configuration> config,
std::shared_ptr<InputDevice> virtual_input,
std::shared_ptr<ipcgull::server> server);
void addDevice(std::string path) final;
void removeDevice(std::string path) final;
private:
class DevicesIPC : public ipcgull::interface {
public:
explicit DevicesIPC(DeviceManager* manager);
void deviceAdded(const std::shared_ptr<Device>& d);
void deviceRemoved(const std::shared_ptr<Device>& d);
};
[[nodiscard]]
std::vector<std::shared_ptr<Device>> listDevices() const;
class ReceiversIPC : public ipcgull::interface {
public:
explicit ReceiversIPC(DeviceManager* manager);
void receiverAdded(const std::shared_ptr<Receiver>& r);
void receiverRemoved(const std::shared_ptr<Receiver>& r);
};
[[nodiscard]]
std::vector<std::shared_ptr<Receiver>> listReceivers() const;
std::shared_ptr<ipcgull::server> _server;
std::shared_ptr<Configuration> _config;
std::shared_ptr<InputDevice> _virtual_input;
std::shared_ptr<ipcgull::node> _root_node;
std::shared_ptr<ipcgull::node> _device_node;
std::shared_ptr<ipcgull::node> _receiver_node;
std::shared_ptr<Configuration::IPC> _ipc_config;
std::shared_ptr<DevicesIPC> _ipc_devices;
std::shared_ptr<ReceiversIPC> _ipc_receivers;
std::map<std::string, std::shared_ptr<Device>> _devices;
std::map<std::string, std::shared_ptr<Receiver>> _receivers;
mutable std::mutex _map_lock;
friend class DeviceNickname;
friend class ReceiverNickname;
[[nodiscard]] int newDeviceNickname();
[[nodiscard]] int newReceiverNickname();
std::mutex _nick_lock;
std::set<int> _device_nicknames;
std::set<int> _receiver_nicknames;
};
extern std::unique_ptr<DeviceManager> device_manager;
}
#endif //LOGID_DEVICEMANAGER_H
#endif //LOGID_DEVICEMANAGER_H

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -20,6 +20,8 @@
#define LOGID_INPUTDEVICE_H
#include <memory>
#include <string>
#include <mutex>
extern "C"
{
@ -27,38 +29,61 @@ extern "C"
#include <libevdev/libevdev-uinput.h>
}
namespace logid
{
class InputDevice
{
namespace logid {
class InputDevice {
public:
class InvalidEventCode : public std::exception
{
class InvalidEventCode : public std::exception {
public:
explicit InvalidEventCode(const std::string& name);
explicit InvalidEventCode(uint code);
const char* what() const noexcept override;
private:
const std::string _what;
};
explicit InputDevice(const char *name);
explicit InputDevice(const char* name);
~InputDevice();
void registerKey(uint code);
void registerAxis(uint axis);
void moveAxis(uint axis, int movement);
void pressKey(uint code);
void releaseKey(uint code);
static std::string toKeyName(uint code);
static uint toKeyCode(const std::string& name);
static std::string toAxisName(uint code);
static uint toAxisCode(const std::string& name);
static int getLowResAxis(uint axis_code);
private:
void _sendEvent(uint type, uint code, int value);
void _enableEvent(uint type, uint name);
static std::string _toEventName(uint type, uint code);
static uint _toEventCode(uint type, const std::string& name);
bool registered_keys[KEY_CNT]{};
bool registered_axis[REL_CNT]{};
libevdev* device;
libevdev_uinput* ui_device{};
};
extern std::unique_ptr<InputDevice> virtual_input;
std::mutex _input_mutex;
};
}
#endif //LOGID_INPUTDEVICE_H
#endif //LOGID_INPUTDEVICE_H

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -20,23 +20,80 @@
#define LOGID_RECEIVER_H
#include <string>
#include "backend/dj/ReceiverMonitor.h"
#include "Device.h"
#include <Device.h>
#include <backend/hidpp10/ReceiverMonitor.h>
namespace logid
{
class Receiver : public backend::dj::ReceiverMonitor
{
namespace logid {
class ReceiverNickname {
public:
Receiver(const std::string& path);
explicit ReceiverNickname(const std::shared_ptr<DeviceManager>& manager);
ReceiverNickname() = delete;
ReceiverNickname(const ReceiverNickname&) = delete;
~ReceiverNickname();
operator std::string() const;
private:
const int _nickname;
const std::weak_ptr<DeviceManager> _manager;
};
class Receiver : public backend::hidpp10::ReceiverMonitor,
public ipcgull::object {
public:
typedef std::map<backend::hidpp::DeviceIndex, std::shared_ptr<Device>>
DeviceList;
~Receiver() noexcept override;
static std::shared_ptr<Receiver> make(
const std::string& path,
const std::shared_ptr<DeviceManager>& manager);
[[nodiscard]] const std::string& path() const;
std::shared_ptr<backend::hidpp10::Receiver> rawReceiver();
[[nodiscard]] const DeviceList& devices() const;
[[nodiscard]] std::vector<std::tuple<int, uint16_t, std::string, uint32_t>>
pairedDevices() const;
void startPair(uint8_t timeout);
void stopPair();
void unpair(int device);
protected:
Receiver(const std::string& path,
const std::shared_ptr<DeviceManager>& manager);
void addDevice(backend::hidpp::DeviceConnectionEvent event) override;
void removeDevice(backend::hidpp::DeviceIndex index) override;
void pairReady(const backend::hidpp10::DeviceDiscoveryEvent& event,
const std::string& passkey) override;
private:
std::mutex _devices_change;
std::map<backend::hidpp::DeviceIndex, std::shared_ptr<Device>> _devices;
DeviceList _devices;
std::string _path;
std::weak_ptr<DeviceManager> _manager;
const ReceiverNickname _nickname;
std::shared_ptr<ipcgull::node> _ipc_node;
class IPC : public ipcgull::interface {
public:
explicit IPC(Receiver* receiver);
};
std::shared_ptr<ipcgull::interface> _ipc_interface;
};
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,64 +16,128 @@
*
*/
#include <algorithm>
#include "Action.h"
#include "../util/log.h"
#include "KeypressAction.h"
#include "ToggleSmartShift.h"
#include "ToggleHiresScroll.h"
#include "GestureAction.h"
#include "NullAction.h"
#include "CycleDPI.h"
#include "ChangeDPI.h"
#include "ChangeHostAction.h"
#include <actions/Action.h>
#include <actions/KeypressAction.h>
#include <actions/ToggleSmartShift.h>
#include <actions/ToggleHiresScroll.h>
#include <actions/GestureAction.h>
#include <actions/NullAction.h>
#include <actions/CycleDPI.h>
#include <actions/ChangeDPI.h>
#include <actions/ChangeHostAction.h>
#include <actions/ChangeProfile.h>
#include <ipc_defs.h>
using namespace logid;
using namespace logid::actions;
std::shared_ptr<Action> Action::makeAction(Device *device, libconfig::Setting
&setting)
{
if(!setting.isGroup()) {
logPrintf(WARN, "Line %d: Action is not a group, ignoring.",
setting.getSourceLine());
throw InvalidAction();
namespace logid::actions {
template<typename T>
struct action_type {
typedef typename T::action type;
};
template<typename T>
struct action_type<const T> : action_type<T> {
};
template<typename T>
struct action_type<T&> : action_type<T> {
};
template<typename T>
std::shared_ptr<Action> _makeAction(
Device* device, T& action,
const std::shared_ptr<ipcgull::node>& parent) {
return parent->make_interface<typename action_type<T>::type>(
device, std::forward<T&>(action), parent);
}
try {
auto& action_type = setting.lookup("type");
if(action_type.getType() != libconfig::Setting::TypeString) {
logPrintf(WARN, "Line %d: Action type must be a string",
action_type.getSourceLine());
throw InvalidAction();
template<typename T>
std::shared_ptr<Action> _makeAction(
Device* device, const std::string& name,
std::optional<T>& config,
const std::shared_ptr<ipcgull::node>& parent) {
if (name == ChangeDPI::interface_name) {
config = config::ChangeDPI();
} else if (name == CycleDPI::interface_name) {
config = config::CycleDPI();
} else if (name == KeypressAction::interface_name) {
config = config::KeypressAction();
} else if (name == NullAction::interface_name) {
config = config::NoAction();
} else if (name == ChangeHostAction::interface_name) {
config = config::ChangeHost();
} else if (name == ToggleHiresScroll::interface_name) {
config = config::ToggleHiresScroll();
} else if (name == ToggleSmartShift::interface_name) {
config = config::ToggleSmartShift();
} else if (name == ChangeProfile::interface_name) {
config = config::ChangeProfile();
} else if (name == "Default") {
config.reset();
return nullptr;
} else {
throw InvalidAction(name);
}
std::string type = action_type;
std::transform(type.begin(), type.end(), type.begin(), ::tolower);
if(type == "keypress")
return std::make_shared<KeypressAction>(device, setting);
else if(type == "togglesmartshift")
return std::make_shared<ToggleSmartShift>(device);
else if(type == "togglehiresscroll")
return std::make_shared<ToggleHiresScroll>(device);
else if(type == "gestures")
return std::make_shared<GestureAction>(device, setting);
else if(type == "cycledpi")
return std::make_shared<CycleDPI>(device, setting);
else if(type == "changedpi")
return std::make_shared<ChangeDPI>(device, setting);
else if(type == "none")
return std::make_shared<NullAction>(device);
else if(type == "changehost")
return std::make_shared<ChangeHostAction>(device, setting);
else
throw InvalidAction(type);
} catch(libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: Action type is missing, ignoring.",
setting.getSourceLine());
throw InvalidAction();
return Action::makeAction(device, config.value(), parent);
}
}
}
std::shared_ptr<Action> Action::makeAction(
Device* device, const std::string& name,
std::optional<config::BasicAction>& config,
const std::shared_ptr<ipcgull::node>& parent) {
auto ret = _makeAction(device, name, config, parent);
if (ret)
ret->_self = ret;
return ret;
}
std::shared_ptr<Action> Action::makeAction(
Device* device, const std::string& name,
std::optional<config::Action>& config,
const std::shared_ptr<ipcgull::node>& parent) {
try {
auto ret = _makeAction(device, name, config, parent);
if (ret)
ret->_self = ret;
return ret;
} catch (actions::InvalidAction& e) {
if (name == GestureAction::interface_name) {
config = config::GestureAction();
return makeAction(device, config.value(), parent);
}
throw;
}
}
std::shared_ptr<Action> Action::makeAction(
Device* device, config::BasicAction& action,
const std::shared_ptr<ipcgull::node>& parent) {
std::shared_ptr<Action> ret;
std::visit([&device, &ret, &parent](auto&& x) {
ret = _makeAction(device, x, parent);
}, action);
if (ret)
ret->_self = ret;
return ret;
}
std::shared_ptr<Action> Action::makeAction(
Device* device, config::Action& action,
const std::shared_ptr<ipcgull::node>& parent) {
std::shared_ptr<Action> ret;
std::visit([&device, &ret, &parent](auto&& x) {
ret = _makeAction(device, x, parent);
}, action);
if (ret)
ret->_self = ret;
return ret;
}
Action::Action(Device* device, const std::string& name, tables t) :
ipcgull::interface(SERVICE_ROOT_NAME ".Action." + name, std::move(t)),
_device(device), _pressed(false) {
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,65 +19,81 @@
#define LOGID_ACTION_H
#include <atomic>
#include <libconfig.h++>
#include <memory>
#include <utility>
#include <shared_mutex>
#include <ipcgull/node.h>
#include <ipcgull/interface.h>
#include <config/schema.h>
namespace logid {
class Device;
namespace actions {
class InvalidAction : public std::exception
{
}
namespace logid::actions {
class InvalidAction : public std::exception {
public:
InvalidAction()
{
}
explicit InvalidAction(std::string& action) : _action (action)
{
}
const char* what() const noexcept override
{
InvalidAction() = default;
InvalidAction(std::string action) : _action(std::move(action)) {}
[[nodiscard]] const char* what() const noexcept override {
return _action.c_str();
}
private:
std::string _action;
};
class Action
{
class Action : public ipcgull::interface {
public:
static std::shared_ptr<Action> makeAction(Device* device,
libconfig::Setting& setting);
static std::shared_ptr<Action> makeAction(
Device* device, const std::string& name,
std::optional<config::BasicAction>& config,
const std::shared_ptr<ipcgull::node>& parent);
static std::shared_ptr<Action> makeAction(
Device* device, const std::string& name,
std::optional<config::Action>& config,
const std::shared_ptr<ipcgull::node>& parent);
static std::shared_ptr<Action> makeAction(
Device* device, config::BasicAction& action,
const std::shared_ptr<ipcgull::node>& parent);
static std::shared_ptr<Action> makeAction(
Device* device, config::Action& action,
const std::shared_ptr<ipcgull::node>& parent);
virtual void press() = 0;
virtual void release() = 0;
virtual void move(int16_t x, int16_t y)
{
// Suppress unused warning
(void)x; (void)y;
}
virtual bool pressed()
{
virtual void release() = 0;
virtual void move([[maybe_unused]] int16_t x, [[maybe_unused]] int16_t y) { }
virtual bool pressed() {
return _pressed;
}
virtual uint8_t reprogFlags() const = 0;
[[nodiscard]] virtual uint8_t reprogFlags() const = 0;
virtual ~Action() = default;
class Config
{
protected:
explicit Config(Device* device) : _device (device)
{
}
Device* _device;
};
protected:
explicit Action(Device* device) : _device (device), _pressed (false)
{
}
Action(Device* device, const std::string& name, tables t = {});
Device* _device;
std::atomic<bool> _pressed;
mutable std::shared_mutex _config_mutex;
template <typename T>
[[nodiscard]] std::weak_ptr<T> self() const {
return std::dynamic_pointer_cast<T>(_self.lock());
}
private:
std::weak_ptr<Action> _self;
};
}}
}
#endif //LOGID_ACTION_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,95 +15,80 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ChangeDPI.h"
#include "../Device.h"
#include "../util/task.h"
#include "../util/log.h"
#include "../backend/hidpp20/Error.h"
#include "../backend/hidpp20/features/ReprogControls.h"
#include <actions/ChangeDPI.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/task.h>
#include <util/log.h>
using namespace logid::actions;
ChangeDPI::ChangeDPI(Device *device, libconfig::Setting &setting) :
Action(device), _config(device, setting)
{
const char* ChangeDPI::interface_name = "ChangeDPI";
ChangeDPI::ChangeDPI(
Device* device, config::ChangeDPI& config,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(device, interface_name, {
{
{"GetConfig", {this, &ChangeDPI::getConfig, {"change", "sensor"}}},
{"SetChange", {this, &ChangeDPI::setChange, {"change"}}},
{"SetSensor", {this, &ChangeDPI::setSensor, {"sensor", "reset"}}},
},
{},
{}}), _config(config) {
_dpi = _device->getFeature<features::DPI>("dpi");
if(!_dpi)
logPrintf(WARN, "%s:%d: DPI feature not found, cannot use "
"ChangeDPI action.",
if (!_dpi)
logPrintf(WARN, "%s:%d: DPI feature not found, cannot use ChangeDPI action.",
_device->hidpp20().devicePath().c_str(),
_device->hidpp20().deviceIndex());
}
void ChangeDPI::press()
{
void ChangeDPI::press() {
_pressed = true;
if(_dpi) {
task::spawn([this]{
try {
uint16_t last_dpi = _dpi->getDPI(_config.sensor());
_dpi->setDPI(last_dpi + _config.interval(), _config.sensor());
} catch (backend::hidpp20::Error& e) {
if(e.code() == backend::hidpp20::Error::InvalidArgument)
logPrintf(WARN, "%s:%d: Could not get/set DPI for sensor "
"%d",
_device->hidpp20().devicePath().c_str(),
_device->hidpp20().deviceIndex(),
_config.sensor());
else
throw e;
std::shared_lock lock(_config_mutex);
if (_dpi && _config.inc.has_value()) {
run_task([self_weak = self<ChangeDPI>(),
sensor = _config.sensor.value_or(0), inc = _config.inc.value()] {
if (auto self = self_weak.lock()) {
try {
uint16_t last_dpi = self->_dpi->getDPI(sensor);
self->_dpi->setDPI(last_dpi + inc, sensor);
} catch (backend::hidpp20::Error& e) {
if (e.code() == backend::hidpp20::Error::InvalidArgument)
logPrintf(WARN, "%s:%d: Could not get/set DPI for sensor %d",
self->_device->hidpp20().devicePath().c_str(),
self->_device->hidpp20().deviceIndex(), sensor);
else
throw e;
}
}
});
}
}
void ChangeDPI::release()
{
void ChangeDPI::release() {
_pressed = false;
}
uint8_t ChangeDPI::reprogFlags() const
{
uint8_t ChangeDPI::reprogFlags() const {
return backend::hidpp20::ReprogControls::TemporaryDiverted;
}
ChangeDPI::Config::Config(Device *device, libconfig::Setting &config) :
Action::Config(device), _interval (0), _sensor (0)
{
if(!config.isGroup()) {
logPrintf(WARN, "Line %d: action must be an object, skipping.",
config.getSourceLine());
return;
}
try {
auto& inc = config.lookup("inc");
if(inc.getType() != libconfig::Setting::TypeInt)
logPrintf(WARN, "Line %d: inc must be an integer",
inc.getSourceLine());
_interval = (int)inc;
} catch(libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: inc is a required field, skipping.",
config.getSourceLine());
}
try {
auto& sensor = config.lookup("sensor");
if(sensor.getType() != libconfig::Setting::TypeInt)
logPrintf(WARN, "Line %d: sensor must be an integer",
sensor.getSourceLine());
_sensor = (int)sensor;
} catch(libconfig::SettingNotFoundException& e) {
// Ignore
}
std::tuple<int16_t, uint16_t> ChangeDPI::getConfig() const {
std::shared_lock lock(_config_mutex);
return {_config.inc.value_or(0), _config.sensor.value_or(0)};
}
uint16_t ChangeDPI::Config::interval() const
{
return _interval;
void ChangeDPI::setChange(int16_t change) {
std::unique_lock lock(_config_mutex);
_config.inc = change;
}
uint8_t ChangeDPI::Config::sensor() const
{
return _sensor;
}
void ChangeDPI::setSensor(uint8_t sensor, bool reset) {
std::unique_lock lock(_config_mutex);
if (reset) {
_config.sensor.reset();
} else {
_config.sensor = sensor;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,37 +18,33 @@
#ifndef LOGID_ACTION_CHANGEDPI_H
#define LOGID_ACTION_CHANGEDPI_H
#include <libconfig.h++>
#include "Action.h"
#include "../features/DPI.h"
#include <actions/Action.h>
#include <features/DPI.h>
namespace logid {
namespace actions {
class ChangeDPI : public Action
{
public:
explicit ChangeDPI(Device* device, libconfig::Setting& setting);
namespace logid::actions {
class ChangeDPI : public Action {
public:
static const char* interface_name;
virtual void press();
virtual void release();
ChangeDPI(Device* device, config::ChangeDPI& setting,
const std::shared_ptr<ipcgull::node>& parent);
virtual uint8_t reprogFlags() const;
void press() final;
class Config : public Action::Config
{
public:
Config(Device* device, libconfig::Setting& setting);
uint16_t interval() const;
uint8_t sensor() const;
private:
uint16_t _interval;
uint8_t _sensor;
};
void release() final;
protected:
Config _config;
std::shared_ptr<features::DPI> _dpi;
};
}}
[[nodiscard]] std::tuple<int16_t, uint16_t> getConfig() const;
void setChange(int16_t change);
void setSensor(uint8_t sensor, bool reset);
[[nodiscard]] uint8_t reprogFlags() const final;
protected:
config::ChangeDPI& _config;
std::shared_ptr<features::DPI> _dpi;
};
}
#endif //LOGID_ACTION_CHANGEDPI_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,105 +15,98 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <actions/ChangeHostAction.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <algorithm>
#include "ChangeHostAction.h"
#include "../Device.h"
#include "../backend/hidpp20/features/ReprogControls.h"
#include "../util/task.h"
#include <util/task.h>
#include <util/log.h>
using namespace logid::actions;
using namespace logid::backend;
ChangeHostAction::ChangeHostAction(Device *device, libconfig::Setting&
config) : Action(device), _config (device, config)
{
const char* ChangeHostAction::interface_name = "ChangeHost";
ChangeHostAction::ChangeHostAction(
Device* device, config::ChangeHost& config,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent)
: Action(device, interface_name, {
{
{"GetHost", {this, &ChangeHostAction::getHost, {"host"}}},
{"SetHost", {this, &ChangeHostAction::setHost, {"host"}}}
},
{},
{}
}), _config(config) {
if (_config.host.has_value()) {
if (std::holds_alternative<std::string>(_config.host.value())) {
auto& host = std::get<std::string>(_config.host.value());
std::transform(host.begin(), host.end(),
host.begin(), ::tolower);
}
}
try {
_change_host = std::make_shared<hidpp20::ChangeHost>(&device->hidpp20());
} catch (hidpp20::UnsupportedFeature& e) {
logPrintf(WARN, "%s:%d: ChangeHost feature not supported, "
"ChangeHostAction will not work.", device->hidpp20()
.devicePath().c_str(), device->hidpp20().deviceIndex());
.devicePath().c_str(), device->hidpp20().deviceIndex());
}
}
void ChangeHostAction::press()
{
std::string ChangeHostAction::getHost() const {
std::shared_lock lock(_config_mutex);
if (_config.host.has_value()) {
if (std::holds_alternative<std::string>(_config.host.value()))
return std::get<std::string>(_config.host.value());
else
return std::to_string(std::get<int>(_config.host.value()));
} else {
return "";
}
}
void ChangeHostAction::setHost(std::string host) {
std::transform(host.begin(), host.end(),
host.begin(), ::tolower);
std::unique_lock lock(_config_mutex);
if (host == "next" || host == "prev" || host == "previous") {
_config.host = std::move(host);
} else {
_config.host = std::stoi(host);
}
}
void ChangeHostAction::press() {
// Do nothing, wait until release
}
void ChangeHostAction::release()
{
if(_change_host) {
task::spawn([this] {
auto host_info = _change_host->getHostInfo();
auto next_host = _config.nextHost(host_info);
if(next_host != host_info.currentHost)
_change_host->setHost(next_host);
void ChangeHostAction::release() {
std::shared_lock lock(_config_mutex);
if (_change_host && _config.host.has_value()) {
run_task([self_weak = self<ChangeHostAction>(), host = _config.host.value()] {
if (auto self = self_weak.lock()) {
auto host_info = self->_change_host->getHostInfo();
int next_host;
if (std::holds_alternative<std::string>(host)) {
const auto& host_str = std::get<std::string>(host);
if (host_str == "next")
next_host = host_info.currentHost + 1;
else if (host_str == "prev" || host_str == "previous")
next_host = host_info.currentHost - 1;
else
next_host = host_info.currentHost;
} else {
next_host = std::get<int>(host) - 1;
}
next_host %= host_info.hostCount;
if (next_host != host_info.currentHost)
self->_change_host->setHost(next_host);
}
});
}
}
uint8_t ChangeHostAction::reprogFlags() const
{
uint8_t ChangeHostAction::reprogFlags() const {
return hidpp20::ReprogControls::TemporaryDiverted;
}
ChangeHostAction::Config::Config(Device *device, libconfig::Setting& config)
: Action::Config(device)
{
try {
auto& host = config.lookup("host");
if(host.getType() == libconfig::Setting::TypeInt) {
_offset = false;
_host = host;
_host--; // hosts are one-indexed in config
if(_host < 0) {
logPrintf(WARN, "Line %d: host must be positive.",
host.getSourceLine());
_offset = true;
_host = 0;
}
} else if(host.getType() == libconfig::Setting::TypeString) {
_offset = true;
std::string hostmode = host;
std::transform(hostmode.begin(), hostmode.end(),
hostmode.begin(), ::tolower);
if(hostmode == "next")
_host = 1;
else if(hostmode == "prev" || hostmode == "previous")
_host = -1;
else {
logPrintf(WARN, "Line %d: host must equal an integer, 'next',"
"or 'prev'.", host.getSourceLine());
_host = 0;
}
} else {
logPrintf(WARN, "Line %d: host must equal an integer, 'next',"
"or 'prev'.", host.getSourceLine());
_offset = true;
_host = 0;
}
} catch (libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: host is a required field, skipping.",
config.getSourceLine());
_offset = true;
_host = 0;
}
}
uint8_t ChangeHostAction::Config::nextHost(hidpp20::ChangeHost::HostInfo info)
{
if(_offset) {
return (info.currentHost + _host) % info.hostCount;
} else {
if(_host >= info.hostCount || _host < 0) {
logPrintf(WARN, "No such host %d, defaulting to current.",
_host+1);
return info.currentHost;
} else
return _host;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,37 +18,31 @@
#ifndef LOGID_ACTION_CHANGEHOSTACTION_H
#define LOGID_ACTION_CHANGEHOSTACTION_H
#include <libconfig.h++>
#include "Action.h"
#include "../backend/hidpp20/features/ChangeHost.h"
#include <actions/Action.h>
#include <backend/hidpp20/features/ChangeHost.h>
namespace logid {
namespace actions
{
class ChangeHostAction : public Action
{
namespace logid::actions {
class ChangeHostAction : public Action {
public:
ChangeHostAction(Device* device, libconfig::Setting& config);
static const char* interface_name;
virtual void press();
virtual void release();
ChangeHostAction(Device* device, config::ChangeHost& config,
const std::shared_ptr<ipcgull::node>& parent);
virtual uint8_t reprogFlags() const;
void press() final;
class Config : public Action::Config
{
public:
Config(Device* device, libconfig::Setting& setting);
uint8_t nextHost(backend::hidpp20::ChangeHost::HostInfo info);
private:
bool _offset;
int _host;
};
void release() final;
[[nodiscard]] std::string getHost() const;
void setHost(std::string host);
[[nodiscard]] uint8_t reprogFlags() const final;
protected:
std::shared_ptr<backend::hidpp20::ChangeHost> _change_host;
Config _config;
config::ChangeHost& _config;
};
}}
}
#endif //LOGID_ACTION_CHANGEHOSTACTION_H

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,34 +15,33 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_CHANGEPROFILE_H
#define LOGID_CHANGEPROFILE_H
#ifndef LOGID_BACKEND_DJ_ERROR_H
#define LOGID_BACKEND_DJ_ERROR_H
#include <actions/Action.h>
#include <cstdint>
#include <stdexcept>
namespace logid {
namespace backend {
namespace dj
{
class Error : public std::exception
{
namespace logid::actions {
class ChangeProfile : public Action {
public:
enum ErrorCode : uint8_t
{
Unknown = 0x00,
KeepAliveTimeout = 0x01
};
static const char* interface_name;
explicit Error(uint8_t code);
ChangeProfile(Device* device, config::ChangeProfile& setting,
const std::shared_ptr<ipcgull::node>& parent);
const char* what() const noexcept override;
uint8_t code() const noexcept;
void press() final;
void release() final;
[[nodiscard]] uint8_t reprogFlags() const final;
std::string getProfile();
void setProfile(std::string profile);
private:
uint8_t _code;
config::ChangeProfile& _config;
};
}}}
}
#endif //LOGID_BACKEND_DJ_ERROR_H
#endif //LOGID_CHANGEPROFILE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,119 +15,84 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "CycleDPI.h"
#include "../Device.h"
#include "../util/task.h"
#include "../util/log.h"
#include "../backend/hidpp20/Error.h"
#include "../backend/hidpp20/features/ReprogControls.h"
#include <actions/CycleDPI.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/task.h>
#include <util/log.h>
using namespace logid::actions;
using namespace libconfig;
CycleDPI::CycleDPI(Device* device, libconfig::Setting& setting) :
Action (device), _config (device, setting)
{
const char* CycleDPI::interface_name = "CycleDPI";
CycleDPI::CycleDPI(Device* device, config::CycleDPI& config,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(device, interface_name, {
{
{"GetDPIs", {this, &CycleDPI::getDPIs, {"dpis"}}},
{"SetDPIs", {this, &CycleDPI::setDPIs, {"dpis"}}}
},
{},
{}
}),
_config(config) {
_dpi = _device->getFeature<features::DPI>("dpi");
if(!_dpi)
if (!_dpi)
logPrintf(WARN, "%s:%d: DPI feature not found, cannot use "
"CycleDPI action.",
_device->hidpp20().devicePath().c_str(),
_device->hidpp20().deviceIndex());
if (_config.dpis.has_value()) {
_current_dpi = _config.dpis.value().begin();
}
}
void CycleDPI::press()
{
std::vector<int> CycleDPI::getDPIs() const {
std::shared_lock lock(_config_mutex);
auto dpis = _config.dpis.value_or(std::list<int>());
return {dpis.begin(), dpis.end()};
}
void CycleDPI::setDPIs(const std::vector<int>& dpis) {
std::unique_lock lock(_config_mutex);
std::lock_guard dpi_lock(_dpi_mutex);
_config.dpis.emplace(dpis.begin(), dpis.end());
_current_dpi = _config.dpis->cbegin();
}
void CycleDPI::press() {
_pressed = true;
if(_dpi && !_config.empty()) {
task::spawn([this](){
uint16_t dpi = _config.nextDPI();
try {
_dpi->setDPI(dpi, _config.sensor());
} catch (backend::hidpp20::Error& e) {
if(e.code() == backend::hidpp20::Error::InvalidArgument)
logPrintf(WARN, "%s:%d: Could not set DPI to %d for "
"sensor %d", _device->hidpp20().devicePath().c_str(),
_device->hidpp20().deviceIndex(), dpi,
_config.sensor());
else
throw e;
std::shared_lock lock(_config_mutex);
std::lock_guard dpi_lock(_dpi_mutex);
if (_dpi && _config.dpis.has_value() && _config.dpis.value().empty()) {
++_current_dpi;
if (_current_dpi == _config.dpis.value().end())
_current_dpi = _config.dpis.value().begin();
run_task([self_weak = self<CycleDPI>(), dpi = *_current_dpi] {
if (auto self = self_weak.lock()) {
try {
self->_dpi->setDPI(dpi, self->_config.sensor.value_or(0));
} catch (backend::hidpp20::Error& e) {
if (e.code() == backend::hidpp20::Error::InvalidArgument)
logPrintf(WARN, "%s:%d: Could not set DPI to %d for "
"sensor %d",
self->_device->hidpp20().devicePath().c_str(),
self->_device->hidpp20().deviceIndex(), dpi,
self->_config.sensor.value_or(0));
else
throw e;
}
}
});
}
}
void CycleDPI::release()
{
void CycleDPI::release() {
_pressed = false;
}
uint8_t CycleDPI::reprogFlags() const
{
uint8_t CycleDPI::reprogFlags() const {
return backend::hidpp20::ReprogControls::TemporaryDiverted;
}
CycleDPI::Config::Config(Device *device, libconfig::Setting &config) :
Action::Config(device), _current_index (0), _sensor (0)
{
if(!config.isGroup()) {
logPrintf(WARN, "Line %d: action must be an object, skipping.",
config.getSourceLine());
return;
}
try {
auto& sensor = config.lookup("sensor");
if(sensor.getType() != Setting::TypeInt)
logPrintf(WARN, "Line %d: sensor must be an integer",
sensor.getSourceLine());
_sensor = (int)sensor;
} catch(libconfig::SettingNotFoundException& e) {
// Ignore
}
try {
auto& dpis = config.lookup("dpis");
if(!dpis.isList() && !dpis.isArray()) {
logPrintf(WARN, "Line %d: dpis must be a list or array, skipping.",
dpis.getSourceLine());
return;
}
int dpi_count = dpis.getLength();
for(int i = 0; i < dpi_count; i++) {
if(dpis[i].getType() != Setting::TypeInt) {
logPrintf(WARN, "Line %d: dpis must be integers, skipping.",
dpis[i].getSourceLine());
if(dpis.isList())
continue;
else
break;
}
_dpis.push_back((int)(dpis[i]));
}
} catch (libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: dpis is a required field, skipping.",
config.getSourceLine());
}
}
uint16_t CycleDPI::Config::nextDPI()
{
uint16_t dpi = _dpis[_current_index++];
if(_current_index >= _dpis.size())
_current_index = 0;
return dpi;
}
bool CycleDPI::Config::empty() const
{
return _dpis.empty();
}
uint8_t CycleDPI::Config::sensor() const
{
return _sensor;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,39 +18,33 @@
#ifndef LOGID_ACTION_CYCLEDPI_H
#define LOGID_ACTION_CYCLEDPI_H
#include <libconfig.h++>
#include "Action.h"
#include "../features/DPI.h"
#include <actions/Action.h>
#include <features/DPI.h>
namespace logid {
namespace actions {
class CycleDPI : public Action
{
namespace logid::actions {
class CycleDPI : public Action {
public:
explicit CycleDPI(Device* device, libconfig::Setting& setting);
static const char* interface_name;
virtual void press();
virtual void release();
CycleDPI(Device* device, config::CycleDPI& setting,
const std::shared_ptr<ipcgull::node>& parent);
virtual uint8_t reprogFlags() const;
void press() final;
class Config : public Action::Config
{
public:
Config(Device* device, libconfig::Setting& setting);
uint16_t nextDPI();
bool empty() const;
uint8_t sensor() const;
private:
std::size_t _current_index;
std::vector<uint16_t> _dpis;
uint8_t _sensor;
};
void release() final;
[[nodiscard]] std::vector<int> getDPIs() const;
void setDPIs(const std::vector<int>& dpis);
[[nodiscard]] uint8_t reprogFlags() const final;
protected:
Config _config;
std::mutex _dpi_mutex;
config::CycleDPI& _config;
std::shared_ptr<features::DPI> _dpi;
std::list<int>::const_iterator _current_dpi;
};
}}
}
#endif //LOGID_ACTION_CYCLEDPI_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,255 +15,247 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <actions/GestureAction.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/log.h>
#include <algorithm>
#include "GestureAction.h"
#include "../Device.h"
#include "../backend/hidpp20/features/ReprogControls.h"
using namespace logid::actions;
using namespace logid;
using namespace logid::backend;
GestureAction::Direction GestureAction::toDirection(std::string direction)
{
const char* GestureAction::interface_name = "Gesture";
GestureAction::Direction GestureAction::toDirection(std::string direction) {
std::transform(direction.begin(), direction.end(), direction.begin(),
::tolower);
if(direction == "up")
::tolower);
if (direction == "up")
return Up;
else if(direction == "down")
else if (direction == "down")
return Down;
else if(direction == "left")
else if (direction == "left")
return Left;
else if(direction == "right")
else if (direction == "right")
return Right;
else if(direction == "none")
else if (direction == "none")
return None;
else
throw std::invalid_argument("direction");
}
GestureAction::Direction GestureAction::toDirection(int16_t x, int16_t y)
{
if(x >= 0 && y >= 0)
std::string GestureAction::fromDirection(Direction direction) {
switch (direction) {
case Up:
return "up";
case Down:
return "down";
case Left:
return "left";
case Right:
return "right";
case None:
return "none";
}
// This shouldn't happen
throw InvalidGesture();
}
GestureAction::Direction GestureAction::toDirection(int32_t x, int32_t y) {
if (x >= 0 && y >= 0)
return x >= y ? Right : Down;
else if(x < 0 && y >= 0)
else if (x < 0 && y >= 0)
return -x <= y ? Down : Left;
else if(x <= 0 && y < 0)
else if (x <= 0 /* && y < 0 */)
return x <= y ? Left : Up;
else
return x <= -y ? Up : Right;
}
GestureAction::GestureAction(Device* dev, libconfig::Setting& config) :
Action (dev), _config (dev, config)
{
GestureAction::GestureAction(Device* dev, config::GestureAction& config,
const std::shared_ptr<ipcgull::node>& parent) :
Action(dev, interface_name,
{
{
{"SetGesture", {this, &GestureAction::setGesture, {"direction", "type"}}}
},
{},
{}
}),
_node(parent->make_child("gestures")), _config(config) {
if (_config.gestures.has_value()) {
auto& gestures = _config.gestures.value();
for (auto&& x: gestures) {
try {
auto direction = toDirection(x.first);
_gestures.emplace(
direction,Gesture::makeGesture(
dev, x.second,
_node->make_child(fromDirection(direction))));
if (direction == None) {
auto& gesture = x.second;
std::visit([](auto&& x) {
x.threshold.emplace(0);
}, gesture);
}
} catch (std::invalid_argument& e) {
logPrintf(WARN, "%s is not a direction", x.first.c_str());
}
}
}
}
void GestureAction::press()
{
void GestureAction::press() {
std::shared_lock lock(_config_mutex);
_pressed = true;
_x = 0, _y = 0;
for(auto& gesture : _config.gestures())
gesture.second->press();
for (auto& gesture: _gestures)
gesture.second->press(false);
}
void GestureAction::release()
{
void GestureAction::release() {
std::shared_lock lock(_config_mutex);
_pressed = false;
bool threshold_met = false;
auto d = toDirection(_x, _y);
auto primary_gesture = _config.gestures().find(d);
if(primary_gesture != _config.gestures().end()) {
auto primary_gesture = _gestures.find(d);
if (primary_gesture != _gestures.end()) {
threshold_met = primary_gesture->second->metThreshold();
primary_gesture->second->release(true);
}
for(auto& gesture : _config.gestures()) {
if(gesture.first == d)
for (auto& gesture: _gestures) {
if (gesture.first == d || gesture.first == None)
continue;
if(!threshold_met) {
if(gesture.second->metThreshold()) {
if (!threshold_met) {
if (gesture.second->metThreshold()) {
// If the primary gesture did not meet its threshold, use the
// secondary one.
threshold_met = true;
gesture.second->release(true);
break;
}
} else {
gesture.second->release(false);
}
}
if(!threshold_met) {
if(_config.noneAction()) {
_config.noneAction()->press();
_config.noneAction()->release();
}
auto none_gesture = _gestures.find(None);
if (none_gesture != _gestures.end()) {
none_gesture->second->release(!threshold_met);
}
}
void GestureAction::move(int16_t x, int16_t y)
{
auto new_x = _x + x, new_y = _y + y;
void GestureAction::move(int16_t x, int16_t y) {
std::shared_lock lock(_config_mutex);
if(abs(x) > 0) {
if(_x < 0 && new_x >= 0) { // Left -> Origin/Right
auto left = _config.gestures().find(Left);
if(left != _config.gestures().end())
left->second->move(_x);
if(new_x) { // Ignore to origin
auto right = _config.gestures().find(Right);
if(right != _config.gestures().end())
right->second->move(new_x);
int32_t new_x = _x + x, new_y = _y + y;
if (abs(x) > 0) {
if (_x < 0 && new_x >= 0) { // Left -> Origin/Right
auto left = _gestures.find(Left);
if (left != _gestures.end() && left->second)
left->second->move((int16_t) _x);
if (new_x) { // Ignore to origin
auto right = _gestures.find(Right);
if (right != _gestures.end() && right->second)
right->second->move((int16_t) new_x);
}
} else if(_x > 0 && new_x <= 0) { // Right -> Origin/Left
auto right = _config.gestures().find(Right);
if(right != _config.gestures().end())
right->second->move(-_x);
if(new_x) { // Ignore to origin
auto left = _config.gestures().find(Left);
if(left != _config.gestures().end())
left->second->move(-new_x);
} else if (_x > 0 && new_x <= 0) { // Right -> Origin/Left
auto right = _gestures.find(Right);
if (right != _gestures.end() && right->second)
right->second->move((int16_t) -_x);
if (new_x) { // Ignore to origin
auto left = _gestures.find(Left);
if (left != _gestures.end() && left->second)
left->second->move((int16_t) -new_x);
}
} else if(new_x < 0) { // Origin/Left to Left
auto left = _config.gestures().find(Left);
if(left != _config.gestures().end())
left->second->move(-x);
} else if(new_x > 0) { // Origin/Right to Right
auto right = _config.gestures().find(Right);
if(right != _config.gestures().end())
} else if (new_x < 0) { // Origin/Left to Left
auto left = _gestures.find(Left);
if (left != _gestures.end() && left->second)
left->second->move((int16_t) -x);
} else if (new_x > 0) { // Origin/Right to Right
auto right = _gestures.find(Right);
if (right != _gestures.end() && right->second)
right->second->move(x);
}
}
if(abs(y) > 0) {
if(_y > 0 && new_y <= 0) { // Up -> Origin/Down
auto up = _config.gestures().find(Up);
if(up != _config.gestures().end())
up->second->move(_y);
if(new_y) { // Ignore to origin
auto down = _config.gestures().find(Down);
if(down != _config.gestures().end())
down->second->move(new_y);
if (abs(y) > 0) {
if (_y > 0 && new_y <= 0) { // Up -> Origin/Down
auto up = _gestures.find(Up);
if (up != _gestures.end() && up->second)
up->second->move((int16_t) _y);
if (new_y) { // Ignore to origin
auto down = _gestures.find(Down);
if (down != _gestures.end() && down->second)
down->second->move((int16_t) new_y);
}
} else if(_y < 0 && new_y >= 0) { // Down -> Origin/Up
auto down = _config.gestures().find(Down);
if(down != _config.gestures().end())
down->second->move(-_y);
if(new_y) { // Ignore to origin
auto up = _config.gestures().find(Up);
if(up != _config.gestures().end())
up->second->move(-new_y);
} else if (_y < 0 && new_y >= 0) { // Down -> Origin/Up
auto down = _gestures.find(Down);
if (down != _gestures.end() && down->second)
down->second->move((int16_t) -_y);
if (new_y) { // Ignore to origin
auto up = _gestures.find(Up);
if (up != _gestures.end() && up->second)
up->second->move((int16_t) -new_y);
}
} else if(new_y < 0) { // Origin/Up to Up
auto up = _config.gestures().find(Up);
if(up != _config.gestures().end())
up->second->move(-y);
} else if(new_y > 0) {// Origin/Down to Down
auto down = _config.gestures().find(Down);
if(down != _config.gestures().end())
} else if (new_y < 0) { // Origin/Up to Up
auto up = _gestures.find(Up);
if (up != _gestures.end() && up->second)
up->second->move((int16_t) -y);
} else if (new_y > 0) {// Origin/Down to Down
auto down = _gestures.find(Down);
if (down != _gestures.end() && down->second)
down->second->move(y);
}
}
_x = new_x; _y = new_y;
_x = new_x;
_y = new_y;
}
uint8_t GestureAction::reprogFlags() const
{
uint8_t GestureAction::reprogFlags() const {
return (hidpp20::ReprogControls::TemporaryDiverted |
hidpp20::ReprogControls::RawXYDiverted);
hidpp20::ReprogControls::RawXYDiverted);
}
GestureAction::Config::Config(Device* device, libconfig::Setting &root) :
Action::Config(device)
{
void GestureAction::setGesture(const std::string& direction, const std::string& type) {
std::unique_lock lock(_config_mutex);
Direction d = toDirection(direction);
auto it = _gestures.find(d);
if (it != _gestures.end()) {
if (pressed()) {
auto current = toDirection(_x, _y);
if (it->second)
it->second->release(current == d);
}
}
auto dir_name = fromDirection(d);
auto& gesture = _config.gestures.value()[dir_name];
_gestures[d].reset();
try {
auto& gestures = root.lookup("gestures");
_gestures[d] = Gesture::makeGesture(
_device, type, gesture,
_node->make_child(dir_name));
} catch (InvalidGesture& e) {
_gestures[d] = Gesture::makeGesture(
_device, gesture,
_node->make_child(dir_name));
throw std::invalid_argument("Invalid gesture type");
}
if(!gestures.isList()) {
logPrintf(WARN, "Line %d: gestures must be a list, ignoring.",
gestures.getSourceLine());
return;
}
int gesture_count = gestures.getLength();
for(int i = 0; i < gesture_count; i++) {
if(!gestures[i].isGroup()) {
logPrintf(WARN, "Line %d: gesture must be a group, skipping.",
gestures[i].getSourceLine());
continue;
}
Direction d;
try {
auto& direction = gestures[i].lookup("direction");
if(direction.getType() != libconfig::Setting::TypeString) {
logPrintf(WARN, "Line %d: direction must be a string, "
"skipping.", direction.getSourceLine());
continue;
}
try {
d = toDirection(direction);
} catch(std::invalid_argument& e) {
logPrintf(WARN, "Line %d: Invalid direction %s",
direction.getSourceLine(), (const char*)direction);
continue;
}
} catch(libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: direction is a required field, "
"skipping.", gestures[i].getSourceLine());
continue;
}
if(_gestures.find(d) != _gestures.end() || (d == None && _none_action)) {
logPrintf(WARN, "Line %d: Gesture is already defined for "
"this direction, duplicate ignored.",
gestures[i].getSourceLine());
continue;
}
if(d == None) {
try {
_none_action = Action::makeAction(_device,
gestures[i].lookup("action"));
} catch (InvalidAction& e) {
logPrintf(WARN, "Line %d: %s is not a valid action, "
"skipping.", gestures[i].lookup("action")
.getSourceLine(), e.what());
} catch (libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: action is a required field, "
"skipping.", gestures[i].getSourceLine(),
e.what());
}
continue;
}
try {
_gestures.emplace(d, Gesture::makeGesture(_device,
gestures[i]));
} catch(InvalidGesture& e) {
logPrintf(WARN, "Line %d: Invalid gesture: %s",
gestures[i].getSourceLine(), e.what());
}
}
} catch(libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: gestures is a required field, ignoring.",
root.getSourceLine());
if (d == None) {
std::visit([](auto&& x) {
x.threshold = 0;
}, gesture);
}
}
std::map<GestureAction::Direction, std::shared_ptr<Gesture>>&
GestureAction::Config::gestures()
{
return _gestures;
}
std::shared_ptr<Action> GestureAction::Config::noneAction()
{
return _none_action;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,49 +19,48 @@
#define LOGID_ACTION_GESTUREACTION_H
#include <map>
#include <libconfig.h++>
#include "Action.h"
#include "gesture/Gesture.h"
#include <actions/Action.h>
#include <actions/gesture/Gesture.h>
namespace logid {
namespace actions {
class GestureAction : public Action
{
namespace logid::actions {
class GestureAction : public Action {
public:
enum Direction
{
static const char* interface_name;
enum Direction {
None,
Up,
Down,
Left,
Right
};
static Direction toDirection(std::string direction);
static Direction toDirection(int16_t x, int16_t y);
GestureAction(Device* dev, libconfig::Setting& config);
static std::string fromDirection(Direction direction);
virtual void press();
virtual void release();
virtual void move(int16_t x, int16_t y);
static Direction toDirection(int32_t x, int32_t y);
virtual uint8_t reprogFlags() const;
GestureAction(Device* dev, config::GestureAction& config,
const std::shared_ptr<ipcgull::node>& parent);
class Config : public Action::Config
{
public:
Config(Device* device, libconfig::Setting& root);
std::map<Direction, std::shared_ptr<Gesture>>& gestures();
std::shared_ptr<Action> noneAction();
protected:
std::map<Direction, std::shared_ptr<Gesture>> _gestures;
std::shared_ptr<Action> _none_action;
};
void press() final;
void release() final;
void move(int16_t x, int16_t y) final;
uint8_t reprogFlags() const final;
void setGesture(const std::string& direction,
const std::string& type);
protected:
int16_t _x, _y;
Config _config;
int32_t _x{}, _y{};
std::shared_ptr<ipcgull::node> _node;
std::map<Direction, std::shared_ptr<Gesture>> _gestures;
config::GestureAction& _config;
};
}}
}
#endif //LOGID_ACTION_GESTUREACTION_H

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,32 +19,33 @@
#define LOGID_ACTION_KEYPRESS_H
#include <vector>
#include <libconfig.h++>
#include "Action.h"
#include <actions/Action.h>
namespace logid {
namespace actions {
class KeypressAction : public Action
{
namespace logid::actions {
class KeypressAction : public Action {
public:
KeypressAction(Device* dev, libconfig::Setting& config);
static const char* interface_name;
virtual void press();
virtual void release();
KeypressAction(Device* dev,
config::KeypressAction& config,
const std::shared_ptr<ipcgull::node>& parent);
virtual uint8_t reprogFlags() const;
void press() final;
void release() final;
[[nodiscard]] std::vector<std::string> getKeys() const;
void setKeys(const std::vector<std::string>& keys);
[[nodiscard]] uint8_t reprogFlags() const final;
class Config : public Action::Config
{
public:
explicit Config(Device* device, libconfig::Setting& root);
std::vector<uint>& keys();
protected:
std::vector<uint> _keys;
};
protected:
Config _config;
config::KeypressAction& _config;
std::list<uint> _keys;
void _setConfig();
};
}}
}
#endif //LOGID_ACTION_KEYPRESS_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,27 +15,27 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "NullAction.h"
#include "../Device.h"
#include "../backend/hidpp20/features/ReprogControls.h"
#include <actions/NullAction.h>
#include <backend/hidpp20/features/ReprogControls.h>
using namespace logid::actions;
NullAction::NullAction(Device* device) : Action(device)
{
const char* NullAction::interface_name = "None";
NullAction::NullAction(
Device* device,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(device, interface_name) {
}
void NullAction::press()
{
void NullAction::press() {
_pressed = true;
}
void NullAction::release()
{
void NullAction::release() {
_pressed = false;
}
uint8_t NullAction::reprogFlags() const
{
uint8_t NullAction::reprogFlags() const {
return backend::hidpp20::ReprogControls::TemporaryDiverted;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,22 +18,27 @@
#ifndef LOGID_ACTION_NULL_H
#define LOGID_ACTION_NULL_H
#include "Action.h"
#include <actions/Action.h>
namespace logid {
namespace actions
{
class NullAction : public Action
{
namespace logid::actions {
class NullAction : public Action {
public:
explicit NullAction(Device* device);
static const char* interface_name;
virtual void press();
virtual void release();
NullAction(Device* device,
const std::shared_ptr<ipcgull::node>& parent);
virtual uint8_t reprogFlags() const;
NullAction(Device* device, [[maybe_unused]] config::NoAction& config,
const std::shared_ptr<ipcgull::node>& parent) :
NullAction(device, parent) {}
void press() final;
void release() final;
[[nodiscard]] uint8_t reprogFlags() const final;
};
}}
}
#endif //LOGID_ACTION_NULL_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,43 +15,46 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ToggleHiresScroll.h"
#include "../Device.h"
#include "../util/task.h"
#include "../backend/hidpp20/features/ReprogControls.h"
#include <actions/ToggleHiresScroll.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/task.h>
#include <util/log.h>
using namespace logid::actions;
using namespace logid::backend;
ToggleHiresScroll::ToggleHiresScroll(Device *dev) : Action (dev)
{
const char* ToggleHiresScroll::interface_name = "ToggleHiresScroll";
ToggleHiresScroll::ToggleHiresScroll(
Device* dev,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(dev, interface_name) {
_hires_scroll = _device->getFeature<features::HiresScroll>("hiresscroll");
if(!_hires_scroll)
if (!_hires_scroll)
logPrintf(WARN, "%s:%d: HiresScroll feature not found, cannot use "
"ToggleHiresScroll action.",
_device->hidpp20().devicePath().c_str(),
_device->hidpp20().devicePath().c_str());
}
void ToggleHiresScroll::press()
{
void ToggleHiresScroll::press() {
_pressed = true;
if(_hires_scroll)
{
task::spawn([hires=this->_hires_scroll](){
auto mode = hires->getMode();
mode ^= backend::hidpp20::HiresScroll::HiRes;
hires->setMode(mode);
if (_hires_scroll) {
run_task([self_weak = self<ToggleHiresScroll>()]() {
if (auto self = self_weak.lock()) {
auto mode = self->_hires_scroll->getMode();
mode ^= backend::hidpp20::HiresScroll::HiRes;
self->_hires_scroll->setMode(mode);
}
});
}
}
void ToggleHiresScroll::release()
{
void ToggleHiresScroll::release() {
_pressed = false;
}
uint8_t ToggleHiresScroll::reprogFlags() const
{
uint8_t ToggleHiresScroll::reprogFlags() const {
return hidpp20::ReprogControls::TemporaryDiverted;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,24 +18,30 @@
#ifndef LOGID_ACTION_TOGGLEHIRESSCROLL_H
#define LOGID_ACTION_TOGGLEHIRESSCROLL_H
#include "Action.h"
#include "../features/HiresScroll.h"
#include <actions/Action.h>
#include <features/HiresScroll.h>
namespace logid {
namespace actions
{
class ToggleHiresScroll : public Action
{
namespace logid::actions {
class ToggleHiresScroll : public Action {
public:
explicit ToggleHiresScroll(Device* dev);
static const char* interface_name;
virtual void press();
virtual void release();
ToggleHiresScroll(Device* dev, const std::shared_ptr<ipcgull::node>& parent);
ToggleHiresScroll(Device* device,
[[maybe_unused]] config::ToggleHiresScroll& action,
const std::shared_ptr<ipcgull::node>& parent) :
ToggleHiresScroll(device, parent) {}
void press() final;
void release() final;
[[nodiscard]] uint8_t reprogFlags() const final;
virtual uint8_t reprogFlags() const;
protected:
std::shared_ptr<features::HiresScroll> _hires_scroll;
};
}}
}
#endif //LOGID_ACTION_TOGGLEHIRESSCROLL_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,43 +15,48 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ToggleSmartShift.h"
#include "../Device.h"
#include "../backend/hidpp20/features/ReprogControls.h"
#include "../util/task.h"
#include <actions/ToggleSmartShift.h>
#include <Device.h>
#include <backend/hidpp20/features/ReprogControls.h>
#include <util/task.h>
#include <util/log.h>
using namespace logid::actions;
using namespace logid::backend;
ToggleSmartShift::ToggleSmartShift(Device *dev) : Action (dev)
{
const char* ToggleSmartShift::interface_name = "ToggleSmartShift";
ToggleSmartShift::ToggleSmartShift(
Device* dev,
[[maybe_unused]] const std::shared_ptr<ipcgull::node>& parent) :
Action(dev, interface_name) {
_smartshift = _device->getFeature<features::SmartShift>("smartshift");
if(!_smartshift)
if (!_smartshift)
logPrintf(WARN, "%s:%d: SmartShift feature not found, cannot use "
"ToggleSmartShift action.",
_device->hidpp20().devicePath().c_str(),
_device->hidpp20().deviceIndex());
_device->hidpp20().devicePath().c_str(),
_device->hidpp20().deviceIndex());
}
void ToggleSmartShift::press()
{
void ToggleSmartShift::press() {
_pressed = true;
if(_smartshift) {
task::spawn([ss=this->_smartshift](){
auto status = ss->getStatus();
status.setActive = true;
status.active = !status.active;
ss->setStatus(status);
if (_smartshift) {
run_task([self_weak = self<ToggleSmartShift>()]() {
if (auto self = self_weak.lock()) {
auto status = self->_smartshift->getStatus();
status.setActive = true;
status.active = !status.active;
self->_smartshift->setStatus(status);
}
});
}
}
void ToggleSmartShift::release()
{
void ToggleSmartShift::release() {
_pressed = false;
}
uint8_t ToggleSmartShift::reprogFlags() const
{
uint8_t ToggleSmartShift::reprogFlags() const {
return hidpp20::ReprogControls::TemporaryDiverted;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,24 +18,31 @@
#ifndef LOGID_ACTION_TOGGLESMARTSHIFT_H
#define LOGID_ACTION_TOGGLESMARTSHIFT_H
#include <libconfig.h++>
#include "Action.h"
#include "../features/SmartShift.h"
#include <actions/Action.h>
#include <features/SmartShift.h>
namespace logid {
namespace actions {
class ToggleSmartShift : public Action
{
namespace logid::actions {
class ToggleSmartShift : public Action {
public:
explicit ToggleSmartShift(Device* dev);
static const char* interface_name;
virtual void press();
virtual void release();
ToggleSmartShift(Device* dev,
const std::shared_ptr<ipcgull::node>& parent);
ToggleSmartShift(Device* device,
[[maybe_unused]] config::ToggleSmartShift& action,
const std::shared_ptr<ipcgull::node>& parent) :
ToggleSmartShift(device, parent) {}
void press() final;
void release() final;
[[nodiscard]] uint8_t reprogFlags() const final;
virtual uint8_t reprogFlags() const;
protected:
std::shared_ptr<features::SmartShift> _smartshift;
};
}}
}
#endif //LOGID_ACTION_TOGGLESMARTSHIFT_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,111 +16,170 @@
*
*/
#include <cmath>
#include "AxisGesture.h"
#include "../../InputDevice.h"
#include "../../util/log.h"
#include <actions/gesture/AxisGesture.h>
#include <Device.h>
#include <InputDevice.h>
#include <util/log.h>
using namespace logid::actions;
AxisGesture::AxisGesture(Device *device, libconfig::Setting &root) :
Gesture (device), _config (device, root)
{
const char* AxisGesture::interface_name = "Axis";
AxisGesture::AxisGesture(Device* device, config::AxisGesture& config,
const std::shared_ptr<ipcgull::node>& parent) :
Gesture(device, parent, interface_name, {
{
{"GetConfig", {this, &AxisGesture::getConfig, {"axis", "multiplier", "threshold"}}},
{"SetThreshold", {this, &AxisGesture::setThreshold, {"threshold"}}},
{"SetMultiplier", {this, &AxisGesture::setMultiplier, {"multiplier"}}},
{"SetAxis", {this, &AxisGesture::setAxis, {"axis"}}}
},
{},
{}
}), _multiplier(1), _config(config) {
if (_config.axis.has_value()) {
if (std::holds_alternative<uint>(_config.axis.value())) {
_input_axis = std::get<uint>(_config.axis.value());
} else {
const auto& axis = std::get<std::string>(_config.axis.value());
try {
_input_axis = _device->virtualInput()->toAxisCode(axis);
} catch (InputDevice::InvalidEventCode& e) {
logPrintf(WARN, "Invalid axis %s.");
}
}
}
if (_input_axis.has_value())
_device->virtualInput()->registerAxis(_input_axis.value());
}
void AxisGesture::press()
{
_axis = 0;
void AxisGesture::press(bool init_threshold) {
std::shared_lock lock(_config_mutex);
if (init_threshold) {
_axis = (int32_t) (_config.threshold.value_or(defaults::gesture_threshold));
} else {
_axis = 0;
}
_axis_remainder = 0;
_hires_remainder = 0;
}
void AxisGesture::release(bool primary)
{
void AxisGesture::release(bool primary) {
// Do nothing
(void)primary; // Suppress unused warning
(void) primary; // Suppress unused warning
}
void AxisGesture::move(int16_t axis)
{
int16_t new_axis = _axis + axis;
if(new_axis > _config.threshold()) {
void AxisGesture::move(int16_t axis) {
std::shared_lock lock(_config_mutex);
if (!_input_axis.has_value())
return;
const auto threshold = _config.threshold.value_or(
defaults::gesture_threshold);
int32_t new_axis = _axis + axis;
int low_res_axis = InputDevice::getLowResAxis(axis);
int hires_remainder = _hires_remainder;
if (new_axis > threshold) {
double move = axis;
if(_axis < _config.threshold())
move = new_axis - _config.threshold();
bool negative_multiplier = _config.multiplier() < 0;
if(negative_multiplier)
move *= -_config.multiplier();
if (_axis < threshold)
move = new_axis - threshold;
bool negative_multiplier = _config.axis_multiplier.value_or(1) < 0;
if (negative_multiplier)
move *= -_config.axis_multiplier.value_or(1);
else
move *= _config.multiplier();
move *= _config.axis_multiplier.value_or(1);
// Handle hi-res multiplier
move *= _multiplier;
double move_floor = floor(move);
_axis_remainder = move - move_floor;
if(_axis_remainder >= 1) {
if (_axis_remainder >= 1) {
double int_remainder = floor(_axis_remainder);
move_floor += int_remainder;
_axis_remainder -= int_remainder;
}
if(negative_multiplier)
if (negative_multiplier)
move_floor = -move_floor;
virtual_input->moveAxis(_config.axis(), move_floor);
if (low_res_axis != -1) {
int lowres_movement, hires_movement = (int) move_floor;
_device->virtualInput()->moveAxis(_input_axis.value(), hires_movement);
hires_remainder += hires_movement;
if (abs(hires_remainder) >= 60) {
lowres_movement = hires_remainder / 120;
if (lowres_movement == 0)
lowres_movement = hires_remainder > 0 ? 1 : -1;
hires_remainder -= lowres_movement * 120;
_device->virtualInput()->moveAxis(low_res_axis, lowres_movement);
}
_hires_remainder = hires_remainder;
} else {
_device->virtualInput()->moveAxis(_input_axis.value(), (int) move_floor);
}
}
_axis = new_axis;
}
bool AxisGesture::metThreshold() const
{
return _axis >= _config.threshold();
bool AxisGesture::metThreshold() const {
std::shared_lock lock(_config_mutex);
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
}
AxisGesture::Config::Config(Device *device, libconfig::Setting &setting) :
Gesture::Config(device, setting, false)
{
try {
auto& axis = setting.lookup("axis");
if(axis.isNumber()) {
_axis = axis;
} else if(axis.getType() == libconfig::Setting::TypeString) {
try {
_axis = virtual_input->toAxisCode(axis);
} catch(InputDevice::InvalidEventCode& e) {
logPrintf(WARN, "Line %d: Invalid axis %s, skipping."
, axis.getSourceLine(), axis.c_str());
}
} else {
logPrintf(WARN, "Line %d: axis must be string or int, skipping.",
axis.getSourceLine(), axis.c_str());
throw InvalidGesture();
}
} catch(libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: axis is a required field, skippimg.",
setting.getSourceLine());
throw InvalidGesture();
}
bool AxisGesture::wheelCompatibility() const {
return true;
}
try {
auto& multiplier = setting.lookup("axis_multiplier");
if(multiplier.isNumber()) {
if(multiplier.getType() == libconfig::Setting::TypeFloat)
_multiplier = multiplier;
else
_multiplier = (int)multiplier;
} else {
logPrintf(WARN, "Line %d: axis_multiplier must be a number, "
"setting to default (1).",
multiplier.getSourceLine());
}
} catch(libconfig::SettingNotFoundException& e) {
// Ignore
void AxisGesture::setHiresMultiplier(double multiplier) {
_hires_multiplier = multiplier;
if (_input_axis.has_value()) {
if (InputDevice::getLowResAxis(_input_axis.value()) != -1)
_multiplier = _config.axis_multiplier.value_or(1) * multiplier;
}
}
unsigned int AxisGesture::Config::axis() const
{
return _axis;
std::tuple<std::string, double, int> AxisGesture::getConfig() const {
std::shared_lock lock(_config_mutex);
std::string axis;
if (_config.axis.has_value()) {
if (std::holds_alternative<std::string>(_config.axis.value())) {
axis = std::get<std::string>(_config.axis.value());
} else {
axis = _device->virtualInput()->toAxisName(std::get<uint>(_config.axis.value()));
}
}
return {axis, _config.axis_multiplier.value_or(1), _config.threshold.value_or(0)};
}
double AxisGesture::Config::multiplier() const
{
return _multiplier;
}
void AxisGesture::setAxis(const std::string& axis) {
std::unique_lock lock(_config_mutex);
if (axis.empty()) {
_config.axis.reset();
_input_axis.reset();
} else {
_input_axis = _device->virtualInput()->toAxisCode(axis);
_config.axis = axis;
_device->virtualInput()->registerAxis(_input_axis.value());
}
setHiresMultiplier(_hires_multiplier);
}
void AxisGesture::setMultiplier(double multiplier) {
std::unique_lock lock(_config_mutex);
_config.axis_multiplier = multiplier;
_multiplier = multiplier;
setHiresMultiplier(_hires_multiplier);
}
void AxisGesture::setThreshold(int threshold) {
std::unique_lock lock(_config_mutex);
if (threshold == 0)
_config.threshold.reset();
else
_config.threshold = threshold;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,38 +18,45 @@
#ifndef LOGID_ACTION_AXISGESTURE_H
#define LOGID_ACTION_AXISGESTURE_H
#include "Gesture.h"
#include <actions/gesture/Gesture.h>
namespace logid {
namespace actions
{
class AxisGesture : public Gesture
{
public:
AxisGesture(Device* device, libconfig::Setting& root);
namespace logid::actions {
class AxisGesture : public Gesture {
public:
static const char* interface_name;
virtual void press();
virtual void release(bool primary=false);
virtual void move(int16_t axis);
AxisGesture(Device* device, config::AxisGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
virtual bool metThreshold() const;
void press(bool init_threshold) final;
class Config : public Gesture::Config
{
public:
Config(Device* device, libconfig::Setting& setting);
unsigned int axis() const;
double multiplier() const;
private:
unsigned int _axis;
double _multiplier = 1;
};
void release(bool primary) final;
protected:
int16_t _axis;
double _axis_remainder;
Config _config;
};
}}
void move(int16_t axis) final;
[[nodiscard]] bool wheelCompatibility() const final;
[[nodiscard]] bool metThreshold() const final;
void setHiresMultiplier(double multiplier);
[[nodiscard]] std::tuple<std::string, double, int> getConfig() const;
void setAxis(const std::string& axis);
void setMultiplier(double multiplier);
void setThreshold(int threshold);
protected:
int32_t _axis{};
double _axis_remainder{};
int _hires_remainder{};
std::optional<uint> _input_axis;
double _multiplier;
double _hires_multiplier = 1.0;
config::AxisGesture& _config;
};
}
#endif //LOGID_ACTION_AXISGESTURE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,103 +16,73 @@
*
*/
#include <algorithm>
#include "Gesture.h"
#include "../../util/log.h"
#include "ReleaseGesture.h"
#include "../../backend/hidpp20/features/ReprogControls.h"
#include "IntervalGesture.h"
#include "AxisGesture.h"
#include "NullGesture.h"
#include <actions/gesture/Gesture.h>
#include <utility>
#include <actions/gesture/ReleaseGesture.h>
#include <actions/gesture/ThresholdGesture.h>
#include <actions/gesture/IntervalGesture.h>
#include <actions/gesture/AxisGesture.h>
#include <actions/gesture/NullGesture.h>
#include <ipc_defs.h>
using namespace logid;
using namespace logid::actions;
Gesture::Gesture(Device *device) : _device (device)
{
Gesture::Gesture(Device* device,
std::shared_ptr<ipcgull::node> node,
const std::string& name, tables t) :
ipcgull::interface(SERVICE_ROOT_NAME ".Gesture." + name, std::move(t)),
_node(std::move(node)), _device(device) {
}
Gesture::Config::Config(Device* device, libconfig::Setting& root,
bool action_required) : _device (device)
{
if(action_required) {
try {
_action = Action::makeAction(_device,
root.lookup("action"));
} catch (libconfig::SettingNotFoundException &e) {
throw InvalidGesture("action is missing");
}
namespace {
template<typename T>
struct gesture_type {
typedef typename T::gesture type;
};
if(_action->reprogFlags() & backend::hidpp20::ReprogControls::RawXYDiverted)
throw InvalidGesture("gesture cannot require RawXY");
}
template<typename T>
struct gesture_type<const T> : gesture_type<T> {
};
_threshold = LOGID_GESTURE_DEFAULT_THRESHOLD;
try {
auto& threshold = root.lookup("threshold");
if(threshold.getType() == libconfig::Setting::TypeInt) {
_threshold = (int)threshold;
if(_threshold <= 0) {
_threshold = LOGID_GESTURE_DEFAULT_THRESHOLD;
logPrintf(WARN, "Line %d: threshold must be positive, setting "
"to default (%d)", threshold.getSourceLine(),
_threshold);
}
} else
logPrintf(WARN, "Line %d: threshold must be an integer, setting "
"to default (%d).", threshold.getSourceLine());
} catch(libconfig::SettingNotFoundException& e) {
// Ignore
template<typename T>
struct gesture_type<T&> : gesture_type<T> {
};
template<typename T>
std::shared_ptr<Gesture> _makeGesture(
Device* device, T& gesture,
const std::shared_ptr<ipcgull::node>& parent) {
return parent->make_interface<typename gesture_type<T>::type>(
device, std::forward<T&>(gesture), parent);
}
}
std::shared_ptr<Gesture> Gesture::makeGesture(Device *device,
libconfig::Setting &setting)
{
if(!setting.isGroup()) {
logPrintf(WARN, "Line %d: Gesture is not a group, ignoring.",
setting.getSourceLine());
std::shared_ptr<Gesture> Gesture::makeGesture(
Device* device, config::Gesture& gesture,
const std::shared_ptr<ipcgull::node>& parent) {
return std::visit([&device, &parent](auto&& x) {
return _makeGesture(device, x, parent);
}, gesture);
}
std::shared_ptr<Gesture> Gesture::makeGesture(
Device* device, const std::string& type,
config::Gesture& config,
const std::shared_ptr<ipcgull::node>& parent) {
if (type == AxisGesture::interface_name) {
config = config::AxisGesture();
} else if (type == IntervalGesture::interface_name) {
config = config::IntervalGesture();
} else if (type == ReleaseGesture::interface_name) {
config = config::ReleaseGesture();
} else if (type == ThresholdGesture::interface_name) {
config = config::ThresholdGesture();
} else if (type == NullGesture::interface_name) {
config = config::NoGesture();
} else {
throw InvalidGesture();
}
try {
auto& gesture_mode = setting.lookup("mode");
if(gesture_mode.getType() != libconfig::Setting::TypeString) {
logPrintf(WARN, "Line %d: Gesture mode must be a string,"
"defaulting to OnRelease.",
gesture_mode.getSourceLine());
return std::make_shared<ReleaseGesture>(device, setting);
}
std::string type = gesture_mode;
std::transform(type.begin(), type.end(), type.begin(), ::tolower);
if(type == "onrelease")
return std::make_shared<ReleaseGesture>(device, setting);
else if(type == "oninterval" || type == "onfewpixels")
return std::make_shared<IntervalGesture>(device, setting);
else if(type == "axis")
return std::make_shared<AxisGesture>(device, setting);
else if(type == "nopress")
return std::make_shared<NullGesture>(device, setting);
else {
logPrintf(WARN, "Line %d: Unknown gesture mode %s, defaulting to "
"OnRelease.", gesture_mode.getSourceLine(),
(const char*)gesture_mode);
return std::make_shared<ReleaseGesture>(device, setting);
}
} catch(libconfig::SettingNotFoundException& e) {
return std::make_shared<ReleaseGesture>(device, setting);
}
return makeGesture(device, config, parent);
}
int16_t Gesture::Config::threshold() const
{
return _threshold;
}
std::shared_ptr<Action> Gesture::Config::action()
{
return _action;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,56 +18,56 @@
#ifndef LOGID_ACTION_GESTURE_H
#define LOGID_ACTION_GESTURE_H
#include "../Action.h"
#include <utility>
#include <actions/Action.h>
#define LOGID_GESTURE_DEFAULT_THRESHOLD 50
namespace logid {
namespace actions
{
class InvalidGesture : public std::exception
{
namespace logid::actions {
class InvalidGesture : public std::exception {
public:
explicit InvalidGesture(std::string what="") : _what (what)
{
explicit InvalidGesture(std::string what = "") : _what(std::move(what)) {
}
virtual const char* what()
{
[[nodiscard]] const char* what() const noexcept override {
return _what.c_str();
}
private:
std::string _what;
};
class Gesture
{
class Gesture : public ipcgull::interface {
public:
virtual void press() = 0;
virtual void release(bool primary=false) = 0;
virtual void press(bool init_threshold) = 0;
virtual void release(bool primary) = 0;
virtual void move(int16_t axis) = 0;
virtual bool metThreshold() const = 0;
[[nodiscard]] virtual bool wheelCompatibility() const = 0;
class Config
{
public:
Config(Device* device, libconfig::Setting& root,
bool action_required=true);
virtual int16_t threshold() const;
virtual std::shared_ptr<Action> action();
protected:
Device* _device;
std::shared_ptr<Action> _action;
int16_t _threshold;
};
[[nodiscard]] virtual bool metThreshold() const = 0;
virtual ~Gesture() = default;
static std::shared_ptr<Gesture> makeGesture(Device* device,
libconfig::Setting& setting);
config::Gesture& gesture,
const std::shared_ptr<ipcgull::node>& parent);
static std::shared_ptr<Gesture> makeGesture(
Device* device, const std::string& type,
config::Gesture& gesture,
const std::shared_ptr<ipcgull::node>& parent);
protected:
explicit Gesture(Device* device);
Gesture(Device* device,
std::shared_ptr<ipcgull::node> parent,
const std::string& name, tables t = {});
mutable std::shared_mutex _config_mutex;
const std::shared_ptr<ipcgull::node> _node;
Device* _device;
};
}}
}
#endif //LOGID_ACTION_GESTURE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,77 +15,103 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "IntervalGesture.h"
#include "../../util/log.h"
#include <actions/gesture/IntervalGesture.h>
#include <Configuration.h>
#include <util/log.h>
using namespace logid::actions;
IntervalGesture::IntervalGesture(Device *device, libconfig::Setting &root) :
Gesture (device), _config (device, root)
{
const char* IntervalGesture::interface_name = "OnInterval";
IntervalGesture::IntervalGesture(
Device* device, config::IntervalGesture& config,
const std::shared_ptr<ipcgull::node>& parent) :
Gesture(device, parent, interface_name, {
{
{"GetConfig", {this, &IntervalGesture::getConfig, {"interval", "threshold"}}},
{"SetInterval", {this, &IntervalGesture::setInterval, {"interval"}}},
{"SetThreshold", {this, &IntervalGesture::setThreshold, {"interval"}}},
{"SetAction", {this, &IntervalGesture::setAction, {"type"}}}
},
{},
{}
}),
_axis(0), _interval_pass_count(0), _config(config) {
if (config.action) {
try {
_action = Action::makeAction(device, config.action.value(), _node);
} catch (InvalidAction& e) {
logPrintf(WARN, "Mapping gesture to invalid action");
}
}
}
void IntervalGesture::press()
{
_axis = 0;
void IntervalGesture::press(bool init_threshold) {
std::shared_lock lock(_config_mutex);
if (init_threshold) {
_axis = (int32_t) _config.threshold.value_or(defaults::gesture_threshold);
} else {
_axis = 0;
}
_interval_pass_count = 0;
}
void IntervalGesture::release(bool primary)
{
// Do nothing
(void)primary; // Suppress unused warning
void IntervalGesture::release([[maybe_unused]] bool primary) {
}
void IntervalGesture::move(int16_t axis)
{
_axis += axis;
if(_axis < _config.threshold())
void IntervalGesture::move(int16_t axis) {
std::shared_lock lock(_config_mutex);
if (!_config.interval.has_value())
return;
int16_t new_interval_count = (_axis - _config.threshold())/
_config.interval();
if(new_interval_count > _interval_pass_count) {
_config.action()->press();
_config.action()->release();
const auto threshold =
_config.threshold.value_or(defaults::gesture_threshold);
_axis += axis;
if (_axis < threshold)
return;
int32_t new_interval_count = (_axis - threshold) / _config.interval.value();
if (new_interval_count > _interval_pass_count) {
if (_action) {
_action->press();
_action->release();
}
}
_interval_pass_count = new_interval_count;
}
bool IntervalGesture::metThreshold() const
{
return _axis >= _config.threshold();
bool IntervalGesture::wheelCompatibility() const {
return true;
}
IntervalGesture::Config::Config(Device *device, libconfig::Setting &setting) :
Gesture::Config(device, setting)
{
try {
auto& interval = setting.lookup("interval");
if(interval.getType() != libconfig::Setting::TypeInt) {
logPrintf(WARN, "Line %d: interval must be an integer, skipping.",
interval.getSourceLine());
throw InvalidGesture();
}
_interval = (int)interval;
} catch(libconfig::SettingNotFoundException& e) {
try {
// pixels is an alias for interval
auto& interval = setting.lookup("pixels");
if(interval.getType() != libconfig::Setting::TypeInt) {
logPrintf(WARN, "Line %d: pixels must be an integer, skipping.",
interval.getSourceLine());
throw InvalidGesture();
}
_interval = (int)interval;
} catch(libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: interval is a required field, skipping.",
setting.getSourceLine());
}
}
bool IntervalGesture::metThreshold() const {
std::shared_lock lock(_config_mutex);
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
}
int16_t IntervalGesture::Config::interval() const
{
return _interval;
}
std::tuple<int, int> IntervalGesture::getConfig() const {
std::shared_lock lock(_config_mutex);
return {_config.interval.value_or(0), _config.threshold.value_or(0)};
}
void IntervalGesture::setInterval(int interval) {
std::unique_lock lock(_config_mutex);
if (interval == 0)
_config.interval.reset();
else
_config.interval = interval;
}
void IntervalGesture::setThreshold(int threshold) {
std::unique_lock lock(_config_mutex);
if (threshold == 0)
_config.threshold.reset();
else
_config.threshold = threshold;
}
void IntervalGesture::setAction(const std::string& type) {
std::unique_lock lock(_config_mutex);
_action.reset();
_action = Action::makeAction(_device, type, _config.action, _node);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,36 +18,41 @@
#ifndef LOGID_ACTION_INTERVALGESTURE_H
#define LOGID_ACTION_INTERVALGESTURE_H
#include "Gesture.h"
#include <actions/gesture/Gesture.h>
namespace logid {
namespace actions
{
class IntervalGesture : public Gesture
{
namespace logid::actions {
class IntervalGesture : public Gesture {
public:
IntervalGesture(Device* device, libconfig::Setting& root);
static const char* interface_name;
virtual void press();
virtual void release(bool primary=false);
virtual void move(int16_t axis);
IntervalGesture(Device* device, config::IntervalGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
virtual bool metThreshold() const;
void press(bool init_threshold) final;
class Config : public Gesture::Config
{
public:
Config(Device* device, libconfig::Setting& setting);
int16_t interval() const;
private:
int16_t _interval;
};
void release(bool primary) final;
void move(int16_t axis) final;
[[nodiscard]] bool wheelCompatibility() const final;
[[nodiscard]] bool metThreshold() const final;
[[nodiscard]] std::tuple<int, int> getConfig() const;
void setInterval(int interval);
void setThreshold(int threshold);
void setAction(const std::string& type);
protected:
int16_t _axis;
int16_t _interval_pass_count;
Config _config;
int32_t _axis;
int32_t _interval_pass_count;
std::shared_ptr<Action> _action;
config::IntervalGesture& _config;
private:
};
}}
}
#endif //LOGID_ACTION_INTERVALGESTURE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,32 +15,37 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "NullGesture.h"
#include <actions/gesture/NullGesture.h>
#include <Configuration.h>
using namespace logid::actions;
NullGesture::NullGesture(Device *device, libconfig::Setting& setting) :
Gesture (device), _config (device, setting, false)
{
const char* NullGesture::interface_name = "None";
NullGesture::NullGesture(Device* device,
config::NoGesture& config,
const std::shared_ptr<ipcgull::node>& parent) :
Gesture(device, parent, interface_name), _config(config) {
}
void NullGesture::press()
{
_axis = 0;
void NullGesture::press(bool init_threshold) {
_axis = init_threshold ? _config.threshold.value_or(
defaults::gesture_threshold) : 0;
}
void NullGesture::release(bool primary)
{
void NullGesture::release(bool primary) {
// Do nothing
(void)primary; // Suppress unused warning
(void) primary; // Suppress unused warning
}
void NullGesture::move(int16_t axis)
{
void NullGesture::move(int16_t axis) {
_axis += axis;
}
bool NullGesture::metThreshold() const
{
return _axis > _config.threshold();
bool NullGesture::wheelCompatibility() const {
return true;
}
bool NullGesture::metThreshold() const {
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,25 +18,31 @@
#ifndef LOGID_ACTION_NULLGESTURE_H
#define LOGID_ACTION_NULLGESTURE_H
#include "Gesture.h"
#include <actions/gesture/Gesture.h>
namespace logid {
namespace actions
{
class NullGesture : public Gesture
{
namespace logid::actions {
class NullGesture : public Gesture {
public:
NullGesture(Device* device, libconfig::Setting& setting);
static const char* interface_name;
virtual void press();
virtual void release(bool primary=false);
virtual void move(int16_t axis);
NullGesture(Device* device,
config::NoGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
void press(bool init_threshold) final;
void release(bool primary) final;
void move(int16_t axis) final;
[[nodiscard]] bool wheelCompatibility() const final;
[[nodiscard]] bool metThreshold() const final;
virtual bool metThreshold() const;
protected:
int16_t _axis;
Gesture::Config _config;
int32_t _axis{};
config::NoGesture& _config;
};
}}
}
#endif //LOGID_ACTION_NULLGESTURE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,34 +15,75 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ReleaseGesture.h"
#include <actions/gesture/ReleaseGesture.h>
#include <Configuration.h>
using namespace logid::actions;
ReleaseGesture::ReleaseGesture(Device *device, libconfig::Setting &root) :
Gesture (device), _config (device, root)
{
const char* ReleaseGesture::interface_name = "OnRelease";
ReleaseGesture::ReleaseGesture(Device* device, config::ReleaseGesture& config,
const std::shared_ptr<ipcgull::node>& parent) :
Gesture(device, parent, interface_name, {
{
{"GetThreshold", {this, &ReleaseGesture::getThreshold, {"threshold"}}},
{"SetThreshold", {this, &ReleaseGesture::setThreshold, {"threshold"}}},
{"SetAction", {this, &ReleaseGesture::setAction, {"type"}}}
},
{},
{}
}), _config(config) {
if (_config.action.has_value())
_action = Action::makeAction(device, _config.action.value(), _node);
}
void ReleaseGesture::press()
{
_axis = 0;
}
void ReleaseGesture::release(bool primary)
{
if(metThreshold() && primary) {
_config.action()->press();
_config.action()->release();
void ReleaseGesture::press(bool init_threshold) {
std::shared_lock lock(_config_mutex);
if (init_threshold) {
_axis = (int32_t) (_config.threshold.value_or(defaults::gesture_threshold));
} else {
_axis = 0;
}
}
void ReleaseGesture::move(int16_t axis)
{
void ReleaseGesture::release(bool primary) {
if (metThreshold() && primary) {
if (_action) {
_action->press();
_action->release();
}
}
}
void ReleaseGesture::move(int16_t axis) {
_axis += axis;
}
bool ReleaseGesture::metThreshold() const
{
return _axis >= _config.threshold();
}
bool ReleaseGesture::wheelCompatibility() const {
return false;
}
bool ReleaseGesture::metThreshold() const {
std::shared_lock lock(_config_mutex);
return _axis >= _config.threshold.value_or(defaults::gesture_threshold);
}
int ReleaseGesture::getThreshold() const {
std::shared_lock lock(_config_mutex);
return _config.threshold.value_or(0);
}
void ReleaseGesture::setThreshold(int threshold) {
std::unique_lock lock(_config_mutex);
if (threshold == 0)
_config.threshold.reset();
else
_config.threshold = threshold;
}
void ReleaseGesture::setAction(const std::string& type) {
std::unique_lock lock(_config_mutex);
_action.reset();
_action = Action::makeAction(_device, type, _config.action, _node);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,26 +18,37 @@
#ifndef LOGID_ACTION_RELEASEGESTURE_H
#define LOGID_ACTION_RELEASEGESTURE_H
#include "Gesture.h"
#include <actions/gesture/Gesture.h>
namespace logid {
namespace actions
{
class ReleaseGesture : public Gesture
{
namespace logid::actions {
class ReleaseGesture : public Gesture {
public:
ReleaseGesture(Device* device, libconfig::Setting& root);
static const char* interface_name;
virtual void press();
virtual void release(bool primary=false);
virtual void move(int16_t axis);
ReleaseGesture(Device* device, config::ReleaseGesture& config,
const std::shared_ptr<ipcgull::node>& parent);
virtual bool metThreshold() const;
void press(bool init_threshold) final;
void release(bool primary) final;
void move(int16_t axis) final;
[[nodiscard]] bool wheelCompatibility() const final;
[[nodiscard]] bool metThreshold() const final;
[[nodiscard]] int getThreshold() const;
void setThreshold(int threshold);
void setAction(const std::string& type);
protected:
int16_t _axis;
Gesture::Config _config;
int32_t _axis{};
std::shared_ptr<Action> _action;
config::ReleaseGesture& _config;
};
}}
}
#endif //LOGID_ACTION_RELEASEGESTURE_H

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,9 +16,14 @@
*
*/
#include "Error.h"
#include <backend/Error.h>
const char *logid::backend::TimeoutError::what() const noexcept
{
using namespace logid::backend;
const char* DeviceNotReady::what() const noexcept {
return "device not ready";
}
const char* TimeoutError::what() const noexcept {
return "Device timed out";
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -21,14 +21,18 @@
#include <stdexcept>
namespace logid {
namespace backend {
class TimeoutError: public std::exception
{
public:
TimeoutError() = default;
const char* what() const noexcept override;
};
}}
namespace logid::backend {
class DeviceNotReady : public std::exception {
public:
[[nodiscard]] const char* what() const noexcept override;
};
class TimeoutError : public std::exception {
public:
TimeoutError() = default;
[[nodiscard]] const char* what() const noexcept override;
};
}
#endif //LOGID_BACKEND_ERROR_H

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,92 +19,168 @@
#ifndef LOGID_BACKEND_HIDPP_DEVICE_H
#define LOGID_BACKEND_HIDPP_DEVICE_H
#include <backend/raw/RawDevice.h>
#include <backend/hidpp/Report.h>
#include <backend/hidpp/defs.h>
#include <backend/EventHandlerList.h>
#include <optional>
#include <variant>
#include <string>
#include <memory>
#include <functional>
#include <map>
#include "../raw/RawDevice.h"
#include "Report.h"
#include "defs.h"
namespace logid {
namespace backend {
namespace dj
{
namespace logid::backend::hidpp10 {
// Need to define here for a constructor
class Receiver;
}
namespace hidpp
{
namespace logid::backend::hidpp {
struct DeviceConnectionEvent;
struct EventHandler
{
std::function<bool(Report&)> condition;
std::function<void(Report&)> callback;
};
class Device
{
template<typename T>
class _deviceWrapper : public T {
friend class Device;
public:
class InvalidDevice : std::exception
{
template<typename... Args>
explicit _deviceWrapper(Args... args) : T(std::forward<Args>(args)...) {}
template<typename... Args>
static std::shared_ptr<T> make(Args... args) {
return std::make_shared<_deviceWrapper>(std::forward<Args>(args)...);
}
};
class Device {
template<typename T>
friend
class _deviceWrapper;
public:
struct EventHandler {
std::function<bool(Report&)> condition;
std::function<void(Report&)> callback;
};
class InvalidDevice : std::exception {
public:
enum Reason
{
enum Reason {
NoHIDPPReport,
InvalidRawDevice,
Asleep
Asleep,
VirtualNode
};
InvalidDevice(Reason reason) : _reason (reason) {}
virtual const char* what() const noexcept;
virtual Reason code() const noexcept;
explicit InvalidDevice(Reason reason) : _reason(reason) {}
[[nodiscard]] const char* what() const noexcept override;
[[nodiscard]] virtual Reason code() const noexcept;
private:
Reason _reason;
};
explicit Device(const std::string& path, DeviceIndex index);
explicit Device(std::shared_ptr<raw::RawDevice> raw_device,
DeviceIndex index);
explicit Device(std::shared_ptr<dj::Receiver> receiver,
hidpp::DeviceConnectionEvent event);
~Device();
[[nodiscard]] const std::string& devicePath() const;
std::string devicePath() const;
DeviceIndex deviceIndex() const;
std::tuple<uint8_t, uint8_t> version() const;
[[nodiscard]] DeviceIndex deviceIndex() const;
std::string name() const;
uint16_t pid() const;
[[nodiscard]] const std::tuple<uint8_t, uint8_t>& version() const;
void listen(); // Runs asynchronously
void stopListening();
[[nodiscard]] const std::string& name() const;
void addEventHandler(const std::string& nickname,
const std::shared_ptr<EventHandler>& handler);
void removeEventHandler(const std::string& nickname);
const std::map<std::string, std::shared_ptr<EventHandler>>&
eventHandlers();
[[nodiscard]] uint16_t pid() const;
Report sendReport(Report& report);
void sendReportNoResponse(Report& report);
EventHandlerLock<Device> addEventHandler(EventHandler handler);
virtual Report sendReport(const Report& report);
virtual void sendReportNoACK(const Report& report);
void handleEvent(Report& report);
[[nodiscard]] const std::shared_ptr<raw::RawDevice>& rawDevice() const;
Device(const Device&) = delete;
Device(Device&&) = delete;
virtual ~Device() = default;
protected:
Device(const std::string& path, DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout);
Device(std::shared_ptr<raw::RawDevice> raw_device, DeviceIndex index,
double timeout);
Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceConnectionEvent event, double timeout);
Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
DeviceIndex index, double timeout);
// Returns whether the report is a response
virtual bool responseReport(const Report& report);
bool isStable20();
bool isStable10();
void _sendReport(Report report);
void reportFixup(Report& report) const;
const std::chrono::milliseconds io_timeout;
uint8_t supported_reports{};
std::mutex _response_mutex;
std::condition_variable _response_cv;
private:
void _setupReportsAndInit();
void _init();
std::shared_ptr<raw::RawDevice> _raw_device;
std::shared_ptr<dj::Receiver> _receiver;
EventHandlerLock<raw::RawDevice> _raw_handler;
std::shared_ptr<hidpp10::Receiver> _receiver;
std::string _path;
DeviceIndex _index;
uint8_t _supported_reports;
std::tuple<uint8_t, uint8_t> _version;
uint16_t _pid;
uint16_t _pid{};
std::string _name;
std::atomic<bool> _listening;
std::mutex _send_mutex;
std::map<std::string, std::shared_ptr<EventHandler>> _event_handlers;
typedef std::variant<Report, Report::Hidpp10Error, Report::Hidpp20Error> Response;
std::optional<Response> _response;
std::optional<uint8_t> _sent_sub_id{};
std::optional<uint8_t> _sent_address{};
std::shared_ptr<EventHandlerList<Device>> _event_handlers;
std::weak_ptr<Device> _self;
protected:
template<typename T, typename... Args>
static std::shared_ptr<T> makeDerived(Args... args) {
auto device = _deviceWrapper<T>::make(std::forward<Args>(args)...);
device->_self = device;
device->_setupReportsAndInit();
return device;
}
public:
template<typename... Args>
static std::shared_ptr<Device> make(Args... args) {
return makeDerived<Device>(std::forward<Args>(args)...);
}
};
} } }
typedef Device::EventHandler EventHandler;
}
#endif //LOGID_BACKEND_HIDPP_DEVICE_H

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,23 +19,19 @@
#ifndef LOGID_BACKEND_HIDPP_REPORT_H
#define LOGID_BACKEND_HIDPP_REPORT_H
#include <backend/raw/RawDevice.h>
#include <backend/hidpp/defs.h>
#include <cstdint>
#include "../raw/RawDevice.h"
#include "defs.h"
/* Some devices only support a subset of these reports */
#define HIDPP_REPORT_SHORT_SUPPORTED 1U
#define HIDPP_REPORT_LONG_SUPPORTED 1U<<1U
/* Very long reports exist, however they have not been encountered so far */
namespace logid::backend::hidpp {
uint8_t getSupportedReports(const std::vector<uint8_t>& report_desc);
namespace logid {
namespace backend {
namespace hidpp
{
uint8_t getSupportedReports(std::vector<uint8_t>&& rdesc);
/* Some devices only support a subset of these reports */
static constexpr uint8_t ShortReportSupported = 1U;
static constexpr uint8_t LongReportSupported = (1U<<1);
/* Very long reports exist, however they have not been encountered so far */
namespace Offset
{
namespace Offset {
static constexpr uint8_t Type = 0;
static constexpr uint8_t DeviceIndex = 1;
static constexpr uint8_t SubID = 2;
@ -45,23 +41,22 @@ namespace hidpp
static constexpr uint8_t Parameters = 4;
}
class Report
{
class Report {
public:
typedef ReportType::ReportType Type;
class InvalidReportID: public std::exception
{
class InvalidReportID : public std::exception {
public:
InvalidReportID() = default;
const char* what() const noexcept override;
[[nodiscard]] const char* what() const noexcept override;
};
class InvalidReportLength: public std::exception
{
class InvalidReportLength : public std::exception {
public:
InvalidReportLength() = default;;
const char* what() const noexcept override;
InvalidReportLength() = default;
[[nodiscard]] const char* what() const noexcept override;
};
static constexpr std::size_t MaxDataLength = 20;
@ -69,57 +64,72 @@ namespace hidpp
Report(Report::Type type, DeviceIndex device_index,
uint8_t sub_id,
uint8_t address);
Report(Report::Type type, DeviceIndex device_index,
uint8_t feature_index,
uint8_t function,
uint8_t sw_id);
uint8_t feature_index,
uint8_t function,
uint8_t sw_id);
explicit Report(const std::vector<uint8_t>& data);
Report::Type type() const;
[[nodiscard]] Report::Type type() const;
void setType(Report::Type type);
logid::backend::hidpp::DeviceIndex deviceIndex() const;
void setDeviceIndex(hidpp::DeviceIndex index);
[[nodiscard]] DeviceIndex deviceIndex() const;
uint8_t feature() const;
void setFeature(uint8_t feature);
[[maybe_unused]] void setDeviceIndex(DeviceIndex index);
uint8_t subId() const;
void setSubId(uint8_t sub_id);
[[nodiscard]] uint8_t feature() const;
uint8_t function() const;
void setFunction(uint8_t function);
[[maybe_unused]] void setFeature(uint8_t feature);
[[nodiscard]] uint8_t subId() const;
[[maybe_unused]] void setSubId(uint8_t sub_id);
[[nodiscard]] uint8_t function() const;
[[maybe_unused]] void setFunction(uint8_t function);
[[nodiscard]] uint8_t swId() const;
uint8_t swId() const;
void setSwId(uint8_t sw_id);
uint8_t address() const;
void setAddress(uint8_t address);
[[nodiscard]] uint8_t address() const;
[[maybe_unused]] void setAddress(uint8_t address);
[[nodiscard]] std::vector<uint8_t>::iterator paramBegin();
[[nodiscard]] std::vector<uint8_t>::iterator paramEnd();
[[nodiscard]] std::vector<uint8_t>::const_iterator paramBegin() const;
[[nodiscard]] std::vector<uint8_t>::const_iterator paramEnd() const;
std::vector<uint8_t>::iterator paramBegin();
std::vector<uint8_t>::iterator paramEnd();
std::vector<uint8_t>::const_iterator paramBegin() const;
std::vector<uint8_t>::const_iterator paramEnd() const;
void setParams(const std::vector<uint8_t>& _params);
struct Hidpp10Error
{
struct Hidpp10Error {
hidpp::DeviceIndex device_index;
uint8_t sub_id, address, error_code;
};
bool isError10(Hidpp10Error* error);
struct Hidpp20Error
{
bool isError10(Hidpp10Error& error) const;
struct Hidpp20Error {
hidpp::DeviceIndex device_index;
uint8_t feature_index, function, software_id, error_code;
};
bool isError20(Hidpp20Error* error);
std::vector<uint8_t> rawReport () const { return _data; }
bool isError20(Hidpp20Error& error) const;
[[nodiscard]] const std::vector<uint8_t>& rawReport() const;
static constexpr std::size_t HeaderLength = 4;
private:
std::vector<uint8_t> _data;
};
}}}
}
#endif //LOGID_BACKEND_HIDPP_REPORT_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,37 +19,33 @@
#ifndef LOGID_BACKEND_HIDPP_DEFS_H
#define LOGID_BACKEND_HIDPP_DEFS_H
#define LOGID_HIDPP_SOFTWARE_ID 0
#include <cstdint>
namespace logid {
namespace backend {
namespace hidpp
{
namespace ReportType
{
enum ReportType : uint8_t
{
namespace logid::backend::hidpp {
namespace ReportType {
enum ReportType : uint8_t {
Short = 0x10,
Long = 0x11
};
}
enum DeviceIndex: uint8_t
{
enum DeviceIndex : uint8_t {
DefaultDevice = 0xff,
CordedDevice = 0,
WirelessDevice1 = 1,
WirelessDevice2 = 2,
WirelessDevice3 = 3,
WirelessDevice4 = 4,
WirelessDevice5 = 5,
WirelessDevice2 [[maybe_unused]] = 2,
WirelessDevice3 [[maybe_unused]] = 3,
WirelessDevice4 [[maybe_unused]] = 4,
WirelessDevice5 [[maybe_unused]] = 5,
WirelessDevice6 = 6,
};
static constexpr uint8_t softwareID = 2;
/* For sending reports with no response, use a different SW ID */
static constexpr uint8_t noAckSoftwareID = 3;
static constexpr std::size_t ShortParamLength = 3;
static constexpr std::size_t LongParamLength = 16;
} } }
}
#endif //LOGID_BACKEND_HIDPP_DEFS_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,41 +16,123 @@
*
*/
#include <backend/hidpp10/Device.h>
#include <backend/Error.h>
#include <cassert>
#include <utility>
#include "Device.h"
#include "defs.h"
using namespace logid::backend;
using namespace logid::backend::hidpp10;
Device::Device(const std::string &path, hidpp::DeviceIndex index) :
hidpp::Device(path, index)
{
assert(version() == std::make_tuple(1, 0));
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(std::shared_ptr<raw::RawDevice> raw_dev,
hidpp::DeviceIndex index) : hidpp::Device(std::move(raw_dev), index)
{
assert(version() == std::make_tuple(1, 0));
Device::Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) :
hidpp::Device(path, index, monitor, timeout) {
}
Device::Device(std::shared_ptr<raw::RawDevice> raw_dev, hidpp::DeviceIndex index,
double timeout) : hidpp::Device(std::move(raw_dev), index, timeout) {
}
Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceIndex index,
double timeout)
: hidpp::Device(receiver, index, timeout) {
}
void Device::ResponseSlot::reset() {
response.reset();
sub_id.reset();
}
hidpp::Report Device::sendReport(const hidpp::Report& report) {
auto& response_slot = _responses[report.subId() % SubIDCount];
std::unique_lock<std::mutex> lock(_response_mutex);
_response_cv.wait(lock, [&response_slot]() {
return !response_slot.sub_id.has_value();
});
response_slot.sub_id = report.subId();
_sendReport(report);
bool valid = _response_cv.wait_for(lock, io_timeout, [&response_slot]() {
return response_slot.response.has_value();
});
if (!valid) {
response_slot.reset();
throw TimeoutError();
}
auto response = response_slot.response.value();
response_slot.reset();
if (std::holds_alternative<hidpp::Report>(response)) {
return std::get<hidpp::Report>(response);
} else { // if(std::holds_alternative<hidpp::Report::Hidpp10Error>(response))
auto error = std::get<hidpp::Report::Hidpp10Error>(response);
throw Error(error.error_code, error.device_index);
}
}
bool Device::responseReport(const hidpp::Report& report) {
std::lock_guard<std::mutex> lock(_response_mutex);
uint8_t sub_id;
bool is_error = false;
hidpp::Report::Hidpp10Error hidpp10_error{};
if (report.isError10(hidpp10_error)) {
sub_id = hidpp10_error.sub_id;
is_error = true;
} else {
sub_id = report.subId();
}
auto& response_slot = _responses[sub_id % SubIDCount];
if (!response_slot.sub_id.has_value() || response_slot.sub_id.value() != sub_id)
return false;
if (is_error) {
response_slot.response = hidpp10_error;
} else {
response_slot.response = report;
}
_response_cv.notify_all();
return true;
}
std::vector<uint8_t> Device::getRegister(uint8_t address,
const std::vector<uint8_t>& params, hidpp::Report::Type type)
{
const std::vector<uint8_t>& params,
hidpp::Report::Type type) {
assert(params.size() <= hidpp::LongParamLength);
uint8_t sub_id = type == hidpp::Report::Type::Short ?
GetRegisterShort : GetRegisterLong;
GetRegisterShort : GetRegisterLong;
return accessRegister(sub_id, address, params);
}
std::vector<uint8_t> Device::setRegister(uint8_t address,
const std::vector<uint8_t>& params,
hidpp::Report::Type type)
{
hidpp::Report::Type type) {
assert(params.size() <= hidpp::LongParamLength);
uint8_t sub_id = type == hidpp::Report::Type::Short ?
@ -59,18 +141,24 @@ std::vector<uint8_t> Device::setRegister(uint8_t address,
return accessRegister(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;
void Device::setRegisterNoResponse(uint8_t address,
const std::vector<uint8_t>& params,
hidpp::Report::Type type) {
assert(params.size() <= hidpp::LongParamLength);
hidpp::Report request(type, deviceIndex(), sub_id, address);
std::copy(params.begin(), params.end(), request.paramBegin());
uint8_t sub_id = type == hidpp::Report::Type::Short ?
SetRegisterShort : SetRegisterLong;
auto response = sendReport(request);
return std::vector<uint8_t>(response.paramBegin(), response.paramEnd());
return accessRegisterNoResponse(sub_id, address, params);
}
std::vector<uint8_t> Device::accessRegister(uint8_t sub_id, uint8_t address,
const std::vector<uint8_t>& params) {
auto response = sendReport(setupRegReport(deviceIndex(), sub_id, address, params));
return {response.paramBegin(), response.paramEnd()};
}
void Device::accessRegisterNoResponse(uint8_t sub_id, uint8_t address,
const std::vector<uint8_t>& params) {
sendReportNoACK(setupRegReport(deviceIndex(), sub_id, address, params));
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,28 +19,76 @@
#ifndef LOGID_BACKEND_HIDPP10_DEVICE_H
#define LOGID_BACKEND_HIDPP10_DEVICE_H
#include "../hidpp/Device.h"
#include <optional>
#include <variant>
#include <backend/hidpp/Device.h>
#include <backend/hidpp10/Error.h>
#include <backend/hidpp10/defs.h>
namespace logid {
namespace backend {
namespace hidpp10
{
class Device : public hidpp::Device
{
namespace logid::backend::hidpp10 {
class Device : public hidpp::Device {
public:
Device(const std::string& path, hidpp::DeviceIndex index);
Device(std::shared_ptr<raw::RawDevice> raw_dev,
hidpp::DeviceIndex index);
hidpp::Report sendReport(const hidpp::Report& report) final;
std::vector<uint8_t> getRegister(uint8_t address,
const std::vector<uint8_t>& params, hidpp::Report::Type type);
const std::vector<uint8_t>& params,
hidpp::Report::Type type);
std::vector<uint8_t> setRegister(uint8_t address,
const std::vector<uint8_t>& params, hidpp::Report::Type type);
const std::vector<uint8_t>& params,
hidpp::Report::Type type);
void setRegisterNoResponse(uint8_t address, const std::vector<uint8_t>& params,
hidpp::Report::Type type);
protected:
Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout);
Device(std::shared_ptr<raw::RawDevice> raw_dev,
hidpp::DeviceIndex index, double timeout);
Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceIndex index, double timeout);
bool responseReport(const hidpp::Report& report) final;
private:
std::vector<uint8_t> accessRegister(uint8_t sub_id,
uint8_t address, const std::vector<uint8_t>& params);
typedef std::variant<hidpp::Report, hidpp::Report::Hidpp10Error> Response;
struct ResponseSlot {
std::optional<Response> response;
std::optional<uint8_t> sub_id;
void reset();
};
std::array<ResponseSlot, SubIDCount> _responses;
std::vector<uint8_t> accessRegister(
uint8_t sub_id, uint8_t address, const std::vector<uint8_t>& params);
void accessRegisterNoResponse(
uint8_t sub_id, uint8_t address, const std::vector<uint8_t>& params);
protected:
template<typename T, typename... Args>
static std::shared_ptr<T> makeDerived(Args... args) {
auto device = hidpp::Device::makeDerived<T>(std::forward<Args>(args)...);
if (std::get<0>(device->version()) != 1)
throw std::invalid_argument("not a hid++ 1.0 device");
return device;
}
public:
template<typename... Args>
static std::shared_ptr<Device> make(Args... args) {
return makeDerived<Device>(std::forward<Args>(args)...);
}
};
}}}
}
#endif //LOGID_BACKEND_HIDPP10_DEVICE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,52 +16,53 @@
*
*/
#include <backend/hidpp10/Error.h>
#include <cassert>
#include <string>
#include "Error.h"
using namespace logid::backend;
using namespace logid::backend::hidpp10;
Error::Error(uint8_t code): _code(code)
{
Error::Error(uint8_t code, hidpp::DeviceIndex index) : _code(code), _index(index) {
assert(code != Success);
}
const char* Error::what() const noexcept
{
switch(_code) {
case Success:
return "Success";
case InvalidSubID:
return "Invalid sub ID";
case InvalidAddress:
return "Invalid address";
case InvalidValue:
return "Invalid value";
case ConnectFail:
return "Connection failure";
case TooManyDevices:
return "Too many devices";
case AlreadyExists:
return "Already exists";
case Busy:
return "Busy";
case UnknownDevice:
return "Unknown device";
case ResourceError:
return "Resource error";
case RequestUnavailable:
return "Request unavailable";
case InvalidParameterValue:
return "Invalid parameter value";
case WrongPINCode:
return "Wrong PIN code";
default:
return "Unknown error code";
const char* Error::what() const noexcept {
switch (_code) {
case Success:
return "Success";
case InvalidSubID:
return "Invalid sub ID";
case InvalidAddress:
return "Invalid address";
case InvalidValue:
return "Invalid value";
case ConnectFail:
return "Connection failure";
case TooManyDevices:
return "Too many devices";
case AlreadyExists:
return "Already exists";
case Busy:
return "Busy";
case UnknownDevice:
return "Unknown device";
case ResourceError:
return "Resource error";
case RequestUnavailable:
return "Request unavailable";
case InvalidParameterValue:
return "Invalid parameter value";
case WrongPINCode:
return "Wrong PIN code";
default:
return "Unknown error code";
}
}
uint8_t Error::code() const noexcept
{
uint8_t Error::code() const noexcept {
return _code;
}
}
hidpp::DeviceIndex Error::deviceIndex() const noexcept {
return _index;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,18 +19,16 @@
#ifndef LOGID_BACKEND_HIDPP10_ERROR_H
#define LOGID_BACKEND_HIDPP10_ERROR_H
#include <backend/hidpp/defs.h>
#include <cstdint>
#include <exception>
namespace logid {
namespace backend {
namespace hidpp10 {
namespace logid::backend::hidpp10 {
static constexpr uint8_t ErrorID = 0x8f;
class Error: public std::exception
{
class Error : public std::exception {
public:
enum ErrorCode: uint8_t
{
enum ErrorCode : uint8_t {
Success = 0x00,
InvalidSubID = 0x01,
InvalidAddress = 0x02,
@ -46,14 +44,18 @@ namespace hidpp10 {
WrongPINCode = 0x0C
};
explicit Error(uint8_t code);
Error(uint8_t code, hidpp::DeviceIndex index);
const char* what() const noexcept override;
uint8_t code() const noexcept;
[[nodiscard]] const char* what() const noexcept override;
[[nodiscard]] uint8_t code() const noexcept;
[[nodiscard]] hidpp::DeviceIndex deviceIndex() const noexcept;
private:
uint8_t _code;
hidpp::DeviceIndex _index;
};
}}}
}
#endif //LOGID_BACKEND_HIDPP10_ERROR_H

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,17 +19,15 @@
#ifndef LOGID_BACKEND_HIDPP10_DEFS_H
#define LOGID_BACKEND_HIDPP10_DEFS_H
namespace logid {
namespace backend {
namespace hidpp10
{
enum SubID: uint8_t
{
namespace logid::backend::hidpp10 {
enum SubID : uint8_t {
SetRegisterShort = 0x80,
GetRegisterShort = 0x81,
SetRegisterLong = 0x82,
GetRegisterLong = 0x83
GetRegisterLong = 0x83,
};
}}}
static constexpr size_t SubIDCount = 4;
}
#endif //LOGID_BACKEND_HIDPP10_DEFS_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,61 +17,146 @@
*/
#include <cassert>
#include <backend/hidpp20/Device.h>
#include <backend/Error.h>
#include <backend/hidpp10/Receiver.h>
#include "Device.h"
#include "../hidpp/defs.h"
using namespace logid::backend;
using namespace logid::backend::hidpp20;
Device::Device(std::string path, hidpp::DeviceIndex index)
: hidpp::Device(path, index)
{
assert(std::get<0>(version()) >= 2);
Device::Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) :
hidpp::Device(path, index, monitor, timeout) {
}
Device::Device(std::shared_ptr<raw::RawDevice> raw_device, hidpp::DeviceIndex index)
: hidpp::Device(raw_device, index)
{
assert(std::get<0>(version()) >= 2);
Device::Device(std::shared_ptr<raw::RawDevice> raw_device,
hidpp::DeviceIndex index, double timeout) :
hidpp::Device(std::move(raw_device), index, timeout) {
}
Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceConnectionEvent event, double timeout) :
hidpp::Device(receiver, event, timeout) {
}
Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceIndex index, double timeout)
: hidpp::Device(receiver, index, timeout) {
}
std::vector<uint8_t> Device::callFunction(uint8_t feature_index,
uint8_t function, std::vector<uint8_t>& params)
{
uint8_t function, std::vector<uint8_t>& params) {
hidpp::Report::Type type;
assert(params.size() <= hidpp::LongParamLength);
if(params.size() <= hidpp::ShortParamLength)
if (params.size() <= hidpp::ShortParamLength)
type = hidpp::Report::Type::Short;
else if(params.size() <= hidpp::LongParamLength)
else if (params.size() <= hidpp::LongParamLength)
type = hidpp::Report::Type::Long;
else
throw hidpp::Report::InvalidReportID();
hidpp::Report request(type, deviceIndex(), feature_index, function,
LOGID_HIDPP_SOFTWARE_ID);
hidpp::softwareID);
std::copy(params.begin(), params.end(), request.paramBegin());
auto response = this->sendReport(request);
return std::vector<uint8_t>(response.paramBegin(), response.paramEnd());
return {response.paramBegin(), response.paramEnd()};
}
void Device::callFunctionNoResponse(uint8_t feature_index, uint8_t function,
std::vector<uint8_t> &params)
{
std::vector<uint8_t>& params) {
hidpp::Report::Type type;
assert(params.size() <= hidpp::LongParamLength);
if(params.size() <= hidpp::ShortParamLength)
if (params.size() <= hidpp::ShortParamLength)
type = hidpp::Report::Type::Short;
else if(params.size() <= hidpp::LongParamLength)
else if (params.size() <= hidpp::LongParamLength)
type = hidpp::Report::Type::Long;
else
throw hidpp::Report::InvalidReportID();
hidpp::Report request(type, deviceIndex(), feature_index, function,
LOGID_HIDPP_SOFTWARE_ID);
hidpp::Report request(type, deviceIndex(), feature_index, function, hidpp::softwareID);
std::copy(params.begin(), params.end(), request.paramBegin());
this->sendReportNoResponse(request);
}
this->sendReportNoACK(request);
}
hidpp::Report Device::sendReport(const hidpp::Report& report) {
auto& response_slot = _responses[report.feature() % _responses.size()];
std::unique_lock<std::mutex> response_lock(_response_mutex);
_response_cv.wait(response_lock, [&response_slot]() {
return !response_slot.feature.has_value();
});
response_slot.feature = report.feature();
_sendReport(report);
bool valid = _response_cv.wait_for(
response_lock, io_timeout,
[&response_slot]() {
return response_slot.response.has_value();
});
if (!valid) {
response_slot.reset();
throw TimeoutError();
}
assert(response_slot.response.has_value());
auto response = response_slot.response.value();
response_slot.reset();
if (std::holds_alternative<hidpp::Report>(response)) {
return std::get<hidpp::Report>(response);
} else { // if(std::holds_alternative<Error::ErrorCode>(response))
auto error = std::get<hidpp::Report::Hidpp20Error>(response);
throw Error(error.error_code, error.device_index);
}
}
void Device::sendReportNoACK(const hidpp::Report& report) {
hidpp::Report no_ack_report(report);
no_ack_report.setSwId(hidpp::noAckSoftwareID);
_sendReport(std::move(no_ack_report));
}
bool Device::responseReport(const hidpp::Report& report) {
auto& response_slot = _responses[report.feature() % _responses.size()];
std::lock_guard<std::mutex> lock(_response_mutex);
uint8_t sw_id, feature;
bool is_error = false;
hidpp::Report::Hidpp20Error hidpp20_error{};
if (report.isError20(hidpp20_error)) {
is_error = true;
sw_id = hidpp20_error.software_id;
feature = hidpp20_error.feature_index;
} else {
sw_id = report.swId();
feature = report.feature();
}
if (sw_id != hidpp::softwareID)
return false;
if (!response_slot.feature || response_slot.feature.value() != feature) {
return false;
}
if (is_error) {
response_slot.response = hidpp20_error;
} else {
response_slot.response = report;
}
_response_cv.notify_all();
return true;
}
void Device::ResponseSlot::reset() {
response.reset();
feature.reset();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,26 +19,64 @@
#ifndef LOGID_BACKEND_HIDPP20_DEVICE_H
#define LOGID_BACKEND_HIDPP20_DEVICE_H
#include "../hidpp/Device.h"
#include <cstdint>
#include <optional>
#include <variant>
#include <backend/hidpp20/Error.h>
#include <backend/hidpp/Device.h>
namespace logid {
namespace backend {
namespace hidpp20 {
class Device : public hidpp::Device
{
namespace logid::backend::hidpp20 {
class Device : public hidpp::Device {
public:
Device(std::string path, hidpp::DeviceIndex index);
Device(std::shared_ptr<raw::RawDevice> raw_device, hidpp::DeviceIndex index);
std::vector<uint8_t> callFunction(uint8_t feature_index,
uint8_t function,
std::vector<uint8_t>& params);
uint8_t function,
std::vector<uint8_t>& params);
void callFunctionNoResponse(uint8_t feature_index,
uint8_t function,
std::vector<uint8_t>& params);
uint8_t function,
std::vector<uint8_t>& params);
hidpp::Report sendReport(const hidpp::Report& report) final;
void sendReportNoACK(const hidpp::Report& report) final;
protected:
Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout);
Device(std::shared_ptr<raw::RawDevice> raw_device,
hidpp::DeviceIndex index, double timeout);
Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceConnectionEvent event, double timeout);
Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
hidpp::DeviceIndex index, double timeout);
bool responseReport(const hidpp::Report& report) final;
private:
typedef std::variant<hidpp::Report, hidpp::Report::Hidpp20Error> Response;
struct ResponseSlot {
std::optional<Response> response;
std::optional<uint8_t> feature;
void reset();
};
/* Multiplex responses on lower nibble of SubID, ignore upper nibble for space */
std::array<ResponseSlot, 16> _responses;
public:
template <typename... Args>
static std::shared_ptr<Device> make(Args... args) {
auto device = makeDerived<Device>(std::forward<Args>(args)...);
if (std::get<0>(device->version()) < 2)
throw std::invalid_argument("not a hid++ 2.0 device");
return device;
}
};
}}}
}
#endif //LOGID_BACKEND_HIDPP20_DEVICE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,47 +16,49 @@
*
*/
#include <backend/hidpp20/Error.h>
#include <cassert>
#include "Error.h"
using namespace logid::backend;
using namespace logid::backend::hidpp20;
Error::Error(uint8_t code) : _code (code)
{
Error::Error(uint8_t code, hidpp::DeviceIndex index) : _code(code), _index (index) {
assert(_code != NoError);
}
const char* Error::what() const noexcept
{
switch(_code) {
case NoError:
return "No error";
case Unknown:
return "Unknown";
case InvalidArgument:
return "Invalid argument";
case OutOfRange:
return "Out of range";
case HardwareError:
return "Hardware error";
case LogitechInternal:
return "Logitech internal feature";
case InvalidFeatureIndex:
return "Invalid feature index";
case InvalidFunctionID:
return "Invalid function ID";
case Busy:
return "Busy";
case Unsupported:
return "Unsupported";
case UnknownDevice:
return "Unknown device";
default:
return "Unknown error code";
const char* Error::what() const noexcept {
switch (_code) {
case NoError:
return "No error";
case Unknown:
return "Unknown";
case InvalidArgument:
return "Invalid argument";
case OutOfRange:
return "Out of range";
case HardwareError:
return "Hardware error";
case LogitechInternal:
return "Logitech internal feature";
case InvalidFeatureIndex:
return "Invalid feature index";
case InvalidFunctionID:
return "Invalid function ID";
case Busy:
return "Busy";
case Unsupported:
return "Unsupported";
case UnknownDevice:
return "Unknown device";
default:
return "Unknown error code";
}
}
uint8_t Error::code() const noexcept
{
uint8_t Error::code() const noexcept {
return _code;
}
hidpp::DeviceIndex Error::deviceIndex() const noexcept {
return _index;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,18 +19,16 @@
#ifndef LOGID_BACKEND_HIDPP20_ERROR_H
#define LOGID_BACKEND_HIDPP20_ERROR_H
#include <backend/hidpp/defs.h>
#include <stdexcept>
#include <cstdint>
namespace logid {
namespace backend {
namespace hidpp20 {
namespace logid::backend::hidpp20 {
static constexpr uint8_t ErrorID = 0xFF;
class Error: public std::exception
{
class Error : public std::exception {
public:
enum ErrorCode: uint8_t {
enum ErrorCode : uint8_t {
NoError = 0,
Unknown = 1,
InvalidArgument = 2,
@ -44,14 +42,18 @@ namespace hidpp20 {
UnknownDevice = 10
};
explicit Error(uint8_t code);
Error(uint8_t code, hidpp::DeviceIndex index);
const char* what() const noexcept override;
uint8_t code() const noexcept;
[[nodiscard]] const char* what() const noexcept override;
[[nodiscard]] uint8_t code() const noexcept;
[[nodiscard]] hidpp::DeviceIndex deviceIndex() const noexcept;
private:
uint8_t _code;
hidpp::DeviceIndex _index;
};
}}}
}
#endif //LOGID_BACKEND_HIDPP20_ERROR_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,57 +16,51 @@
*
*/
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/EssentialFeature.h>
#include <backend/hidpp20/features/Root.h>
#include <backend/hidpp20/Error.h>
#include <cassert>
#include "EssentialFeature.h"
#include "feature_defs.h"
#include "features/Root.h"
#include "Error.h"
using namespace logid::backend::hidpp20;
std::vector<uint8_t> EssentialFeature::callFunction(uint8_t function_id,
std::vector<uint8_t>& params)
{
std::vector<uint8_t>& params) {
hidpp::Report::Type type;
assert(params.size() <= hidpp::LongParamLength);
if(params.size() <= hidpp::ShortParamLength)
if (params.size() <= hidpp::ShortParamLength)
type = hidpp::Report::Type::Short;
else if(params.size() <= hidpp::LongParamLength)
else if (params.size() <= hidpp::LongParamLength)
type = hidpp::Report::Type::Long;
else
throw hidpp::Report::InvalidReportID();
hidpp::Report request(type, _device->deviceIndex(), _index, function_id,
LOGID_HIDPP_SOFTWARE_ID);
hidpp::Report request(type, _device->deviceIndex(), _index, function_id, hidpp::softwareID);
std::copy(params.begin(), params.end(), request.paramBegin());
auto response = _device->sendReport(request);
return std::vector<uint8_t>(response.paramBegin(), response.paramEnd());
return {response.paramBegin(), response.paramEnd()};
}
EssentialFeature::EssentialFeature(hidpp::Device* dev, uint16_t _id) :
_device (dev)
{
_device(dev) {
_index = hidpp20::FeatureID::ROOT;
if(_id)
{
if (_id) {
std::vector<uint8_t> getFunc_req(2);
getFunc_req[0] = (_id >> 8) & 0xff;
getFunc_req[1] = _id & 0xff;
try {
auto getFunc_resp = this->callFunction(Root::GetFeature,
getFunc_req);
_index = getFunc_resp[0];
} catch(Error& e) {
if(e.code() == Error::InvalidFeatureIndex)
_index = this->callFunction(Root::GetFeature, getFunc_req).at(0);
} catch (Error& e) {
if (e.code() == Error::InvalidFeatureIndex)
throw UnsupportedFeature(_id);
throw e;
}
// 0 if not found
if(!_index)
if (!_index)
throw UnsupportedFeature(_id);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -25,26 +25,24 @@
* hidpp::Device class. No version checks are provided here
*/
#include "Device.h"
#include <backend/hidpp/Device.h>
namespace logid {
namespace backend {
namespace hidpp20
{
class EssentialFeature
{
namespace logid::backend::hidpp20 {
class EssentialFeature {
public:
static const uint16_t ID;
virtual uint16_t getID() = 0;
protected:
EssentialFeature(hidpp::Device* dev, uint16_t _id);
std::vector<uint8_t> callFunction(uint8_t function_id,
std::vector<uint8_t>& params);
private:
hidpp::Device* _device;
std::vector<uint8_t>& params);
hidpp::Device* const _device;
uint8_t _index;
};
}}}
}
#endif //LOGID_BACKEND_HIDPP20_ESSENTIAL_FEATURE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,62 +16,53 @@
*
*/
#include "Error.h"
#include "Feature.h"
#include "feature_defs.h"
#include "features/Root.h"
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/Device.h>
#include <backend/hidpp20/features/Root.h>
using namespace logid::backend::hidpp20;
const char* UnsupportedFeature::what() const noexcept
{
const char* UnsupportedFeature::what() const noexcept {
return "Unsupported feature";
}
uint16_t UnsupportedFeature::code() const noexcept
{
uint16_t UnsupportedFeature::code() const noexcept {
return _f_id;
}
std::vector<uint8_t> Feature::callFunction(uint8_t function_id,
std::vector<uint8_t>& params)
{
std::vector<uint8_t>& params) {
return _device->callFunction(_index, function_id, params);
}
void Feature::callFunctionNoResponse(uint8_t function_id,
std::vector<uint8_t>& params)
{
std::vector<uint8_t>& params) {
_device->callFunctionNoResponse(_index, function_id, params);
}
Feature::Feature(Device* dev, uint16_t _id) : _device (dev)
{
Feature::Feature(Device* dev, uint16_t _id) : _device(dev) {
_index = hidpp20::FeatureID::ROOT;
if(_id)
{
if (_id) {
std::vector<uint8_t> getFunc_req(2);
getFunc_req[0] = (_id >> 8) & 0xff;
getFunc_req[1] = _id & 0xff;
try {
auto getFunc_resp = this->callFunction(Root::GetFeature,
getFunc_req);
auto getFunc_resp = this->callFunction(Root::GetFeature, getFunc_req);
_index = getFunc_resp[0];
} catch(Error& e) {
if(e.code() == Error::InvalidFeatureIndex)
} catch (Error& e) {
if (e.code() == Error::InvalidFeatureIndex)
throw UnsupportedFeature(_id);
throw e;
}
// 0 if not found
if(!_index)
if (!_index)
throw UnsupportedFeature(_id);
}
}
uint8_t Feature::featureIndex()
{
uint8_t Feature::featureIndex() const {
return _index;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -20,37 +20,44 @@
#define LOGID_BACKEND_HIDPP20_FEATURE_H
#include <cstdint>
#include "Device.h"
#include <exception>
#include <vector>
namespace logid {
namespace backend {
namespace hidpp20 {
class UnsupportedFeature : public std::exception
{
namespace logid::backend::hidpp20 {
class Device;
class UnsupportedFeature : public std::exception {
public:
explicit UnsupportedFeature(uint16_t ID) : _f_id (ID) {}
const char* what() const noexcept override;
uint16_t code() const noexcept;
explicit UnsupportedFeature(uint16_t ID) : _f_id(ID) {}
[[nodiscard]] const char* what() const noexcept override;
[[nodiscard]] uint16_t code() const noexcept;
private:
uint16_t _f_id;
};
class Feature
{
class Feature {
public:
static const uint16_t ID;
virtual uint16_t getID() = 0;
uint8_t featureIndex();
[[nodiscard]] uint8_t featureIndex() const;
virtual ~Feature() = default;
protected:
explicit Feature(Device* dev, uint16_t _id);
std::vector<uint8_t> callFunction(uint8_t function_id,
std::vector<uint8_t>& params);
void callFunctionNoResponse(uint8_t function_id,
std::vector<uint8_t>& params);
private:
Device* _device;
std::vector<uint8_t> callFunction(uint8_t function_id, std::vector<uint8_t>& params);
void callFunctionNoResponse(uint8_t function_id, std::vector<uint8_t>& params);
Device* const _device;
uint8_t _index;
};
}}}
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -21,9 +21,7 @@
#include <cstdint>
namespace logid {
namespace backend {
namespace hidpp20 {
namespace logid::backend::hidpp20 {
struct feature_info {
uint16_t feature_id;
bool obsolete;
@ -31,10 +29,8 @@ namespace hidpp20 {
bool hidden;
};
namespace FeatureID
{
enum FeatureID : uint16_t
{
namespace FeatureID {
enum FeatureID : uint16_t {
ROOT = 0x0000,
FEATURE_SET = 0x0001,
FEATURE_INFO = 0x0002,
@ -78,9 +74,11 @@ namespace hidpp20 {
POINTER_AXES_ORIENTATION = 0x2006,
VERTICAL_SCROLLING = 0x2100,
SMART_SHIFT = 0x2110,
SMART_SHIFT_V2 = 0x2111,
HIRES_SCROLLING = 0x2120,
HIRES_SCROLLING_V2 = 0x2121, // Referred to as Hi-res wheel in cvuchener/hidpp, seems to be V2?
LORES_SCROLLING = 0x2130,
THUMB_WHEEL = 0x2150,
MOUSE_POINTER = 0x2200, // Possibly predecessor to 0x2201?
ADJUSTABLE_DPI = 0x2201,
ANGLE_SNAPPING = 0x2230,
@ -126,6 +124,6 @@ namespace hidpp20 {
};
}
}}}
}
#endif //LOGID_BACKEND_HIDPP20_FEATUREDEFS

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,35 +15,32 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "AdjustableDPI.h"
#include <backend/hidpp20/features/AdjustableDPI.h>
using namespace logid::backend::hidpp20;
AdjustableDPI::AdjustableDPI(Device* dev) : Feature(dev, ID)
{
AdjustableDPI::AdjustableDPI(Device* dev) : Feature(dev, ID) {
}
uint8_t AdjustableDPI::getSensorCount()
{
uint8_t AdjustableDPI::getSensorCount() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetSensorCount, params);
return response[0];
}
AdjustableDPI::SensorDPIList AdjustableDPI::getSensorDPIList(uint8_t sensor)
{
AdjustableDPI::SensorDPIList AdjustableDPI::getSensorDPIList(uint8_t sensor) {
SensorDPIList dpi_list{};
std::vector<uint8_t> params(1);
params[0] = sensor;
auto response = callFunction(GetSensorDPIList, params);
dpi_list.dpiStep = false;
for(std::size_t i = 1; i < response.size(); i+=2) {
for (std::size_t i = 1; i < response.size(); i += 2) {
uint16_t dpi = response[i + 1];
dpi |= (response[i] << 8);
if(!dpi)
if (!dpi)
break;
if(dpi >= 0xe000) {
if (dpi >= 0xe000) {
dpi_list.isRange = true;
dpi_list.dpiStep = dpi - 0xe000;
} else {
@ -54,8 +51,7 @@ AdjustableDPI::SensorDPIList AdjustableDPI::getSensorDPIList(uint8_t sensor)
return dpi_list;
}
uint16_t AdjustableDPI::getDefaultSensorDPI(uint8_t sensor)
{
uint16_t AdjustableDPI::getDefaultSensorDPI(uint8_t sensor) {
std::vector<uint8_t> params(1);
params[0] = sensor;
auto response = callFunction(GetSensorDPI, params);
@ -66,8 +62,7 @@ uint16_t AdjustableDPI::getDefaultSensorDPI(uint8_t sensor)
return default_dpi;
}
uint16_t AdjustableDPI::getSensorDPI(uint8_t sensor)
{
uint16_t AdjustableDPI::getSensorDPI(uint8_t sensor) {
std::vector<uint8_t> params(1);
params[0] = sensor;
auto response = callFunction(GetSensorDPI, params);
@ -78,8 +73,7 @@ uint16_t AdjustableDPI::getSensorDPI(uint8_t sensor)
return dpi;
}
void AdjustableDPI::setSensorDPI(uint8_t sensor, uint16_t dpi)
{
void AdjustableDPI::setSensorDPI(uint8_t sensor, uint16_t dpi) {
std::vector<uint8_t> params(3);
params[0] = sensor;
params[1] = (dpi >> 8);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,18 +18,15 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_ADJUSTABLEDPI_H
#define LOGID_BACKEND_HIDPP20_FEATURE_ADJUSTABLEDPI_H
#include "../feature_defs.h"
#include "../Feature.h"
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
namespace logid {
namespace backend {
namespace hidpp20
{
class AdjustableDPI : public Feature
{
namespace logid::backend::hidpp20 {
class AdjustableDPI : public Feature {
public:
static const uint16_t ID = FeatureID::ADJUSTABLE_DPI;
virtual uint16_t getID() { return ID; }
[[nodiscard]] uint16_t getID() final { return ID; }
enum Function {
GetSensorCount = 0,
@ -38,23 +35,24 @@ namespace hidpp20
SetSensorDPI = 3
};
AdjustableDPI(Device* dev);
explicit AdjustableDPI(Device* dev);
uint8_t getSensorCount();
struct SensorDPIList
{
struct SensorDPIList {
std::vector<uint16_t> dpis;
bool isRange;
uint16_t dpiStep;
};
SensorDPIList getSensorDPIList(uint8_t sensor);
uint16_t getDefaultSensorDPI(uint8_t sensor);
uint16_t getSensorDPI(uint8_t sensor);
void setSensorDPI(uint8_t sensor, uint16_t dpi);
};
}}}
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_ADJUSTABLEDPI_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,17 +15,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ChangeHost.h"
#include "../Error.h"
#include <backend/hidpp20/features/ChangeHost.h>
#include <backend/hidpp20/Device.h>
using namespace logid::backend::hidpp20;
ChangeHost::ChangeHost(Device *dev) : Feature(dev, ID), _host_count (0)
{
ChangeHost::ChangeHost(Device* dev) : Feature(dev, ID), _host_count(0) {
}
ChangeHost::HostInfo ChangeHost::getHostInfo()
{
ChangeHost::HostInfo ChangeHost::getHostInfo() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetHostInfo, params);
@ -34,33 +32,32 @@ ChangeHost::HostInfo ChangeHost::getHostInfo()
info.currentHost = response[1];
info.enhancedHostSwitch = response[2] & 1;
if(!_host_count)
if (!_host_count)
_host_count = info.hostCount;
return info;
}
void ChangeHost::setHost(uint8_t host)
{
void ChangeHost::setHost(uint8_t host) {
/* Expect connection to be severed here, send without response
*
* Since there is no response, we have to emulate any kind of
* error that may be returned (i.e. InvalidArgument as per the docs)
*/
if(!_host_count)
if (!_host_count)
getHostInfo();
if(host >= _host_count)
throw hidpp20::Error(hidpp20::Error::InvalidArgument);
if (host >= _host_count)
throw Error(hidpp20::Error::InvalidArgument, _device->deviceIndex());
std::vector<uint8_t> params = {host};
callFunctionNoResponse(SetCurrentHost, params);
}
std::vector<uint8_t> ChangeHost::getCookies()
{
if(!_host_count)
[[maybe_unused]]
std::vector<uint8_t> ChangeHost::getCookies() {
if (!_host_count)
getHostInfo();
std::vector<uint8_t> params(0);
@ -71,8 +68,8 @@ std::vector<uint8_t> ChangeHost::getCookies()
return response;
}
void ChangeHost::setCookie(uint8_t host, uint8_t cookie)
{
[[maybe_unused]]
void ChangeHost::setCookie(uint8_t host, uint8_t cookie) {
std::vector<uint8_t> params = {host, cookie};
callFunction(SetCookie, params);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,20 +18,17 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_CHANGEHOST_H
#define LOGID_BACKEND_HIDPP20_FEATURE_CHANGEHOST_H
#include "../feature_defs.h"
#include "../Feature.h"
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp20/Feature.h>
namespace logid {
namespace backend {
namespace hidpp20
{
class ChangeHost : public Feature
{
namespace logid::backend::hidpp20 {
class ChangeHost : public Feature {
public:
static const uint16_t ID = FeatureID::CHANGE_HOST;
virtual uint16_t getID() { return ID; }
ChangeHost(Device* dev);
[[nodiscard]] uint16_t getID() final { return ID; }
explicit ChangeHost(Device* dev);
enum Function {
GetHostInfo = 0,
@ -40,22 +37,23 @@ namespace hidpp20
SetCookie = 3
};
struct HostInfo
{
struct HostInfo {
uint8_t hostCount;
uint8_t currentHost;
bool enhancedHostSwitch;
};
HostInfo getHostInfo();
void setHost(uint8_t host);
std::vector<uint8_t> getCookies();
void setCookie(uint8_t host, uint8_t cookie);
[[maybe_unused]] [[maybe_unused]] std::vector<uint8_t> getCookies();
[[maybe_unused]] [[maybe_unused]] void setCookie(uint8_t host, uint8_t cookie);
private:
uint8_t _host_count;
};
}}}
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_CHANGEHOST_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,71 +16,49 @@
*
*/
#include <cmath>
#include "DeviceName.h"
#include <backend/hidpp20/features/DeviceName.h>
using namespace logid::backend;
using namespace logid::backend::hidpp20;
DeviceName::DeviceName(Device* dev) : Feature(dev, ID)
{
}
namespace {
std::string _getName(uint8_t length,
const std::function<std::vector<uint8_t>(std::vector<uint8_t>)>& fcall) {
uint8_t function_calls = length / hidpp::LongParamLength;
if (length % hidpp::LongParamLength)
function_calls++;
std::vector<uint8_t> params(1);
std::string name;
uint8_t DeviceName::getNameLength()
{
std::vector<uint8_t> params(0);
auto response = this->callFunction(Function::GetLength, params);
return response[0];
}
std::string _getName(uint8_t length,
const std::function<std::vector<uint8_t>(std::vector<uint8_t>)>& fcall)
{
uint8_t function_calls = length/hidpp::LongParamLength;
if(length % hidpp::LongParamLength)
function_calls++;
std::vector<uint8_t> params(1);
std::string name;
for(uint8_t i = 0; i < function_calls; i++) {
params[0] = i*hidpp::LongParamLength;
auto name_section = fcall(params);
for(std::size_t j = 0; j < hidpp::LongParamLength; j++) {
if(params[0] + j >= length)
return name;
name += name_section[j];
for (uint8_t i = 0; i < function_calls; i++) {
params[0] = i * hidpp::LongParamLength;
auto name_section = fcall(params);
for (std::size_t j = 0; j < hidpp::LongParamLength; j++) {
if (params[0] + j >= length)
return name;
name += (char) name_section[j];
}
}
return name;
}
return name;
}
std::string DeviceName::getName()
{
return _getName(getNameLength(), [this]
(std::vector<uint8_t> params)->std::vector<uint8_t> {
return this->callFunction(Function::GetDeviceName, params);
});
DeviceName::DeviceName(hidpp::Device* dev) :
EssentialFeature(dev, ID) {
}
EssentialDeviceName::EssentialDeviceName(hidpp::Device* dev) :
EssentialFeature(dev, ID)
{
}
uint8_t EssentialDeviceName::getNameLength()
{
uint8_t DeviceName::getNameLength() {
std::vector<uint8_t> params(0);
auto response = this->callFunction(DeviceName::Function::GetLength, params);
return response[0];
}
std::string EssentialDeviceName::getName()
{
std::string DeviceName::getName() {
return _getName(getNameLength(), [this]
(std::vector<uint8_t> params)->std::vector<uint8_t> {
(std::vector<uint8_t> params) -> std::vector<uint8_t> {
return this->callFunction(DeviceName::Function::GetDeviceName, params);
});
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,43 +19,27 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_DEVICENAME_H
#define LOGID_BACKEND_HIDPP20_FEATURE_DEVICENAME_H
#include "../Feature.h"
#include "../feature_defs.h"
#include "../EssentialFeature.h"
#include <backend/hidpp20/EssentialFeature.h>
#include <backend/hidpp20/feature_defs.h>
namespace logid {
namespace backend {
namespace hidpp20
{
class DeviceName : public Feature
{
namespace logid::backend::hidpp20 {
class DeviceName : public EssentialFeature {
public:
static const uint16_t ID = FeatureID::DEVICE_NAME;
virtual uint16_t getID() { return ID; }
enum Function : uint8_t
{
enum Function : uint8_t {
GetLength = 0,
GetDeviceName = 1
};
explicit DeviceName(Device* device);
[[nodiscard]] uint16_t getID() final { return ID; }
uint8_t getNameLength();
std::string getName();
explicit DeviceName(hidpp::Device* device);
[[nodiscard]] uint8_t getNameLength();
[[nodiscard]] std::string getName();
};
class EssentialDeviceName : public EssentialFeature
{
public:
static const uint16_t ID = FeatureID::DEVICE_NAME;
virtual uint16_t getID() { return ID; }
explicit EssentialDeviceName(hidpp::Device* device);
uint8_t getNameLength();
std::string getName();
};
}}}
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_DEVICENAME_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,23 +15,21 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "FeatureSet.h"
#include <backend/hidpp20/features/FeatureSet.h>
using namespace logid::backend::hidpp20;
FeatureSet::FeatureSet(Device *device) : Feature(device, ID)
{
[[maybe_unused]]
FeatureSet::FeatureSet(Device* device) : Feature(device, ID) {
}
uint8_t FeatureSet::getFeatureCount()
{
uint8_t FeatureSet::getFeatureCount() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetFeatureCount, params);
return response[0];
}
uint16_t FeatureSet::getFeature(uint8_t feature_index)
{
uint16_t FeatureSet::getFeature(uint8_t feature_index) {
std::vector<uint8_t> params(1);
params[0] = feature_index;
auto response = callFunction(GetFeature, params);
@ -41,11 +39,11 @@ uint16_t FeatureSet::getFeature(uint8_t feature_index)
return feature_id;
}
std::map<uint8_t, uint16_t> FeatureSet::getFeatures()
{
[[maybe_unused]]
std::map<uint8_t, uint16_t> FeatureSet::getFeatures() {
uint8_t feature_count = getFeatureCount();
std::map<uint8_t, uint16_t> features;
for(uint8_t i = 0; i < feature_count; i++)
for (uint8_t i = 0; i < feature_count; i++)
features[i] = getFeature(i);
return features;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,31 +18,32 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_FEATURESET_H
#define LOGID_BACKEND_HIDPP20_FEATURE_FEATURESET_H
#include "../Feature.h"
#include "../feature_defs.h"
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
#include <map>
namespace logid {
namespace backend {
namespace hidpp20
{
class FeatureSet : public Feature
{
namespace logid::backend::hidpp20 {
class FeatureSet : public Feature {
public:
static const uint16_t ID = FeatureID::FEATURE_SET;
virtual uint16_t getID() { return ID; }
enum Function : uint8_t
{
[[nodiscard]] uint16_t getID() final { return ID; }
enum Function : uint8_t {
GetFeatureCount = 0,
GetFeature = 1
};
[[maybe_unused]] [[maybe_unused]]
explicit FeatureSet(Device* device);
uint8_t getFeatureCount();
uint16_t getFeature(uint8_t feature_index);
std::map<uint8_t, uint16_t> getFeatures();
[[nodiscard]] uint8_t getFeatureCount();
[[nodiscard]] uint16_t getFeature(uint8_t feature_index);
[[maybe_unused]]
[[nodiscard]] std::map<uint8_t, uint16_t> getFeatures();
};
}}}
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_FEATURESET_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,17 +15,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <backend/hidpp20/features/HiresScroll.h>
#include <cassert>
#include "HiresScroll.h"
using namespace logid::backend::hidpp20;
HiresScroll::HiresScroll(Device *device) : Feature(device, ID)
{
HiresScroll::HiresScroll(Device* device) : Feature(device, ID) {
}
HiresScroll::Capabilities HiresScroll::getCapabilities()
{
HiresScroll::Capabilities HiresScroll::getCapabilities() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetCapabilities, params);
@ -35,42 +33,36 @@ HiresScroll::Capabilities HiresScroll::getCapabilities()
return capabilities;
}
uint8_t HiresScroll::getMode()
{
uint8_t HiresScroll::getMode() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetMode, params);
return response[0];
}
void HiresScroll::setMode(uint8_t mode)
{
void HiresScroll::setMode(uint8_t mode) {
std::vector<uint8_t> params(1);
params[0] = mode;
callFunction(SetMode, params);
}
bool HiresScroll::getRatchetState()
{
[[maybe_unused]] bool HiresScroll::getRatchetState() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetRatchetState, params);
return params[0];
}
HiresScroll::WheelStatus HiresScroll::wheelMovementEvent(const hidpp::Report
&report)
{
HiresScroll::WheelStatus HiresScroll::wheelMovementEvent(const hidpp::Report& report) {
assert(report.function() == WheelMovement);
WheelStatus status{};
status.hiRes = report.paramBegin()[0] & 1<<4;
status.hiRes = report.paramBegin()[0] & 1 << 4;
status.periods = report.paramBegin()[0] & 0x0F;
status.deltaV = report.paramBegin()[1] << 8 | report.paramBegin()[2];
status.deltaV = (int16_t) (report.paramBegin()[1] << 8 | report.paramBegin()[2]);
return status;
}
HiresScroll::RatchetState HiresScroll::ratchetSwitchEvent(const hidpp::Report
&report)
{
assert(report.function() == WheelMovement);
[[maybe_unused]]
HiresScroll::RatchetState HiresScroll::ratchetSwitchEvent(const hidpp::Report& report) {
assert(report.function() == RatchetSwitch);
// Possible bad cast
return static_cast<RatchetState>(report.paramBegin()[0]);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,76 +18,73 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H
#define LOGID_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H
#include "../Feature.h"
#include "../feature_defs.h"
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp/Report.h>
namespace logid {
namespace backend {
namespace hidpp20
{
class HiresScroll : public Feature
{
namespace logid::backend::hidpp20 {
class HiresScroll : public Feature {
public:
///TODO: Hires scroll V1?
static const uint16_t ID = FeatureID::HIRES_SCROLLING_V2;
virtual uint16_t getID() { return ID; }
enum Function : uint8_t
{
uint16_t getID() final { return ID; }
enum Function : uint8_t {
GetCapabilities = 0,
GetMode = 1,
SetMode = 2,
GetRatchetState = 3
};
enum Event : uint8_t
{
enum Event : uint8_t {
WheelMovement = 0,
RatchetSwitch = 1,
};
enum Capability : uint8_t
{
Invertable = 1<<3,
HasRatchet = 1<<2
enum Capability : uint8_t {
Invertible = 1 << 3,
HasRatchet = 1 << 2
};
enum Mode : uint8_t
{
Inverted = 1<<2,
HiRes = 1<<1,
enum Mode : uint8_t {
Inverted = 1 << 2,
HiRes = 1 << 1,
Target = 1
};
enum RatchetState : uint8_t
{
enum RatchetState : uint8_t {
FreeWheel = 0,
Ratchet = 1
};
struct Capabilities
{
struct Capabilities {
uint8_t multiplier;
uint8_t flags;
};
struct WheelStatus
{
struct WheelStatus {
bool hiRes;
uint8_t periods;
uint16_t deltaV;
int16_t deltaV;
};
explicit HiresScroll(Device* device);
Capabilities getCapabilities();
uint8_t getMode();
void setMode(uint8_t mode);
[[maybe_unused]]
bool getRatchetState();
static WheelStatus wheelMovementEvent(const hidpp::Report& report);
[[maybe_unused]]
static RatchetState ratchetSwitchEvent(const hidpp::Report& report);
};
}}}
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,53 +15,58 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <backend/hidpp20/features/ReprogControls.h>
#include <backend/hidpp20/Error.h>
#include <backend/hidpp20/Device.h>
#include <cassert>
#include "../Error.h"
#include "ReprogControls.h"
using namespace logid::backend::hidpp20;
#define DEFINE_REPROG(x, base) \
x::x(Device* dev) : base(dev, ID) \
{ \
} \
x::x(Device* dev, uint16_t _id) : base(dev, _id) \
{ \
}
// Define all the ReprogControls versions
#define DEFINE_REPROG(T, Base) \
T::T(Device* dev, uint16_t _id) : Base(dev, _id) { } \
T::T(Device* dev) : T(dev, ID) { }
#define MAKE_REPROG(x, dev) \
try { \
return std::make_shared<x>(dev); \
} catch(UnsupportedFeature &e) {\
}
// Define all of the ReprogControls versions
DEFINE_REPROG(ReprogControls, Feature)
DEFINE_REPROG(ReprogControlsV2, ReprogControls)
DEFINE_REPROG(ReprogControlsV2_2, ReprogControlsV2)
DEFINE_REPROG(ReprogControlsV3, ReprogControlsV2_2)
DEFINE_REPROG(ReprogControlsV4, ReprogControlsV3)
std::shared_ptr<ReprogControls> ReprogControls::autoVersion(Device *dev)
{
MAKE_REPROG(ReprogControlsV4, dev)
MAKE_REPROG(ReprogControlsV3, dev)
MAKE_REPROG(ReprogControlsV2_2, dev)
MAKE_REPROG(ReprogControlsV2, dev)
template<typename T>
std::shared_ptr<T> make_reprog(Device* dev) {
try {
return std::make_shared<T>(dev);
} catch (UnsupportedFeature& e) {
return {};
}
}
std::shared_ptr<ReprogControls> ReprogControls::autoVersion(Device* dev) {
if (auto v4 = make_reprog<ReprogControlsV4>(dev)) {
return v4;
} else if (auto v3 = make_reprog<ReprogControlsV3>(dev)) {
return v3;
} else if (auto v2_2 = make_reprog<ReprogControlsV2_2>(dev)) {
return v2_2;
} else if (auto v2 = make_reprog<ReprogControlsV2>(dev)) {
return v2;
}
// If base version cannot be made, throw error
return std::make_shared<ReprogControls>(dev);
}
uint8_t ReprogControls::getControlCount()
{
uint8_t ReprogControls::getControlCount() {
std::vector<uint8_t> params(0);
auto response = callFunction(GetControlCount, params);
return response[0];
}
ReprogControls::ControlInfo ReprogControls::getControlInfo(uint8_t index)
{
ReprogControls::ControlInfo ReprogControls::getControlInfo(uint8_t index) {
std::vector<uint8_t> params(1);
ControlInfo info{};
params[0] = index;
@ -79,13 +84,12 @@ ReprogControls::ControlInfo ReprogControls::getControlInfo(uint8_t index)
return info;
}
void ReprogControls::initCidMap()
{
void ReprogControls::initCidMap() {
std::unique_lock<std::mutex> lock(_cids_populating);
if(_cids_initialized)
if (_cids_initialized)
return;
uint8_t controls = getControlCount();
for(uint8_t i = 0; i < controls; i++) {
for (uint8_t i = 0; i < controls; i++) {
auto info = getControlInfo(i);
_cids.emplace(info.controlID, info);
}
@ -93,57 +97,53 @@ void ReprogControls::initCidMap()
}
const std::map<uint16_t, ReprogControls::ControlInfo>&
ReprogControls::getControls() const
{
ReprogControls::getControls() const {
return _cids;
}
ReprogControls::ControlInfo ReprogControls::getControlIdInfo(uint16_t cid)
{
if(!_cids_initialized)
ReprogControls::ControlInfo ReprogControls::getControlIdInfo(uint16_t cid) {
if (!_cids_initialized)
initCidMap();
auto it = _cids.find(cid);
if(it == _cids.end())
throw Error(Error::InvalidArgument);
if (it == _cids.end())
throw Error(Error::InvalidArgument, _device->deviceIndex());
else
return it->second;
}
ReprogControls::ControlInfo ReprogControls::getControlReporting(uint16_t cid)
{
[[maybe_unused]] ReprogControls::ControlInfo ReprogControls::getControlReporting(uint16_t cid) {
// Emulate this function, only Reprog controls v4 supports this
auto info = getControlIdInfo(cid);
ControlInfo report{};
report.controlID = cid;
report.flags = 0;
if(info.flags & TemporaryDivertable)
if (info.flags & TemporaryDivertable)
report.flags |= TemporaryDiverted;
if(info.flags & PersisentlyDivertable)
if (info.flags & PersistentlyDivertable)
report.flags |= PersistentlyDiverted;
if(info.additionalFlags & RawXY)
if (info.additionalFlags & RawXY)
report.flags |= RawXYDiverted;
return report;
}
void ReprogControls::setControlReporting(uint8_t cid, ControlInfo info)
{
void ReprogControls::setControlReporting(uint16_t cid, ControlInfo info) {
// This function does not exist pre-v4 and cannot be emulated, ignore.
(void)cid; (void)info; // Suppress unused warnings
(void) cid;
(void) info; // Suppress unused warnings
}
std::set<uint16_t> ReprogControls::divertedButtonEvent(
const hidpp::Report& report)
{
const hidpp::Report& report) {
assert(report.function() == DivertedButtonEvent);
std::set<uint16_t> buttons;
uint8_t cids = std::distance(report.paramBegin(), report.paramEnd())/2;
for(uint8_t i = 0; i < cids; i++) {
uint16_t cid = report.paramBegin()[2*i + 1];
cid |= report.paramBegin()[2*i] << 8;
if(cid)
uint8_t cids = std::distance(report.paramBegin(), report.paramEnd()) / 2;
for (uint8_t i = 0; i < cids; i++) {
uint16_t cid = report.paramBegin()[2 * i + 1];
cid |= report.paramBegin()[2 * i] << 8;
if (cid)
buttons.insert(cid);
else
break;
@ -152,19 +152,15 @@ std::set<uint16_t> ReprogControls::divertedButtonEvent(
}
ReprogControls::Move ReprogControls::divertedRawXYEvent(const hidpp::Report
&report)
{
& report) {
assert(report.function() == DivertedRawXYEvent);
Move move{};
move.x = report.paramBegin()[1];
move.x |= report.paramBegin()[0] << 8;
move.y = report.paramBegin()[3];
move.y |= report.paramBegin()[2] << 8;
move.x = (int16_t) ((report.paramBegin()[0] << 8) | report.paramBegin()[1]);
move.y = (int16_t) ((report.paramBegin()[2] << 8) | report.paramBegin()[3]);
return move;
}
ReprogControls::ControlInfo ReprogControlsV4::getControlReporting(uint16_t cid)
{
ReprogControls::ControlInfo ReprogControlsV4::getControlReporting(uint16_t cid) {
std::vector<uint8_t> params(2);
ControlInfo info{};
params[0] = (cid >> 8) & 0xff;
@ -177,8 +173,7 @@ ReprogControls::ControlInfo ReprogControlsV4::getControlReporting(uint16_t cid)
return info;
}
void ReprogControlsV4::setControlReporting(uint8_t cid, ControlInfo info)
{
void ReprogControlsV4::setControlReporting(uint16_t cid, ControlInfo info) {
std::vector<uint8_t> params(5);
params[0] = (cid >> 8) & 0xff;
params[1] = cid & 0xff;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,17 +18,15 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H
#define LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H
#include <backend/hidpp20/feature_defs.h>
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp/Report.h>
#include <map>
#include <set>
#include <memory>
#include "../feature_defs.h"
#include "../Feature.h"
namespace logid {
namespace backend {
namespace hidpp20
{
class ReprogControls : public Feature
{
namespace logid::backend::hidpp20 {
class ReprogControls : public Feature {
public:
enum Function {
GetControlCount = 0,
@ -41,8 +39,7 @@ namespace hidpp20
DivertedRawXYEvent = 1
};
struct ControlInfo
{
struct ControlInfo {
uint16_t controlID;
uint16_t taskID;
uint8_t flags;
@ -52,121 +49,127 @@ namespace hidpp20
uint8_t additionalFlags;
};
enum ControlInfoFlags: uint8_t
{
enum ControlInfoFlags : uint8_t {
MouseButton = 1, //Mouse button
FKey = 1<<1, //Fx key
Hotkey = 1<<2,
FnToggle = 1<<3,
ReprogHint = 1<<4,
TemporaryDivertable = 1<<5,
PersisentlyDivertable = 1<<6,
Virtual = 1<<7
FKey = 1 << 1, //Fx key
Hotkey = 1 << 2,
FnToggle = 1 << 3,
ReprogHint = 1 << 4,
TemporaryDivertable = 1 << 5,
PersistentlyDivertable = 1 << 6,
Virtual = 1 << 7
};
enum ControlInfoAdditionalFlags: uint8_t {
RawXY = 1<<0
enum ControlInfoAdditionalFlags : uint8_t {
RawXY = 1 << 0
};
enum ControlReportingFlags: uint8_t {
TemporaryDiverted = 1<<0,
ChangeTemporaryDivert = 1<<1,
PersistentlyDiverted = 1<<2,
ChangePersistentDivert = 1<<3,
RawXYDiverted = 1<<4,
ChangeRawXYDivert = 1<<5
enum ControlReportingFlags : uint8_t {
TemporaryDiverted = 1 << 0,
ChangeTemporaryDivert = 1 << 1,
PersistentlyDiverted = 1 << 2,
ChangePersistentDivert [[maybe_unused]] = 1 << 3,
RawXYDiverted = 1 << 4,
ChangeRawXYDivert = 1 << 5
};
struct Move
{
struct Move {
int16_t x;
int16_t y;
};
static const uint16_t ID = FeatureID::REPROG_CONTROLS;
virtual uint16_t getID() { return ID; }
virtual bool supportsRawXY() { return false; }
[[nodiscard]] uint16_t getID() override { return ID; }
[[nodiscard]] virtual bool supportsRawXY() { return false; }
explicit ReprogControls(Device* dev);
virtual uint8_t getControlCount();
[[nodiscard]] virtual uint8_t getControlCount();
virtual ControlInfo getControlInfo(uint8_t cid);
[[nodiscard]] virtual ControlInfo getControlInfo(uint8_t cid);
virtual ControlInfo getControlIdInfo(uint16_t cid);
[[nodiscard]] virtual ControlInfo getControlIdInfo(uint16_t cid);
virtual void initCidMap();
const std::map<uint16_t, ControlInfo>& getControls() const;
[[nodiscard]] const std::map<uint16_t, ControlInfo>& getControls() const;
// Onlu controlId and flags will be set
virtual ControlInfo getControlReporting(uint16_t cid);
// Only controlId and flags will be set
[[maybe_unused]]
[[nodiscard]] virtual ControlInfo getControlReporting(uint16_t cid);
// Only controlId (for remap) and flags will be read
virtual void setControlReporting(uint8_t cid, ControlInfo info);
virtual void setControlReporting(uint16_t cid, ControlInfo info);
static std::set<uint16_t> divertedButtonEvent(const hidpp::Report&
report);
[[nodiscard]] static std::set<uint16_t> divertedButtonEvent(const hidpp::Report& report);
static Move divertedRawXYEvent(const hidpp::Report& report);
[[nodiscard]] static Move divertedRawXYEvent(const hidpp::Report& report);
[[nodiscard]] static std::shared_ptr<ReprogControls> autoVersion(Device* dev);
static std::shared_ptr<ReprogControls> autoVersion(Device *dev);
protected:
ReprogControls(Device* dev, uint16_t _id);
std::map<uint16_t, ControlInfo> _cids;
bool _cids_initialized = false;
std::mutex _cids_populating;
};
class ReprogControlsV2 : public ReprogControls
{
class ReprogControlsV2 : public ReprogControls {
public:
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V2;
virtual uint16_t getID() { return ID; }
[[nodiscard]] uint16_t getID() override { return ID; }
explicit ReprogControlsV2(Device* dev);
protected:
ReprogControlsV2(Device* dev, uint16_t _id);
};
class ReprogControlsV2_2 : public ReprogControlsV2
{
class ReprogControlsV2_2 : public ReprogControlsV2 {
public:
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V2_2;
virtual uint16_t getID() { return ID; }
[[nodiscard]] uint16_t getID() override { return ID; }
explicit ReprogControlsV2_2(Device* dev);
protected:
ReprogControlsV2_2(Device* dev, uint16_t _id);
};
class ReprogControlsV3 : public ReprogControlsV2_2
{
class ReprogControlsV3 : public ReprogControlsV2_2 {
public:
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V3;
virtual uint16_t getID() { return ID; }
[[nodiscard]] uint16_t getID() override { return ID; }
explicit ReprogControlsV3(Device* dev);
protected:
ReprogControlsV3(Device* dev, uint16_t _id);
};
class ReprogControlsV4 : public ReprogControlsV3
{
class ReprogControlsV4 : public ReprogControlsV3 {
public:
static const uint16_t ID = FeatureID::REPROG_CONTROLS_V4;
virtual uint16_t getID() { return ID; }
bool supportsRawXY() override { return true; }
[[nodiscard]] uint16_t getID() final { return ID; }
ControlInfo getControlReporting(uint16_t cid) override;
[[nodiscard]] bool supportsRawXY() override { return true; }
void setControlReporting(uint8_t cid, ControlInfo info) override;
[[nodiscard]] ControlInfo getControlReporting(uint16_t cid) override;
void setControlReporting(uint16_t cid, ControlInfo info) override;
explicit ReprogControlsV4(Device* dev);
protected:
[[maybe_unused]]
ReprogControlsV4(Device* dev, uint16_t _id);
};
}}}
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,16 +15,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "Reset.h"
#include <backend/hidpp20/features/Reset.h>
using namespace logid::backend::hidpp20;
Reset::Reset(Device *device) : Feature(device, ID)
{
Reset::Reset(Device* device) : Feature(device, ID) {
}
uint16_t Reset::getProfile()
{
uint16_t Reset::getProfile() {
std::vector<uint8_t> params(0);
auto results = callFunction(GetProfile, params);
@ -33,8 +31,7 @@ uint16_t Reset::getProfile()
return profile;
}
void Reset::reset(uint16_t profile)
{
void Reset::reset(uint16_t profile) {
std::vector<uint8_t> params(2);
params[0] = (profile >> 8) & 0xff;
params[1] = profile & 0xff;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 PixlOne
* Copyright 2019-2023 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,30 +18,27 @@
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_RESET_H
#define LOGID_BACKEND_HIDPP20_FEATURE_RESET_H
#include "../Feature.h"
#include "../feature_defs.h"
#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>
namespace logid {
namespace backend {
namespace hidpp20
{
class Reset : public Feature
{
namespace logid::backend::hidpp20 {
class Reset : public Feature {
public:
static const uint16_t ID = FeatureID::RESET;
virtual uint16_t getID() { return ID; }
enum Function : uint8_t
{
GetProfile = 0,
ResetToProfile = 1
[[nodiscard]] uint16_t getID() final { return ID; }
enum Function : uint8_t {
GetProfile = 0,
ResetToProfile = 1
};
explicit Reset(Device* device);
uint16_t getProfile();
void reset(uint16_t profile = 0);
};
}}}
}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_RESET_H

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