Skip to content

Commit

Permalink
feat: plugged fake udev implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
ABeltramo committed Oct 15, 2023
1 parent fdf86cf commit 0ebba42
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 55 deletions.
3 changes: 2 additions & 1 deletion src/core/src/core/input.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ class VirtualDevice {
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 std::vector<std::pair<std::string, /* filename */ std::vector<std::string> /* file rows */>>
get_udev_hw_db_entries() const = 0;

virtual ~VirtualDevice() = default;
};
Expand Down
4 changes: 2 additions & 2 deletions src/core/src/platforms/all/helpers/helpers/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ inline const char *get_env(const char *tag, const char *def = nullptr) noexcept
/**
* Join a list of strings into a single string with separator in between elements
*/
template <class T> inline std::string join(const std::vector<T> &vec, std::string_view separator) {
return vec | views::join(separator);
inline std::string join(const std::vector<std::string> &vec, std::string_view separator) {
return vec | view::join(separator) | to<std::string>();
}

/**
Expand Down
55 changes: 39 additions & 16 deletions src/core/src/platforms/linux/uinput/joypad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,21 +101,6 @@ 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);
Expand Down Expand Up @@ -158,7 +143,45 @@ std::vector<std::map<std::string, std::string>> Joypad::get_udev_events() const
}

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

if (_state->joy.get()) {
result.push_back({gen_udev_hw_db_filename(_state->joy),
{"E:ID_INPUT=1",
"E:ID_INPUT_JOYSTICK=1",
"E:ID_BUS=usb",
"G:seat",
"G:uaccess",
"Q:seat",
"Q:uaccess",
"V:1"}});
}

if (_state->trackpad.get()) {
result.push_back({gen_udev_hw_db_filename(_state->trackpad),
{"E:ID_INPUT=1",
"E:ID_INPUT_TOUCHPAD=1",
"E:ID_BUS=usb",
"G:seat",
"G:uaccess",
"Q:seat",
"Q:uaccess",
"V:1"}});
}

if (_state->motion_sensor.get()) {
result.push_back({gen_udev_hw_db_filename(_state->motion_sensor),
{"E:ID_INPUT=1",
"E:ID_INPUT_ACCELEROMETER=1",
"E:ID_BUS=usb",
"G:seat",
"G:uaccess",
"Q:seat",
"Q:uaccess",
"V:1"}});
}

return result;
}

static void set_controller_type(libevdev *dev, Joypad::CONTROLLER_TYPE type) {
Expand Down
10 changes: 9 additions & 1 deletion src/core/src/platforms/linux/uinput/keyboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,15 @@ std::vector<std::map<std::string, std::string>> Keyboard::get_udev_events() cons
}

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

if (_state->kb.get()) {
// TODO: E:XKBMODEL=pc105 E:XKBLAYOUT=gb
result.push_back({gen_udev_hw_db_filename(_state->kb),
{"E:ID_INPUT=1", "E:ID_INPUT_KEY=1", "E:ID_INPUT_KEYBOARD=1", "E:ID_SERIAL=noserial", "V:1"}});
}

return result;
}

static std::optional<libevdev_uinput *> create_keyboard(libevdev *dev) {
Expand Down
14 changes: 13 additions & 1 deletion src/core/src/platforms/linux/uinput/mouse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,19 @@ std::vector<std::map<std::string, std::string>> Mouse::get_udev_events() const {
}

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

if (_state->mouse_rel.get()) {
result.push_back({gen_udev_hw_db_filename(_state->mouse_rel),
{"E:ID_INPUT=1", "E:ID_INPUT_MOUSE=1", "E:ID_SERIAL=noserial", "V:1"}});
}

if (_state->mouse_abs.get()) {
result.push_back({gen_udev_hw_db_filename(_state->mouse_abs),
{"E:ID_INPUT=1", "E:ID_INPUT_TOUCHPAD=1", "E:ID_SERIAL=noserial", "V:1"}});
}

return result;
}

constexpr int ABS_MAX_WIDTH = 19200;
Expand Down
19 changes: 14 additions & 5 deletions src/core/src/platforms/linux/uinput/uinput.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,7 @@ 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
static std::pair<unsigned int, unsigned int> get_major_minor(const std::string &devnode) {
struct stat buf {};
if (stat(devnode.c_str(), &buf) == -1) {
logs::log(logs::warning, "Unable to get stats of {}", devnode);
Expand All @@ -87,8 +85,19 @@ gen_udev_base_event(const std::string &devnode, const std::string &syspath, cons
return {};
}

auto dev_major = major(buf.st_rdev);
auto dev_minor = minor(buf.st_rdev);
return {major(buf.st_rdev), minor(buf.st_rdev)};
}

static std::string gen_udev_hw_db_filename(libevdev_uinput_ptr node){
auto [dev_major, dev_minor] = get_major_minor(libevdev_uinput_get_devnode(node.get()));
auto filename = fmt::format("c{}:{}", dev_major, dev_minor);
return filename;
}

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
auto [dev_major, dev_minor] = get_major_minor(devnode);

// Current timestamp
auto now = std::chrono::system_clock::now();
Expand Down
4 changes: 4 additions & 0 deletions src/fake-udev/fake-udev-cli.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#include <fake-udev/fake-udev.hpp>
#include <filesystem>
#include <fstream>
#include <sys/stat.h>
#include <unistd.h>

constexpr int UDEV_EVENT_MODE = 2;
int main(int argc, char *argv[]) {
Expand Down
86 changes: 60 additions & 26 deletions src/moonlight-server/runners/docker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
#include <platforms/hw.hpp>
#include <range/v3/view.hpp>
#include <state/data-structures.hpp>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <utility>

namespace wolf::core::docker {
Expand Down Expand Up @@ -91,6 +89,8 @@ class RunDocker : public state::Runner {
}

void run(std::size_t session_id,
std::string_view app_state_folder,
std::shared_ptr<state::devices_atom_queue> plugged_devices_queue,
const immer::array<std::string> &virtual_inputs,
const immer::array<std::pair<std::string, std::string>> &paths,
const immer::map<std::string, std::string> &env_variables) override;
Expand Down Expand Up @@ -123,7 +123,19 @@ class RunDocker : public state::Runner {
docker::DockerAPI docker_api;
};

void create_udev_hw_files(std::filesystem::path base_hw_db_path, std::shared_ptr<input::VirtualDevice> device) {
for (const auto &[filename, content] : device->get_udev_hw_db_entries()) {
auto host_file_path = (base_hw_db_path / filename).string();
logs::log(logs::debug, "[DOCKER] Writing hwdb file: {}", host_file_path);
std::ofstream host_file(host_file_path);
host_file << utils::join(content, "\n");
host_file.close();
}
}

void RunDocker::run(std::size_t session_id,
std::string_view app_state_folder,
std::shared_ptr<state::devices_atom_queue> plugged_devices_queue,
const immer::array<std::string> &virtual_inputs,
const immer::array<std::pair<std::string, std::string>> &paths,
const immer::map<std::string, std::string> &env_variables) {
Expand All @@ -148,6 +160,25 @@ void RunDocker::run(std::size_t session_id,
mounts.insert(mounts.end(), MountPoint{.source = path.first, .destination = path.second, .mode = "rw"});
}

// Fake udev
auto udev_base_path = std::filesystem::path(app_state_folder) / "udev";
auto hw_db_path = udev_base_path / "data";
auto fake_udev_cli_path = std::string(utils::get_env("WOLF_DOCKER_FAKE_UDEV_PATH"));
bool use_fake_udev = !fake_udev_cli_path.empty();
if (use_fake_udev) {
std::filesystem::create_directories(hw_db_path);

// Check if /run/udev/control exists
auto udev_ctrl_path = udev_base_path / "control";
if (!std::filesystem::exists(udev_ctrl_path)) {
if (auto control_file = std::ofstream(udev_ctrl_path)) {
control_file.close();
std::filesystem::permissions(udev_ctrl_path, std::filesystem::perms::all); // set 777
}
}
mounts.push_back(MountPoint{.source = udev_base_path.string(), .destination = "/run/udev/", .mode = "rw"});
mounts.push_back(MountPoint{.source = fake_udev_cli_path, .destination = "/usr/bin/fake-udev", .mode = "ro"});
}
Container new_container = {.id = "",
.name = fmt::format("{}_{}", this->container.name, session_id),
.image = this->container.image,
Expand All @@ -171,32 +202,35 @@ void RunDocker::run(std::size_t session_id,
}
});

auto hotplug_handler = this->ev_bus->register_handler<immer::box<state::HotPlugDeviceEvent>>(
[session_id, container_id, this](const immer::box<state::HotPlugDeviceEvent> &hotplug_ev) {
if (hotplug_ev->session_id == session_id) {
logs::log(logs::debug, "{} received hot-plug device event", session_id);
for (auto udev_ev : hotplug_ev->device->get_udev_events()) {
std::string cmd;
std::string udev_msg = base64_encode(map_to_string(udev_ev));
if (udev_ev.count("DEVNAME") == 0) {
cmd = fmt::format("fake-udev -m {}", udev_msg);
} else {
cmd = fmt::format("mknod {} c {} {} && chmod 777 {} && fake-udev -m {}",
udev_ev["DEVNAME"],
udev_ev["MAJOR"],
udev_ev["MINOR"],
udev_ev["DEVNAME"],
udev_msg);
}
logs::log(logs::debug, "[DOCKER] Executing command: {}", cmd);
docker_api.exec(container_id, {"/bin/bash", "-c", cmd}, "root");
}
do {
// Plug all devices that are waiting in the queue
plugged_devices_queue->update([this, container_id, use_fake_udev, hw_db_path](const auto devices) {
for (const auto device : devices) {
if (use_fake_udev) {
create_udev_hw_files(hw_db_path, device);
}

// TODO: get_udev_hw_db_entries
for (auto udev_ev : device->get_udev_events()) {
std::string cmd;
std::string udev_msg = base64_encode(map_to_string(udev_ev));
if (udev_ev.count("DEVNAME") == 0) {
cmd = fmt::format("fake-udev -m {}", udev_msg);
} else {
cmd = fmt::format("mknod {} c {} {} && chmod 777 {} && fake-udev -m {}",
udev_ev["DEVNAME"],
udev_ev["MAJOR"],
udev_ev["MINOR"],
udev_ev["DEVNAME"],
udev_msg);
}
logs::log(logs::debug, "[DOCKER] Executing command: {}", cmd);
docker_api.exec(container_id, {"/bin/bash", "-c", cmd}, "root");
}
});
}

do {
// Remove all devices that we have plugged
return immer::vector<std::shared_ptr<input::VirtualDevice>>{};
});
boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
} while (docker_api.get_by_id(container_id)->status == RUNNING);

Expand All @@ -209,8 +243,8 @@ void RunDocker::run(std::size_t session_id,
}
}
logs::log(logs::info, "Stopped container: {}", docker_container->name);
std::filesystem::remove_all(udev_base_path);
terminate_handler.unregister();
hotplug_handler.unregister();
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/moonlight-server/runners/process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ namespace process {
using namespace moonlight::control;

void RunProcess::run(std::size_t session_id,
std::string_view app_state_folder,
std::shared_ptr<state::devices_atom_queue> plugged_devices_queue,
const immer::array<std::string> &virtual_inputs,
const immer::array<std::pair<std::string, std::string>> &paths,
const immer::map<std::string, std::string> &env_variables) {
Expand Down
2 changes: 2 additions & 0 deletions src/moonlight-server/runners/process.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class RunProcess : public state::Runner {
: run_cmd(std::move(run_cmd)), ev_bus(std::move(ev_bus)) {}

void run(std::size_t session_id,
std::string_view app_state_folder,
std::shared_ptr<state::devices_atom_queue> plugged_devices_queue,
const immer::array<std::string> &virtual_inputs,
const immer::array<std::pair<std::string, std::string>> &paths,
const immer::map<std::string, std::string> &env_variables) override;
Expand Down
4 changes: 4 additions & 0 deletions src/moonlight-server/state/data-structures.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ namespace state {
using namespace std::chrono_literals;
using namespace wolf::core;
namespace ba = boost::asio;
using devices_atom_queue = immer::atom<immer::vector<std::shared_ptr<input::VirtualDevice>>>;


struct Runner {

virtual void run(std::size_t session_id,
std::string_view app_state_folder,
std::shared_ptr<devices_atom_queue> plugged_devices_queue,
const immer::array<std::string> &virtual_inputs,
const immer::array<std::pair<std::string, std::string>> &paths,
const immer::map<std::string, std::string> &env_variables) = 0;
Expand Down
Loading

0 comments on commit 0ebba42

Please sign in to comment.