Skip to content

Commit

Permalink
fix: implemented get_nodes() for virtual PS5 joypad
Browse files Browse the repository at this point in the history
  • Loading branch information
ABeltramo committed May 19, 2024
1 parent 528bfed commit 15fa550
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 10 deletions.
2 changes: 1 addition & 1 deletion include/inputtino/input.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,6 @@ class PS5Joypad : public Joypad {
std::shared_ptr<PS5JoypadState> _state;

private:
PS5Joypad();
PS5Joypad(uint16_t vendor_id);
};
} // namespace inputtino
3 changes: 2 additions & 1 deletion src/c-bindings/joypad_ps5.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#include "helpers.hpp"
#include <inputtino/input.h>

InputtinoPS5Joypad *inputtino_joypad_ps5_create(const InputtinoDeviceDefinition *device, const InputtinoErrorHandler *eh) {
InputtinoPS5Joypad *inputtino_joypad_ps5_create(const InputtinoDeviceDefinition *device,
const InputtinoErrorHandler *eh) {
auto joypad_ = inputtino::PS5Joypad::create({
.name = device->name ? device->name : "Inputtino virtual device",
.vendor_id = device->vendor_id,
Expand Down
1 change: 1 addition & 0 deletions src/uhid/include/uhid/protected_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ struct PS5JoypadState {
unsigned char mac_address[6] = {
0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
};
uint16_t vendor_id;

uhid::dualsense_input_report_usb current_state;
uint8_t touch_points_ids[2] = {0};
Expand Down
78 changes: 73 additions & 5 deletions src/uhid/joypad_ps5.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#include <cmath>
#include <endian.h>
#include <filesystem>
#include <fstream>
#include <inputtino/input.hpp>
#include <iomanip>
#include <random>
#include <uhid/protected_types.hpp>
#include <uhid/ps5.hpp>
#include <uhid/uhid.hpp>
#include <random>

namespace inputtino {

Expand Down Expand Up @@ -115,8 +118,9 @@ void generate_mac_address(PS5JoypadState *state) {
}
}

PS5Joypad::PS5Joypad() : _state(std::make_shared<PS5JoypadState>()) {
PS5Joypad::PS5Joypad(uint16_t vendor_id) : _state(std::make_shared<PS5JoypadState>()) {
generate_mac_address(this->_state.get());
this->_state->vendor_id = vendor_id;
}

PS5Joypad::~PS5Joypad() {
Expand All @@ -138,7 +142,7 @@ Result<PS5Joypad> PS5Joypad::create(const DeviceDefinition &device) {
.country = 0,
.report_description = {&uhid::ps5_rdesc[0], &uhid::ps5_rdesc[0] + sizeof(uhid::ps5_rdesc)}};

auto joypad = PS5Joypad();
auto joypad = PS5Joypad(device.vendor_id);
auto dev =
uhid::Device::create(def, [state = joypad._state](uhid_event ev, int fd) { on_uhid_event(state, ev, fd); });
if (dev) {
Expand All @@ -153,9 +157,73 @@ static int scale_value(int input, int input_start, int input_end, int output_sta
return output_start + std::round(slope * (input - input_start));
}

template <typename T> std::string to_hex(T i) {
std::stringstream stream;
stream << std::hex << std::uppercase << i;
return stream.str();
}

std::string mac_to_str(const PS5JoypadState &state) {
std::stringstream stream;
stream << std::hex << (unsigned int)state.mac_address[0] << ":" << (unsigned int)state.mac_address[1] << ":"
<< (unsigned int)state.mac_address[2] << ":" << (unsigned int)state.mac_address[3] << ":"
<< (unsigned int)state.mac_address[4] << ":" << (unsigned int)state.mac_address[5];
return stream.str();
}

/**
* The trick here is to match the devices under /sys/devices/virtual/misc/uhid/
* with the MAC address that we've set for the current device
*/
std::vector<std::string> PS5Joypad::get_nodes() const {
// TODO
return std::vector<std::string>();
std::vector<std::string> nodes;
auto base_path = "/sys/devices/virtual/misc/uhid/";
auto target_mac = mac_to_str(*this->_state);
if (std::filesystem::exists(base_path)) {
auto uhid_entries = std::filesystem::directory_iterator{base_path};
for (auto uhid_entry : uhid_entries) {
// Here we are looking for a directory that has a name like {BUS_ID}:{VENDOR_ID}:{PRODUCT_ID}.xxxx
// (ex: 0003:054C:0CE6.000D)
auto uhid_candidate_path = uhid_entry.path().filename().string();
auto target_id = to_hex(this->_state->vendor_id);
if (uhid_entry.is_directory() && uhid_candidate_path.find(target_id) != std::string::npos) {
// Found a match! Let's scan the input devices in that directory
if (std::filesystem::exists(uhid_entry.path() / "input")) {
// ex: /sys/devices/virtual/misc/uhid/0003:054C:0CE6.000D/input/
auto dev_entries = std::filesystem::directory_iterator{uhid_entry.path() / "input"};
for (auto dev_entry : dev_entries) {
// Here we only have a match if the "uniq" file inside contains the same MAC address that we've set
if (dev_entry.is_directory()) {
// ex: /sys/devices/virtual/misc/uhid/0003:054C:0CE6.000D/input/input58/uniq
auto dev_uniq_path = dev_entry.path() / "uniq";
if (std::filesystem::exists(dev_uniq_path)) {
std::ifstream dev_uniq_file{dev_uniq_path};
std::string line;
std::getline(dev_uniq_file, line);
if (line.find(target_mac) != std::string::npos) {
// Found a match! Let's scan the folders for the corresponding event and js node
auto dev_nodes = std::filesystem::directory_iterator{dev_entry.path()};
for (auto dev_node : dev_nodes) {
if (dev_node.is_directory() && (dev_node.path().filename().string().rfind("event", 0) == 0 ||
dev_node.path().filename().string().rfind("js", 0) == 0)) {
nodes.push_back("/dev/input/" / dev_node.path().filename());
}
}
}
} else {
fprintf(stderr, "Unable to get joypad nodes, path %s does not exist\n", dev_uniq_path.string().c_str());
}
}
}
} else {
fprintf(stderr, "Unable to get joypad nodes, path %s does not exist\n", uhid_entry.path().string().c_str());
}
}
}
} else {
fprintf(stderr, "Unable to get joypad nodes, path %s does not exist\n", base_path);
}
return nodes;
}

void PS5Joypad::set_pressed_buttons(int pressed) {
Expand Down
8 changes: 6 additions & 2 deletions tests/testCAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,17 @@ TEST_CASE("C Switch API", "[C-API]") {
TEST_CASE("C PS5 API", "[C-API]") {
InputtinoErrorHandler error_handler = {.eh = [](const char *message, void *_data) { FAIL(message); },
.user_data = nullptr};
InputtinoDeviceDefinition def = {};
InputtinoDeviceDefinition def = {.name = "Wolf DualSense (virtual) pad",
.vendor_id = 0x054C,
.product_id = 0x0CE6,
.version = 0x8111};
auto ps_pad = inputtino_joypad_ps5_create(&def, &error_handler);
REQUIRE(ps_pad != nullptr);

int num_nodes = 0;
auto nodes = inputtino_joypad_ps5_get_nodes(ps_pad, &num_nodes);
REQUIRE(num_nodes == 0); // TODO: implement this!
REQUIRE(num_nodes == 5);
REQUIRE_THAT(std::string(nodes[0]), Catch::Matchers::StartsWith("/dev/input/"));

{ // TODO: test that this actually work
inputtino_joypad_ps5_set_pressed_buttons(ps_pad, INPUTTINO_JOYPAD_BTN::A | INPUTTINO_JOYPAD_BTN::B);
Expand Down
19 changes: 18 additions & 1 deletion tests/testJoypads.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
#include <SDL.h>
#include <thread>

using Catch::Matchers::Contains;
using Catch::Matchers::ContainsSubstring;
using Catch::Matchers::Equals;
using Catch::Matchers::SizeIs;
using Catch::Matchers::WithinAbs;
using namespace inputtino;
using namespace std::chrono_literals;
Expand Down Expand Up @@ -89,6 +92,11 @@ TEST_CASE_METHOD(SDLTestsFixture, "PS Joypad", "[SDL]") {

std::this_thread::sleep_for(250ms);

auto devices = joypad.get_nodes();
REQUIRE_THAT(devices, SizeIs(5)); // 3 eventXX and 2 jsYY
REQUIRE_THAT(devices, Contains(ContainsSubstring("/dev/input/event")));
REQUIRE_THAT(devices, Contains(ContainsSubstring("/dev/input/js")));

// TODO: seems that I can't force it to use HIDAPI, it's picking up sysjoystick which is lacking features
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5, "1");
Expand All @@ -103,7 +111,6 @@ TEST_CASE_METHOD(SDLTestsFixture, "PS Joypad", "[SDL]") {
REQUIRE(gc);

REQUIRE(SDL_GameControllerGetType(gc) == SDL_CONTROLLER_TYPE_PS5);

{ // Rumble
// Checking for basic capability
REQUIRE(SDL_GameControllerHasRumble(gc));
Expand Down Expand Up @@ -256,6 +263,11 @@ TEST_CASE_METHOD(SDLTestsFixture, "XBOX Joypad", "[SDL]") {

std::this_thread::sleep_for(150ms);

auto devices = joypad.get_nodes();
REQUIRE_THAT(devices, SizeIs(2)); // 1 eventXX and 1 jsYY
REQUIRE_THAT(devices, Contains(ContainsSubstring("/dev/input/event")));
REQUIRE_THAT(devices, Contains(ContainsSubstring("/dev/input/js")));

// Initializing the controller
flush_sdl_events();
SDL_GameController *gc = SDL_GameControllerOpen(0);
Expand Down Expand Up @@ -324,6 +336,11 @@ TEST_CASE_METHOD(SDLTestsFixture, "Nintendo Joypad", "[SDL]") {

std::this_thread::sleep_for(150ms);

auto devices = joypad.get_nodes();
REQUIRE_THAT(devices, SizeIs(2)); // 1 eventXX and 1 jsYY
REQUIRE_THAT(devices, Contains(ContainsSubstring("/dev/input/event")));
REQUIRE_THAT(devices, Contains(ContainsSubstring("/dev/input/js")));

// Initializing the controller
flush_sdl_events();
SDL_GameController *gc = SDL_GameControllerOpen(0);
Expand Down

0 comments on commit 15fa550

Please sign in to comment.