diff --git a/src/common/include/displaydevice/settingsmanagerinterface.h b/src/common/include/displaydevice/settingsmanagerinterface.h index 7f225d7..e49c615 100644 --- a/src/common/include/displaydevice/settingsmanagerinterface.h +++ b/src/common/include/displaydevice/settingsmanagerinterface.h @@ -18,6 +18,7 @@ namespace display_device { DevicePrepFailed, PrimaryDevicePrepFailed, DisplayModePrepFailed, + HdrStatePrepFailed, PersistenceSaveFailed, }; diff --git a/src/windows/include/displaydevice/windows/settingsmanager.h b/src/windows/include/displaydevice/windows/settingsmanager.h index 01c4338..4ed05c5 100644 --- a/src/windows/include/displaydevice/windows/settingsmanager.h +++ b/src/windows/include/displaydevice/windows/settingsmanager.h @@ -76,6 +76,18 @@ namespace display_device { [[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 Changes or restores the HDR states based on the cached state, new state and configuration. + * @param config Configuration to be used for preparing HDR states. + * @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 + prepareHdrStates(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 24f5fc7..b88a610 100644 --- a/src/windows/include/displaydevice/windows/settingsutils.h +++ b/src/windows/include/displaydevice/windows/settingsutils.h @@ -117,6 +117,22 @@ namespace display_device::win_utils { const std::set<std::string> &additional_devices_to_configure, const DeviceDisplayModeMap &original_modes); + /** + * @brief Compute new HDR states from arbitrary data. + * @param hdr_state Specify state that should be used to override the original states. + * @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_states HDR states to be used as a base onto which changes are made. + * @return New HDR states that should be set. + */ + HdrStateMap + computeNewHdrStates(const std::optional<HdrState> &hdr_state, + bool configuring_primary_devices, + const std::string &device_to_configure, + const std::set<std::string> &additional_devices_to_configure, + const HdrStateMap &original_states); + /** * @brief Make guard function for the topology. * @param win_dd Interface for interacting with the OS. @@ -210,4 +226,21 @@ namespace display_device::win_utils { */ DdGuardFn hdrStateGuardFn(WinDisplayDeviceInterface &win_dd, const ActiveTopology &topology); + + /** + * @brief Make guard function for the HDR states. + * @param win_dd Interface for interacting with the OS. + * @param states HDR states to restore when the guard is executed. + * @return Function that once called will try to revert HDR states to the ones that were provided. + * + * EXAMPLES: + * ```cpp + * WinDisplayDeviceInterface* iface = getIface(...); + * const HdrStateMap states { }; + * + * boost::scope::scope_exit guard { hdrStateGuardFn(*iface, states) }; + * ``` + */ + DdGuardFn + hdrStateGuardFn(WinDisplayDeviceInterface &win_dd, const HdrStateMap &states); } // namespace display_device::win_utils diff --git a/src/windows/settingsmanagerapply.cpp b/src/windows/settingsmanagerapply.cpp index 94ef909..024c9b1 100644 --- a/src/windows/settingsmanagerapply.cpp +++ b/src/windows/settingsmanagerapply.cpp @@ -82,12 +82,12 @@ namespace display_device { return ApplyResult::DisplayModePrepFailed; } - // TODO: - // - // Other device handling goes here that will use device_to_configure and additional_devices_to_configure: - // - // - handle HDR (need to replicate the HDR bug and find the best place for workaround) - // + DdGuardFn hdr_state_guard_fn { noopFn }; + boost::scope::scope_exit<DdGuardFn &> hdr_state_guard { hdr_state_guard_fn }; + if (!prepareHdrStates(config, device_to_configure, additional_devices_to_configure, hdr_state_guard_fn, new_state)) { + // Error already logged + return ApplyResult::HdrStatePrepFailed; + } // We will always keep the new state persistently, even if there are no new meaningful changes, because // we want to preserve the initial state for consistency. @@ -105,6 +105,7 @@ namespace display_device { topology_prep_guard.set_active(false); primary_guard.set_active(false); mode_guard.set_active(false); + hdr_state_guard.set_active(false); return ApplyResult::Ok; } @@ -304,4 +305,59 @@ namespace display_device { return true; } + + [[nodiscard]] bool + SettingsManager::prepareHdrStates(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_hdr_states { cached_state ? cached_state->m_modified.m_original_hdr_states : HdrStateMap {} }; + const bool change_required { config.m_hdr_state }; + const bool might_need_to_restore { !cached_hdr_states.empty() }; + + HdrStateMap current_hdr_states; + if (change_required || might_need_to_restore) { + current_hdr_states = m_dd_api->getCurrentHdrStates(win_utils::flattenTopology(new_state.m_modified.m_topology)); + if (current_hdr_states.empty()) { + DD_LOG(error) << "Failed to get current HDR states!"; + return false; + } + } + + const auto try_change { [&](const HdrStateMap &new_states, const auto info_preamble, const auto error_log) { + if (current_hdr_states != new_states) { + DD_LOG(info) << info_preamble << toJson(new_states); + if (!m_dd_api->setHdrStates(new_states)) { + DD_LOG(error) << error_log; + return false; + } + + guard_fn = win_utils::hdrStateGuardFn(*m_dd_api, current_hdr_states); + } + + return true; + } }; + + if (change_required) { + const bool configuring_primary_devices { config.m_device_id.empty() }; + const auto original_hdr_states { cached_hdr_states.empty() ? current_hdr_states : cached_hdr_states }; + const auto new_hdr_states { win_utils::computeNewHdrStates(config.m_hdr_state, configuring_primary_devices, device_to_configure, additional_devices_to_configure, original_hdr_states) }; + + if (!try_change(new_hdr_states, "Changing HDR states to: ", "Failed to apply new configuration, because new HDR states 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_hdr_states = original_hdr_states; + return true; + } + + if (might_need_to_restore) { + if (!try_change(cached_hdr_states, "Changing HDR states back to: ", "Failed to restore original HDR states!")) { + // Error already logged + return false; + } + } + + return true; + } } // namespace display_device diff --git a/src/windows/settingsutils.cpp b/src/windows/settingsutils.cpp index f881ac2..71a9720 100644 --- a/src/windows/settingsutils.cpp +++ b/src/windows/settingsutils.cpp @@ -296,6 +296,39 @@ namespace display_device::win_utils { return new_modes; } + HdrStateMap + computeNewHdrStates(const std::optional<HdrState> &hdr_state, bool configuring_primary_devices, const std::string &device_to_configure, const std::set<std::string> &additional_devices_to_configure, const HdrStateMap &original_states) { + HdrStateMap new_states { original_states }; + + if (hdr_state) { + const auto try_update_new_state = [&new_states, &hdr_state](const std::string &device_id) { + const auto current_state { new_states[device_id] }; + if (!current_state) { + return; + } + + new_states[device_id] = *hdr_state; + }; + + if (configuring_primary_devices) { + // No device has been specified, so if they're all are primary devices + // we need to update state for all duplicates. + const auto devices { joinConfigurableDevices(device_to_configure, additional_devices_to_configure) }; + for (const auto &device_id : devices) { + try_update_new_state(device_id); + } + } + else { + // Even if we have duplicate devices, their HDR states may differ + // and since the device was specified, let's apply the HDR state + // only to the specified device. + try_update_new_state(device_to_configure); + } + } + + return new_states; + } + DdGuardFn topologyGuardFn(WinDisplayDeviceInterface &win_dd, const ActiveTopology &topology) { DD_LOG(debug) << "Got topology in topologyGuardFn:\n" @@ -344,7 +377,11 @@ namespace display_device::win_utils { DdGuardFn hdrStateGuardFn(WinDisplayDeviceInterface &win_dd, const ActiveTopology &topology) { - const auto states = win_dd.getCurrentHdrStates(flattenTopology(topology)); + return hdrStateGuardFn(win_dd, win_dd.getCurrentHdrStates(flattenTopology(topology))); + } + + DdGuardFn + hdrStateGuardFn(WinDisplayDeviceInterface &win_dd, const HdrStateMap &states) { DD_LOG(debug) << "Got states in hdrStateGuardFn:\n" << toJson(states); return [&win_dd, states]() { diff --git a/tests/unit/windows/test_settingsmanagerapply.cpp b/tests/unit/windows/test_settingsmanagerapply.cpp index 8123c41..4447053 100644 --- a/tests/unit/windows/test_settingsmanagerapply.cpp +++ b/tests/unit/windows/test_settingsmanagerapply.cpp @@ -34,6 +34,11 @@ namespace { { "DeviceId2", { { 1920, 1080 }, { 60, 1 } } }, { "DeviceId3", { { 2560, 1440 }, { 30, 1 } } } }; + const display_device::HdrStateMap DEFAULT_CURRENT_HDR_STATES { + { "DeviceId1", display_device::HdrState::Disabled }, + { "DeviceId2", display_device::HdrState::Disabled }, + { "DeviceId3", std::nullopt } + }; const display_device::SingleDisplayConfigState DEFAULT_PERSISTENCE_INPUT_BASE { { DEFAULT_CURRENT_TOPOLOGY, { "DeviceId1", "DeviceId2" } } }; // Test fixture(s) for this file @@ -193,6 +198,30 @@ namespace { .RetiresOnSaturation(); } + void + expectedSetHdrStatesCall(InSequence &sequence /* To ensure that sequence is created outside this scope */, const display_device::HdrStateMap &states, const bool success = true) { + EXPECT_CALL(*m_dd_api, setHdrStates(states)) + .Times(1) + .WillOnce(Return(success)) + .RetiresOnSaturation(); + } + + void + expectedGetCurrentHdrStatesCall(InSequence &sequence /* To ensure that sequence is created outside this scope */, const std::set<std::string> &devices, const display_device::HdrStateMap &states) { + EXPECT_CALL(*m_dd_api, getCurrentHdrStates(devices)) + .Times(1) + .WillOnce(Return(states)) + .RetiresOnSaturation(); + } + + void + expectedSetHdrStatesGuardCall(InSequence &sequence /* To ensure that sequence is created outside this scope */, const display_device::HdrStateMap &states) { + EXPECT_CALL(*m_dd_api, setHdrStates(states)) + .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>>() }; @@ -947,6 +976,237 @@ TEST_F_S_MOCKED(PrepareDisplayModes, DisplayModesRestoreSkipped, PersistenceFail EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1" }), display_device::SettingsManager::ApplyResult::PersistenceSaveFailed); } +TEST_F_S_MOCKED(PrepareHdrStates, FailedToGetHdrStates) { + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentHdrStatesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), {}); + + expectedTopologyGuardTopologyCall(sequence); + expectedTopologyGuardNewlyCapturedContextCall(sequence, false); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1", .m_hdr_state = display_device::HdrState::Enabled }), display_device::SettingsManager::ApplyResult::HdrStatePrepFailed); +} + +TEST_F_S_MOCKED(PrepareHdrStates, FailedToSetHdrStates) { + auto new_states { DEFAULT_CURRENT_HDR_STATES }; + new_states["DeviceId1"] = display_device::HdrState::Enabled; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentHdrStatesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_HDR_STATES); + expectedSetHdrStatesCall(sequence, new_states, false); + + expectedTopologyGuardTopologyCall(sequence); + expectedTopologyGuardNewlyCapturedContextCall(sequence, false); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1", .m_hdr_state = display_device::HdrState::Enabled }), display_device::SettingsManager::ApplyResult::HdrStatePrepFailed); +} + +TEST_F_S_MOCKED(PrepareHdrStates, HdrStatesSet) { + auto new_states { DEFAULT_CURRENT_HDR_STATES }; + new_states["DeviceId1"] = display_device::HdrState::Enabled; + + auto persistence_input { DEFAULT_PERSISTENCE_INPUT_BASE }; + persistence_input.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + persistence_input.m_modified.m_original_hdr_states = DEFAULT_CURRENT_HDR_STATES; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentHdrStatesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_HDR_STATES); + expectedSetHdrStatesCall(sequence, new_states); + expectedPersistenceCall(sequence, persistence_input); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1", .m_hdr_state = display_device::HdrState::Enabled }), display_device::SettingsManager::ApplyResult::Ok); +} + +TEST_F_S_MOCKED(PrepareHdrStates, HdrStatesSet, PrimaryDeviceSpecified) { + auto new_states { DEFAULT_CURRENT_HDR_STATES }; + new_states["DeviceId1"] = display_device::HdrState::Enabled; + new_states["DeviceId2"] = display_device::HdrState::Enabled; + + auto persistence_input { DEFAULT_PERSISTENCE_INPUT_BASE }; + persistence_input.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + persistence_input.m_modified.m_original_hdr_states = DEFAULT_CURRENT_HDR_STATES; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentHdrStatesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_HDR_STATES); + expectedSetHdrStatesCall(sequence, new_states); + expectedPersistenceCall(sequence, persistence_input); + + EXPECT_EQ(getImpl().applySettings({ .m_hdr_state = display_device::HdrState::Enabled }), display_device::SettingsManager::ApplyResult::Ok); +} + +TEST_F_S_MOCKED(PrepareHdrStates, HdrStatesSet, CachedModesReused) { + auto new_states { DEFAULT_CURRENT_HDR_STATES }; + new_states["DeviceId1"] = display_device::HdrState::Enabled; + + auto initial_state { DEFAULT_PERSISTENCE_INPUT_BASE }; + initial_state.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + initial_state.m_modified.m_original_hdr_states = DEFAULT_CURRENT_HDR_STATES; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence, DEFAULT_CURRENT_TOPOLOGY, initial_state); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentHdrStatesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_HDR_STATES); + expectedSetHdrStatesCall(sequence, new_states); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1", .m_hdr_state = display_device::HdrState::Enabled }), display_device::SettingsManager::ApplyResult::Ok); +} + +TEST_F_S_MOCKED(PrepareHdrStates, HdrStatesSet, GuardInvoked) { + auto new_states { DEFAULT_CURRENT_HDR_STATES }; + new_states["DeviceId1"] = display_device::HdrState::Enabled; + + auto persistence_input { DEFAULT_PERSISTENCE_INPUT_BASE }; + persistence_input.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + persistence_input.m_modified.m_original_hdr_states = DEFAULT_CURRENT_HDR_STATES; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentHdrStatesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_HDR_STATES); + expectedSetHdrStatesCall(sequence, new_states); + expectedPersistenceCall(sequence, persistence_input, false); + + expectedSetHdrStatesGuardCall(sequence, DEFAULT_CURRENT_HDR_STATES); + expectedTopologyGuardTopologyCall(sequence); + expectedTopologyGuardNewlyCapturedContextCall(sequence, false); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1", .m_hdr_state = display_device::HdrState::Enabled }), display_device::SettingsManager::ApplyResult::PersistenceSaveFailed); +} + +TEST_F_S_MOCKED(PrepareHdrStates, HdrStatesSetSkipped) { + auto persistence_input { DEFAULT_PERSISTENCE_INPUT_BASE }; + persistence_input.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + persistence_input.m_modified.m_original_hdr_states = DEFAULT_CURRENT_HDR_STATES; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentHdrStatesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_HDR_STATES); + expectedPersistenceCall(sequence, persistence_input); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId3", .m_hdr_state = display_device::HdrState::Enabled }), display_device::SettingsManager::ApplyResult::Ok); +} + +TEST_F_S_MOCKED(PrepareHdrStates, FailedToRestoreHdrStates) { + auto initial_state { DEFAULT_PERSISTENCE_INPUT_BASE }; + initial_state.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + initial_state.m_modified.m_original_hdr_states = DEFAULT_CURRENT_HDR_STATES; + initial_state.m_modified.m_original_hdr_states["DeviceId1"] = display_device::HdrState::Enabled; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence, DEFAULT_CURRENT_TOPOLOGY, initial_state); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + + expectedGetCurrentHdrStatesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_HDR_STATES); + expectedSetHdrStatesCall(sequence, initial_state.m_modified.m_original_hdr_states, false); + + expectedTopologyGuardTopologyCall(sequence); + expectedTopologyGuardNewlyCapturedContextCall(sequence, false); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1" }), display_device::SettingsManager::ApplyResult::HdrStatePrepFailed); +} + +TEST_F_S_MOCKED(PrepareHdrStates, HdrStatesRestored) { + auto initial_state { DEFAULT_PERSISTENCE_INPUT_BASE }; + initial_state.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + initial_state.m_modified.m_original_hdr_states = DEFAULT_CURRENT_HDR_STATES; + initial_state.m_modified.m_original_hdr_states["DeviceId1"] = display_device::HdrState::Enabled; + + 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); + + expectedGetCurrentHdrStatesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_HDR_STATES); + expectedSetHdrStatesCall(sequence, initial_state.m_modified.m_original_hdr_states); + expectedPersistenceCall(sequence, persistence_input); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1" }), display_device::SettingsManager::ApplyResult::Ok); +} + +TEST_F_S_MOCKED(PrepareHdrStates, HdrStatesRestored, PersistenceFailed) { + auto initial_state { DEFAULT_PERSISTENCE_INPUT_BASE }; + initial_state.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + initial_state.m_modified.m_original_hdr_states = DEFAULT_CURRENT_HDR_STATES; + initial_state.m_modified.m_original_hdr_states["DeviceId1"] = display_device::HdrState::Enabled; + + 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); + + expectedGetCurrentHdrStatesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_HDR_STATES); + expectedSetHdrStatesCall(sequence, initial_state.m_modified.m_original_hdr_states); + expectedPersistenceCall(sequence, persistence_input, false); + + expectedSetHdrStatesGuardCall(sequence, DEFAULT_CURRENT_HDR_STATES); + expectedTopologyGuardTopologyCall(sequence); + expectedTopologyGuardNewlyCapturedContextCall(sequence, false); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId1" }), display_device::SettingsManager::ApplyResult::PersistenceSaveFailed); +} + +TEST_F_S_MOCKED(PrepareHdrStates, HdrStatesRestoreSkipped, PersistenceFailed) { + auto initial_state { DEFAULT_PERSISTENCE_INPUT_BASE }; + initial_state.m_modified.m_topology = DEFAULT_CURRENT_TOPOLOGY; + initial_state.m_modified.m_original_hdr_states = DEFAULT_CURRENT_HDR_STATES; + + 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); + + expectedGetCurrentHdrStatesCall(sequence, display_device::win_utils::flattenTopology(DEFAULT_CURRENT_TOPOLOGY), DEFAULT_CURRENT_HDR_STATES); + 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 dd868d0..fa19705 100644 --- a/tests/unit/windows/test_settingsutils.cpp +++ b/tests/unit/windows/test_settingsutils.cpp @@ -26,6 +26,11 @@ namespace { { "DeviceId2", { { 1920, 1080 }, { 60, 1 } } }, { "DeviceId3", { { 2560, 1440 }, { 30, 1 } } } }; + const display_device::HdrStateMap DEFAULT_CURRENT_HDR_STATES { + { "DeviceId1", { display_device::HdrState::Disabled } }, + { "DeviceId2", { display_device::HdrState::Disabled } }, + { "DeviceId3", std::nullopt } + }; } // namespace TEST_F_S_MOCKED(FlattenTopology) { @@ -101,6 +106,27 @@ TEST_F_S_MOCKED(ComputeNewDisplayModes, NonPrimaryDevices) { EXPECT_EQ(display_device::win_utils::computeNewDisplayModes({ { 1920, 1080 } }, { 120. }, false, "DeviceId1", { "DeviceId2" }, DEFAULT_CURRENT_MODES), expected_modes); } +TEST_F_S_MOCKED(ComputeNewHdrStates, PrimaryDevices) { + auto expected_states { DEFAULT_CURRENT_HDR_STATES }; + expected_states["DeviceId1"] = display_device::HdrState::Enabled; + expected_states["DeviceId2"] = display_device::HdrState::Enabled; + + EXPECT_EQ(display_device::win_utils::computeNewHdrStates(display_device::HdrState::Enabled, true, "DeviceId1", { "DeviceId2", "DeviceId3" }, DEFAULT_CURRENT_HDR_STATES), expected_states); +} + +TEST_F_S_MOCKED(ComputeNewHdrStates, NonPrimaryDevices) { + auto expected_states { DEFAULT_CURRENT_HDR_STATES }; + expected_states["DeviceId1"] = display_device::HdrState::Enabled; + + EXPECT_EQ(display_device::win_utils::computeNewHdrStates(display_device::HdrState::Enabled, false, "DeviceId1", { "DeviceId2", "DeviceId3" }, DEFAULT_CURRENT_HDR_STATES), expected_states); + EXPECT_EQ(display_device::win_utils::computeNewHdrStates(std::nullopt, false, "DeviceId1", { "DeviceId2", "DeviceId3" }, DEFAULT_CURRENT_HDR_STATES), DEFAULT_CURRENT_HDR_STATES); +} + +TEST_F_S_MOCKED(ComputeNewHdrStates, NoStateProvided) { + EXPECT_EQ(display_device::win_utils::computeNewHdrStates(std::nullopt, true, "DeviceId1", { "DeviceId2", "DeviceId3" }, DEFAULT_CURRENT_HDR_STATES), DEFAULT_CURRENT_HDR_STATES); + EXPECT_EQ(display_device::win_utils::computeNewHdrStates(std::nullopt, false, "DeviceId1", { "DeviceId2", "DeviceId3" }, DEFAULT_CURRENT_HDR_STATES), DEFAULT_CURRENT_HDR_STATES); +} + TEST_F_S_MOCKED(StripInitialState, NoStripIsPerformed) { const display_device::SingleDisplayConfigState::Initial initial_state { DEFAULT_INITIAL_TOPOLOGY, { "DeviceId1", "DeviceId2" } }; const display_device::EnumeratedDeviceList devices {