server/src/display_device.cpp
Rama main · réplica de lectura del repositorio original.
/**
* @file src/display_device.cpp
* @brief Definitions for display device handling.
*/
// header include
#include "display_device.h"
#include "display_device_policy.h"
// lib includes
#include <boost/algorithm/string.hpp>
#include <display_device/json.h>
#include <regex>
// local includes
#include "platform/common.h"
#include "rtsp.h"
// No direct helper calls here; this unit now focuses on parsing and small conveniences.
#ifdef _WIN32
#include <display_device/windows/win_api_layer.h>
#include <display_device/windows/win_display_device.h>
#include "platform/windows/virtual_display.h"
#endif
namespace display_device {
namespace {
/**
* @brief Convert string to unsigned int.
* @note For random reason there is std::stoi, but not std::stou...
* @param value String to be converted
* @return Parsed unsigned integer.
*/
unsigned int stou(const std::string &value) {
unsigned long result {std::stoul(value)};
if (result > std::numeric_limits<unsigned int>::max()) {
throw std::out_of_range("stou");
}
return (int) result;
}
/**
* @brief Parse resolution value from the string.
* @param input String to be parsed.
* @param output Reference to output variable to fill in.
* @returns True on successful parsing (empty string allowed), false otherwise.
*
* @examples
* std::optional<Resolution> resolution;
* if (parse_resolution_string("1920x1080", resolution)) {
* if (resolution) {
* BOOST_LOG(info) << "Value was specified";
* }
* else {
* BOOST_LOG(info) << "Value was empty";
* }
* }
* @examples_end
*/
bool parse_resolution_string(const std::string &input, std::optional<Resolution> &output) {
std::string normalized_input {boost::algorithm::trim_copy(input)};
boost::algorithm::replace_all(normalized_input, "×", "x");
const std::string &trimmed_input = normalized_input;
const std::regex resolution_regex {R"(^(\d+)x(\d+)$)"};
if (std::smatch match; std::regex_match(trimmed_input, match, resolution_regex)) {
try {
output = Resolution {
stou(match[1].str()),
stou(match[2].str())
};
return true;
} catch (const std::out_of_range &) {
BOOST_LOG(error) << "Failed to parse resolution string " << trimmed_input << " (number out of range).";
} catch (const std::exception &err) {
BOOST_LOG(error) << "Failed to parse resolution string " << trimmed_input << ":\n"
<< err.what();
}
} else {
if (trimmed_input.empty()) {
output = std::nullopt;
return true;
}
BOOST_LOG(error) << "Failed to parse resolution string " << trimmed_input << R"(. It must match a "1920x1080" pattern!)";
}
return false;
}
/**
* @brief Parse refresh rate value from the string.
* @param input String to be parsed.
* @param output Reference to output variable to fill in.
* @param allow_decimal_point Specify whether the decimal point is allowed or not.
* @returns True on successful parsing (empty string allowed), false otherwise.
*
* @examples
* std::optional<FloatingPoint> refresh_rate;
* if (parse_refresh_rate_string("59.95", refresh_rate)) {
* if (refresh_rate) {
* BOOST_LOG(info) << "Value was specified";
* }
* else {
* BOOST_LOG(info) << "Value was empty";
* }
* }
* @examples_end
*/
bool parse_refresh_rate_string(const std::string &input, std::optional<FloatingPoint> &output, const bool allow_decimal_point = true) {
static const auto is_zero {[](const auto &character) {
return character == '0';
}};
const std::string trimmed_input {boost::algorithm::trim_copy(input)};
const std::regex refresh_rate_regex {allow_decimal_point ? R"(^(\d+)(?:\.(\d+))?$)" : R"(^(\d+)$)"};
if (std::smatch match; std::regex_match(trimmed_input, match, refresh_rate_regex)) {
try {
// Here we are trimming zeros from the string to possibly reduce out of bounds case
std::string trimmed_match_1 {boost::algorithm::trim_left_copy_if(match[1].str(), is_zero)};
if (trimmed_match_1.empty()) {
trimmed_match_1 = "0"s; // Just in case ALL the string is full of zeros, we want to leave one
}
std::string trimmed_match_2;
if (allow_decimal_point && match[2].matched) {
trimmed_match_2 = boost::algorithm::trim_right_copy_if(match[2].str(), is_zero);
}
if (!trimmed_match_2.empty()) {
// We have a decimal point and will have to split it into numerator and denominator.
// For example:
// 59.995:
// numerator = 59995
// denominator = 1000
// We are essentially removing the decimal point here: 59.995 -> 59995
const std::string numerator_str {trimmed_match_1 + trimmed_match_2};
const auto numerator {stou(numerator_str)};
// Here we are counting decimal places and calculating denominator: 10^decimal_places
const auto denominator {static_cast<unsigned int>(std::pow(10, trimmed_match_2.size()))};
output = Rational {numerator, denominator};
} else {
// We do not have a decimal point, just a valid number.
// For example:
// 60:
// numerator = 60
// denominator = 1
output = Rational {stou(trimmed_match_1), 1};
}
return true;
} catch (const std::out_of_range &) {
BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << " (number out of range).";
} catch (const std::exception &err) {
BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << ":\n"
<< err.what();
}
} else {
if (trimmed_input.empty()) {
output = std::nullopt;
return true;
}
BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << ". Must have a pattern of " << (allow_decimal_point ? R"("123" or "123.456")" : R"("123")") << "!";
}
return false;
}
/**
* @brief Parse device preparation option from the user configuration and the session information.
* @param video_config User's video related configuration.
* @returns Parsed device preparation value we need to use.
* Empty optional if no preparation nor configuration shall take place.
*
* @examples
* const config::video_t &video_config { config::video };
* const auto device_prep_option = parse_device_prep_option(video_config);
* @examples_end
*/
[[maybe_unused]] std::optional<SingleDisplayConfiguration::DevicePreparation> parse_device_prep_option(const config::video_t &video_config) {
using enum config::video_t::dd_t::config_option_e;
using enum SingleDisplayConfiguration::DevicePreparation;
switch (video_config.dd.configuration_option) {
case verify_only:
return VerifyOnly;
case ensure_active:
return EnsureActive;
case ensure_primary:
return EnsurePrimary;
case ensure_only_display:
return EnsureOnlyDisplay;
case disabled:
break;
}
return std::nullopt;
}
/**
* @brief Parse resolution option from the user configuration and the session information.
* @param video_config User's video related configuration.
* @param session Session information.
* @param config A reference to a display config object that will be modified on success.
* @returns True on successful parsing, false otherwise.
*
* @examples
* const std::shared_ptr<rtsp_stream::launch_session_t> launch_session;
* const config::video_t &video_config { config::video };
*
* SingleDisplayConfiguration config;
* const bool success = parse_resolution_option(video_config, *launch_session, config);
* @examples_end
*/
[[maybe_unused]] bool parse_resolution_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
using resolution_option_e = config::video_t::dd_t::resolution_option_e;
// An explicit launch resolution override takes highest priority.
if (session.resolution_override) {
if (session.resolution_override->width > 0 && session.resolution_override->height > 0) {
config.m_resolution = Resolution {
static_cast<unsigned int>(session.resolution_override->width),
static_cast<unsigned int>(session.resolution_override->height)
};
BOOST_LOG(debug) << "Using launch resolution override for resolution: "
<< session.resolution_override->width << "x" << session.resolution_override->height;
} else {
BOOST_LOG(error) << "Launch resolution override is invalid: "
<< session.resolution_override->width << "x" << session.resolution_override->height;
return false;
}
return true;
}
// Client display_mode override takes highest priority after launch overrides.
if (session.client_display_mode_override) {
if (session.width >= 0 && session.height >= 0) {
config.m_resolution = Resolution {
static_cast<unsigned int>(session.width),
static_cast<unsigned int>(session.height)
};
BOOST_LOG(debug) << "Using client display mode override for resolution: " << session.width << "x" << session.height;
} else {
BOOST_LOG(error) << "Resolution provided by client display mode override is invalid: " << session.width << "x" << session.height;
return false;
}
return true;
}
switch (video_config.dd.resolution_option) {
case resolution_option_e::automatic:
{
if (session.width >= 0 && session.height >= 0) {
config.m_resolution = Resolution {
static_cast<unsigned int>(session.width),
static_cast<unsigned int>(session.height)
};
} else {
BOOST_LOG(error) << "Resolution provided by client session config is invalid: " << session.width << "x" << session.height;
return false;
}
break;
}
case resolution_option_e::manual:
{
if (!parse_resolution_string(video_config.dd.manual_resolution, config.m_resolution)) {
BOOST_LOG(error) << "Failed to parse manual resolution string!";
return false;
}
if (!config.m_resolution) {
BOOST_LOG(error) << "Manual resolution must be specified!";
return false;
}
break;
}
case resolution_option_e::disabled:
break;
}
return true;
}
/**
* @brief Parse refresh rate option from the user configuration and the session information.
* @param video_config User's video related configuration.
* @param session Session information.
* @param config A reference to a config object that will be modified on success.
* @returns True on successful parsing, false otherwise.
*
* @examples
* const std::shared_ptr<rtsp_stream::launch_session_t> launch_session;
* const config::video_t &video_config { config::video };
*
* SingleDisplayConfiguration config;
* const bool success = parse_refresh_rate_option(video_config, *launch_session, config);
* @examples_end
*/
[[maybe_unused]] bool parse_refresh_rate_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
using refresh_rate_option_e = config::video_t::dd_t::refresh_rate_option_e;
// Client display_mode override takes highest priority
if (session.client_display_mode_override) {
const auto target_millihz = rtsp_stream::effective_display_refresh_millihz(session);
if (target_millihz > 0) {
config.m_refresh_rate = Rational {target_millihz, 1000u};
BOOST_LOG(debug) << "Using client display mode override for refresh rate: "
<< (static_cast<double>(target_millihz) / 1000.0) << " Hz";
} else {
BOOST_LOG(error) << "Refresh value provided by client display mode override is invalid.";
return false;
}
return true;
}
switch (video_config.dd.refresh_rate_option) {
case refresh_rate_option_e::automatic:
{
const auto target_millihz = rtsp_stream::effective_display_refresh_millihz(session);
if (target_millihz > 0) {
config.m_refresh_rate = Rational {target_millihz, 1000u};
} else {
BOOST_LOG(error) << "Refresh value provided by client session config is invalid.";
return false;
}
break;
}
case refresh_rate_option_e::manual:
{
if (!parse_refresh_rate_string(video_config.dd.manual_refresh_rate, config.m_refresh_rate)) {
BOOST_LOG(error) << "Failed to parse manual refresh rate string!";
return false;
}
if (!config.m_refresh_rate) {
BOOST_LOG(error) << "Manual refresh rate must be specified!";
return false;
}
break;
}
case refresh_rate_option_e::prefer_highest:
{
// Hint to Windows to pick the highest available refresh rate for the mode.
// Strategy: request an unrealistically high refresh rate (e.g. 10000 Hz),
// and with SDC_ALLOW_CHANGES the OS will clamp to the closest supported value,
// which for an oversized request resolves to the maximum available.
config.m_refresh_rate = Rational {10000u, 1u};
break;
}
case refresh_rate_option_e::disabled:
break;
}
return true;
}
/**
* @brief Parse HDR option from the user configuration and the session information.
* @param video_config User's video related configuration.
* @param session Session information.
* @returns Parsed HDR state value we need to switch to.
* Empty optional if no action is required.
*
* @examples
* const std::shared_ptr<rtsp_stream::launch_session_t> launch_session;
* const config::video_t &video_config { config::video };
* const auto hdr_option = parse_hdr_option(video_config, *launch_session);
* @examples_end
*/
[[maybe_unused]] std::optional<HdrState> parse_hdr_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
using hdr_option_e = config::video_t::dd_t::hdr_option_e;
if (rtsp_stream::rtx_hdr_enabled(video_config)) {
BOOST_LOG(info) << "RTX HDR: enabled for this app; forcing the source display to SDR so ArtLight Server does not enable host HDR.";
return HdrState::Disabled;
}
if (video_config.dd.wa.dummy_plug_hdr10) {
return HdrState::Enabled;
}
switch (video_config.dd.hdr_option) {
case hdr_option_e::automatic:
return rtsp_stream::effective_hdr_requested(session) ? HdrState::Enabled : HdrState::Disabled;
case hdr_option_e::disabled:
break;
}
return std::nullopt;
}
/**
* @brief Indicates which remapping fields and config structure shall be used.
*/
enum class remapping_type_e {
mixed, ///! Both reseolution and refresh rate may be remapped
resolution_only, ///! Only resolution will be remapped
refresh_rate_only ///! Only refresh rate will be remapped
};
/**
* @brief Determine the ramapping type from the user config.
* @param video_config User's video related configuration.
* @returns Enum value if remapping can be performed, null optional if remapping shall be skipped.
*/
std::optional<remapping_type_e> determine_remapping_type(const config::video_t &video_config) {
using dd_t = config::video_t::dd_t;
const bool auto_resolution {video_config.dd.resolution_option == dd_t::resolution_option_e::automatic};
const bool auto_refresh_rate {video_config.dd.refresh_rate_option == dd_t::refresh_rate_option_e::automatic};
if (auto_resolution && auto_refresh_rate) {
return remapping_type_e::mixed;
}
if (auto_resolution) {
return remapping_type_e::resolution_only;
}
if (auto_refresh_rate) {
return remapping_type_e::refresh_rate_only;
}
return std::nullopt;
}
/**
* @brief Contains remapping data parsed from the string values.
*/
struct parsed_remapping_entry_t {
std::optional<Resolution> requested_resolution;
std::optional<FloatingPoint> requested_fps;
std::optional<Resolution> final_resolution;
std::optional<FloatingPoint> final_refresh_rate;
};
/**
* @brief Check if resolution is to be mapped based on remmaping type.
* @param type Remapping type to check.
* @returns True if resolution is to be mapped, false otherwise.
*/
bool is_resolution_mapped(const remapping_type_e type) {
return type == remapping_type_e::resolution_only || type == remapping_type_e::mixed;
}
/**
* @brief Check if FPS is to be mapped based on remmaping type.
* @param type Remapping type to check.
* @returns True if FPS is to be mapped, false otherwise.
*/
bool is_fps_mapped(const remapping_type_e type) {
return type == remapping_type_e::refresh_rate_only || type == remapping_type_e::mixed;
}
/**
* @brief Parse the remapping entry from the config into an internal structure.
* @param entry Entry to parse.
* @param type Specify which entry fields should be parsed.
* @returns Parsed structure or null optional if a necessary field could not be parsed.
*/
std::optional<parsed_remapping_entry_t> parse_remapping_entry(const config::video_t::dd_t::mode_remapping_entry_t &entry, const remapping_type_e type) {
parsed_remapping_entry_t result {};
if (is_resolution_mapped(type) && (!parse_resolution_string(entry.requested_resolution, result.requested_resolution) ||
!parse_resolution_string(entry.final_resolution, result.final_resolution))) {
return std::nullopt;
}
if (is_fps_mapped(type) && (!parse_refresh_rate_string(entry.requested_fps, result.requested_fps, false) ||
!parse_refresh_rate_string(entry.final_refresh_rate, result.final_refresh_rate))) {
return std::nullopt;
}
return result;
}
/**
* @brief Remap the the requested display mode based on the config.
* @param video_config User's video related configuration.
* @param session Session information.
* @param config A reference to a config object that will be modified on success.
* @returns True if the remapping was performed or skipped, false if remapping has failed due to invalid config.
*
* @examples
* const std::shared_ptr<rtsp_stream::launch_session_t> launch_session;
* const config::video_t &video_config { config::video };
*
* SingleDisplayConfiguration config;
* const bool success = remap_display_mode_if_needed(video_config, *launch_session, config);
* @examples_end
*/
[[maybe_unused]] bool remap_display_mode_if_needed(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
// Client display_mode override takes highest priority - skip remapping
if (session.client_display_mode_override) {
BOOST_LOG(debug) << "Skipping display mode remapping because client has display mode override active.";
return true;
}
const auto remapping_type {determine_remapping_type(video_config)};
if (!remapping_type) {
return true;
}
const auto &remapping_list {[&]() {
using enum remapping_type_e;
switch (*remapping_type) {
case resolution_only:
return video_config.dd.mode_remapping.resolution_only;
case refresh_rate_only:
return video_config.dd.mode_remapping.refresh_rate_only;
case mixed:
default:
return video_config.dd.mode_remapping.mixed;
}
}()};
if (remapping_list.empty()) {
BOOST_LOG(debug) << "No values are available for display mode remapping.";
return true;
}
BOOST_LOG(debug) << "Trying to remap display modes...";
const auto entry_to_string {[type = *remapping_type](const config::video_t::dd_t::mode_remapping_entry_t &entry) {
const bool mapping_resolution {is_resolution_mapped(type)};
const bool mapping_fps {is_fps_mapped(type)};
// clang-format off
return (mapping_resolution ? " - requested resolution: "s + entry.requested_resolution + "\n" : "") +
(mapping_fps ? " - requested FPS: "s + entry.requested_fps + "\n" : "") +
(mapping_resolution ? " - final resolution: "s + entry.final_resolution + "\n" : "") +
(mapping_fps ? " - final refresh rate: "s + entry.final_refresh_rate : "");
// clang-format on
}};
for (const auto &entry : remapping_list) {
const auto parsed_entry {parse_remapping_entry(entry, *remapping_type)};
if (!parsed_entry) {
BOOST_LOG(error) << "Failed to parse remapping entry from:\n"
<< entry_to_string(entry);
return false;
}
if (!parsed_entry->final_resolution && !parsed_entry->final_refresh_rate) {
BOOST_LOG(error) << "At least one final value must be set for remapping display modes! Entry:\n"
<< entry_to_string(entry);
return false;
}
// Note: at this point config should already have parsed resolution set.
if (parsed_entry->requested_resolution && parsed_entry->requested_resolution != config.m_resolution) {
BOOST_LOG(verbose) << "Skipping remapping because requested resolutions do not match! Entry:\n"
<< entry_to_string(entry);
continue;
}
// Note: at this point config should already have parsed refresh rate set.
if (parsed_entry->requested_fps && parsed_entry->requested_fps != config.m_refresh_rate) {
BOOST_LOG(verbose) << "Skipping remapping because requested FPS do not match! Entry:\n"
<< entry_to_string(entry);
continue;
}
BOOST_LOG(info) << "Remapping requested display mode. Entry:\n"
<< entry_to_string(entry);
if (parsed_entry->final_resolution) {
config.m_resolution = parsed_entry->final_resolution;
}
if (parsed_entry->final_refresh_rate) {
config.m_refresh_rate = parsed_entry->final_refresh_rate;
}
break;
}
return true;
}
// All non-helper scheduling and platform display APIs are removed. We route exclusively
// through the Windows display helper (no-ops on other platforms).
} // namespace
// Old in-process API removed: no init/apply/revert/enumeration here.
#ifdef _WIN32
static bool iequals(const std::string &a, const std::string &b) {
if (a.size() != b.size()) {
return false;
}
for (size_t i = 0; i < a.size(); ++i) {
if (std::tolower(static_cast<unsigned char>(a[i])) != std::tolower(static_cast<unsigned char>(b[i]))) {
return false;
}
}
return true;
}
[[maybe_unused]] static std::string resolve_device_id(const std::string &output_name) {
if (output_name.empty()) {
return output_name;
}
try {
auto api = std::make_shared<display_device::WinApiLayer>();
display_device::WinDisplayDevice dd(api);
const auto devices = dd.enumAvailableDevices(DeviceEnumerationDetail::Minimal);
for (const auto &d : devices) {
if (d.m_device_id.empty()) {
continue;
}
if (iequals(d.m_device_id, output_name) ||
(!d.m_display_name.empty() && iequals(d.m_display_name, output_name)) ||
(!d.m_friendly_name.empty() && iequals(d.m_friendly_name, output_name))) {
return d.m_device_id;
}
}
} catch (...) {
}
return output_name;
}
#else
[[maybe_unused]] static std::string resolve_device_id(const std::string &output_name) {
return output_name;
}
#endif
std::string map_output_name(const std::string &output_name) {
#ifdef _WIN32
try {
if (output_name.empty()) {
return output_name;
}
// If caller already provided a Windows logical display name, return it.
// These are of the form "\\.\\DISPLAY#".
const auto is_win_display_name = [&]() -> bool {
// Minimal check: begins with \\.\DISPLAY (case-insensitive)
static const std::string prefix = "\\\\.\\DISPLAY";
if (output_name.size() < prefix.size()) {
return false;
}
for (size_t i = 0; i < prefix.size(); ++i) {
if (std::tolower((unsigned char) output_name[i]) != std::tolower((unsigned char) prefix[i])) {
return false;
}
}
return true;
}();
if (is_win_display_name) {
return output_name;
}
// Otherwise try to map any provided identifier (device_id, friendly name, or display name)
// to the Windows logical display name using libdisplaydevice enumeration.
auto api = std::make_shared<display_device::WinApiLayer>();
display_device::WinDisplayDevice dd(api);
const auto devices = dd.enumAvailableDevices(DeviceEnumerationDetail::Minimal);
auto equals_ci = [](const std::string &a, const std::string &b) {
if (a.size() != b.size()) {
return false;
}
for (size_t i = 0; i < a.size(); ++i) {
if (std::tolower((unsigned char) a[i]) != std::tolower((unsigned char) b[i])) {
return false;
}
}
return true;
};
for (const auto &d : devices) {
// Match against device_id (preferred), display_name, or friendly_name
if ((!d.m_device_id.empty() && equals_ci(d.m_device_id, output_name)) ||
(!d.m_display_name.empty() && equals_ci(d.m_display_name, output_name)) ||
(!d.m_friendly_name.empty() && equals_ci(d.m_friendly_name, output_name))) {
if (!d.m_display_name.empty()) {
return d.m_display_name; // Return logical name consumable by DXGI
}
break;
}
}
// Fallback to original if not found
return output_name;
} catch (...) {
// If enumeration fails for any reason, fall back to the provided value.
return output_name;
}
#else
// Non-Windows: no mapping needed
return output_name;
#endif
}
bool output_exists(const std::string &output_name) {
#ifdef _WIN32
if (output_name.empty()) {
return true;
}
try {
auto api = std::make_shared<display_device::WinApiLayer>();
display_device::WinDisplayDevice dd(api);
const auto devices = dd.enumAvailableDevices(DeviceEnumerationDetail::Minimal);
auto equals_ci = [](const std::string &a, const std::string &b) {
if (a.size() != b.size()) {
return false;
}
for (size_t i = 0; i < a.size(); ++i) {
if (std::tolower((unsigned char) a[i]) != std::tolower((unsigned char) b[i])) {
return false;
}
}
return true;
};
for (const auto &d : devices) {
if ((!d.m_device_id.empty() && equals_ci(d.m_device_id, output_name)) ||
(!d.m_display_name.empty() && equals_ci(d.m_display_name, output_name)) ||
(!d.m_friendly_name.empty() && equals_ci(d.m_friendly_name, output_name))) {
return true;
}
}
return false;
} catch (...) {
// Avoid false negatives if enumeration fails; assume the display may exist.
return true;
}
#else
(void) output_name;
return true;
#endif
}
bool output_is_active(const std::string &output_name) {
#ifdef _WIN32
if (output_name.empty()) {
return true;
}
try {
auto api = std::make_shared<display_device::WinApiLayer>();
display_device::WinDisplayDevice dd(api);
const auto devices = dd.enumAvailableDevices(DeviceEnumerationDetail::Minimal);
auto equals_ci = [](const std::string &a, const std::string &b) {
if (a.size() != b.size()) {
return false;
}
for (size_t i = 0; i < a.size(); ++i) {
if (std::tolower((unsigned char) a[i]) != std::tolower((unsigned char) b[i])) {
return false;
}
}
return true;
};
for (const auto &d : devices) {
if ((!d.m_device_id.empty() && equals_ci(d.m_device_id, output_name)) ||
(!d.m_display_name.empty() && equals_ci(d.m_display_name, output_name)) ||
(!d.m_friendly_name.empty() && equals_ci(d.m_friendly_name, output_name))) {
return !d.m_display_name.empty();
}
}
return false;
} catch (...) {
// Avoid false negatives if enumeration fails; assume the display may be active.
return true;
}
#else
(void) output_name;
return true;
#endif
}
namespace {
policy::video_config_t to_policy_config(const config::video_t &video_config) {
policy::video_config_t result {};
result.output_name = config::get_active_output_name();
result.rtx_hdr_enabled = rtsp_stream::rtx_hdr_enabled(video_config);
result.dd.wa.dummy_plug_hdr10 = video_config.dd.wa.dummy_plug_hdr10;
result.dd.configuration_option = static_cast<policy::video_config_t::dd_t::config_option_e>(video_config.dd.configuration_option);
result.dd.resolution_option = static_cast<policy::video_config_t::dd_t::resolution_option_e>(video_config.dd.resolution_option);
result.dd.manual_resolution = video_config.dd.manual_resolution;
result.dd.refresh_rate_option = static_cast<policy::video_config_t::dd_t::refresh_rate_option_e>(video_config.dd.refresh_rate_option);
result.dd.manual_refresh_rate = video_config.dd.manual_refresh_rate;
result.dd.hdr_option = static_cast<policy::video_config_t::dd_t::hdr_option_e>(video_config.dd.hdr_option);
const auto copy_entries = [](const auto &source, auto &destination) {
for (const auto &entry : source) destination.push_back({entry.requested_resolution, entry.requested_fps, entry.final_resolution, entry.final_refresh_rate});
};
copy_entries(video_config.dd.mode_remapping.mixed, result.dd.mode_remapping.mixed);
copy_entries(video_config.dd.mode_remapping.resolution_only, result.dd.mode_remapping.resolution_only);
copy_entries(video_config.dd.mode_remapping.refresh_rate_only, result.dd.mode_remapping.refresh_rate_only);
return result;
}
policy::session_t to_policy_session(const rtsp_stream::launch_session_t &session) {
policy::session_t result {
.width = session.width, .height = session.height, .fps = session.fps,
.enable_hdr = session.enable_hdr, .prefer_sdr_10bit = session.prefer_sdr_10bit, .force_sdr = session.force_sdr,
.client_display_mode_override = session.client_display_mode_override,
// Normalize at the RTSP boundary: downstream sessions may store FPS in
// millihertz, while the isolated policy interprets its FPS fallback as Hz.
.client_display_refresh_millihz = rtsp_stream::effective_display_refresh_millihz(session),
.framegen_refresh_rate = session.framegen_refresh_rate,
.framegen_refresh_millihz = session.framegen_refresh_millihz,
};
if (session.resolution_override) result.resolution_override = {{session.resolution_override->width, session.resolution_override->height}};
return result;
}
} // namespace
bool refresh_rate_override_active(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
return policy::refresh_rate_override_active(to_policy_config(video_config), to_policy_session(session));
#if 0
using refresh_rate_option_e = config::video_t::dd_t::refresh_rate_option_e;
if (video_config.dd.refresh_rate_option == refresh_rate_option_e::manual) {
return true;
}
if (session.client_display_mode_override) {
return false;
}
const auto remapping_type = determine_remapping_type(video_config);
if (!remapping_type || !is_fps_mapped(*remapping_type)) {
return false;
}
const auto &remapping_list {[&]() {
using enum remapping_type_e;
switch (*remapping_type) {
case resolution_only:
return video_config.dd.mode_remapping.resolution_only;
case refresh_rate_only:
return video_config.dd.mode_remapping.refresh_rate_only;
case mixed:
default:
return video_config.dd.mode_remapping.mixed;
}
}()};
if (remapping_list.empty()) {
return false;
}
const auto target_millihz = rtsp_stream::effective_display_refresh_millihz(session);
if (target_millihz == 0) {
return false;
}
const FloatingPoint requested_fps = Rational {target_millihz, 1000u};
for (const auto &entry : remapping_list) {
const auto parsed_entry {parse_remapping_entry(entry, *remapping_type)};
if (!parsed_entry) {
return false;
}
if (!parsed_entry->final_refresh_rate) {
continue;
}
if (parsed_entry->requested_fps && parsed_entry->requested_fps != requested_fps) {
continue;
}
return true;
}
return false;
#endif
}
std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration> parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
const auto parsed = policy::parse_configuration(to_policy_config(video_config), to_policy_session(session));
if (std::holds_alternative<policy::failed_to_parse_tag_t>(parsed)) return failed_to_parse_tag_t {};
if (std::holds_alternative<policy::configuration_disabled_tag_t>(parsed)) return configuration_disabled_tag_t {};
const auto &policy_result = std::get<policy::configuration_t>(parsed);
SingleDisplayConfiguration result {};
result.m_device_id = policy_result.m_device_id;
result.m_device_prep = static_cast<SingleDisplayConfiguration::DevicePreparation>(policy_result.m_device_prep);
if (policy_result.m_resolution) result.m_resolution = Resolution {policy_result.m_resolution->m_width, policy_result.m_resolution->m_height};
if (policy_result.m_refresh_rate) result.m_refresh_rate = Rational {policy_result.m_refresh_rate->m_numerator, policy_result.m_refresh_rate->m_denominator};
if (policy_result.m_hdr_state) result.m_hdr_state = static_cast<HdrState>(*policy_result.m_hdr_state);
return result;
#if 0
const auto device_prep {parse_device_prep_option(video_config)};
if (!device_prep) {
return configuration_disabled_tag_t {};
}
SingleDisplayConfiguration config;
config.m_device_id = resolve_device_id(config::get_active_output_name());
config.m_device_prep = *device_prep;
config.m_hdr_state = parse_hdr_option(video_config, session);
if (!parse_resolution_option(video_config, session, config)) {
// Error already logged
return failed_to_parse_tag_t {};
}
if (!parse_refresh_rate_option(video_config, session, config)) {
// Error already logged
return failed_to_parse_tag_t {};
}
if (!remap_display_mode_if_needed(video_config, session, config)) {
// Error already logged
return failed_to_parse_tag_t {};
}
return config;
#endif
}
} // namespace display_device