Skip to content

Commit

Permalink
fix: co-op added mouse, keyboard and support for all virtual devices
Browse files Browse the repository at this point in the history
  • Loading branch information
ABeltramo committed Oct 10, 2024
1 parent c9c375a commit d12ab4d
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 74 deletions.
7 changes: 5 additions & 2 deletions src/moonlight-server/api/endpoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ void UnixSocketServer::endpoint_AddApp(const HTTPRequest &req, std::shared_ptr<U
auto app = rfl::json::read<rfl::Reflector<wolf::core::events::App>::ReflType>(req.body);
if (app) {
state_->app_state->config->apps->update([app = app.value(), this](auto &apps) {
auto runner = state::get_runner(app.runner, this->state_->app_state->event_bus);
auto runner =
state::get_runner(app.runner, this->state_->app_state->event_bus, this->state_->app_state->running_sessions);
return apps.push_back(events::App{
.base =
{.title = app.title, .id = app.id, .support_hdr = app.support_hdr, .icon_png_path = app.icon_png_path},
Expand Down Expand Up @@ -214,7 +215,9 @@ void UnixSocketServer::endpoint_RunnerStart(const wolf::api::HTTPRequest &req, s
return;
}

auto runner = state::get_runner(event.value().runner, this->state_->app_state->event_bus);
auto runner = state::get_runner(event.value().runner,
this->state_->app_state->event_bus,
this->state_->app_state->running_sessions);
state_->app_state->event_bus->fire_event(immer::box<events::StartRunner>(
events::StartRunner{.stop_stream_when_over = event.value().stop_stream_when_over,
.runner = runner,
Expand Down
92 changes: 92 additions & 0 deletions src/moonlight-server/runners/child_session.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#include <chrono>
#include <core/virtual-display.hpp>
#include <runners/child_session.hpp>
#include <state/sessions.hpp>

namespace wolf::core::coop {

using namespace wolf::core;
using namespace std::chrono_literals;

void RunChildSession::run(std::size_t session_id,
std::string_view app_state_folder,
std::shared_ptr<events::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,
std::string_view render_node) {

auto child_session = state::get_session_by_id(running_sessions->load(), session_id);
auto parent_session = state::get_session_by_id(running_sessions->load(), parent_session_id);

if (!child_session.has_value() || !parent_session.has_value()) {
logs::log(logs::error, "Unable to run child session, could not find parent or child session");
return;
}

/* inherit the wayland connection, needed in order to advertise some devices (ex: PS5 trackpad) */
if (auto wl = *parent_session->wayland_display->load()) {
child_session->wayland_display = parent_session->wayland_display;

/* Add mouse and keyboards to our wayland display */
if (child_session->mouse->has_value()) {
if (auto mouse = std::get_if<input::Mouse>(&child_session->mouse->value())) {
for (auto path : mouse->get_nodes()) {
add_input_device(*wl, path);
}
}
}

if (child_session->keyboard->has_value()) {
if (auto kb = std::get_if<input::Keyboard>(&child_session->keyboard->value())) {
for (auto path : kb->get_nodes()) {
add_input_device(*wl, path);
}
}
}
}

/* Keep a history of plugged devices so that we can clean up when over */
std::vector<immer::box<events::PlugDeviceEvent>> plugged_devices = {};
/* true when this session should quit */
std::shared_ptr<std::atomic_bool> is_over = std::make_shared<std::atomic<bool>>(false);

auto stop_handler = ev_bus->register_handler<immer::box<events::StopStreamEvent>>(
[session_id, parent_session_id = parent_session_id, is_over](
const immer::box<events::StopStreamEvent> &terminate_ev) {
if (terminate_ev->session_id == session_id || terminate_ev->session_id == parent_session_id) {
*is_over = true;
}
});

auto unplug_handler = ev_bus->register_handler<immer::box<events::UnplugDeviceEvent>>(
[session_id, parent_id = parent_session_id, ev_bus = ev_bus](const immer::box<events::UnplugDeviceEvent> &ev) {
if (ev->session_id == session_id) {
events::UnplugDeviceEvent unplug_ev = *ev;
unplug_ev.session_id = parent_id;
ev_bus->fire_event(immer::box<events::UnplugDeviceEvent>(unplug_ev));
}
});

while (!*is_over) {
while (auto device_ev = plugged_devices_queue->pop(500ms)) {
if (device_ev->get().session_id == session_id) {
events::PlugDeviceEvent plug_ev = device_ev->get();
plug_ev.session_id = parent_session_id;
ev_bus->fire_event(immer::box<events::PlugDeviceEvent>(plug_ev));
plugged_devices.push_back(plug_ev);
}
}
}

// This child session is over, unplug all devices that we've plugged
for (const auto &device_ev : plugged_devices) {
events::UnplugDeviceEvent unplug_ev;
unplug_ev.session_id = parent_session_id;
unplug_ev.udev_hw_db_entries = device_ev->udev_hw_db_entries;
unplug_ev.udev_events = device_ev->udev_events;
ev_bus->fire_event(immer::box<events::UnplugDeviceEvent>(unplug_ev));
}
}

} // namespace wolf::core::coop
59 changes: 9 additions & 50 deletions src/moonlight-server/runners/child_session.hpp
Original file line number Diff line number Diff line change
@@ -1,71 +1,29 @@
#pragma once

#include <chrono>
#include <events/events.hpp>
#include <state/data-structures.hpp>

namespace wolf::core::coop {

using namespace wolf::core;
using namespace std::chrono_literals;

/**
* A child session will just forward all the events to the parent session.
* It's used to implement co-op sessions where a different client connects to another active session
*/
class RunChildSession : public events::Runner {
public:
RunChildSession(std::size_t parent_session_id, std::shared_ptr<events::EventBusType> ev_bus)
: ev_bus(std::move(ev_bus)), parent_session_id(parent_session_id){};
RunChildSession(std::size_t parent_session_id,
std::shared_ptr<events::EventBusType> ev_bus,
state::SessionsAtoms running_sessions)
: ev_bus(std::move(ev_bus)), parent_session_id(parent_session_id),
running_sessions(std::move(running_sessions)) {};

void run(std::size_t session_id,
std::string_view app_state_folder,
std::shared_ptr<events::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,
std::string_view render_node) override {
/* Keep a history of plugged devices so that we can clean up when over */
std::vector<immer::box<events::PlugDeviceEvent>> plugged_devices = {};
/* true when this session should quit */
std::shared_ptr<std::atomic_bool> is_over = std::make_shared<std::atomic<bool>>(false);

auto stop_handler = ev_bus->register_handler<immer::box<events::StopStreamEvent>>(
[session_id, parent_session_id = parent_session_id, is_over](
const immer::box<events::StopStreamEvent> &terminate_ev) {
if (terminate_ev->session_id == session_id || terminate_ev->session_id == parent_session_id) {
*is_over = true;
}
});

auto unplug_handler = ev_bus->register_handler<immer::box<events::UnplugDeviceEvent>>(
[session_id, parent_id = parent_session_id, ev_bus = ev_bus](const immer::box<events::UnplugDeviceEvent> &ev) {
if (ev->session_id == session_id) {
events::UnplugDeviceEvent unplug_ev = *ev;
unplug_ev.session_id = parent_id;
ev_bus->fire_event(immer::box<events::UnplugDeviceEvent>(unplug_ev));
}
});

while (!*is_over) {
while (auto device_ev = plugged_devices_queue->pop(500ms)) {
if (device_ev->get().session_id == session_id) {
events::PlugDeviceEvent plug_ev = device_ev->get();
plug_ev.session_id = parent_session_id;
ev_bus->fire_event(immer::box<events::PlugDeviceEvent>(plug_ev));
plugged_devices.push_back(plug_ev);
}
}
}

// This child session is over, unplug all devices that we've plugged
for (const auto &device_ev : plugged_devices) {
events::UnplugDeviceEvent unplug_ev;
unplug_ev.session_id = parent_session_id;
unplug_ev.udev_hw_db_entries = device_ev->udev_hw_db_entries;
unplug_ev.udev_events = device_ev->udev_events;
ev_bus->fire_event(immer::box<events::UnplugDeviceEvent>(unplug_ev));
}
}
std::string_view render_node) override;

rfl::TaggedUnion<"type", wolf::config::AppCMD, wolf::config::AppDocker, wolf::config::AppChildSession>
serialize() override {
Expand All @@ -75,6 +33,7 @@ class RunChildSession : public events::Runner {
private:
std::shared_ptr<events::EventBusType> ev_bus;
std::size_t parent_session_id;
state::SessionsAtoms running_sessions;
};

} // namespace wolf::core::coop
} // namespace wolf::core::coop
7 changes: 4 additions & 3 deletions src/moonlight-server/state/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ using namespace wolf::config;
*
* If the source is not present, it'll provide some sensible defaults
*/
Config load_or_default(const std::string &source, const std::shared_ptr<events::EventBusType> &ev_bus);
Config load_or_default(const std::string &source, const std::shared_ptr<events::EventBusType> &ev_bus, state::SessionsAtoms running_sessions);

/**
* Side effect, will atomically update the paired clients list in cfg
Expand Down Expand Up @@ -106,7 +106,8 @@ inline std::string gen_uuid() {

static std::shared_ptr<events::Runner>
get_runner(const rfl::TaggedUnion<"type", AppCMD, AppDocker, AppChildSession> &runner,
const std::shared_ptr<events::EventBusType> &ev_bus) {
const std::shared_ptr<events::EventBusType> &ev_bus,
state::SessionsAtoms running_sessions) {
if (rfl::holds_alternative<AppCMD>(runner.variant())) {
auto run_cmd = rfl::get<AppCMD>(runner.variant()).run_cmd;
return std::make_shared<process::RunProcess>(ev_bus, run_cmd);
Expand All @@ -115,7 +116,7 @@ get_runner(const rfl::TaggedUnion<"type", AppCMD, AppDocker, AppChildSession> &r
docker::RunDocker::from_cfg(ev_bus, rfl::get<AppDocker>(runner.variant())));
} else if (rfl::holds_alternative<AppChildSession>(runner.variant())) {
auto session_id = rfl::get<AppChildSession>(runner.variant()).parent_session_id;
return std::make_shared<coop::RunChildSession>(std::stoul(session_id), ev_bus);
return std::make_shared<coop::RunChildSession>(std::stoul(session_id), ev_bus, running_sessions);
} else {
logs::log(logs::error, "Found runner of unknown type");
throw std::runtime_error("Unknown runner type");
Expand Down
6 changes: 4 additions & 2 deletions src/moonlight-server/state/configTOML.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ get_encoder(std::string_view tech, const std::vector<GstEncoder> &encoders, cons
return std::nullopt;
}

Config load_or_default(const std::string &source, const std::shared_ptr<events::EventBusType> &ev_bus) {
Config load_or_default(const std::string &source,
const std::shared_ptr<events::EventBusType> &ev_bus,
state::SessionsAtoms running_sessions) {
if (!file_exist(source)) {
logs::log(logs::warning, "Unable to open config file: {}, creating one using defaults", source);
create_default(source);
Expand Down Expand Up @@ -233,7 +235,7 @@ Config load_or_default(const std::string &source, const std::shared_ptr<events::

.opus_gst_pipeline = opus_gst_pipeline,
.start_virtual_compositor = app.start_virtual_compositor.value_or(true),
.runner = get_runner(app.runner, ev_bus),
.runner = get_runner(app.runner, ev_bus, running_sessions),
.joypad_type = get_controller_type(app.joypad_type.value_or(ControllerType::AUTO))}};
}) | //
ranges::to<immer::vector<immer::box<events::App>>>(); //
Expand Down
11 changes: 7 additions & 4 deletions src/moonlight-server/wolf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ static constexpr int DEFAULT_SESSION_TIMEOUT_MILLIS = 4000;
/**
* @brief Will try to load the config file and fallback to defaults
*/
auto load_config(std::string_view config_file, const std::shared_ptr<events::EventBusType> &ev_bus) {
auto load_config(std::string_view config_file,
const std::shared_ptr<events::EventBusType> &ev_bus,
state::SessionsAtoms running_sessions) {
logs::log(logs::info, "Reading config file from: {}", config_file);
return state::load_or_default(config_file.data(), ev_bus);
return state::load_or_default(config_file.data(), ev_bus, running_sessions);
}

state::Host get_host_config(std::string_view pkey_filename, std::string_view cert_filename) {
Expand Down Expand Up @@ -73,7 +75,8 @@ state::Host get_host_config(std::string_view pkey_filename, std::string_view cer
*/
auto initialize(std::string_view config_file, std::string_view pkey_filename, std::string_view cert_filename) {
auto event_bus = std::make_shared<events::EventBusType>();
auto config = load_config(config_file, event_bus);
auto running_sessions = std::make_shared<immer::atom<immer::vector<events::StreamSession>>>();
auto config = load_config(config_file, event_bus, running_sessions);

auto host = get_host_config(pkey_filename, cert_filename);
auto state = state::AppState{
Expand All @@ -82,7 +85,7 @@ auto initialize(std::string_view config_file, std::string_view pkey_filename, st
.pairing_cache = std::make_shared<immer::atom<immer::map<std::string, state::PairCache>>>(),
.pairing_atom = std::make_shared<immer::atom<immer::map<std::string, immer::box<events::PairSignal>>>>(),
.event_bus = event_bus,
.running_sessions = std::make_shared<immer::atom<immer::vector<events::StreamSession>>>()};
.running_sessions = running_sessions};
return immer::box<state::AppState>(state);
}

Expand Down
3 changes: 2 additions & 1 deletion tests/docker/testDocker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ TEST_CASE("Docker TOML", "DOCKER") {
docker::DockerAPI docker_api;

auto event_bus = std::make_shared<events::EventBusType>();
auto running_sessions = std::make_shared<immer::atom<immer::vector<events::StreamSession>>>();
std::string toml_cfg = R"(
type = "docker"
Expand All @@ -86,7 +87,7 @@ TEST_CASE("Docker TOML", "DOCKER") {
)";
std::istringstream is(toml_cfg, std::ios_base::binary | std::ios_base::in);
// Round trip: load TOML -> serialize back
auto runner = state::get_runner(rfl::toml::read<wolf::config::AppDocker>(is).value(), event_bus);
auto runner = state::get_runner(rfl::toml::read<wolf::config::AppDocker>(is).value(), event_bus, running_sessions);
auto container = rfl::get<wolf::config::AppDocker>(runner->serialize().variant());

REQUIRE_THAT(container.name, Equals("WolfTestHelloWorld"));
Expand Down
12 changes: 8 additions & 4 deletions tests/testMoonlight.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ using namespace ranges;

TEST_CASE("LocalState load TOML", "[LocalState]") {
auto event_bus = std::make_shared<events::EventBusType>();
auto state = state::load_or_default("config.test.toml", event_bus);
auto running_sessions = std::make_shared<immer::atom<immer::vector<events::StreamSession>>>();
auto state = state::load_or_default("config.test.toml", event_bus, running_sessions);
REQUIRE(state.hostname == "Wolf");
REQUIRE(state.uuid == "0000-1111-2222-3333");
REQUIRE(state.support_hevc);
Expand Down Expand Up @@ -131,7 +132,8 @@ TEST_CASE("LocalState pairing information", "[LocalState]") {

TEST_CASE("Mocked serverinfo", "[MoonlightProtocol]") {
auto event_bus = std::make_shared<events::EventBusType>();
auto cfg = state::load_or_default("config.test.toml", event_bus);
auto running_sessions = std::make_shared<immer::atom<immer::vector<events::StreamSession>>>();
auto cfg = state::load_or_default("config.test.toml", event_bus, running_sessions);
immer::array<DisplayMode> displayModes = {{1920, 1080, 60}, {1024, 768, 30}};

SECTION("server_info conforms with the expected HEVC response") {
Expand Down Expand Up @@ -335,7 +337,8 @@ TEST_CASE("Pairing moonlight", "[MoonlightProtocol]") {

TEST_CASE("applist", "[MoonlightProtocol]") {
auto event_bus = std::make_shared<events::EventBusType>();
auto cfg = state::load_or_default("config.test.toml", event_bus);
auto running_sessions = std::make_shared<immer::atom<immer::vector<events::StreamSession>>>();
auto cfg = state::load_or_default("config.test.toml", event_bus, running_sessions);
auto base_apps = cfg.apps->load().get() | views::transform([](auto app) { return app->base; }) |
to<immer::vector<moonlight::App>>();
auto result = applist(base_apps);
Expand All @@ -348,7 +351,8 @@ TEST_CASE("applist", "[MoonlightProtocol]") {

TEST_CASE("launch", "[MoonlightProtocol]") {
auto event_bus = std::make_shared<events::EventBusType>();
auto cfg = state::load_or_default("config.test.toml", event_bus);
auto running_sessions = std::make_shared<immer::atom<immer::vector<events::StreamSession>>>();
auto cfg = state::load_or_default("config.test.toml", event_bus, running_sessions);
auto result = launch_success("192.168.1.1", "3021");
REQUIRE(xml_to_str(result) == "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<root status_code=\"200\">"
Expand Down
Loading

0 comments on commit d12ab4d

Please sign in to comment.