diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index e0442157390..8acbd178b96 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -6,6 +6,7 @@ #define BOOST_BIND_GLOBAL_PLACEHOLDERS // standard includes +#include #include #include #include @@ -1102,6 +1103,7 @@ namespace nvhttp { tree.put("root.currentgame", current_appid); tree.put("root.state", current_appid > 0 ? "SUNSHINE_SERVER_BUSY" : "SUNSHINE_SERVER_FREE"); tree.put("root.appListEtag", proc::proc.get_apps_etag()); + tree.put("root.DesktopSpecialAppSupport", 1); // AI capability: inform client if AI proxy is available tree.put("root.AiCapability", confighttp::isAiEnabled() ? 1 : 0); @@ -1777,13 +1779,24 @@ namespace nvhttp { } } - void - launch(bool &host_audio, resp_https_t response, req_https_t request) { - print_req(request); + static bool + has_required_launch_params(const args_t &args, bool require_appid) { + return args.find("rikey"s) != std::end(args) + && args.find("rikeyid"s) != std::end(args) + && args.find("localAudioPlayMode"s) != std::end(args) + && (!require_appid || args.find("appid"s) != std::end(args)); + } - print_request_ip(request, "Launch request"); + static bool + has_required_resume_params(const args_t &args) { + return args.find("rikey"s) != std::end(args) + && args.find("rikeyid"s) != std::end(args); + } + void + launch_app(bool &host_audio, resp_https_t response, req_https_t request, const args_t &args, int appid, const char *result_node_name) { pt::ptree tree; + const auto result_node = "root."s + result_node_name; bool need_to_restore_display_state { false }; auto g = util::fail_guard([&]() { std::ostringstream data; @@ -1801,24 +1814,9 @@ namespace nvhttp { } }); - auto args = request->parse_query_string(); - if ( - args.find("rikey"s) == std::end(args) || - args.find("rikeyid"s) == std::end(args) || - args.find("localAudioPlayMode"s) == std::end(args) || - args.find("appid"s) == std::end(args)) { - tree.put("root.resume", 0); - tree.put("root..status_code", 400); - tree.put("root..status_message", "Missing a required launch parameter"); - - return; - } - - auto appid = util::from_view(get_arg(args, "appid")); - auto current_appid = proc::proc.running(); if (current_appid > 0) { - tree.put("root.resume", 0); + tree.put(result_node, 0); tree.put("root..status_code", 400); tree.put("root..status_message", "An app is already running on this host"); @@ -1828,15 +1826,18 @@ namespace nvhttp { // Early validation of AppID to prevent starting VDD or other expensive operations // if the requested app does not exist. if (proc::proc.get_app_name(appid).empty()) { - tree.put("root.resume", 0); + tree.put(result_node, 0); tree.put("root..status_code", 404); tree.put("root..status_message", "App not found"); BOOST_LOG(error) << "Launch couldn't find app with ID ["sv << appid << ']'; return; } - host_audio = util::from_view(get_arg(args, "localAudioPlayMode")); + if (args.find("localAudioPlayMode"s) != std::end(args)) { + host_audio = util::from_view(get_arg(args, "localAudioPlayMode")); + } const auto launch_session = make_launch_session(host_audio, args); + launch_session->appid = appid; // 获取客户端证书UUID(稳定的客户端标识符) std::string client_cert_uuid = get_client_cert_uuid_from_request(request); @@ -1860,7 +1861,7 @@ namespace nvhttp { if (video::probe_encoders()) { tree.put("root..status_code", 503); tree.put("root..status_message", "Failed to initialize video capture/encoding. Is a display connected and turned on?"); - tree.put("root.gamesession", 0); + tree.put(result_node, 0); return; } @@ -1872,27 +1873,25 @@ namespace nvhttp { tree.put("root..status_code", 403); tree.put("root..status_message", "Encryption is mandatory for this host but unsupported by the client"); - tree.put("root.gamesession", 0); + tree.put(result_node, 0); return; } - if (appid > 0) { - auto err = proc::proc.execute(appid, launch_session); - if (err) { - tree.put("root..status_code", err); - tree.put("root..status_message", "Failed to start the specified application"); - tree.put("root.gamesession", 0); + auto err = proc::proc.execute(appid, launch_session); + if (err) { + tree.put("root..status_code", err); + tree.put("root..status_message", "Failed to start the specified application"); + tree.put(result_node, 0); - return; - } + return; } tree.put("root..status_code", 200); tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT))); - tree.put("root.gamesession", 1); + tree.put(result_node, 1); rtsp_stream::launch_session_raise(launch_session); @@ -1916,6 +1915,35 @@ namespace nvhttp { need_to_restore_display_state = false; } + void + launch(bool &host_audio, resp_https_t response, req_https_t request) { + print_req(request); + + print_request_ip(request, "Launch request"); + + auto args = request->parse_query_string(); + if (!has_required_launch_params(args, true)) { + pt::ptree tree; + auto g = util::fail_guard([&]() { + std::ostringstream data; + + pt::write_xml(data, tree); + response->write(data.str()); + response->close_connection_after_response = true; + }); + + tree.put("root.gamesession", 0); + tree.put("root..status_code", 400); + tree.put("root..status_message", "Missing a required launch parameter"); + + return; + } + + auto appid = util::from_view(get_arg(args, "appid")); + + launch_app(host_audio, response, request, args, appid, "gamesession"); + } + void resume(bool &host_audio, resp_https_t response, req_https_t request) { print_req(request); @@ -1941,19 +1969,8 @@ namespace nvhttp { response->close_connection_after_response = true; }); - auto current_appid = proc::proc.running(); - if (current_appid == 0) { - tree.put("root.resume", 0); - tree.put("root..status_code", 503); - tree.put("root..status_message", "No running app to resume"); - - return; - } - auto args = request->parse_query_string(); - if ( - args.find("rikey"s) == std::end(args) || - args.find("rikeyid"s) == std::end(args)) { + if (!has_required_resume_params(args)) { tree.put("root.resume", 0); tree.put("root..status_code", 400); tree.put("root..status_message", "Missing a required resume parameter"); @@ -1961,6 +1978,13 @@ namespace nvhttp { return; } + auto current_appid = proc::proc.running(); + if (current_appid == 0) { + g.disable(); + launch_app(host_audio, response, request, args, proc::DESKTOP_APP_ID, "resume"); + return; + } + // Newer Moonlight clients send localAudioPlayMode on /resume too, // so we should use it if it's present in the args and there are // no active sessions we could be interfering with. @@ -1969,6 +1993,7 @@ namespace nvhttp { host_audio = util::from_view(get_arg(args, "localAudioPlayMode")); } const auto launch_session = make_launch_session(host_audio, args); + launch_session->appid = current_appid; // Get client certificate UUID (stable client identifier) and store it in env std::string client_cert_uuid = get_client_cert_uuid_from_request(request); @@ -2000,7 +2025,7 @@ namespace nvhttp { tree.put("root..status_code", 403); tree.put("root..status_message", "Encryption is mandatory for this host but unsupported by the client"); - tree.put("root.gamesession", 0); + tree.put("root.resume", 0); return; } diff --git a/src/process.cpp b/src/process.cpp index 4a38b8fdceb..d41ecc8a98a 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -62,6 +62,21 @@ namespace proc { } }; + namespace { + ctx_t + make_desktop_app() { + ctx_t app; + app.name = std::string(DESKTOP_APP_NAME); + app.image_path = std::string(DESKTOP_APP_IMAGE_PATH); + app.id = std::to_string(DESKTOP_APP_ID); + app.auto_detach = true; + app.wait_all = true; + app.mouse_mode = 0; + app.exit_timeout = std::chrono::seconds { 5 }; + return app; + } + } // namespace + std::unique_ptr init() { return std::make_unique(); @@ -156,17 +171,23 @@ namespace proc { // Ensure starting from a clean slate terminate(); - auto iter = std::find_if(_apps.begin(), _apps.end(), [&app_id](const auto app) { - return app.id == std::to_string(app_id); - }); + _app_id = app_id; + if (app_id == DESKTOP_APP_ID) { + _app = make_desktop_app(); + } + else { + auto iter = std::find_if(_apps.begin(), _apps.end(), [&app_id](const auto app) { + return app.id == std::to_string(app_id); + }); - if (iter == _apps.end()) { - BOOST_LOG(error) << "Couldn't find app with ID ["sv << app_id << ']'; - return 404; + if (iter == _apps.end()) { + BOOST_LOG(error) << "Couldn't find app with ID ["sv << app_id << ']'; + return 404; + } + + _app = *iter; } - _app_id = app_id; - _app = *iter; _app_prep_begin = std::begin(_app.prep_cmds); _app_prep_it = _app_prep_begin; @@ -409,7 +430,8 @@ namespace proc { for (const auto &app : apps) { combined_info += app.id + app.name; } - + combined_info += std::to_string(DESKTOP_APP_ID) + std::string(DESKTOP_APP_NAME); + // Use CRC32 for the tag, same as used elsewhere auto crc = calculate_crc32(combined_info); _apps_etag = std::to_string(crc); @@ -441,6 +463,10 @@ namespace proc { // Returns http content-type header compatible image type. std::string proc_t::get_app_image(int app_id) { + if (app_id == DESKTOP_APP_ID) { + return validate_app_image_path(std::string(DESKTOP_APP_IMAGE_PATH)); + } + auto iter = std::find_if(_apps.begin(), _apps.end(), [&app_id](const auto app) { return app.id == std::to_string(app_id); }); @@ -451,6 +477,10 @@ namespace proc { std::string proc_t::get_app_name(int app_id) { + if (app_id == DESKTOP_APP_ID) { + return std::string(DESKTOP_APP_NAME); + } + auto iter = std::find_if(_apps.begin(), _apps.end(), [&app_id](const auto app) { return app.id == std::to_string(app_id); }); @@ -459,6 +489,10 @@ namespace proc { std::string proc_t::get_app_cmd(int app_id) { + if (app_id == DESKTOP_APP_ID) { + return std::string(); + } + auto iter = std::find_if(_apps.begin(), _apps.end(), [&app_id](const auto app) { return app.id == std::to_string(app_id); }); @@ -885,15 +919,19 @@ namespace proc { ctx.mouse_mode = mouse_mode.value_or(0); ctx.exit_timeout = std::chrono::seconds { exit_timeout.value_or(5) }; - auto possible_ids = calculate_app_id(name, ctx.image_path, i++); - if (ids.count(std::get<0>(possible_ids)) == 0) { - // Avoid using index to generate id if possible - ctx.id = std::get<0>(possible_ids); - } - else { - // Fallback to include index on collision - ctx.id = std::get<1>(possible_ids); - } + std::tuple possible_ids; + do { + possible_ids = calculate_app_id(name, ctx.image_path, i++); + if (ids.count(std::get<0>(possible_ids)) == 0) { + // Avoid using index to generate id if possible + ctx.id = std::get<0>(possible_ids); + } + else { + // Fallback to include index on collision + ctx.id = std::get<1>(possible_ids); + } + } while (ids.count(ctx.id) != 0 || ctx.id == std::to_string(DESKTOP_APP_ID)); + ids.insert(ctx.id); ctx.name = std::move(name); diff --git a/src/process.h b/src/process.h index 0b0e686b3a4..431f3cbf8df 100644 --- a/src/process.h +++ b/src/process.h @@ -8,7 +8,9 @@ #define __kernel_entry #endif +#include #include +#include #include #include @@ -21,6 +23,10 @@ namespace proc { using file_t = util::safe_ptr_v2; + inline constexpr int DESKTOP_APP_ID = std::numeric_limits::max(); + inline constexpr std::string_view DESKTOP_APP_NAME = "Desktop"; + inline constexpr std::string_view DESKTOP_APP_IMAGE_PATH = "desktop"; + typedef config::prep_cmd_t cmd_t; struct scmd_t { scmd_t(std::string &&id, std::string &&name, std::string &&do_cmd, bool &&elevated):