diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index 3f4cd2ee..d22555f6 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -107,6 +107,20 @@ log_path log_path = sunshine.log +global_prep_cmd +^^^^^^^^^^^^^^^ + +**Description** + A list of commands to be run before/after all applications. If any of the prep-commands fail, starting the application is aborted. + +**Default** + ``[]`` + +**Example** + .. code-block:: text + + global_prep_cmd = [{"do":"nircmd.exe setdisplay 1280 720 32 144","undo":"nircmd.exe setdisplay 2560 1440 32 144"}] + Controls -------- diff --git a/src/config.cpp b/src/config.cpp index 89c7dba1..4a454b41 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -7,6 +7,8 @@ #include #include +#include +#include #include "config.h" #include "main.h" @@ -426,6 +428,7 @@ sunshine_t sunshine { {}, // cmd args 47989, platf::appdata().string() + "/sunshine.log", // log file + {}, // prep commands }; bool endline(char ch) { @@ -759,6 +762,27 @@ void list_string_f(std::unordered_map &vars, const std input.emplace_back(begin, pos); } } +void list_prep_cmd_f(std::unordered_map &vars, const std::string &name, std::vector &input) { + std::string string; + string_f(vars, name, string); + + std::stringstream jsonStream; + + // We need to add a wrapping object to make it valid JSON, otherwise ptree cannot parse it. + jsonStream << "{\"prep_cmd\":" << string << "}"; + + boost::property_tree::ptree jsonTree; + boost::property_tree::read_json(jsonStream, jsonTree); + + for(auto &[_, prep_cmd] : jsonTree.get_child("prep_cmd"s)) { + auto do_cmd = prep_cmd.get("do"s); + auto undo_cmd = prep_cmd.get("undo"s); + + input.emplace_back( + std::move(do_cmd), + std::move(undo_cmd)); + } +} void list_int_f(std::unordered_map &vars, const std::string &name, std::vector &input) { std::vector list; @@ -902,6 +926,7 @@ void apply_config(std::unordered_map &&vars) { string_f(vars, "external_ip", nvhttp.external_ip); list_string_f(vars, "resolutions"s, nvhttp.resolutions); list_int_f(vars, "fps"s, nvhttp.fps); + list_prep_cmd_f(vars, "global_prep_cmd", config::sunshine.prep_cmds); string_f(vars, "audio_sink", audio.sink); string_f(vars, "virtual_sink", audio.virtual_sink); diff --git a/src/config.h b/src/config.h index 3a2ff822..28871833 100644 --- a/src/config.h +++ b/src/config.h @@ -118,6 +118,13 @@ enum flag_e : std::size_t { }; } +struct prep_cmd_t { + prep_cmd_t(std::string &&do_cmd, std::string &&undo_cmd) : do_cmd(std::move(do_cmd)), undo_cmd(std::move(undo_cmd)) {} + explicit prep_cmd_t(std::string &&do_cmd) : do_cmd(std::move(do_cmd)) {} + std::string do_cmd; + std::string undo_cmd; +}; + struct sunshine_t { int min_log_level; std::bitset flags; @@ -137,6 +144,8 @@ struct sunshine_t { std::uint16_t port; std::string log_file; + + std::vector prep_cmds; }; extern video_t video; diff --git a/src/process.cpp b/src/process.cpp index 000329e1..32ab32ff 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -18,6 +18,7 @@ #include #include +#include "config.h" #include "crypto.h" #include "main.h" #include "platform/common.h" @@ -457,19 +458,30 @@ std::optional parse(const std::string &file_name) { for(auto &[_, app_node] : apps_node) { proc::ctx_t ctx; - auto prep_nodes_opt = app_node.get_child_optional("prep-cmd"s); - auto detached_nodes_opt = app_node.get_child_optional("detached"s); - auto output = app_node.get_optional("output"s); - auto name = parse_env_val(this_env, app_node.get("name"s)); - auto cmd = app_node.get_optional("cmd"s); - auto image_path = app_node.get_optional("image-path"s); - auto working_dir = app_node.get_optional("working-dir"s); + 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("exclude-global-prep-cmd"s); + auto output = app_node.get_optional("output"s); + auto name = parse_env_val(this_env, app_node.get("name"s)); + auto cmd = app_node.get_optional("cmd"s); + auto image_path = app_node.get_optional("image-path"s); + auto working_dir = app_node.get_optional("working-dir"s); std::vector 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)); + } + } + if(prep_nodes_opt) { auto &prep_nodes = *prep_nodes_opt; - prep_cmds.reserve(prep_nodes.size()); + prep_cmds.reserve(prep_cmds.size() + prep_nodes.size()); for(auto &[_, prep_node] : prep_nodes) { auto do_cmd = parse_env_val(this_env, prep_node.get("do"s)); auto undo_cmd = prep_node.get_optional("undo"s); diff --git a/src/process.h b/src/process.h index 99af563d..eab67779 100644 --- a/src/process.h +++ b/src/process.h @@ -12,20 +12,13 @@ #include +#include "config.h" #include "utility.h" namespace proc { using file_t = util::safe_ptr_v2; -struct cmd_t { - cmd_t(std::string &&do_cmd, std::string &&undo_cmd) : do_cmd(std::move(do_cmd)), undo_cmd(std::move(undo_cmd)) {} - explicit cmd_t(std::string &&do_cmd) : do_cmd(std::move(do_cmd)) {} - - std::string do_cmd; - - // Executed when proc_t has finished running, meant to reverse 'do_cmd' if applicable - std::string undo_cmd; -}; +typedef config::prep_cmd_t cmd_t; /* * pre_cmds -- guaranteed to be executed unless any of the commands fail. * detached -- commands detached from Sunshine diff --git a/src_assets/common/assets/web/apps.html b/src_assets/common/assets/web/apps.html index 87c22c0d..f026cba0 100644 --- a/src_assets/common/assets/web/apps.html +++ b/src_assets/common/assets/web/apps.html @@ -57,9 +57,18 @@
+
+ + +
+ Enable/Disable the execution of Global Prep Commands for this application. +
+
- A list of commands to be run before/after the application.
+ A list of commands to be run before/after this application.
If any of the prep-commands fail, starting the application is aborted
@@ -183,7 +192,7 @@ Find Cover
+ @click="useCover(cover)">
@@ -246,7 +255,7 @@ detachedCmd: "", coverSearching: false, coverFinderBusy: false, - coverCandidates: [], + coverCandidates: [] }; }, created() { @@ -438,4 +447,10 @@ object-fit: cover; } + .config-page { + padding: 1em; + border: 1px solid #dee2e6; + border-top: none; + } + \ No newline at end of file diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index f4ea4171..9e664ee7 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -213,10 +213,58 @@ +
+ It may be possible that you cannot send the Windows Key from Moonlight directly.
+ In those cases it may be useful to make Sunshine think the Right Alt key is the Windows key +
-
- It may be possible that you cannot send the Windows Key from Moonlight directly.
- In those cases it may be useful to make Sunshine think the Right Alt key is the Windows key + +
+ +
+ A list of commands to be run before/after all applications.
+ If any of the prep-commands fail, starting the application is aborted. +
+
+ + + + + + + + + + + + +
DoUndo
+ + + + + +
+
@@ -951,6 +999,7 @@ "vt_coder": "auto", "vt_realtime": "enabled", "vt_software": "auto", + "global_prep_cmd": "[]", } new Vue({ @@ -967,6 +1016,7 @@ currentTab: "general", resIn: "", fpsIn: "", + global_prep_cmd: [], tabs: [ { id: "general", @@ -1061,6 +1111,9 @@ let resolutions = []; res.split(",").forEach((r) => resolutions.push(r.trim())); this.resolutions = resolutions; + + this.config.global_prep_cmd = this.config.global_prep_cmd || []; + this.global_prep_cmd = JSON.parse(this.config.global_prep_cmd); }); }, methods: { @@ -1075,6 +1128,7 @@ "]"; // remove quotes from values in fps this.config.fps = JSON.stringify(this.fps).replace(/"/g, ""); + this.config.global_prep_cmd = JSON.stringify(this.global_prep_cmd); }, save() { this.saved = false; @@ -1135,6 +1189,12 @@ } }); }, + add_global_prep_cmd() { + this.global_prep_cmd.push({ + do: "", + undo: "", + }); + }, }, });