mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-01-16 07:10:50 +00:00
Execute commands defined in apps.json
This commit is contained in:
parent
20f5f90588
commit
b7eab0883c
@ -1,29 +1,34 @@
|
||||
{
|
||||
"env":[
|
||||
{ "VAR":"VAL", "VAR2":"VAL2" }
|
||||
{
|
||||
"DISPLAY":":0",
|
||||
"DRI_PRIME":"1",
|
||||
"XAUTHORITY":"/home/loki/.Xauthority"
|
||||
}
|
||||
],
|
||||
"apps":[
|
||||
{
|
||||
"name":"echo-1",
|
||||
|
||||
"output":"output.txt",
|
||||
"cmd":"sh -c \"echo $VAR\"",
|
||||
"name":"Low Res Desktop 2",
|
||||
"cmd":"sh -c \"while true; do sleep 10000; done;\"",
|
||||
"prep-cmd":[
|
||||
{ "do":"echo pre-1", "undo":"echo post-1" },
|
||||
{ "do":"echo pre-2" },
|
||||
{ "do":"echo pre-3", "undo":"echo post-3" }
|
||||
{ "do":"xrandr --output HDMI-1 --mode 1920x1080", "undo":"xrandr --output HDMI-1 --mode 1920x1200" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"echo-2",
|
||||
|
||||
"output":"output.txt",
|
||||
"cmd":"sleep 10",
|
||||
"name":"Low Res Desktop",
|
||||
"cmd":"sh -c \"while true; do sleep 10000; done;\"",
|
||||
"prep-cmd":[
|
||||
{ "do":"echo pre-1", "undo":"echo post-1" },
|
||||
{ "do":"echo pre-2" },
|
||||
{ "do":"echo pre-3", "undo":"echo post-3" }
|
||||
{ "do":"xrandr --output HDMI-1 --mode 1920x1080", "undo":"xrandr --output HDMI-1 --mode 1920x1200" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"Steam BigPicture",
|
||||
|
||||
"output":"steam.txt",
|
||||
"cmd":"steam -bigpicture",
|
||||
"prep-cmd":[
|
||||
{ "do":"sh -c \"PATH=$PATH:$HOME/.local/bin sudo freeHugePages\""},
|
||||
{ "do":"sh -c \"PATH=$PATH:$HOME/.local/bin sudo claim_amdgpu\"" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -6,9 +6,6 @@
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
#include "nvhttp.h"
|
||||
#include "stream.h"
|
||||
#include "config.h"
|
||||
@ -18,85 +15,9 @@ extern "C" {
|
||||
#include <rs.h>
|
||||
}
|
||||
|
||||
|
||||
using namespace std::literals;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
std::optional<proc::proc_t> parse(const std::string& file_name) {
|
||||
pt::ptree tree;
|
||||
|
||||
try {
|
||||
pt::read_json(file_name, tree);
|
||||
|
||||
auto &apps_node = tree.get_child("apps"s);
|
||||
auto &env_vars = tree.get_child("env"s);
|
||||
|
||||
boost::process::environment env = boost::this_process::environment();
|
||||
|
||||
std::unordered_map<std::string, proc::ctx_t> apps;
|
||||
for(auto &[_,app_node] : apps_node) {
|
||||
proc::ctx_t ctx;
|
||||
|
||||
auto &prep_nodes = app_node.get_child("prep-cmd"s);
|
||||
auto name = app_node.get<std::string>("name"s);
|
||||
auto output = app_node.get_optional<std::string>("output"s);
|
||||
auto cmd = app_node.get<std::string>("cmd"s);
|
||||
|
||||
std::vector<proc::cmd_t> prep_cmds;
|
||||
prep_cmds.reserve(prep_nodes.size());
|
||||
for(auto &[_, prep_node] : prep_nodes) {
|
||||
auto do_cmd = prep_node.get<std::string>("do"s);
|
||||
auto undo_cmd = prep_node.get_optional<std::string>("undo"s);
|
||||
|
||||
if(undo_cmd) {
|
||||
prep_cmds.emplace_back(std::move(do_cmd), std::move(*undo_cmd));
|
||||
}
|
||||
else {
|
||||
prep_cmds.emplace_back(std::move(do_cmd));
|
||||
}
|
||||
}
|
||||
|
||||
if(output) {
|
||||
ctx.output = std::move(*output);
|
||||
}
|
||||
ctx.cmd = std::move(cmd);
|
||||
ctx.prep_cmds = std::move(prep_cmds);
|
||||
|
||||
apps.emplace(std::move(name), std::move(ctx));
|
||||
}
|
||||
|
||||
for(auto &[_,env_var] : env_vars) {
|
||||
for(auto &[name,val] : env_var) {
|
||||
env[name] = val.get_value<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
return proc::proc_t {
|
||||
std::move(env), std::move(apps)
|
||||
};
|
||||
} catch (std::exception &e) {
|
||||
std::cout << e.what() << std::endl;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
auto proc_opt = parse(SUNSHINE_ASSETS_DIR "/apps.json");
|
||||
|
||||
if(!proc_opt) {
|
||||
return 7;
|
||||
}
|
||||
|
||||
auto &proc = *proc_opt;
|
||||
|
||||
proc.execute("echo-2");
|
||||
std::this_thread::sleep_for(50ms);
|
||||
|
||||
proc.execute("echo-1");
|
||||
std::this_thread::sleep_for(50ms);
|
||||
return proc.running();
|
||||
|
||||
if(argc > 1) {
|
||||
if(!std::filesystem::exists(argv[1])) {
|
||||
std::cout << "Error: Couldn't find configuration file ["sv << argv[1] << ']' << std::endl;
|
||||
@ -106,6 +27,12 @@ int main(int argc, char *argv[]) {
|
||||
config::parse_file(argv[1]);
|
||||
}
|
||||
|
||||
auto proc_opt = proc::parse(SUNSHINE_ASSETS_DIR "/apps.json");
|
||||
if(!proc_opt) {
|
||||
return 7;
|
||||
}
|
||||
proc::proc = std::move(*proc_opt);
|
||||
|
||||
reed_solomon_init();
|
||||
|
||||
std::thread httpThread { nvhttp::start };
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "stream.h"
|
||||
#include "nvhttp.h"
|
||||
#include "platform/common.h"
|
||||
#include "process.h"
|
||||
|
||||
|
||||
namespace nvhttp {
|
||||
@ -441,20 +442,26 @@ void applist(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> respon
|
||||
}
|
||||
|
||||
auto &apps = tree.add_child("root", pt::ptree {});
|
||||
|
||||
pt::ptree desktop;
|
||||
pt::ptree fakegame;
|
||||
|
||||
apps.put("<xmlattr>.status_code", 200);
|
||||
desktop.put("IsHdrSupported"s, 0);
|
||||
desktop.put("AppTitle"s, "Desktop");
|
||||
desktop.put("ID"s, 1);
|
||||
|
||||
fakegame.put("IsHdrSupported"s, 0);
|
||||
fakegame.put("AppTitle"s, "FakeGame");
|
||||
fakegame.put("ID"s, 2);
|
||||
int x = 2;
|
||||
for(auto &[name, proc] : proc::proc.get_apps()) {
|
||||
pt::ptree app;
|
||||
|
||||
app.put("IsHdrSupported"s, 0);
|
||||
app.put("AppTitle"s, name);
|
||||
app.put("ID"s, x++);
|
||||
|
||||
apps.push_back(std::make_pair("App", std::move(app)));
|
||||
}
|
||||
|
||||
apps.push_back(std::make_pair("App", desktop));
|
||||
apps.push_back(std::make_pair("App", fakegame));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
@ -462,6 +469,17 @@ void launch(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> respons
|
||||
print_req<T>(request);
|
||||
|
||||
auto args = request->parse_query_string();
|
||||
|
||||
auto appid = util::from_view(args.at("appid")) -2;
|
||||
|
||||
stream::app_name.clear();
|
||||
if(appid >= 0) {
|
||||
auto pos = std::begin(proc::proc.get_apps());
|
||||
std::advance(pos, appid);
|
||||
|
||||
stream::app_name = pos->first;
|
||||
}
|
||||
|
||||
auto clientID = args.at("uniqueid"s);
|
||||
auto aesKey = *util::from_hex<crypto::aes_t>(args.at("rikey"s), true);
|
||||
uint32_t prepend_iv = util::endian::big<uint32_t>(util::from_view(args.at("rikeyid"s)));
|
||||
|
@ -34,7 +34,7 @@ struct gamepad_state_t {
|
||||
|
||||
std::string get_local_ip();
|
||||
|
||||
void interrupt_process(std::uint64_t handle);
|
||||
void terminate_process(std::uint64_t handle);
|
||||
|
||||
mic_t microphone();
|
||||
audio_t audio(mic_t &mic, std::uint32_t sample_size);
|
||||
|
@ -84,8 +84,8 @@ std::string get_local_ip(int family) {
|
||||
|
||||
std::string get_local_ip() { return get_local_ip(AF_INET); }
|
||||
|
||||
void interrupt_process(std::uint64_t handle) {
|
||||
kill((pid_t)handle, SIGINT);
|
||||
void terminate_process(std::uint64_t handle) {
|
||||
kill((pid_t)handle, SIGTERM);
|
||||
}
|
||||
|
||||
struct display_attr_t {
|
||||
|
@ -6,6 +6,9 @@
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
#include "process.h"
|
||||
#include "config.h"
|
||||
#include "utility.h"
|
||||
@ -14,6 +17,9 @@
|
||||
namespace proc {
|
||||
using namespace std::literals;
|
||||
namespace bp = boost::process;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
proc_t proc;
|
||||
|
||||
template<class Rep, class Period>
|
||||
void process_end(bp::child &proc, const std::chrono::duration<Rep, Period>& rel_time) {
|
||||
@ -21,10 +27,12 @@ void process_end(bp::child &proc, const std::chrono::duration<Rep, Period>& rel_
|
||||
return;
|
||||
}
|
||||
|
||||
platf::interrupt_process((std::uint64_t)proc.native_handle());
|
||||
std::cout << "Interruping Child-Process"sv << std::endl;
|
||||
platf::terminate_process((std::uint64_t) proc.native_handle());
|
||||
|
||||
// Force termination if it takes too long
|
||||
if(!proc.wait_for(rel_time)) {
|
||||
std::cout << "Force termination Child-Process"sv << std::endl;
|
||||
proc.terminate();
|
||||
}
|
||||
}
|
||||
@ -40,8 +48,10 @@ int exe(const std::string &cmd, bp::environment &env, file_t &file, std::error_c
|
||||
int proc_t::execute(const std::string &name) {
|
||||
auto it = _name_to_proc.find(name);
|
||||
|
||||
std::cout << "Ensure clean slate"sv << std::endl;
|
||||
// Ensure starting from a clean slate
|
||||
_undo_pre_cmd();
|
||||
std::cout << "Clean slate"sv << std::endl;
|
||||
|
||||
if(it == std::end(_name_to_proc)) {
|
||||
std::cout << "Error: Couldn't find ["sv << name << ']' << std::endl;
|
||||
@ -137,7 +147,79 @@ void proc_t::_undo_pre_cmd() {
|
||||
|
||||
_pipe.reset();
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, ctx_t> &proc_t::get_apps() const {
|
||||
return _name_to_proc;
|
||||
}
|
||||
|
||||
proc_t::~proc_t() {
|
||||
_undo_pre_cmd();
|
||||
}
|
||||
|
||||
std::optional<proc::proc_t> parse(const std::string& file_name) {
|
||||
pt::ptree tree;
|
||||
|
||||
try {
|
||||
pt::read_json(file_name, tree);
|
||||
|
||||
auto &apps_node = tree.get_child("apps"s);
|
||||
auto &env_vars = tree.get_child("env"s);
|
||||
|
||||
boost::process::environment env = boost::this_process::environment();
|
||||
|
||||
std::unordered_map<std::string, proc::ctx_t> apps;
|
||||
for(auto &[_,app_node] : apps_node) {
|
||||
proc::ctx_t ctx;
|
||||
|
||||
auto &prep_nodes = app_node.get_child("prep-cmd"s);
|
||||
auto name = app_node.get<std::string>("name"s);
|
||||
auto output = app_node.get_optional<std::string>("output"s);
|
||||
auto cmd = app_node.get<std::string>("cmd"s);
|
||||
|
||||
std::vector<proc::cmd_t> prep_cmds;
|
||||
prep_cmds.reserve(prep_nodes.size());
|
||||
for(auto &[_, prep_node] : prep_nodes) {
|
||||
auto do_cmd = prep_node.get<std::string>("do"s);
|
||||
auto undo_cmd = prep_node.get_optional<std::string>("undo"s);
|
||||
|
||||
if(undo_cmd) {
|
||||
prep_cmds.emplace_back(std::move(do_cmd), std::move(*undo_cmd));
|
||||
}
|
||||
else {
|
||||
prep_cmds.emplace_back(std::move(do_cmd));
|
||||
}
|
||||
}
|
||||
|
||||
if(output) {
|
||||
ctx.output = std::move(*output);
|
||||
}
|
||||
ctx.cmd = std::move(cmd);
|
||||
ctx.prep_cmds = std::move(prep_cmds);
|
||||
|
||||
apps.emplace(std::move(name), std::move(ctx));
|
||||
}
|
||||
|
||||
for(auto &[_,env_var] : env_vars) {
|
||||
for(auto &[name,val] : env_var) {
|
||||
env[name] = val.get_value<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
return proc::proc_t {
|
||||
std::move(env), std::move(apps)
|
||||
};
|
||||
} catch (std::exception &e) {
|
||||
std::cout << e.what() << std::endl;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void refresh(const std::string &file_name) {
|
||||
auto proc_opt = proc::parse(file_name);
|
||||
|
||||
if(proc_opt) {
|
||||
proc = std::move(*proc_opt);
|
||||
}
|
||||
}
|
||||
}
|
@ -6,8 +6,10 @@
|
||||
#define SUNSHINE_PROCESS_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
|
||||
#include <boost/process.hpp>
|
||||
|
||||
#include "utility.h"
|
||||
|
||||
namespace proc {
|
||||
@ -41,7 +43,7 @@ struct ctx_t {
|
||||
|
||||
class proc_t {
|
||||
public:
|
||||
KITTY_DEFAULT_CONSTR(proc_t)
|
||||
KITTY_DEFAULT_CONSTR_THROW(proc_t)
|
||||
|
||||
proc_t(
|
||||
boost::process::environment &&env,
|
||||
@ -54,6 +56,7 @@ public:
|
||||
|
||||
~proc_t();
|
||||
|
||||
const std::unordered_map<std::string, ctx_t> &get_apps() const;
|
||||
private:
|
||||
void _undo_pre_cmd();
|
||||
|
||||
@ -66,5 +69,9 @@ private:
|
||||
std::vector<cmd_t>::const_iterator _undo_begin;
|
||||
};
|
||||
|
||||
void refresh(const std::string &file_name);
|
||||
std::optional<proc::proc_t> parse(const std::string& file_name);
|
||||
|
||||
extern proc_t proc;
|
||||
}
|
||||
#endif //SUNSHINE_PROCESS_H
|
||||
|
@ -31,6 +31,7 @@ extern "C" {
|
||||
#include "thread_safe.h"
|
||||
#include "crypto.h"
|
||||
#include "input.h"
|
||||
#include "process.h"
|
||||
|
||||
#define IDX_START_A 0
|
||||
#define IDX_REQUEST_IDR_FRAME 0
|
||||
@ -88,8 +89,10 @@ struct audio_packet_raw_t {
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
//TODO: Bundle in thread safe container
|
||||
crypto::aes_t gcm_key;
|
||||
crypto::aes_t iv;
|
||||
std::string app_name;
|
||||
|
||||
struct config_t {
|
||||
audio::config_t audio;
|
||||
@ -114,6 +117,8 @@ struct session_t {
|
||||
|
||||
crypto::aes_t gcm_key;
|
||||
crypto::aes_t iv;
|
||||
|
||||
std::string app_name;
|
||||
} session;
|
||||
|
||||
void free_msg(PRTSP_MESSAGE msg) {
|
||||
@ -166,7 +171,7 @@ public:
|
||||
parseRtspMessage(req.get(), (char*)packet->data, packet->dataLength);
|
||||
for(auto option = req->options; option != nullptr; option = option->next) {
|
||||
if("Content-length"sv == option->option) {
|
||||
_queue_packet = std::make_pair(std::move(peer), std::move(packet));
|
||||
_queue_packet = std::make_pair(peer, std::move(packet));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -272,6 +277,7 @@ public:
|
||||
}
|
||||
}
|
||||
void map(uint16_t type, std::function<void(const std::string_view&)> cb);
|
||||
void send(const std::string_view &payload);
|
||||
private:
|
||||
std::unordered_map<std::uint16_t, std::function<void(const std::string_view&)>> _map_type_cb;
|
||||
ENetAddress _addr;
|
||||
@ -431,6 +437,17 @@ void control_server_t::map(uint16_t type, std::function<void(const std::string_v
|
||||
_map_type_cb.emplace(type, std::move(cb));
|
||||
}
|
||||
|
||||
void control_server_t::send(const std::string_view & payload) {
|
||||
std::for_each(_host->peers, _host->peers + _host->peerCount, [payload](auto &peer) {
|
||||
auto packet = enet_packet_create(payload.data(), payload.size(), ENET_PACKET_FLAG_RELIABLE);
|
||||
if(enet_peer_send(&peer, 0, packet)) {
|
||||
enet_packet_destroy(packet);
|
||||
}
|
||||
});
|
||||
|
||||
enet_host_flush(_host.get());
|
||||
}
|
||||
|
||||
void controlThread(video::idr_event_t idr_events) {
|
||||
control_server_t server { CONTROL_PORT };
|
||||
|
||||
@ -515,6 +532,21 @@ void controlThread(video::idr_event_t idr_events) {
|
||||
session.audio_packets->stop();
|
||||
}
|
||||
|
||||
if(!session.app_name.empty() && !proc::proc.running()) {
|
||||
std::cout << "Process terminated"sv << std::endl;
|
||||
|
||||
std::uint16_t reason = 0x0100;
|
||||
|
||||
std::array<std::uint16_t, 2> payload;
|
||||
payload[0] = packetTypes[IDX_TERMINATION];
|
||||
payload[1] = reason;
|
||||
|
||||
server.send(std::string_view {(char*)payload.data(), payload.size()});
|
||||
|
||||
session.video_packets->stop();
|
||||
session.audio_packets->stop();
|
||||
}
|
||||
|
||||
server.iterate(500ms);
|
||||
}
|
||||
}
|
||||
@ -885,6 +917,22 @@ void cmd_announce(host_t &host, peer_t peer, msg_t &&req) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!app_name.empty() && session.app_name != app_name ) {
|
||||
if(auto err_code = proc::proc.execute(app_name)) {
|
||||
if(err_code == 404) {
|
||||
respond(host, peer, &option, 404, (app_name + " NOT FOUND").c_str(), req->sequenceNumber, {});
|
||||
return;
|
||||
}
|
||||
|
||||
else {
|
||||
respond(host, peer, &option, 500, "INTERNAL ERROR", req->sequenceNumber, {});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
session.app_name = std::move(app_name);
|
||||
|
||||
std::copy(std::begin(gcm_key), std::end(gcm_key), std::begin(session.gcm_key));
|
||||
std::copy(std::begin(iv), std::end(iv), std::begin(session.iv));
|
||||
|
||||
|
@ -9,8 +9,10 @@
|
||||
|
||||
namespace stream {
|
||||
|
||||
//FIXME: Make thread safe
|
||||
extern crypto::aes_t gcm_key;
|
||||
extern crypto::aes_t iv;
|
||||
extern std::string app_name;
|
||||
|
||||
void rtpThread();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user