Implement server commands through control stream

This commit is contained in:
Yukino Song
2024-09-11 07:30:50 +08:00
parent 2d084ed6f5
commit df7c742ca8
10 changed files with 190 additions and 24 deletions

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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");

View File

@@ -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,

View File

@@ -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.

View File

@@ -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;