Skip to content

Commit b8d3f35

Browse files
committed
feat: implemented basic uinput PS joypad and toggle in the Cmake options
1 parent 6b26593 commit b8d3f35

File tree

7 files changed

+760
-343
lines changed

7 files changed

+760
-343
lines changed

CMakeLists.txt

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ generate_export_header(libinputtino EXPORT_FILE_NAME include/inputtino/${export_
7474

7575
option(BUILD_C_BINDINGS "Build C bindings" OFF)
7676
option(LIBINPUTTINO_INSTALL "Generate the install target" OFF)
77+
option(USE_UHID "Add uhid implementation for advanced DualSense features" ON)
7778

7879
#----------------------------------------------------------------------------------------------------------------------
7980
# Dependencies
@@ -107,12 +108,26 @@ set(PUBLIC_HEADERS
107108
include/inputtino/input.h)
108109

109110
if (UNIX AND NOT APPLE)
110-
file(GLOB SRC_LIST SRCS src/uinput/*.cpp)
111-
target_sources(libinputtino PRIVATE
112-
${SRC_LIST}
113-
"src/uinput/joypad_utils.hpp"
114-
"src/uhid/joypad_ps5.cpp")
115-
target_include_directories(libinputtino PUBLIC "src/uinput/include" "src/uhid/include/")
111+
list(APPEND SRC_LIST
112+
"src/uinput/joypad_nintendo.cpp"
113+
"src/uinput/joypad_xbox.cpp"
114+
"src/uinput/keyboard.cpp"
115+
"src/uinput/mouse.cpp"
116+
"src/uinput/pentablet.cpp"
117+
"src/uinput/touchscreen.cpp"
118+
"src/uinput/trackpad.cpp")
119+
120+
if (USE_UHID)
121+
message("Using uhid implementation for DualSense controller")
122+
list(APPEND SRC_LIST "src/uhid/joypad_ps5.cpp")
123+
target_include_directories(libinputtino PUBLIC "src/uhid/include/")
124+
else ()
125+
message("Using uinput implementation for DualSense controller")
126+
list(APPEND SRC_LIST "src/uinput/joypad_ps.cpp")
127+
endif ()
128+
129+
target_sources(libinputtino PRIVATE ${SRC_LIST})
130+
target_include_directories(libinputtino PUBLIC "src/uinput/include")
116131
endif ()
117132

118133
if (BUILD_C_BINDINGS)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#pragma once
2+
3+
#include <inputtino/input.hpp>
4+
5+
namespace inputtino {
6+
struct PS5JoypadState : BaseJoypadState {};
7+
} // namespace inputtino

src/uinput/joypad_ps.cpp

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
#include "joypad_utils.hpp"
2+
#include <cstring>
3+
#include <fcntl.h>
4+
#include <inputtino/input.hpp>
5+
#include <inputtino/protected_ps5_types.hpp>
6+
#include <linux/input.h>
7+
#include <optional>
8+
#include <thread>
9+
10+
namespace inputtino {
11+
12+
std::vector<std::string> PS5Joypad::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_ps_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_TR2, nullptr);
43+
libevdev_enable_event_code(dev, EV_KEY, BTN_TL2, nullptr);
44+
libevdev_enable_event_code(dev, EV_KEY, BTN_SELECT, nullptr);
45+
libevdev_enable_event_code(dev, EV_KEY, BTN_MODE, nullptr);
46+
libevdev_enable_event_code(dev, EV_KEY, BTN_START, nullptr);
47+
48+
libevdev_enable_event_type(dev, EV_ABS);
49+
50+
input_absinfo dpad{0, -1, 1, 0, 0, 0};
51+
libevdev_enable_event_code(dev, EV_ABS, ABS_HAT0Y, &dpad);
52+
libevdev_enable_event_code(dev, EV_ABS, ABS_HAT0X, &dpad);
53+
54+
// see: https://github.com/games-on-whales/wolf/issues/56
55+
input_absinfo stick{0, -32768, 32767, 16, 128, 0};
56+
libevdev_enable_event_code(dev, EV_ABS, ABS_X, &stick);
57+
libevdev_enable_event_code(dev, EV_ABS, ABS_RX, &stick);
58+
libevdev_enable_event_code(dev, EV_ABS, ABS_Y, &stick);
59+
libevdev_enable_event_code(dev, EV_ABS, ABS_RY, &stick);
60+
61+
input_absinfo trigger{0, 0, 255, 0, 0, 0};
62+
libevdev_enable_event_code(dev, EV_ABS, ABS_Z, &trigger);
63+
libevdev_enable_event_code(dev, EV_ABS, ABS_RZ, &trigger);
64+
65+
libevdev_enable_event_type(dev, EV_FF);
66+
libevdev_enable_event_code(dev, EV_FF, FF_RUMBLE, nullptr);
67+
libevdev_enable_event_code(dev, EV_FF, FF_CONSTANT, nullptr);
68+
libevdev_enable_event_code(dev, EV_FF, FF_PERIODIC, nullptr);
69+
libevdev_enable_event_code(dev, EV_FF, FF_SINE, nullptr);
70+
libevdev_enable_event_code(dev, EV_FF, FF_RAMP, nullptr);
71+
libevdev_enable_event_code(dev, EV_FF, FF_GAIN, nullptr);
72+
73+
auto err = libevdev_uinput_create_from_device(dev, LIBEVDEV_UINPUT_OPEN_MANAGED, &uidev);
74+
libevdev_free(dev);
75+
if (err != 0) {
76+
return Error(strerror(-err));
77+
}
78+
79+
return libevdev_uinput_ptr{uidev, ::libevdev_uinput_destroy};
80+
}
81+
82+
PS5Joypad::PS5Joypad(uint16_t vendor_id) : _state(std::make_shared<PS5JoypadState>()) {}
83+
84+
PS5Joypad::~PS5Joypad() {
85+
if (_state) {
86+
_state->stop_listening_events = true;
87+
if (_state->joy.get() != nullptr && _state->events_thread.joinable()) {
88+
_state->events_thread.join();
89+
}
90+
}
91+
}
92+
93+
Result<PS5Joypad> PS5Joypad::create(const DeviceDefinition &device) {
94+
auto joy_el = create_ps_controller(device);
95+
if (!joy_el) {
96+
return Error(joy_el.getErrorMessage());
97+
}
98+
99+
PS5Joypad joypad(0);
100+
joypad._state->joy = std::move(*joy_el);
101+
102+
auto event_thread = std::thread(event_listener, joypad._state);
103+
joypad._state->events_thread = std::move(event_thread);
104+
joypad._state->events_thread.detach();
105+
106+
return joypad;
107+
}
108+
109+
void PS5Joypad::set_pressed_buttons(unsigned int newly_pressed) {
110+
// Button flags that have been changed between current and prev
111+
auto bf_changed = newly_pressed ^ this->_state->currently_pressed_btns;
112+
// Button flags that are only part of the new packet
113+
auto bf_new = newly_pressed;
114+
if (auto controller = this->_state->joy.get()) {
115+
116+
if (bf_changed) {
117+
if ((DPAD_UP | DPAD_DOWN) & bf_changed) {
118+
int button_state = bf_new & DPAD_UP ? -1 : (bf_new & DPAD_DOWN ? 1 : 0);
119+
120+
libevdev_uinput_write_event(controller, EV_ABS, ABS_HAT0Y, button_state);
121+
}
122+
123+
if ((DPAD_LEFT | DPAD_RIGHT) & bf_changed) {
124+
int button_state = bf_new & DPAD_LEFT ? -1 : (bf_new & DPAD_RIGHT ? 1 : 0);
125+
126+
libevdev_uinput_write_event(controller, EV_ABS, ABS_HAT0X, button_state);
127+
}
128+
129+
if (START & bf_changed)
130+
libevdev_uinput_write_event(controller, EV_KEY, BTN_START, bf_new & START ? 1 : 0);
131+
if (BACK & bf_changed)
132+
libevdev_uinput_write_event(controller, EV_KEY, BTN_SELECT, bf_new & BACK ? 1 : 0);
133+
if (LEFT_STICK & bf_changed)
134+
libevdev_uinput_write_event(controller, EV_KEY, BTN_THUMBL, bf_new & LEFT_STICK ? 1 : 0);
135+
if (RIGHT_STICK & bf_changed)
136+
libevdev_uinput_write_event(controller, EV_KEY, BTN_THUMBR, bf_new & RIGHT_STICK ? 1 : 0);
137+
if (LEFT_BUTTON & bf_changed)
138+
libevdev_uinput_write_event(controller, EV_KEY, BTN_TL, bf_new & LEFT_BUTTON ? 1 : 0);
139+
if (RIGHT_BUTTON & bf_changed)
140+
libevdev_uinput_write_event(controller, EV_KEY, BTN_TR, bf_new & RIGHT_BUTTON ? 1 : 0);
141+
if (HOME & bf_changed)
142+
libevdev_uinput_write_event(controller, EV_KEY, BTN_MODE, bf_new & HOME ? 1 : 0);
143+
if (A & bf_changed)
144+
libevdev_uinput_write_event(controller, EV_KEY, BTN_SOUTH, bf_new & A ? 1 : 0);
145+
if (B & bf_changed)
146+
libevdev_uinput_write_event(controller, EV_KEY, BTN_EAST, bf_new & B ? 1 : 0);
147+
if (X & bf_changed)
148+
libevdev_uinput_write_event(controller, EV_KEY, BTN_WEST, bf_new & X ? 1 : 0);
149+
if (Y & bf_changed)
150+
libevdev_uinput_write_event(controller, EV_KEY, BTN_NORTH, bf_new & Y ? 1 : 0);
151+
}
152+
153+
libevdev_uinput_write_event(controller, EV_SYN, SYN_REPORT, 0);
154+
}
155+
this->_state->currently_pressed_btns = bf_new;
156+
}
157+
158+
void PS5Joypad::set_stick(Joypad::STICK_POSITION stick_type, short x, short y) {
159+
if (auto controller = this->_state->joy.get()) {
160+
if (stick_type == LS) {
161+
libevdev_uinput_write_event(controller, EV_ABS, ABS_X, x);
162+
libevdev_uinput_write_event(controller, EV_ABS, ABS_Y, -y);
163+
} else {
164+
libevdev_uinput_write_event(controller, EV_ABS, ABS_RX, x);
165+
libevdev_uinput_write_event(controller, EV_ABS, ABS_RY, -y);
166+
}
167+
168+
libevdev_uinput_write_event(controller, EV_SYN, SYN_REPORT, 0);
169+
}
170+
}
171+
172+
void PS5Joypad::set_triggers(int16_t left, int16_t right) {
173+
if (auto controller = this->_state->joy.get()) {
174+
if (left > 0) {
175+
libevdev_uinput_write_event(controller, EV_ABS, ABS_Z, left);
176+
} else {
177+
libevdev_uinput_write_event(controller, EV_ABS, ABS_Z, left);
178+
}
179+
180+
if (right > 0) {
181+
libevdev_uinput_write_event(controller, EV_ABS, ABS_RZ, right);
182+
} else {
183+
libevdev_uinput_write_event(controller, EV_ABS, ABS_RZ, right);
184+
}
185+
186+
libevdev_uinput_write_event(controller, EV_SYN, SYN_REPORT, 0);
187+
}
188+
}
189+
190+
void PS5Joypad::set_on_rumble(const std::function<void(int, int)> &callback) {
191+
this->_state->on_rumble = callback;
192+
}
193+
194+
// Followings aren't supported when not using the UHID implementation
195+
void PS5Joypad::place_finger(int finger_nr, uint16_t x, uint16_t y) {}
196+
void PS5Joypad::release_finger(int finger_nr) {}
197+
void PS5Joypad::set_motion(MOTION_TYPE type, float x, float y, float z) {}
198+
void PS5Joypad::set_battery(BATTERY_STATE state, int percentage) {}
199+
void PS5Joypad::set_on_led(const std::function<void(int r, int g, int b)> &callback) {}
200+
void PS5Joypad::set_on_trigger_effect(const std::function<void(const TriggerEffect &)> &callback) {}
201+
202+
} // namespace inputtino

tests/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ if (UNIX AND NOT APPLE)
2323
if (SDL_CUSTOM_SRC)
2424
SET(SDL_TEST OFF)
2525
SET(SDL_HIDAPI_JOYSTICK ON)
26+
SET(SDL_PIPEWIRE OFF)
2627
add_subdirectory(${SDL_CUSTOM_SRC} ${CMAKE_CURRENT_BINARY_DIR}/sdl EXCLUDE_FROM_ALL)
2728
else ()
2829
find_package(SDL2 REQUIRED CONFIG REQUIRED COMPONENTS SDL2)
@@ -35,6 +36,10 @@ if (UNIX AND NOT APPLE)
3536
list(APPEND SRC_LIST
3637
"testJoypads.cpp"
3738
"testLibinput.cpp")
39+
40+
if (USE_UHID)
41+
list(APPEND SRC_LIST "testUHID.cpp")
42+
endif ()
3843
endif ()
3944
endif ()
4045

tests/testCAPI.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ TEST_CASE("C PS5 API", "[C-API]") {
191191
std::this_thread::sleep_for(std::chrono::milliseconds(50));
192192
int num_nodes = 0;
193193
auto nodes = inputtino_joypad_ps5_get_nodes(ps_pad, &num_nodes);
194-
REQUIRE(num_nodes == 5);
194+
REQUIRE(num_nodes >= 2);
195195
REQUIRE_THAT(std::string(nodes[0]), Catch::Matchers::StartsWith("/dev/input/"));
196196

197197
{ // TODO: test that this actually work

0 commit comments

Comments
 (0)