Merge remote-tracking branch 'origin/master'

This commit is contained in:
Yukino Song
2025-02-06 09:14:18 +08:00
195 changed files with 9602 additions and 13677 deletions

View File

@@ -4,28 +4,29 @@
*/
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include "process.h"
// standard includes
#include <filesystem>
#include <string>
#include <thread>
#include <vector>
// lib includes
#include <boost/algorithm/string.hpp>
#include <boost/crc.hpp>
#include <boost/filesystem.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <openssl/evp.h>
#include <openssl/sha.h>
// local includes
#include "config.h"
#include "crypto.h"
#include "display_device.h"
#include "logging.h"
#include "platform/common.h"
#include "process.h"
#include "httpcommon.h"
#include "system_tray.h"
#include "utility.h"
@@ -80,13 +81,11 @@ namespace proc {
}
};
std::unique_ptr<platf::deinit_t>
init() {
std::unique_ptr<platf::deinit_t> init() {
return std::make_unique<deinit_t>();
}
void
terminate_process_group(boost::process::v1::child &proc, boost::process::v1::group &group, std::chrono::seconds exit_timeout) {
void terminate_process_group(boost::process::v1::child &proc, boost::process::v1::group &group, std::chrono::seconds exit_timeout) {
if (group.valid() && platf::process_group_running((std::uintptr_t) group.native_handle())) {
if (exit_timeout.count() > 0) {
// Request processes in the group to exit gracefully
@@ -101,16 +100,13 @@ namespace proc {
if (exit_timeout.count() < 0) {
BOOST_LOG(warning) << "App did not fully exit within the timeout. Terminating the app's remaining processes."sv;
}
else {
} else {
BOOST_LOG(info) << "All app processes have successfully exited."sv;
}
}
else {
} else {
BOOST_LOG(info) << "App did not respond to a graceful termination request. Forcefully terminating the app's processes."sv;
}
}
else {
} else {
BOOST_LOG(info) << "No graceful exit timeout was specified for this app. Forcefully terminating the app's processes."sv;
}
@@ -127,8 +123,7 @@ namespace proc {
}
}
boost::filesystem::path
find_working_directory(const std::string &cmd, boost::process::v1::environment &env) {
boost::filesystem::path find_working_directory(const std::string &cmd, boost::process::v1::environment &env) {
// Parse the raw command string into parts to get the actual command portion
#ifdef _WIN32
auto parts = boost::program_options::split_winmain(cmd);
@@ -163,8 +158,7 @@ namespace proc {
return cmd_path.parent_path();
}
void
proc_t::launch_input_only() {
void proc_t::launch_input_only() {
_app_id = input_only_app_id;
_app_name = "Remote Input";
allow_client_commands = false;
@@ -175,8 +169,7 @@ namespace proc {
#endif
}
int
proc_t::execute(int app_id, const ctx_t& app, std::shared_ptr<rtsp_stream::launch_session_t> launch_session) {
int proc_t::execute(int app_id, const ctx_t& app, std::shared_ptr<rtsp_stream::launch_session_t> launch_session) {
if (_app_id == input_only_app_id) {
terminate();
std::this_thread::sleep_for(1s);
@@ -412,8 +405,7 @@ namespace proc {
auto child = platf::run_command(_app.elevated, true, cmd, working_dir, _env, _pipe.get(), ec, nullptr);
if (ec) {
BOOST_LOG(warning) << "Couldn't spawn ["sv << cmd << "]: System: "sv << ec.message();
}
else {
} else {
child.detach();
}
}
@@ -421,8 +413,7 @@ namespace proc {
if (_app.cmd.empty()) {
BOOST_LOG(info) << "Executing [Desktop]"sv;
placebo = true;
}
else {
} else {
boost::filesystem::path working_dir = _app.working_dir.empty() ?
find_working_directory(_app.cmd, _env) :
boost::filesystem::path(_app.working_dir);
@@ -493,8 +484,7 @@ namespace proc {
return 0;
}
int
proc_t::running() {
int proc_t::running() {
#ifndef _WIN32
// On POSIX OSes, we must periodically wait for our children to avoid
// them becoming zombies. This must be synchronized carefully with
@@ -507,16 +497,13 @@ namespace proc {
if (placebo) {
return _app_id;
}
else if (_app.wait_all && _process_group && platf::process_group_running((std::uintptr_t) _process_group.native_handle())) {
} else if (_app.wait_all && _process_group && platf::process_group_running((std::uintptr_t) _process_group.native_handle())) {
// The app is still running if any process in the group is still running
return _app_id;
}
else if (_process.running()) {
} else if (_process.running()) {
// The app is still running only if the initial process launched is still running
return _app_id;
}
else if (_app.auto_detach && std::chrono::steady_clock::now() - _app_launch_time < 5s) {
} else if (_app.auto_detach && std::chrono::steady_clock::now() - _app_launch_time < 5s) {
BOOST_LOG(info) << "App exited with code ["sv << _process.native_exit_code() << "] within 5 seconds of launch. Treating the app as a detached command."sv;
BOOST_LOG(info) << "Adjust this behavior in the Applications tab or apps.json if this is not what you want."sv;
placebo = true;
@@ -538,8 +525,7 @@ namespace proc {
return 0;
}
void
proc_t::terminate(bool immediate) {
void proc_t::terminate(bool immediate) {
std::error_code ec;
placebo = false;
@@ -625,12 +611,11 @@ namespace proc {
allow_client_commands = false;
}
const std::vector<ctx_t> &
proc_t::get_apps() const {
const std::vector<ctx_t> &proc_t::get_apps() const {
return _apps;
}
std::vector<ctx_t> &
proc_t::get_apps() {
std::vector<ctx_t> &proc_t::get_apps() {
return _apps;
}
@@ -638,8 +623,7 @@ namespace proc {
// Returns image from assets directory if found there.
// Returns default image if image configuration is not set.
// Returns http content-type header compatible image type.
std::string
proc_t::get_app_image(int app_id) {
std::string proc_t::get_app_image(int app_id) {
auto iter = std::find_if(_apps.begin(), _apps.end(), [&app_id](const auto app) {
return app.id == std::to_string(app_id);
});
@@ -648,13 +632,11 @@ namespace proc {
return validate_app_image_path(app_image_path);
}
std::string
proc_t::get_last_run_app_name() {
std::string proc_t::get_last_run_app_name() {
return _app_name;
}
boost::process::environment
proc_t::get_env() {
boost::process::environment proc_t::get_env() {
return _env;
}
@@ -667,8 +649,7 @@ namespace proc {
assert(!_process.running());
}
std::string_view::iterator
find_match(std::string_view::iterator begin, std::string_view::iterator end) {
std::string_view::iterator find_match(std::string_view::iterator begin, std::string_view::iterator end) {
int stack = 0;
--begin;
@@ -689,8 +670,7 @@ namespace proc {
return begin;
}
std::string
parse_env_val(boost::process::v1::native_environment &env, const std::string_view &val_raw) {
std::string parse_env_val(boost::process::v1::native_environment &env, const std::string_view &val_raw) {
auto pos = std::begin(val_raw);
auto dollar = std::find(pos, std::end(val_raw), '$');
@@ -700,31 +680,33 @@ namespace proc {
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));
auto var_name = std::string { var_begin, var_end };
case '(':
{
ss.write(pos, (dollar - pos));
auto var_begin = next + 1;
auto var_end = find_match(next, std::end(val_raw));
auto var_name = std::string {var_begin, var_end};
#ifdef _WIN32
// Windows treats environment variable names in a case-insensitive manner,
// so we look for a case-insensitive match here. This is critical for
// correctly appending to PATH on Windows.
auto itr = std::find_if(env.cbegin(), env.cend(),
[&](const auto &e) { return boost::iequals(e.get_name(), var_name); });
if (itr != env.cend()) {
// Use an existing case-insensitive match
var_name = itr->get_name();
}
// Windows treats environment variable names in a case-insensitive manner,
// so we look for a case-insensitive match here. This is critical for
// correctly appending to PATH on Windows.
auto itr = std::find_if(env.cbegin(), env.cend(), [&](const auto &e) {
return boost::iequals(e.get_name(), var_name);
});
if (itr != env.cend()) {
// Use an existing case-insensitive match
var_name = itr->get_name();
}
#endif
ss << env[var_name].to_string();
ss << env[var_name].to_string();
pos = var_end + 1;
next = var_end;
pos = var_end + 1;
next = var_end;
break;
}
break;
}
case '$':
ss.write(pos, (next - pos));
pos = next + 1;
@@ -733,8 +715,7 @@ namespace proc {
}
dollar = std::find(next, std::end(val_raw), '$');
}
else {
} else {
dollar = next;
}
}
@@ -744,8 +725,7 @@ namespace proc {
return ss.str();
}
std::string
validate_app_image_path(std::string app_image_path) {
std::string validate_app_image_path(std::string app_image_path) {
if (app_image_path.empty()) {
return DEFAULT_APP_IMAGE_PATH;
}
@@ -763,8 +743,7 @@ namespace proc {
auto full_image_path = std::filesystem::path(SUNSHINE_ASSETS_DIR) / app_image_path;
if (std::filesystem::exists(full_image_path)) {
return full_image_path.string();
}
else if (app_image_path == "./assets/steam.png") {
} else if (app_image_path == "./assets/steam.png") {
// handle old default steam image definition
return SUNSHINE_ASSETS_DIR "/steam.png";
}
@@ -782,9 +761,8 @@ namespace proc {
return app_image_path;
}
std::optional<std::string>
calculate_sha256(const std::string &filename) {
crypto::md_ctx_t ctx { EVP_MD_CTX_create() };
std::optional<std::string> calculate_sha256(const std::string &filename) {
crypto::md_ctx_t ctx {EVP_MD_CTX_create()};
if (!ctx) {
return std::nullopt;
}
@@ -818,15 +796,13 @@ namespace proc {
return ss.str();
}
uint32_t
calculate_crc32(const std::string &input) {
uint32_t calculate_crc32(const std::string &input) {
boost::crc_32_type result;
result.process_bytes(input.data(), input.length());
return result.checksum();
}
std::tuple<std::string, std::string>
calculate_app_id(const std::string &app_name, std::string app_image_path, int index) {
std::tuple<std::string, std::string> calculate_app_id(const std::string &app_name, std::string app_image_path, int index) {
// Generate id by hashing name with image data if present
std::vector<std::string> to_hash;
to_hash.push_back(app_name);
@@ -835,8 +811,7 @@ namespace proc {
auto file_hash = calculate_sha256(file_path);
if (file_hash) {
to_hash.push_back(file_hash.value());
}
else {
} else {
// Fallback to just hashing image path
to_hash.push_back(file_path);
}
@@ -844,7 +819,9 @@ namespace proc {
// Create combined strings for hash
std::stringstream ss;
for_each(to_hash.begin(), to_hash.end(), [&ss](const std::string &s) { ss << s; });
for_each(to_hash.begin(), to_hash.end(), [&ss](const std::string &s) {
ss << s;
});
auto input_no_index = ss.str();
ss << index;
auto input_with_index = ss.str();
@@ -856,8 +833,7 @@ namespace proc {
return std::make_tuple(id_no_index, id_with_index);
}
void
migrate_apps(pt::ptree* fileTree_p, pt::ptree* inputTree_p) {
void migrate_apps(pt::ptree* fileTree_p, pt::ptree* inputTree_p) {
std::string new_app_uuid;
if (inputTree_p) {
@@ -912,8 +888,7 @@ namespace proc {
fileTree_p->push_back(std::make_pair("apps", newApps));
}
std::optional<proc::proc_t>
parse(const std::string &file_name) {
std::optional<proc::proc_t> parse(const std::string &file_name) {
pt::ptree tree;
try {
@@ -1101,7 +1076,8 @@ namespace proc {
prep_cmds.emplace_back(
parse_env_val(this_env, do_cmd.value_or("")),
parse_env_val(this_env, undo_cmd.value_or("")),
std::move(elevated.value_or(false)));
std::move(elevated.value_or(false))
);
}
}
@@ -1141,7 +1117,7 @@ namespace proc {
ctx.elevated = elevated.value_or(false);
ctx.auto_detach = auto_detach.value_or(true);
ctx.wait_all = wait_all.value_or(true);
ctx.exit_timeout = std::chrono::seconds { exit_timeout.value_or(5) };
ctx.exit_timeout = std::chrono::seconds {exit_timeout.value_or(5)};
ctx.virtual_display = virtual_display.value_or(false);
ctx.scale_factor = resolution_scale_factor.value_or(100);
ctx.use_app_identity = use_app_identity.value_or(false);
@@ -1152,8 +1128,7 @@ namespace proc {
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 {
} else {
// Fallback to include index on collision
ctx.id = std::get<1>(possible_ids);
}
@@ -1167,18 +1142,17 @@ namespace proc {
}
return proc::proc_t {
std::move(this_env), std::move(apps)
std::move(this_env),
std::move(apps)
};
}
catch (std::exception &e) {
} catch (std::exception &e) {
BOOST_LOG(error) << e.what();
}
return std::nullopt;
}
void
refresh(const std::string &file_name) {
void refresh(const std::string &file_name) {
proc.terminate();
#ifdef _WIN32
if (vDisplayDriverStatus != VDISPLAY::DRIVER_STATUS::OK) {