Migrate virtual display config to new Display Device API

This commit is contained in:
Yukino Song
2025-01-15 22:50:08 +08:00
parent 4a2d8b474b
commit 4b78e3b47d
9 changed files with 62 additions and 165 deletions

View File

@@ -417,8 +417,6 @@ namespace config {
video_t video {
false, // headless_mode
false, // follow_client_hdr
true, // set_vdisplay_primary
28, // qp
0, // hevc_mode
@@ -1091,8 +1089,6 @@ namespace config {
}
bool_f(vars, "headless_mode", video.headless_mode);
bool_f(vars, "follow_client_hdr", video.follow_client_hdr);
bool_f(vars, "set_vdisplay_primary", video.set_vdisplay_primary);
int_f(vars, "qp", video.qp);
int_between_f(vars, "hevc_mode", video.hevc_mode, { 0, 3 });
int_between_f(vars, "av1_mode", video.av1_mode, { 0, 3 });

View File

@@ -16,8 +16,6 @@
namespace config {
struct video_t {
bool headless_mode;
bool follow_client_hdr;
bool set_vdisplay_primary;
// ffmpeg params
int qp; // higher == more compression and less quality

View File

@@ -738,6 +738,24 @@ namespace display_device {
return DD_DATA.sm_instance->execute([&output_name](auto &settings_iface) { return settings_iface.getDisplayName(output_name); });
}
std::string
map_display_name(const std::string &display_name) {
std::lock_guard lock { DD_DATA.mutex };
if (!DD_DATA.sm_instance) {
return {};
}
const auto available_devices { DD_DATA.sm_instance->execute([](auto &settings_iface) { return settings_iface.enumAvailableDevices(); }) };
for (auto &i : available_devices) {
if (i.m_display_name == display_name) {
return i.m_device_id;
}
}
return {};
}
void
configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
const auto result { parse_configuration(video_config, session) };

View File

@@ -48,6 +48,9 @@ namespace display_device {
[[nodiscard]] std::string
map_output_name(const std::string &output_name);
[[nodiscard]] std::string
map_display_name(const std::string &display_name);
/**
* @brief Configure the display device based on the user configuration and the session information.
* @note This is a convenience method for calling similar method of a different signature.
@@ -119,7 +122,7 @@ namespace display_device {
* const auto result = reset_persistence();
* @examples_end
*/
[[nodiscard]] bool
bool
reset_persistence();
/**

View File

@@ -163,9 +163,6 @@ namespace proc {
// Ensure starting from a clean slate
terminate();
// Save the original output name in case we modify it temporary later
std::string output_name_orig = config::video.output_name;
_app = app;
_app_id = app_id;
_launch_session = launch_session;
@@ -191,17 +188,19 @@ namespace proc {
render_height &= ~1;
}
launch_session->width = render_width;
launch_session->height = render_height;
#ifdef _WIN32
bool create_virtual_display = config::video.headless_mode || launch_session->virtual_display || _app.virtual_display;
this->initial_display = config::video.output_name;
// Executed when returning from function
auto fg = util::fail_guard([&]() {
// Restore to user defined output name
config::video.output_name = output_name_orig;
config::video.output_name = this->initial_display;
terminate();
if (!create_virtual_display) {
display_device::revert_configuration();
}
display_device::revert_configuration();
});
if (create_virtual_display) {
@@ -211,8 +210,6 @@ namespace proc {
}
if (vDisplayDriverStatus == VDISPLAY::DRIVER_STATUS::OK) {
std::wstring prevPrimaryDisplayName = VDISPLAY::getPrimaryDisplay();
// Try set the render adapter matching the capture adapter if user has specified one
if (!config::video.adapter_name.empty()) {
VDISPLAY::setRenderAdapterByName(platf::from_utf8(config::video.adapter_name));
@@ -236,69 +233,26 @@ namespace proc {
BOOST_LOG(info) << "Virtual Display created at " << vdisplayName;
std::wstring currentPrimaryDisplayName = VDISPLAY::getPrimaryDisplay();
// Don't change display settings when no params are given
if (launch_session->width && launch_session->height && launch_session->fps) {
// Apply display settings
VDISPLAY::changeDisplaySettings(vdisplayName.c_str(), render_width, render_height, launch_session->fps);
}
bool shouldActuallySetPrimary = false;
// Determine if we need to set the virtual display as primary
// Client request overrides local config
bool shouldSetVDisplayPrimary = launch_session->virtual_display;
if (!shouldSetVDisplayPrimary) {
// App config overrides global config
if (_app.virtual_display) {
shouldSetVDisplayPrimary = _app.virtual_display_primary;
} else {
shouldSetVDisplayPrimary = config::video.set_vdisplay_primary;
}
}
if (shouldSetVDisplayPrimary) {
shouldActuallySetPrimary = (currentPrimaryDisplayName != vdisplayName);
} else {
shouldActuallySetPrimary = (currentPrimaryDisplayName != prevPrimaryDisplayName);
}
// Set primary display if needed
if (shouldActuallySetPrimary) {
auto disp = shouldSetVDisplayPrimary ? vdisplayName : prevPrimaryDisplayName;
BOOST_LOG(info) << "Setting display " << disp << " primary";
if (!VDISPLAY::setPrimaryDisplay(disp.c_str())) {
BOOST_LOG(info) << "Setting display " << disp << " primary failed! Are you using Windows 11 24H2?";
}
}
// Set virtual_display to true when everything went fine
this->virtual_display = true;
this->display_name = platf::to_utf8(vdisplayName);
if (config::video.headless_mode) {
// When using headless mode, we don't care which display user configured to use.
// So we always set output_name to the newly created virtual display as a workaround for
// empty name when probing graphics cards.
config::video.output_name = this->display_name;
}
// When using virtual display, we don't care which display user configured to use.
// So we always set output_name to the newly created virtual display as a workaround for
// empty name when probing graphics cards.
config::video.output_name = display_device::map_display_name(this->display_name);
}
} else {
display_device::configure_display(config::video, *launch_session);
}
#else
// Executed when returning from function
auto fg = util::fail_guard([&]() {
// Restore to user defined output name
config::video.output_name = output_name_orig;
terminate();
display_device::revert_configuration();
});
#endif
display_device::configure_display(config::video, *launch_session);
#endif
// Probe encoders again before streaming to ensure our chosen
// encoder matches the active GPU (which could have changed
@@ -420,60 +374,8 @@ namespace proc {
_app_launch_time = std::chrono::steady_clock::now();
#ifdef _WIN32
auto resetHDRThread = std::thread([this, enable_hdr = launch_session->enable_hdr]{
// Windows doesn't seem to be able to set HDR correctly when a display is just connected / changed resolution,
// so we have tooggle HDR for the virtual display manually after a delay.
auto retryInterval = 200ms;
while (is_changing_settings_going_to_fail()) {
if (retryInterval > 2s) {
BOOST_LOG(warning) << "Restoring HDR settings failed due to retry timeout!";
return;
}
std::this_thread::sleep_for(retryInterval);
retryInterval *= 2;
}
// We should have got the actual streaming display by now
std::string currentDisplay = this->display_name;
if (currentDisplay.empty()) {
BOOST_LOG(warning) << "Not getting current display in time! HDR will not be toggled.";
} else {
auto currentDisplayW = platf::from_utf8(currentDisplay).c_str();
this->initial_display = currentDisplay;
this->initial_hdr = VDISPLAY::getDisplayHDRByName(currentDisplayW);
if (config::video.follow_client_hdr) {
if (!VDISPLAY::setDisplayHDRByName(currentDisplayW, false)) {
return;
}
if (enable_hdr) {
if (VDISPLAY::setDisplayHDRByName(currentDisplayW, true)) {
BOOST_LOG(info) << "HDR enabled for display " << currentDisplay;
} else {
BOOST_LOG(info) << "HDR enable failed for display " << currentDisplay;
}
}
} else if (this->initial_hdr) {
if (VDISPLAY::setDisplayHDRByName(currentDisplayW, false) && VDISPLAY::setDisplayHDRByName(currentDisplayW, true)) {
BOOST_LOG(info) << "HDR toggled successfully for display " << currentDisplay;
} else {
BOOST_LOG(info) << "HDR toggle failed for display " << currentDisplay;
}
}
}
});
resetHDRThread.detach();
#endif
fg.disable();
// Restore to user defined output name
config::video.output_name = output_name_orig;
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
system_tray::update_tray_playing(_app.name);
#endif
@@ -561,36 +463,39 @@ namespace proc {
_pipe.reset();
#ifdef _WIN32
if (config::video.follow_client_hdr && !this->initial_display.empty()) {
if (VDISPLAY::setDisplayHDRByName(platf::from_utf8(this->initial_display).c_str(), this->initial_hdr)) {
BOOST_LOG(info) << "HDR restored successfully for display " << this->initial_display;
} else {
BOOST_LOG(info) << "HDR restore failed for display " << this->initial_display;
};
}
bool has_run = _app_id > 0;
if (vDisplayDriverStatus == VDISPLAY::DRIVER_STATUS::OK && _launch_session && this->virtual_display) {
#ifdef _WIN32
bool used_virtual_display = vDisplayDriverStatus == VDISPLAY::DRIVER_STATUS::OK && _launch_session && this->virtual_display;
if (used_virtual_display) {
if (VDISPLAY::removeVirtualDisplay(_launch_session->display_guid)) {
BOOST_LOG(info) << "Virtual Display removed successfully";
} else {
BOOST_LOG(info) << "Virtual Display remove failed";
}
}
#endif
bool has_run = _app_id > 0;
// Only show the Stopped notification if we actually have an app to stop
// Since terminate() is always run when a new app has started
if (proc::proc.get_last_run_app_name().length() > 0 && has_run) {
if (used_virtual_display) {
display_device::reset_persistence();
} else {
display_device::revert_configuration();
}
#else
if (proc::proc.get_last_run_app_name().length() > 0 && has_run) {
display_device::revert_configuration();
#endif
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
system_tray::update_tray_stopped(proc::proc.get_last_run_app_name());
#endif
display_device::revert_configuration();
}
// Restore output name to its original value
config::video.output_name = initial_display;
_app_id = -1;
display_name.clear();
initial_hdr = false;
@@ -937,7 +842,6 @@ namespace proc {
auto wait_all = app_node.get_optional<bool>("wait-all"s);
auto exit_timeout = app_node.get_optional<int>("exit-timeout"s);
auto virtual_display = app_node.get_optional<bool>("virtual-display"s);
auto virtual_display_primary = app_node.get_optional<bool>("virtual-display-primary"s);
auto resolution_scale_factor = app_node.get_optional<int>("scale-factor"s);
auto use_app_identity = app_node.get_optional<bool>("use-app-identity"s);
@@ -1012,7 +916,6 @@ namespace proc {
ctx.wait_all = wait_all.value_or(true);
ctx.exit_timeout = std::chrono::seconds { exit_timeout.value_or(5) };
ctx.virtual_display = virtual_display.value_or(false);
ctx.virtual_display_primary = virtual_display_primary.value_or(true);
ctx.scale_factor = resolution_scale_factor.value_or(100);
ctx.use_app_identity = use_app_identity.value_or(false);
@@ -1041,7 +944,6 @@ namespace proc {
ctx.name = "Virtual Display";
ctx.image_path = parse_env_val(this_env, "virtual_desktop.png");
ctx.virtual_display = true;
ctx.virtual_display_primary = true;
ctx.scale_factor = 100;
ctx.use_app_identity = false;

View File

@@ -277,16 +277,9 @@
id="virtualDisplay"
label="apps.virtual_display"
desc="apps.virtual_display_desc"
v-model="editForm['exclude-global-prep-cmd']"
v-model="editForm['virtual-display']"
default="false"
></Checkbox>
<!-- set virtual display to primary -->
<div class="mb-3 form-check" v-if="platform === 'windows' && editForm['virtual-display'] == 'true'">
<label for="virtualDisplayPrimary" class="form-check-label">{{ $t('apps.virtual_display_primary') }}</label>
<input type="checkbox" class="form-check-input" id="virtualDisplayPrimary" v-model="editForm['virtual-display-primary']"
true-value="true" false-value="false" />
<div class="form-text">{{ $t('apps.virtual_display_primary_desc') }}</div>
</div>
<!-- use app identity -->
<div class="mb-3 form-check">
<label for="useAppIdentity" class="form-check-label">{{ $t('apps.use_app_identity') }}</label>
@@ -414,7 +407,6 @@
"prep-cmd": [],
detached: [],
"image-path": "",
"virtual-display-primary": true,
"scale-factor": "100",
"use-app-identity": false
}

View File

@@ -183,9 +183,7 @@
"auto_capture_sink": "enabled",
"adapter_name": "",
"output_name": "",
"headless_mode": "disabled",
"fallback_mode": "",
"set_vdisplay_primary": "enabled",
"dd_configuration_option": "verify_only",
"dd_resolution_option": "auto",
"dd_manual_resolution": "",

View File

@@ -125,23 +125,17 @@ const validateFallbackMode = (event) => {
<!-- Fallback Display Mode -->
<div class="mb-3">
<label for="fallback_mode" class="form-label">{{ $t('config.fallback_mode') }}</label>
<input
type="text"
class="form-control"
id="fallback_mode"
v-model="config.fallback_mode"
<input
type="text"
class="form-control"
id="fallback_mode"
v-model="config.fallback_mode"
placeholder="1920x1080x60"
@input="validateFallbackMode"
/>
<div class="form-text">{{ $t('config.fallback_mode_desc') }}</div>
</div>
<div class="mb-3 form-check" v-if="platform === 'windows'">
<input type="checkbox" class="form-check-input" id="follow_client_hdr" v-model="config.follow_client_hdr" true-value="enabled" false-value="disabled"/>
<label for="follow_client_hdr" class="form-check-label">{{ $t('config.follow_client_hdr') }}</label>
<div class="form-text pre-wrap">{{ $t('config.follow_client_hdr_desc') }}</div>
</div>
<!-- Headless Mode -->
<div class="mb-3 form-check" v-if="platform === 'windows'">
<input type="checkbox" class="form-check-input" id="headless_mode" v-model="config.headless_mode" true-value="enabled" false-value="disabled"/>
@@ -149,13 +143,6 @@ const validateFallbackMode = (event) => {
<div class="form-text">{{ $t('config.headless_mode_desc') }}</div>
</div>
<!-- Set VDisplay Primary -->
<div class="mb-3 form-check" v-if="platform === 'windows'">
<input type="checkbox" class="form-check-input" id="set_vdisplay_primary" v-model="config.set_vdisplay_primary" true-value="enabled" false-value="disabled"/>
<label for="set_vdisplay_primary" class="form-check-label">{{ $t('config.set_vdisplay_primary') }}</label>
<div class="form-text">{{ $t('config.set_vdisplay_primary_desc') }}</div>
</div>
<!-- SudoVDA Driver Status -->
<div class="alert" :class="[vdisplay === '0' ? 'alert-success' : 'alert-warning']" v-if="platform === 'windows'">
<i class="fa-solid fa-xl fa-circle-info"></i> SudoVDA Driver status: {{currentDriverStatus}}

View File

@@ -55,12 +55,15 @@ function addRemappingEntry() {
<h2 class="accordion-header">
<button class="accordion-button" type="button" data-bs-toggle="collapse"
data-bs-target="#panelsStayOpen-collapseOne">
{{ $t('config.dd_options_header') }} {{ $t('dd_options_header_vdd_na') }}
{{ $t('config.dd_options_header') }}
</button>
</h2>
<div id="panelsStayOpen-collapseOne" class="accordion-collapse collapse show"
aria-labelledby="panelsStayOpen-headingOne">
<div class="accordion-body">
<div class="alert alert-info" v-if="platform === 'windows'">
<i class="fa-solid fa-xl fa-circle-info"></i>{{ $t('config.dd_resolution_option_vdisplay_desc') }}
</div>
<!-- Configuration option -->
<div class="mb-3">