diff --git a/assets/apps.json b/assets/apps.json index 1f0a4838..7b72ad1d 100644 --- a/assets/apps.json +++ b/assets/apps.json @@ -1,29 +1,34 @@ { "env":[ - { "VAR":"VAL", "VAR2":"VAL2" } + { + "DISPLAY":":0", + "DRI_PRIME":"1", + "XAUTHORITY":"/home/loki/.Xauthority" + } ], "apps":[ { - "name":"echo-1", - - "output":"output.txt", - "cmd":"sh -c \"echo $VAR\"", + "name":"Low Res Desktop 2", + "cmd":"sh -c \"while true; do sleep 10000; done;\"", "prep-cmd":[ - { "do":"echo pre-1", "undo":"echo post-1" }, - { "do":"echo pre-2" }, - { "do":"echo pre-3", "undo":"echo post-3" } + { "do":"xrandr --output HDMI-1 --mode 1920x1080", "undo":"xrandr --output HDMI-1 --mode 1920x1200" } ] }, { - "name":"echo-2", - - "output":"output.txt", - "cmd":"sleep 10", + "name":"Low Res Desktop", + "cmd":"sh -c \"while true; do sleep 10000; done;\"", "prep-cmd":[ - { "do":"echo pre-1", "undo":"echo post-1" }, - { "do":"echo pre-2" }, - { "do":"echo pre-3", "undo":"echo post-3" } + { "do":"xrandr --output HDMI-1 --mode 1920x1080", "undo":"xrandr --output HDMI-1 --mode 1920x1200" } ] + }, + { + "name":"Steam BigPicture", + + "output":"steam.txt", + "cmd":"steam -bigpicture", + "prep-cmd":[ + { "do":"sh -c \"PATH=$PATH:$HOME/.local/bin sudo freeHugePages\""}, + { "do":"sh -c \"PATH=$PATH:$HOME/.local/bin sudo claim_amdgpu\"" }] } ] } diff --git a/sunshine/main.cpp b/sunshine/main.cpp index a895ad72..015ef020 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -6,9 +6,6 @@ #include #include -#include -#include - #include "nvhttp.h" #include "stream.h" #include "config.h" @@ -18,85 +15,9 @@ extern "C" { #include } - using namespace std::literals; -namespace pt = boost::property_tree; - -std::optional parse(const std::string& file_name) { - pt::ptree tree; - - try { - pt::read_json(file_name, tree); - - auto &apps_node = tree.get_child("apps"s); - auto &env_vars = tree.get_child("env"s); - - boost::process::environment env = boost::this_process::environment(); - - std::unordered_map apps; - for(auto &[_,app_node] : apps_node) { - proc::ctx_t ctx; - - auto &prep_nodes = app_node.get_child("prep-cmd"s); - auto name = app_node.get("name"s); - auto output = app_node.get_optional("output"s); - auto cmd = app_node.get("cmd"s); - - std::vector prep_cmds; - prep_cmds.reserve(prep_nodes.size()); - for(auto &[_, prep_node] : prep_nodes) { - auto do_cmd = prep_node.get("do"s); - auto undo_cmd = prep_node.get_optional("undo"s); - - if(undo_cmd) { - prep_cmds.emplace_back(std::move(do_cmd), std::move(*undo_cmd)); - } - else { - prep_cmds.emplace_back(std::move(do_cmd)); - } - } - - if(output) { - ctx.output = std::move(*output); - } - ctx.cmd = std::move(cmd); - ctx.prep_cmds = std::move(prep_cmds); - - apps.emplace(std::move(name), std::move(ctx)); - } - - for(auto &[_,env_var] : env_vars) { - for(auto &[name,val] : env_var) { - env[name] = val.get_value(); - } - } - - return proc::proc_t { - std::move(env), std::move(apps) - }; - } catch (std::exception &e) { - std::cout << e.what() << std::endl; - } - - return std::nullopt; -} int main(int argc, char *argv[]) { - auto proc_opt = parse(SUNSHINE_ASSETS_DIR "/apps.json"); - - if(!proc_opt) { - return 7; - } - - auto &proc = *proc_opt; - - proc.execute("echo-2"); - std::this_thread::sleep_for(50ms); - - proc.execute("echo-1"); - std::this_thread::sleep_for(50ms); - return proc.running(); - if(argc > 1) { if(!std::filesystem::exists(argv[1])) { std::cout << "Error: Couldn't find configuration file ["sv << argv[1] << ']' << std::endl; @@ -106,6 +27,12 @@ int main(int argc, char *argv[]) { config::parse_file(argv[1]); } + auto proc_opt = proc::parse(SUNSHINE_ASSETS_DIR "/apps.json"); + if(!proc_opt) { + return 7; + } + proc::proc = std::move(*proc_opt); + reed_solomon_init(); std::thread httpThread { nvhttp::start }; diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 69330145..0733056c 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -20,6 +20,7 @@ #include "stream.h" #include "nvhttp.h" #include "platform/common.h" +#include "process.h" namespace nvhttp { @@ -441,20 +442,26 @@ void applist(std::shared_ptr::Response> respon } auto &apps = tree.add_child("root", pt::ptree {}); + pt::ptree desktop; - pt::ptree fakegame; apps.put(".status_code", 200); desktop.put("IsHdrSupported"s, 0); desktop.put("AppTitle"s, "Desktop"); desktop.put("ID"s, 1); - fakegame.put("IsHdrSupported"s, 0); - fakegame.put("AppTitle"s, "FakeGame"); - fakegame.put("ID"s, 2); + int x = 2; + for(auto &[name, proc] : proc::proc.get_apps()) { + pt::ptree app; + + app.put("IsHdrSupported"s, 0); + app.put("AppTitle"s, name); + app.put("ID"s, x++); + + apps.push_back(std::make_pair("App", std::move(app))); + } apps.push_back(std::make_pair("App", desktop)); - apps.push_back(std::make_pair("App", fakegame)); } template @@ -462,6 +469,17 @@ void launch(std::shared_ptr::Response> respons print_req(request); auto args = request->parse_query_string(); + + auto appid = util::from_view(args.at("appid")) -2; + + stream::app_name.clear(); + if(appid >= 0) { + auto pos = std::begin(proc::proc.get_apps()); + std::advance(pos, appid); + + stream::app_name = pos->first; + } + auto clientID = args.at("uniqueid"s); auto aesKey = *util::from_hex(args.at("rikey"s), true); uint32_t prepend_iv = util::endian::big(util::from_view(args.at("rikeyid"s))); diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index 6ee83282..b9be9e21 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -34,7 +34,7 @@ struct gamepad_state_t { std::string get_local_ip(); -void interrupt_process(std::uint64_t handle); +void terminate_process(std::uint64_t handle); mic_t microphone(); audio_t audio(mic_t &mic, std::uint32_t sample_size); diff --git a/sunshine/platform/linux.cpp b/sunshine/platform/linux.cpp index 8713ab50..7a638c28 100644 --- a/sunshine/platform/linux.cpp +++ b/sunshine/platform/linux.cpp @@ -84,8 +84,8 @@ std::string get_local_ip(int family) { std::string get_local_ip() { return get_local_ip(AF_INET); } -void interrupt_process(std::uint64_t handle) { - kill((pid_t)handle, SIGINT); +void terminate_process(std::uint64_t handle) { + kill((pid_t)handle, SIGTERM); } struct display_attr_t { diff --git a/sunshine/process.cpp b/sunshine/process.cpp index 00a83469..2d3b7fc0 100644 --- a/sunshine/process.cpp +++ b/sunshine/process.cpp @@ -6,6 +6,9 @@ #include #include +#include +#include + #include "process.h" #include "config.h" #include "utility.h" @@ -14,6 +17,9 @@ namespace proc { using namespace std::literals; namespace bp = boost::process; +namespace pt = boost::property_tree; + +proc_t proc; template void process_end(bp::child &proc, const std::chrono::duration& rel_time) { @@ -21,10 +27,12 @@ void process_end(bp::child &proc, const std::chrono::duration& rel_ return; } - platf::interrupt_process((std::uint64_t)proc.native_handle()); + std::cout << "Interruping Child-Process"sv << std::endl; + platf::terminate_process((std::uint64_t) proc.native_handle()); // Force termination if it takes too long if(!proc.wait_for(rel_time)) { + std::cout << "Force termination Child-Process"sv << std::endl; proc.terminate(); } } @@ -40,8 +48,10 @@ int exe(const std::string &cmd, bp::environment &env, file_t &file, std::error_c int proc_t::execute(const std::string &name) { auto it = _name_to_proc.find(name); + std::cout << "Ensure clean slate"sv << std::endl; // Ensure starting from a clean slate _undo_pre_cmd(); + std::cout << "Clean slate"sv << std::endl; if(it == std::end(_name_to_proc)) { std::cout << "Error: Couldn't find ["sv << name << ']' << std::endl; @@ -137,7 +147,79 @@ void proc_t::_undo_pre_cmd() { _pipe.reset(); } + +const std::unordered_map &proc_t::get_apps() const { + return _name_to_proc; +} + proc_t::~proc_t() { _undo_pre_cmd(); } + +std::optional parse(const std::string& file_name) { + pt::ptree tree; + + try { + pt::read_json(file_name, tree); + + auto &apps_node = tree.get_child("apps"s); + auto &env_vars = tree.get_child("env"s); + + boost::process::environment env = boost::this_process::environment(); + + std::unordered_map apps; + for(auto &[_,app_node] : apps_node) { + proc::ctx_t ctx; + + auto &prep_nodes = app_node.get_child("prep-cmd"s); + auto name = app_node.get("name"s); + auto output = app_node.get_optional("output"s); + auto cmd = app_node.get("cmd"s); + + std::vector prep_cmds; + prep_cmds.reserve(prep_nodes.size()); + for(auto &[_, prep_node] : prep_nodes) { + auto do_cmd = prep_node.get("do"s); + auto undo_cmd = prep_node.get_optional("undo"s); + + if(undo_cmd) { + prep_cmds.emplace_back(std::move(do_cmd), std::move(*undo_cmd)); + } + else { + prep_cmds.emplace_back(std::move(do_cmd)); + } + } + + if(output) { + ctx.output = std::move(*output); + } + ctx.cmd = std::move(cmd); + ctx.prep_cmds = std::move(prep_cmds); + + apps.emplace(std::move(name), std::move(ctx)); + } + + for(auto &[_,env_var] : env_vars) { + for(auto &[name,val] : env_var) { + env[name] = val.get_value(); + } + } + + return proc::proc_t { + std::move(env), std::move(apps) + }; + } catch (std::exception &e) { + std::cout << e.what() << std::endl; + } + + return std::nullopt; +} + +void refresh(const std::string &file_name) { + auto proc_opt = proc::parse(file_name); + + if(proc_opt) { + proc = std::move(*proc_opt); + } +} } \ No newline at end of file diff --git a/sunshine/process.h b/sunshine/process.h index a43360d2..dd6e2f84 100644 --- a/sunshine/process.h +++ b/sunshine/process.h @@ -6,8 +6,10 @@ #define SUNSHINE_PROCESS_H #include +#include #include + #include "utility.h" namespace proc { @@ -41,7 +43,7 @@ struct ctx_t { class proc_t { public: - KITTY_DEFAULT_CONSTR(proc_t) + KITTY_DEFAULT_CONSTR_THROW(proc_t) proc_t( boost::process::environment &&env, @@ -54,6 +56,7 @@ public: ~proc_t(); + const std::unordered_map &get_apps() const; private: void _undo_pre_cmd(); @@ -66,5 +69,9 @@ private: std::vector::const_iterator _undo_begin; }; +void refresh(const std::string &file_name); +std::optional parse(const std::string& file_name); + +extern proc_t proc; } #endif //SUNSHINE_PROCESS_H diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index b43f5a9f..08594862 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -31,6 +31,7 @@ extern "C" { #include "thread_safe.h" #include "crypto.h" #include "input.h" +#include "process.h" #define IDX_START_A 0 #define IDX_REQUEST_IDR_FRAME 0 @@ -88,8 +89,10 @@ struct audio_packet_raw_t { #pragma pack(pop) +//TODO: Bundle in thread safe container crypto::aes_t gcm_key; crypto::aes_t iv; +std::string app_name; struct config_t { audio::config_t audio; @@ -114,6 +117,8 @@ struct session_t { crypto::aes_t gcm_key; crypto::aes_t iv; + + std::string app_name; } session; void free_msg(PRTSP_MESSAGE msg) { @@ -166,7 +171,7 @@ public: parseRtspMessage(req.get(), (char*)packet->data, packet->dataLength); for(auto option = req->options; option != nullptr; option = option->next) { if("Content-length"sv == option->option) { - _queue_packet = std::make_pair(std::move(peer), std::move(packet)); + _queue_packet = std::make_pair(peer, std::move(packet)); return; } } @@ -272,6 +277,7 @@ public: } } void map(uint16_t type, std::function cb); + void send(const std::string_view &payload); private: std::unordered_map> _map_type_cb; ENetAddress _addr; @@ -431,6 +437,17 @@ void control_server_t::map(uint16_t type, std::functionpeers, _host->peers + _host->peerCount, [payload](auto &peer) { + auto packet = enet_packet_create(payload.data(), payload.size(), ENET_PACKET_FLAG_RELIABLE); + if(enet_peer_send(&peer, 0, packet)) { + enet_packet_destroy(packet); + } + }); + + enet_host_flush(_host.get()); +} + void controlThread(video::idr_event_t idr_events) { control_server_t server { CONTROL_PORT }; @@ -515,6 +532,21 @@ void controlThread(video::idr_event_t idr_events) { session.audio_packets->stop(); } + if(!session.app_name.empty() && !proc::proc.running()) { + std::cout << "Process terminated"sv << std::endl; + + std::uint16_t reason = 0x0100; + + std::array payload; + payload[0] = packetTypes[IDX_TERMINATION]; + payload[1] = reason; + + server.send(std::string_view {(char*)payload.data(), payload.size()}); + + session.video_packets->stop(); + session.audio_packets->stop(); + } + server.iterate(500ms); } } @@ -885,6 +917,22 @@ void cmd_announce(host_t &host, peer_t peer, msg_t &&req) { return; } + if(!app_name.empty() && session.app_name != app_name ) { + if(auto err_code = proc::proc.execute(app_name)) { + if(err_code == 404) { + respond(host, peer, &option, 404, (app_name + " NOT FOUND").c_str(), req->sequenceNumber, {}); + return; + } + + else { + respond(host, peer, &option, 500, "INTERNAL ERROR", req->sequenceNumber, {}); + return; + } + } + } + + session.app_name = std::move(app_name); + std::copy(std::begin(gcm_key), std::end(gcm_key), std::begin(session.gcm_key)); std::copy(std::begin(iv), std::end(iv), std::begin(session.iv)); diff --git a/sunshine/stream.h b/sunshine/stream.h index 27449a9c..d5824483 100644 --- a/sunshine/stream.h +++ b/sunshine/stream.h @@ -9,8 +9,10 @@ namespace stream { +//FIXME: Make thread safe extern crypto::aes_t gcm_key; extern crypto::aes_t iv; +extern std::string app_name; void rtpThread();