Fix #345
This commit is contained in:
@@ -514,44 +514,6 @@ namespace confighttp {
|
||||
std::string content = file_handler::read_file(config::stream.file_apps.c_str());
|
||||
nlohmann::json file_tree = nlohmann::json::parse(content);
|
||||
|
||||
// Legacy versions of Sunshine/Apollo used strings for boolean and integers, let's convert them
|
||||
// List of keys to convert to boolean
|
||||
std::vector<std::string> boolean_keys = {
|
||||
"exclude-global-prep-cmd",
|
||||
"elevated",
|
||||
"auto-detach",
|
||||
"wait-all",
|
||||
"use-app-identity",
|
||||
"per-client-app-identity",
|
||||
"virtual-display"
|
||||
};
|
||||
|
||||
// List of keys to convert to integers
|
||||
std::vector<std::string> integer_keys = {
|
||||
"exit-timeout"
|
||||
};
|
||||
|
||||
// Walk fileTree and convert true/false strings to boolean or integer values
|
||||
for (auto &app : file_tree["apps"]) {
|
||||
for (const auto &key : boolean_keys) {
|
||||
if (app.contains(key) && app[key].is_string()) {
|
||||
app[key] = app[key] == "true";
|
||||
}
|
||||
}
|
||||
for (const auto &key : integer_keys) {
|
||||
if (app.contains(key) && app[key].is_string()) {
|
||||
app[key] = std::stoi(app[key].get<std::string>());
|
||||
}
|
||||
}
|
||||
if (app.contains("prep-cmd")) {
|
||||
for (auto &prep : app["prep-cmd"]) {
|
||||
if (prep.contains("elevated") && prep["elevated"].is_string()) {
|
||||
prep["elevated"] = prep["elevated"] == "true";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file_tree["current_app"] = proc::proc.get_running_app_uuid();
|
||||
|
||||
send_response(response, file_tree);
|
||||
@@ -562,7 +524,8 @@ namespace confighttp {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Save an application. To save a new application the index must be `-1`. To update an existing application, you must provide the current index of the application.
|
||||
* @brief Save an application. To save a new application the UUID must be empty.
|
||||
* To update an existing application, you must provide the current UUID of the application.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
* The body for the post request should be JSON serialized in the following format:
|
||||
@@ -571,7 +534,6 @@ namespace confighttp {
|
||||
* "name": "Application Name",
|
||||
* "output": "Log Output Path",
|
||||
* "cmd": "Command to run the application",
|
||||
* "index": -1,
|
||||
* "exclude-global-prep-cmd": false,
|
||||
* "elevated": false,
|
||||
* "auto-detach": true,
|
||||
@@ -587,11 +549,12 @@ namespace confighttp {
|
||||
* "detached": [
|
||||
* "Detached command"
|
||||
* ],
|
||||
* "image-path": "Full path to the application image. Must be a png file."
|
||||
* "image-path": "Full path to the application image. Must be a png file.",
|
||||
* "uuid": "aaaa-bbbb"
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @api_examples{/api/apps| POST| {"name":"Hello, World!","index":-1}}
|
||||
* @api_examples{/api/apps| POST| {"name":"Hello, World!","uuid": "aaaa-bbbb"}}
|
||||
*/
|
||||
void saveApp(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
@@ -602,39 +565,31 @@ namespace confighttp {
|
||||
|
||||
std::stringstream ss;
|
||||
ss << request->content.rdbuf();
|
||||
|
||||
BOOST_LOG(info) << config::stream.file_apps;
|
||||
try {
|
||||
nlohmann::json input_tree = nlohmann::json::parse(ss.str());
|
||||
std::string file = file_handler::read_file(config::stream.file_apps.c_str());
|
||||
nlohmann::json file_tree = nlohmann::json::parse(file);
|
||||
// Remove empty keys if needed
|
||||
if (input_tree.contains("prep-cmd") && input_tree["prep-cmd"].empty())
|
||||
input_tree.erase("prep-cmd");
|
||||
if (input_tree.contains("detached") && input_tree["detached"].empty())
|
||||
input_tree.erase("detached");
|
||||
auto &apps_node = file_tree["apps"];
|
||||
int index = input_tree["index"].get<int>();
|
||||
input_tree.erase("index");
|
||||
if (index == -1) {
|
||||
apps_node.push_back(input_tree);
|
||||
} else {
|
||||
nlohmann::json newApps = nlohmann::json::array();
|
||||
for (size_t i = 0; i < apps_node.size(); ++i) {
|
||||
if (static_cast<int>(i) == index)
|
||||
newApps.push_back(input_tree);
|
||||
else
|
||||
newApps.push_back(apps_node[i]);
|
||||
}
|
||||
file_tree["apps"] = newApps;
|
||||
}
|
||||
std::sort(apps_node.begin(), apps_node.end(), [](const nlohmann::json &a, const nlohmann::json &b) {
|
||||
return a["name"].get<std::string>() < b["name"].get<std::string>();
|
||||
});
|
||||
file_handler::write_file(config::stream.file_apps.c_str(), file_tree.dump(4));
|
||||
// TODO: Input Validation
|
||||
|
||||
// Read the input JSON from the request body.
|
||||
nlohmann::json inputTree = nlohmann::json::parse(ss.str());
|
||||
|
||||
// Read the existing apps file.
|
||||
std::string content = file_handler::read_file(config::stream.file_apps.c_str());
|
||||
nlohmann::json fileTree = nlohmann::json::parse(content);
|
||||
|
||||
// Migrate/merge the new app into the file tree.
|
||||
proc::migrate_apps(&fileTree, &inputTree);
|
||||
|
||||
// Write the updated file tree back to disk.
|
||||
file_handler::write_file(config::stream.file_apps.c_str(), fileTree.dump(4));
|
||||
proc::refresh(config::stream.file_apps);
|
||||
nlohmann::json output_tree;
|
||||
output_tree["status"] = true;
|
||||
send_response(response, output_tree);
|
||||
} catch (std::exception &e) {
|
||||
|
||||
// Prepare and send the output response.
|
||||
nlohmann::json outputTree;
|
||||
outputTree["status"] = true;
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "SaveApp: "sv << e.what();
|
||||
bad_request(response, request, e.what());
|
||||
}
|
||||
@@ -668,41 +623,44 @@ namespace confighttp {
|
||||
* @api_examples{/api/apps/9999| DELETE| null}
|
||||
*/
|
||||
void deleteApp(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
if (!authenticate(response, request))
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
auto args = request->parse_query_string();
|
||||
if (args.find("uuid"s) == std::end(args)) {
|
||||
bad_request(response, request, "Missing a required parameter to delete app");
|
||||
return;
|
||||
}
|
||||
auto uuid = nvhttp::get_arg(args, "uuid");
|
||||
|
||||
try {
|
||||
nlohmann::json output_tree;
|
||||
nlohmann::json new_apps = nlohmann::json::array();
|
||||
std::string file = file_handler::read_file(config::stream.file_apps.c_str());
|
||||
nlohmann::json file_tree = nlohmann::json::parse(file);
|
||||
auto &apps_node = file_tree["apps"];
|
||||
// In this merged version we assume the app index is part of the URL match (convert as needed)
|
||||
const int index = std::stoi(request->path_match[1]);
|
||||
if (index < 0 || index >= static_cast<int>(apps_node.size())) {
|
||||
std::string error;
|
||||
if (apps_node.empty()) {
|
||||
error = "No applications to delete";
|
||||
} else {
|
||||
error = "'index' out of range, max index is " + std::to_string(apps_node.size() - 1);
|
||||
}
|
||||
bad_request(response, request, error);
|
||||
return;
|
||||
// Read the apps file into a nlohmann::json object.
|
||||
std::string content = file_handler::read_file(config::stream.file_apps.c_str());
|
||||
nlohmann::json fileTree = nlohmann::json::parse(content);
|
||||
|
||||
// Remove any app with the matching uuid directly from the "apps" array.
|
||||
if (fileTree.contains("apps") && fileTree["apps"].is_array()) {
|
||||
auto& apps = fileTree["apps"];
|
||||
apps.erase(
|
||||
std::remove_if(apps.begin(), apps.end(), [&uuid](const nlohmann::json& app) {
|
||||
return app.value("uuid", "") == uuid;
|
||||
}),
|
||||
apps.end()
|
||||
);
|
||||
}
|
||||
for (size_t i = 0; i < apps_node.size(); ++i) {
|
||||
if (static_cast<int>(i) != index)
|
||||
new_apps.push_back(apps_node[i]);
|
||||
}
|
||||
file_tree["apps"] = new_apps;
|
||||
file_handler::write_file(config::stream.file_apps.c_str(), file_tree.dump(4));
|
||||
|
||||
// Write the updated JSON back to the file.
|
||||
file_handler::write_file(config::stream.file_apps.c_str(), fileTree.dump(4));
|
||||
proc::refresh(config::stream.file_apps);
|
||||
output_tree["status"] = true;
|
||||
output_tree["result"] = "application " + std::to_string(index) + " deleted";
|
||||
send_response(response, output_tree);
|
||||
} catch (std::exception &e) {
|
||||
|
||||
// Prepare and send the response.
|
||||
nlohmann::json outputTree;
|
||||
outputTree["status"] = true;
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "DeleteApp: "sv << e.what();
|
||||
bad_request(response, request, e.what());
|
||||
}
|
||||
|
||||
539
src/process.cpp
539
src/process.cpp
@@ -24,6 +24,7 @@
|
||||
#include "config.h"
|
||||
#include "crypto.h"
|
||||
#include "display_device.h"
|
||||
#include "file_handler.h"
|
||||
#include "logging.h"
|
||||
#include "platform/common.h"
|
||||
#include "process.h"
|
||||
@@ -838,158 +839,280 @@ namespace proc {
|
||||
return std::make_tuple(id_no_index, id_with_index);
|
||||
}
|
||||
|
||||
void migrate_apps(pt::ptree* fileTree_p, pt::ptree* inputTree_p) {
|
||||
/**
|
||||
* @brief Migrate the applications stored in the file tree by merging in a new app.
|
||||
*
|
||||
* This function updates the application entries in *fileTree_p* using the data in *inputTree_p*.
|
||||
* If an app in the file tree does not have a UUID, one is generated and inserted.
|
||||
* If an app with the same UUID as the new app is found, it is replaced.
|
||||
* Additionally, empty keys (such as "prep-cmd" or "detached") and keys no longer needed ("launching", "index")
|
||||
* are removed from the input.
|
||||
*
|
||||
* Legacy versions of Sunshine/Apollo stored boolean and integer values as strings.
|
||||
* The following keys are converted:
|
||||
* - Boolean keys: "exclude-global-prep-cmd", "elevated", "auto-detach", "wait-all",
|
||||
* "use-app-identity", "per-client-app-identity", "virtual-display"
|
||||
* - Integer keys: "exit-timeout"
|
||||
*
|
||||
* A migration version is stored in the file tree (under "version") so that future changes can be applied.
|
||||
*
|
||||
* @param fileTree_p Pointer to the JSON object representing the file tree.
|
||||
* @param inputTree_p Pointer to the JSON object representing the new app.
|
||||
*/
|
||||
void migrate_apps(nlohmann::json* fileTree_p, nlohmann::json* inputTree_p) {
|
||||
std::string new_app_uuid;
|
||||
|
||||
if (inputTree_p) {
|
||||
auto input_uuid = inputTree_p->get_optional<std::string>("uuid"s);
|
||||
if (input_uuid && !input_uuid.value().empty()) {
|
||||
new_app_uuid = input_uuid.value();
|
||||
// If the input contains a non-empty "uuid", use it; otherwise generate one.
|
||||
if (inputTree_p->contains("uuid") && !(*inputTree_p)["uuid"].get<std::string>().empty()) {
|
||||
new_app_uuid = (*inputTree_p)["uuid"].get<std::string>();
|
||||
} else {
|
||||
new_app_uuid = uuid_util::uuid_t::generate().string();
|
||||
inputTree_p->erase("uuid");
|
||||
inputTree_p->put("uuid", new_app_uuid);
|
||||
(*inputTree_p)["uuid"] = new_app_uuid;
|
||||
}
|
||||
|
||||
if (inputTree_p->get_child("prep-cmd").empty()) {
|
||||
// Remove "prep-cmd" if empty.
|
||||
if (inputTree_p->contains("prep-cmd") && (*inputTree_p)["prep-cmd"].empty()) {
|
||||
inputTree_p->erase("prep-cmd");
|
||||
}
|
||||
|
||||
if (inputTree_p->get_child("detached").empty()) {
|
||||
// Remove "detached" if empty.
|
||||
if (inputTree_p->contains("detached") && (*inputTree_p)["detached"].empty()) {
|
||||
inputTree_p->erase("detached");
|
||||
}
|
||||
|
||||
// Remove keys that are no longer needed.
|
||||
inputTree_p->erase("launching");
|
||||
inputTree_p->erase("index");
|
||||
}
|
||||
|
||||
auto &apps_node = fileTree_p->get_child("apps"s);
|
||||
|
||||
pt::ptree newApps;
|
||||
for (auto &kv : apps_node) {
|
||||
// Check if we have apps that have not got an uuid assigned
|
||||
auto app_uuid = kv.second.get_optional<std::string>("uuid"s);
|
||||
if (!app_uuid || app_uuid.value().empty()) {
|
||||
kv.second.erase("uuid");
|
||||
kv.second.put("uuid", uuid_util::uuid_t::generate().string());
|
||||
kv.second.erase("launching");
|
||||
newApps.push_back(std::make_pair("", std::move(kv.second)));
|
||||
} else {
|
||||
if (!new_app_uuid.empty() && app_uuid.value() == new_app_uuid) {
|
||||
newApps.push_back(std::make_pair("", *inputTree_p));
|
||||
new_app_uuid.clear();
|
||||
// Get the current apps array; if it doesn't exist, create one.
|
||||
nlohmann::json newApps = nlohmann::json::array();
|
||||
if (fileTree_p->contains("apps") && (*fileTree_p)["apps"].is_array()) {
|
||||
for (auto &app : (*fileTree_p)["apps"]) {
|
||||
// For apps without a UUID, generate one and remove "launching".
|
||||
if (!app.contains("uuid") || app["uuid"].get<std::string>().empty()) {
|
||||
app["uuid"] = uuid_util::uuid_t::generate().string();
|
||||
app.erase("launching");
|
||||
newApps.push_back(std::move(app));
|
||||
} else {
|
||||
newApps.push_back(std::make_pair("", std::move(kv.second)));
|
||||
// If an app with the same UUID as the new app is found, replace it.
|
||||
if (!new_app_uuid.empty() && app["uuid"].get<std::string>() == new_app_uuid) {
|
||||
newApps.push_back(*inputTree_p);
|
||||
new_app_uuid.clear();
|
||||
} else {
|
||||
newApps.push_back(std::move(app));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the new app's UUID has not been merged yet, add it.
|
||||
if (!new_app_uuid.empty() && inputTree_p) {
|
||||
newApps.push_back(*inputTree_p);
|
||||
}
|
||||
(*fileTree_p)["apps"] = newApps;
|
||||
}
|
||||
|
||||
// Finally add the new app
|
||||
if (!new_app_uuid.empty()) {
|
||||
newApps.push_back(std::make_pair("", *inputTree_p));
|
||||
void migration_v2(nlohmann::json& fileTree) {
|
||||
int this_version = 2;
|
||||
// Determine the current migration version (default to 1 if not present).
|
||||
int file_version = 1;
|
||||
if (fileTree.contains("version")) {
|
||||
file_version = fileTree["version"].get<int>();
|
||||
}
|
||||
|
||||
fileTree_p->erase("apps");
|
||||
fileTree_p->push_back(std::make_pair("apps", newApps));
|
||||
// If the version is less than this_version, perform legacy conversion.
|
||||
if (file_version < this_version) {
|
||||
BOOST_LOG(info) << "Migrating app list from v1 to v2...";
|
||||
migrate_apps(&fileTree, nullptr);
|
||||
|
||||
// List of keys to convert to booleans.
|
||||
std::vector<std::string> boolean_keys = {
|
||||
"allow-client-commands",
|
||||
"exclude-global-prep-cmd",
|
||||
"elevated",
|
||||
"auto-detach",
|
||||
"wait-all",
|
||||
"use-app-identity",
|
||||
"per-client-app-identity",
|
||||
"virtual-display"
|
||||
};
|
||||
|
||||
// List of keys to convert to integers.
|
||||
std::vector<std::string> integer_keys = {
|
||||
"exit-timeout",
|
||||
"scale-factor"
|
||||
};
|
||||
|
||||
// Walk through each app and convert legacy string values.
|
||||
for (auto &app : fileTree["apps"]) {
|
||||
for (const auto &key : boolean_keys) {
|
||||
if (app.contains(key) && app[key].is_string()) {
|
||||
std::string s = app[key].get<std::string>();
|
||||
app[key] = (s == "true");
|
||||
}
|
||||
}
|
||||
for (const auto &key : integer_keys) {
|
||||
if (app.contains(key) && app[key].is_string()) {
|
||||
std::string s = app[key].get<std::string>();
|
||||
app[key] = std::stoi(s);
|
||||
}
|
||||
}
|
||||
// For each entry in the "prep-cmd" array, convert "elevated" if necessary.
|
||||
if (app.contains("prep-cmd") && app["prep-cmd"].is_array()) {
|
||||
for (auto &prep : app["prep-cmd"]) {
|
||||
if (prep.contains("elevated") && prep["elevated"].is_string()) {
|
||||
std::string s = prep["elevated"].get<std::string>();
|
||||
prep["elevated"] = (s == "true");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update migration version to this_version.
|
||||
fileTree["version"] = this_version;
|
||||
|
||||
BOOST_LOG(info) << "Migrated app list from v1 to v2.";
|
||||
}
|
||||
}
|
||||
|
||||
void migrate(nlohmann::json& fileTree, const std::string& fileName) {
|
||||
int last_version = 2;
|
||||
|
||||
int file_version = 0;
|
||||
if (fileTree.contains("version")) {
|
||||
file_version = fileTree["version"].get<int>();
|
||||
}
|
||||
|
||||
if (file_version < last_version) {
|
||||
migration_v2(fileTree);
|
||||
file_handler::write_file(fileName.c_str(), fileTree.dump(4));
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<proc::proc_t> parse(const std::string &file_name) {
|
||||
pt::ptree tree;
|
||||
// Prepare environment variables.
|
||||
auto this_env = boost::this_process::environment();
|
||||
|
||||
std::set<std::string> ids;
|
||||
std::vector<proc::ctx_t> apps;
|
||||
int i = 0;
|
||||
|
||||
try {
|
||||
pt::read_json(file_name, tree);
|
||||
// Read the JSON file into a tree.
|
||||
std::string content = file_handler::read_file(file_name.c_str());
|
||||
nlohmann::json tree = nlohmann::json::parse(content);
|
||||
|
||||
auto &apps_node = tree.get_child("apps"s);
|
||||
auto &env_vars = tree.get_child("env"s);
|
||||
migrate(tree, file_name);
|
||||
|
||||
auto this_env = boost::this_process::environment();
|
||||
|
||||
for (auto &[name, val] : env_vars) {
|
||||
this_env[name] = parse_env_val(this_env, val.get_value<std::string>());
|
||||
}
|
||||
|
||||
std::set<std::string> ids;
|
||||
std::vector<proc::ctx_t> apps;
|
||||
int i = 0;
|
||||
|
||||
if (config::input.enable_input_only_mode) {
|
||||
// Input Only entry
|
||||
{
|
||||
proc::ctx_t ctx;
|
||||
// ctx.uuid = ""; // We're not using uuid for this special entry
|
||||
ctx.name = "Remote Input";
|
||||
ctx.image_path = parse_env_val(this_env, "input_only.png");
|
||||
ctx.virtual_display = false;
|
||||
ctx.scale_factor = 100;
|
||||
ctx.use_app_identity = false;
|
||||
ctx.per_client_app_identity = false;
|
||||
ctx.allow_client_commands = false;
|
||||
|
||||
ctx.elevated = false;
|
||||
ctx.auto_detach = true;
|
||||
ctx.wait_all = true;
|
||||
ctx.exit_timeout = 5s;
|
||||
|
||||
auto possible_ids = calculate_app_id(ctx.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);
|
||||
}
|
||||
ids.insert(ctx.id);
|
||||
|
||||
input_only_app_id_str = ctx.id;
|
||||
input_only_app_id = util::from_view(ctx.id);
|
||||
|
||||
apps.emplace_back(std::move(ctx));
|
||||
}
|
||||
|
||||
// Terminate entry
|
||||
{
|
||||
proc::ctx_t ctx;
|
||||
// ctx.uuid = ""; // We're not using uuid for this special entry
|
||||
ctx.name = "Terminate";
|
||||
ctx.image_path = parse_env_val(this_env, "terminate.png");
|
||||
ctx.virtual_display = false;
|
||||
ctx.scale_factor = 100;
|
||||
ctx.use_app_identity = false;
|
||||
ctx.per_client_app_identity = false;
|
||||
ctx.allow_client_commands = false;
|
||||
|
||||
ctx.elevated = false;
|
||||
ctx.auto_detach = true;
|
||||
ctx.wait_all = true;
|
||||
ctx.exit_timeout = 5s;
|
||||
|
||||
auto possible_ids = calculate_app_id(ctx.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);
|
||||
}
|
||||
ids.insert(ctx.id);
|
||||
|
||||
terminate_app_id_str = ctx.id;
|
||||
terminate_app_id = util::from_view(ctx.id);
|
||||
|
||||
apps.emplace_back(std::move(ctx));
|
||||
if (tree.contains("env") && tree["env"].is_object()) {
|
||||
for (auto &item : tree["env"].items()) {
|
||||
this_env[item.key()] = parse_env_val(this_env, item.value().get<std::string>());
|
||||
}
|
||||
}
|
||||
|
||||
// Virtual Display entry
|
||||
#ifdef _WIN32
|
||||
if (vDisplayDriverStatus == VDISPLAY::DRIVER_STATUS::OK) {
|
||||
// Ensure the "apps" array exists.
|
||||
if (!tree.contains("apps") || !tree["apps"].is_array()) {
|
||||
BOOST_LOG(warning) << "No apps were defined in apps.json!!!";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Iterate over each application in the "apps" array.
|
||||
for (auto &app_node : tree["apps"]) {
|
||||
proc::ctx_t ctx;
|
||||
ctx.uuid = app_node.value("uuid", "");
|
||||
|
||||
// Build the list of preparation commands.
|
||||
std::vector<proc::cmd_t> prep_cmds;
|
||||
bool exclude_global_prep = app_node.value("exclude-global-prep-cmd", false);
|
||||
if (!exclude_global_prep) {
|
||||
prep_cmds.reserve(config::sunshine.prep_cmds.size());
|
||||
for (auto &prep_cmd : config::sunshine.prep_cmds) {
|
||||
auto do_cmd = parse_env_val(this_env, prep_cmd.do_cmd);
|
||||
auto undo_cmd = parse_env_val(this_env, prep_cmd.undo_cmd);
|
||||
prep_cmds.emplace_back(
|
||||
std::move(do_cmd),
|
||||
std::move(undo_cmd),
|
||||
std::move(prep_cmd.elevated)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (app_node.contains("prep-cmd") && app_node["prep-cmd"].is_array()) {
|
||||
for (auto &prep_node : app_node["prep-cmd"]) {
|
||||
std::string do_cmd = parse_env_val(this_env, prep_node.value("do", ""));
|
||||
std::string undo_cmd = parse_env_val(this_env, prep_node.value("undo", ""));
|
||||
bool elevated = prep_node.value("elevated", false);
|
||||
prep_cmds.emplace_back(
|
||||
std::move(do_cmd),
|
||||
std::move(undo_cmd),
|
||||
std::move(elevated)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Build the list of detached commands.
|
||||
std::vector<std::string> detached;
|
||||
if (app_node.contains("detached") && app_node["detached"].is_array()) {
|
||||
for (auto &detached_val : app_node["detached"]) {
|
||||
detached.emplace_back(parse_env_val(this_env, detached_val.get<std::string>()));
|
||||
}
|
||||
}
|
||||
|
||||
// Process other fields.
|
||||
if (app_node.contains("output"))
|
||||
ctx.output = parse_env_val(this_env, app_node.value("output", ""));
|
||||
std::string name = parse_env_val(this_env, app_node.value("name", ""));
|
||||
if (app_node.contains("cmd"))
|
||||
ctx.cmd = parse_env_val(this_env, app_node.value("cmd", ""));
|
||||
if (app_node.contains("working-dir")) {
|
||||
ctx.working_dir = parse_env_val(this_env, app_node.value("working-dir", ""));
|
||||
#ifdef _WIN32
|
||||
// The working directory, unlike the command itself, should not be quoted.
|
||||
boost::erase_all(ctx.working_dir, "\"");
|
||||
ctx.working_dir += '\\';
|
||||
#endif
|
||||
}
|
||||
if (app_node.contains("image-path"))
|
||||
ctx.image_path = parse_env_val(this_env, app_node.value("image-path", ""));
|
||||
|
||||
ctx.elevated = app_node.value("elevated", false);
|
||||
ctx.auto_detach = app_node.value("auto-detach", true);
|
||||
ctx.wait_all = app_node.value("wait-all", true);
|
||||
ctx.exit_timeout = std::chrono::seconds { app_node.value("exit-timeout", 5) };
|
||||
ctx.virtual_display = app_node.value("virtual-display", false);
|
||||
ctx.scale_factor = app_node.value("scale-factor", 100);
|
||||
ctx.use_app_identity = app_node.value("use-app-identity", false);
|
||||
ctx.per_client_app_identity = app_node.value("per-client-app-identity", false);
|
||||
ctx.allow_client_commands = app_node.value("allow-client-commands", true);
|
||||
|
||||
// Calculate a unique application id.
|
||||
auto possible_ids = calculate_app_id(name, ctx.image_path, i++);
|
||||
if (ids.count(std::get<0>(possible_ids)) == 0) {
|
||||
ctx.id = std::get<0>(possible_ids);
|
||||
} else {
|
||||
ctx.id = std::get<1>(possible_ids);
|
||||
}
|
||||
ids.insert(ctx.id);
|
||||
|
||||
ctx.name = std::move(name);
|
||||
ctx.prep_cmds = std::move(prep_cmds);
|
||||
ctx.detached = std::move(detached);
|
||||
|
||||
apps.emplace_back(std::move(ctx));
|
||||
}
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(error) << e.what();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (config::input.enable_input_only_mode) {
|
||||
// Input Only entry
|
||||
{
|
||||
proc::ctx_t ctx;
|
||||
// ctx.uuid = ""; // We're not using uuid for this special entry
|
||||
ctx.name = "Virtual Display";
|
||||
ctx.image_path = parse_env_val(this_env, "virtual_desktop.png");
|
||||
ctx.virtual_display = true;
|
||||
ctx.name = "Remote Input";
|
||||
ctx.image_path = parse_env_val(this_env, "input_only.png");
|
||||
ctx.virtual_display = false;
|
||||
ctx.scale_factor = 100;
|
||||
ctx.use_app_identity = false;
|
||||
ctx.per_client_app_identity = false;
|
||||
@@ -1011,150 +1134,84 @@ namespace proc {
|
||||
}
|
||||
ids.insert(ctx.id);
|
||||
|
||||
input_only_app_id_str = ctx.id;
|
||||
input_only_app_id = util::from_view(ctx.id);
|
||||
|
||||
apps.emplace_back(std::move(ctx));
|
||||
}
|
||||
#endif
|
||||
|
||||
for (auto &[_, app_node] : apps_node) {
|
||||
// Terminate entry
|
||||
{
|
||||
proc::ctx_t ctx;
|
||||
// ctx.uuid = ""; // We're not using uuid for this special entry
|
||||
ctx.name = "Terminate";
|
||||
ctx.image_path = parse_env_val(this_env, "terminate.png");
|
||||
ctx.virtual_display = false;
|
||||
ctx.scale_factor = 100;
|
||||
ctx.use_app_identity = false;
|
||||
ctx.per_client_app_identity = false;
|
||||
ctx.allow_client_commands = false;
|
||||
|
||||
auto app_uuid = app_node.get_optional<std::string>("uuid"s);
|
||||
ctx.elevated = false;
|
||||
ctx.auto_detach = true;
|
||||
ctx.wait_all = true;
|
||||
ctx.exit_timeout = 5s;
|
||||
|
||||
if (!app_uuid) {
|
||||
// We need an upgrade to the app list
|
||||
try {
|
||||
BOOST_LOG(info) << "Migrating app list...";
|
||||
migrate_apps(&tree, nullptr);
|
||||
pt::write_json(file_name, tree);
|
||||
BOOST_LOG(info) << "Migration complete.";
|
||||
return parse(file_name);
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "Error happened wilie migrating the app list: "sv << e.what();
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
auto prep_nodes_opt = app_node.get_child_optional("prep-cmd"s);
|
||||
auto detached_nodes_opt = app_node.get_child_optional("detached"s);
|
||||
auto exclude_global_prep = app_node.get_optional<bool>("exclude-global-prep-cmd"s);
|
||||
auto output = app_node.get_optional<std::string>("output"s);
|
||||
auto name = parse_env_val(this_env, app_node.get<std::string>("name"s));
|
||||
auto cmd = app_node.get_optional<std::string>("cmd"s);
|
||||
auto image_path = app_node.get_optional<std::string>("image-path"s);
|
||||
auto working_dir = app_node.get_optional<std::string>("working-dir"s);
|
||||
auto elevated = app_node.get_optional<bool>("elevated"s);
|
||||
auto auto_detach = app_node.get_optional<bool>("auto-detach"s);
|
||||
auto wait_all = app_node.get_optional<bool>("wait-all"s);
|
||||
auto exit_timeout = app_node.get_optional<int>("exit-timeout"s);
|
||||
auto virtual_display = app_node.get_optional<bool>("virtual-display"s);
|
||||
auto resolution_scale_factor = app_node.get_optional<int>("scale-factor"s);
|
||||
auto use_app_identity = app_node.get_optional<bool>("use-app-identity"s);
|
||||
auto per_client_app_identity = app_node.get_optional<bool>("per-client-app-identity");
|
||||
auto allow_client_commands = app_node.get_optional<bool>("allow-client-commands");
|
||||
|
||||
ctx.uuid = app_uuid.value();
|
||||
|
||||
std::vector<proc::cmd_t> prep_cmds;
|
||||
if (!exclude_global_prep.value_or(false)) {
|
||||
prep_cmds.reserve(config::sunshine.prep_cmds.size());
|
||||
for (auto &prep_cmd : config::sunshine.prep_cmds) {
|
||||
auto do_cmd = parse_env_val(this_env, prep_cmd.do_cmd);
|
||||
auto undo_cmd = parse_env_val(this_env, prep_cmd.undo_cmd);
|
||||
|
||||
prep_cmds.emplace_back(
|
||||
std::move(do_cmd),
|
||||
std::move(undo_cmd),
|
||||
std::move(prep_cmd.elevated)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (prep_nodes_opt) {
|
||||
auto &prep_nodes = *prep_nodes_opt;
|
||||
|
||||
prep_cmds.reserve(prep_cmds.size() + prep_nodes.size());
|
||||
for (auto &[_, prep_node] : prep_nodes) {
|
||||
auto do_cmd = prep_node.get_optional<std::string>("do"s);
|
||||
auto undo_cmd = prep_node.get_optional<std::string>("undo"s);
|
||||
auto elevated = prep_node.get_optional<bool>("elevated");
|
||||
|
||||
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::vector<std::string> detached;
|
||||
if (detached_nodes_opt) {
|
||||
auto &detached_nodes = *detached_nodes_opt;
|
||||
|
||||
detached.reserve(detached_nodes.size());
|
||||
for (auto &[_, detached_val] : detached_nodes) {
|
||||
detached.emplace_back(parse_env_val(this_env, detached_val.get_value<std::string>()));
|
||||
}
|
||||
}
|
||||
|
||||
if (output) {
|
||||
ctx.output = parse_env_val(this_env, *output);
|
||||
}
|
||||
|
||||
if (cmd) {
|
||||
ctx.cmd = parse_env_val(this_env, *cmd);
|
||||
}
|
||||
|
||||
if (working_dir) {
|
||||
ctx.working_dir = parse_env_val(this_env, *working_dir);
|
||||
#ifdef _WIN32
|
||||
// The working directory, unlike the command itself, should not be quoted
|
||||
// when it contains spaces. Unlike POSIX, Windows forbids quotes in paths,
|
||||
// so we can safely strip them all out here to avoid confusing the user.
|
||||
boost::erase_all(ctx.working_dir, "\"");
|
||||
ctx.working_dir += '\\';
|
||||
#endif
|
||||
}
|
||||
|
||||
if (image_path) {
|
||||
ctx.image_path = parse_env_val(this_env, *image_path);
|
||||
}
|
||||
|
||||
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.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);
|
||||
ctx.per_client_app_identity = per_client_app_identity.value_or(false);
|
||||
ctx.allow_client_commands = allow_client_commands.value_or(true);
|
||||
|
||||
auto possible_ids = calculate_app_id(name, ctx.image_path, i++);
|
||||
auto possible_ids = calculate_app_id(ctx.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 {
|
||||
}
|
||||
else {
|
||||
// Fallback to include index on collision
|
||||
ctx.id = std::get<1>(possible_ids);
|
||||
}
|
||||
ids.insert(ctx.id);
|
||||
|
||||
ctx.name = std::move(name);
|
||||
ctx.prep_cmds = std::move(prep_cmds);
|
||||
ctx.detached = std::move(detached);
|
||||
terminate_app_id_str = ctx.id;
|
||||
terminate_app_id = util::from_view(ctx.id);
|
||||
|
||||
apps.emplace_back(std::move(ctx));
|
||||
}
|
||||
|
||||
return proc::proc_t {
|
||||
std::move(this_env),
|
||||
std::move(apps)
|
||||
};
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(error) << e.what();
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
// Virtual Display entry
|
||||
#ifdef _WIN32
|
||||
if (vDisplayDriverStatus == VDISPLAY::DRIVER_STATUS::OK) {
|
||||
proc::ctx_t ctx;
|
||||
// ctx.uuid = ""; // We're not using uuid for this special entry
|
||||
ctx.name = "Virtual Display";
|
||||
ctx.image_path = parse_env_val(this_env, "virtual_desktop.png");
|
||||
ctx.virtual_display = true;
|
||||
ctx.scale_factor = 100;
|
||||
ctx.use_app_identity = false;
|
||||
ctx.per_client_app_identity = false;
|
||||
ctx.allow_client_commands = false;
|
||||
|
||||
ctx.elevated = false;
|
||||
ctx.auto_detach = true;
|
||||
ctx.wait_all = true;
|
||||
ctx.exit_timeout = 5s;
|
||||
|
||||
auto possible_ids = calculate_app_id(ctx.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);
|
||||
}
|
||||
ids.insert(ctx.id);
|
||||
|
||||
apps.emplace_back(std::move(ctx));
|
||||
}
|
||||
#endif
|
||||
|
||||
return proc::proc_t {
|
||||
std::move(this_env),
|
||||
std::move(apps)
|
||||
};
|
||||
}
|
||||
|
||||
void refresh(const std::string &file_name) {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// lib includes
|
||||
#include <boost/process/v1.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// local includes
|
||||
#include "config.h"
|
||||
@@ -155,7 +156,7 @@ namespace proc {
|
||||
|
||||
std::string validate_app_image_path(std::string app_image_path);
|
||||
void refresh(const std::string &file_name);
|
||||
void migrate_apps(boost::property_tree::ptree* fileTree_p, boost::property_tree::ptree* inputTree_p);
|
||||
void migrate_apps(nlohmann::json* fileTree_p, nlohmann::json* inputTree_p);
|
||||
std::optional<proc::proc_t> parse(const std::string &file_name);
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user