Skip to content

Commit

Permalink
feat: implemented joypad trackpad support
Browse files Browse the repository at this point in the history
  • Loading branch information
ABeltramo committed Nov 6, 2023
1 parent 96913b5 commit ef37bfd
Show file tree
Hide file tree
Showing 9 changed files with 468 additions and 78 deletions.
30 changes: 30 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated from CLion Inspection settings
---
Checks: '-*,
bugprone-*,
mpi-*,
cert-err52-cpp,
cert-err60-cpp,
cert-err34-c,
cert-str34-c,
cert-dcl21-cpp,
cert-msc50-cpp,
cert-msc51-cpp,
cert-dcl58-cpp,
cert-flp30-c,
cppcoreguidelines-*,
-cppcoreguidelines-avoid-do-while,
google-default-arguments,
google-runtime-operator,
google-explicit-constructor,
hicpp-multiway-paths-covered,
hicpp-exception-baseclass,
misc-unconventional-assign-operator,
misc-misplaced-const,
misc-new-delete-overloads,
misc-non-copyable-objects,
misc-no-recursion,
misc-throw-by-value-catch-by-reference,
misc-uniqueptr-reset-release,
openmp-use-default-none,
portability-simd-intrinsics'
40 changes: 28 additions & 12 deletions src/core/src/core/input.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,12 @@ class Keyboard : public VirtualDevice {

public:
explicit Keyboard(std::chrono::milliseconds timeout_repress_key = 50ms);
Keyboard(const Keyboard &j) : _state(j._state) {}
Keyboard(Keyboard &&j) : _state(std::move(j._state)) {}

// A keyboard can't be copied, you can only create a new one
Keyboard(const Keyboard &j) = delete;
Keyboard(Keyboard &&j) = delete;
void operator=(const Keyboard &j) = delete;

~Keyboard() override;

std::vector<std::string> get_nodes() const override;
Expand Down Expand Up @@ -149,13 +153,17 @@ class Joypad : public VirtualDevice {
};

Joypad(CONTROLLER_TYPE type, uint8_t capabilities);
Joypad(const Joypad &j) : _state(j._state) {}
Joypad(Joypad &&j) : _state(std::move(j._state)) {}

// A joypad can't be copied, you can only create a new one
Joypad(const Joypad &j) = delete;
Joypad(Joypad &&j) = delete;
void operator=(const Joypad &j) = delete;

~Joypad() override;

std::vector<std::string> get_nodes() const override;

enum CONTROLLER_BTN : unsigned short {
enum CONTROLLER_BTN : int {
DPAD_UP = 0x0001,
DPAD_DOWN = 0x0002,
DPAD_LEFT = 0x0004,
Expand All @@ -171,12 +179,12 @@ class Joypad : public VirtualDevice {
RIGHT_BUTTON = 0x0200,

SPECIAL_FLAG = 0x0400,
PADDLE1_FLAG = 0x0100,
PADDLE2_FLAG = 0x0200,
PADDLE3_FLAG = 0x0400,
PADDLE4_FLAG = 0x0800,
TOUCHPAD_FLAG = 0x1000, // Touchpad buttons on Sony controllers
MISC_FLAG = 0x2000, // Share/Mic/Capture/Mute buttons on various controllers
PADDLE1_FLAG = 0x010000,
PADDLE2_FLAG = 0x020000,
PADDLE3_FLAG = 0x040000,
PADDLE4_FLAG = 0x080000,
TOUCHPAD_FLAG = 0x100000, // Touchpad buttons on Sony controllers
MISC_FLAG = 0x200000, // Share/Mic/Capture/Mute buttons on various controllers

A = 0x1000,
B = 0x2000,
Expand All @@ -193,7 +201,7 @@ class Joypad : public VirtualDevice {
*
* Example: previous state had `DPAD_UP` and `A` -> user release `A` -> new state only has `DPAD_UP`
*/
void set_pressed_buttons(short newly_pressed);
void set_pressed_buttons(int newly_pressed);

void set_triggers(unsigned char left, unsigned char right);

Expand All @@ -206,6 +214,14 @@ class Joypad : public VirtualDevice {

void set_on_rumble(const std::function<void(int low_freq, int high_freq)> &callback);

/**
* We expect (x,y) to be in the range [0.0, 1.0]; x and y values are normalised device coordinates
* from the top-left corner (0.0, 0.0) to bottom-right corner (1.0, 1.0)
*/
void touchpad_place_finger(int finger_nr, float x, float y);

void touchpad_release_finger(int finger_nr);

enum MOTION_TYPE : uint8_t {
ACCELERATION = 0x01,
GYROSCOPE = 0x02
Expand Down
15 changes: 15 additions & 0 deletions src/core/src/platforms/all/helpers/helpers/utils.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once
#include <algorithm>
#include <boost/endian.hpp>
#include <range/v3/view.hpp>
#include <sstream>
#include <stdlib.h>
Expand Down Expand Up @@ -65,4 +66,18 @@ template <class T> inline std::string join(const std::vector<T> &vec, std::strin
return vec | views::join(separator);
}

/**
* netfloat is just a little-endian float in byte form for network transmission.
*/
typedef uint8_t netfloat[4];

/**
* @brief Converts a little-endian netfloat to a native endianness float.
* @param f Netfloat value.
* @return Float value.
*/
inline float from_netfloat(netfloat f) {
return boost::endian::endian_load<float, sizeof(float), boost::endian::order::little>(f);
}

} // namespace utils
200 changes: 185 additions & 15 deletions src/core/src/platforms/linux/uinput/joypad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,29 @@ namespace wolf::core::input {

struct JoypadState {
libevdev_uinput_ptr joy = nullptr;
short currently_pressed_btns = 0;
int currently_pressed_btns = 0;

libevdev_uinput_ptr trackpad = nullptr;

/**
* Multi touch protocol type B is stateful; see: https://docs.kernel.org/input/multi-touch-protocol.html
* Slots are numbered starting from 0 up to <number of currently connected fingers> (max: 4)
*
* The way it works:
* - first time a new finger_id arrives we'll create a new slot and call MT_TRACKING_ID = slot_number
* - we can keep updating ABS_X and ABS_Y as long as the finger_id stays the same
* - if we want to update a different finger we'll have to call ABS_MT_SLOT = slot_number
* - when a finger is released we'll call ABS_MT_SLOT = slot_number && MT_TRACKING_ID = -1
*
* The other thing that needs to be kept in sync is the EV_KEY.
* EX: enabling BTN_TOOL_DOUBLETAP will result in scrolling instead of moving the mouse
*/
struct TrackpadState {
/* The MT_SLOT we are currently updating */
int current_slot = -1;
/* A map of finger_id to MT_SLOT */
std::map<int /* finger_id */, int /* MT_SLOT */> fingers;
} trackpad_state;

bool stop_listening_events = false;
std::thread events_thread;
Expand Down Expand Up @@ -55,13 +77,16 @@ std::vector<std::string> Joypad::get_nodes() const {
std::vector<std::string> nodes;

if (auto joy = _state->joy.get()) {
auto dev_node = libevdev_uinput_get_devnode(joy);
nodes.emplace_back(dev_node);
nodes.emplace_back(libevdev_uinput_get_devnode(joy));

auto additional_nodes = get_child_dev_nodes(joy);
nodes.insert(nodes.end(), additional_nodes.begin(), additional_nodes.end());
}

if (auto trackpad = _state->trackpad.get()) {
nodes.emplace_back(libevdev_uinput_get_devnode(trackpad));
}

return nodes;
}

Expand Down Expand Up @@ -153,6 +178,62 @@ std::optional<libevdev_uinput *> create_controller(Joypad::CONTROLLER_TYPE type,
return uidev;
}

constexpr int TOUCH_MAX_X = 1919;
constexpr int TOUCH_MAX_Y = 1079;

/**
* All values in here have been taken from a real PS5 gamepad using evemu-record
* You should read the kernel docs for this: https://docs.kernel.org/input/multi-touch-protocol.html
*/
std::optional<libevdev_uinput *> create_trackpad() {
libevdev *dev = libevdev_new();
libevdev_uinput *uidev;

libevdev_set_uniq(dev, "Wolf gamepad touchpad");
libevdev_set_name(dev, "Wolf gamepad (virtual) touchpad");
libevdev_set_id_version(dev, 0xAB00);

libevdev_set_id_product(dev, 0xce6);
libevdev_set_id_vendor(dev, 0x54c);
libevdev_set_id_bustype(dev, BUS_USB);

libevdev_enable_event_type(dev, EV_KEY);
libevdev_enable_event_code(dev, EV_KEY, BTN_LEFT, nullptr);
libevdev_enable_event_code(dev, EV_KEY, BTN_TOUCH, nullptr);
libevdev_enable_event_code(dev, EV_KEY, BTN_TOOL_FINGER, nullptr);
libevdev_enable_event_code(dev, EV_KEY, BTN_TOOL_DOUBLETAP, nullptr);
libevdev_enable_event_code(dev, EV_KEY, BTN_TOOL_TRIPLETAP, nullptr);
libevdev_enable_event_code(dev, EV_KEY, BTN_TOOL_QUADTAP, nullptr);

libevdev_enable_event_type(dev, EV_ABS);
input_absinfo mt_slot{0, 0, 4, 0, 0, 0}; // We only support up to 4 fingers
libevdev_enable_event_code(dev, EV_ABS, ABS_MT_SLOT, &mt_slot);

input_absinfo abs_x{0, 0, TOUCH_MAX_X, 0, 0, 0};
libevdev_enable_event_code(dev, EV_ABS, ABS_X, &abs_x);
libevdev_enable_event_code(dev, EV_ABS, ABS_MT_POSITION_X, &abs_x);

input_absinfo abs_y{0, 0, TOUCH_MAX_Y, 0, 0, 0};
libevdev_enable_event_code(dev, EV_ABS, ABS_Y, &abs_y);
libevdev_enable_event_code(dev, EV_ABS, ABS_MT_POSITION_Y, &abs_y);

input_absinfo tracking{0, 0, 65535, 0, 0, 0};
libevdev_enable_event_code(dev, EV_ABS, ABS_MT_TRACKING_ID, &tracking);

libevdev_enable_property(dev, INPUT_PROP_POINTER);
libevdev_enable_property(dev, INPUT_PROP_BUTTONPAD);

auto err = libevdev_uinput_create_from_device(dev, LIBEVDEV_UINPUT_OPEN_MANAGED, &uidev);
if (err != 0) {
logs::log(logs::error, "Unable to create joypad trackpad device, error code: {}", strerror(-err));
return {};
}

logs::log(logs::debug, "[INPUT] Created virtual controller touchpad {}", libevdev_uinput_get_devnode(uidev));

return uidev;
}

struct ActiveRumbleEffect {
int effect_id;

Expand Down Expand Up @@ -369,17 +450,8 @@ static void event_listener(const std::shared_ptr<JoypadState> &state) {
}
}

static void destroy_controller(JoypadState *state) {
state->stop_listening_events = true;
if (state->joy.get() != nullptr) {
state->events_thread.join();
}
free(state);
}

Joypad::Joypad(Joypad::CONTROLLER_TYPE type, uint8_t capabilities) {
auto state = new JoypadState;
this->_state = std::shared_ptr<JoypadState>(state, destroy_controller);
this->_state = std::make_shared<JoypadState>(JoypadState{});

if (auto joy_el = create_controller(type, capabilities)) {
this->_state->joy = {*joy_el, ::libevdev_uinput_destroy};
Expand All @@ -388,11 +460,22 @@ Joypad::Joypad(Joypad::CONTROLLER_TYPE type, uint8_t capabilities) {
this->_state->events_thread = std::move(event_thread);
this->_state->events_thread.detach();
}

if (capabilities & Joypad::TOUCHPAD) {
if (auto trackpad = create_trackpad()) {
this->_state->trackpad = {*trackpad, ::libevdev_uinput_destroy};
}
}
}

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

void Joypad::set_pressed_buttons(short newly_pressed) {
void Joypad::set_pressed_buttons(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
Expand Down Expand Up @@ -434,6 +517,13 @@ void Joypad::set_pressed_buttons(short newly_pressed) {
libevdev_uinput_write_event(controller, EV_KEY, BTN_NORTH, bf_new & X ? 1 : 0);
if (Y & bf_changed)
libevdev_uinput_write_event(controller, EV_KEY, BTN_WEST, bf_new & Y ? 1 : 0);

if (TOUCHPAD_FLAG & bf_changed) {
if (auto touchpad = this->_state->trackpad.get()) {
libevdev_uinput_write_event(touchpad, EV_KEY, BTN_LEFT, bf_new & TOUCHPAD_FLAG ? 1 : 0);
libevdev_uinput_write_event(touchpad, EV_SYN, SYN_REPORT, 0);
}
}
}

libevdev_uinput_write_event(controller, EV_SYN, SYN_REPORT, 0);
Expand Down Expand Up @@ -476,4 +566,84 @@ void Joypad::set_on_battery(const std::function<void(BATTERY_STATE, int)> &callb
this->_state->on_battery = callback;
};

void Joypad::touchpad_place_finger(int finger_nr, float x, float y) {
if (auto touchpad = this->_state->trackpad.get()) {
int scaled_x = (int)std::lround(TOUCH_MAX_X * x);
int scaled_y = (int)std::lround(TOUCH_MAX_Y * y);

if (_state->trackpad_state.fingers.find(finger_nr) == _state->trackpad_state.fingers.end()) {
// Wow, a wild finger appeared!
auto finger_slot = _state->trackpad_state.fingers.size();
_state->trackpad_state.fingers[finger_nr] = finger_slot;
libevdev_uinput_write_event(touchpad, EV_ABS, ABS_MT_SLOT, finger_slot);
libevdev_uinput_write_event(touchpad, EV_ABS, ABS_MT_TRACKING_ID, finger_slot);

{ // Update number of fingers pressed
if (_state->trackpad_state.fingers.size() == 1) {
libevdev_uinput_write_event(touchpad, EV_KEY, BTN_TOOL_FINGER, 1);
libevdev_uinput_write_event(touchpad, EV_KEY, BTN_TOUCH, 1);
} else if (_state->trackpad_state.fingers.size() == 2) {
libevdev_uinput_write_event(touchpad, EV_KEY, BTN_TOOL_FINGER, 0);
libevdev_uinput_write_event(touchpad, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
} else if (_state->trackpad_state.fingers.size() == 3) {
libevdev_uinput_write_event(touchpad, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
libevdev_uinput_write_event(touchpad, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
} else if (_state->trackpad_state.fingers.size() == 4) {
libevdev_uinput_write_event(touchpad, EV_KEY, BTN_TOOL_TRIPLETAP, 0);
libevdev_uinput_write_event(touchpad, EV_KEY, BTN_TOOL_QUADTAP, 1);
} else {
logs::log(logs::warning, "Joypad, exceeded max number of fingers of 4");
}
}
} else {
// I already know this finger, let's check the slot
auto finger_slot = _state->trackpad_state.fingers[finger_nr];
if (_state->trackpad_state.current_slot != finger_slot) {
libevdev_uinput_write_event(touchpad, EV_ABS, ABS_MT_SLOT, finger_slot);
_state->trackpad_state.current_slot = finger_slot;
}
}

libevdev_uinput_write_event(touchpad, EV_ABS, ABS_X, scaled_x);
libevdev_uinput_write_event(touchpad, EV_ABS, ABS_MT_POSITION_X, scaled_x);
libevdev_uinput_write_event(touchpad, EV_ABS, ABS_Y, scaled_y);
libevdev_uinput_write_event(touchpad, EV_ABS, ABS_MT_POSITION_Y, scaled_y);

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

void Joypad::touchpad_release_finger(int finger_nr) {
if (auto touchpad = this->_state->trackpad.get()) {
auto finger_slot = _state->trackpad_state.fingers[finger_nr];
if (_state->trackpad_state.current_slot != finger_slot) {
libevdev_uinput_write_event(touchpad, EV_ABS, ABS_MT_SLOT, finger_slot);
_state->trackpad_state.current_slot = -1;
}

_state->trackpad_state.fingers.erase(finger_nr);
libevdev_uinput_write_event(touchpad, EV_ABS, ABS_MT_TRACKING_ID, -1);

{ // Update number of fingers pressed
if (_state->trackpad_state.fingers.size() == 0) {
libevdev_uinput_write_event(touchpad, EV_KEY, BTN_TOOL_FINGER, 0);
libevdev_uinput_write_event(touchpad, EV_KEY, BTN_TOUCH, 0);
} else if (_state->trackpad_state.fingers.size() == 1) {
libevdev_uinput_write_event(touchpad, EV_KEY, BTN_TOOL_FINGER, 1);
libevdev_uinput_write_event(touchpad, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
} else if (_state->trackpad_state.fingers.size() == 2) {
libevdev_uinput_write_event(touchpad, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
libevdev_uinput_write_event(touchpad, EV_KEY, BTN_TOOL_TRIPLETAP, 0);
} else if (_state->trackpad_state.fingers.size() == 3) {
libevdev_uinput_write_event(touchpad, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
libevdev_uinput_write_event(touchpad, EV_KEY, BTN_TOOL_QUADTAP, 0);
} else {
logs::log(logs::warning, "Joypad, exceeded max number of fingers of 4");
}
}

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

} // namespace wolf::core::input
Loading

0 comments on commit ef37bfd

Please sign in to comment.