From a4f35ec738b33eb4d247ee23c5233b2bbfb69731 Mon Sep 17 00:00:00 2001 From: FrogTheFrog Date: Thu, 6 Jun 2024 22:13:17 +0300 Subject: [PATCH] feat: add methods for handling hdr state --- src/common/include/displaydevice/types.h | 8 + .../include/displaydevice/windows/types.h | 4 + .../displaydevice/windows/winapilayer.h | 8 + .../windows/winapilayerinterface.h | 31 ++ .../displaydevice/windows/windisplaydevice.h | 8 + .../windows/windisplaydeviceinterface.h | 35 ++ src/windows/winapilayer.cpp | 37 +- src/windows/windisplaydevicehdr.cpp | 127 ++++++ tests/unit/windows/test_winapilayer.cpp | 8 + .../unit/windows/test_windisplaydevicehdr.cpp | 362 ++++++++++++++++++ tests/unit/windows/utils/guards.h | 7 + tests/unit/windows/utils/mockwinapilayer.h | 2 + 12 files changed, 636 insertions(+), 1 deletion(-) create mode 100644 src/windows/windisplaydevicehdr.cpp create mode 100644 tests/unit/windows/test_windisplaydevicehdr.cpp diff --git a/src/common/include/displaydevice/types.h b/src/common/include/displaydevice/types.h index c31816d..f7bdd86 100644 --- a/src/common/include/displaydevice/types.h +++ b/src/common/include/displaydevice/types.h @@ -1,6 +1,14 @@ #pragma once namespace display_device { + /** + * @brief The device's HDR state in the operating system. + */ + enum class HdrState { + Disabled, + Enabled + }; + /** * @brief Display's resolution. */ diff --git a/src/windows/include/displaydevice/windows/types.h b/src/windows/include/displaydevice/windows/types.h index 2d4da8e..6bb376e 100644 --- a/src/windows/include/displaydevice/windows/types.h +++ b/src/windows/include/displaydevice/windows/types.h @@ -91,4 +91,8 @@ namespace display_device { */ using DeviceDisplayModeMap = std::map; + /** + * @brief Ordered map of [DEVICE_ID -> std::optional]. + */ + using HdrStateMap = std::map>; } // namespace display_device diff --git a/src/windows/include/displaydevice/windows/winapilayer.h b/src/windows/include/displaydevice/windows/winapilayer.h index 7b67648..58e9b10 100644 --- a/src/windows/include/displaydevice/windows/winapilayer.h +++ b/src/windows/include/displaydevice/windows/winapilayer.h @@ -36,5 +36,13 @@ namespace display_device { /** For details @see WinApiLayerInterface::setDisplayConfig */ [[nodiscard]] LONG setDisplayConfig(std::vector paths, std::vector modes, UINT32 flags) override; + + /** For details @see WinApiLayerInterface::getHdrState */ + [[nodiscard]] std::optional + getHdrState(const DISPLAYCONFIG_PATH_INFO &path) const override; + + /** For details @see WinApiLayerInterface::setHdrState */ + [[nodiscard]] bool + setHdrState(const DISPLAYCONFIG_PATH_INFO &path, HdrState state) override; }; } // namespace display_device diff --git a/src/windows/include/displaydevice/windows/winapilayerinterface.h b/src/windows/include/displaydevice/windows/winapilayerinterface.h index 551757d..a40fe0f 100644 --- a/src/windows/include/displaydevice/windows/winapilayerinterface.h +++ b/src/windows/include/displaydevice/windows/winapilayerinterface.h @@ -166,5 +166,36 @@ namespace display_device { */ [[nodiscard]] virtual LONG setDisplayConfig(std::vector paths, std::vector modes, UINT32 flags) = 0; + + /** + * @brief Get the HDR state the path. + * @param path Path to get HDR state for. + * @returns std::nullopt if the state could not be retrieved, or other enum values describing the state otherwise. + * + * EXAMPLES: + * ```cpp + * DISPLAYCONFIG_PATH_INFO path; + * const WinApiLayerInterface* iface = getIface(...); + * const auto hdr_state = iface->getHdrState(path); + * ``` + */ + [[nodiscard]] virtual std::optional + getHdrState(const DISPLAYCONFIG_PATH_INFO &path) const = 0; + + /** + * @brief Set the HDR state for the path. + * @param path Path to set HDR state for. + * @param state Specify new HDR state. + * @returns True if the device is in the new state, false otherwise. + * + * EXAMPLES: + * ```cpp + * DISPLAYCONFIG_PATH_INFO path; + * const WinApiLayerInterface* iface = getIface(...); + * const bool success = iface->setHdrState(path, HdrState::Enabled); + * ``` + */ + [[nodiscard]] virtual bool + setHdrState(const DISPLAYCONFIG_PATH_INFO &path, HdrState state) = 0; }; } // namespace display_device diff --git a/src/windows/include/displaydevice/windows/windisplaydevice.h b/src/windows/include/displaydevice/windows/windisplaydevice.h index d9a9039..e90fd04 100644 --- a/src/windows/include/displaydevice/windows/windisplaydevice.h +++ b/src/windows/include/displaydevice/windows/windisplaydevice.h @@ -51,6 +51,14 @@ namespace display_device { [[nodiscard]] bool setAsPrimary(const std::string &device_id) override; + /** For details @see WinDisplayDeviceInterface::getCurrentHdrStates */ + [[nodiscard]] HdrStateMap + getCurrentHdrStates(const std::set &device_ids) const override; + + /** For details @see WinDisplayDeviceInterface::setHdrStates */ + [[nodiscard]] bool + setHdrStates(const HdrStateMap &states) override; + private: std::shared_ptr m_w_api; }; diff --git a/src/windows/include/displaydevice/windows/windisplaydeviceinterface.h b/src/windows/include/displaydevice/windows/windisplaydeviceinterface.h index 6fff4f7..3feaf26 100644 --- a/src/windows/include/displaydevice/windows/windisplaydeviceinterface.h +++ b/src/windows/include/displaydevice/windows/windisplaydeviceinterface.h @@ -150,5 +150,40 @@ namespace display_device { */ [[nodiscard]] virtual bool setAsPrimary(const std::string &device_id) = 0; + + /** + * @brief Get HDR state for the devices. + * @param device_ids A list of devices to get the HDR states for. + * @returns A map of HDR states per a device or an empty map if an error has occurred. + * @note On Windows the state cannot be retrieved until the device is active even if it supports it. + * + * EXAMPLES: + * ```cpp + * const WinDisplayDeviceInterface* iface = getIface(...); + * const std::unordered_set device_ids { "DEVICE_ID_1", "DEVICE_ID_2" }; + * const auto current_hdr_states = iface->getCurrentHdrStates(device_ids); + * ``` + */ + [[nodiscard]] virtual HdrStateMap + getCurrentHdrStates(const std::set &device_ids) const = 0; + + /** + * @brief Set HDR states for the devices. + * @param modes A map of HDR states to set. + * @returns True if HDR states were set, false otherwise. + * @note If `unknown` states are provided, they will be silently ignored + * and current state will not be changed. + * + * EXAMPLES: + * ```cpp + * const WinDisplayDeviceInterface* iface = getIface(...); + * const std::string display_a { "MY_ID_1" }; + * const std::string display_b { "MY_ID_2" }; + * const auto success = iface->setHdrStates({ { display_a, HdrState::Enabled }, + * { display_b, HdrState::Disabled } }); + * ``` + */ + [[nodiscard]] virtual bool + setHdrStates(const HdrStateMap &states) = 0; }; } // namespace display_device diff --git a/src/windows/winapilayer.cpp b/src/windows/winapilayer.cpp index 89f91bb..eb0c3d3 100644 --- a/src/windows/winapilayer.cpp +++ b/src/windows/winapilayer.cpp @@ -522,7 +522,7 @@ namespace display_device { LONG result { DisplayConfigGetDeviceInfo(&source_name.header) }; if (result != ERROR_SUCCESS) { - DD_LOG(error) << getErrorString(result) << " failed to get display name! "; + DD_LOG(error) << getErrorString(result) << " failed to get display name!"; return {}; } @@ -539,4 +539,39 @@ namespace display_device { modes.empty() ? nullptr : modes.data(), flags); } + + std::optional + WinApiLayer::getHdrState(const DISPLAYCONFIG_PATH_INFO &path) const { + DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO color_info = {}; + color_info.header.adapterId = path.targetInfo.adapterId; + color_info.header.id = path.targetInfo.id; + color_info.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO; + color_info.header.size = sizeof(color_info); + + LONG result { DisplayConfigGetDeviceInfo(&color_info.header) }; + if (result != ERROR_SUCCESS) { + DD_LOG(error) << getErrorString(result) << " failed to get advanced color info!"; + return std::nullopt; + } + + return color_info.advancedColorSupported ? std::make_optional(color_info.advancedColorEnabled ? HdrState::Enabled : HdrState::Disabled) : std::nullopt; + } + + bool + WinApiLayer::setHdrState(const DISPLAYCONFIG_PATH_INFO &path, HdrState state) { + DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE color_state = {}; + color_state.header.adapterId = path.targetInfo.adapterId; + color_state.header.id = path.targetInfo.id; + color_state.header.type = DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE; + color_state.header.size = sizeof(color_state); + color_state.enableAdvancedColor = state == HdrState::Enabled ? 1 : 0; + + LONG result { DisplayConfigSetDeviceInfo(&color_state.header) }; + if (result != ERROR_SUCCESS) { + DD_LOG(error) << getErrorString(result) << " failed to set advanced color info!"; + return false; + } + + return true; + } } // namespace display_device diff --git a/src/windows/windisplaydevicehdr.cpp b/src/windows/windisplaydevicehdr.cpp new file mode 100644 index 0000000..482fc06 --- /dev/null +++ b/src/windows/windisplaydevicehdr.cpp @@ -0,0 +1,127 @@ +// class header include +#include "displaydevice/windows/windisplaydevice.h" + +// system includes +#include + +// local includes +#include "displaydevice/logging.h" +#include "displaydevice/windows/winapiutils.h" + +namespace display_device { + namespace { + /** @brief HDR state map without optional values. */ + using HdrStateMapNoOpt = std::map; + + /** + * @see setHdrStates for a description as this was split off to reduce cognitive complexity. + */ + bool + doSetHdrStates(WinApiLayerInterface &w_api, const PathAndModeData &display_data, const HdrStateMapNoOpt &states, HdrStateMapNoOpt *changed_states) { + const auto try_set_state { + [&w_api, &display_data](const auto &device_id, const auto &state, auto ¤t_state) { + const auto path { win_utils::getActivePath(w_api, device_id, display_data.m_paths) }; + if (!path) { + DD_LOG(error) << "Failed to find device for " << device_id << "!"; + return false; + } + + const auto current_state_int { w_api.getHdrState(*path) }; + if (!current_state_int) { + DD_LOG(error) << "HDR state cannot be changed for " << device_id << "!"; + return false; + } + + if (state != *current_state_int) { + if (!w_api.setHdrState(*path, state)) { + // Error already logged + return false; + } + + current_state = current_state_int; + } + + return true; + } + }; + + for (const auto &[device_id, state] : states) { + std::optional current_state; + if (try_set_state(device_id, state, current_state)) { + if (current_state && changed_states != nullptr) { + (*changed_states)[device_id] = *current_state; + } + } + // If we are undoing changes we don't want to return early and continue regardless of what error we get. + else if (changed_states != nullptr) { + return false; + } + } + + return true; + } + + } // namespace + + HdrStateMap + WinDisplayDevice::getCurrentHdrStates(const std::set &device_ids) const { + if (device_ids.empty()) { + DD_LOG(error) << "Device id set is empty!"; + return {}; + } + + const auto display_data { m_w_api->queryDisplayConfig(QueryType::Active) }; + if (!display_data) { + // Error already logged + return {}; + } + + HdrStateMap states; + for (const auto &device_id : device_ids) { + const auto path { win_utils::getActivePath(*m_w_api, device_id, display_data->m_paths) }; + if (!path) { + DD_LOG(error) << "Failed to find device for " << device_id << "!"; + return {}; + } + + states[device_id] = m_w_api->getHdrState(*path); + } + + return states; + } + + bool + WinDisplayDevice::setHdrStates(const HdrStateMap &states) { + if (states.empty()) { + DD_LOG(error) << "States map is empty!"; + return false; + } + + HdrStateMapNoOpt states_without_opt; + std::ranges::copy(states | + std::ranges::views::filter([](const auto &entry) { return static_cast(entry.second); }) | + std::views::transform([](const auto &entry) { return std::make_pair(entry.first, *entry.second); }), + std::inserter(states_without_opt, std::begin(states_without_opt))); + + if (states_without_opt.empty()) { + // Return early as there is nothing to do... + return true; + } + + const auto display_data { m_w_api->queryDisplayConfig(QueryType::Active) }; + if (!display_data) { + // Error already logged + return {}; + } + + HdrStateMapNoOpt changed_states; + if (!doSetHdrStates(*m_w_api, *display_data, states_without_opt, &changed_states)) { + if (!changed_states.empty()) { + doSetHdrStates(*m_w_api, *display_data, changed_states, nullptr); // return value does not matter + } + return false; + } + + return true; + } +} // namespace display_device diff --git a/tests/unit/windows/test_winapilayer.cpp b/tests/unit/windows/test_winapilayer.cpp index 64b93c2..e31920d 100644 --- a/tests/unit/windows/test_winapilayer.cpp +++ b/tests/unit/windows/test_winapilayer.cpp @@ -204,3 +204,11 @@ TEST_F_S(GetDisplayName) { TEST_F_S(GetDisplayName, InvalidPath) { EXPECT_EQ(m_layer.getDisplayName(INVALID_PATH), std::string {}); } + +TEST_F_S(GetHdrState, InvalidPath) { + EXPECT_EQ(m_layer.getHdrState(INVALID_PATH), std::nullopt); +} + +TEST_F_S(SetHdrState, InvalidPath) { + EXPECT_FALSE(m_layer.setHdrState(INVALID_PATH, display_device::HdrState::Enabled)); +} diff --git a/tests/unit/windows/test_windisplaydevicehdr.cpp b/tests/unit/windows/test_windisplaydevicehdr.cpp new file mode 100644 index 0000000..5a9c554 --- /dev/null +++ b/tests/unit/windows/test_windisplaydevicehdr.cpp @@ -0,0 +1,362 @@ +// local includes +#include "displaydevice/windows/winapilayer.h" +#include "displaydevice/windows/windisplaydevice.h" +#include "fixtures.h" +#include "utils/comparison.h" +#include "utils/guards.h" +#include "utils/mockwinapilayer.h" + +namespace { + // Convenience keywords for GMock + using ::testing::_; + using ::testing::InSequence; + using ::testing::Return; + using ::testing::StrictMock; + + // Test fixture(s) for this file + class WinDisplayDeviceHdr: public BaseTest { + public: + bool + isSystemTest() const override { + return true; + } + + std::shared_ptr m_layer { std::make_shared() }; + display_device::WinDisplayDevice m_win_dd { m_layer }; + }; + + class WinDisplayDeviceHdrMocked: public BaseTest { + public: + void + setupExpectedGetActivePathCall(int id_number, InSequence & /* To ensure that sequence is created outside this scope */) { + for (int i = 1; i <= id_number; ++i) { + EXPECT_CALL(*m_layer, getMonitorDevicePath(_)) + .Times(1) + .WillOnce(Return("PathX")) + .RetiresOnSaturation(); + EXPECT_CALL(*m_layer, getDeviceId(_)) + .Times(1) + .WillOnce(Return("DeviceId" + std::to_string(i))) + .RetiresOnSaturation(); + EXPECT_CALL(*m_layer, getDisplayName(_)) + .Times(1) + .WillOnce(Return("DisplayNameX")) + .RetiresOnSaturation(); + } + } + + std::shared_ptr> m_layer { std::make_shared>() }; + display_device::WinDisplayDevice m_win_dd { m_layer }; + }; + + // Specialized TEST macro(s) for this test file +#define TEST_F_S(...) DD_MAKE_TEST(TEST_F, WinDisplayDeviceHdr, __VA_ARGS__) +#define TEST_F_S_MOCKED(...) DD_MAKE_TEST(TEST_F, WinDisplayDeviceHdrMocked, __VA_ARGS__) + + // Helper functions + display_device::ActiveTopology + makeExtendedTopology(const std::vector &device_ids) { + display_device::ActiveTopology topology; + for (const auto &device_id : device_ids) { + topology.push_back({ device_id }); + } + return topology; + } +} // namespace + +TEST_F_S(GetSetHdrStates) { + const auto available_devices { getAvailableDevices(*m_layer) }; + ASSERT_TRUE(available_devices); + + const auto topology_guard { makeTopologyGuard(m_win_dd) }; + ASSERT_TRUE(m_win_dd.setTopology(makeExtendedTopology(*available_devices))); + + const auto hdr_states { m_win_dd.getCurrentHdrStates(flattenTopology(m_win_dd.getCurrentTopology())) }; + if (!std::ranges::any_of(hdr_states, [](auto entry) -> bool { return static_cast(entry.second); })) { + GTEST_SKIP_("No HDR display is available in the system."); + } + + auto flipped_states { hdr_states }; + for (auto &[key, state] : flipped_states) { + state = state ? (*state == display_device::HdrState::Disabled ? + display_device::HdrState::Enabled : + display_device::HdrState::Disabled) : + state; + } + + const auto hdr_state_guard { makeHdrStateGuard(m_win_dd) }; + ASSERT_TRUE(m_win_dd.setHdrStates(flipped_states)); + ASSERT_TRUE(m_win_dd.setHdrStates(hdr_states)); +} + +TEST_F_S_MOCKED(GetHdrStates) { + InSequence sequence; + EXPECT_CALL(*m_layer, queryDisplayConfig(display_device::QueryType::Active)) + .Times(1) + .WillOnce(Return(ut_consts::PAM_3_ACTIVE)) + .RetiresOnSaturation(); + + setupExpectedGetActivePathCall(1, sequence); + EXPECT_CALL(*m_layer, getHdrState(ut_consts::PAM_3_ACTIVE->m_paths.at(0))) + .Times(1) + .WillOnce(Return(std::make_optional(display_device::HdrState::Disabled))) + .RetiresOnSaturation(); + + setupExpectedGetActivePathCall(2, sequence); + EXPECT_CALL(*m_layer, getHdrState(ut_consts::PAM_3_ACTIVE->m_paths.at(1))) + .Times(1) + .WillOnce(Return(std::nullopt)) + .RetiresOnSaturation(); + + setupExpectedGetActivePathCall(3, sequence); + EXPECT_CALL(*m_layer, getHdrState(ut_consts::PAM_3_ACTIVE->m_paths.at(2))) + .Times(1) + .WillOnce(Return(std::make_optional(display_device::HdrState::Enabled))) + .RetiresOnSaturation(); + + const display_device::HdrStateMap expected_states { + { "DeviceId1", std::make_optional(display_device::HdrState::Disabled) }, + { "DeviceId2", std::nullopt }, + { "DeviceId3", std::make_optional(display_device::HdrState::Enabled) } + }; + EXPECT_EQ(m_win_dd.getCurrentHdrStates({ "DeviceId1", "DeviceId2", "DeviceId3" }), expected_states); +} + +TEST_F_S_MOCKED(GetHdrStates, EmptyIdList) { + EXPECT_EQ(m_win_dd.getCurrentHdrStates({}), display_device::HdrStateMap {}); +} + +TEST_F_S_MOCKED(GetHdrStates, FailedToGetDisplayData) { + EXPECT_CALL(*m_layer, queryDisplayConfig(display_device::QueryType::Active)) + .Times(1) + .WillOnce(Return(ut_consts::PAM_NULL)); + + EXPECT_EQ(m_win_dd.getCurrentHdrStates({ "DeviceId1" }), display_device::HdrStateMap {}); +} + +TEST_F_S_MOCKED(GetHdrStates, FailedToGetActivePath) { + EXPECT_CALL(*m_layer, queryDisplayConfig(display_device::QueryType::Active)) + .Times(1) + .WillOnce(Return(ut_consts::PAM_EMPTY)); + + EXPECT_EQ(m_win_dd.getCurrentHdrStates({ "DeviceId1" }), display_device::HdrStateMap {}); +} + +TEST_F_S_MOCKED(SetHdrStates) { + InSequence sequence; + EXPECT_CALL(*m_layer, queryDisplayConfig(display_device::QueryType::Active)) + .Times(1) + .WillOnce(Return(ut_consts::PAM_3_ACTIVE)) + .RetiresOnSaturation(); + + setupExpectedGetActivePathCall(1, sequence); + EXPECT_CALL(*m_layer, getHdrState(ut_consts::PAM_3_ACTIVE->m_paths.at(0))) + .Times(1) + .WillOnce(Return(std::make_optional(display_device::HdrState::Disabled))) + .RetiresOnSaturation(); + EXPECT_CALL(*m_layer, setHdrState(ut_consts::PAM_3_ACTIVE->m_paths.at(0), display_device::HdrState::Enabled)) + .Times(1) + .WillOnce(Return(true)) + .RetiresOnSaturation(); + + setupExpectedGetActivePathCall(3, sequence); + EXPECT_CALL(*m_layer, getHdrState(ut_consts::PAM_3_ACTIVE->m_paths.at(2))) + .Times(1) + .WillOnce(Return(std::make_optional(display_device::HdrState::Enabled))) + .RetiresOnSaturation(); + + const display_device::HdrStateMap new_states { + { "DeviceId1", std::make_optional(display_device::HdrState::Enabled) }, + { "DeviceId2", std::nullopt }, + { "DeviceId3", std::make_optional(display_device::HdrState::Enabled) } + }; + EXPECT_TRUE(m_win_dd.setHdrStates(new_states)); +} + +TEST_F_S_MOCKED(SetHdrStates, AllDevicesWithOptStates) { + const display_device::HdrStateMap new_states { + { "DeviceId1", std::nullopt }, + { "DeviceId2", std::nullopt }, + { "DeviceId3", std::nullopt } + }; + EXPECT_TRUE(m_win_dd.setHdrStates(new_states)); +} + +TEST_F_S_MOCKED(SetHdrStates, FailedToGetHdrState) { + InSequence sequence; + EXPECT_CALL(*m_layer, queryDisplayConfig(display_device::QueryType::Active)) + .Times(1) + .WillOnce(Return(ut_consts::PAM_3_ACTIVE)) + .RetiresOnSaturation(); + + setupExpectedGetActivePathCall(1, sequence); + EXPECT_CALL(*m_layer, getHdrState(ut_consts::PAM_3_ACTIVE->m_paths.at(0))) + .Times(1) + .WillOnce(Return(std::nullopt)) + .RetiresOnSaturation(); + + const display_device::HdrStateMap new_states { + { "DeviceId1", std::make_optional(display_device::HdrState::Enabled) }, + { "DeviceId2", std::nullopt }, + { "DeviceId3", std::make_optional(display_device::HdrState::Enabled) } + }; + EXPECT_FALSE(m_win_dd.setHdrStates(new_states)); +} + +TEST_F_S_MOCKED(SetHdrStates, FailedToSetHdrState, LastDevice) { + InSequence sequence; + EXPECT_CALL(*m_layer, queryDisplayConfig(display_device::QueryType::Active)) + .Times(1) + .WillOnce(Return(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES)) + .RetiresOnSaturation(); + + // Setting states + { + setupExpectedGetActivePathCall(1, sequence); + EXPECT_CALL(*m_layer, getHdrState(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES->m_paths.at(0))) + .Times(1) + .WillOnce(Return(std::make_optional(display_device::HdrState::Disabled))) + .RetiresOnSaturation(); + EXPECT_CALL(*m_layer, setHdrState(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES->m_paths.at(0), display_device::HdrState::Enabled)) + .Times(1) + .WillOnce(Return(true)) + .RetiresOnSaturation(); + + setupExpectedGetActivePathCall(3, sequence); + EXPECT_CALL(*m_layer, getHdrState(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES->m_paths.at(2))) + .Times(1) + .WillOnce(Return(std::make_optional(display_device::HdrState::Disabled))) + .RetiresOnSaturation(); + + setupExpectedGetActivePathCall(4, sequence); + EXPECT_CALL(*m_layer, getHdrState(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES->m_paths.at(3))) + .Times(1) + .WillOnce(Return(std::nullopt)) + .RetiresOnSaturation(); + } + + // Reverting only changed states + { + setupExpectedGetActivePathCall(1, sequence); + EXPECT_CALL(*m_layer, getHdrState(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES->m_paths.at(0))) + .Times(1) + .WillOnce(Return(std::make_optional(display_device::HdrState::Enabled))) + .RetiresOnSaturation(); + EXPECT_CALL(*m_layer, setHdrState(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES->m_paths.at(0), display_device::HdrState::Disabled)) + .Times(1) + .WillOnce(Return(true)) + .RetiresOnSaturation(); + } + + const display_device::HdrStateMap new_states { + { "DeviceId1", std::make_optional(display_device::HdrState::Enabled) }, + { "DeviceId2", std::nullopt }, + { "DeviceId3", std::make_optional(display_device::HdrState::Disabled) }, + { "DeviceId4", std::make_optional(display_device::HdrState::Enabled) } + }; + EXPECT_FALSE(m_win_dd.setHdrStates(new_states)); +} + +TEST_F_S_MOCKED(SetHdrStates, FailedToSetHdrState, LastDevice, NoEarlyExitInRecovery) { + InSequence sequence; + EXPECT_CALL(*m_layer, queryDisplayConfig(display_device::QueryType::Active)) + .Times(1) + .WillOnce(Return(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES)) + .RetiresOnSaturation(); + + // Setting states + { + setupExpectedGetActivePathCall(1, sequence); + EXPECT_CALL(*m_layer, getHdrState(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES->m_paths.at(0))) + .Times(1) + .WillOnce(Return(std::make_optional(display_device::HdrState::Disabled))) + .RetiresOnSaturation(); + EXPECT_CALL(*m_layer, setHdrState(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES->m_paths.at(0), display_device::HdrState::Enabled)) + .Times(1) + .WillOnce(Return(true)) + .RetiresOnSaturation(); + + setupExpectedGetActivePathCall(2, sequence); + EXPECT_CALL(*m_layer, getHdrState(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES->m_paths.at(1))) + .Times(1) + .WillOnce(Return(std::make_optional(display_device::HdrState::Disabled))) + .RetiresOnSaturation(); + EXPECT_CALL(*m_layer, setHdrState(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES->m_paths.at(1), display_device::HdrState::Enabled)) + .Times(1) + .WillOnce(Return(true)) + .RetiresOnSaturation(); + + setupExpectedGetActivePathCall(3, sequence); + EXPECT_CALL(*m_layer, getHdrState(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES->m_paths.at(2))) + .Times(1) + .WillOnce(Return(std::make_optional(display_device::HdrState::Disabled))) + .RetiresOnSaturation(); + EXPECT_CALL(*m_layer, setHdrState(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES->m_paths.at(2), display_device::HdrState::Enabled)) + .Times(1) + .WillOnce(Return(true)) + .RetiresOnSaturation(); + + setupExpectedGetActivePathCall(4, sequence); + EXPECT_CALL(*m_layer, getHdrState(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES->m_paths.at(3))) + .Times(1) + .WillOnce(Return(std::nullopt)) + .RetiresOnSaturation(); + } + + // Reverting only changed states + { + setupExpectedGetActivePathCall(1, sequence); + EXPECT_CALL(*m_layer, getHdrState(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES->m_paths.at(0))) + .Times(1) + .WillOnce(Return(std::make_optional(display_device::HdrState::Enabled))) + .RetiresOnSaturation(); + EXPECT_CALL(*m_layer, setHdrState(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES->m_paths.at(0), display_device::HdrState::Disabled)) + .Times(1) + .WillOnce(Return(false)) + .RetiresOnSaturation(); + + setupExpectedGetActivePathCall(2, sequence); + EXPECT_CALL(*m_layer, getHdrState(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES->m_paths.at(1))) + .Times(1) + .WillOnce(Return(std::nullopt)) + .RetiresOnSaturation(); + + setupExpectedGetActivePathCall(3, sequence); + EXPECT_CALL(*m_layer, getHdrState(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES->m_paths.at(2))) + .Times(1) + .WillOnce(Return(std::make_optional(display_device::HdrState::Enabled))) + .RetiresOnSaturation(); + EXPECT_CALL(*m_layer, setHdrState(ut_consts::PAM_4_ACTIVE_WITH_2_DUPLICATES->m_paths.at(2), display_device::HdrState::Disabled)) + .Times(1) + .WillOnce(Return(true)) + .RetiresOnSaturation(); + } + + const display_device::HdrStateMap new_states { + { "DeviceId1", std::make_optional(display_device::HdrState::Enabled) }, + { "DeviceId2", std::make_optional(display_device::HdrState::Enabled) }, + { "DeviceId3", std::make_optional(display_device::HdrState::Enabled) }, + { "DeviceId4", std::make_optional(display_device::HdrState::Enabled) } + }; + EXPECT_FALSE(m_win_dd.setHdrStates(new_states)); +} + +TEST_F_S_MOCKED(SetHdrStates, EmptyMap) { + EXPECT_FALSE(m_win_dd.setHdrStates({})); +} + +TEST_F_S_MOCKED(SetHdrStates, FailedToGetDisplayData) { + EXPECT_CALL(*m_layer, queryDisplayConfig(display_device::QueryType::Active)) + .Times(1) + .WillOnce(Return(ut_consts::PAM_NULL)); + + EXPECT_FALSE(m_win_dd.setHdrStates({ { "DeviceId1", std::make_optional(display_device::HdrState::Disabled) } })); +} + +TEST_F_S_MOCKED(SetHdrStates, FailedToGetActivePath) { + EXPECT_CALL(*m_layer, queryDisplayConfig(display_device::QueryType::Active)) + .Times(1) + .WillOnce(Return(ut_consts::PAM_EMPTY)); + + EXPECT_FALSE(m_win_dd.setHdrStates({ { "DeviceId1", std::make_optional(display_device::HdrState::Disabled) } })); +} diff --git a/tests/unit/windows/utils/guards.h b/tests/unit/windows/utils/guards.h index f8b33b4..5cabfd5 100644 --- a/tests/unit/windows/utils/guards.h +++ b/tests/unit/windows/utils/guards.h @@ -37,3 +37,10 @@ makePrimaryGuard(display_device::WinDisplayDevice &win_dd) { static_cast(win_dd.setAsPrimary(primary_device)); }); } + +inline auto +makeHdrStateGuard(display_device::WinDisplayDevice &win_dd) { + return boost::scope::make_scope_exit([&win_dd, states = win_dd.getCurrentHdrStates(flattenTopology(win_dd.getCurrentTopology()))]() { + static_cast(win_dd.setHdrStates(states)); + }); +} diff --git a/tests/unit/windows/utils/mockwinapilayer.h b/tests/unit/windows/utils/mockwinapilayer.h index c7c2cc1..51541a1 100644 --- a/tests/unit/windows/utils/mockwinapilayer.h +++ b/tests/unit/windows/utils/mockwinapilayer.h @@ -16,6 +16,8 @@ namespace display_device { MOCK_METHOD(std::string, getFriendlyName, (const DISPLAYCONFIG_PATH_INFO &), (const, override)); MOCK_METHOD(std::string, getDisplayName, (const DISPLAYCONFIG_PATH_INFO &), (const, override)); MOCK_METHOD(LONG, setDisplayConfig, (std::vector, std::vector, UINT32), (override)); + MOCK_METHOD(std::optional, getHdrState, (const DISPLAYCONFIG_PATH_INFO &), (const, override)); + MOCK_METHOD(bool, setHdrState, (const DISPLAYCONFIG_PATH_INFO &, HdrState), (override)); }; } // namespace display_device