Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/source/about/advanced_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------

Expand Down
25 changes: 25 additions & 0 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#include <boost/asio.hpp>
#include <boost/filesystem.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>

#include "config.h"
#include "main.h"
Expand Down Expand Up @@ -426,6 +428,7 @@ sunshine_t sunshine {
{}, // cmd args
47989,
platf::appdata().string() + "/sunshine.log", // log file
{}, // prep commands
};

bool endline(char ch) {
Expand Down Expand Up @@ -759,6 +762,27 @@ void list_string_f(std::unordered_map<std::string, std::string> &vars, const std
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) {
std::vector<std::string> list;
Expand Down Expand Up @@ -902,6 +926,7 @@ void apply_config(std::unordered_map<std::string, std::string> &&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);
Expand Down
9 changes: 9 additions & 0 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<flag::FLAG_SIZE> flags;
Expand All @@ -137,6 +144,8 @@ struct sunshine_t {

std::uint16_t port;
std::string log_file;

std::vector<prep_cmd_t> prep_cmds;
};

extern video_t video;
Expand Down
28 changes: 20 additions & 8 deletions src/process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <openssl/evp.h>
#include <openssl/sha.h>

#include "config.h"
#include "crypto.h"
#include "main.h"
#include "platform/common.h"
Expand Down Expand Up @@ -457,19 +458,30 @@ std::optional<proc::proc_t> 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<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 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);

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) {
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<std::string>("do"s));
auto undo_cmd = prep_node.get_optional<std::string>("undo"s);
Expand Down
11 changes: 2 additions & 9 deletions src/process.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,13 @@

#include <boost/process.hpp>

#include "config.h"
#include "utility.h"

namespace proc {
using file_t = util::safe_ptr_v2<FILE, int, fclose>;

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
Expand Down
23 changes: 19 additions & 4 deletions src_assets/common/assets/web/apps.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,18 @@ <h1>Applications</h1>
</div>
<!--prep-cmd-->
<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>
<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
</div>
<table v-if="editForm['prep-cmd'].length > 0">
Expand Down Expand Up @@ -183,7 +192,7 @@ <h1>Applications</h1>
Find Cover
</button>
<div class="dropdown-menu dropdown-menu-end w-50 cover-finder overflow-hidden"
aria-labelledby="findCoverToggle">
aria-labelledby="findCoverToggle">
<div class="modal-header">
<h4 class="modal-title">Covers Found</h4>
<button type="button" class="btn-close" aria-label="Close" @click="closeCoverFinder"></button>
Expand All @@ -198,7 +207,7 @@ <h4 class="modal-title">Covers Found</h4>
</div>
</div>
<div v-for="(cover,i) in coverCandidates" :key="i" class="col-12 col-sm-6 col-lg-4 mb-3"
@click="useCover(cover)">
@click="useCover(cover)">
<div class="cover-container result">
<img class="rounded" :src="cover.url"/>
</div>
Expand Down Expand Up @@ -246,7 +255,7 @@ <h4 class="modal-title">Covers Found</h4>
detachedCmd: "",
coverSearching: false,
coverFinderBusy: false,
coverCandidates: [],
coverCandidates: []
};
},
created() {
Expand Down Expand Up @@ -438,4 +447,10 @@ <h4 class="modal-title">Covers Found</h4>
object-fit: cover;
}

.config-page {
padding: 1em;
border: 1px solid #dee2e6;
border-top: none;
}

</style>
66 changes: 63 additions & 3 deletions src_assets/common/assets/web/config.html
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,58 @@ <h1 class="my-4">Configuration</h1>
<option value="disabled">Disabled</option>
<option value="enabled">Enabled</option>
</select>
<div class="form-text">
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
</div>
</div>
<div class="form-text">
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
<!-- 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-->
Expand Down Expand Up @@ -951,6 +999,7 @@ <h1 class="my-4">Configuration</h1>
"vt_coder": "auto",
"vt_realtime": "enabled",
"vt_software": "auto",
"global_prep_cmd": "[]",
}

new Vue({
Expand All @@ -967,6 +1016,7 @@ <h1 class="my-4">Configuration</h1>
currentTab: "general",
resIn: "",
fpsIn: "",
global_prep_cmd: [],
tabs: [
{
id: "general",
Expand Down Expand Up @@ -1061,6 +1111,9 @@ <h1 class="my-4">Configuration</h1>
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: {
Expand All @@ -1075,6 +1128,7 @@ <h1 class="my-4">Configuration</h1>
"]";
// 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;
Expand Down Expand Up @@ -1135,6 +1189,12 @@ <h1 class="my-4">Configuration</h1>
}
});
},
add_global_prep_cmd() {
this.global_prep_cmd.push({
do: "",
undo: "",
});
},
},
});
</script>
Expand Down