diff --git a/src/common/include/displaydevice/settingsmanagerinterface.h b/src/common/include/displaydevice/settingsmanagerinterface.h index 93b99a4..7f225d7 100644 --- a/src/common/include/displaydevice/settingsmanagerinterface.h +++ b/src/common/include/displaydevice/settingsmanagerinterface.h @@ -17,6 +17,7 @@ namespace display_device { ApiTemporarilyUnavailable, DevicePrepFailed, PrimaryDevicePrepFailed, + DisplayModePrepFailed, PersistenceSaveFailed, }; diff --git a/src/common/include/displaydevice/types.h b/src/common/include/displaydevice/types.h index 457bec0..eb29fb3 100644 --- a/src/common/include/displaydevice/types.h +++ b/src/common/include/displaydevice/types.h @@ -51,8 +51,8 @@ namespace display_device { */ struct Info { Resolution m_resolution {}; /**< Resolution of an active device. */ - float m_resolution_scale {}; /**< Resolution scaling of an active device. */ - float m_refresh_rate {}; /**< Refresh rate of an active device. */ + double m_resolution_scale {}; /**< Resolution scaling of an active device. */ + double m_refresh_rate {}; /**< Refresh rate of an active device. */ bool m_primary {}; /**< Indicates whether the device is a primary display. */ Point m_origin_point {}; /**< A starting point of the display. */ std::optional<HdrState> m_hdr_state {}; /**< HDR of an active device. */ @@ -101,7 +101,7 @@ namespace display_device { std::string m_device_id {}; /**< Device to perform configuration for (can be empty if primary device should be used). */ DevicePreparation m_device_prep {}; /**< Instruction on how to prepare device. */ std::optional<Resolution> m_resolution {}; /**< Resolution to configure. */ - std::optional<float> m_refresh_rate {}; /**< Refresh rate to configure. */ + std::optional<double> m_refresh_rate {}; /**< Refresh rate to configure. */ std::optional<HdrState> m_hdr_state {}; /**< HDR state to configure (if supported by the display). */ /** diff --git a/src/common/types.cpp b/src/common/types.cpp index b60314c..f6901c8 100644 --- a/src/common/types.cpp +++ b/src/common/types.cpp @@ -3,8 +3,8 @@ namespace { bool - fuzzyCompare(const float lhs, const float rhs) { - return std::abs(lhs - rhs) * 100000.f <= std::min(std::abs(lhs), std::abs(rhs)); + fuzzyCompare(const double lhs, const double rhs) { + return std::abs(lhs - rhs) * 1000000000000. <= std::min(std::abs(lhs), std::abs(rhs)); } } // namespace diff --git a/src/windows/include/displaydevice/windows/settingsmanager.h b/src/windows/include/displaydevice/windows/settingsmanager.h index 98573f7..01c4338 100644 --- a/src/windows/include/displaydevice/windows/settingsmanager.h +++ b/src/windows/include/displaydevice/windows/settingsmanager.h @@ -64,6 +64,18 @@ namespace display_device { [[nodiscard]] bool preparePrimaryDevice(const SingleDisplayConfiguration &config, const std::string &device_to_configure, DdGuardFn &guard_fn, SingleDisplayConfigState &new_state); + /** + * @brief Changes or restores the display modes based on the cached state, new state and configuration. + * @param config Configuration to be used for preparing display modes. + * @param device_to_configure The main device to be used for preparation. + * @param additional_devices_to_configure Additional devices that should be configured. + * @param guard_fn Reference to the guard function which will be set to restore original state (if needed) in case something else fails down the line. + * @param new_state Reference to the new state which is to be updated accordingly. + * @return True if no errors have occured, false otherwise. + */ + [[nodiscard]] bool + prepareDisplayModes(const SingleDisplayConfiguration &config, const std::string &device_to_configure, const std::set<std::string> &additional_devices_to_configure, DdGuardFn &guard_fn, SingleDisplayConfigState &new_state); + /** * @brief Try to revert the modified settings. * @returns True on success, false otherwise. diff --git a/src/windows/include/displaydevice/windows/settingsutils.h b/src/windows/include/displaydevice/windows/settingsutils.h index 377ef63..24f5fc7 100644 --- a/src/windows/include/displaydevice/windows/settingsutils.h +++ b/src/windows/include/displaydevice/windows/settingsutils.h @@ -72,7 +72,7 @@ namespace display_device::win_utils { stripInitialState(const SingleDisplayConfigState::Initial &initial_state, const EnumeratedDeviceList &devices); /** - * @brief Compute new topology from arbitrary. + * @brief Compute new topology from arbitrary data. * @param device_prep Specify how to to compute the new topology. * @param configuring_primary_devices Specify whether the `device_to_configure` was unspecified (primary device was selected). * @param device_to_configure Main device to be configured. @@ -99,6 +99,24 @@ namespace display_device::win_utils { const std::string &device_id, const SingleDisplayConfigState::Initial &initial_state); + /** + * @brief Compute new display modes from arbitrary data. + * @param resolution Specify resolution that should be used to override the original modes. + * @param refresh_rate Specify refresh rate that should be used to override the original modes. + * @param configuring_primary_devices Specify whether the `device_to_configure` was unspecified (primary device was selected). + * @param device_to_configure Main device to be configured. + * @param additional_devices_to_configure Additional devices that belong to the same group as `device_to_configure`. + * @param original_modes Display modes to be used as a base onto which changes are made. + * @return New display modes that should be set. + */ + DeviceDisplayModeMap + computeNewDisplayModes(const std::optional<Resolution> &resolution, + const std::optional<double> &refresh_rate, + bool configuring_primary_devices, + const std::string &device_to_configure, + const std::set<std::string> &additional_devices_to_configure, + const DeviceDisplayModeMap &original_modes); + /** * @brief Make guard function for the topology. * @param win_dd Interface for interacting with the OS. @@ -129,6 +147,23 @@ namespace display_device::win_utils { DdGuardFn modeGuardFn(WinDisplayDeviceInterface &win_dd, const ActiveTopology &topology); + /** + * @brief Make guard function for the display modes. + * @param win_dd Interface for interacting with the OS. + * @param modes Display modes to restore when the guard is executed. + * @return Function that once called will try to revert display modes to the ones that were provided. + * + * EXAMPLES: + * ```cpp + * WinDisplayDeviceInterface* iface = getIface(...); + * const DeviceDisplayModeMap modes { }; + * + * boost::scope::scope_exit guard { modeGuardFn(*iface, modes) }; + * ``` + */ + DdGuardFn + modeGuardFn(WinDisplayDeviceInterface &win_dd, const DeviceDisplayModeMap &modes); + /** * @brief Make guard function for the primary display. * @param win_dd Interface for interacting with the OS. diff --git a/src/windows/include/displaydevice/windows/types.h b/src/windows/include/displaydevice/windows/types.h index 080f19b..477360e 100644 --- a/src/windows/include/displaydevice/windows/types.h +++ b/src/windows/include/displaydevice/windows/types.h @@ -84,6 +84,12 @@ namespace display_device { unsigned int m_numerator {}; unsigned int m_denominator {}; + /** + * @brief Contruct rational struct from double precission floating point. + */ + static Rational + fromFloatingPoint(double value); + /** * @brief Comparator for strict equality. */ diff --git a/src/windows/include/displaydevice/windows/winapilayer.h b/src/windows/include/displaydevice/windows/winapilayer.h index ae3b9be..2b3e071 100644 --- a/src/windows/include/displaydevice/windows/winapilayer.h +++ b/src/windows/include/displaydevice/windows/winapilayer.h @@ -46,7 +46,7 @@ namespace display_device { setHdrState(const DISPLAYCONFIG_PATH_INFO &path, HdrState state) override; /** For details @see WinApiLayerInterface::getDisplayScale */ - [[nodiscard]] std::optional<float> + [[nodiscard]] std::optional<double> getDisplayScale(const std::string &display_name, const DISPLAYCONFIG_SOURCE_MODE &source_mode) const override; }; } // namespace display_device diff --git a/src/windows/include/displaydevice/windows/winapilayerinterface.h b/src/windows/include/displaydevice/windows/winapilayerinterface.h index ba1c221..94d6aad 100644 --- a/src/windows/include/displaydevice/windows/winapilayerinterface.h +++ b/src/windows/include/displaydevice/windows/winapilayerinterface.h @@ -212,7 +212,7 @@ namespace display_device { * const auto scale = iface->getDisplayScale(iface->getDisplayName(path), source_mode); * ``` */ - [[nodiscard]] virtual std::optional<float> + [[nodiscard]] virtual std::optional<double> getDisplayScale(const std::string &display_name, const DISPLAYCONFIG_SOURCE_MODE &source_mode) const = 0; }; } // namespace display_device diff --git a/src/windows/settingsmanagerapply.cpp b/src/windows/settingsmanagerapply.cpp index 26da342..94ef909 100644 --- a/src/windows/settingsmanagerapply.cpp +++ b/src/windows/settingsmanagerapply.cpp @@ -75,11 +75,17 @@ namespace display_device { return ApplyResult::PrimaryDevicePrepFailed; } + DdGuardFn mode_guard_fn { noopFn }; + boost::scope::scope_exit<DdGuardFn &> mode_guard { mode_guard_fn }; + if (!prepareDisplayModes(config, device_to_configure, additional_devices_to_configure, mode_guard_fn, new_state)) { + // Error already logged + return ApplyResult::DisplayModePrepFailed; + } + // TODO: // // Other device handling goes here that will use device_to_configure and additional_devices_to_configure: // - // - handle display modes // - handle HDR (need to replicate the HDR bug and find the best place for workaround) // @@ -98,6 +104,7 @@ namespace display_device { // Disable all guards before returning topology_prep_guard.set_active(false); primary_guard.set_active(false); + mode_guard.set_active(false); return ApplyResult::Ok; } @@ -241,4 +248,60 @@ namespace display_device { return true; } + + bool + SettingsManager::prepareDisplayModes(const SingleDisplayConfiguration &config, const std::string &device_to_configure, const std::set<std::string> &additional_devices_to_configure, DdGuardFn &guard_fn, SingleDisplayConfigState &new_state) { + const auto &cached_state { m_persistence_state->getState() }; + const auto cached_display_modes { cached_state ? cached_state->m_modified.m_original_modes : DeviceDisplayModeMap {} }; + const bool change_required { config.m_resolution || config.m_refresh_rate }; + const bool might_need_to_restore { !cached_display_modes.empty() }; + + DeviceDisplayModeMap current_display_modes; + if (change_required || might_need_to_restore) { + current_display_modes = m_dd_api->getCurrentDisplayModes(win_utils::flattenTopology(new_state.m_modified.m_topology)); + if (current_display_modes.empty()) { + DD_LOG(error) << "Failed to get current display modes!"; + return false; + } + } + + const auto try_change { [&](const DeviceDisplayModeMap &new_modes, const auto info_preamble, const auto error_log) { + if (current_display_modes != new_modes) { + DD_LOG(info) << info_preamble << toJson(new_modes); + if (!m_dd_api->setDisplayModes(new_modes)) { + DD_LOG(error) << error_log; + return false; + } + + guard_fn = win_utils::modeGuardFn(*m_dd_api, current_display_modes); + } + + return true; + } }; + + if (change_required) { + const bool configuring_primary_devices { config.m_device_id.empty() }; + const auto original_display_modes { cached_display_modes.empty() ? current_display_modes : cached_display_modes }; + const auto new_display_modes { win_utils::computeNewDisplayModes(config.m_resolution, + config.m_refresh_rate, configuring_primary_devices, device_to_configure, additional_devices_to_configure, original_display_modes) }; + + if (!try_change(new_display_modes, "Changing display modes to: ", "Failed to apply new configuration, because new display modes could not be set!")) { + // Error already logged + return false; + } + + // Here we preserve the data from persistence (unless there's none) as in the end that is what we want to go back to. + new_state.m_modified.m_original_modes = original_display_modes; + return true; + } + + if (might_need_to_restore) { + if (!try_change(cached_display_modes, "Changing display modes back to: ", "Failed to restore original display modes!")) { + // Error already logged + return false; + } + } + + return true; + } } // namespace display_device diff --git a/src/windows/settingsutils.cpp b/src/windows/settingsutils.cpp index d6c542a..f881ac2 100644 --- a/src/windows/settingsutils.cpp +++ b/src/windows/settingsutils.cpp @@ -197,15 +197,13 @@ namespace display_device::win_utils { return ActiveTopology { { device_to_configure } }; } - // DevicePrep::EnsureActive || DevicePrep::EnsurePrimary - else { - // The device needs to be active at least. - if (!flattenTopology(initial_topology).contains(device_to_configure)) { - // Create an extended topology as it's probably what makes sense the most... - ActiveTopology new_topology { initial_topology }; - new_topology.push_back({ device_to_configure }); - return new_topology; - } + + // The device needs to be active at least for `DevicePrep::EnsureActive || DevicePrep::EnsurePrimary`. + if (!flattenTopology(initial_topology).contains(device_to_configure)) { + // Create an extended topology as it's probably what makes sense the most... + ActiveTopology new_topology { initial_topology }; + new_topology.push_back({ device_to_configure }); + return new_topology; } } @@ -264,6 +262,40 @@ namespace display_device::win_utils { return std::make_tuple(new_topology, device_to_configure, additional_devices_to_configure); } + DeviceDisplayModeMap + computeNewDisplayModes(const std::optional<Resolution> &resolution, const std::optional<double> &refresh_rate, const bool configuring_primary_devices, const std::string &device_to_configure, const std::set<std::string> &additional_devices_to_configure, const DeviceDisplayModeMap &original_modes) { + DeviceDisplayModeMap new_modes { original_modes }; + + if (resolution) { + // For duplicate devices the resolution must match no matter what, otherwise + // they cannot be duplicated, which breaks Windows' rules. Therefore + // we change resolution for all devices. + const auto devices { joinConfigurableDevices(device_to_configure, additional_devices_to_configure) }; + for (const auto &device_id : devices) { + new_modes[device_id].m_resolution = *resolution; + } + } + + if (refresh_rate) { + if (configuring_primary_devices) { + // No device has been specified, so if they're all are primary devices + // we need to apply the refresh rate change to all duplicates. + const auto devices { joinConfigurableDevices(device_to_configure, additional_devices_to_configure) }; + for (const auto &device_id : devices) { + new_modes[device_id].m_refresh_rate = Rational::fromFloatingPoint(*refresh_rate); + } + } + else { + // Even if we have duplicate devices, their refresh rate may differ + // and since the device was specified, let's apply the refresh + // rate only to the specified device. + new_modes[device_to_configure].m_refresh_rate = Rational::fromFloatingPoint(*refresh_rate); + } + } + + return new_modes; + } + DdGuardFn topologyGuardFn(WinDisplayDeviceInterface &win_dd, const ActiveTopology &topology) { DD_LOG(debug) << "Got topology in topologyGuardFn:\n" @@ -278,7 +310,11 @@ namespace display_device::win_utils { DdGuardFn modeGuardFn(WinDisplayDeviceInterface &win_dd, const ActiveTopology &topology) { - const auto modes = win_dd.getCurrentDisplayModes(flattenTopology(topology)); + return modeGuardFn(win_dd, win_dd.getCurrentDisplayModes(flattenTopology(topology))); + } + + DdGuardFn + modeGuardFn(WinDisplayDeviceInterface &win_dd, const DeviceDisplayModeMap &modes) { DD_LOG(debug) << "Got modes in modeGuardFn:\n" << toJson(modes); return [&win_dd, modes]() { diff --git a/src/windows/types.cpp b/src/windows/types.cpp index d4fd343..61b83c0 100644 --- a/src/windows/types.cpp +++ b/src/windows/types.cpp @@ -1,7 +1,20 @@ // header include #include "displaydevice/windows/types.h" +// system includes +#include <cmath> + namespace display_device { + Rational + Rational::fromFloatingPoint(const double value) { + // It's hard to deal with floating values, so we just multiply it + // to keep 4 decimal places (if any) and let Windows deal with it! + // Genius idea if I'm being honest. + constexpr auto multiplier { static_cast<unsigned int>(std::pow(10, 4)) }; + const double transformed_value { std::round(value * multiplier) }; + return Rational { static_cast<unsigned int>(transformed_value), multiplier }; + } + bool operator==(const Rational &lhs, const Rational &rhs) { return lhs.m_numerator == rhs.m_numerator && lhs.m_denominator == rhs.m_denominator; diff --git a/src/windows/winapilayer.cpp b/src/windows/winapilayer.cpp index 8fef080..3d5b2f7 100644 --- a/src/windows/winapilayer.cpp +++ b/src/windows/winapilayer.cpp @@ -575,7 +575,7 @@ namespace display_device { return true; } - std::optional<float> + std::optional<double> WinApiLayer::getDisplayScale(const std::string &display_name, const DISPLAYCONFIG_SOURCE_MODE &source_mode) const { // Note: implementation based on https://stackoverflow.com/a/74046173 struct EnumData { @@ -616,7 +616,7 @@ namespace display_device { return std::nullopt; } - const auto width { static_cast<float>(*enum_data.m_width) / static_cast<float>(source_mode.width) }; - return static_cast<float>(GetDpiForSystem()) / 96.0f / width; + const auto width { static_cast<double>(*enum_data.m_width) / static_cast<double>(source_mode.width) }; + return static_cast<double>(GetDpiForSystem()) / 96. / width; } } // namespace display_device diff --git a/src/windows/winapiutils.cpp b/src/windows/winapiutils.cpp index d8e639f..b749593 100644 --- a/src/windows/winapiutils.cpp +++ b/src/windows/winapiutils.cpp @@ -483,9 +483,9 @@ namespace display_device::win_utils { bool fuzzyCompareRefreshRates(const Rational &lhs, const Rational &rhs) { if (lhs.m_denominator > 0 && rhs.m_denominator > 0) { - const float lhs_f { static_cast<float>(lhs.m_numerator) / static_cast<float>(lhs.m_denominator) }; - const float rhs_f { static_cast<float>(rhs.m_numerator) / static_cast<float>(rhs.m_denominator) }; - return (std::abs(lhs_f - rhs_f) <= 0.9f); + const double lhs_f { static_cast<double>(lhs.m_numerator) / static_cast<double>(lhs.m_denominator) }; + const double rhs_f { static_cast<double>(rhs.m_numerator) / static_cast<double>(rhs.m_denominator) }; + return (std::abs(lhs_f - rhs_f) <= 0.9); } return false; diff --git a/src/windows/windisplaydevicegeneral.cpp b/src/windows/windisplaydevicegeneral.cpp index d098e23..06c2a97 100644 --- a/src/windows/windisplaydevicegeneral.cpp +++ b/src/windows/windisplaydevicegeneral.cpp @@ -59,12 +59,12 @@ namespace display_device { } const auto display_name { m_w_api->getDisplayName(best_path) }; - const float refresh_rate { best_path.targetInfo.refreshRate.Denominator > 0 ? - static_cast<float>(best_path.targetInfo.refreshRate.Numerator) / static_cast<float>(best_path.targetInfo.refreshRate.Denominator) : - 0.f }; + const double refresh_rate { best_path.targetInfo.refreshRate.Denominator > 0 ? + static_cast<double>(best_path.targetInfo.refreshRate.Numerator) / static_cast<double>(best_path.targetInfo.refreshRate.Denominator) : + 0. }; const EnumeratedDevice::Info info { { source_mode->width, source_mode->height }, - m_w_api->getDisplayScale(display_name, *source_mode).value_or(0.f), + m_w_api->getDisplayScale(display_name, *source_mode).value_or(0.), refresh_rate, win_utils::isPrimary(*source_mode), { source_mode->position.x, source_mode->position.y }, diff --git a/tests/unit/general/test_comparison.cpp b/tests/unit/general/test_comparison.cpp index 68cc708..7a8a031 100644 --- a/tests/unit/general/test_comparison.cpp +++ b/tests/unit/general/test_comparison.cpp @@ -20,20 +20,20 @@ TEST_S(Resolution) { } TEST_S(EnumeratedDevice, Info) { - EXPECT_EQ(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1.f, 1.f, true, { 1, 1 }, std::nullopt }), - display_device::EnumeratedDevice::Info({ { 1, 1 }, 1.f, 1.f, true, { 1, 1 }, std::nullopt })); - EXPECT_NE(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1.f, 1.f, true, { 1, 1 }, std::nullopt }), - display_device::EnumeratedDevice::Info({ { 1, 0 }, 1.f, 1.f, true, { 1, 1 }, std::nullopt })); - EXPECT_NE(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1.f, 1.f, true, { 1, 1 }, std::nullopt }), - display_device::EnumeratedDevice::Info({ { 1, 1 }, 1.1f, 1.f, true, { 1, 1 }, std::nullopt })); - EXPECT_NE(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1.f, 1.f, true, { 1, 1 }, std::nullopt }), - display_device::EnumeratedDevice::Info({ { 1, 1 }, 1.f, 1.1f, true, { 1, 1 }, std::nullopt })); - EXPECT_NE(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1.f, 1.f, true, { 1, 1 }, std::nullopt }), - display_device::EnumeratedDevice::Info({ { 1, 1 }, 1.f, 1.f, false, { 1, 1 }, std::nullopt })); - EXPECT_NE(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1.f, 1.f, true, { 1, 1 }, std::nullopt }), - display_device::EnumeratedDevice::Info({ { 1, 1 }, 1.f, 1.f, true, { 1, 0 }, std::nullopt })); - EXPECT_NE(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1.f, 1.f, true, { 1, 1 }, std::nullopt }), - display_device::EnumeratedDevice::Info({ { 1, 1 }, 1.f, 1.f, true, { 1, 1 }, display_device::HdrState::Disabled })); + EXPECT_EQ(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., 1., true, { 1, 1 }, std::nullopt }), + display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., 1., true, { 1, 1 }, std::nullopt })); + EXPECT_NE(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., 1., true, { 1, 1 }, std::nullopt }), + display_device::EnumeratedDevice::Info({ { 1, 0 }, 1., 1., true, { 1, 1 }, std::nullopt })); + EXPECT_NE(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., 1., true, { 1, 1 }, std::nullopt }), + display_device::EnumeratedDevice::Info({ { 1, 1 }, 1.1, 1., true, { 1, 1 }, std::nullopt })); + EXPECT_NE(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., 1., true, { 1, 1 }, std::nullopt }), + display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., 1.1, true, { 1, 1 }, std::nullopt })); + EXPECT_NE(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., 1., true, { 1, 1 }, std::nullopt }), + display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., 1., false, { 1, 1 }, std::nullopt })); + EXPECT_NE(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., 1., true, { 1, 1 }, std::nullopt }), + display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., 1., true, { 1, 0 }, std::nullopt })); + EXPECT_NE(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., 1., true, { 1, 1 }, std::nullopt }), + display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., 1., true, { 1, 1 }, display_device::HdrState::Disabled })); } TEST_S(EnumeratedDevice) { @@ -51,16 +51,16 @@ TEST_S(EnumeratedDevice) { TEST_S(SingleDisplayConfiguration) { using DevicePrep = display_device::SingleDisplayConfiguration::DevicePreparation; - EXPECT_EQ(display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1.f, display_device::HdrState::Disabled }), - display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1.f, display_device::HdrState::Disabled })); - EXPECT_NE(display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1.f, display_device::HdrState::Disabled }), - display_device::SingleDisplayConfiguration({ "0", DevicePrep::EnsureActive, { { 1, 1 } }, 1.f, display_device::HdrState::Disabled })); - EXPECT_NE(display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1.f, display_device::HdrState::Disabled }), - display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsurePrimary, { { 1, 1 } }, 1.f, display_device::HdrState::Disabled })); - EXPECT_NE(display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1.f, display_device::HdrState::Disabled }), - display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 0 } }, 1.f, display_device::HdrState::Disabled })); - EXPECT_NE(display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1.f, display_device::HdrState::Disabled }), - display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1.1f, display_device::HdrState::Disabled })); - EXPECT_NE(display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1.f, display_device::HdrState::Disabled }), - display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1.f, display_device::HdrState::Enabled })); + EXPECT_EQ(display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1., display_device::HdrState::Disabled }), + display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1., display_device::HdrState::Disabled })); + EXPECT_NE(display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1., display_device::HdrState::Disabled }), + display_device::SingleDisplayConfiguration({ "0", DevicePrep::EnsureActive, { { 1, 1 } }, 1., display_device::HdrState::Disabled })); + EXPECT_NE(display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1., display_device::HdrState::Disabled }), + display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsurePrimary, { { 1, 1 } }, 1., display_device::HdrState::Disabled })); + EXPECT_NE(display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1., display_device::HdrState::Disabled }), + display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 0 } }, 1., display_device::HdrState::Disabled })); + EXPECT_NE(display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1., display_device::HdrState::Disabled }), + display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1.1, display_device::HdrState::Disabled })); + EXPECT_NE(display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1., display_device::HdrState::Disabled }), + display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1., display_device::HdrState::Enabled })); } diff --git a/tests/unit/general/test_jsonconverter.cpp b/tests/unit/general/test_jsonconverter.cpp index 24662b5..355ce50 100644 --- a/tests/unit/general/test_jsonconverter.cpp +++ b/tests/unit/general/test_jsonconverter.cpp @@ -13,8 +13,8 @@ TEST_F_S(EnumeratedDeviceList) { "FU_NAME_3", display_device::EnumeratedDevice::Info { { 1920, 1080 }, - 1.75f, - 119.9554f, + 1.75, + 119.9554, false, { 1, 2 }, display_device::HdrState::Enabled } @@ -25,8 +25,8 @@ TEST_F_S(EnumeratedDeviceList) { "FU_NAME_2", display_device::EnumeratedDevice::Info { { 1920, 1080 }, - 1.75f, - 119.9554f, + 1.75, + 119.9554, true, { 0, 0 }, display_device::HdrState::Disabled } @@ -35,14 +35,14 @@ TEST_F_S(EnumeratedDeviceList) { executeTestCase(display_device::EnumeratedDeviceList {}, R"([])"); executeTestCase(display_device::EnumeratedDeviceList { item_1, item_2, item_3 }, - R"([{"device_id":"ID_1","display_name":"NAME_2","friendly_name":"FU_NAME_3","info":{"hdr_state":"Enabled","origin_point":{"x":1,"y":2},"primary":false,"refresh_rate":119.95539855957031,"resolution":{"height":1080,"width":1920},"resolution_scale":1.75}},)" - R"({"device_id":"ID_2","display_name":"NAME_2","friendly_name":"FU_NAME_2","info":{"hdr_state":"Disabled","origin_point":{"x":0,"y":0},"primary":true,"refresh_rate":119.95539855957031,"resolution":{"height":1080,"width":1920},"resolution_scale":1.75}},)" + R"([{"device_id":"ID_1","display_name":"NAME_2","friendly_name":"FU_NAME_3","info":{"hdr_state":"Enabled","origin_point":{"x":1,"y":2},"primary":false,"refresh_rate":119.9554,"resolution":{"height":1080,"width":1920},"resolution_scale":1.75}},)" + R"({"device_id":"ID_2","display_name":"NAME_2","friendly_name":"FU_NAME_2","info":{"hdr_state":"Disabled","origin_point":{"x":0,"y":0},"primary":true,"refresh_rate":119.9554,"resolution":{"height":1080,"width":1920},"resolution_scale":1.75}},)" R"({"device_id":"","display_name":"","friendly_name":"","info":null}])"); } TEST_F_S(SingleDisplayConfiguration) { - display_device::SingleDisplayConfiguration config_1 { "ID_1", display_device::SingleDisplayConfiguration::DevicePreparation::VerifyOnly, { { 156, 123 } }, 85.f, display_device::HdrState::Enabled }; - display_device::SingleDisplayConfiguration config_2 { "ID_2", display_device::SingleDisplayConfiguration::DevicePreparation::EnsureActive, std::nullopt, 85.f, display_device::HdrState::Disabled }; + display_device::SingleDisplayConfiguration config_1 { "ID_1", display_device::SingleDisplayConfiguration::DevicePreparation::VerifyOnly, { { 156, 123 } }, 85., display_device::HdrState::Enabled }; + display_device::SingleDisplayConfiguration config_2 { "ID_2", display_device::SingleDisplayConfiguration::DevicePreparation::EnsureActive, std::nullopt, 85., display_device::HdrState::Disabled }; display_device::SingleDisplayConfiguration config_3 { "ID_3", display_device::SingleDisplayConfiguration::DevicePreparation::EnsureOnlyDisplay, { { 156, 123 } }, std::nullopt, std::nullopt }; display_device::SingleDisplayConfiguration config_4 { "ID_4", display_device::SingleDisplayConfiguration::DevicePreparation::EnsurePrimary, std::nullopt, std::nullopt, std::nullopt }; diff --git a/tests/unit/windows/test_settingsmanagerapply.cpp b/tests/unit/windows/test_settingsmanagerapply.cpp index 46771fd..8123c41 100644 --- a/tests/unit/windows/test_settingsmanagerapply.cpp +++ b/tests/unit/windows/test_settingsmanagerapply.cpp @@ -1,9 +1,9 @@ // local includes #include "displaydevice/windows/settingsmanager.h" +#include "displaydevice/windows/settingsutils.h" #include "fixtures/fixtures.h" #include "fixtures/mockaudiocontext.h" #include "fixtures/mocksettingspersistence.h" -#include "utils/comparison.h" #include "utils/helpers.h" #include "utils/mockwindisplaydevice.h" @@ -29,6 +29,11 @@ namespace { { .m_device_id = "DeviceId3", .m_info = display_device::EnumeratedDevice::Info { .m_primary = false } }, { .m_device_id = "DeviceId4" } }; + const display_device::DeviceDisplayModeMap DEFAULT_CURRENT_MODES { + { "DeviceId1", { { 1080, 720 }, { 120, 1 } } }, + { "DeviceId2", { { 1920, 1080 }, { 60, 1 } } }, + { "DeviceId3", { { 2560, 1440 }, { 30, 1 } } } + }; const display_device::SingleDisplayConfigState DEFAULT_PERSISTENCE_INPUT_BASE { { DEFAULT_CURRENT_TOPOLOGY, { "DeviceId1", "DeviceId2" } } }; // Test fixture(s) for this file @@ -164,6 +169,30 @@ namespace { .RetiresOnSaturation(); } + void + expectedSetDisplayModesCall(InSequence &sequence /* To ensure that sequence is created outside this scope */, const display_device::DeviceDisplayModeMap &modes, const bool success = true) { + EXPECT_CALL(*m_dd_api, setDisplayModes(modes)) + .Times(1) + .WillOnce(Return(success)) + .RetiresOnSaturation(); + } + + void + expectedGetCurrentDisplayModesCall(InSequence &sequence /* To ensure that sequence is created outside this scope */, const std::set<std::string> &devices, const display_device::DeviceDisplayModeMap &modes) { + EXPECT_CALL(*m_dd_api, getCurrentDisplayModes(devices)) + .Times(1) + .WillOnce(Return(modes)) + .RetiresOnSaturation(); + } + + void + expectedSetDisplayModesGuardCall(InSequence &sequence /* To ensure that sequence is created outside this scope */, const display_device::DeviceDisplayModeMap &modes) { + EXPECT_CALL(*m_dd_api, setDisplayModes(modes)) + .Times(1) + .WillOnce(Return(true)) + .RetiresOnSaturation(); + } + std::shared_ptr<StrictMock<display_device::MockWinDisplayDevice>> m_dd_api { std::make_shared<StrictMock<display_device::MockWinDisplayDevice>>() }; std::shared_ptr<StrictMock<display_device::MockSettingsPersistence>> m_settings_persistence_api { std::make_shared<StrictMock<display_device::MockSettingsPersistence>>() }; std::shared_ptr<StrictMock<display_device::MockAudioContext>> m_audio_context_api { std::make_shared<StrictMock<display_device::MockAudioContext>>() }; @@ -593,7 +622,7 @@ TEST_F_S_MOCKED(PreparePrimaryDevice, PrimaryDeviceRestored) { EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId3", .m_device_prep = DevicePrep::EnsureActive }), display_device::SettingsManager::ApplyResult::Ok); } -TEST_F_S_MOCKED(PreparePrimaryDevice, PrimaryDeviceRestored, GuardInvoked) { +TEST_F_S_MOCKED(PreparePrimaryDevice, PrimaryDeviceRestored, PersistenceFailed) { using DevicePrep = display_device::SingleDisplayConfiguration::DevicePreparation; auto intial_state { ut_consts::SDCS_NO_MODIFICATIONS }; intial_state->m_modified.m_original_primary_device = "DeviceId1"; @@ -619,7 +648,7 @@ TEST_F_S_MOCKED(PreparePrimaryDevice, PrimaryDeviceRestored, GuardInvoked) { EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId3", .m_device_prep = DevicePrep::EnsureActive }), display_device::SettingsManager::ApplyResult::PersistenceSaveFailed); } -TEST_F_S_MOCKED(PreparePrimaryDevice, PrimaryDeviceRestoreSkipped) { +TEST_F_S_MOCKED(PreparePrimaryDevice, PrimaryDeviceRestoreSkipped, PersistenceFailed) { using DevicePrep = display_device::SingleDisplayConfiguration::DevicePreparation; auto intial_state { ut_consts::SDCS_NO_MODIFICATIONS }; intial_state->m_modified.m_original_primary_device = "DeviceId1"; @@ -642,6 +671,282 @@ TEST_F_S_MOCKED(PreparePrimaryDevice, PrimaryDeviceRestoreSkipped) { EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId3", .m_device_prep = DevicePrep::EnsureActive }), display_device::SettingsManager::ApplyResult::PersistenceSaveFailed); } +TEST_F_S_MOCKED(PrepareDisplayModes, FailedToGetDisplayModes) { + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentDisplayModesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), {}); + + expectedTopologyGuardTopologyCall(sequence); + expectedTopologyGuardNewlyCapturedContextCall(sequence, false); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1", .m_resolution = { { 1920, 1080 } } }), display_device::SettingsManager::ApplyResult::DisplayModePrepFailed); +} + +TEST_F_S_MOCKED(PrepareDisplayModes, FailedToSetDisplayModes) { + auto new_modes { DEFAULT_CURRENT_MODES }; + new_modes["DeviceId1"].m_resolution = { 1920, 1080 }; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentDisplayModesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_MODES); + expectedSetDisplayModesCall(sequence, new_modes, false); + + expectedTopologyGuardTopologyCall(sequence); + expectedTopologyGuardNewlyCapturedContextCall(sequence, false); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1", .m_resolution = { { 1920, 1080 } } }), display_device::SettingsManager::ApplyResult::DisplayModePrepFailed); +} + +TEST_F_S_MOCKED(PrepareDisplayModes, DisplayModesSet, ResolutionOnly) { + auto new_modes { DEFAULT_CURRENT_MODES }; + new_modes["DeviceId1"].m_resolution = { 1920, 1080 }; + + auto persistence_input { DEFAULT_PERSISTENCE_INPUT_BASE }; + persistence_input.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + persistence_input.m_modified.m_original_modes = DEFAULT_CURRENT_MODES; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentDisplayModesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_MODES); + expectedSetDisplayModesCall(sequence, new_modes); + expectedPersistenceCall(sequence, persistence_input); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1", .m_resolution = { { 1920, 1080 } } }), display_device::SettingsManager::ApplyResult::Ok); +} + +TEST_F_S_MOCKED(PrepareDisplayModes, DisplayModesSet, RefreshRateOnly) { + auto new_modes { DEFAULT_CURRENT_MODES }; + new_modes["DeviceId1"].m_refresh_rate = { 308500, 10000 }; + + auto persistence_input { DEFAULT_PERSISTENCE_INPUT_BASE }; + persistence_input.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + persistence_input.m_modified.m_original_modes = DEFAULT_CURRENT_MODES; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentDisplayModesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_MODES); + expectedSetDisplayModesCall(sequence, new_modes); + expectedPersistenceCall(sequence, persistence_input); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1", .m_refresh_rate = { { 30.85 } } }), display_device::SettingsManager::ApplyResult::Ok); +} + +TEST_F_S_MOCKED(PrepareDisplayModes, DisplayModesSet, ResolutionAndRefreshRate) { + auto new_modes { DEFAULT_CURRENT_MODES }; + new_modes["DeviceId1"].m_resolution = { 1920, 1080 }; + new_modes["DeviceId1"].m_refresh_rate = { 308500, 10000 }; + + auto persistence_input { DEFAULT_PERSISTENCE_INPUT_BASE }; + persistence_input.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + persistence_input.m_modified.m_original_modes = DEFAULT_CURRENT_MODES; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentDisplayModesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_MODES); + expectedSetDisplayModesCall(sequence, new_modes); + expectedPersistenceCall(sequence, persistence_input); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1", .m_resolution = { { 1920, 1080 } }, .m_refresh_rate = { { 30.85 } } }), display_device::SettingsManager::ApplyResult::Ok); +} + +TEST_F_S_MOCKED(PrepareDisplayModes, DisplayModesSet, ResolutionAndRefreshRate, PrimaryDeviceSpecified) { + auto new_modes { DEFAULT_CURRENT_MODES }; + new_modes["DeviceId1"].m_resolution = { 1920, 1080 }; + new_modes["DeviceId1"].m_refresh_rate = { 308500, 10000 }; + new_modes["DeviceId2"].m_resolution = { 1920, 1080 }; + new_modes["DeviceId2"].m_refresh_rate = { 308500, 10000 }; + + auto persistence_input { DEFAULT_PERSISTENCE_INPUT_BASE }; + persistence_input.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + persistence_input.m_modified.m_original_modes = DEFAULT_CURRENT_MODES; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentDisplayModesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_MODES); + expectedSetDisplayModesCall(sequence, new_modes); + expectedPersistenceCall(sequence, persistence_input); + + EXPECT_EQ(getImpl().applySettings({ .m_resolution = { { 1920, 1080 } }, .m_refresh_rate = { { 30.85 } } }), display_device::SettingsManager::ApplyResult::Ok); +} + +TEST_F_S_MOCKED(PrepareDisplayModes, DisplayModesSet, CachedModesReused) { + auto new_modes { DEFAULT_CURRENT_MODES }; + new_modes["DeviceId1"].m_resolution = { 1920, 1080 }; + + auto initial_state { DEFAULT_PERSISTENCE_INPUT_BASE }; + initial_state.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + initial_state.m_modified.m_original_modes = DEFAULT_CURRENT_MODES; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence, DEFAULT_CURRENT_TOPOLOGY, initial_state); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentDisplayModesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_MODES); + expectedSetDisplayModesCall(sequence, new_modes); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1", .m_resolution = { { 1920, 1080 } } }), display_device::SettingsManager::ApplyResult::Ok); +} + +TEST_F_S_MOCKED(PrepareDisplayModes, DisplayModesSet, GuardInvoked) { + auto new_modes { DEFAULT_CURRENT_MODES }; + new_modes["DeviceId1"].m_resolution = { 1920, 1080 }; + + auto persistence_input { DEFAULT_PERSISTENCE_INPUT_BASE }; + persistence_input.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + persistence_input.m_modified.m_original_modes = DEFAULT_CURRENT_MODES; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentDisplayModesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_MODES); + expectedSetDisplayModesCall(sequence, new_modes); + expectedPersistenceCall(sequence, persistence_input, false); + + expectedSetDisplayModesGuardCall(sequence, DEFAULT_CURRENT_MODES); + expectedTopologyGuardTopologyCall(sequence); + expectedTopologyGuardNewlyCapturedContextCall(sequence, false); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1", .m_resolution = { { 1920, 1080 } } }), display_device::SettingsManager::ApplyResult::PersistenceSaveFailed); +} + +TEST_F_S_MOCKED(PrepareDisplayModes, DisplayModesSetSkipped) { + auto persistence_input { DEFAULT_PERSISTENCE_INPUT_BASE }; + persistence_input.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + persistence_input.m_modified.m_original_modes = DEFAULT_CURRENT_MODES; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentDisplayModesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_MODES); + expectedPersistenceCall(sequence, persistence_input); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId3", .m_resolution = { { 2560, 1440 } } }), display_device::SettingsManager::ApplyResult::Ok); +} + +TEST_F_S_MOCKED(PrepareDisplayModes, FailedToRestoreDisplayModes) { + auto initial_state { DEFAULT_PERSISTENCE_INPUT_BASE }; + initial_state.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + initial_state.m_modified.m_original_modes = DEFAULT_CURRENT_MODES; + initial_state.m_modified.m_original_modes["DeviceId1"].m_resolution = { 1920, 1080 }; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence, DEFAULT_CURRENT_TOPOLOGY, initial_state); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentDisplayModesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_MODES); + expectedSetDisplayModesCall(sequence, initial_state.m_modified.m_original_modes, false); + + expectedTopologyGuardTopologyCall(sequence); + expectedTopologyGuardNewlyCapturedContextCall(sequence, false); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1" }), display_device::SettingsManager::ApplyResult::DisplayModePrepFailed); +} + +TEST_F_S_MOCKED(PrepareDisplayModes, DisplayModesRestored) { + auto initial_state { DEFAULT_PERSISTENCE_INPUT_BASE }; + initial_state.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + initial_state.m_modified.m_original_modes = DEFAULT_CURRENT_MODES; + initial_state.m_modified.m_original_modes["DeviceId1"].m_resolution = { 1920, 1080 }; + + auto persistence_input { DEFAULT_PERSISTENCE_INPUT_BASE }; + persistence_input.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence, DEFAULT_CURRENT_TOPOLOGY, initial_state); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentDisplayModesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_MODES); + expectedSetDisplayModesCall(sequence, initial_state.m_modified.m_original_modes); + expectedPersistenceCall(sequence, persistence_input); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1" }), display_device::SettingsManager::ApplyResult::Ok); +} + +TEST_F_S_MOCKED(PrepareDisplayModes, DisplayModesRestored, PersistenceFailed) { + auto initial_state { DEFAULT_PERSISTENCE_INPUT_BASE }; + initial_state.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + initial_state.m_modified.m_original_modes = DEFAULT_CURRENT_MODES; + initial_state.m_modified.m_original_modes["DeviceId1"].m_resolution = { 1920, 1080 }; + + auto persistence_input { DEFAULT_PERSISTENCE_INPUT_BASE }; + persistence_input.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence, DEFAULT_CURRENT_TOPOLOGY, initial_state); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentDisplayModesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_MODES); + expectedSetDisplayModesCall(sequence, initial_state.m_modified.m_original_modes); + expectedPersistenceCall(sequence, persistence_input, false); + + expectedSetDisplayModesGuardCall(sequence, DEFAULT_CURRENT_MODES); + expectedTopologyGuardTopologyCall(sequence); + expectedTopologyGuardNewlyCapturedContextCall(sequence, false); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1" }), display_device::SettingsManager::ApplyResult::PersistenceSaveFailed); +} + +TEST_F_S_MOCKED(PrepareDisplayModes, DisplayModesRestoreSkipped, PersistenceFailed) { + auto initial_state { DEFAULT_PERSISTENCE_INPUT_BASE }; + initial_state.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + initial_state.m_modified.m_original_modes = DEFAULT_CURRENT_MODES; + + auto persistence_input { DEFAULT_PERSISTENCE_INPUT_BASE }; + persistence_input.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence, DEFAULT_CURRENT_TOPOLOGY, initial_state); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentDisplayModesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_MODES); + expectedPersistenceCall(sequence, persistence_input, false); + + expectedTopologyGuardTopologyCall(sequence); + expectedTopologyGuardNewlyCapturedContextCall(sequence, false); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1" }), display_device::SettingsManager::ApplyResult::PersistenceSaveFailed); +} + TEST_F_S_MOCKED(AudioContextDelayedRelease) { using DevicePrep = display_device::SingleDisplayConfiguration::DevicePreparation; auto persistence_input { *ut_consts::SDCS_NO_MODIFICATIONS }; diff --git a/tests/unit/windows/test_settingsutils.cpp b/tests/unit/windows/test_settingsutils.cpp index eaf609c..dd868d0 100644 --- a/tests/unit/windows/test_settingsutils.cpp +++ b/tests/unit/windows/test_settingsutils.cpp @@ -21,6 +21,11 @@ namespace { // Additional convenience global const(s) const display_device::ActiveTopology DEFAULT_INITIAL_TOPOLOGY { { "DeviceId1", "DeviceId2" }, { "DeviceId3" } }; + const display_device::DeviceDisplayModeMap DEFAULT_CURRENT_MODES { + { "DeviceId1", { { 1080, 720 }, { 120, 1 } } }, + { "DeviceId2", { { 1920, 1080 }, { 60, 1 } } }, + { "DeviceId3", { { 2560, 1440 }, { 30, 1 } } } + }; } // namespace TEST_F_S_MOCKED(FlattenTopology) { @@ -80,6 +85,22 @@ TEST_F_S_MOCKED(ComputeNewTopology, EnsurePrimary) { (display_device::ActiveTopology { { "DeviceId3" }, { "DeviceId4" } })); } +TEST_F_S_MOCKED(ComputeNewDisplayModes, PrimaryDevices) { + auto expected_modes { DEFAULT_CURRENT_MODES }; + expected_modes["DeviceId1"] = { { 1920, 1080 }, { 1200000, 10000 } }; + expected_modes["DeviceId2"] = { { 1920, 1080 }, { 1200000, 10000 } }; + + EXPECT_EQ(display_device::win_utils::computeNewDisplayModes({ { 1920, 1080 } }, { 120. }, true, "DeviceId1", { "DeviceId2" }, DEFAULT_CURRENT_MODES), expected_modes); +} + +TEST_F_S_MOCKED(ComputeNewDisplayModes, NonPrimaryDevices) { + auto expected_modes { DEFAULT_CURRENT_MODES }; + expected_modes["DeviceId1"] = { { 1920, 1080 }, { 1200000, 10000 } }; + expected_modes["DeviceId2"] = { { 1920, 1080 }, expected_modes["DeviceId2"].m_refresh_rate }; + + EXPECT_EQ(display_device::win_utils::computeNewDisplayModes({ { 1920, 1080 } }, { 120. }, false, "DeviceId1", { "DeviceId2" }, DEFAULT_CURRENT_MODES), expected_modes); +} + TEST_F_S_MOCKED(StripInitialState, NoStripIsPerformed) { const display_device::SingleDisplayConfigState::Initial initial_state { DEFAULT_INITIAL_TOPOLOGY, { "DeviceId1", "DeviceId2" } }; const display_device::EnumeratedDeviceList devices { diff --git a/tests/unit/windows/test_windisplaydevicegeneral.cpp b/tests/unit/windows/test_windisplaydevicegeneral.cpp index 79bf052..54278d3 100644 --- a/tests/unit/windows/test_windisplaydevicegeneral.cpp +++ b/tests/unit/windows/test_windisplaydevicegeneral.cpp @@ -160,7 +160,7 @@ TEST_F_S_MOCKED(EnumAvailableDevices) { .RetiresOnSaturation(); EXPECT_CALL(*m_layer, getDisplayScale(_, _)) .Times(1) - .WillOnce(Return(1.75f)) + .WillOnce(Return(1.75)) .RetiresOnSaturation(); EXPECT_CALL(*m_layer, getHdrState(_)) .Times(1) @@ -178,8 +178,8 @@ TEST_F_S_MOCKED(EnumAvailableDevices) { "FriendlyName1", display_device::EnumeratedDevice::Info { { 1920, 1080 }, - 0.f, - 0.f, + 0., + 0., true, { 0, 0 }, std::nullopt } }, @@ -188,8 +188,8 @@ TEST_F_S_MOCKED(EnumAvailableDevices) { "FriendlyName2", display_device::EnumeratedDevice::Info { { 1920, 2160 }, - 1.75f, - 119.995f, + 1.75, + 119.995, false, { 1921, 0 }, display_device::HdrState::Enabled } }, diff --git a/tests/unit/windows/utils/mockwinapilayer.h b/tests/unit/windows/utils/mockwinapilayer.h index 9a15035..b28f7a3 100644 --- a/tests/unit/windows/utils/mockwinapilayer.h +++ b/tests/unit/windows/utils/mockwinapilayer.h @@ -18,7 +18,7 @@ namespace display_device { MOCK_METHOD(LONG, setDisplayConfig, (std::vector<DISPLAYCONFIG_PATH_INFO>, std::vector<DISPLAYCONFIG_MODE_INFO>, UINT32), (override)); MOCK_METHOD(std::optional<HdrState>, getHdrState, (const DISPLAYCONFIG_PATH_INFO &), (const, override)); MOCK_METHOD(bool, setHdrState, (const DISPLAYCONFIG_PATH_INFO &, HdrState), (override)); - MOCK_METHOD(std::optional<float>, getDisplayScale, (const std::string &, const DISPLAYCONFIG_SOURCE_MODE &), (const, override)); + MOCK_METHOD(std::optional<double>, getDisplayScale, (const std::string &, const DISPLAYCONFIG_SOURCE_MODE &), (const, override)); }; } // namespace display_device