/** * @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 #include #include // 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 #include #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::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; * 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 &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 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 &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(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 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 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(session.resolution_override->width), static_cast(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(session.width), static_cast(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(session.width), static_cast(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 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(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 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 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 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 requested_resolution; std::optional requested_fps; std::optional final_resolution; std::optional 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 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 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(a[i])) != std::tolower(static_cast(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::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::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::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::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(video_config.dd.configuration_option); result.dd.resolution_option = static_cast(video_config.dd.resolution_option); result.dd.manual_resolution = video_config.dd.manual_resolution; result.dd.refresh_rate_option = static_cast(video_config.dd.refresh_rate_option); result.dd.manual_refresh_rate = video_config.dd.manual_refresh_rate; result.dd.hdr_option = static_cast(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 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(parsed)) return failed_to_parse_tag_t {}; if (std::holds_alternative(parsed)) return configuration_disabled_tag_t {}; const auto &policy_result = std::get(parsed); SingleDisplayConfiguration result {}; result.m_device_id = policy_result.m_device_id; result.m_device_prep = static_cast(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(*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