From 4fe9f3d14540cebba5400d8a1efcd3512a5d37ca Mon Sep 17 00:00:00 2001 From: loki-47-6F-64 Date: Tue, 5 Oct 2021 22:39:22 +0200 Subject: [PATCH] Pass stream parameters to the apps.json --- assets/apps_linux.json | 4 +- sunshine/confighttp.cpp | 30 ++++--- sunshine/nvhttp.cpp | 33 ++++---- sunshine/process.cpp | 172 ++++++++++++++++++++++------------------ sunshine/process.h | 12 +-- sunshine/rtsp.cpp | 18 +++++ sunshine/rtsp.h | 2 + sunshine/stream.cpp | 2 +- 8 files changed, 159 insertions(+), 114 deletions(-) diff --git a/assets/apps_linux.json b/assets/apps_linux.json index 1cfeb3d4..383bb82c 100644 --- a/assets/apps_linux.json +++ b/assets/apps_linux.json @@ -4,9 +4,9 @@ }, "apps":[ { - "name":"Low Res Desktop", + "name":"Change Resolution Desktop", "prep-cmd":[ - { "do":"xrandr --output HDMI-1 --mode 1920x1080", "undo":"xrandr --output HDMI-1 --mode 1920x1200" } + { "do":"xrandr --mode $[width]x$[height]", "undo":"xrandr --auto" } ] }, { diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index c4440b95..6d6bd952 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -93,8 +93,8 @@ bool authenticate(resp_https_t response, req_https_t request) { } //If credentials are shown, redirect the user to a /welcome page - if(config::sunshine.username.empty()){ - send_redirect(response,request,"/welcome"); + if(config::sunshine.username.empty()) { + send_redirect(response, request, "/welcome"); return false; } @@ -202,8 +202,8 @@ void getPasswordPage(resp_https_t response, req_https_t request) { void getWelcomePage(resp_https_t response, req_https_t request) { print_req(request); - if(!config::sunshine.username.empty()){ - send_redirect(response,request,"/"); + if(!config::sunshine.username.empty()) { + send_redirect(response, request, "/"); return; } std::string header = read_file(WEB_DIR "header-no-nav.html"); @@ -453,16 +453,18 @@ void savePassword(resp_https_t response, req_https_t request) { auto newPassword = inputTree.count("newPassword") > 0 ? inputTree.get("newPassword") : ""; auto confirmPassword = inputTree.count("confirmNewPassword") > 0 ? inputTree.get("confirmNewPassword") : ""; if(newUsername.length() == 0) newUsername = username; - if(newUsername.length() == 0){ + if(newUsername.length() == 0) { outputTree.put("status", false); outputTree.put("error", "Invalid Username"); - } else { + } + else { auto hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string(); if(config::sunshine.username.empty() || (username == config::sunshine.username && hash == config::sunshine.password)) { if(newPassword.empty() || newPassword != confirmPassword) { outputTree.put("status", false); outputTree.put("error", "Password Mismatch"); - } else { + } + else { http::save_user_creds(config::sunshine.credentials_file, newUsername, newPassword); http::reload_user_creds(config::sunshine.credentials_file); outputTree.put("status", true); @@ -512,9 +514,9 @@ void savePin(resp_https_t response, req_https_t request) { } } -void unpairAll(resp_https_t response, req_https_t request){ +void unpairAll(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; - + print_req(request); pt::ptree outputTree; @@ -528,9 +530,9 @@ void unpairAll(resp_https_t response, req_https_t request){ outputTree.put("status", true); } -void closeApp(resp_https_t response, req_https_t request){ +void closeApp(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; - + print_req(request); pt::ptree outputTree; @@ -541,7 +543,11 @@ void closeApp(resp_https_t response, req_https_t request){ response->write(data.str()); }); - proc::proc.terminate(); + { + auto lg = proc::proc.lock(); + proc::proc->terminate(); + } + outputTree.put("status", true); } diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index f167778a..b7f3569d 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -186,7 +186,7 @@ void update_id_client(const std::string &uniqueID, std::string &&cert, op_e op) } } -stream::launch_session_t make_launch_session(bool host_audio, const args_t &args) { +stream::launch_session_t make_launch_session(bool host_audio, int appid, const args_t &args) { stream::launch_session_t launch_session; launch_session.host_audio = host_audio; @@ -197,6 +197,8 @@ stream::launch_session_t make_launch_session(bool host_audio, const args_t &args auto next = std::copy(prepend_iv_p, prepend_iv_p + sizeof(prepend_iv), std::begin(launch_session.iv)); std::fill(next, std::end(launch_session.iv), 0); + launch_session.appid = appid; + return launch_session; } @@ -568,7 +570,8 @@ void serverinfo(std::shared_ptr::Response> res if(!config::nvhttp.resolutions.empty()) { tree.add_child("root.SupportedDisplayMode", display_nodes); } - auto current_appid = proc::proc.running(); + + auto current_appid = proc::proc->running(); tree.put("root.PairStatus", pair_status); tree.put("root.currentgame", current_appid >= 0 ? current_appid + 1 : 0); tree.put("root.state", current_appid >= 0 ? "SUNSHINE_SERVER_BUSY" : "SUNSHINE_SERVER_FREE"); @@ -614,7 +617,7 @@ void applist(resp_https_t response, req_https_t request) { apps.put(".status_code", 200); int x = 0; - for(auto &proc : proc::proc.get_apps()) { + for(auto &proc : proc::proc->get_apps()) { // apps don't change between invocations pt::ptree app; app.put("IsHdrSupported"s, config::video.hevc_mode == 3 ? 1 : 0); @@ -659,7 +662,7 @@ void launch(bool &host_audio, resp_https_t response, req_https_t request) { auto appid = util::from_view(args.at("appid")) - 1; - auto current_appid = proc::proc.running(); + auto current_appid = proc::proc->running(); if(current_appid != -1) { tree.put("root.resume", 0); tree.put("root..status_code", 400); @@ -667,18 +670,9 @@ void launch(bool &host_audio, resp_https_t response, req_https_t request) { return; } - if(appid >= 0) { - auto err = proc::proc.execute(appid); - if(err) { - tree.put("root..status_code", err); - tree.put("root.gamesession", 0); - - return; - } - } host_audio = util::from_view(args.at("localAudioPlayMode")); - stream::launch_session_raise(make_launch_session(host_audio, args)); + stream::launch_session_raise(make_launch_session(host_audio, appid, args)); tree.put("root..status_code", 200); tree.put("root.sessionUrl0", "rtsp://"s + request->local_endpoint_address() + ':' + std::to_string(map_port(stream::RTSP_SETUP_PORT))); @@ -706,7 +700,7 @@ void resume(bool &host_audio, resp_https_t response, req_https_t request) { return; } - auto current_appid = proc::proc.running(); + auto current_appid = proc::proc->running(); if(current_appid == -1) { tree.put("root.resume", 0); tree.put("root..status_code", 503); @@ -725,7 +719,7 @@ void resume(bool &host_audio, resp_https_t response, req_https_t request) { return; } - stream::launch_session_raise(make_launch_session(host_audio, args)); + stream::launch_session_raise(make_launch_session(host_audio, -1, args)); tree.put("root..status_code", 200); tree.put("root.sessionUrl0", "rtsp://"s + request->local_endpoint_address() + ':' + std::to_string(map_port(stream::RTSP_SETUP_PORT))); @@ -756,8 +750,9 @@ void cancel(resp_https_t response, req_https_t request) { tree.put("root.cancel", 1); tree.put("root..status_code", 200); - if(proc::proc.running() != -1) { - proc::proc.terminate(); + auto lg = proc::proc.lock(); + if(proc::proc->running() != -1) { + proc::proc->terminate(); } } @@ -928,7 +923,7 @@ void start() { tcp.join(); } -void erase_all_clients(){ +void erase_all_clients() { map_id_client.clear(); save_state(); } diff --git a/sunshine/process.cpp b/sunshine/process.cpp index f68593c0..4bed7fa0 100644 --- a/sunshine/process.cpp +++ b/sunshine/process.cpp @@ -9,9 +9,9 @@ #include #include +#include #include #include -#include #include "main.h" #include "utility.h" @@ -21,7 +21,85 @@ using namespace std::literals; namespace bp = boost::process; namespace pt = boost::property_tree; -proc_t proc; +util::sync_t proc; + +std::string_view::iterator find_match(char in, char out, std::string_view::iterator begin, std::string_view::iterator end) { + int stack = 0; + + --begin; + do { + ++begin; + + if(*begin == in) { + ++stack; + } + else if(*begin == out) { + --stack; + } + } while(begin != end && stack != 0); + + if(begin == end) { + throw std::out_of_range("Missing closing character \'" + out + '\''); + } + return begin; +} + +template +std::string parse(char in, char out, const std::string_view &val_raw, F &&f) { + auto pos = std::begin(val_raw); + auto dollar = std::find(pos, std::end(val_raw), '$'); + + std::stringstream ss; + + while(dollar != std::end(val_raw)) { + auto next = dollar + 1; + if(next != std::end(val_raw)) { + if(*next == in) { + ss.write(pos, (dollar - pos)); + auto var_begin = next + 1; + auto var_end = find_match(in, out, next, std::end(val_raw)); + + ss << f(var_begin, var_end); + + pos = var_end + 1; + next = var_end; + } + else if(*next == '$') { + ss.write(pos, (next - pos)); + pos = next + 1; + ++next; + } + + dollar = std::find(next, std::end(val_raw), '$'); + } + else { + dollar = next; + } + } + + ss.write(pos, (dollar - pos)); + + return ss.str(); +} + +std::string parse_env_val(bp::native_environment &env, const std::string_view &val_raw) { + return parse('(', ')', val_raw, [&](const char *begin, const char *end) -> std::string { + return env[std::string { begin, end }].to_string(); + }); +} + +std::string parse_command(const std::string_view &cmd, const std::unordered_map &args) { + return parse('[', ']', cmd, [&](const char *begin, const char *end) -> std::string { + std::string_view arg { begin, (std::size_t)(end - begin) }; + + auto val = args.find(arg); + if(val == std::end(args)) { + return {}; + } + + return std::string { val->second.data(), val->second.size() }; + }); +} void process_end(bp::child &proc, bp::group &proc_handle) { if(!proc.running()) { @@ -43,7 +121,7 @@ int exe(const std::string &cmd, bp::environment &env, file_t &file, std::error_c return bp::system(cmd, env, bp::std_out > file.get(), bp::std_err > file.get(), ec); } -int proc_t::execute(int app_id) { +int proc_t::execute(int app_id, const std::unordered_map &args) { if(!running() && _app_id != -1) { // previous process exited on it's own, reset _process_handle _process_handle = bp::group(); @@ -77,7 +155,7 @@ int proc_t::execute(int app_id) { }); for(; _undo_it != std::end(proc.prep_cmds); ++_undo_it) { - auto &cmd = _undo_it->do_cmd; + auto cmd = parse_command(_undo_it->do_cmd, args); BOOST_LOG(info) << "Executing: ["sv << cmd << ']'; auto ret = exe(cmd, _env, _pipe, ec); @@ -93,7 +171,9 @@ int proc_t::execute(int app_id) { } } - for(auto &cmd : proc.detached) { + for(auto &cmd_raw : proc.detached) { + auto cmd = parse_command(cmd_raw, args); + BOOST_LOG(info) << "Spawning ["sv << cmd << ']'; if(proc.output.empty() || proc.output == "null"sv) { bp::spawn(cmd, _env, bp::std_out > bp::null, bp::std_err > bp::null, ec); @@ -107,24 +187,28 @@ int proc_t::execute(int app_id) { } } + auto cmd = parse_command(proc.cmd, args); if(proc.cmd.empty()) { BOOST_LOG(debug) << "Executing [Desktop]"sv; placebo = true; - } else { - boost::filesystem::path working_dir = proc.working_dir.empty() ? - boost::filesystem::path(proc.cmd).parent_path() : boost::filesystem::path(proc.working_dir); + } + else { + boost::filesystem::path working_dir = proc.working_dir.empty() ? + boost::filesystem::path(proc.cmd).parent_path() : + boost::filesystem::path(proc.working_dir); + if(proc.output.empty() || proc.output == "null"sv) { - BOOST_LOG(info) << "Executing: ["sv << proc.cmd << ']'; - _process = bp::child(_process_handle, proc.cmd, _env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec); + BOOST_LOG(info) << "Executing: ["sv << cmd << ']'; + _process = bp::child(_process_handle, cmd, _env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec); } else { - BOOST_LOG(info) << "Executing: ["sv << proc.cmd << ']'; - _process = bp::child(_process_handle, proc.cmd, _env, bp::start_dir(working_dir), bp::std_out > _pipe.get(), bp::std_err > _pipe.get(), ec); + BOOST_LOG(info) << "Executing: ["sv << cmd << ']'; + _process = bp::child(_process_handle, cmd, _env, bp::start_dir(working_dir), bp::std_out > _pipe.get(), bp::std_err > _pipe.get(), ec); } } if(ec) { - BOOST_LOG(warning) << "Couldn't run ["sv << proc.cmd << "]: System: "sv << ec.message(); + BOOST_LOG(warning) << "Couldn't run ["sv << cmd << "]: System: "sv << ec.message(); return -1; } @@ -193,68 +277,6 @@ proc_t::~proc_t() { terminate(); } -std::string_view::iterator find_match(std::string_view::iterator begin, std::string_view::iterator end) { - int stack = 0; - - --begin; - do { - ++begin; - switch(*begin) { - case '(': - ++stack; - break; - case ')': - --stack; - } - } while(begin != end && stack != 0); - - if(begin == end) { - throw std::out_of_range("Missing closing bracket \')\'"); - } - return begin; -} - -std::string parse_env_val(bp::native_environment &env, const std::string_view &val_raw) { - auto pos = std::begin(val_raw); - auto dollar = std::find(pos, std::end(val_raw), '$'); - - std::stringstream ss; - - while(dollar != std::end(val_raw)) { - auto next = dollar + 1; - if(next != std::end(val_raw)) { - switch(*next) { - case '(': { - ss.write(pos, (dollar - pos)); - auto var_begin = next + 1; - auto var_end = find_match(next, std::end(val_raw)); - - ss << env[std::string { var_begin, var_end }].to_string(); - - pos = var_end + 1; - next = var_end; - - break; - } - case '$': - ss.write(pos, (next - pos)); - pos = next + 1; - ++next; - break; - } - - dollar = std::find(next, std::end(val_raw), '$'); - } - else { - dollar = next; - } - } - - ss.write(pos, (dollar - pos)); - - return ss.str(); -} - std::optional parse(const std::string &file_name) { pt::ptree tree; diff --git a/sunshine/process.h b/sunshine/process.h index 86016b64..590ce4db 100644 --- a/sunshine/process.h +++ b/sunshine/process.h @@ -15,6 +15,7 @@ #include #include "utility.h" +#include "sync.h" namespace proc { using file_t = util::safe_ptr_v2; @@ -63,11 +64,12 @@ public: proc_t( boost::process::environment &&env, - std::vector &&apps) : _app_id(-1), - _env(std::move(env)), - _apps(std::move(apps)) {} + std::vector &&apps) + : _app_id(-1), + _env(std::move(env)), + _apps(std::move(apps)) {} - int execute(int app_id); + int execute(int app_id, const std::unordered_map &args); /** * @return _app_id if a process is running, otherwise returns -1 @@ -101,6 +103,6 @@ private: void refresh(const std::string &file_name); std::optional parse(const std::string &file_name); -extern proc_t proc; +extern util::sync_t proc; } // namespace proc #endif //SUNSHINE_PROCESS_H diff --git a/sunshine/rtsp.cpp b/sunshine/rtsp.cpp index 68c6f268..0491fee5 100644 --- a/sunshine/rtsp.cpp +++ b/sunshine/rtsp.cpp @@ -18,6 +18,7 @@ extern "C" { #include "input.h" #include "main.h" #include "network.h" +#include "process.h" #include "rtsp.h" #include "stream.h" #include "sync.h" @@ -653,6 +654,23 @@ void cmd_announce(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) { return; } + auto appid = launch_session->appid; + if(appid >= 0) { + // Special variables. + + args.emplace("height"sv, args.at("x-nv-video[0].clientViewportHt"sv)); + args.emplace("width"sv, args.at("x-nv-video[0].clientViewportWd"sv)); + args.emplace("fps"sv, args.at("x-nv-video[0].maxFPS"sv)); + + auto lg = proc::proc.lock(); + auto err = proc::proc->execute(appid, args); + if(err) { + respond(sock, &option, 500, "Couldn't launch application: Internal Server Error", req->sequenceNumber, {}); + + return; + } + } + auto session = session::alloc(config, launch_session->gcm_key, launch_session->iv); auto slot = server->accept(session); diff --git a/sunshine/rtsp.h b/sunshine/rtsp.h index f038a5cf..0cb65a65 100644 --- a/sunshine/rtsp.h +++ b/sunshine/rtsp.h @@ -17,6 +17,8 @@ struct launch_session_t { crypto::aes_t gcm_key; crypto::aes_t iv; + int appid; + bool host_audio; }; diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index dd14e829..0c3616ba 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -757,7 +757,7 @@ void controlBroadcastThread(control_server_t *server) { }) } - if(proc::proc.running() == -1) { + if(proc::proc->running() == -1) { BOOST_LOG(debug) << "Process terminated"sv; break;