@desklink/host 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +44 -0
- package/README.md +266 -0
- package/bin/desklink-host.mjs +113 -0
- package/dist/bridge.d.ts +57 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.js +208 -0
- package/dist/bridge.js.map +1 -0
- package/dist/engineProcess.d.ts +82 -0
- package/dist/engineProcess.d.ts.map +1 -0
- package/dist/engineProcess.js +243 -0
- package/dist/engineProcess.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +169 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +18 -0
- package/dist/protocol.js.map +1 -0
- package/dist/resolveEngine.d.ts +34 -0
- package/dist/resolveEngine.d.ts.map +1 -0
- package/dist/resolveEngine.js +103 -0
- package/dist/resolveEngine.js.map +1 -0
- package/docs/BOUNDARY.md +145 -0
- package/docs/PROTOCOL.md +363 -0
- package/engine/Cargo.lock +3077 -0
- package/engine/Cargo.toml +37 -0
- package/engine/build.rs +107 -0
- package/engine/native/inputtino_shim.cpp +11 -0
- package/engine/native/vpx_shim.c +184 -0
- package/engine/native/wrapper.h +7 -0
- package/engine/src/capture.rs +510 -0
- package/engine/src/clipboard.rs +132 -0
- package/engine/src/convert.rs +299 -0
- package/engine/src/encoder.rs +204 -0
- package/engine/src/input.rs +355 -0
- package/engine/src/keymap.rs +338 -0
- package/engine/src/main.rs +614 -0
- package/engine/src/peer.rs +775 -0
- package/engine/src/portal.rs +115 -0
- package/engine/src/protocol.rs +331 -0
- package/engine/src/session.rs +2159 -0
- package/engine/src/x11.rs +244 -0
- package/engine/vendor/inputtino/.clang-format +166 -0
- package/engine/vendor/inputtino/.clang-tidy +30 -0
- package/engine/vendor/inputtino/.dockerignore +11 -0
- package/engine/vendor/inputtino/.github/workflows/mirror-repo.yml +14 -0
- package/engine/vendor/inputtino/.github/workflows/python-lint-format-test-typecheck.yml +124 -0
- package/engine/vendor/inputtino/.github/workflows/python-test-install.yaml +49 -0
- package/engine/vendor/inputtino/.github/workflows/rust.yml +40 -0
- package/engine/vendor/inputtino/CMakeLists.txt +187 -0
- package/engine/vendor/inputtino/LICENSE +21 -0
- package/engine/vendor/inputtino/README.md +110 -0
- package/engine/vendor/inputtino/bindings/python/.devcontainer/Dockerfile +23 -0
- package/engine/vendor/inputtino/bindings/python/.devcontainer/devcontainer.json +28 -0
- package/engine/vendor/inputtino/bindings/python/.python-version +1 -0
- package/engine/vendor/inputtino/bindings/python/CMakeLists.txt +23 -0
- package/engine/vendor/inputtino/bindings/python/CPPLINT.cfg +2 -0
- package/engine/vendor/inputtino/bindings/python/Makefile +49 -0
- package/engine/vendor/inputtino/bindings/python/README.md +215 -0
- package/engine/vendor/inputtino/bindings/python/pyproject.toml +178 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/__init__.py +43 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/_core.pyi +533 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/base.py +63 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/joypad.py +248 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/keyboard.py +256 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/mouse.py +106 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/pentablet.py +73 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/py.typed +0 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/touchscreen.py +61 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/trackpad.py +74 -0
- package/engine/vendor/inputtino/bindings/python/src/main.cpp +217 -0
- package/engine/vendor/inputtino/bindings/python/tests/__init__.py +0 -0
- package/engine/vendor/inputtino/bindings/python/tests/helpers.py +3 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_base.py +60 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_inputtino.py +5 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_joypad.py +332 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_keyboard.py +164 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_mouse.py +185 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_pentablet.py +198 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_touchscreen.py +144 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_trackpad.py +146 -0
- package/engine/vendor/inputtino/bindings/python/uv.lock +390 -0
- package/engine/vendor/inputtino/bindings/rust/Cargo.toml +6 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/.dockerignore +1 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/Cargo.toml +18 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/examples/keyboard.rs +33 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/examples/mouse.rs +17 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/common.rs +116 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/error.rs +17 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/joypad.rs +74 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/joypad_nintendo.rs +135 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/joypad_ps5.rs +280 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/joypad_xbox.rs +134 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/keyboard.rs +76 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/lib.rs +32 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/mouse.rs +178 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/tests/common.rs +38 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/tests/joypads.rs +150 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/tests/keyboard.rs +73 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/tests/mouse.rs +139 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino-sys/.dockerignore +1 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino-sys/Cargo.toml +12 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino-sys/build.rs +48 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino-sys/src/lib.rs +5 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino-sys/wrapper.h +1 -0
- package/engine/vendor/inputtino/cmake/utils.cmake +9 -0
- package/engine/vendor/inputtino/docker/Dockerfile +70 -0
- package/engine/vendor/inputtino/include/inputtino/input.h +324 -0
- package/engine/vendor/inputtino/include/inputtino/input.hpp +466 -0
- package/engine/vendor/inputtino/include/inputtino/result.hpp +37 -0
- package/engine/vendor/inputtino/share/pkgconfig/libinputtino.pc.in +17 -0
- package/engine/vendor/inputtino/src/c-bindings/helpers.hpp +19 -0
- package/engine/vendor/inputtino/src/c-bindings/joypad_ps5.cpp +115 -0
- package/engine/vendor/inputtino/src/c-bindings/joypad_switch.cpp +63 -0
- package/engine/vendor/inputtino/src/c-bindings/joypad_xone.cpp +62 -0
- package/engine/vendor/inputtino/src/c-bindings/keyboard.cpp +42 -0
- package/engine/vendor/inputtino/src/c-bindings/mouse.cpp +67 -0
- package/engine/vendor/inputtino/src/c-bindings/pen_tablet.cpp +50 -0
- package/engine/vendor/inputtino/src/c-bindings/touchscreen.cpp +43 -0
- package/engine/vendor/inputtino/src/c-bindings/trackpad.cpp +49 -0
- package/engine/vendor/inputtino/src/uhid/README.adoc +63 -0
- package/engine/vendor/inputtino/src/uhid/include/crc32.hpp +38 -0
- package/engine/vendor/inputtino/src/uhid/include/uhid/protected_types.hpp +36 -0
- package/engine/vendor/inputtino/src/uhid/include/uhid/ps5.hpp +550 -0
- package/engine/vendor/inputtino/src/uhid/include/uhid/uhid.hpp +168 -0
- package/engine/vendor/inputtino/src/uhid/joypad_ps5.cpp +580 -0
- package/engine/vendor/inputtino/src/uinput/include/inputtino/keyboard.hpp +84 -0
- package/engine/vendor/inputtino/src/uinput/include/inputtino/protected_ps5_types.hpp +7 -0
- package/engine/vendor/inputtino/src/uinput/include/inputtino/protected_types.hpp +110 -0
- package/engine/vendor/inputtino/src/uinput/joypad_nintendo.cpp +190 -0
- package/engine/vendor/inputtino/src/uinput/joypad_ps.cpp +203 -0
- package/engine/vendor/inputtino/src/uinput/joypad_utils.hpp +246 -0
- package/engine/vendor/inputtino/src/uinput/joypad_xbox.cpp +190 -0
- package/engine/vendor/inputtino/src/uinput/keyboard.cpp +120 -0
- package/engine/vendor/inputtino/src/uinput/mouse.cpp +199 -0
- package/engine/vendor/inputtino/src/uinput/pentablet.cpp +162 -0
- package/engine/vendor/inputtino/src/uinput/touchscreen.cpp +140 -0
- package/engine/vendor/inputtino/src/uinput/trackpad.cpp +192 -0
- package/examples/reference-client.html +428 -0
- package/package.json +38 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <algorithm>
|
|
3
|
+
#include <chrono>
|
|
4
|
+
#include <cstring>
|
|
5
|
+
#include <fcntl.h>
|
|
6
|
+
#include <filesystem>
|
|
7
|
+
#include <inputtino/input.hpp>
|
|
8
|
+
#include <inputtino/protected_types.hpp>
|
|
9
|
+
#include <iostream>
|
|
10
|
+
#include <linux/input.h>
|
|
11
|
+
#include <linux/uinput.h>
|
|
12
|
+
#include <optional>
|
|
13
|
+
#include <poll.h>
|
|
14
|
+
#include <thread>
|
|
15
|
+
|
|
16
|
+
namespace inputtino {
|
|
17
|
+
|
|
18
|
+
using namespace std::chrono_literals;
|
|
19
|
+
|
|
20
|
+
constexpr long MAX_GAIN = 0xFFFF;
|
|
21
|
+
constexpr int RUMBLE_POLL_TIMEOUT = 500; // ms
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Joypads will also have one `/dev/input/js*` device as child, we want to expose that as well
|
|
25
|
+
*/
|
|
26
|
+
static std::vector<std::string> get_child_dev_nodes(libevdev_uinput *device) {
|
|
27
|
+
std::vector<std::string> result;
|
|
28
|
+
|
|
29
|
+
auto dev_path = libevdev_uinput_get_devnode(device);
|
|
30
|
+
if (dev_path) {
|
|
31
|
+
result.push_back(dev_path);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
auto sys_path = libevdev_uinput_get_syspath(device);
|
|
35
|
+
if (sys_path) {
|
|
36
|
+
for (const auto &entry : std::filesystem::directory_iterator(sys_path)) {
|
|
37
|
+
if (entry.is_directory() && entry.path().filename().string().rfind("js", 0) == 0) { // starts with "js"?
|
|
38
|
+
result.push_back("/dev/input/" + entry.path().filename().string());
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
struct ActiveRumbleEffect {
|
|
47
|
+
std::chrono::steady_clock::time_point start_point;
|
|
48
|
+
std::chrono::steady_clock::time_point end_point;
|
|
49
|
+
std::chrono::milliseconds length;
|
|
50
|
+
std::chrono::milliseconds delay;
|
|
51
|
+
ff_envelope envelope;
|
|
52
|
+
|
|
53
|
+
struct {
|
|
54
|
+
long weak, strong;
|
|
55
|
+
} start;
|
|
56
|
+
|
|
57
|
+
struct {
|
|
58
|
+
long weak, strong;
|
|
59
|
+
} end;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
static long rumble_magnitude(std::chrono::milliseconds time_left,
|
|
63
|
+
std::uint32_t start,
|
|
64
|
+
std::uint32_t end,
|
|
65
|
+
std::chrono::milliseconds length) {
|
|
66
|
+
auto rel = end - start;
|
|
67
|
+
return start + (rel * time_left.count() / length.count());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static std::pair<long, long> simulate_rumble(const ActiveRumbleEffect &effect,
|
|
71
|
+
const std::chrono::steady_clock::time_point &now) {
|
|
72
|
+
if (effect.end_point < now || now < effect.start_point) {
|
|
73
|
+
return {0, 0};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
auto time_left = std::chrono::duration_cast<std::chrono::milliseconds>(effect.end_point - now);
|
|
77
|
+
auto t = effect.length - time_left;
|
|
78
|
+
|
|
79
|
+
auto weak = rumble_magnitude(t, effect.start.weak, effect.end.weak, effect.length);
|
|
80
|
+
auto strong = rumble_magnitude(t, effect.start.strong, effect.end.strong, effect.length);
|
|
81
|
+
|
|
82
|
+
if (t.count() < effect.envelope.attack_length) {
|
|
83
|
+
weak = (effect.envelope.attack_level * t.count() + weak * (effect.envelope.attack_length - t.count())) /
|
|
84
|
+
effect.envelope.attack_length;
|
|
85
|
+
strong = (effect.envelope.attack_level * t.count() + strong * (effect.envelope.attack_length - t.count())) /
|
|
86
|
+
effect.envelope.attack_length;
|
|
87
|
+
} else if (time_left.count() < effect.envelope.fade_length) {
|
|
88
|
+
auto dt = (t - effect.length).count() + effect.envelope.fade_length;
|
|
89
|
+
|
|
90
|
+
weak = (effect.envelope.fade_level * dt + weak * (effect.envelope.fade_length - dt)) / effect.envelope.fade_length;
|
|
91
|
+
strong = (effect.envelope.fade_level * dt + strong * (effect.envelope.fade_length - dt)) /
|
|
92
|
+
effect.envelope.fade_length;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {weak, strong};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static ActiveRumbleEffect create_rumble_effect(const ff_effect &effect) {
|
|
99
|
+
// All duration values are expressed in ms. Values above 32767 ms (0x7fff) should not be used
|
|
100
|
+
ActiveRumbleEffect r_effect{
|
|
101
|
+
.start_point = std::chrono::steady_clock::time_point::min(),
|
|
102
|
+
.end_point = std::chrono::steady_clock::time_point::min(),
|
|
103
|
+
.length = std::chrono::milliseconds{std::clamp(effect.replay.length, (std::uint16_t)0, (std::uint16_t)32767)},
|
|
104
|
+
.delay = std::chrono::milliseconds{std::clamp(effect.replay.delay, (std::uint16_t)0, (std::uint16_t)32767)},
|
|
105
|
+
.envelope = {}};
|
|
106
|
+
switch (effect.type) {
|
|
107
|
+
case FF_CONSTANT:
|
|
108
|
+
r_effect.start.weak = effect.u.constant.level;
|
|
109
|
+
r_effect.start.strong = effect.u.constant.level;
|
|
110
|
+
r_effect.end.weak = effect.u.constant.level;
|
|
111
|
+
r_effect.end.strong = effect.u.constant.level;
|
|
112
|
+
r_effect.envelope = effect.u.constant.envelope;
|
|
113
|
+
break;
|
|
114
|
+
case FF_PERIODIC:
|
|
115
|
+
r_effect.start.weak = effect.u.periodic.magnitude;
|
|
116
|
+
r_effect.start.strong = effect.u.periodic.magnitude;
|
|
117
|
+
r_effect.end.weak = effect.u.periodic.magnitude;
|
|
118
|
+
r_effect.end.strong = effect.u.periodic.magnitude;
|
|
119
|
+
r_effect.envelope = effect.u.periodic.envelope;
|
|
120
|
+
break;
|
|
121
|
+
case FF_RAMP:
|
|
122
|
+
r_effect.start.weak = effect.u.ramp.start_level;
|
|
123
|
+
r_effect.start.strong = effect.u.ramp.start_level;
|
|
124
|
+
r_effect.end.weak = effect.u.ramp.end_level;
|
|
125
|
+
r_effect.end.strong = effect.u.ramp.end_level;
|
|
126
|
+
r_effect.envelope = effect.u.ramp.envelope;
|
|
127
|
+
break;
|
|
128
|
+
case FF_RUMBLE:
|
|
129
|
+
r_effect.start.weak = effect.u.rumble.weak_magnitude;
|
|
130
|
+
r_effect.start.strong = effect.u.rumble.strong_magnitude;
|
|
131
|
+
r_effect.end.weak = effect.u.rumble.weak_magnitude;
|
|
132
|
+
r_effect.end.strong = effect.u.rumble.strong_magnitude;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
return r_effect;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Here we listen for events from the device and call the corresponding callback functions
|
|
140
|
+
*
|
|
141
|
+
* Rumble:
|
|
142
|
+
* First of, this is called force feedback (FF) in linux,
|
|
143
|
+
* you can see some docs here: https://www.kernel.org/doc/html/latest/input/ff.html
|
|
144
|
+
* In uinput this works as a two step process:
|
|
145
|
+
* - you first upload the FF effect with a given request ID
|
|
146
|
+
* - later on when the rumble has been activated you'll receive an EV_FF in your /dev/input/event**
|
|
147
|
+
* where the value is the request ID
|
|
148
|
+
* You can test the virtual devices that we create by simply using the utility `fftest`
|
|
149
|
+
*/
|
|
150
|
+
static void event_listener(const std::shared_ptr<BaseJoypadState> &state) {
|
|
151
|
+
std::this_thread::sleep_for(100ms); // We have to sleep in order to be able to read from the newly created device
|
|
152
|
+
|
|
153
|
+
auto uinput_fd = libevdev_uinput_get_fd(state->joy.get());
|
|
154
|
+
if (uinput_fd < 0) {
|
|
155
|
+
std::cerr << "Unable to open uinput device, additional events will be disabled.";
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// We have to add 0_NONBLOCK to the flags in order to be able to read the events
|
|
160
|
+
int flags = fcntl(uinput_fd, F_GETFL, 0);
|
|
161
|
+
fcntl(uinput_fd, F_SETFL, flags | O_NONBLOCK);
|
|
162
|
+
|
|
163
|
+
/* Local copy of all the uploaded ff effects */
|
|
164
|
+
std::map<int, ActiveRumbleEffect> ff_effects = {};
|
|
165
|
+
std::pair<std::uint32_t, std::uint32_t> prev_rumble = {0, 0};
|
|
166
|
+
|
|
167
|
+
/* This can only be set globally when receiving FF_GAIN */
|
|
168
|
+
unsigned int current_gain = MAX_GAIN;
|
|
169
|
+
|
|
170
|
+
std::array<pollfd, 1> pfds = {pollfd{.fd = uinput_fd, .events = POLLIN}};
|
|
171
|
+
int poll_rs = 0;
|
|
172
|
+
|
|
173
|
+
while (!state->stop_listening_events) {
|
|
174
|
+
poll_rs = poll(pfds.data(), pfds.size(), RUMBLE_POLL_TIMEOUT);
|
|
175
|
+
if (poll_rs < 0) {
|
|
176
|
+
std::cerr << "Failed polling uinput fd; ret=" << strerror(errno);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
auto events = fetch_events(uinput_fd);
|
|
181
|
+
for (auto ev : events) {
|
|
182
|
+
if (ev->type == EV_UINPUT && ev->code == UI_FF_UPLOAD) { // Upload a new FF effect
|
|
183
|
+
uinput_ff_upload upload{};
|
|
184
|
+
upload.request_id = ev->value;
|
|
185
|
+
|
|
186
|
+
ioctl(uinput_fd, UI_BEGIN_FF_UPLOAD, &upload); // retrieve the effect
|
|
187
|
+
|
|
188
|
+
auto new_effect = create_rumble_effect(upload.effect);
|
|
189
|
+
if (ff_effects.find(upload.effect.id) != ff_effects.end()) {
|
|
190
|
+
// We have to keep the original start and end points of the effect
|
|
191
|
+
new_effect.start_point = ff_effects[upload.effect.id].start_point;
|
|
192
|
+
new_effect.end_point = ff_effects[upload.effect.id].end_point;
|
|
193
|
+
}
|
|
194
|
+
ff_effects.insert_or_assign(upload.effect.id, create_rumble_effect(upload.effect));
|
|
195
|
+
upload.retval = 0;
|
|
196
|
+
|
|
197
|
+
ioctl(uinput_fd, UI_END_FF_UPLOAD, &upload);
|
|
198
|
+
} else if (ev->type == EV_UINPUT && ev->code == UI_FF_ERASE) { // Remove an uploaded FF effect
|
|
199
|
+
uinput_ff_erase erase{};
|
|
200
|
+
erase.request_id = ev->value;
|
|
201
|
+
|
|
202
|
+
ioctl(uinput_fd, UI_BEGIN_FF_ERASE, &erase); // retrieve ff_erase
|
|
203
|
+
|
|
204
|
+
ff_effects.erase(erase.effect_id);
|
|
205
|
+
erase.retval = 0;
|
|
206
|
+
|
|
207
|
+
ioctl(uinput_fd, UI_END_FF_ERASE, &erase);
|
|
208
|
+
} else if (ev->type == EV_FF && ev->code == FF_GAIN) { // Force feedback set gain
|
|
209
|
+
current_gain = std::clamp((long)ev->value, 0l, MAX_GAIN);
|
|
210
|
+
} else if (ev->type == EV_FF) { // Force feedback effect
|
|
211
|
+
auto effect_id = ev->code;
|
|
212
|
+
if (auto effect = ff_effects.find(effect_id); effect != ff_effects.end()) {
|
|
213
|
+
if (ev->value) { // Activate
|
|
214
|
+
auto now = std::chrono::steady_clock::now();
|
|
215
|
+
effect->second.start_point = now + effect->second.delay;
|
|
216
|
+
effect->second.end_point = now + effect->second.delay + effect->second.length;
|
|
217
|
+
} else { // Deactivate
|
|
218
|
+
effect->second.end_point = std::chrono::steady_clock::time_point::min();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
auto now = std::chrono::steady_clock::now();
|
|
225
|
+
|
|
226
|
+
// Accumulate all rumble effects
|
|
227
|
+
std::pair<long, long> current_rumble = {0, 0};
|
|
228
|
+
for (auto &[_id, effect] : ff_effects) {
|
|
229
|
+
auto [weak, strong] = simulate_rumble(effect, now);
|
|
230
|
+
current_rumble.first += weak;
|
|
231
|
+
current_rumble.second += strong;
|
|
232
|
+
}
|
|
233
|
+
// Avoid sending too many events
|
|
234
|
+
if (prev_rumble.first != current_rumble.first || prev_rumble.second != current_rumble.second) {
|
|
235
|
+
prev_rumble.first = current_rumble.first;
|
|
236
|
+
prev_rumble.second = current_rumble.second;
|
|
237
|
+
|
|
238
|
+
if (auto callback = state->on_rumble) {
|
|
239
|
+
callback.value()(static_cast<int>((current_rumble.second * current_gain / MAX_GAIN)),
|
|
240
|
+
static_cast<int>((current_rumble.first * current_gain / MAX_GAIN)));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
} // namespace inputtino
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#include "joypad_utils.hpp"
|
|
2
|
+
#include <cstring>
|
|
3
|
+
#include <inputtino/input.hpp>
|
|
4
|
+
#include <inputtino/protected_types.hpp>
|
|
5
|
+
#include <iostream>
|
|
6
|
+
#include <linux/input.h>
|
|
7
|
+
#include <optional>
|
|
8
|
+
#include <thread>
|
|
9
|
+
|
|
10
|
+
namespace inputtino {
|
|
11
|
+
|
|
12
|
+
std::vector<std::string> XboxOneJoypad::get_nodes() const {
|
|
13
|
+
std::vector<std::string> nodes;
|
|
14
|
+
|
|
15
|
+
if (auto joy = _state->joy.get()) {
|
|
16
|
+
auto additional_nodes = get_child_dev_nodes(joy);
|
|
17
|
+
nodes.insert(nodes.end(), additional_nodes.begin(), additional_nodes.end());
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return nodes;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
Result<libevdev_uinput_ptr> create_xbox_controller(const DeviceDefinition &device) {
|
|
24
|
+
libevdev *dev = libevdev_new();
|
|
25
|
+
libevdev_uinput *uidev;
|
|
26
|
+
|
|
27
|
+
libevdev_set_name(dev, device.name.c_str());
|
|
28
|
+
libevdev_set_id_vendor(dev, device.vendor_id);
|
|
29
|
+
libevdev_set_id_product(dev, device.product_id);
|
|
30
|
+
libevdev_set_id_version(dev, device.version);
|
|
31
|
+
libevdev_set_id_bustype(dev, BUS_USB);
|
|
32
|
+
|
|
33
|
+
libevdev_enable_event_type(dev, EV_KEY);
|
|
34
|
+
libevdev_enable_event_code(dev, EV_KEY, BTN_WEST, nullptr);
|
|
35
|
+
libevdev_enable_event_code(dev, EV_KEY, BTN_EAST, nullptr);
|
|
36
|
+
libevdev_enable_event_code(dev, EV_KEY, BTN_NORTH, nullptr);
|
|
37
|
+
libevdev_enable_event_code(dev, EV_KEY, BTN_SOUTH, nullptr);
|
|
38
|
+
libevdev_enable_event_code(dev, EV_KEY, BTN_THUMBL, nullptr);
|
|
39
|
+
libevdev_enable_event_code(dev, EV_KEY, BTN_THUMBR, nullptr);
|
|
40
|
+
libevdev_enable_event_code(dev, EV_KEY, BTN_TR, nullptr);
|
|
41
|
+
libevdev_enable_event_code(dev, EV_KEY, BTN_TL, nullptr);
|
|
42
|
+
libevdev_enable_event_code(dev, EV_KEY, BTN_SELECT, nullptr);
|
|
43
|
+
libevdev_enable_event_code(dev, EV_KEY, BTN_MODE, nullptr);
|
|
44
|
+
libevdev_enable_event_code(dev, EV_KEY, BTN_START, nullptr);
|
|
45
|
+
|
|
46
|
+
libevdev_enable_event_type(dev, EV_ABS);
|
|
47
|
+
|
|
48
|
+
input_absinfo dpad{0, -1, 1, 0, 0, 0};
|
|
49
|
+
libevdev_enable_event_code(dev, EV_ABS, ABS_HAT0Y, &dpad);
|
|
50
|
+
libevdev_enable_event_code(dev, EV_ABS, ABS_HAT0X, &dpad);
|
|
51
|
+
|
|
52
|
+
input_absinfo stick{0, -32768, 32767, 16, 128, 0};
|
|
53
|
+
libevdev_enable_event_code(dev, EV_ABS, ABS_X, &stick);
|
|
54
|
+
libevdev_enable_event_code(dev, EV_ABS, ABS_RX, &stick);
|
|
55
|
+
libevdev_enable_event_code(dev, EV_ABS, ABS_Y, &stick);
|
|
56
|
+
libevdev_enable_event_code(dev, EV_ABS, ABS_RY, &stick);
|
|
57
|
+
|
|
58
|
+
input_absinfo trigger{0, 0, 255, 0, 0, 0};
|
|
59
|
+
libevdev_enable_event_code(dev, EV_ABS, ABS_Z, &trigger);
|
|
60
|
+
libevdev_enable_event_code(dev, EV_ABS, ABS_RZ, &trigger);
|
|
61
|
+
|
|
62
|
+
libevdev_enable_event_type(dev, EV_FF);
|
|
63
|
+
libevdev_enable_event_code(dev, EV_FF, FF_RUMBLE, nullptr);
|
|
64
|
+
libevdev_enable_event_code(dev, EV_FF, FF_CONSTANT, nullptr);
|
|
65
|
+
libevdev_enable_event_code(dev, EV_FF, FF_PERIODIC, nullptr);
|
|
66
|
+
libevdev_enable_event_code(dev, EV_FF, FF_SINE, nullptr);
|
|
67
|
+
libevdev_enable_event_code(dev, EV_FF, FF_RAMP, nullptr);
|
|
68
|
+
libevdev_enable_event_code(dev, EV_FF, FF_GAIN, nullptr);
|
|
69
|
+
|
|
70
|
+
auto err = libevdev_uinput_create_from_device(dev, LIBEVDEV_UINPUT_OPEN_MANAGED, &uidev);
|
|
71
|
+
libevdev_free(dev);
|
|
72
|
+
if (err != 0) {
|
|
73
|
+
return Error(strerror(-err));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return libevdev_uinput_ptr{uidev, ::libevdev_uinput_destroy};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
XboxOneJoypad::XboxOneJoypad() : _state(std::make_shared<XboxOneJoypadState>()) {}
|
|
80
|
+
|
|
81
|
+
XboxOneJoypad::~XboxOneJoypad() {
|
|
82
|
+
if (_state) {
|
|
83
|
+
_state->stop_listening_events = true;
|
|
84
|
+
if (_state->joy.get() != nullptr && _state->events_thread.joinable()) {
|
|
85
|
+
_state->events_thread.join();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
Result<XboxOneJoypad> XboxOneJoypad::create(const DeviceDefinition &device) {
|
|
91
|
+
auto joy_el = create_xbox_controller(device);
|
|
92
|
+
if (!joy_el) {
|
|
93
|
+
return Error(joy_el.getErrorMessage());
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
XboxOneJoypad joypad;
|
|
97
|
+
joypad._state->joy = std::move(*joy_el);
|
|
98
|
+
|
|
99
|
+
auto event_thread = std::thread(event_listener, joypad._state);
|
|
100
|
+
joypad._state->events_thread = std::move(event_thread);
|
|
101
|
+
joypad._state->events_thread.detach();
|
|
102
|
+
return joypad;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
void XboxOneJoypad::set_pressed_buttons(unsigned int newly_pressed) {
|
|
106
|
+
// Button flags that have been changed between current and prev
|
|
107
|
+
auto bf_changed = newly_pressed ^ this->_state->currently_pressed_btns;
|
|
108
|
+
// Button flags that are only part of the new packet
|
|
109
|
+
auto bf_new = newly_pressed;
|
|
110
|
+
if (auto controller = this->_state->joy.get()) {
|
|
111
|
+
|
|
112
|
+
if (bf_changed) {
|
|
113
|
+
if ((DPAD_UP | DPAD_DOWN) & bf_changed) {
|
|
114
|
+
int button_state = bf_new & DPAD_UP ? -1 : (bf_new & DPAD_DOWN ? 1 : 0);
|
|
115
|
+
|
|
116
|
+
libevdev_uinput_write_event(controller, EV_ABS, ABS_HAT0Y, button_state);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if ((DPAD_LEFT | DPAD_RIGHT) & bf_changed) {
|
|
120
|
+
int button_state = bf_new & DPAD_LEFT ? -1 : (bf_new & DPAD_RIGHT ? 1 : 0);
|
|
121
|
+
|
|
122
|
+
libevdev_uinput_write_event(controller, EV_ABS, ABS_HAT0X, button_state);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (START & bf_changed)
|
|
126
|
+
libevdev_uinput_write_event(controller, EV_KEY, BTN_START, bf_new & START ? 1 : 0);
|
|
127
|
+
if (BACK & bf_changed)
|
|
128
|
+
libevdev_uinput_write_event(controller, EV_KEY, BTN_SELECT, bf_new & BACK ? 1 : 0);
|
|
129
|
+
if (LEFT_STICK & bf_changed)
|
|
130
|
+
libevdev_uinput_write_event(controller, EV_KEY, BTN_THUMBL, bf_new & LEFT_STICK ? 1 : 0);
|
|
131
|
+
if (RIGHT_STICK & bf_changed)
|
|
132
|
+
libevdev_uinput_write_event(controller, EV_KEY, BTN_THUMBR, bf_new & RIGHT_STICK ? 1 : 0);
|
|
133
|
+
if (LEFT_BUTTON & bf_changed)
|
|
134
|
+
libevdev_uinput_write_event(controller, EV_KEY, BTN_TL, bf_new & LEFT_BUTTON ? 1 : 0);
|
|
135
|
+
if (RIGHT_BUTTON & bf_changed)
|
|
136
|
+
libevdev_uinput_write_event(controller, EV_KEY, BTN_TR, bf_new & RIGHT_BUTTON ? 1 : 0);
|
|
137
|
+
if (HOME & bf_changed)
|
|
138
|
+
libevdev_uinput_write_event(controller, EV_KEY, BTN_MODE, bf_new & HOME ? 1 : 0);
|
|
139
|
+
if (A & bf_changed)
|
|
140
|
+
libevdev_uinput_write_event(controller, EV_KEY, BTN_SOUTH, bf_new & A ? 1 : 0);
|
|
141
|
+
if (B & bf_changed)
|
|
142
|
+
libevdev_uinput_write_event(controller, EV_KEY, BTN_EAST, bf_new & B ? 1 : 0);
|
|
143
|
+
if (X & bf_changed)
|
|
144
|
+
libevdev_uinput_write_event(controller, EV_KEY, BTN_NORTH, bf_new & X ? 1 : 0);
|
|
145
|
+
if (Y & bf_changed)
|
|
146
|
+
libevdev_uinput_write_event(controller, EV_KEY, BTN_WEST, bf_new & Y ? 1 : 0);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
libevdev_uinput_write_event(controller, EV_SYN, SYN_REPORT, 0);
|
|
150
|
+
}
|
|
151
|
+
this->_state->currently_pressed_btns = bf_new;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
void XboxOneJoypad::set_stick(STICK_POSITION stick_type, short x, short y) {
|
|
155
|
+
if (auto controller = this->_state->joy.get()) {
|
|
156
|
+
if (stick_type == LS) {
|
|
157
|
+
libevdev_uinput_write_event(controller, EV_ABS, ABS_X, x);
|
|
158
|
+
libevdev_uinput_write_event(controller, EV_ABS, ABS_Y, -y);
|
|
159
|
+
} else {
|
|
160
|
+
libevdev_uinput_write_event(controller, EV_ABS, ABS_RX, x);
|
|
161
|
+
libevdev_uinput_write_event(controller, EV_ABS, ABS_RY, -y);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
libevdev_uinput_write_event(controller, EV_SYN, SYN_REPORT, 0);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
void XboxOneJoypad::set_triggers(int16_t left, int16_t right) {
|
|
169
|
+
if (auto controller = this->_state->joy.get()) {
|
|
170
|
+
if (left > 0) {
|
|
171
|
+
libevdev_uinput_write_event(controller, EV_ABS, ABS_Z, left);
|
|
172
|
+
} else {
|
|
173
|
+
libevdev_uinput_write_event(controller, EV_ABS, ABS_Z, left);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (right > 0) {
|
|
177
|
+
libevdev_uinput_write_event(controller, EV_ABS, ABS_RZ, right);
|
|
178
|
+
} else {
|
|
179
|
+
libevdev_uinput_write_event(controller, EV_ABS, ABS_RZ, right);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
libevdev_uinput_write_event(controller, EV_SYN, SYN_REPORT, 0);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
void XboxOneJoypad::set_on_rumble(const std::function<void(int, int)> &callback) {
|
|
187
|
+
this->_state->on_rumble = callback;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
} // namespace inputtino
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#include "inputtino/input.hpp"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cstring>
|
|
5
|
+
#include <inputtino/protected_types.hpp>
|
|
6
|
+
#include <inputtino/keyboard.hpp>
|
|
7
|
+
#include <thread>
|
|
8
|
+
|
|
9
|
+
namespace inputtino {
|
|
10
|
+
|
|
11
|
+
using namespace std::string_literals;
|
|
12
|
+
|
|
13
|
+
std::vector<std::string> Keyboard::get_nodes() const {
|
|
14
|
+
std::vector<std::string> nodes;
|
|
15
|
+
|
|
16
|
+
if (auto kb = _state->kb.get()) {
|
|
17
|
+
nodes.emplace_back(libevdev_uinput_get_devnode(kb));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return nodes;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
Result<libevdev_uinput_ptr> create_keyboard(const DeviceDefinition &device) {
|
|
24
|
+
auto dev = libevdev_new();
|
|
25
|
+
libevdev_uinput *uidev;
|
|
26
|
+
|
|
27
|
+
libevdev_set_name(dev, device.name.c_str());
|
|
28
|
+
libevdev_set_id_vendor(dev, device.vendor_id);
|
|
29
|
+
libevdev_set_id_product(dev, device.product_id);
|
|
30
|
+
libevdev_set_id_version(dev, device.version);
|
|
31
|
+
libevdev_set_id_bustype(dev, BUS_USB);
|
|
32
|
+
|
|
33
|
+
libevdev_enable_event_type(dev, EV_KEY);
|
|
34
|
+
libevdev_enable_event_code(dev, EV_KEY, KEY_BACKSPACE, nullptr);
|
|
35
|
+
|
|
36
|
+
for (auto ev : keyboard::key_mappings) {
|
|
37
|
+
libevdev_enable_event_code(dev, EV_KEY, ev.second.linux_code, nullptr);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
auto err = libevdev_uinput_create_from_device(dev, LIBEVDEV_UINPUT_OPEN_MANAGED, &uidev);
|
|
41
|
+
libevdev_free(dev);
|
|
42
|
+
if (err != 0) {
|
|
43
|
+
return Error(strerror(-err));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return libevdev_uinput_ptr{uidev, ::libevdev_uinput_destroy};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static std::optional<keyboard::KEY_MAP> press_btn(libevdev_uinput *kb, short key_code) {
|
|
50
|
+
auto search_key = keyboard::key_mappings.find(key_code);
|
|
51
|
+
if (search_key != keyboard::key_mappings.end()) {
|
|
52
|
+
auto mapped_key = search_key->second;
|
|
53
|
+
|
|
54
|
+
libevdev_uinput_write_event(kb, EV_MSC, MSC_SCAN, mapped_key.scan_code);
|
|
55
|
+
libevdev_uinput_write_event(kb, EV_KEY, mapped_key.linux_code, 1);
|
|
56
|
+
libevdev_uinput_write_event(kb, EV_SYN, SYN_REPORT, 0);
|
|
57
|
+
return mapped_key;
|
|
58
|
+
}
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Keyboard::Keyboard() : _state(std::make_shared<KeyboardState>()) {}
|
|
63
|
+
|
|
64
|
+
Keyboard::~Keyboard() {
|
|
65
|
+
if (_state) {
|
|
66
|
+
_state->stop_repeat_thread = true;
|
|
67
|
+
if (_state->repeat_press_t.joinable()) {
|
|
68
|
+
_state->repeat_press_t.join();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
Result<Keyboard> Keyboard::create(const DeviceDefinition &device, int millis_repress_key) {
|
|
74
|
+
auto kb_el = create_keyboard(device);
|
|
75
|
+
if (kb_el) {
|
|
76
|
+
Keyboard kb;
|
|
77
|
+
kb._state->kb = std::move(*kb_el);
|
|
78
|
+
auto repeat_thread = std::thread([state = kb._state, millis_repress_key]() {
|
|
79
|
+
while (!state->stop_repeat_thread) {
|
|
80
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(millis_repress_key));
|
|
81
|
+
for (auto key : state->cur_press_keys) {
|
|
82
|
+
if (auto keyboard = state->kb.get()) {
|
|
83
|
+
press_btn(keyboard, key);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
kb._state->repeat_press_t = std::move(repeat_thread);
|
|
89
|
+
kb._state->repeat_press_t.detach();
|
|
90
|
+
return kb;
|
|
91
|
+
} else {
|
|
92
|
+
return Error(kb_el.getErrorMessage());
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
void Keyboard::press(short key_code) {
|
|
97
|
+
if (auto keyboard = _state->kb.get()) {
|
|
98
|
+
if (auto key = press_btn(keyboard, key_code)) {
|
|
99
|
+
_state->cur_press_keys.push_back(key_code);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
void Keyboard::release(short key_code) {
|
|
105
|
+
auto search_key = keyboard::key_mappings.find(key_code);
|
|
106
|
+
if (search_key != keyboard::key_mappings.end()) {
|
|
107
|
+
if (auto keyboard = _state->kb.get()) {
|
|
108
|
+
auto mapped_key = search_key->second;
|
|
109
|
+
this->_state->cur_press_keys.erase(
|
|
110
|
+
std::remove(this->_state->cur_press_keys.begin(), this->_state->cur_press_keys.end(), key_code),
|
|
111
|
+
this->_state->cur_press_keys.end());
|
|
112
|
+
|
|
113
|
+
libevdev_uinput_write_event(keyboard, EV_MSC, MSC_SCAN, mapped_key.scan_code);
|
|
114
|
+
libevdev_uinput_write_event(keyboard, EV_KEY, mapped_key.linux_code, 0);
|
|
115
|
+
libevdev_uinput_write_event(keyboard, EV_SYN, SYN_REPORT, 0);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
} // namespace inputtino
|