diff --git a/src/common/include/display_device/detail/json_serializer_details.h b/src/common/include/display_device/detail/json_serializer_details.h index a5ab6ab..cd41c8b 100644 --- a/src/common/include/display_device/detail/json_serializer_details.h +++ b/src/common/include/display_device/detail/json_serializer_details.h @@ -109,7 +109,7 @@ namespace nlohmann { nlohmann_json_t = std::nullopt; } else { - nlohmann_json_t = nlohmann_json_j.template get(); + nlohmann_json_t = nlohmann_json_j.get(); } } }; @@ -138,5 +138,23 @@ namespace nlohmann { } } }; + + // Specialization for chrono duration. + template + struct adl_serializer> { + using NanoRep = decltype(std::chrono::nanoseconds {}.count()); + static_assert(std::numeric_limits::max() <= std::numeric_limits::max(), + "Duration support above nanoseconds have not been tested/verified yet!"); + + static void + to_json(json &nlohmann_json_j, const std::chrono::duration &nlohmann_json_t) { + nlohmann_json_j = nlohmann_json_t.count(); + } + + static void + from_json(const json &nlohmann_json_j, std::chrono::duration &nlohmann_json_t) { + nlohmann_json_t = std::chrono::duration { nlohmann_json_j.get() }; + } + }; } // namespace nlohmann #endif diff --git a/src/common/include/display_device/json.h b/src/common/include/display_device/json.h index ffb2ca8..e22d9b7 100644 --- a/src/common/include/display_device/json.h +++ b/src/common/include/display_device/json.h @@ -25,6 +25,7 @@ namespace display_device { extern const std::optional JSON_COMPACT; + DD_JSON_DECLARE_CONVERTER(EnumeratedDevice) DD_JSON_DECLARE_CONVERTER(EnumeratedDeviceList) DD_JSON_DECLARE_CONVERTER(SingleDisplayConfiguration) DD_JSON_DECLARE_CONVERTER(std::set) diff --git a/src/common/json.cpp b/src/common/json.cpp index 41d32d3..b997510 100644 --- a/src/common/json.cpp +++ b/src/common/json.cpp @@ -13,6 +13,7 @@ // clang-format on namespace display_device { + DD_JSON_DEFINE_CONVERTER(EnumeratedDevice) DD_JSON_DEFINE_CONVERTER(EnumeratedDeviceList) DD_JSON_DEFINE_CONVERTER(SingleDisplayConfiguration) DD_JSON_DEFINE_CONVERTER(std::set) diff --git a/src/windows/include/display_device/windows/detail/json_serializer.h b/src/windows/include/display_device/windows/detail/json_serializer.h index 3db4d8c..95d5e2b 100644 --- a/src/windows/include/display_device/windows/detail/json_serializer.h +++ b/src/windows/include/display_device/windows/detail/json_serializer.h @@ -14,5 +14,6 @@ namespace display_device { DD_JSON_DECLARE_SERIALIZE_TYPE(SingleDisplayConfigState::Initial) DD_JSON_DECLARE_SERIALIZE_TYPE(SingleDisplayConfigState::Modified) DD_JSON_DECLARE_SERIALIZE_TYPE(SingleDisplayConfigState) + DD_JSON_DECLARE_SERIALIZE_TYPE(WinWorkarounds) } // namespace display_device #endif diff --git a/src/windows/include/display_device/windows/json.h b/src/windows/include/display_device/windows/json.h index 9f0cec7..4ab32bf 100644 --- a/src/windows/include/display_device/windows/json.h +++ b/src/windows/include/display_device/windows/json.h @@ -14,4 +14,5 @@ namespace display_device { DD_JSON_DECLARE_CONVERTER(DeviceDisplayModeMap) DD_JSON_DECLARE_CONVERTER(HdrStateMap) DD_JSON_DECLARE_CONVERTER(SingleDisplayConfigState) + DD_JSON_DECLARE_CONVERTER(WinWorkarounds) } // namespace display_device diff --git a/src/windows/include/display_device/windows/settings_manager.h b/src/windows/include/display_device/windows/settings_manager.h index 5092fc1..9d955a2 100644 --- a/src/windows/include/display_device/windows/settings_manager.h +++ b/src/windows/include/display_device/windows/settings_manager.h @@ -5,7 +5,6 @@ #pragma once // system includes -#include #include // local includes @@ -25,11 +24,13 @@ namespace display_device { * @param dd_api A pointer to the Windows Display Device interface. Will throw on nullptr! * @param audio_context_api [Optional] A pointer to the Audio Context interface. * @param persistent_state A pointer to a class for managing persistence. + * @param workarounds Workaround settings for the APIs. */ explicit SettingsManager( std::shared_ptr dd_api, std::shared_ptr audio_context_api, - std::unique_ptr persistent_state); + std::unique_ptr persistent_state, + WinWorkarounds workarounds); /** For details @see SettingsManagerInterface::enumAvailableDevices */ [[nodiscard]] EnumeratedDeviceList @@ -115,9 +116,6 @@ namespace display_device { std::shared_ptr m_dd_api; std::shared_ptr m_audio_context_api; std::unique_ptr m_persistence_state; - - private: - /** @see win_utils::blankHdrStates for more details. */ - std::chrono::milliseconds m_hdr_blank_delay { 500 }; // 500ms should be more than enough... + WinWorkarounds m_workarounds; }; } // namespace display_device diff --git a/src/windows/include/display_device/windows/settings_utils.h b/src/windows/include/display_device/windows/settings_utils.h index 410770b..e9fcac9 100644 --- a/src/windows/include/display_device/windows/settings_utils.h +++ b/src/windows/include/display_device/windows/settings_utils.h @@ -165,9 +165,10 @@ namespace display_device::win_utils { * * @param win_dd Interface for interacting with the OS. * @param delay Delay between OFF and ON states (ON -> OFF -> DELAY -> ON). + * If null optional is provided, the function does nothing. */ void - blankHdrStates(WinDisplayDeviceInterface &win_dd, std::chrono::milliseconds delay); + blankHdrStates(WinDisplayDeviceInterface &win_dd, const std::optional &delay); /** * @brief Make guard function for the topology. diff --git a/src/windows/include/display_device/windows/types.h b/src/windows/include/display_device/windows/types.h index 1d1ff1b..2fad0b4 100644 --- a/src/windows/include/display_device/windows/types.h +++ b/src/windows/include/display_device/windows/types.h @@ -11,6 +11,7 @@ #include // system includes +#include #include #include #include @@ -171,4 +172,17 @@ namespace display_device { * @brief Default function type used for cleanup/guard functions. */ using DdGuardFn = std::function; + + /** + * @brief Settings for workarounds/hacks for Windows. + */ + struct WinWorkarounds { + std::optional m_hdr_blank_delay { std::nullopt }; ///< @seealso{win_utils::blankHdrStates for more details.} + + /** + * @brief Comparator for strict equality. + */ + friend bool + operator==(const WinWorkarounds &lhs, const WinWorkarounds &rhs); + }; } // namespace display_device diff --git a/src/windows/json.cpp b/src/windows/json.cpp index d476aca..44b49d9 100644 --- a/src/windows/json.cpp +++ b/src/windows/json.cpp @@ -19,4 +19,5 @@ namespace display_device { DD_JSON_DEFINE_CONVERTER(DeviceDisplayModeMap) DD_JSON_DEFINE_CONVERTER(HdrStateMap) DD_JSON_DEFINE_CONVERTER(SingleDisplayConfigState) + DD_JSON_DEFINE_CONVERTER(WinWorkarounds) } // namespace display_device diff --git a/src/windows/json_serializer.cpp b/src/windows/json_serializer.cpp index 63649b3..c553dca 100644 --- a/src/windows/json_serializer.cpp +++ b/src/windows/json_serializer.cpp @@ -15,4 +15,5 @@ namespace display_device { DD_JSON_DEFINE_SERIALIZE_STRUCT(SingleDisplayConfigState::Initial, topology, primary_devices) DD_JSON_DEFINE_SERIALIZE_STRUCT(SingleDisplayConfigState::Modified, topology, original_modes, original_hdr_states, original_primary_device) DD_JSON_DEFINE_SERIALIZE_STRUCT(SingleDisplayConfigState, initial, modified) + DD_JSON_DEFINE_SERIALIZE_STRUCT(WinWorkarounds, hdr_blank_delay) } // namespace display_device diff --git a/src/windows/settings_manager_apply.cpp b/src/windows/settings_manager_apply.cpp index bd7d017..e97034e 100644 --- a/src/windows/settings_manager_apply.cpp +++ b/src/windows/settings_manager_apply.cpp @@ -47,7 +47,7 @@ namespace display_device { bool system_settings_touched { false }; boost::scope::scope_exit hdr_blank_always_executed_guard { [this, &system_settings_touched]() { if (system_settings_touched) { - win_utils::blankHdrStates(*m_dd_api, m_hdr_blank_delay); + win_utils::blankHdrStates(*m_dd_api, m_workarounds.m_hdr_blank_delay); } } }; diff --git a/src/windows/settings_manager_general.cpp b/src/windows/settings_manager_general.cpp index 3da6ecc..4a2ba4b 100644 --- a/src/windows/settings_manager_general.cpp +++ b/src/windows/settings_manager_general.cpp @@ -8,15 +8,18 @@ // local includes #include "display_device/logging.h" #include "display_device/noop_audio_context.h" +#include "display_device/windows/json.h" namespace display_device { SettingsManager::SettingsManager( std::shared_ptr dd_api, std::shared_ptr audio_context_api, - std::unique_ptr persistent_state): + std::unique_ptr persistent_state, + WinWorkarounds workarounds): m_dd_api { std::move(dd_api) }, m_audio_context_api { std::move(audio_context_api) }, - m_persistence_state { std::move(persistent_state) } { + m_persistence_state { std::move(persistent_state) }, + m_workarounds { std::move(workarounds) } { if (!m_dd_api) { throw std::logic_error { "Nullptr provided for WinDisplayDeviceInterface in SettingsManager!" }; } @@ -28,6 +31,9 @@ namespace display_device { if (!m_persistence_state) { throw std::logic_error { "Nullptr provided for PersistentState in SettingsManager!" }; } + + DD_LOG(info) << "Provided workaround settings for SettingsManager:\n" + << toJson(m_workarounds); } EnumeratedDeviceList diff --git a/src/windows/settings_manager_revert.cpp b/src/windows/settings_manager_revert.cpp index a8b4711..22f9b1f 100644 --- a/src/windows/settings_manager_revert.cpp +++ b/src/windows/settings_manager_revert.cpp @@ -47,7 +47,7 @@ namespace display_device { bool system_settings_touched { false }; boost::scope::scope_exit hdr_blank_always_executed_guard { [this, &system_settings_touched]() { if (system_settings_touched) { - win_utils::blankHdrStates(*m_dd_api, m_hdr_blank_delay); + win_utils::blankHdrStates(*m_dd_api, m_workarounds.m_hdr_blank_delay); } } }; boost::scope::scope_exit topology_prep_guard { [this, &cached_state, ¤t_topology, &system_settings_touched]() { diff --git a/src/windows/settings_utils.cpp b/src/windows/settings_utils.cpp index c6bbb3a..d1f14c2 100644 --- a/src/windows/settings_utils.cpp +++ b/src/windows/settings_utils.cpp @@ -358,7 +358,11 @@ namespace display_device::win_utils { } void - blankHdrStates(WinDisplayDeviceInterface &win_dd, const std::chrono::milliseconds delay) { + blankHdrStates(WinDisplayDeviceInterface &win_dd, const std::optional &delay) { + if (!delay) { + return; + } + const auto topology { win_dd.getCurrentTopology() }; if (!win_dd.isTopologyValid(topology)) { DD_LOG(error) << "Got an invalid topology while trying to blank HDR states!"; @@ -390,13 +394,13 @@ namespace display_device::win_utils { return; } - DD_LOG(info) << "Applying HDR state \"blank\" workaround (" << delay.count() << "ms) to devices: " << toJson(device_ids, JSON_COMPACT); + DD_LOG(info) << "Applying HDR state \"blank\" workaround (" << delay->count() << "ms) to devices: " << toJson(device_ids, JSON_COMPACT); if (!win_dd.setHdrStates(inverse_states)) { DD_LOG(error) << "Failed to apply inverse HDR states during \"blank\"!"; return; } - std::this_thread::sleep_for(delay); + std::this_thread::sleep_for(*delay); if (!win_dd.setHdrStates(original_states)) { DD_LOG(error) << "Failed to apply original HDR states during \"blank\"!"; } diff --git a/src/windows/types.cpp b/src/windows/types.cpp index b33655e..44d0708 100644 --- a/src/windows/types.cpp +++ b/src/windows/types.cpp @@ -30,4 +30,9 @@ namespace display_device { operator==(const SingleDisplayConfigState &lhs, const SingleDisplayConfigState &rhs) { return lhs.m_initial == rhs.m_initial && lhs.m_modified == rhs.m_modified; } + + bool + operator==(const WinWorkarounds &lhs, const WinWorkarounds &rhs) { + return lhs.m_hdr_blank_delay == rhs.m_hdr_blank_delay; + } } // namespace display_device diff --git a/tests/unit/general/test_json.cpp b/tests/unit/general/test_json.cpp index bc27668..8764e98 100644 --- a/tests/unit/general/test_json.cpp +++ b/tests/unit/general/test_json.cpp @@ -44,11 +44,25 @@ namespace display_device { DD_JSON_DEFINE_CONVERTER(TestEnum) DD_JSON_DEFINE_CONVERTER(TestStruct) DD_JSON_DEFINE_CONVERTER(TestVariant) + DD_JSON_DEFINE_CONVERTER(std::chrono::nanoseconds) + DD_JSON_DEFINE_CONVERTER(std::chrono::microseconds) + DD_JSON_DEFINE_CONVERTER(std::chrono::milliseconds) + DD_JSON_DEFINE_CONVERTER(std::chrono::seconds) + DD_JSON_DEFINE_CONVERTER(std::chrono::minutes) + DD_JSON_DEFINE_CONVERTER(std::chrono::hours) + DD_JSON_DEFINE_CONVERTER(std::chrono::days) + DD_JSON_DEFINE_CONVERTER(std::chrono::weeks) + DD_JSON_DEFINE_CONVERTER(std::chrono::months) + DD_JSON_DEFINE_CONVERTER(std::chrono::years) } // namespace display_device namespace { // Specialized TEST macro(s) for this test file #define TEST_S(...) DD_MAKE_TEST(TEST, JsonTest, __VA_ARGS__) + + // Additional convenience global const(s) + constexpr auto MIN_NANO_VAL { std::numeric_limits::min() }; + constexpr auto MAX_NANO_VAL { std::numeric_limits::max() }; } // namespace TEST_S(ToJson, NoError, WithSuccessParam) { @@ -192,3 +206,55 @@ TEST_S(FromJson, TestVariant, UnknownVariantType) { EXPECT_FALSE(display_device::fromJson(R"({"type":"SomeUnknownType","value":123.0})", variant, &error_message)); EXPECT_EQ(error_message, "Could not parse variant from type SomeUnknownType!"); } + +TEST_S(ToJson, ChronoDuration) { + using namespace std::chrono; + + EXPECT_EQ(display_device::toJson(nanoseconds { 2000000000 }, std::nullopt, nullptr), R"(2000000000)"); + EXPECT_EQ(display_device::toJson(microseconds { 2000000 }, std::nullopt, nullptr), R"(2000000)"); + EXPECT_EQ(display_device::toJson(milliseconds { 2000 }, std::nullopt, nullptr), R"(2000)"); + EXPECT_EQ(display_device::toJson(seconds { 2 }, std::nullopt, nullptr), R"(2)"); + EXPECT_EQ(display_device::toJson(minutes { 20 }, std::nullopt, nullptr), R"(20)"); + EXPECT_EQ(display_device::toJson(hours { 20 }, std::nullopt, nullptr), R"(20)"); + EXPECT_EQ(display_device::toJson(days { 20 }, std::nullopt, nullptr), R"(20)"); + EXPECT_EQ(display_device::toJson(weeks { 20 }, std::nullopt, nullptr), R"(20)"); + EXPECT_EQ(display_device::toJson(months { 20 }, std::nullopt, nullptr), R"(20)"); + EXPECT_EQ(display_device::toJson(years { 20 }, std::nullopt, nullptr), R"(20)"); +} + +TEST_S(FromJson, ChronoDuration) { + const auto doTest { [](const std::string &string_input, T expected_value) { + T value {}; + + EXPECT_TRUE(display_device::fromJson(string_input, value, nullptr)); + EXPECT_EQ(value, expected_value); + } }; + + using namespace std::chrono; + + doTest(R"(2000000000)", nanoseconds { 2000000000 }); + doTest(R"(2000000)", microseconds { 2000000 }); + doTest(R"(2000)", milliseconds { 2000 }); + doTest(R"(2)", seconds { 2 }); + doTest(R"(20)", minutes { 20 }); + doTest(R"(20)", hours { 20 }); + doTest(R"(20)", days { 20 }); + doTest(R"(20)", weeks { 20 }); + doTest(R"(20)", months { 20 }); + doTest(R"(20)", years { 20 }); +} + +TEST_S(ToJson, ChronoDuration, Ranges) { + EXPECT_EQ(display_device::toJson(std::chrono::nanoseconds { MIN_NANO_VAL }, std::nullopt, nullptr), std::to_string(MIN_NANO_VAL)); + EXPECT_EQ(display_device::toJson(std::chrono::nanoseconds { MAX_NANO_VAL }, std::nullopt, nullptr), std::to_string(MAX_NANO_VAL)); +} + +TEST_S(FromJson, ChronoDuration, Ranges) { + std::chrono::nanoseconds value {}; + + EXPECT_TRUE(display_device::fromJson(std::to_string(MIN_NANO_VAL), value, nullptr)); + EXPECT_EQ(value, std::chrono::nanoseconds { MIN_NANO_VAL }); + + EXPECT_TRUE(display_device::fromJson(std::to_string(MAX_NANO_VAL), value, nullptr)); + EXPECT_EQ(value, std::chrono::nanoseconds { MAX_NANO_VAL }); +} diff --git a/tests/unit/general/test_json_converter.cpp b/tests/unit/general/test_json_converter.cpp index b4f0f72..22d642d 100644 --- a/tests/unit/general/test_json_converter.cpp +++ b/tests/unit/general/test_json_converter.cpp @@ -6,6 +6,37 @@ namespace { #define TEST_F_S(...) DD_MAKE_TEST(TEST_F, JsonConverterTest, __VA_ARGS__) } // namespace +TEST_F_S(EnumeratedDevice) { + display_device::EnumeratedDevice item_1 { + "ID_1", + "NAME_2", + "FU_NAME_3", + display_device::EnumeratedDevice::Info { + { 1920, 1080 }, + display_device::Rational { 175, 100 }, + 119.9554, + false, + { 1, 2 }, + display_device::HdrState::Enabled } + }; + display_device::EnumeratedDevice item_2 { + "ID_2", + "NAME_2", + "FU_NAME_2", + display_device::EnumeratedDevice::Info { + { 1920, 1080 }, + 1.75, + display_device::Rational { 1199554, 10000 }, + true, + { 0, 0 }, + display_device::HdrState::Disabled } + }; + + executeTestCase(display_device::EnumeratedDevice {}, R"({"device_id":"","display_name":"","friendly_name":"","info":null})"); + executeTestCase(item_1, 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":{"type":"double","value":119.9554},"resolution":{"height":1080,"width":1920},"resolution_scale":{"type":"rational","value":{"denominator":100,"numerator":175}}}})"); + executeTestCase(item_2, 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":{"type":"rational","value":{"denominator":10000,"numerator":1199554}},"resolution":{"height":1080,"width":1920},"resolution_scale":{"type":"double","value":1.75}}})"); +} + TEST_F_S(EnumeratedDeviceList) { display_device::EnumeratedDevice item_1 { "ID_1", diff --git a/tests/unit/windows/test_json_converter.cpp b/tests/unit/windows/test_json_converter.cpp index e5bf280..d0f5fcc 100644 --- a/tests/unit/windows/test_json_converter.cpp +++ b/tests/unit/windows/test_json_converter.cpp @@ -40,3 +40,12 @@ TEST_F_S(SingleDisplayConfigState) { executeTestCase(display_device::SingleDisplayConfigState {}, R"({"initial":{"primary_devices":[],"topology":[]},"modified":{"original_hdr_states":{},"original_modes":{},"original_primary_device":"","topology":[]}})"); executeTestCase(valid_input, R"({"initial":{"primary_devices":["DeviceId1"],"topology":[["DeviceId1"]]},"modified":{"original_hdr_states":{"DeviceId2":"Disabled"},"original_modes":{"DeviceId2":{"refresh_rate":{"denominator":1,"numerator":120},"resolution":{"height":1080,"width":1920}}},"original_primary_device":"DeviceId2","topology":[["DeviceId2"]]}})"); } + +TEST_F_S(WinWorkarounds) { + display_device::WinWorkarounds input { + std::chrono::milliseconds { 500 } + }; + + executeTestCase(display_device::WinWorkarounds {}, R"({"hdr_blank_delay":null})"); + executeTestCase(input, R"({"hdr_blank_delay":500})"); +} diff --git a/tests/unit/windows/test_settings_manager_apply.cpp b/tests/unit/windows/test_settings_manager_apply.cpp index 5caf34f..982ffe1 100644 --- a/tests/unit/windows/test_settings_manager_apply.cpp +++ b/tests/unit/windows/test_settings_manager_apply.cpp @@ -47,7 +47,10 @@ namespace { display_device::SettingsManager & getImpl() { if (!m_impl) { - m_impl = std::make_unique(m_dd_api, m_audio_context_api, std::make_unique(m_settings_persistence_api)); + m_impl = std::make_unique(m_dd_api, m_audio_context_api, std::make_unique(m_settings_persistence_api), + display_device::WinWorkarounds { + .m_hdr_blank_delay = std::chrono::milliseconds { 123 } // Value is irrelevant for the tests + }); } return *m_impl; diff --git a/tests/unit/windows/test_settings_manager_general.cpp b/tests/unit/windows/test_settings_manager_general.cpp index 79ed5ad..a9e7ac5 100644 --- a/tests/unit/windows/test_settings_manager_general.cpp +++ b/tests/unit/windows/test_settings_manager_general.cpp @@ -23,7 +23,7 @@ namespace { display_device::SettingsManager & getImpl() { if (!m_impl) { - m_impl = std::make_unique(m_dd_api, m_audio_context_api, std::make_unique(m_settings_persistence_api)); + m_impl = std::make_unique(m_dd_api, m_audio_context_api, std::make_unique(m_settings_persistence_api), display_device::WinWorkarounds {}); } return *m_impl; @@ -42,7 +42,7 @@ namespace { } // namespace TEST_F_S_MOCKED(NullptrDisplayDeviceApiProvided) { - EXPECT_THAT([]() { const display_device::SettingsManager settings_manager(nullptr, nullptr, nullptr); }, + EXPECT_THAT([]() { const display_device::SettingsManager settings_manager(nullptr, nullptr, nullptr, {}); }, ThrowsMessage(HasSubstr("Nullptr provided for WinDisplayDeviceInterface in SettingsManager!"))); } @@ -53,12 +53,12 @@ TEST_F_S_MOCKED(NoopAudioContext) { using SettingsManager::SettingsManager; }; - const NakedSettingsManager settings_manager { m_dd_api, nullptr, std::make_unique(nullptr) }; + const NakedSettingsManager settings_manager { m_dd_api, nullptr, std::make_unique(nullptr), {} }; EXPECT_TRUE(std::dynamic_pointer_cast(settings_manager.m_audio_context_api) != nullptr); } TEST_F_S_MOCKED(NullptrPersistentStateProvided) { - EXPECT_THAT([this]() { const display_device::SettingsManager settings_manager(m_dd_api, nullptr, nullptr); }, + EXPECT_THAT([this]() { const display_device::SettingsManager settings_manager(m_dd_api, nullptr, nullptr, {}); }, ThrowsMessage(HasSubstr("Nullptr provided for PersistentState in SettingsManager!"))); } diff --git a/tests/unit/windows/test_settings_manager_revert.cpp b/tests/unit/windows/test_settings_manager_revert.cpp index c0783ca..a8ace9f 100644 --- a/tests/unit/windows/test_settings_manager_revert.cpp +++ b/tests/unit/windows/test_settings_manager_revert.cpp @@ -39,7 +39,10 @@ namespace { display_device::SettingsManager & getImpl() { if (!m_impl) { - m_impl = std::make_unique(m_dd_api, m_audio_context_api, std::make_unique(m_settings_persistence_api)); + m_impl = std::make_unique(m_dd_api, m_audio_context_api, std::make_unique(m_settings_persistence_api), + display_device::WinWorkarounds { + .m_hdr_blank_delay = std::chrono::milliseconds { 123 } // Value is irrelevant for the tests + }); } return *m_impl; diff --git a/tests/unit/windows/test_settings_utils.cpp b/tests/unit/windows/test_settings_utils.cpp index 4ab207b..b514584 100644 --- a/tests/unit/windows/test_settings_utils.cpp +++ b/tests/unit/windows/test_settings_utils.cpp @@ -363,6 +363,10 @@ TEST_F_S_MOCKED(BlankHdrStates, FailedToApplyOriginalStates) { EXPECT_NO_THROW(display_device::win_utils::blankHdrStates(m_dd_api, std::chrono::milliseconds(0))); } +TEST_F_S_MOCKED(BlankHdrStates, NullDelay) { + EXPECT_NO_THROW(display_device::win_utils::blankHdrStates(m_dd_api, std::nullopt)); +} + TEST_F_S_MOCKED(BlankHdrStates, Success) { const display_device::HdrStateMap initial_states { { "DeviceId1", { display_device::HdrState::Enabled } }, diff --git a/tests/unit/windows/test_win_playground.cpp b/tests/unit/windows/test_win_playground.cpp index 7fa1b0c..f999513 100644 --- a/tests/unit/windows/test_win_playground.cpp +++ b/tests/unit/windows/test_win_playground.cpp @@ -26,10 +26,20 @@ namespace { return BaseTest::getDefaultLogLevel().value_or(display_device::Logger::LogLevel::info); } - std::unique_ptr m_settings { std::make_unique( - std::make_shared(std::make_shared()), - nullptr, - std::make_unique(nullptr)) }; + display_device::SettingsManager & + getImpl(const display_device::WinWorkarounds &workarounds = {}) { + if (!m_impl) { + m_impl = std::make_unique( + std::make_shared(std::make_shared()), + nullptr, + std::make_unique(nullptr), workarounds); + } + + return *m_impl; + } + + private: + std::unique_ptr m_impl; }; // Specialized TEST macro(s) for this test file @@ -41,29 +51,42 @@ TEST_F_S(EnumAvailableDevices) { // test_libdisplaydevice.exe --gtest_color=yes --gtest_also_run_disabled_tests --gtest_filter=*WinPlayground.EnumAvailableDevices DD_LOG(info) << "enumerated devices:\n" - << toJson(m_settings->enumAvailableDevices()); + << toJson(getImpl().enumAvailableDevices()); } TEST_F_S(ApplySettings) { // Usage example: // test_libdisplaydevice.exe --gtest_color=yes --gtest_also_run_disabled_tests --gtest_filter=*WinPlayground.ApplySettings config='{\"device_id\":\"{77f67f3e-754f-5d31-af64-ee037e18100a}\",\"device_prep\":\"EnsureActive\",\"hdr_state\":null,\"refresh_rate\":null,\"resolution\":null}' + // + // With workarounds (optional): + // test_libdisplaydevice.exe --gtest_color=yes --gtest_also_run_disabled_tests --gtest_filter=*WinPlayground.ApplySettings config='...' workarounds='{\"hdr_blank_delay\":500}' - const auto arg { getArgWithMatchingPattern(R"(^config=)", true) }; - if (!arg) { + const auto config_arg { getArgWithMatchingPattern(R"(^config=)", true) }; + if (!config_arg) { GTEST_FAIL() << "\"config=\" argument not found!"; } std::string parse_error {}; display_device::SingleDisplayConfiguration config; - if (!fromJson(*arg, config, &parse_error)) { - GTEST_FAIL() << "Config argument could not be parsed!\nArgument:\n " << *arg << "\nError:\n " << parse_error; + if (!fromJson(*config_arg, config, &parse_error)) { + GTEST_FAIL() << "Config argument could not be parsed!\nArgument:\n " << *config_arg << "\nError:\n " << parse_error; + } + + if (const auto workarounds_arg { getArgWithMatchingPattern(R"(^workarounds=)", true) }) { + display_device::WinWorkarounds workarounds; + if (!fromJson(*workarounds_arg, workarounds, &parse_error)) { + GTEST_FAIL() << "Workarounds argument could not be parsed!\nArgument:\n " << *workarounds_arg << "\nError:\n " << parse_error; + } + + // Initialize the implementation + getImpl(workarounds); } - const boost::scope::scope_exit cleanup { [this]() { static_cast(m_settings->revertSettings()); } }; + const boost::scope::scope_exit cleanup { [this]() { static_cast(getImpl().revertSettings()); } }; std::cout << "Applying settings. Press enter to continue..." << std::endl; std::cin.get(); - if (m_settings->applySettings(config) != display_device::SettingsManagerInterface::ApplyResult::Ok) { + if (getImpl().applySettings(config) != display_device::SettingsManagerInterface::ApplyResult::Ok) { GTEST_FAIL() << "Failed to apply configuration!"; }