Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ generate_export_header(libinputtino EXPORT_FILE_NAME include/inputtino/${export_

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

#----------------------------------------------------------------------------------------------------------------------
# Dependencies
Expand Down Expand Up @@ -107,12 +108,26 @@ set(PUBLIC_HEADERS
include/inputtino/input.h)

if (UNIX AND NOT APPLE)
file(GLOB SRC_LIST SRCS src/uinput/*.cpp)
target_sources(libinputtino PRIVATE
${SRC_LIST}
"src/uinput/joypad_utils.hpp"
"src/uhid/joypad_ps5.cpp")
target_include_directories(libinputtino PUBLIC "src/uinput/include" "src/uhid/include/")
list(APPEND SRC_LIST
"src/uinput/joypad_nintendo.cpp"
"src/uinput/joypad_xbox.cpp"
"src/uinput/keyboard.cpp"
"src/uinput/mouse.cpp"
"src/uinput/pentablet.cpp"
"src/uinput/touchscreen.cpp"
"src/uinput/trackpad.cpp")

if (USE_UHID)
message("Using uhid implementation for DualSense controller")
list(APPEND SRC_LIST "src/uhid/joypad_ps5.cpp")
target_include_directories(libinputtino PUBLIC "src/uhid/include/")
else ()
message("Using uinput implementation for DualSense controller")
list(APPEND SRC_LIST "src/uinput/joypad_ps.cpp")
endif ()

target_sources(libinputtino PRIVATE ${SRC_LIST})
target_include_directories(libinputtino PUBLIC "src/uinput/include")
endif ()

if (BUILD_C_BINDINGS)
Expand Down
4 changes: 2 additions & 2 deletions src/uhid/include/uhid/uhid.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ inputtino::Result<Device> Device::create(const DeviceDefinition &definition,
ev.type = UHID_CREATE2, ev.u.create2.bus = definition.bus, ev.u.create2.vendor = definition.vendor,
ev.u.create2.product = definition.product, ev.u.create2.version = definition.version,
ev.u.create2.country = definition.country,
ev.u.create2.rd_size = static_cast<__u16>(definition.report_description.size()),
ev.u.create2.rd_size = static_cast<std::uint16_t>(definition.report_description.size()),
std::copy(definition.report_description.begin(), definition.report_description.end(), ev.u.create2.rd_data);
set_c_str(definition.name, ev.u.create2.name);
set_c_str(definition.phys, ev.u.create2.phys);
Expand Down Expand Up @@ -165,4 +165,4 @@ inputtino::Result<Device> Device::create(const DeviceDefinition &definition,
}
}

} // namespace uhid
} // namespace uhid
7 changes: 7 additions & 0 deletions src/uinput/include/inputtino/protected_ps5_types.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once

#include <inputtino/input.hpp>

namespace inputtino {
struct PS5JoypadState : BaseJoypadState {};
} // namespace inputtino
202 changes: 202 additions & 0 deletions src/uinput/joypad_ps.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
#include "joypad_utils.hpp"
#include <cstring>
#include <fcntl.h>
#include <inputtino/input.hpp>
#include <inputtino/protected_ps5_types.hpp>
#include <linux/input.h>
#include <optional>
#include <thread>

namespace inputtino {

std::vector<std::string> PS5Joypad::get_nodes() const {
std::vector<std::string> nodes;

if (auto joy = _state->joy.get()) {
auto additional_nodes = get_child_dev_nodes(joy);
nodes.insert(nodes.end(), additional_nodes.begin(), additional_nodes.end());
}

return nodes;
}

Result<libevdev_uinput_ptr> create_ps_controller(const DeviceDefinition &device) {
libevdev *dev = libevdev_new();
libevdev_uinput *uidev;

libevdev_set_name(dev, device.name.c_str());
libevdev_set_id_vendor(dev, device.vendor_id);
libevdev_set_id_product(dev, device.product_id);
libevdev_set_id_version(dev, device.version);
libevdev_set_id_bustype(dev, BUS_USB);

libevdev_enable_event_type(dev, EV_KEY);
libevdev_enable_event_code(dev, EV_KEY, BTN_WEST, nullptr);
libevdev_enable_event_code(dev, EV_KEY, BTN_EAST, nullptr);
libevdev_enable_event_code(dev, EV_KEY, BTN_NORTH, nullptr);
libevdev_enable_event_code(dev, EV_KEY, BTN_SOUTH, nullptr);
libevdev_enable_event_code(dev, EV_KEY, BTN_THUMBL, nullptr);
libevdev_enable_event_code(dev, EV_KEY, BTN_THUMBR, nullptr);
libevdev_enable_event_code(dev, EV_KEY, BTN_TR, nullptr);
libevdev_enable_event_code(dev, EV_KEY, BTN_TL, nullptr);
libevdev_enable_event_code(dev, EV_KEY, BTN_TR2, nullptr);
libevdev_enable_event_code(dev, EV_KEY, BTN_TL2, nullptr);
libevdev_enable_event_code(dev, EV_KEY, BTN_SELECT, nullptr);
libevdev_enable_event_code(dev, EV_KEY, BTN_MODE, nullptr);
libevdev_enable_event_code(dev, EV_KEY, BTN_START, nullptr);

libevdev_enable_event_type(dev, EV_ABS);

input_absinfo dpad{0, -1, 1, 0, 0, 0};
libevdev_enable_event_code(dev, EV_ABS, ABS_HAT0Y, &dpad);
libevdev_enable_event_code(dev, EV_ABS, ABS_HAT0X, &dpad);

// see: https://github.com/games-on-whales/wolf/issues/56
input_absinfo stick{0, -32768, 32767, 16, 128, 0};
libevdev_enable_event_code(dev, EV_ABS, ABS_X, &stick);
libevdev_enable_event_code(dev, EV_ABS, ABS_RX, &stick);
libevdev_enable_event_code(dev, EV_ABS, ABS_Y, &stick);
libevdev_enable_event_code(dev, EV_ABS, ABS_RY, &stick);

input_absinfo trigger{0, 0, 255, 0, 0, 0};
libevdev_enable_event_code(dev, EV_ABS, ABS_Z, &trigger);
libevdev_enable_event_code(dev, EV_ABS, ABS_RZ, &trigger);

libevdev_enable_event_type(dev, EV_FF);
libevdev_enable_event_code(dev, EV_FF, FF_RUMBLE, nullptr);
libevdev_enable_event_code(dev, EV_FF, FF_CONSTANT, nullptr);
libevdev_enable_event_code(dev, EV_FF, FF_PERIODIC, nullptr);
libevdev_enable_event_code(dev, EV_FF, FF_SINE, nullptr);
libevdev_enable_event_code(dev, EV_FF, FF_RAMP, nullptr);
libevdev_enable_event_code(dev, EV_FF, FF_GAIN, nullptr);

auto err = libevdev_uinput_create_from_device(dev, LIBEVDEV_UINPUT_OPEN_MANAGED, &uidev);
libevdev_free(dev);
if (err != 0) {
return Error(strerror(-err));
}

return libevdev_uinput_ptr{uidev, ::libevdev_uinput_destroy};
}

PS5Joypad::PS5Joypad(uint16_t vendor_id) : _state(std::make_shared<PS5JoypadState>()) {}

PS5Joypad::~PS5Joypad() {
if (_state) {
_state->stop_listening_events = true;
if (_state->joy.get() != nullptr && _state->events_thread.joinable()) {
_state->events_thread.join();
}
}
}

Result<PS5Joypad> PS5Joypad::create(const DeviceDefinition &device) {
auto joy_el = create_ps_controller(device);
if (!joy_el) {
return Error(joy_el.getErrorMessage());
}

PS5Joypad joypad(0);
joypad._state->joy = std::move(*joy_el);

auto event_thread = std::thread(event_listener, joypad._state);
joypad._state->events_thread = std::move(event_thread);
joypad._state->events_thread.detach();

return joypad;
}

void PS5Joypad::set_pressed_buttons(unsigned int newly_pressed) {
// Button flags that have been changed between current and prev
auto bf_changed = newly_pressed ^ this->_state->currently_pressed_btns;
// Button flags that are only part of the new packet
auto bf_new = newly_pressed;
if (auto controller = this->_state->joy.get()) {

if (bf_changed) {
if ((DPAD_UP | DPAD_DOWN) & bf_changed) {
int button_state = bf_new & DPAD_UP ? -1 : (bf_new & DPAD_DOWN ? 1 : 0);

libevdev_uinput_write_event(controller, EV_ABS, ABS_HAT0Y, button_state);
}

if ((DPAD_LEFT | DPAD_RIGHT) & bf_changed) {
int button_state = bf_new & DPAD_LEFT ? -1 : (bf_new & DPAD_RIGHT ? 1 : 0);

libevdev_uinput_write_event(controller, EV_ABS, ABS_HAT0X, button_state);
}

if (START & bf_changed)
libevdev_uinput_write_event(controller, EV_KEY, BTN_START, bf_new & START ? 1 : 0);
if (BACK & bf_changed)
libevdev_uinput_write_event(controller, EV_KEY, BTN_SELECT, bf_new & BACK ? 1 : 0);
if (LEFT_STICK & bf_changed)
libevdev_uinput_write_event(controller, EV_KEY, BTN_THUMBL, bf_new & LEFT_STICK ? 1 : 0);
if (RIGHT_STICK & bf_changed)
libevdev_uinput_write_event(controller, EV_KEY, BTN_THUMBR, bf_new & RIGHT_STICK ? 1 : 0);
if (LEFT_BUTTON & bf_changed)
libevdev_uinput_write_event(controller, EV_KEY, BTN_TL, bf_new & LEFT_BUTTON ? 1 : 0);
if (RIGHT_BUTTON & bf_changed)
libevdev_uinput_write_event(controller, EV_KEY, BTN_TR, bf_new & RIGHT_BUTTON ? 1 : 0);
if (HOME & bf_changed)
libevdev_uinput_write_event(controller, EV_KEY, BTN_MODE, bf_new & HOME ? 1 : 0);
if (A & bf_changed)
libevdev_uinput_write_event(controller, EV_KEY, BTN_SOUTH, bf_new & A ? 1 : 0);
if (B & bf_changed)
libevdev_uinput_write_event(controller, EV_KEY, BTN_EAST, bf_new & B ? 1 : 0);
if (X & bf_changed)
libevdev_uinput_write_event(controller, EV_KEY, BTN_WEST, bf_new & X ? 1 : 0);
if (Y & bf_changed)
libevdev_uinput_write_event(controller, EV_KEY, BTN_NORTH, bf_new & Y ? 1 : 0);
}

libevdev_uinput_write_event(controller, EV_SYN, SYN_REPORT, 0);
}
this->_state->currently_pressed_btns = bf_new;
}

void PS5Joypad::set_stick(Joypad::STICK_POSITION stick_type, short x, short y) {
if (auto controller = this->_state->joy.get()) {
if (stick_type == LS) {
libevdev_uinput_write_event(controller, EV_ABS, ABS_X, x);
libevdev_uinput_write_event(controller, EV_ABS, ABS_Y, -y);
} else {
libevdev_uinput_write_event(controller, EV_ABS, ABS_RX, x);
libevdev_uinput_write_event(controller, EV_ABS, ABS_RY, -y);
}

libevdev_uinput_write_event(controller, EV_SYN, SYN_REPORT, 0);
}
}

void PS5Joypad::set_triggers(int16_t left, int16_t right) {
if (auto controller = this->_state->joy.get()) {
if (left > 0) {
libevdev_uinput_write_event(controller, EV_ABS, ABS_Z, left);
} else {
libevdev_uinput_write_event(controller, EV_ABS, ABS_Z, left);
}

if (right > 0) {
libevdev_uinput_write_event(controller, EV_ABS, ABS_RZ, right);
} else {
libevdev_uinput_write_event(controller, EV_ABS, ABS_RZ, right);
}

libevdev_uinput_write_event(controller, EV_SYN, SYN_REPORT, 0);
}
}

void PS5Joypad::set_on_rumble(const std::function<void(int, int)> &callback) {
this->_state->on_rumble = callback;
}

// Followings aren't supported when not using the UHID implementation
void PS5Joypad::place_finger(int finger_nr, uint16_t x, uint16_t y) {}
void PS5Joypad::release_finger(int finger_nr) {}
void PS5Joypad::set_motion(MOTION_TYPE type, float x, float y, float z) {}
void PS5Joypad::set_battery(BATTERY_STATE state, int percentage) {}
void PS5Joypad::set_on_led(const std::function<void(int r, int g, int b)> &callback) {}
void PS5Joypad::set_on_trigger_effect(const std::function<void(const TriggerEffect &)> &callback) {}

} // namespace inputtino
4 changes: 2 additions & 2 deletions src/uinput/joypad_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ static ActiveRumbleEffect create_rumble_effect(const ff_effect &effect) {
ActiveRumbleEffect r_effect{
.start_point = std::chrono::steady_clock::time_point::min(),
.end_point = std::chrono::steady_clock::time_point::min(),
.length = std::chrono::milliseconds{std::clamp(effect.replay.length, (__u16)0, (__u16)32767)},
.delay = std::chrono::milliseconds{std::clamp(effect.replay.delay, (__u16)0, (__u16)32767)},
.length = std::chrono::milliseconds{std::clamp(effect.replay.length, (std::uint16_t)0, (std::uint16_t)32767)},
.delay = std::chrono::milliseconds{std::clamp(effect.replay.delay, (std::uint16_t)0, (std::uint16_t)32767)},
.envelope = {}};
switch (effect.type) {
case FF_CONSTANT:
Expand Down
5 changes: 5 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ if (UNIX AND NOT APPLE)
if (SDL_CUSTOM_SRC)
SET(SDL_TEST OFF)
SET(SDL_HIDAPI_JOYSTICK ON)
SET(SDL_PIPEWIRE OFF)
add_subdirectory(${SDL_CUSTOM_SRC} ${CMAKE_CURRENT_BINARY_DIR}/sdl EXCLUDE_FROM_ALL)
else ()
find_package(SDL2 REQUIRED CONFIG REQUIRED COMPONENTS SDL2)
Expand All @@ -35,6 +36,10 @@ if (UNIX AND NOT APPLE)
list(APPEND SRC_LIST
"testJoypads.cpp"
"testLibinput.cpp")

if (USE_UHID)
list(APPEND SRC_LIST "testUHID.cpp")
endif ()
endif ()
endif ()

Expand Down
2 changes: 1 addition & 1 deletion tests/testCAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ TEST_CASE("C PS5 API", "[C-API]") {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
int num_nodes = 0;
auto nodes = inputtino_joypad_ps5_get_nodes(ps_pad, &num_nodes);
REQUIRE(num_nodes == 5);
REQUIRE(num_nodes >= 2);
REQUIRE_THAT(std::string(nodes[0]), Catch::Matchers::StartsWith("/dev/input/"));

{ // TODO: test that this actually work
Expand Down
Loading
Loading