From 2dcf5f763464c74bfbaba6c0ce8a5424e4df7b43 Mon Sep 17 00:00:00 2001 From: Lukas Senionis Date: Wed, 10 Jul 2024 18:07:27 +0300 Subject: [PATCH] feat: Add primary device handling logic (#52) --- .../displaydevice/settingsmanagerinterface.h | 1 + .../displaydevice/windows/settingsmanager.h | 11 + .../displaydevice/windows/settingsutils.h | 33 +++ src/windows/settingsmanagerapply.cpp | 75 ++++- src/windows/settingsmanagerrevert.cpp | 27 +- src/windows/settingsutils.cpp | 24 +- .../windows/test_settingsmanagerapply.cpp | 262 ++++++++++++++++++ .../windows/test_settingsmanagerrevert.cpp | 9 +- tests/unit/windows/test_settingsutils.cpp | 4 +- .../windows/utils/mockwindisplaydevice.cpp | 12 +- 10 files changed, 425 insertions(+), 33 deletions(-) diff --git a/src/common/include/displaydevice/settingsmanagerinterface.h b/src/common/include/displaydevice/settingsmanagerinterface.h index d663a72..93b99a4 100644 --- a/src/common/include/displaydevice/settingsmanagerinterface.h +++ b/src/common/include/displaydevice/settingsmanagerinterface.h @@ -16,6 +16,7 @@ namespace display_device { Ok, ApiTemporarilyUnavailable, DevicePrepFailed, + PrimaryDevicePrepFailed, PersistenceSaveFailed, }; diff --git a/src/windows/include/displaydevice/windows/settingsmanager.h b/src/windows/include/displaydevice/windows/settingsmanager.h index 3c1da4b..98573f7 100644 --- a/src/windows/include/displaydevice/windows/settingsmanager.h +++ b/src/windows/include/displaydevice/windows/settingsmanager.h @@ -53,6 +53,17 @@ namespace display_device { [[nodiscard]] std::optional>> prepareTopology(const SingleDisplayConfiguration &config, const ActiveTopology &topology_before_changes, bool &release_context); + /** + * @brief Changes or restores the primary device based on the cached state, new state and configuration. + * @param config Configuration to be used for preparing primary device. + * @param device_to_configure The main device to be used for preparation. + * @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 + preparePrimaryDevice(const SingleDisplayConfiguration &config, const std::string &device_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 c4d3791..377ef63 100644 --- a/src/windows/include/displaydevice/windows/settingsutils.h +++ b/src/windows/include/displaydevice/windows/settingsutils.h @@ -25,6 +25,22 @@ namespace display_device::win_utils { std::set flattenTopology(const ActiveTopology &topology); + /** + * @brief Get one primary device from the provided topology. + * @param win_dd Interface for interacting with the OS. + * @param topology Topology to be searched. + * @return Id of a primary device or an empty string if not found or an error has occured. + * + * EXAMPLES: + * ```cpp + * const WinDisplayDeviceInterface* iface = getIface(...); + * const ActiveTopology topology { { "DeviceId1", "DeviceId2" }, { "DeviceId3" } }; + * const auto primary_device_id { getPrimaryDevice(*iface, topology) }; + * ``` + */ + std::string + getPrimaryDevice(WinDisplayDeviceInterface &win_dd, const ActiveTopology &topology); + /** * @brief Compute the new intial state from arbitrary data. * @param prev_state Previous initial state if available. @@ -128,6 +144,23 @@ namespace display_device::win_utils { DdGuardFn primaryGuardFn(WinDisplayDeviceInterface &win_dd, const ActiveTopology &topology); + /** + * @brief Make guard function for the primary display. + * @param win_dd Interface for interacting with the OS. + * @param primary_device Primary device to restore when the guard is executed. + * @return Function that once called will try to revert primary display to the one that was provided. + * + * EXAMPLES: + * ```cpp + * WinDisplayDeviceInterface* iface = getIface(...); + * const std::string prev_primary_device { "MyDeviceId" }; + * + * boost::scope::scope_exit guard { primaryGuardFn(*iface, prev_primary_device) }; + * ``` + */ + DdGuardFn + primaryGuardFn(WinDisplayDeviceInterface &win_dd, const std::string &primary_device); + /** * @brief Make guard function for the HDR states. * @param win_dd Interface for interacting with the OS. diff --git a/src/windows/settingsmanagerapply.cpp b/src/windows/settingsmanagerapply.cpp index 51fefd7..26da342 100644 --- a/src/windows/settingsmanagerapply.cpp +++ b/src/windows/settingsmanagerapply.cpp @@ -11,6 +11,15 @@ #include "displaydevice/windows/settingsutils.h" namespace display_device { + namespace { + /** + * @brief Function that does nothing. + */ + void + noopFn() { + } + } // namespace + SettingsManager::ApplyResult SettingsManager::applySettings(const SingleDisplayConfiguration &config) { const auto api_access { m_dd_api->isApiAccessAvailable() }; @@ -57,13 +66,19 @@ namespace display_device { // Error already logged return ApplyResult::DevicePrepFailed; } - const auto &[new_state, device_to_configure, additional_devices_to_configure] = *prepped_topology_data; + auto [new_state, device_to_configure, additional_devices_to_configure] = *prepped_topology_data; + + DdGuardFn primary_guard_fn { noopFn }; + boost::scope::scope_exit primary_guard { primary_guard_fn }; + if (!preparePrimaryDevice(config, device_to_configure, primary_guard_fn, new_state)) { + // Error already logged + return ApplyResult::PrimaryDevicePrepFailed; + } // TODO: // // Other device handling goes here that will use device_to_configure and additional_devices_to_configure: // - // - handle primary device // - handle display modes // - handle HDR (need to replicate the HDR bug and find the best place for workaround) // @@ -82,6 +97,7 @@ namespace display_device { // Disable all guards before returning topology_prep_guard.set_active(false); + primary_guard.set_active(false); return ApplyResult::Ok; } @@ -170,4 +186,59 @@ namespace display_device { new_state.m_modified.m_topology = new_topology; return std::make_tuple(new_state, device_to_configure, additional_devices_to_configure); } + + bool + SettingsManager::preparePrimaryDevice(const SingleDisplayConfiguration &config, const std::string &device_to_configure, DdGuardFn &guard_fn, SingleDisplayConfigState &new_state) { + const auto &cached_state { m_persistence_state->getState() }; + const auto cached_primary_device { cached_state ? cached_state->m_modified.m_original_primary_device : std::string {} }; + const bool ensure_primary { config.m_device_prep == SingleDisplayConfiguration::DevicePreparation::EnsurePrimary }; + const bool might_need_to_restore { !cached_primary_device.empty() }; + + std::string current_primary_device; + if (ensure_primary || might_need_to_restore) { + current_primary_device = win_utils::getPrimaryDevice(*m_dd_api, new_state.m_modified.m_topology); + if (current_primary_device.empty()) { + DD_LOG(error) << "Failed to get primary device for the topology! Searched topology:\n" + << toJson(new_state.m_modified.m_topology); + return false; + } + } + + const auto try_change { [&](const std::string &new_device, const auto info_preamble, const auto error_log) { + if (current_primary_device != new_device) { + DD_LOG(info) << info_preamble << toJson(new_device); + if (!m_dd_api->setAsPrimary(new_device)) { + DD_LOG(error) << error_log; + return false; + } + + guard_fn = win_utils::primaryGuardFn(*m_dd_api, current_primary_device); + } + + return true; + } }; + + if (ensure_primary) { + const auto original_primary_device { cached_primary_device.empty() ? current_primary_device : cached_primary_device }; + const auto &new_primary_device { device_to_configure }; + + if (!try_change(new_primary_device, "Changing primary display to: ", "Failed to apply new configuration, because a new primary device 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_primary_device = original_primary_device; + return true; + } + + if (might_need_to_restore) { + if (!try_change(cached_primary_device, "Changing primary display back to: ", "Failed to restore original primary device!")) { + // Error already logged + return false; + } + } + + return true; + } } // namespace display_device diff --git a/src/windows/settingsmanagerrevert.cpp b/src/windows/settingsmanagerrevert.cpp index bf0fb3f..14b3eb0 100644 --- a/src/windows/settingsmanagerrevert.cpp +++ b/src/windows/settingsmanagerrevert.cpp @@ -10,6 +10,15 @@ #include "displaydevice/windows/settingsutils.h" namespace display_device { + namespace { + /** + * @brief Function that does nothing. + */ + void + noopFn() { + } + } // namespace + bool SettingsManager::revertSettings() { const auto &cached_state { m_persistence_state->getState() }; @@ -84,40 +93,40 @@ namespace display_device { return false; } - DdGuardFn hdr_guard_fn; - boost::scope::scope_exit hdr_guard { hdr_guard_fn, false }; + DdGuardFn hdr_guard_fn { noopFn }; + boost::scope::scope_exit hdr_guard { hdr_guard_fn }; if (!cached_state->m_modified.m_original_hdr_states.empty()) { hdr_guard_fn = win_utils::hdrStateGuardFn(*m_dd_api, cached_state->m_modified.m_topology); - hdr_guard.set_active(true); DD_LOG(info) << "Trying to change back the HDR states to:\n" << toJson(cached_state->m_modified.m_original_hdr_states); if (!m_dd_api->setHdrStates(cached_state->m_modified.m_original_hdr_states)) { // Error already logged + hdr_guard.set_active(false); return false; } } - DdGuardFn mode_guard_fn; - boost::scope::scope_exit mode_guard { mode_guard_fn, false }; + DdGuardFn mode_guard_fn { noopFn }; + boost::scope::scope_exit mode_guard { mode_guard_fn }; if (!cached_state->m_modified.m_original_modes.empty()) { mode_guard_fn = win_utils::modeGuardFn(*m_dd_api, cached_state->m_modified.m_topology); - mode_guard.set_active(true); DD_LOG(info) << "Trying to change back the display modes to:\n" << toJson(cached_state->m_modified.m_original_modes); if (!m_dd_api->setDisplayModes(cached_state->m_modified.m_original_modes)) { // Error already logged + mode_guard.set_active(false); return false; } } - DdGuardFn primary_guard_fn; - boost::scope::scope_exit primary_guard { primary_guard_fn, false }; + DdGuardFn primary_guard_fn { noopFn }; + boost::scope::scope_exit primary_guard { primary_guard_fn }; if (!cached_state->m_modified.m_original_primary_device.empty()) { primary_guard_fn = win_utils::primaryGuardFn(*m_dd_api, cached_state->m_modified.m_topology); - primary_guard.set_active(true); DD_LOG(info) << "Trying to change back the original primary device to: " << toJson(cached_state->m_modified.m_original_primary_device); if (!m_dd_api->setAsPrimary(cached_state->m_modified.m_original_primary_device)) { // Error already logged + primary_guard.set_active(false); return false; } } diff --git a/src/windows/settingsutils.cpp b/src/windows/settingsutils.cpp index 983780f..d6c542a 100644 --- a/src/windows/settingsutils.cpp +++ b/src/windows/settingsutils.cpp @@ -142,6 +142,18 @@ namespace display_device::win_utils { return flattened_topology; } + std::string + getPrimaryDevice(WinDisplayDeviceInterface &win_dd, const ActiveTopology &topology) { + const auto flat_topology { flattenTopology(topology) }; + for (const auto &device_id : flat_topology) { + if (win_dd.isPrimary(device_id)) { + return device_id; + } + } + + return {}; + } + std::optional computeInitialState(const std::optional &prev_state, const ActiveTopology &topology_before_changes, const EnumeratedDeviceList &devices) { // We first need to determine the "initial" state that will be used when reverting @@ -279,15 +291,11 @@ namespace display_device::win_utils { DdGuardFn primaryGuardFn(WinDisplayDeviceInterface &win_dd, const ActiveTopology &topology) { - std::string primary_device {}; - const auto flat_topology { flattenTopology(topology) }; - for (const auto &device_id : flat_topology) { - if (win_dd.isPrimary(device_id)) { - primary_device = device_id; - break; - } - } + return primaryGuardFn(win_dd, getPrimaryDevice(win_dd, topology)); + } + DdGuardFn + primaryGuardFn(WinDisplayDeviceInterface &win_dd, const std::string &primary_device) { DD_LOG(debug) << "Got primary device in primaryGuardFn:\n" << toJson(primary_device); return [&win_dd, primary_device]() { diff --git a/tests/unit/windows/test_settingsmanagerapply.cpp b/tests/unit/windows/test_settingsmanagerapply.cpp index 2e4768d..46771fd 100644 --- a/tests/unit/windows/test_settingsmanagerapply.cpp +++ b/tests/unit/windows/test_settingsmanagerapply.cpp @@ -140,6 +140,30 @@ namespace { } } + void + expectedIsPrimaryCall(InSequence &sequence /* To ensure that sequence is created outside this scope */, const std::string &device_id, const bool success = true) { + EXPECT_CALL(*m_dd_api, isPrimary(device_id)) + .Times(1) + .WillOnce(Return(success)) + .RetiresOnSaturation(); + } + + void + expectedSetAsPrimaryCall(InSequence &sequence /* To ensure that sequence is created outside this scope */, const std::string &device_id, const bool success = true) { + EXPECT_CALL(*m_dd_api, setAsPrimary(device_id)) + .Times(1) + .WillOnce(Return(success)) + .RetiresOnSaturation(); + } + + void + expectedPrimaryGuardCall(InSequence &sequence /* To ensure that sequence is created outside this scope */, const std::string &device_id) { + EXPECT_CALL(*m_dd_api, setAsPrimary(device_id)) + .Times(1) + .WillOnce(Return(true)) + .RetiresOnSaturation(); + } + std::shared_ptr> m_dd_api { std::make_shared>() }; std::shared_ptr> m_settings_persistence_api { std::make_shared>() }; std::shared_ptr> m_audio_context_api { std::make_shared>() }; @@ -380,6 +404,244 @@ TEST_F_S_MOCKED(PrepareTopology, AudioContextCaptureSkipped, NoDevicesAreGone) { EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId4", .m_device_prep = DevicePrep::EnsureActive }), display_device::SettingsManager::ApplyResult::Ok); } +TEST_F_S_MOCKED(PreparePrimaryDevice, FailedToGetPrimaryDevice) { + using DevicePrep = display_device::SingleDisplayConfiguration::DevicePreparation; + const display_device::ActiveTopology topology { { "DeviceId1", "DeviceId2" }, { "DeviceId3" }, { "DeviceId4" } }; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, topology); + expectedIsCapturedCall(sequence, false); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + expectedSetTopologyCall(sequence, topology); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, topology); + + expectedIsPrimaryCall(sequence, "DeviceId1", false); + expectedIsPrimaryCall(sequence, "DeviceId2", false); + expectedIsPrimaryCall(sequence, "DeviceId3", false); + expectedIsPrimaryCall(sequence, "DeviceId4", false); + + expectedTopologyGuardTopologyCall(sequence); + expectedTopologyGuardNewlyCapturedContextCall(sequence, false); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId4", .m_device_prep = DevicePrep::EnsurePrimary }), display_device::SettingsManager::ApplyResult::PrimaryDevicePrepFailed); +} + +TEST_F_S_MOCKED(PreparePrimaryDevice, FailedToSetPrimaryDevice) { + using DevicePrep = display_device::SingleDisplayConfiguration::DevicePreparation; + const display_device::ActiveTopology topology { { "DeviceId1", "DeviceId2" }, { "DeviceId3" }, { "DeviceId4" } }; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, topology); + expectedIsCapturedCall(sequence, false); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + expectedSetTopologyCall(sequence, topology); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, topology); + + expectedIsPrimaryCall(sequence, "DeviceId1"); + expectedSetAsPrimaryCall(sequence, "DeviceId4", false); + + expectedTopologyGuardTopologyCall(sequence); + expectedTopologyGuardNewlyCapturedContextCall(sequence, false); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId4", .m_device_prep = DevicePrep::EnsurePrimary }), display_device::SettingsManager::ApplyResult::PrimaryDevicePrepFailed); +} + +TEST_F_S_MOCKED(PreparePrimaryDevice, PrimaryDeviceSet) { + using DevicePrep = display_device::SingleDisplayConfiguration::DevicePreparation; + auto persistence_input { DEFAULT_PERSISTENCE_INPUT_BASE }; + persistence_input.m_modified.m_topology = { { "DeviceId1", "DeviceId2" }, { "DeviceId3" }, { "DeviceId4" } }; + persistence_input.m_modified.m_original_primary_device = "DeviceId1"; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, persistence_input.m_modified.m_topology); + expectedIsCapturedCall(sequence, false); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + expectedSetTopologyCall(sequence, persistence_input.m_modified.m_topology); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, persistence_input.m_modified.m_topology); + + expectedIsPrimaryCall(sequence, "DeviceId1"); + expectedSetAsPrimaryCall(sequence, "DeviceId4"); + expectedPersistenceCall(sequence, persistence_input); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId4", .m_device_prep = DevicePrep::EnsurePrimary }), display_device::SettingsManager::ApplyResult::Ok); +} + +TEST_F_S_MOCKED(PreparePrimaryDevice, PrimaryDeviceSet, CachedDeviceReused) { + using DevicePrep = display_device::SingleDisplayConfiguration::DevicePreparation; + auto initial_state { DEFAULT_PERSISTENCE_INPUT_BASE }; + initial_state.m_modified.m_topology = { { "DeviceId1", "DeviceId2" }, { "DeviceId3" }, { "DeviceId4" } }; + initial_state.m_modified.m_original_primary_device = "DeviceId1"; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence, initial_state.m_modified.m_topology, initial_state); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, initial_state.m_modified.m_topology, initial_state.m_modified.m_topology); + + expectedIsPrimaryCall(sequence, "DeviceId1", false); + expectedIsPrimaryCall(sequence, "DeviceId2", false); + expectedIsPrimaryCall(sequence, "DeviceId3"); + expectedSetAsPrimaryCall(sequence, "DeviceId4"); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId4", .m_device_prep = DevicePrep::EnsurePrimary }), display_device::SettingsManager::ApplyResult::Ok); +} + +TEST_F_S_MOCKED(PreparePrimaryDevice, PrimaryDeviceSet, GuardInvoked) { + using DevicePrep = display_device::SingleDisplayConfiguration::DevicePreparation; + auto persistence_input { DEFAULT_PERSISTENCE_INPUT_BASE }; + persistence_input.m_modified.m_topology = { { "DeviceId1", "DeviceId2" }, { "DeviceId3" }, { "DeviceId4" } }; + persistence_input.m_modified.m_original_primary_device = "DeviceId1"; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, persistence_input.m_modified.m_topology); + expectedIsCapturedCall(sequence, false); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + expectedSetTopologyCall(sequence, persistence_input.m_modified.m_topology); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, persistence_input.m_modified.m_topology); + + expectedIsPrimaryCall(sequence, "DeviceId1"); + expectedSetAsPrimaryCall(sequence, "DeviceId4"); + expectedPersistenceCall(sequence, persistence_input, false); + + expectedPrimaryGuardCall(sequence, "DeviceId1"); + expectedTopologyGuardTopologyCall(sequence); + expectedTopologyGuardNewlyCapturedContextCall(sequence, false); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId4", .m_device_prep = DevicePrep::EnsurePrimary }), display_device::SettingsManager::ApplyResult::PersistenceSaveFailed); +} + +TEST_F_S_MOCKED(PreparePrimaryDevice, PrimaryDeviceSetSkipped) { + using DevicePrep = display_device::SingleDisplayConfiguration::DevicePreparation; + auto persistence_input { DEFAULT_PERSISTENCE_INPUT_BASE }; + persistence_input.m_modified.m_topology = { { "DeviceId1", "DeviceId2" }, { "DeviceId3" }, { "DeviceId4" } }; + persistence_input.m_modified.m_original_primary_device = "DeviceId4"; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, persistence_input.m_modified.m_topology); + expectedIsCapturedCall(sequence, false); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, DEFAULT_CURRENT_TOPOLOGY); + expectedSetTopologyCall(sequence, persistence_input.m_modified.m_topology); + expectedIsTopologyTheSameCall(sequence, DEFAULT_CURRENT_TOPOLOGY, persistence_input.m_modified.m_topology); + + expectedIsPrimaryCall(sequence, "DeviceId1", false); + expectedIsPrimaryCall(sequence, "DeviceId2", false); + expectedIsPrimaryCall(sequence, "DeviceId3", false); + expectedIsPrimaryCall(sequence, "DeviceId4"); + expectedPersistenceCall(sequence, persistence_input, false); + + expectedTopologyGuardTopologyCall(sequence); + expectedTopologyGuardNewlyCapturedContextCall(sequence, false); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId4", .m_device_prep = DevicePrep::EnsurePrimary }), display_device::SettingsManager::ApplyResult::PersistenceSaveFailed); +} + +TEST_F_S_MOCKED(PreparePrimaryDevice, FailedToRestorePrimaryDevice) { + using DevicePrep = display_device::SingleDisplayConfiguration::DevicePreparation; + auto intial_state { ut_consts::SDCS_NO_MODIFICATIONS }; + intial_state->m_modified.m_original_primary_device = "DeviceId1"; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence, intial_state->m_modified.m_topology, intial_state); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, intial_state->m_modified.m_topology, intial_state->m_modified.m_topology); + + expectedIsPrimaryCall(sequence, "DeviceId1", false); + expectedIsPrimaryCall(sequence, "DeviceId3"); + expectedSetAsPrimaryCall(sequence, "DeviceId1", false); + + expectedTopologyGuardTopologyCall(sequence, intial_state->m_modified.m_topology); + expectedTopologyGuardNewlyCapturedContextCall(sequence, false); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId3", .m_device_prep = DevicePrep::EnsureActive }), display_device::SettingsManager::ApplyResult::PrimaryDevicePrepFailed); +} + +TEST_F_S_MOCKED(PreparePrimaryDevice, PrimaryDeviceRestored) { + using DevicePrep = display_device::SingleDisplayConfiguration::DevicePreparation; + auto intial_state { ut_consts::SDCS_NO_MODIFICATIONS }; + intial_state->m_modified.m_original_primary_device = "DeviceId1"; + + auto persistence_input { intial_state }; + persistence_input->m_modified.m_original_primary_device = ""; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence, intial_state->m_modified.m_topology, intial_state); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, intial_state->m_modified.m_topology, intial_state->m_modified.m_topology); + + expectedIsPrimaryCall(sequence, "DeviceId1", false); + expectedIsPrimaryCall(sequence, "DeviceId3"); + expectedSetAsPrimaryCall(sequence, "DeviceId1", true); + expectedPersistenceCall(sequence, persistence_input); + + 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) { + using DevicePrep = display_device::SingleDisplayConfiguration::DevicePreparation; + auto intial_state { ut_consts::SDCS_NO_MODIFICATIONS }; + intial_state->m_modified.m_original_primary_device = "DeviceId1"; + + auto persistence_input { intial_state }; + persistence_input->m_modified.m_original_primary_device = ""; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence, intial_state->m_modified.m_topology, intial_state); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, intial_state->m_modified.m_topology, intial_state->m_modified.m_topology); + + expectedIsPrimaryCall(sequence, "DeviceId1", false); + expectedIsPrimaryCall(sequence, "DeviceId3"); + expectedSetAsPrimaryCall(sequence, "DeviceId1", true); + expectedPersistenceCall(sequence, persistence_input, false); + + expectedPrimaryGuardCall(sequence, "DeviceId3"); + expectedTopologyGuardTopologyCall(sequence, intial_state->m_modified.m_topology); + expectedTopologyGuardNewlyCapturedContextCall(sequence, false); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId3", .m_device_prep = DevicePrep::EnsureActive }), display_device::SettingsManager::ApplyResult::PersistenceSaveFailed); +} + +TEST_F_S_MOCKED(PreparePrimaryDevice, PrimaryDeviceRestoreSkipped) { + using DevicePrep = display_device::SingleDisplayConfiguration::DevicePreparation; + auto intial_state { ut_consts::SDCS_NO_MODIFICATIONS }; + intial_state->m_modified.m_original_primary_device = "DeviceId1"; + + auto persistence_input { intial_state }; + persistence_input->m_modified.m_original_primary_device = ""; + + InSequence sequence; + expectedDefaultCallsUntilTopologyPrep(sequence, intial_state->m_modified.m_topology, intial_state); + expectedIsCapturedCall(sequence, false); + expectedDeviceEnumCall(sequence); + expectedIsTopologyTheSameCall(sequence, intial_state->m_modified.m_topology, intial_state->m_modified.m_topology); + + expectedIsPrimaryCall(sequence, "DeviceId1"); + expectedPersistenceCall(sequence, persistence_input, false); + + expectedTopologyGuardTopologyCall(sequence, intial_state->m_modified.m_topology); + expectedTopologyGuardNewlyCapturedContextCall(sequence, false); + + EXPECT_EQ(getImpl().applySettings({ .m_device_id = "DeviceId3", .m_device_prep = DevicePrep::EnsureActive }), 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_settingsmanagerrevert.cpp b/tests/unit/windows/test_settingsmanagerrevert.cpp index 77a0c23..31b64c3 100644 --- a/tests/unit/windows/test_settingsmanagerrevert.cpp +++ b/tests/unit/windows/test_settingsmanagerrevert.cpp @@ -19,14 +19,14 @@ namespace { // Additional convenience global const(s) const display_device::ActiveTopology CURRENT_TOPOLOGY { { "DeviceId4" } }; const display_device::HdrStateMap CURRENT_MODIFIED_HDR_STATES { - { "DeviceId2", { display_device::HdrState::Enabled } }, + { "DeviceId1", { display_device::HdrState::Enabled } }, { "DeviceId3", std::nullopt } }; const display_device::DeviceDisplayModeMap CURRENT_MODIFIED_DISPLAY_MODES { - { "DeviceId2", { { 123, 456 }, { 120, 1 } } }, + { "DeviceId1", { { 123, 456 }, { 120, 1 } } }, { "DeviceId3", { { 456, 123 }, { 60, 1 } } } }; - const std::string CURRENT_MODIFIED_PRIMARY_DEVICE { "DeviceId2" }; + const std::string CURRENT_MODIFIED_PRIMARY_DEVICE { "DeviceId1" }; // Test fixture(s) for this file class SettingsManagerRevertMocked: public BaseTest { @@ -301,7 +301,6 @@ TEST_F_S_MOCKED(RevertModifiedSettings, FailedToRevertHdrStates) { .WillOnce(Return(false)) .RetiresOnSaturation(); - expectedDefaultHdrStateGuardCall(sequence); expectedDefaultTopologyGuardCall(sequence); EXPECT_FALSE(getImpl().revertSettings()); @@ -321,7 +320,6 @@ TEST_F_S_MOCKED(RevertModifiedSettings, FailedToRevertDisplayModes) { .WillOnce(Return(false)) .RetiresOnSaturation(); - expectedDefaultDisplayModeGuardCall(sequence); expectedDefaultTopologyGuardCall(sequence); EXPECT_FALSE(getImpl().revertSettings()); @@ -341,7 +339,6 @@ TEST_F_S_MOCKED(RevertModifiedSettings, FailedToRevertPrimaryDevice) { .WillOnce(Return(false)) .RetiresOnSaturation(); - expectedDefaultPrimaryDeviceGuardCall(sequence); expectedDefaultTopologyGuardCall(sequence); EXPECT_FALSE(getImpl().revertSettings()); diff --git a/tests/unit/windows/test_settingsutils.cpp b/tests/unit/windows/test_settingsutils.cpp index 3ed87d3..eaf609c 100644 --- a/tests/unit/windows/test_settingsutils.cpp +++ b/tests/unit/windows/test_settingsutils.cpp @@ -245,7 +245,7 @@ TEST_F_S_MOCKED(PrimaryGuardFn, Success) { .WillOnce(Return(true)) .RetiresOnSaturation(); - const auto guard_fn { display_device::win_utils::primaryGuardFn(m_dd_api, { { "DeviceId1" } }) }; + const auto guard_fn { display_device::win_utils::primaryGuardFn(m_dd_api, display_device::ActiveTopology { { "DeviceId1" } }) }; EXPECT_NO_THROW(guard_fn()); } @@ -259,7 +259,7 @@ TEST_F_S_MOCKED(PrimaryGuardFn, Failure) { .WillOnce(Return(false)) .RetiresOnSaturation(); - const auto guard_fn { display_device::win_utils::primaryGuardFn(m_dd_api, { { "DeviceId1" } }) }; + const auto guard_fn { display_device::win_utils::primaryGuardFn(m_dd_api, display_device::ActiveTopology { { "DeviceId1" } }) }; EXPECT_NO_THROW(guard_fn()); } diff --git a/tests/unit/windows/utils/mockwindisplaydevice.cpp b/tests/unit/windows/utils/mockwindisplaydevice.cpp index 3728c62..16adba8 100644 --- a/tests/unit/windows/utils/mockwindisplaydevice.cpp +++ b/tests/unit/windows/utils/mockwindisplaydevice.cpp @@ -10,12 +10,12 @@ namespace ut_consts { { { { "DeviceId1" } }, { "DeviceId1" } }, { display_device::SingleDisplayConfigState::Modified { - { { "DeviceId2" }, { "DeviceId3" } }, - { { "DeviceId2", { { 1920, 1080 }, { 120, 1 } } }, + { { "DeviceId1" }, { "DeviceId3" } }, + { { "DeviceId1", { { 1920, 1080 }, { 120, 1 } } }, { "DeviceId3", { { 1920, 1080 }, { 60, 1 } } } }, - { { "DeviceId2", { display_device::HdrState::Disabled } }, - { "DeviceId3", std::nullopt } }, - { "DeviceId3" }, + { { "DeviceId1", { display_device::HdrState::Disabled } }, + { "DeviceId3", display_device::HdrState::Enabled } }, + { "DeviceId1" }, } } }; @@ -26,7 +26,7 @@ namespace ut_consts { { { { "DeviceId1" } }, { "DeviceId1" } }, { display_device::SingleDisplayConfigState::Modified { - { { "DeviceId2" }, { "DeviceId3" } } } } + { { "DeviceId1" }, { "DeviceId3" } } } } }; return state;