Skip to content
Open
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
1 change: 1 addition & 0 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1157,6 +1157,7 @@ namespace config {

string_f(vars, "external_ip", nvhttp.external_ip);
list_prep_cmd_f(vars, "global_prep_cmd", config::sunshine.prep_cmds);
list_prep_cmd_f(vars, "pre_probe_cmd", config::sunshine.pre_probe_cmds);

string_f(vars, "audio_sink", audio.sink);
string_f(vars, "virtual_sink", audio.virtual_sink);
Expand Down
1 change: 1 addition & 0 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ namespace config {
bool notify_pre_releases;
bool system_tray;
std::vector<prep_cmd_t> prep_cmds;
std::vector<prep_cmd_t> pre_probe_cmds;
};

extern video_t video;
Expand Down
50 changes: 50 additions & 0 deletions src/nvhttp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,39 @@ namespace nvhttp {
}
}

/**
* @brief Execute pre-probe commands before encoder probing.
* @param launch_session The session to extract client parameters from (may be null for undo-only).
* @param execute_do If true, run "do" commands; if false, run "undo" commands.
*/
static void run_pre_probe_cmds(
const std::shared_ptr<rtsp_stream::launch_session_t> &launch_session,
bool execute_do) {
for (auto &cmd : config::sunshine.pre_probe_cmds) {
auto &cmd_str = execute_do ? cmd.do_cmd : cmd.undo_cmd;
if (cmd_str.empty()) continue;

boost::process::v1::environment env = boost::this_process::environment();
if (launch_session) {
env["SUNSHINE_CLIENT_WIDTH"] = std::to_string(launch_session->width);
env["SUNSHINE_CLIENT_HEIGHT"] = std::to_string(launch_session->height);
env["SUNSHINE_CLIENT_FPS"] = std::to_string(launch_session->fps);
env["SUNSHINE_CLIENT_HDR"] = launch_session->enable_hdr ? "true" : "false";
}

std::error_code ec;
boost::filesystem::path working_dir(".");
BOOST_LOG(info) << "Executing pre-probe cmd: ["sv << cmd_str << ']';
auto child = platf::run_command(cmd.elevated, true, cmd_str, working_dir, env, nullptr, ec, nullptr);
if (ec) {
BOOST_LOG(warning) << "Pre-probe cmd failed: "sv << ec.message();
}
else {
child.wait();
}
}
}

void launch(bool &host_audio, resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request);

Expand Down Expand Up @@ -863,6 +896,10 @@ namespace nvhttp {
// The display should be restored in case something fails as there are no other sessions.
revert_display_configuration = true;

// Run pre-probe commands (e.g. enable virtual display) before anything
// that depends on a connected monitor.
run_pre_probe_cmds(launch_session, true);

// We want to prepare display only if there are no active sessions at
// the moment. This should be done before probing encoders as it could
// change the active displays.
Expand All @@ -877,6 +914,7 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_message", "Failed to initialize video capture/encoding. Is a display connected and turned on?");
tree.put("root.gamesession", 0);

run_pre_probe_cmds(launch_session, false);
return;
}
}
Expand Down Expand Up @@ -968,6 +1006,10 @@ namespace nvhttp {
const auto launch_session = make_launch_session(host_audio, args);

if (no_active_sessions) {
// Run pre-probe commands (e.g. enable virtual display) before anything
// that depends on a connected monitor.
run_pre_probe_cmds(launch_session, true);

// We want to prepare display only if there are no active sessions at
// the moment. This should be done before probing encoders as it could
// change the active displays.
Expand All @@ -982,6 +1024,7 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 503);
tree.put("root.<xmlattr>.status_message", "Failed to initialize video capture/encoding. Is a display connected and turned on?");

run_pre_probe_cmds(launch_session, false);
return;
}
}
Expand Down Expand Up @@ -1033,10 +1076,17 @@ namespace nvhttp {
proc::proc.terminate();
}

// Undo pre-probe commands (e.g. disable virtual display).
run_pre_probe_cmds(nullptr, false);

// The config needs to be reverted regardless of whether "proc::proc.terminate()" was called or not.
display_device::revert_configuration();
}

void undo_pre_probe_cmds() {
run_pre_probe_cmds(nullptr, false);
}

void appasset(resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request);

Expand Down
5 changes: 5 additions & 0 deletions src/nvhttp.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ namespace nvhttp {
*/
void start();

/**
* @brief Run the undo commands of all pre_probe_cmds.
*/
void undo_pre_probe_cmds();

/**
* @brief Setup the nvhttp server.
* @param pkey
Expand Down
82 changes: 81 additions & 1 deletion src/platform/linux/kmsgrab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ namespace fs = std::filesystem;

namespace platf {

// Forward declaration: refresh card_descriptors for connector name resolution
std::vector<std::string> kms_display_names(mem_type_e hwdevice_type);

namespace kms {

class cap_sys_admin {
Expand Down Expand Up @@ -594,6 +597,78 @@ namespace platf {
BOOST_LOG(debug) << ss.str();
}

/**
* @brief Resolve a display name to a numeric monitor index.
*
* Accepts either a plain numeric index ("0", "1", ...) or a DRM connector
* name in the form "{type}-{index}" (e.g. "DP-2", "HDMI-A-1").
* Connector names are matched against the card_descriptors populated by
* kms_display_names(), reusing the same parsing logic as correlate_to_wayland().
*
* @param display_name The user-provided output_name value.
* @return The resolved monitor index, or -1 on failure.
*/
static int resolve_display_name(const std::string &display_name) {
// If the name is purely numeric, use it directly as a monitor index
bool all_digits = !display_name.empty();
for (auto c : display_name) {
if (!std::isdigit(static_cast<unsigned char>(c))) {
all_digits = false;
break;
}
}

if (all_digits) {
return util::from_view(display_name);
}

// Try to parse as connector name: "{type}-{index}" (e.g. "DP-2", "HDMI-A-1")
auto dash_pos = display_name.find_last_of('-');
if (dash_pos == std::string::npos || dash_pos == 0 || dash_pos == display_name.size() - 1) {
return -1;
}

auto type_str = std::string_view(display_name).substr(0, dash_pos);
auto index_str = std::string_view(display_name).substr(dash_pos + 1);

// Verify the suffix is numeric before calling util::from_view()
for (auto c : index_str) {
if (!std::isdigit(static_cast<unsigned char>(c))) {
return -1;
}
}

auto type = kms::from_view(type_str);
auto index = std::max<std::int64_t>(1, util::from_view(index_str));

if (type == DRM_MODE_CONNECTOR_Unknown) {
return -1;
}

// Search card_descriptors, refreshing once if the connector is not found.
// This handles the case where pre_probe_cmd just enabled a virtual display
// that wasn't present when card_descriptors was initially populated.
for (int attempt = 0; attempt < 2; ++attempt) {
for (auto &cd : card_descriptors) {
for (auto &[crtc_id, mon] : cd.crtc_to_monitor) {
if (mon.type == type && mon.index == index) {
BOOST_LOG(info) << "Resolved connector name '"sv << display_name
<< "' to monitor index "sv << mon.monitor_index;
return mon.monitor_index;
}
}
}

if (attempt == 0) {
BOOST_LOG(info) << "Connector '"sv << display_name
<< "' not found in cached descriptors, refreshing display list..."sv;
kms_display_names(mem_type_e::unknown);
}
}

return -1;
}

class display_t: public platf::display_t {
public:
display_t(mem_type_e mem_type):
Expand All @@ -604,7 +679,12 @@ namespace platf {
int init(const std::string &display_name, const ::video::config_t &config) {
delay = std::chrono::nanoseconds {1s} / config.framerate;

int monitor_index = util::from_view(display_name);
int monitor_index = resolve_display_name(display_name);
if (monitor_index < 0) {
BOOST_LOG(error) << "Could not resolve output name '"sv << display_name
<< "'. Use a numeric index (0, 1, 2, ...) or a connector name (DP-1, HDMI-A-1, ...)"sv;
return -1;
}
int monitor = 0;

fs::path card_dir {"/dev/dri"sv};
Expand Down
4 changes: 4 additions & 0 deletions src/stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ extern "C" {
#include "input.h"
#include "logging.h"
#include "network.h"
#include "nvhttp.h"
#include "platform/common.h"
#include "process.h"
#include "stream.h"
Expand Down Expand Up @@ -1951,6 +1952,9 @@ namespace stream {
revert_display_config = true;
}

// Undo pre-probe commands (e.g. disable virtual display).
nvhttp::undo_pre_probe_cmds();

if (revert_display_config) {
display_device::revert_configuration();
}
Expand Down
8 changes: 8 additions & 0 deletions src/video.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1171,6 +1171,14 @@ namespace video {
return;
}
}

// If output_name is a connector name (e.g. "DP-2") rather than a numeric
// index, it won't match the numeric display_names list. Add it directly
// so display_t::init() can resolve the connector name.
if (!output_name.empty()) {
display_names.emplace_back(output_name);
current_display_index = display_names.size() - 1;
}
}
}

Expand Down