Add support for global prep commands (#977)

This commit is contained in:
pgrunzjr
2023-03-27 11:02:20 -05:00
committed by GitHub
parent c2fba6f651
commit 8c86baf627
7 changed files with 152 additions and 24 deletions
+14
View File
@@ -107,6 +107,20 @@ log_path
log_path = sunshine.log 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 Controls
-------- --------
+25
View File
@@ -7,6 +7,8 @@
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include "config.h" #include "config.h"
#include "main.h" #include "main.h"
@@ -426,6 +428,7 @@ sunshine_t sunshine {
{}, // cmd args {}, // cmd args
47989, 47989,
platf::appdata().string() + "/sunshine.log", // log file platf::appdata().string() + "/sunshine.log", // log file
{}, // prep commands
}; };
bool endline(char ch) { bool endline(char ch) {
@@ -759,6 +762,27 @@ void list_string_f(std::unordered_map<std::string, std::string> &vars, const std
input.emplace_back(begin, pos); input.emplace_back(begin, pos);
} }
} }
void list_prep_cmd_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<prep_cmd_t> &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<std::string>("do"s);
auto undo_cmd = prep_cmd.get<std::string>("undo"s);
input.emplace_back(
std::move(do_cmd),
std::move(undo_cmd));
}
}
void list_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<int> &input) { void list_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<int> &input) {
std::vector<std::string> list; std::vector<std::string> list;
@@ -902,6 +926,7 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
string_f(vars, "external_ip", nvhttp.external_ip); string_f(vars, "external_ip", nvhttp.external_ip);
list_string_f(vars, "resolutions"s, nvhttp.resolutions); list_string_f(vars, "resolutions"s, nvhttp.resolutions);
list_int_f(vars, "fps"s, nvhttp.fps); 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, "audio_sink", audio.sink);
string_f(vars, "virtual_sink", audio.virtual_sink); string_f(vars, "virtual_sink", audio.virtual_sink);
+9
View File
@@ -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 { struct sunshine_t {
int min_log_level; int min_log_level;
std::bitset<flag::FLAG_SIZE> flags; std::bitset<flag::FLAG_SIZE> flags;
@@ -137,6 +144,8 @@ struct sunshine_t {
std::uint16_t port; std::uint16_t port;
std::string log_file; std::string log_file;
std::vector<prep_cmd_t> prep_cmds;
}; };
extern video_t video; extern video_t video;
+13 -1
View File
@@ -18,6 +18,7 @@
#include <openssl/evp.h> #include <openssl/evp.h>
#include <openssl/sha.h> #include <openssl/sha.h>
#include "config.h"
#include "crypto.h" #include "crypto.h"
#include "main.h" #include "main.h"
#include "platform/common.h" #include "platform/common.h"
@@ -459,6 +460,7 @@ std::optional<proc::proc_t> parse(const std::string &file_name) {
auto prep_nodes_opt = app_node.get_child_optional("prep-cmd"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 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 output = app_node.get_optional<std::string>("output"s);
auto name = parse_env_val(this_env, app_node.get<std::string>("name"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 cmd = app_node.get_optional<std::string>("cmd"s);
@@ -466,10 +468,20 @@ std::optional<proc::proc_t> parse(const std::string &file_name) {
auto working_dir = app_node.get_optional<std::string>("working-dir"s); auto working_dir = app_node.get_optional<std::string>("working-dir"s);
std::vector<proc::cmd_t> prep_cmds; 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));
}
}
if(prep_nodes_opt) { if(prep_nodes_opt) {
auto &prep_nodes = *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) { for(auto &[_, prep_node] : prep_nodes) {
auto do_cmd = parse_env_val(this_env, prep_node.get<std::string>("do"s)); auto do_cmd = parse_env_val(this_env, prep_node.get<std::string>("do"s));
auto undo_cmd = prep_node.get_optional<std::string>("undo"s); auto undo_cmd = prep_node.get_optional<std::string>("undo"s);
+2 -9
View File
@@ -12,20 +12,13 @@
#include <boost/process.hpp> #include <boost/process.hpp>
#include "config.h"
#include "utility.h" #include "utility.h"
namespace proc { namespace proc {
using file_t = util::safe_ptr_v2<FILE, int, fclose>; using file_t = util::safe_ptr_v2<FILE, int, fclose>;
struct cmd_t { typedef config::prep_cmd_t 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;
};
/* /*
* pre_cmds -- guaranteed to be executed unless any of the commands fail. * pre_cmds -- guaranteed to be executed unless any of the commands fail.
* detached -- commands detached from Sunshine * detached -- commands detached from Sunshine
+17 -2
View File
@@ -57,9 +57,18 @@
</div> </div>
<!--prep-cmd--> <!--prep-cmd-->
<div class="mb-3 d-flex flex-column"> <div class="mb-3 d-flex flex-column">
<div class="mb-3">
<label for="excludeGlobalPrep" class="form-label">Global Prep Commands</label>
<select id="excludeGlobalPrep" class="form-select" v-model="editForm['exclude-global-prep-cmd']">
<option v-for="val in [false, true]" :value="val">{{ !val ? 'Enabled' : 'Disabled' }}</option>
</select>
<div class="form-text">
Enable/Disable the execution of Global Prep Commands for this application.
</div>
</div>
<label for="appName" class="form-label">Command Preparations</label> <label for="appName" class="form-label">Command Preparations</label>
<div class="form-text"> <div class="form-text">
A list of commands to be run before/after the application. <br /> A list of commands to be run before/after this application. <br />
If any of the prep-commands fail, starting the application is aborted If any of the prep-commands fail, starting the application is aborted
</div> </div>
<table v-if="editForm['prep-cmd'].length > 0"> <table v-if="editForm['prep-cmd'].length > 0">
@@ -246,7 +255,7 @@
detachedCmd: "", detachedCmd: "",
coverSearching: false, coverSearching: false,
coverFinderBusy: false, coverFinderBusy: false,
coverCandidates: [], coverCandidates: []
}; };
}, },
created() { created() {
@@ -438,4 +447,10 @@
object-fit: cover; object-fit: cover;
} }
.config-page {
padding: 1em;
border: 1px solid #dee2e6;
border-top: none;
}
</style> </style>
+61 -1
View File
@@ -213,12 +213,60 @@
<option value="disabled">Disabled</option> <option value="disabled">Disabled</option>
<option value="enabled">Enabled</option> <option value="enabled">Enabled</option>
</select> </select>
</div>
<div class="form-text"> <div class="form-text">
It may be possible that you cannot send the Windows Key from Moonlight directly.<br /> It may be possible that you cannot send the Windows Key from Moonlight directly.<br />
In those cases it may be useful to make Sunshine think the Right Alt key is the Windows key In those cases it may be useful to make Sunshine think the Right Alt key is the Windows key
</div> </div>
</div> </div>
<!-- Global Prep Commands -->
<div class="mb-3 d-flex flex-column">
<label class="form-label">Command Preparations</label>
<div class="form-text">
A list of commands to be run before/after all applications. <br />
If any of the prep-commands fail, starting the application is aborted.
</div>
<table v-if="global_prep_cmd.length > 0">
<thead>
<th>Do</th>
<th>Undo</th>
<th style="width: 48px"></th>
</thead>
<tbody>
<tr v-for="(c,i) in global_prep_cmd">
<td>
<input
type="text"
class="form-control monospace"
v-model="c.do"
/>
</td>
<td>
<input
type="text"
class="form-control monospace"
v-model="c.undo"
/>
</td>
<td>
<button
class="btn btn-danger"
@click="global_prep_cmd.splice(i,1)"
>
&times;
</button>
</td>
</tr>
</tbody>
</table>
<button
class="mt-2 btn btn-success"
style="margin: 0 auto"
@click="add_global_prep_cmd"
>
&plus; Add
</button>
</div>
</div>
<!--Files Tab--> <!--Files Tab-->
<div v-if="currentTab === 'files'" class="config-page"> <div v-if="currentTab === 'files'" class="config-page">
<!--Private Key--> <!--Private Key-->
@@ -951,6 +999,7 @@
"vt_coder": "auto", "vt_coder": "auto",
"vt_realtime": "enabled", "vt_realtime": "enabled",
"vt_software": "auto", "vt_software": "auto",
"global_prep_cmd": "[]",
} }
new Vue({ new Vue({
@@ -967,6 +1016,7 @@
currentTab: "general", currentTab: "general",
resIn: "", resIn: "",
fpsIn: "", fpsIn: "",
global_prep_cmd: [],
tabs: [ tabs: [
{ {
id: "general", id: "general",
@@ -1061,6 +1111,9 @@
let resolutions = []; let resolutions = [];
res.split(",").forEach((r) => resolutions.push(r.trim())); res.split(",").forEach((r) => resolutions.push(r.trim()));
this.resolutions = resolutions; 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: { methods: {
@@ -1075,6 +1128,7 @@
"]"; "]";
// remove quotes from values in fps // remove quotes from values in fps
this.config.fps = JSON.stringify(this.fps).replace(/"/g, ""); this.config.fps = JSON.stringify(this.fps).replace(/"/g, "");
this.config.global_prep_cmd = JSON.stringify(this.global_prep_cmd);
}, },
save() { save() {
this.saved = false; this.saved = false;
@@ -1135,6 +1189,12 @@
} }
}); });
}, },
add_global_prep_cmd() {
this.global_prep_cmd.push({
do: "",
undo: "",
});
},
}, },
}); });
</script> </script>