Skip to content

Commit

Permalink
feat: added support for hot-plugging controllers and partial fake ude…
Browse files Browse the repository at this point in the history
…v implementation
  • Loading branch information
ABeltramo committed Sep 28, 2023
1 parent f0d9f1c commit d1f1794
Show file tree
Hide file tree
Showing 21 changed files with 756 additions and 33 deletions.
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ include(TargetLinkLibrariesSystem)

add_subdirectory(src/core)

if (UNIX AND NOT APPLE)
add_subdirectory(src/fake-udev)
endif ()

option(BUILD_MOONLIGHT "Build Moonlight server" ON)
if (BUILD_MOONLIGHT)
add_subdirectory(src/moonlight-protocol)
Expand Down
17 changes: 17 additions & 0 deletions src/core/src/core/input.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
#include <cstdint>
#include <immer/array.hpp>
#include <immer/box.hpp>
#include <map>
#include <optional>
#include <thread>
#include <vector>

namespace wolf::core::input {

Expand All @@ -16,6 +18,10 @@ using namespace std::chrono_literals;
class VirtualDevice {
public:
virtual std::vector<std::string> get_nodes() const = 0;

virtual std::vector<std::map<std::string, std::string>> get_udev_events() const = 0;
virtual std::vector<std::pair<std::string, std::vector<std::string>>> get_udev_hw_db_entries() const = 0;

virtual ~VirtualDevice() = default;
};

Expand All @@ -37,6 +43,9 @@ class Mouse : public VirtualDevice {

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

std::vector<std::map<std::string, std::string>> get_udev_events() const override;
std::vector<std::pair<std::string, std::vector<std::string>>> get_udev_hw_db_entries() const override;

void move(int delta_x, int delta_y);

void move_abs(int x, int y, int screen_width, int screen_height);
Expand Down Expand Up @@ -104,6 +113,10 @@ class Keyboard : public VirtualDevice {

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

std::vector<std::map<std::string, std::string>> get_udev_events() const override;
std::vector<std::pair<std::string, std::vector<std::string>>> get_udev_hw_db_entries() const override;


void press(short key_code);

void release(short key_code);
Expand Down Expand Up @@ -163,6 +176,10 @@ class Joypad : public VirtualDevice {

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

std::vector<std::map<std::string, std::string>> get_udev_events() const override;
std::vector<std::pair<std::string, std::vector<std::string>>> get_udev_hw_db_entries() const override;


enum CONTROLLER_BTN : int {
DPAD_UP = 0x0001,
DPAD_DOWN = 0x0002,
Expand Down
31 changes: 31 additions & 0 deletions src/core/src/platforms/all/helpers/helpers/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,35 @@ inline float from_netfloat(netfloat f) {
return boost::endian::endian_load<float, sizeof(float), boost::endian::order::little>(f);
}

inline std::string base64_encode(const std::string &in) {
std::string out;

int val = 0, valb = -6;
for (unsigned char c : in) {
val = (val << 8) + c;
valb += 8;
while (valb >= 0) {
out.push_back("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(val >> valb) & 0x3F]);
valb -= 6;
}
}
if (valb > -6)
out.push_back(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[((val << 8) >> (valb + 8)) & 0x3F]);
while (out.size() % 4)
out.push_back('=');
return out;
}

inline std::string
map_to_string(const std::map<std::string, std::string> &m, char val_separator = '=', char row_separator = '\0') {
std::stringstream ss;

for (auto it = m.cbegin(); it != m.cend(); it++) {
ss << it->first << val_separator << it->second << row_separator;
}

return ss.str();
}

} // namespace utils
74 changes: 69 additions & 5 deletions src/core/src/platforms/linux/uinput/joypad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,21 +80,85 @@ std::vector<std::string> Joypad::get_nodes() const {
std::vector<std::string> nodes;

if (auto joy = _state->joy.get()) {
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()) {
auto additional_nodes = get_child_dev_nodes(trackpad);
nodes.insert(nodes.end(), additional_nodes.begin(), additional_nodes.end());
}

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

return nodes;
}

std::vector<std::map<std::string, std::string>> Joypad::get_udev_events() const {
std::vector<std::map<std::string, std::string>> events;

if (auto joy = _state->joy.get()) {
// Udev sends an additional event for the base /sys/ device
// I don't think it's used, so I've commented it for now
// auto device_event = gen_udev_base_device_event(_state->joy);
// device_event["ID_INPUT_JOYSTICK"] = "1";
// device_event[".INPUT_CLASS"] = "joystick";
// device_event["MODALIAS"] =
// "input:b0003v054Cp0CE6eAB00-e0,1,3,15,k130,131,133,134,136,137,13A,13B,13C,13D,13E,ra0,"
// "1,2,3,4,5,10,11,mlsf50,51,52,57,5A,60,w";
// device_event["FF"] = "104870000 0";
// device_event["ABS"] = "3003f";
// device_event["PROP"] = "0";
// device_event["NAME"] = "\"Wolf PS5 (virtual) pad\"";
// device_event["PRODUCT"] = "3/54c/ce6/ab00";
// events.emplace_back(device_event);

// eventXY and jsX devices
for (const auto &devnode : get_child_dev_nodes(joy)) {
std::string syspath = libevdev_uinput_get_syspath(joy);
syspath.erase(0, 4); // Remove leading /sys/ from syspath TODO: what if it's not /sys/?
syspath.append("/" + std::filesystem::path(devnode).filename().string()); // Adds /jsX

auto event = gen_udev_base_event(devnode, syspath);
event["ID_INPUT_JOYSTICK"] = "1";
event[".INPUT_CLASS"] = "joystick";
events.emplace_back(event);
}
}

if (auto trackpad = _state->trackpad.get()) {
nodes.emplace_back(libevdev_uinput_get_devnode(trackpad));
for (const auto &devnode : get_child_dev_nodes(trackpad)) {
std::string syspath = libevdev_uinput_get_syspath(trackpad);
syspath.erase(0, 4); // Remove leading /sys/ from syspath TODO: what if it's not /sys/?
syspath.append("/" + std::filesystem::path(devnode).filename().string()); // Adds /jsX

auto event = gen_udev_base_event(devnode, syspath);
event["ID_INPUT_TOUCHPAD"] = "1";
event[".INPUT_CLASS"] = "mouse";
events.emplace_back(event);
}
}

return nodes;
if (auto motion_sensor = _state->motion_sensor.get()) {
for (const auto &devnode : get_child_dev_nodes(motion_sensor)) {
std::string syspath = libevdev_uinput_get_syspath(motion_sensor);
syspath.erase(0, 4); // Remove leading /sys/ from syspath TODO: what if it's not /sys/?
syspath.append("/" + std::filesystem::path(devnode).filename().string()); // Adds /jsX

auto event = gen_udev_base_event(devnode, syspath);
event["ID_INPUT_ACCELEROMETER"] = "1";
events.emplace_back(event);
}
}

return events;
}

std::vector<std::pair<std::string, std::vector<std::string>>> Joypad::get_udev_hw_db_entries() const {
return {};
}

static void set_controller_type(libevdev *dev, Joypad::CONTROLLER_TYPE type) {
Expand Down
17 changes: 17 additions & 0 deletions src/core/src/platforms/linux/uinput/keyboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ std::vector<std::string> Keyboard::get_nodes() const {
return nodes;
}

std::vector<std::map<std::string, std::string>> Keyboard::get_udev_events() const {
std::vector<std::map<std::string, std::string>> events;

if (_state->kb.get()) {
auto event = gen_udev_base_event(_state->kb);
event["ID_INPUT_KEYBOARD"] = "1";
event[".INPUT_CLASS"] = "keyboard";
events.emplace_back(event);
}

return events;
}

std::vector<std::pair<std::string, std::vector<std::string>>> Keyboard::get_udev_hw_db_entries() const {
return {};
}

static std::optional<libevdev_uinput *> create_keyboard(libevdev *dev) {
libevdev_uinput *uidev;

Expand Down
24 changes: 24 additions & 0 deletions src/core/src/platforms/linux/uinput/mouse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,30 @@ std::vector<std::string> Mouse::get_nodes() const {
return nodes;
}

std::vector<std::map<std::string, std::string>> Mouse::get_udev_events() const {
std::vector<std::map<std::string, std::string>> events;

if(_state->mouse_rel.get()){
auto base = gen_udev_base_event(_state->mouse_rel);
base["ID_INPUT_MOUSE"] = "1";
base[".INPUT_CLASS"] = "mouse";
events.emplace_back(std::move(base));
}

if(_state->mouse_abs.get()){
auto base = gen_udev_base_event(_state->mouse_abs);
base["ID_INPUT_TOUCHPAD"] = "1";
base[".INPUT_CLASS"] = "mouse";
events.emplace_back(std::move(base));
}

return events;
}

std::vector<std::pair<std::string, std::vector<std::string>>> Mouse::get_udev_hw_db_entries() const {
return {};
}

constexpr int ABS_MAX_WIDTH = 19200;
constexpr int ABS_MAX_HEIGHT = 12000;

Expand Down
73 changes: 73 additions & 0 deletions src/core/src/platforms/linux/uinput/uinput.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
* For force feedback see: https://www.kernel.org/doc/html/latest/input/ff.html
*/

#include <chrono>
#include <core/input.hpp>
#include <filesystem>
#include <helpers/logger.hpp>
#include <immer/array.hpp>
#include <immer/atom.hpp>
#include <iomanip>
Expand All @@ -29,6 +32,8 @@
#include <memory>
#include <optional>
#include <string>
#include <sys/stat.h>
#include <sys/sysmacros.h>

namespace wolf::core::input {

Expand Down Expand Up @@ -68,4 +73,72 @@ static std::string to_hex(const std::basic_string<char32_t> &str) {
return hex_unicode;
}

static std::map<std::string, std::string>
gen_udev_base_event(const std::string &devnode, const std::string &syspath, const std::string &action = "add") {
// Get major:minor
struct stat buf {};
if (stat(devnode.c_str(), &buf) == -1) {
logs::log(logs::warning, "Unable to get stats of {}", devnode);
return {};
}

if (!S_ISCHR(buf.st_mode)) {
logs::log(logs::warning, "Device {} is not a character device", devnode);
return {};
}

auto dev_major = major(buf.st_rdev);
auto dev_minor = minor(buf.st_rdev);

// Current timestamp
auto now = std::chrono::system_clock::now();
auto timestamp = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
return {
{"ACTION", action},
{"SEQNUM", "7"}, // We don't want to keep global state, let's hope it's not used
{"USEC_INITIALIZED", std::to_string(timestamp)},
{"SUBSYSTEM", "input"},
{"ID_INPUT", "1"},
{"ID_SERIAL", "noserial"},
{"TAGS", ":seat:uaccess:"},
{"CURRENT_TAGS", ":seat:uaccess:"},
{"DEVNAME", devnode},
{"DEVPATH", syspath},
{"MAJOR", std::to_string(dev_major)},
{"MINOR", std::to_string(dev_minor)},
};
}

static std::map<std::string, std::string> gen_udev_base_event(libevdev_uinput_ptr node,
const std::string &action = "add") {

// Get paths
auto devnode = libevdev_uinput_get_devnode(node.get());
std::string syspath = libevdev_uinput_get_syspath(node.get());
syspath.erase(0, 4); // Remove leading /sys/ from syspath TODO: what if it's not /sys/?
syspath.append("/" + std::filesystem::path(devnode).filename().string()); // Adds /eventXY

return gen_udev_base_event(devnode, syspath, action);
}

static std::map<std::string, std::string> gen_udev_base_device_event(libevdev_uinput_ptr node,
const std::string &action = "add") {
std::string syspath = libevdev_uinput_get_syspath(node.get());
syspath.erase(0, 4); // Remove leading /sys/ from syspath TODO: what if it's not /sys/?
// Current timestamp
auto now = std::chrono::system_clock::now();
auto timestamp = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
return {
{"ACTION", action},
{"SEQNUM", "7"}, // We don't want to keep global state, let's hope it's not used
{"USEC_INITIALIZED", std::to_string(timestamp)},
{"SUBSYSTEM", "input"},
{"ID_INPUT", "1"},
{"ID_SERIAL", "noserial"},
{"TAGS", ":seat:uaccess:"},
{"CURRENT_TAGS", ":seat:uaccess:"},
{"DEVPATH", syspath},
};
}

} // namespace wolf::core::input
18 changes: 18 additions & 0 deletions src/fake-udev/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
option(BUILD_FAKE_UDEV_CLI "Build fake-udev CLI executable" ON)
if (BUILD_FAKE_UDEV_CLI)
message(STATUS "Building fake-udev CLI tool")

add_library(fake_udev_lib STATIC)
add_library(fake-udev::lib ALIAS fake_udev_lib)
target_include_directories(fake_udev_lib PUBLIC .)
target_sources(fake_udev_lib
PUBLIC fake-udev/fake-udev.hpp fake-udev/MurmurHash2.h
PRIVATE fake-udev/MurmurHash2.cpp
)
target_compile_features(fake_udev_lib PRIVATE cxx_std_17)

# Executable
add_executable(fake-udev fake-udev-cli.cpp)
target_link_libraries(fake-udev fake-udev::lib -static)
target_compile_features(fake-udev PRIVATE cxx_std_17)
endif ()
Loading

0 comments on commit d1f1794

Please sign in to comment.