@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.
Files changed (142) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +44 -0
  3. package/README.md +266 -0
  4. package/bin/desklink-host.mjs +113 -0
  5. package/dist/bridge.d.ts +57 -0
  6. package/dist/bridge.d.ts.map +1 -0
  7. package/dist/bridge.js +208 -0
  8. package/dist/bridge.js.map +1 -0
  9. package/dist/engineProcess.d.ts +82 -0
  10. package/dist/engineProcess.d.ts.map +1 -0
  11. package/dist/engineProcess.js +243 -0
  12. package/dist/engineProcess.js.map +1 -0
  13. package/dist/index.d.ts +12 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +12 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/protocol.d.ts +169 -0
  18. package/dist/protocol.d.ts.map +1 -0
  19. package/dist/protocol.js +18 -0
  20. package/dist/protocol.js.map +1 -0
  21. package/dist/resolveEngine.d.ts +34 -0
  22. package/dist/resolveEngine.d.ts.map +1 -0
  23. package/dist/resolveEngine.js +103 -0
  24. package/dist/resolveEngine.js.map +1 -0
  25. package/docs/BOUNDARY.md +145 -0
  26. package/docs/PROTOCOL.md +363 -0
  27. package/engine/Cargo.lock +3077 -0
  28. package/engine/Cargo.toml +37 -0
  29. package/engine/build.rs +107 -0
  30. package/engine/native/inputtino_shim.cpp +11 -0
  31. package/engine/native/vpx_shim.c +184 -0
  32. package/engine/native/wrapper.h +7 -0
  33. package/engine/src/capture.rs +510 -0
  34. package/engine/src/clipboard.rs +132 -0
  35. package/engine/src/convert.rs +299 -0
  36. package/engine/src/encoder.rs +204 -0
  37. package/engine/src/input.rs +355 -0
  38. package/engine/src/keymap.rs +338 -0
  39. package/engine/src/main.rs +614 -0
  40. package/engine/src/peer.rs +775 -0
  41. package/engine/src/portal.rs +115 -0
  42. package/engine/src/protocol.rs +331 -0
  43. package/engine/src/session.rs +2159 -0
  44. package/engine/src/x11.rs +244 -0
  45. package/engine/vendor/inputtino/.clang-format +166 -0
  46. package/engine/vendor/inputtino/.clang-tidy +30 -0
  47. package/engine/vendor/inputtino/.dockerignore +11 -0
  48. package/engine/vendor/inputtino/.github/workflows/mirror-repo.yml +14 -0
  49. package/engine/vendor/inputtino/.github/workflows/python-lint-format-test-typecheck.yml +124 -0
  50. package/engine/vendor/inputtino/.github/workflows/python-test-install.yaml +49 -0
  51. package/engine/vendor/inputtino/.github/workflows/rust.yml +40 -0
  52. package/engine/vendor/inputtino/CMakeLists.txt +187 -0
  53. package/engine/vendor/inputtino/LICENSE +21 -0
  54. package/engine/vendor/inputtino/README.md +110 -0
  55. package/engine/vendor/inputtino/bindings/python/.devcontainer/Dockerfile +23 -0
  56. package/engine/vendor/inputtino/bindings/python/.devcontainer/devcontainer.json +28 -0
  57. package/engine/vendor/inputtino/bindings/python/.python-version +1 -0
  58. package/engine/vendor/inputtino/bindings/python/CMakeLists.txt +23 -0
  59. package/engine/vendor/inputtino/bindings/python/CPPLINT.cfg +2 -0
  60. package/engine/vendor/inputtino/bindings/python/Makefile +49 -0
  61. package/engine/vendor/inputtino/bindings/python/README.md +215 -0
  62. package/engine/vendor/inputtino/bindings/python/pyproject.toml +178 -0
  63. package/engine/vendor/inputtino/bindings/python/src/inputtino/__init__.py +43 -0
  64. package/engine/vendor/inputtino/bindings/python/src/inputtino/_core.pyi +533 -0
  65. package/engine/vendor/inputtino/bindings/python/src/inputtino/base.py +63 -0
  66. package/engine/vendor/inputtino/bindings/python/src/inputtino/joypad.py +248 -0
  67. package/engine/vendor/inputtino/bindings/python/src/inputtino/keyboard.py +256 -0
  68. package/engine/vendor/inputtino/bindings/python/src/inputtino/mouse.py +106 -0
  69. package/engine/vendor/inputtino/bindings/python/src/inputtino/pentablet.py +73 -0
  70. package/engine/vendor/inputtino/bindings/python/src/inputtino/py.typed +0 -0
  71. package/engine/vendor/inputtino/bindings/python/src/inputtino/touchscreen.py +61 -0
  72. package/engine/vendor/inputtino/bindings/python/src/inputtino/trackpad.py +74 -0
  73. package/engine/vendor/inputtino/bindings/python/src/main.cpp +217 -0
  74. package/engine/vendor/inputtino/bindings/python/tests/__init__.py +0 -0
  75. package/engine/vendor/inputtino/bindings/python/tests/helpers.py +3 -0
  76. package/engine/vendor/inputtino/bindings/python/tests/test_base.py +60 -0
  77. package/engine/vendor/inputtino/bindings/python/tests/test_inputtino.py +5 -0
  78. package/engine/vendor/inputtino/bindings/python/tests/test_joypad.py +332 -0
  79. package/engine/vendor/inputtino/bindings/python/tests/test_keyboard.py +164 -0
  80. package/engine/vendor/inputtino/bindings/python/tests/test_mouse.py +185 -0
  81. package/engine/vendor/inputtino/bindings/python/tests/test_pentablet.py +198 -0
  82. package/engine/vendor/inputtino/bindings/python/tests/test_touchscreen.py +144 -0
  83. package/engine/vendor/inputtino/bindings/python/tests/test_trackpad.py +146 -0
  84. package/engine/vendor/inputtino/bindings/python/uv.lock +390 -0
  85. package/engine/vendor/inputtino/bindings/rust/Cargo.toml +6 -0
  86. package/engine/vendor/inputtino/bindings/rust/inputtino/.dockerignore +1 -0
  87. package/engine/vendor/inputtino/bindings/rust/inputtino/Cargo.toml +18 -0
  88. package/engine/vendor/inputtino/bindings/rust/inputtino/examples/keyboard.rs +33 -0
  89. package/engine/vendor/inputtino/bindings/rust/inputtino/examples/mouse.rs +17 -0
  90. package/engine/vendor/inputtino/bindings/rust/inputtino/src/common.rs +116 -0
  91. package/engine/vendor/inputtino/bindings/rust/inputtino/src/error.rs +17 -0
  92. package/engine/vendor/inputtino/bindings/rust/inputtino/src/joypad.rs +74 -0
  93. package/engine/vendor/inputtino/bindings/rust/inputtino/src/joypad_nintendo.rs +135 -0
  94. package/engine/vendor/inputtino/bindings/rust/inputtino/src/joypad_ps5.rs +280 -0
  95. package/engine/vendor/inputtino/bindings/rust/inputtino/src/joypad_xbox.rs +134 -0
  96. package/engine/vendor/inputtino/bindings/rust/inputtino/src/keyboard.rs +76 -0
  97. package/engine/vendor/inputtino/bindings/rust/inputtino/src/lib.rs +32 -0
  98. package/engine/vendor/inputtino/bindings/rust/inputtino/src/mouse.rs +178 -0
  99. package/engine/vendor/inputtino/bindings/rust/inputtino/tests/common.rs +38 -0
  100. package/engine/vendor/inputtino/bindings/rust/inputtino/tests/joypads.rs +150 -0
  101. package/engine/vendor/inputtino/bindings/rust/inputtino/tests/keyboard.rs +73 -0
  102. package/engine/vendor/inputtino/bindings/rust/inputtino/tests/mouse.rs +139 -0
  103. package/engine/vendor/inputtino/bindings/rust/inputtino-sys/.dockerignore +1 -0
  104. package/engine/vendor/inputtino/bindings/rust/inputtino-sys/Cargo.toml +12 -0
  105. package/engine/vendor/inputtino/bindings/rust/inputtino-sys/build.rs +48 -0
  106. package/engine/vendor/inputtino/bindings/rust/inputtino-sys/src/lib.rs +5 -0
  107. package/engine/vendor/inputtino/bindings/rust/inputtino-sys/wrapper.h +1 -0
  108. package/engine/vendor/inputtino/cmake/utils.cmake +9 -0
  109. package/engine/vendor/inputtino/docker/Dockerfile +70 -0
  110. package/engine/vendor/inputtino/include/inputtino/input.h +324 -0
  111. package/engine/vendor/inputtino/include/inputtino/input.hpp +466 -0
  112. package/engine/vendor/inputtino/include/inputtino/result.hpp +37 -0
  113. package/engine/vendor/inputtino/share/pkgconfig/libinputtino.pc.in +17 -0
  114. package/engine/vendor/inputtino/src/c-bindings/helpers.hpp +19 -0
  115. package/engine/vendor/inputtino/src/c-bindings/joypad_ps5.cpp +115 -0
  116. package/engine/vendor/inputtino/src/c-bindings/joypad_switch.cpp +63 -0
  117. package/engine/vendor/inputtino/src/c-bindings/joypad_xone.cpp +62 -0
  118. package/engine/vendor/inputtino/src/c-bindings/keyboard.cpp +42 -0
  119. package/engine/vendor/inputtino/src/c-bindings/mouse.cpp +67 -0
  120. package/engine/vendor/inputtino/src/c-bindings/pen_tablet.cpp +50 -0
  121. package/engine/vendor/inputtino/src/c-bindings/touchscreen.cpp +43 -0
  122. package/engine/vendor/inputtino/src/c-bindings/trackpad.cpp +49 -0
  123. package/engine/vendor/inputtino/src/uhid/README.adoc +63 -0
  124. package/engine/vendor/inputtino/src/uhid/include/crc32.hpp +38 -0
  125. package/engine/vendor/inputtino/src/uhid/include/uhid/protected_types.hpp +36 -0
  126. package/engine/vendor/inputtino/src/uhid/include/uhid/ps5.hpp +550 -0
  127. package/engine/vendor/inputtino/src/uhid/include/uhid/uhid.hpp +168 -0
  128. package/engine/vendor/inputtino/src/uhid/joypad_ps5.cpp +580 -0
  129. package/engine/vendor/inputtino/src/uinput/include/inputtino/keyboard.hpp +84 -0
  130. package/engine/vendor/inputtino/src/uinput/include/inputtino/protected_ps5_types.hpp +7 -0
  131. package/engine/vendor/inputtino/src/uinput/include/inputtino/protected_types.hpp +110 -0
  132. package/engine/vendor/inputtino/src/uinput/joypad_nintendo.cpp +190 -0
  133. package/engine/vendor/inputtino/src/uinput/joypad_ps.cpp +203 -0
  134. package/engine/vendor/inputtino/src/uinput/joypad_utils.hpp +246 -0
  135. package/engine/vendor/inputtino/src/uinput/joypad_xbox.cpp +190 -0
  136. package/engine/vendor/inputtino/src/uinput/keyboard.cpp +120 -0
  137. package/engine/vendor/inputtino/src/uinput/mouse.cpp +199 -0
  138. package/engine/vendor/inputtino/src/uinput/pentablet.cpp +162 -0
  139. package/engine/vendor/inputtino/src/uinput/touchscreen.cpp +140 -0
  140. package/engine/vendor/inputtino/src/uinput/trackpad.cpp +192 -0
  141. package/examples/reference-client.html +428 -0
  142. package/package.json +38 -0
@@ -0,0 +1,580 @@
1
+ #include <algorithm>
2
+ #include <climits>
3
+ #include <cmath>
4
+ #include <crc32.hpp>
5
+ #include <endian.h>
6
+ #include <filesystem>
7
+ #include <fstream>
8
+ #include <inputtino/input.hpp>
9
+ #include <iomanip>
10
+ #include <random>
11
+ #include <uhid/protected_types.hpp>
12
+ #include <uhid/ps5.hpp>
13
+ #include <uhid/uhid.hpp>
14
+
15
+ namespace inputtino {
16
+
17
+ static uint32_t sign_crc32(uint32_t seed, const unsigned char *buffer, size_t length) {
18
+ auto crc = CRC32(buffer, length, seed);
19
+ crc = htole32(crc); // Convert to little endian
20
+ return crc;
21
+ }
22
+
23
+ static void send_report(PS5JoypadState &state) {
24
+ { // setup timestamp and increase seq_number
25
+ state.current_state.seq_number++;
26
+ if (state.current_state.seq_number >= 255) {
27
+ state.current_state.seq_number = 0;
28
+ }
29
+
30
+ // Seems that the timestamp is little endian and 0.33us units
31
+ // see:
32
+ // https://github.com/torvalds/linux/blob/305230142ae0637213bf6e04f6d9f10bbcb74af8/drivers/hid/hid-playstation.c#L1409-L1410
33
+ auto now = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch())
34
+ .count();
35
+ state.current_state.sensor_timestamp = htole32(now / 333);
36
+ }
37
+
38
+ struct uhid_event ev{};
39
+ {
40
+ ev.type = UHID_INPUT2;
41
+
42
+ std::size_t header_size;
43
+ if (state.is_bluetooth) {
44
+ auto header = uhid::dualsense_input_report_bt_header{};
45
+ header_size = sizeof(header);
46
+ std::copy(reinterpret_cast<unsigned char *>(&header),
47
+ reinterpret_cast<unsigned char *>(&header) + header_size,
48
+ &ev.u.input2.data[0]);
49
+ } else {
50
+ auto header = uhid::dualsense_input_report_usb_header{};
51
+ header_size = sizeof(header);
52
+ std::copy(reinterpret_cast<unsigned char *>(&header),
53
+ reinterpret_cast<unsigned char *>(&header) + header_size,
54
+ &ev.u.input2.data[0]);
55
+ }
56
+
57
+ unsigned char *data = (unsigned char *)&state.current_state;
58
+ std::copy(data, data + sizeof(state.current_state), &ev.u.input2.data[header_size]);
59
+
60
+ ev.u.input2.size = header_size + sizeof(state.current_state);
61
+ }
62
+
63
+ if (state.is_bluetooth) { // CRC32 encode the data and append it to the reply
64
+ ev.u.input2.size += uhid::PS_INPUT_REPORT_BT_OFFSET;
65
+
66
+ auto end_of_msg = ev.u.input2.size - 4; // (Last 4 bytes contains crc32)
67
+ auto crc = sign_crc32(uhid::PS_INPUT_CRC32, &ev.u.input2.data[0], end_of_msg);
68
+ std::copy(reinterpret_cast<unsigned char *>(&crc),
69
+ reinterpret_cast<unsigned char *>(&crc) + 4,
70
+ &ev.u.input2.data[end_of_msg]);
71
+ }
72
+
73
+ state.dev->send(ev);
74
+ }
75
+
76
+ static void on_uhid_event(std::shared_ptr<PS5JoypadState> state, uhid_event ev, int fd) {
77
+ switch (ev.type) {
78
+ case UHID_GET_REPORT: {
79
+ uhid_event answer{};
80
+ answer.type = UHID_GET_REPORT_REPLY;
81
+ answer.u.get_report_reply.id = ev.u.get_report.id;
82
+ answer.u.get_report_reply.err = 0;
83
+ switch (ev.u.get_report.rnum) {
84
+ case uhid::PS5_REPORT_TYPES::CALIBRATION: {
85
+ std::copy(&uhid::ps5_calibration_info[0],
86
+ &uhid::ps5_calibration_info[0] + sizeof(uhid::ps5_calibration_info),
87
+ &answer.u.get_report_reply.data[0]);
88
+ answer.u.get_report_reply.size = sizeof(uhid::ps5_calibration_info);
89
+ break;
90
+ }
91
+ case uhid::PS5_REPORT_TYPES::PAIRING_INFO: {
92
+ std::copy(&uhid::ps5_pairing_info[0],
93
+ &uhid::ps5_pairing_info[0] + sizeof(uhid::ps5_pairing_info),
94
+ &answer.u.get_report_reply.data[0]);
95
+
96
+ // Copy MAC address data
97
+ std::reverse_copy(&state->mac_address[0],
98
+ &state->mac_address[0] + sizeof(state->mac_address),
99
+ &answer.u.get_report_reply.data[1]);
100
+
101
+ answer.u.get_report_reply.size = sizeof(uhid::ps5_pairing_info);
102
+ break;
103
+ }
104
+ case uhid::PS5_REPORT_TYPES::FIRMWARE_INFO: {
105
+ std::copy(&uhid::ps5_firmware_info[0],
106
+ &uhid::ps5_firmware_info[0] + sizeof(uhid::ps5_firmware_info),
107
+ &answer.u.get_report_reply.data[0]);
108
+ answer.u.get_report_reply.size = sizeof(uhid::ps5_firmware_info);
109
+ break;
110
+ }
111
+ default:
112
+ answer.u.get_report_reply.err = -EINVAL;
113
+ break;
114
+ }
115
+
116
+ if (state->is_bluetooth) {
117
+ // CRC32 encode the data and append it to the reply
118
+ auto end_of_msg = answer.u.get_report_reply.size - 4; // (Last 4 bytes contains crc32)
119
+ auto crc = sign_crc32(uhid::PS_FEATURE_CRC32, &answer.u.get_report_reply.data[0], end_of_msg);
120
+ std::copy(reinterpret_cast<unsigned char *>(&crc),
121
+ reinterpret_cast<unsigned char *>(&crc) + 4,
122
+ &answer.u.get_report_reply.data[end_of_msg]);
123
+ }
124
+
125
+ auto res = uhid::uhid_write(fd, &answer);
126
+ // TODO: signal error somehow
127
+ break;
128
+ }
129
+ case UHID_OUTPUT: { // This is sent if the HID device driver wants to send raw data to the device
130
+ // Here is where we'll get Rumble and LED events
131
+ // Check the first byte to see if it's a USB or BT report
132
+ uint8_t report_type = ev.u.output.data[0];
133
+ uhid::dualsense_output_report_common report;
134
+ if (report_type == uhid::DS_OUTPUT_REPORT_USB) {
135
+ auto report_usb = (uhid::dualsense_output_report_usb *)ev.u.output.data;
136
+ report = report_usb->common;
137
+ } else {
138
+ uhid::dualsense_output_report_bt *report_bt = (uhid::dualsense_output_report_bt *)ev.u.output.data;
139
+ /*
140
+ * SDL2 sets the EnableHID flag and will send the output report straight after
141
+ * https://github.com/libsdl-org/SDL/blob/c8c4c9772758de2ae466d27f13eb3ed4233e3f32/src/joystick/hidapi/SDL_hidapi_ps5.c#L788-L789
142
+ *
143
+ * The Linux kernel instead, sets this as 0, properly set the SeqNo and adds a hardcoded `tag` field before the
144
+ * actual output report
145
+ * https://github.com/torvalds/linux/blob/305230142ae0637213bf6e04f6d9f10bbcb74af8/drivers/hid/hid-playstation.c#L1184-L1192
146
+ */
147
+ if (report_bt->EnableHID == 0) {
148
+ report_bt = (uhid::dualsense_output_report_bt *)&ev.u.output.data[1]; // Skip the tag field
149
+ }
150
+ report = report_bt->common;
151
+ }
152
+
153
+ /*
154
+ * RUMBLE
155
+ * The PS5 joypad seems to report values in the range 0-255,
156
+ * we'll turn those into 0-0xFFFF
157
+ */
158
+ if (report.valid_flag0 & uhid::MOTOR_OR_COMPATIBLE_VIBRATION || report.valid_flag2 & uhid::COMPATIBLE_VIBRATION) {
159
+ auto left = (report.motor_left / 255.0f) * 0xFFFF;
160
+ auto right = (report.motor_right / 255.0f) * 0xFFFF;
161
+ if (state->on_rumble) {
162
+ (*state->on_rumble)(left, right);
163
+ }
164
+ } else if (report.valid_flag0 == 0 && report.valid_flag1 == 0 && report.valid_flag2 == 0) {
165
+ // Seems to be a special stop rumble event, let's propagate it
166
+ if (state->on_rumble) {
167
+ (*state->on_rumble)(0, 0);
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Trigger effects
173
+ */
174
+ bool right_trigger = report.valid_flag0 & uhid::RIGHT_TRIGGER_EFFECT;
175
+ bool left_trigger = report.valid_flag0 & uhid::LEFT_TRIGGER_EFFECT;
176
+ if ((right_trigger || left_trigger) && state->on_trigger_effect) {
177
+ auto left_array_start = std::begin(report.left_trigger_effect);
178
+ auto left_array_end = std::end(report.left_trigger_effect);
179
+ auto right_array_start = std::begin(report.right_trigger_effect);
180
+ auto right_array_end = std::end(report.right_trigger_effect);
181
+ // We have to cache these values because these flags will be set as long as the effect is active
182
+ uint32_t left_trigger_hash = std::accumulate(left_array_start, left_array_end, 0ul);
183
+ uint32_t right_trigger_hash = std::accumulate(right_array_start, right_array_end, 0ul);
184
+ if ((left_trigger && state->last_left_trigger_event != left_trigger_hash) ||
185
+ (right_trigger && state->last_right_trigger_event != right_trigger_hash)) {
186
+ // First, update the cache
187
+ if (left_trigger)
188
+ state->last_left_trigger_event = left_trigger_hash;
189
+ if (right_trigger)
190
+ state->last_right_trigger_event = right_trigger_hash;
191
+
192
+ // Then, trigger the event
193
+ uint8_t event_flags = (report.valid_flag0 & uhid::LEFT_TRIGGER_EFFECT) |
194
+ (report.valid_flag0 & uhid::RIGHT_TRIGGER_EFFECT);
195
+ PS5Joypad::TriggerEffect effect = {.event_flags = event_flags,
196
+ .type_left = report.left_trigger_effect_type,
197
+ .type_right = report.right_trigger_effect_type};
198
+ std::copy(left_array_start, left_array_end, std::begin(effect.left));
199
+ std::copy(right_array_start, right_array_end, std::begin(effect.right));
200
+ (*state->on_trigger_effect)(effect);
201
+ }
202
+ }
203
+
204
+ /*
205
+ * LED
206
+ */
207
+ if (report.valid_flag1 & uhid::LIGHTBAR_ENABLE) {
208
+ if (state->on_led) {
209
+ // TODO: should we blend brightness?
210
+ (*state->on_led)(report.lightbar_red, report.lightbar_green, report.lightbar_blue);
211
+ }
212
+ }
213
+ }
214
+ default:
215
+ break;
216
+ }
217
+ }
218
+
219
+ PS5Joypad::PS5Joypad(uint16_t vendor_id, std::array<unsigned char, 6> mac_address)
220
+ : _state(std::make_shared<PS5JoypadState>()) {
221
+ std::copy(mac_address.begin(), mac_address.end(), this->_state->mac_address);
222
+ this->_state->vendor_id = vendor_id;
223
+ // Set touchpad as not pressed
224
+ this->_state->current_state.points[0].contact = 1;
225
+ this->_state->current_state.points[1].contact = 1;
226
+ // Set the battery to 100% (so that if the client doesn't report it we don't trigger annoying low battery warnings)
227
+ this->_state->current_state.battery_charge = 10;
228
+ this->_state->current_state.battery_status = BATTERY_FULL;
229
+ }
230
+
231
+ PS5Joypad::~PS5Joypad() {
232
+ if (this->_state && this->_state->dev) {
233
+ this->_state->stop_repeat_thread = true;
234
+ if (this->_send_input_thread.joinable()) {
235
+ this->_send_input_thread.join();
236
+ }
237
+ this->_state->dev->stop_thread();
238
+ this->_state->dev.reset(); // Will trigger ~Device and ultimately destroy the device
239
+ }
240
+ }
241
+
242
+ Result<PS5Joypad> PS5Joypad::create(const DeviceDefinition &device) {
243
+ bool use_bluetooth = true; // TODO: expose this
244
+
245
+ auto def = uhid::DeviceDefinition{
246
+ .name = device.name,
247
+ .phys = device.device_phys,
248
+ .uniq = device.device_uniq,
249
+ .bus = BUS_BLUETOOTH,
250
+ .vendor = static_cast<uint32_t>(device.vendor_id),
251
+ .product = static_cast<uint32_t>(device.product_id),
252
+ .version = static_cast<uint32_t>(device.version),
253
+ .country = 0,
254
+ .report_description = {&uhid::ps5_rdesc_bt[0], &uhid::ps5_rdesc_bt[0] + sizeof(uhid::ps5_rdesc_bt)}};
255
+
256
+ if (!use_bluetooth) {
257
+ def.bus = BUS_USB;
258
+ def.report_description = {&uhid::ps5_rdesc[0], &uhid::ps5_rdesc[0] + sizeof(uhid::ps5_rdesc)};
259
+ }
260
+
261
+ std::array<unsigned char, 6> mac_address = {};
262
+ if (def.uniq.empty()) {
263
+ mac_address = generate_mac_address();
264
+ } else {
265
+ // Assuming we have in input a MAC address in the format of xx:xx:xx:xx:xx:xx
266
+ std::stringstream ss(def.uniq);
267
+ for (int i = 0; i < 6; ++i) {
268
+ unsigned int value;
269
+ ss >> std::hex >> value;
270
+ mac_address[i] = static_cast<unsigned char>(value);
271
+ if (i < 5)
272
+ ss.ignore(1, ':');
273
+ }
274
+ }
275
+ auto joypad = PS5Joypad(device.vendor_id, mac_address);
276
+
277
+ if (def.phys.empty()) {
278
+ def.phys = "INPUTTINO_BT_LINK";
279
+ }
280
+ if (def.uniq.empty()) {
281
+ def.uniq = joypad.get_mac_address();
282
+ }
283
+
284
+ auto dev =
285
+ uhid::Device::create(def, [state = joypad._state](uhid_event ev, int fd) { on_uhid_event(state, ev, fd); });
286
+ if (dev) {
287
+ joypad._state->is_bluetooth = use_bluetooth;
288
+ joypad._state->dev = std::make_shared<uhid::Device>(std::move(*dev));
289
+
290
+ // Readers will expect frequent events event if the state hasn't changed
291
+ joypad._send_input_thread = std::thread([state = joypad._state]() {
292
+ while (!state->stop_repeat_thread) {
293
+ send_report(*state);
294
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
295
+ }
296
+ });
297
+ joypad._send_input_thread.detach();
298
+
299
+ return joypad;
300
+ }
301
+ return Error(dev.getErrorMessage());
302
+ }
303
+
304
+ static int scale_value(int input, int input_start, int input_end, int output_start, int output_end) {
305
+ auto slope = 1.0 * (output_end - output_start) / (input_end - input_start);
306
+ return output_start + std::round(slope * (input - input_start));
307
+ }
308
+
309
+ template <typename T> std::string to_hex(T i) {
310
+ std::stringstream stream;
311
+ stream << std::hex << std::uppercase << i;
312
+ return stream.str();
313
+ }
314
+
315
+ std::string PS5Joypad::get_mac_address() const {
316
+ std::stringstream stream;
317
+ stream << std::hex << std::setfill('0') << std::setw(2) << (unsigned int)_state->mac_address[0] << ":" << std::setw(2)
318
+ << (unsigned int)_state->mac_address[1] << ":" << std::setw(2) << (unsigned int)_state->mac_address[2] << ":"
319
+ << std::setw(2) << (unsigned int)_state->mac_address[3] << ":" << std::setw(2)
320
+ << (unsigned int)_state->mac_address[4] << ":" << std::setw(2) << (unsigned int)_state->mac_address[5];
321
+ return stream.str();
322
+ }
323
+
324
+ /**
325
+ * The trick here is to match the devices under /sys/devices/virtual/misc/uhid/
326
+ * with the MAC address that we've set for the current device
327
+ *
328
+ * @returns a list of paths to the created input devices ex:
329
+ * /sys/devices/virtual/misc/uhid/0003:054C:0CE6.000D/input/input58/
330
+ */
331
+ std::vector<std::string> PS5Joypad::get_sys_nodes() const {
332
+ std::vector<std::string> nodes;
333
+ auto base_path = "/sys/devices/virtual/misc/uhid/";
334
+ auto target_mac = get_mac_address();
335
+ if (std::filesystem::exists(base_path)) {
336
+ auto uhid_entries = std::filesystem::directory_iterator{base_path};
337
+ for (auto uhid_entry : uhid_entries) {
338
+ // Here we are looking for a directory that has a name like {BUS_ID}:{VENDOR_ID}:{PRODUCT_ID}.xxxx
339
+ // (ex: 0003:054C:0CE6.000D)
340
+ auto uhid_candidate_path = uhid_entry.path().filename().string();
341
+ auto target_id = to_hex(this->_state->vendor_id);
342
+ if (uhid_entry.is_directory() && uhid_candidate_path.find(target_id) != std::string::npos) {
343
+ // Found a match! Let's scan the input devices in that directory
344
+ if (std::filesystem::exists(uhid_entry.path() / "input")) {
345
+ // ex: /sys/devices/virtual/misc/uhid/0003:054C:0CE6.000D/input/
346
+ auto dev_entries = std::filesystem::directory_iterator{uhid_entry.path() / "input"};
347
+ for (auto dev_entry : dev_entries) {
348
+ // Here we only have a match if the "uniq" file inside contains the same MAC address that we've set
349
+ if (dev_entry.is_directory()) {
350
+ // ex: /sys/devices/virtual/misc/uhid/0003:054C:0CE6.000D/input/input58/uniq
351
+ auto dev_uniq_path = dev_entry.path() / "uniq";
352
+ if (std::filesystem::exists(dev_uniq_path)) {
353
+ std::ifstream dev_uniq_file{dev_uniq_path};
354
+ std::string line;
355
+ std::getline(dev_uniq_file, line);
356
+ if (line == target_mac) {
357
+ nodes.push_back(dev_entry.path().string());
358
+ }
359
+ } else {
360
+ fprintf(stderr, "Unable to get joypad nodes, path %s does not exist\n", dev_uniq_path.string().c_str());
361
+ }
362
+ }
363
+ }
364
+ } else {
365
+ fprintf(stderr, "Unable to get joypad nodes, path %s does not exist\n", uhid_entry.path().string().c_str());
366
+ }
367
+ }
368
+ }
369
+ } else {
370
+ fprintf(stderr, "Unable to get joypad nodes, path %s does not exist\n", base_path);
371
+ }
372
+ return nodes;
373
+ }
374
+
375
+ std::vector<std::string> PS5Joypad::get_nodes() const {
376
+ std::vector<std::string> nodes;
377
+
378
+ auto sys_nodes = get_sys_nodes();
379
+ for (const auto dev_entry : sys_nodes) {
380
+ auto dev_nodes = std::filesystem::directory_iterator{dev_entry};
381
+ for (auto dev_node : dev_nodes) {
382
+ if (dev_node.is_directory() && (dev_node.path().filename().string().rfind("event", 0) == 0 ||
383
+ dev_node.path().filename().string().rfind("js", 0) == 0)) {
384
+ nodes.push_back(("/dev/input/" / dev_node.path().filename()).string());
385
+ }
386
+ }
387
+ }
388
+
389
+ return nodes;
390
+ }
391
+
392
+ void PS5Joypad::set_pressed_buttons(unsigned int pressed) {
393
+ { // First reset everything to non-pressed
394
+ this->_state->current_state.buttons[0] = 0;
395
+ // Don't reset L2 and R2, these are handled in set_triggers
396
+ this->_state->current_state.buttons[1] &= (uhid::L2 | uhid::R2);
397
+ this->_state->current_state.buttons[2] = 0;
398
+ this->_state->current_state.buttons[3] = 0;
399
+ }
400
+ {
401
+ if (DPAD_UP & pressed) { // Pressed UP
402
+ if (DPAD_LEFT & pressed) { // NW
403
+ this->_state->current_state.buttons[0] |= uhid::HAT_NW;
404
+ } else if (DPAD_RIGHT & pressed) { // NE
405
+ this->_state->current_state.buttons[0] |= uhid::HAT_NE;
406
+ } else { // N
407
+ this->_state->current_state.buttons[0] |= uhid::HAT_N;
408
+ }
409
+ }
410
+
411
+ if (DPAD_DOWN & pressed) { // Pressed DOWN
412
+ if (DPAD_LEFT & pressed) { // SW
413
+ this->_state->current_state.buttons[0] |= uhid::HAT_SW;
414
+ } else if (DPAD_RIGHT & pressed) { // SE
415
+ this->_state->current_state.buttons[0] |= uhid::HAT_SE;
416
+ } else { // S
417
+ this->_state->current_state.buttons[0] |= uhid::HAT_S;
418
+ }
419
+ }
420
+
421
+ if (DPAD_LEFT & pressed) { // Pressed LEFT
422
+ if (!(DPAD_UP & pressed) && !(DPAD_DOWN & pressed)) { // Pressed only LEFT
423
+ this->_state->current_state.buttons[0] |= uhid::HAT_W;
424
+ }
425
+ }
426
+
427
+ if (DPAD_RIGHT & pressed) { // Pressed RIGHT
428
+ if (!(DPAD_UP & pressed) && !(DPAD_DOWN & pressed)) { // Pressed only RIGHT
429
+ this->_state->current_state.buttons[0] |= uhid::HAT_E;
430
+ }
431
+ }
432
+
433
+ if (!(DPAD_UP & pressed) && !(DPAD_DOWN & pressed) && !(DPAD_LEFT & pressed) && !(DPAD_RIGHT & pressed)) {
434
+ this->_state->current_state.buttons[0] |= uhid::HAT_NEUTRAL;
435
+ }
436
+
437
+ // TODO: L2/R2 ??
438
+
439
+ if (X & pressed)
440
+ this->_state->current_state.buttons[0] |= uhid::SQUARE;
441
+ if (Y & pressed)
442
+ this->_state->current_state.buttons[0] |= uhid::TRIANGLE;
443
+ if (A & pressed)
444
+ this->_state->current_state.buttons[0] |= uhid::CROSS;
445
+ if (B & pressed)
446
+ this->_state->current_state.buttons[0] |= uhid::CIRCLE;
447
+ if (LEFT_BUTTON & pressed)
448
+ this->_state->current_state.buttons[1] |= uhid::L1;
449
+ if (RIGHT_BUTTON & pressed)
450
+ this->_state->current_state.buttons[1] |= uhid::R1;
451
+ if (LEFT_STICK & pressed)
452
+ this->_state->current_state.buttons[1] |= uhid::L3;
453
+ if (RIGHT_STICK & pressed)
454
+ this->_state->current_state.buttons[1] |= uhid::R3;
455
+ if (START & pressed)
456
+ this->_state->current_state.buttons[1] |= uhid::OPTIONS;
457
+ if (BACK & pressed)
458
+ this->_state->current_state.buttons[1] |= uhid::CREATE;
459
+ if (TOUCHPAD_FLAG & pressed)
460
+ this->_state->current_state.buttons[2] |= uhid::TOUCHPAD;
461
+ if (HOME & pressed)
462
+ this->_state->current_state.buttons[2] |= uhid::PS_HOME;
463
+ if (MISC_FLAG & pressed)
464
+ this->_state->current_state.buttons[2] |= uhid::MIC_MUTE;
465
+ }
466
+ send_report(*this->_state);
467
+ }
468
+ void PS5Joypad::set_triggers(int16_t left, int16_t right) {
469
+ this->_state->current_state.z = scale_value(left, 0, 255, uhid::PS5_AXIS_MIN, uhid::PS5_AXIS_MAX);
470
+ this->_state->current_state.rz = scale_value(right, 0, 255, uhid::PS5_AXIS_MIN, uhid::PS5_AXIS_MAX);
471
+
472
+ if (left == 0)
473
+ this->_state->current_state.buttons[1] &= ~uhid::L2;
474
+ else
475
+ this->_state->current_state.buttons[1] |= uhid::L2;
476
+
477
+ if (right == 0)
478
+ this->_state->current_state.buttons[1] &= ~uhid::R2;
479
+ else
480
+ this->_state->current_state.buttons[1] |= uhid::R2;
481
+
482
+ send_report(*this->_state);
483
+ }
484
+ void PS5Joypad::set_stick(Joypad::STICK_POSITION stick_type, short x, short y) {
485
+ switch (stick_type) {
486
+ case RS: {
487
+ this->_state->current_state.rx = scale_value(x, -32768, 32767, uhid::PS5_AXIS_MIN, uhid::PS5_AXIS_MAX);
488
+ this->_state->current_state.ry = scale_value(-y, -32768, 32767, uhid::PS5_AXIS_MIN, uhid::PS5_AXIS_MAX);
489
+ send_report(*this->_state);
490
+ break;
491
+ }
492
+ case LS: {
493
+ this->_state->current_state.x = scale_value(x, -32768, 32767, uhid::PS5_AXIS_MIN, uhid::PS5_AXIS_MAX);
494
+ this->_state->current_state.y = scale_value(-y, -32768, 32767, uhid::PS5_AXIS_MIN, uhid::PS5_AXIS_MAX);
495
+ send_report(*this->_state);
496
+ break;
497
+ }
498
+ }
499
+ }
500
+ void PS5Joypad::set_on_rumble(const std::function<void(int, int)> &callback) {
501
+ this->_state->on_rumble = callback;
502
+ }
503
+
504
+ /**
505
+ * For a rationale behind this, see: https://github.com/LizardByte/Sunshine/issues/3247#issuecomment-2428065349
506
+ */
507
+ static __le16 to_le_signed(float original, float value) {
508
+ value = std::clamp(value, static_cast<float>(SHRT_MIN), static_cast<float>(SHRT_MAX));
509
+ return htole16(value);
510
+ }
511
+
512
+ void PS5Joypad::set_motion(PS5Joypad::MOTION_TYPE type, float x, float y, float z) {
513
+ switch (type) {
514
+ case ACCELERATION: {
515
+ this->_state->current_state.accel[0] = to_le_signed(x, (x * uhid::SDL_STANDARD_GRAVITY_CONST * 100));
516
+ this->_state->current_state.accel[1] = to_le_signed(y, (y * uhid::SDL_STANDARD_GRAVITY_CONST * 100));
517
+ this->_state->current_state.accel[2] = to_le_signed(z, (z * uhid::SDL_STANDARD_GRAVITY_CONST * 100));
518
+
519
+ send_report(*this->_state);
520
+ break;
521
+ }
522
+ case GYROSCOPE: {
523
+ this->_state->current_state.gyro[0] = to_le_signed(x, x * uhid::gyro_resolution);
524
+ this->_state->current_state.gyro[1] = to_le_signed(y, y * uhid::gyro_resolution);
525
+ this->_state->current_state.gyro[2] = to_le_signed(z, z * uhid::gyro_resolution);
526
+
527
+ send_report(*this->_state);
528
+ break;
529
+ }
530
+ }
531
+ }
532
+
533
+ void PS5Joypad::set_battery(PS5Joypad::BATTERY_STATE state, int percentage) {
534
+ /*
535
+ * Each unit of battery data corresponds to 10%
536
+ * 0 = 0-9%, 1 = 10-19%, .. and 10 = 100%
537
+ */
538
+ this->_state->current_state.battery_charge = std::lround((percentage / 10));
539
+ this->_state->current_state.battery_status = state;
540
+ send_report(*this->_state);
541
+ }
542
+
543
+ void PS5Joypad::set_on_led(const std::function<void(int, int, int)> &callback) {
544
+ this->_state->on_led = callback;
545
+ }
546
+
547
+ void PS5Joypad::set_on_trigger_effect(const std::function<void(const TriggerEffect &)> &callback) {
548
+ this->_state->on_trigger_effect = callback;
549
+ }
550
+
551
+ void PS5Joypad::place_finger(int finger_nr, uint16_t x, uint16_t y) {
552
+ if (finger_nr <= 1) {
553
+ // If this finger was previously unpressed, we should increase the touch id
554
+ if (this->_state->current_state.points[finger_nr].contact == 1) {
555
+ this->_state->current_state.points[finger_nr].id = ++this->_state->last_touch_id;
556
+ }
557
+ this->_state->current_state.points[finger_nr].contact = 0;
558
+
559
+ this->_state->current_state.points[finger_nr].x_lo = static_cast<uint8_t>(x & 0x00FF);
560
+ this->_state->current_state.points[finger_nr].x_hi = static_cast<uint8_t>((x & 0x0F00) >> 8);
561
+
562
+ this->_state->current_state.points[finger_nr].y_lo = static_cast<uint8_t>(y & 0x000F);
563
+ this->_state->current_state.points[finger_nr].y_hi = static_cast<uint8_t>((y & 0x0FF0) >> 4);
564
+
565
+ send_report(*this->_state);
566
+ }
567
+ }
568
+
569
+ void PS5Joypad::release_finger(int finger_nr) {
570
+ if (finger_nr <= 1) {
571
+ // if it goes above 0x7F we should reset it to 0
572
+ if (this->_state->last_touch_id >= 0x7E) {
573
+ this->_state->last_touch_id = 0;
574
+ }
575
+ this->_state->current_state.points[finger_nr].contact = 1;
576
+ send_report(*this->_state);
577
+ }
578
+ }
579
+
580
+ } // namespace inputtino
@@ -0,0 +1,84 @@
1
+ #include <linux/input-event-codes.h>
2
+ #include <map>
3
+
4
+ namespace inputtino::keyboard {
5
+
6
+ struct KEY_MAP {
7
+ int linux_code;
8
+ int scan_code;
9
+ };
10
+
11
+ constexpr auto UNKNOWN = 0;
12
+
13
+ /**
14
+ * A map of [Moonlight keyboard code] -> {linux_code, scan_code}
15
+ */
16
+ static const std::map<short, KEY_MAP> key_mappings = {
17
+ {0x08, {KEY_BACKSPACE, 0x7002A}}, {0x09, {KEY_TAB, 0x7002B}},
18
+ {0x0C, {KEY_CLEAR, UNKNOWN}}, {0x0D, {KEY_ENTER, 0x70028}},
19
+ {0x10, {KEY_LEFTSHIFT, 0x700E1}}, {0x11, {KEY_LEFTCTRL, 0x700E0}},
20
+ {0x12, {KEY_LEFTALT, UNKNOWN}}, {0x13, {KEY_PAUSE, UNKNOWN}},
21
+ {0x14, {KEY_CAPSLOCK, 0x70039}}, {0x15, {KEY_KATAKANAHIRAGANA, UNKNOWN}},
22
+ {0x16, {KEY_HANGEUL, UNKNOWN}}, {0x17, {KEY_HANJA, UNKNOWN}},
23
+ {0x19, {KEY_KATAKANA, UNKNOWN}}, {0x1B, {KEY_ESC, 0x70029}},
24
+ {0x20, {KEY_SPACE, 0x7002C}}, {0x21, {KEY_PAGEUP, 0x7004B}},
25
+ {0x22, {KEY_PAGEDOWN, 0x7004E}}, {0x23, {KEY_END, 0x7004D}},
26
+ {0x24, {KEY_HOME, 0x7004A}}, {0x25, {KEY_LEFT, 0x70050}},
27
+ {0x26, {KEY_UP, 0x70052}}, {0x27, {KEY_RIGHT, 0x7004F}},
28
+ {0x28, {KEY_DOWN, 0x70051}}, {0x29, {KEY_SELECT, UNKNOWN}},
29
+ {0x2A, {KEY_PRINT, UNKNOWN}}, {0x2C, {KEY_SYSRQ, 0x70046}},
30
+ {0x2D, {KEY_INSERT, 0x70049}}, {0x2E, {KEY_DELETE, 0x7004C}},
31
+ {0x2F, {KEY_HELP, UNKNOWN}}, {0x30, {KEY_0, 0x70027}},
32
+ {0x31, {KEY_1, 0x7001E}}, {0x32, {KEY_2, 0x7001F}},
33
+ {0x33, {KEY_3, 0x70020}}, {0x34, {KEY_4, 0x70021}},
34
+ {0x35, {KEY_5, 0x70022}}, {0x36, {KEY_6, 0x70023}},
35
+ {0x37, {KEY_7, 0x70024}}, {0x38, {KEY_8, 0x70025}},
36
+ {0x39, {KEY_9, 0x70026}}, {0x41, {KEY_A, 0x70004}},
37
+ {0x42, {KEY_B, 0x70005}}, {0x43, {KEY_C, 0x70006}},
38
+ {0x44, {KEY_D, 0x70007}}, {0x45, {KEY_E, 0x70008}},
39
+ {0x46, {KEY_F, 0x70009}}, {0x47, {KEY_G, 0x7000A}},
40
+ {0x48, {KEY_H, 0x7000B}}, {0x49, {KEY_I, 0x7000C}},
41
+ {0x4A, {KEY_J, 0x7000D}}, {0x4B, {KEY_K, 0x7000E}},
42
+ {0x4C, {KEY_L, 0x7000F}}, {0x4D, {KEY_M, 0x70010}},
43
+ {0x4E, {KEY_N, 0x70011}}, {0x4F, {KEY_O, 0x70012}},
44
+ {0x50, {KEY_P, 0x70013}}, {0x51, {KEY_Q, 0x70014}},
45
+ {0x52, {KEY_R, 0x70015}}, {0x53, {KEY_S, 0x70016}},
46
+ {0x54, {KEY_T, 0x70017}}, {0x55, {KEY_U, 0x70018}},
47
+ {0x56, {KEY_V, 0x70019}}, {0x57, {KEY_W, 0x7001A}},
48
+ {0x58, {KEY_X, 0x7001B}}, {0x59, {KEY_Y, 0x7001C}},
49
+ {0x5A, {KEY_Z, 0x7001D}}, {0x5B, {KEY_LEFTMETA, 0x700E3}},
50
+ {0x5C, {KEY_RIGHTMETA, 0x700E7}}, {0x5D, {KEY_COMPOSE, UNKNOWN}},
51
+ {0x5F, {KEY_SLEEP, UNKNOWN}},
52
+ {0x60, {KEY_KP0, 0x70062}}, {0x61, {KEY_KP1, 0x70059}},
53
+ {0x62, {KEY_KP2, 0x7005A}}, {0x63, {KEY_KP3, 0x7005B}},
54
+ {0x64, {KEY_KP4, 0x7005C}}, {0x65, {KEY_KP5, 0x7005D}},
55
+ {0x66, {KEY_KP6, 0x7005E}}, {0x67, {KEY_KP7, 0x7005F}},
56
+ {0x68, {KEY_KP8, 0x70060}}, {0x69, {KEY_KP9, 0x70061}},
57
+ {0x6A, {KEY_KPASTERISK, 0x70055}}, {0x6B, {KEY_KPPLUS, 0x70057}},
58
+ {0x6C, {KEY_KPCOMMA, UNKNOWN}}, {0x6D, {KEY_KPMINUS, 0x70056}},
59
+ {0x6E, {KEY_KPDOT, 0x70063}}, {0x6F, {KEY_KPSLASH, 0x70054}},
60
+ {0x70, {KEY_F1, 0x70059}}, {0x71, {KEY_F2, 0x70060}},
61
+ {0x72, {KEY_F3, 0x70061}}, {0x73, {KEY_F4, 0x70062}},
62
+ {0x74, {KEY_F5, 0x70063}}, {0x75, {KEY_F6, 0x70064}},
63
+ {0x76, {KEY_F7, 0x70065}}, {0x77, {KEY_F8, 0x70066}},
64
+ {0x78, {KEY_F9, 0x70067}}, {0x79, {KEY_F10, 0x70068}},
65
+ {0x7A, {KEY_F11, 70044}}, {0x7B, {KEY_F12, 70045}},
66
+ {0x7C, {KEY_F13, 0x70068}}, {0x7D, {KEY_F14, 0x70069}},
67
+ {0x7E, {KEY_F15, 0x7006a}}, {0x7F, {KEY_F16, 0x7006b}},
68
+ {0x80, {KEY_F17, 0x7006c}}, {0x81, {KEY_F18, 0x7006d}},
69
+ {0x82, {KEY_F19, 0x7006e}}, {0x83, {KEY_F20, 0x7006f}},
70
+ {0x84, {KEY_F21, 0x70070}}, {0x85, {KEY_F22, 0x70071}},
71
+ {0x86, {KEY_F23, 0x70072}}, {0x87, {KEY_F24, 0x70073}},
72
+ {0x90, {KEY_NUMLOCK, 0x70053}}, {0x91, {KEY_SCROLLLOCK, 0x70047}},
73
+ {0xA0, {KEY_LEFTSHIFT, 0x700E1}}, {0xA1, {KEY_RIGHTSHIFT, 0x700E5}},
74
+ {0xA2, {KEY_LEFTCTRL, 0x700E0}}, {0xA3, {KEY_RIGHTCTRL, 0x700E4}},
75
+ {0xA4, {KEY_LEFTALT, 0x7002E}}, {0xA5, {KEY_RIGHTALT, 0x700E6}},
76
+ {0xBA, {KEY_SEMICOLON, 0x70033}}, {0xBB, {KEY_EQUAL, 0x7002E}},
77
+ {0xBC, {KEY_COMMA, 0x70036}}, {0xBD, {KEY_MINUS, 0x7002D}},
78
+ {0xBE, {KEY_DOT, 0x70037}}, {0xBF, {KEY_SLASH, 0x70038}},
79
+ {0xC0, {KEY_GRAVE, 0x70035}}, {0xDB, {KEY_LEFTBRACE, 0x7002F}},
80
+ {0xDC, {KEY_BACKSLASH, 0x70031}}, {0xDD, {KEY_RIGHTBRACE, 0x70030}},
81
+ {0xDE, {KEY_APOSTROPHE, 0x70034}}, {0xE2, {KEY_102ND, 0x70064}},
82
+ };
83
+
84
+ } // namespace wolf::core::input::keyboard
@@ -0,0 +1,7 @@
1
+ #pragma once
2
+
3
+ #include <inputtino/input.hpp>
4
+
5
+ namespace inputtino {
6
+ struct PS5JoypadState : BaseJoypadState {};
7
+ } // namespace inputtino