Implement server commands through control stream
This commit is contained in:
@@ -455,6 +455,7 @@ namespace config {
|
||||
platf::appdata().string() + "/sunshine.log", // log file
|
||||
false, // notify_pre_releases
|
||||
{}, // prep commands
|
||||
{}, // server commands
|
||||
};
|
||||
|
||||
bool
|
||||
@@ -846,6 +847,33 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
list_server_cmd_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<server_cmd_t> &input) {
|
||||
std::string string;
|
||||
string_f(vars, name, string);
|
||||
|
||||
std::stringstream jsonStream;
|
||||
|
||||
// check if string is empty, i.e. when the value doesn't exist in the config file
|
||||
if (string.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to add a wrapping object to make it valid JSON, otherwise ptree cannot parse it.
|
||||
jsonStream << "{\"server_cmd\":" << string << "}";
|
||||
|
||||
boost::property_tree::ptree jsonTree;
|
||||
boost::property_tree::read_json(jsonStream, jsonTree);
|
||||
|
||||
for (auto &[_, prep_cmd] : jsonTree.get_child("server_cmd"s)) {
|
||||
auto cmd_name = prep_cmd.get_optional<std::string>("name"s);
|
||||
auto cmd_val = prep_cmd.get_optional<std::string>("cmd"s);
|
||||
auto elevated = prep_cmd.get_optional<bool>("elevated"s);
|
||||
|
||||
input.emplace_back(cmd_name.value_or(""), cmd_val.value_or(""), elevated.value_or(false));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -1037,6 +1065,7 @@ namespace config {
|
||||
|
||||
string_f(vars, "external_ip", nvhttp.external_ip);
|
||||
list_prep_cmd_f(vars, "global_prep_cmd", config::sunshine.prep_cmds);
|
||||
list_server_cmd_f(vars, "server_cmd", config::sunshine.server_cmds);
|
||||
|
||||
string_f(vars, "audio_sink", audio.sink);
|
||||
string_f(vars, "virtual_sink", audio.virtual_sink);
|
||||
@@ -1283,7 +1312,7 @@ namespace config {
|
||||
// so that service instance will do the work instead.
|
||||
|
||||
if (!config_loaded && !shortcut_launch) {
|
||||
BOOST_LOG(fatal) << "To relaunch Sunshine successfully, use the shortcut in the Start Menu. Do not run Sunshine.exe manually."sv;
|
||||
BOOST_LOG(fatal) << "To relaunch Apollo successfully, use the shortcut in the Start Menu. Do not run sunshine.exe manually."sv;
|
||||
std::this_thread::sleep_for(10s);
|
||||
#else
|
||||
if (!config_loaded) {
|
||||
|
||||
@@ -163,6 +163,14 @@ namespace config {
|
||||
std::string undo_cmd;
|
||||
bool elevated;
|
||||
};
|
||||
|
||||
struct server_cmd_t {
|
||||
server_cmd_t(std::string &&cmd_name, std::string &&cmd_val, bool &&elevated):
|
||||
cmd_name(std::move(cmd_name)), cmd_val(std::move(cmd_val)), elevated(std::move(elevated)) {}
|
||||
std::string cmd_name;
|
||||
std::string cmd_val;
|
||||
bool elevated;
|
||||
};
|
||||
struct sunshine_t {
|
||||
bool hide_tray_controls;
|
||||
std::string locale;
|
||||
@@ -188,6 +196,7 @@ namespace config {
|
||||
std::string log_file;
|
||||
bool notify_pre_releases;
|
||||
std::vector<prep_cmd_t> prep_cmds;
|
||||
std::vector<server_cmd_t> server_cmds;
|
||||
};
|
||||
|
||||
extern video_t video;
|
||||
|
||||
@@ -755,15 +755,26 @@ namespace nvhttp {
|
||||
tree.put("root.ExternalPort", net::map_port(PORT_HTTP));
|
||||
tree.put("root.MaxLumaPixelsHEVC", video::active_hevc_mode > 1 ? "1869449984" : "0");
|
||||
|
||||
#ifdef _WIN32
|
||||
tree.put("root.VirtualDisplayCapable", true);
|
||||
tree.put("root.VirtualDisplayDriverReady", proc::vDisplayDriverStatus == VDISPLAY::DRIVER_STATUS::OK);
|
||||
#endif
|
||||
|
||||
// Only include the MAC address for requests sent from paired clients over HTTPS.
|
||||
// For HTTP requests, use a placeholder MAC address that Moonlight knows to ignore.
|
||||
if constexpr (std::is_same_v<SunshineHTTPS, T>) {
|
||||
tree.put("root.mac", platf::get_mac_address(net::addr_to_normalized_string(local_endpoint.address())));
|
||||
|
||||
pt::ptree& root_node = tree.get_child("root");
|
||||
|
||||
if (config::sunshine.server_cmds.size() > 0) {
|
||||
// Broadcast server_cmds
|
||||
for (const auto& cmd : config::sunshine.server_cmds) {
|
||||
pt::ptree cmd_node;
|
||||
cmd_node.put_value(cmd.cmd_name);
|
||||
root_node.push_back(std::make_pair("ServerCommand", cmd_node));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
tree.put("root.VirtualDisplayCapable", true);
|
||||
tree.put("root.VirtualDisplayDriverReady", proc::vDisplayDriverStatus == VDISPLAY::DRIVER_STATUS::OK);
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
tree.put("root.mac", "00:00:00:00:00:00");
|
||||
|
||||
@@ -554,6 +554,11 @@ namespace proc {
|
||||
return _app.name;
|
||||
}
|
||||
|
||||
boost::process::environment
|
||||
proc_t::get_env() {
|
||||
return _env;
|
||||
}
|
||||
|
||||
proc_t::~proc_t() {
|
||||
// It's not safe to call terminate() here because our proc_t is a static variable
|
||||
// that may be destroyed after the Boost loggers have been destroyed. Instead,
|
||||
|
||||
@@ -83,6 +83,7 @@ namespace proc {
|
||||
bool virtual_display;
|
||||
bool initial_hdr;
|
||||
|
||||
|
||||
proc_t(
|
||||
boost::process::environment &&env,
|
||||
std::vector<ctx_t> &&apps):
|
||||
@@ -109,15 +110,17 @@ namespace proc {
|
||||
get_app_image(int app_id);
|
||||
std::string
|
||||
get_last_run_app_name();
|
||||
boost::process::environment
|
||||
get_env();
|
||||
void
|
||||
terminate();
|
||||
|
||||
private:
|
||||
int _app_id;
|
||||
boost::process::environment _env;
|
||||
|
||||
std::shared_ptr<rtsp_stream::launch_session_t> _launch_session;
|
||||
|
||||
boost::process::environment _env;
|
||||
std::vector<ctx_t> _apps;
|
||||
ctx_t _app;
|
||||
std::chrono::steady_clock::time_point _app_launch_time;
|
||||
@@ -133,6 +136,9 @@ namespace proc {
|
||||
std::vector<cmd_t>::const_iterator _app_prep_begin;
|
||||
};
|
||||
|
||||
boost::filesystem::path
|
||||
find_working_directory(const std::string &cmd, boost::process::environment &env);
|
||||
|
||||
/**
|
||||
* @brief Calculate a stable id based on name and image data
|
||||
* @return Tuple of id calculated without index (for use if no collision) and one with.
|
||||
|
||||
@@ -46,6 +46,9 @@ extern "C" {
|
||||
#define IDX_RUMBLE_TRIGGER_DATA 12
|
||||
#define IDX_SET_MOTION_EVENT 13
|
||||
#define IDX_SET_RGB_LED 14
|
||||
#define IDX_EXEC_SERVER_CMD 15
|
||||
#define IDX_SET_CLIPBOARD 16
|
||||
#define IDX_FILE_TRANSFER_NONCE_REQUEST 17
|
||||
|
||||
static const short packetTypes[] = {
|
||||
0x0305, // Start A
|
||||
@@ -63,6 +66,9 @@ static const short packetTypes[] = {
|
||||
0x5500, // Rumble triggers (Sunshine protocol extension)
|
||||
0x5501, // Set motion event (Sunshine protocol extension)
|
||||
0x5502, // Set RGB LED (Sunshine protocol extension)
|
||||
0x3000, // Execute Server Command (Apollo protocol extension)
|
||||
0x3001, // Set Clipboard (Apollo protocol extension)
|
||||
0x3002, // File transfer nonce request (Apollo protocol extension)
|
||||
};
|
||||
|
||||
namespace asio = boost::asio;
|
||||
@@ -989,6 +995,37 @@ namespace stream {
|
||||
input::passthrough(session->input, std::move(plaintext));
|
||||
});
|
||||
|
||||
server->map(packetTypes[IDX_EXEC_SERVER_CMD], [server](session_t *session, const std::string_view &payload) {
|
||||
BOOST_LOG(debug) << "type [IDX_EXEC_SERVER_CMD]: "sv;
|
||||
uint8_t cmdIndex = *(uint8_t*)payload.data();
|
||||
|
||||
if (cmdIndex < config::sunshine.server_cmds.size()) {
|
||||
const auto& cmd = config::sunshine.server_cmds[cmdIndex];
|
||||
BOOST_LOG(info) << "Executing server command: " << cmd.cmd_name;
|
||||
|
||||
std::error_code ec;
|
||||
auto env = proc::proc.get_env();
|
||||
boost::filesystem::path working_dir = proc::find_working_directory(cmd.cmd_val, env);
|
||||
auto child = platf::run_command(cmd.elevated, true, cmd.cmd_val, working_dir, {}, nullptr, ec, nullptr);
|
||||
|
||||
if (ec) {
|
||||
BOOST_LOG(error) << "Failed to execute server command: " << ec.message();
|
||||
} else {
|
||||
child.detach();
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG(error) << "Invalid server command index: " << (int)cmdIndex;
|
||||
}
|
||||
});
|
||||
|
||||
server->map(packetTypes[IDX_SET_CLIPBOARD], [server](session_t *session, const std::string_view &payload) {
|
||||
BOOST_LOG(info) << "type [IDX_SET_CLIPBOARD]: "sv << payload << " size: " << payload.size();
|
||||
});
|
||||
|
||||
server->map(packetTypes[IDX_FILE_TRANSFER_NONCE_REQUEST], [server](session_t *session, const std::string_view &payload) {
|
||||
BOOST_LOG(info) << "type [IDX_FILE_TRANSFER_NONCE_REQUEST]: "sv << payload << " size: " << payload.size();
|
||||
});
|
||||
|
||||
server->map(packetTypes[IDX_ENCRYPTED], [server](session_t *session, const std::string_view &payload) {
|
||||
BOOST_LOG(verbose) << "type [IDX_ENCRYPTED]"sv;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user