From c0d24ce575825a2045a320ae02993ff29497b2ef Mon Sep 17 00:00:00 2001 From: Lukas Senionis <warliukz@gmail.com> Date: Wed, 17 Jul 2024 19:06:47 +0300 Subject: [PATCH] feat: Use new FloatingPoint type (#62) Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> --- .../displaydevice/detail/jsonserializer.h | 1 + .../detail/jsonserializerdetails.h | 58 ++++++++++++++++++- src/common/include/displaydevice/types.h | 26 ++++++++- src/common/jsonserializer.cpp | 1 + src/common/types.cpp | 18 +++++- .../windows/detail/jsonserializer.h | 1 - .../displaydevice/windows/settingsutils.h | 2 +- .../include/displaydevice/windows/types.h | 20 ------- .../displaydevice/windows/winapilayer.h | 2 +- .../windows/winapilayerinterface.h | 2 +- src/windows/jsonserializer.cpp | 1 - src/windows/settingsutils.cpp | 20 ++++++- src/windows/types.cpp | 18 ------ src/windows/winapilayer.cpp | 5 +- src/windows/windisplaydevicegeneral.cpp | 10 ++-- .../include/fixtures/jsonconvertertest.h | 5 +- tests/unit/general/test_comparison.cpp | 22 +++++++ tests/unit/general/test_json.cpp | 26 +++++++++ tests/unit/general/test_jsonconverter.cpp | 14 ++--- tests/unit/windows/test_comparison.cpp | 6 -- tests/unit/windows/test_settingsutils.cpp | 20 ++++++- .../windows/test_windisplaydevicegeneral.cpp | 10 ++-- tests/unit/windows/utils/mockwinapilayer.h | 2 +- 23 files changed, 209 insertions(+), 81 deletions(-) diff --git a/src/common/include/displaydevice/detail/jsonserializer.h b/src/common/include/displaydevice/detail/jsonserializer.h index 23ee4f8..0db59f0 100644 --- a/src/common/include/displaydevice/detail/jsonserializer.h +++ b/src/common/include/displaydevice/detail/jsonserializer.h @@ -11,6 +11,7 @@ namespace display_device { // Structs DD_JSON_DECLARE_SERIALIZE_TYPE(Resolution) + DD_JSON_DECLARE_SERIALIZE_TYPE(Rational) DD_JSON_DECLARE_SERIALIZE_TYPE(Point) DD_JSON_DECLARE_SERIALIZE_TYPE(EnumeratedDevice::Info) DD_JSON_DECLARE_SERIALIZE_TYPE(EnumeratedDevice) diff --git a/src/common/include/displaydevice/detail/jsonserializerdetails.h b/src/common/include/displaydevice/detail/jsonserializerdetails.h index 7dadca2..c8e6763 100644 --- a/src/common/include/displaydevice/detail/jsonserializerdetails.h +++ b/src/common/include/displaydevice/detail/jsonserializerdetails.h @@ -43,9 +43,38 @@ } namespace display_device { + /** + * @brief Holds information for serializing variants. + */ + namespace detail { + template <class T> + struct JsonTypeName; + + template <> + struct JsonTypeName<double> { + static constexpr std::string_view m_name { "double" }; + }; + + template <> + struct JsonTypeName<Rational> { + static constexpr std::string_view m_name { "rational" }; + }; + + template <class T, class... Ts> + bool + variantFromJson(const nlohmann::json &nlohmann_json_j, std::variant<Ts...> &value) { + if (nlohmann_json_j.at("type").get<std::string_view>() != JsonTypeName<T>::m_name) { + return false; + } + + value = nlohmann_json_j.at("value").get<T>(); + return true; + } + } // namespace detail + // A shared function for enums to find values in the map. Extracted here for UTs + coverage template <class T, class Predicate> - std::map<T, nlohmann::json>::const_iterator + typename std::map<T, nlohmann::json>::const_iterator findInEnumMap(const char *error_msg, Predicate predicate) { const auto &map { getEnumMap(T {}) }; auto it { std::find_if(std::begin(map), std::end(map), predicate) }; @@ -58,7 +87,7 @@ namespace display_device { namespace nlohmann { // Specialization for optional types until they actually implement it. - template <typename T> + template <class T> struct adl_serializer<std::optional<T>> { static void to_json(json &nlohmann_json_j, const std::optional<T> &nlohmann_json_t) { @@ -80,5 +109,30 @@ namespace nlohmann { } } }; + + // Specialization for variant type. + // See https://github.com/nlohmann/json/issues/1261#issuecomment-2048770747 + template <typename... Ts> + struct adl_serializer<std::variant<Ts...>> { + static void + to_json(json &nlohmann_json_j, const std::variant<Ts...> &nlohmann_json_t) { + std::visit( + [&nlohmann_json_j]<class T>(const T &value) { + nlohmann_json_j["type"] = display_device::detail::JsonTypeName<std::decay_t<T>>::m_name; + nlohmann_json_j["value"] = value; + }, + nlohmann_json_t); + } + + static void + from_json(const json &nlohmann_json_j, std::variant<Ts...> &nlohmann_json_t) { + // Call variant_from_json for all types, only one will succeed + const bool found { (display_device::detail::variantFromJson<Ts>(nlohmann_json_j, nlohmann_json_t) || ...) }; + if (!found) { + const std::string error { "Could not parse variant from type " + nlohmann_json_j.at("type").get<std::string>() + "!" }; + throw std::runtime_error(error); + } + } + }; } // namespace nlohmann #endif diff --git a/src/common/include/displaydevice/types.h b/src/common/include/displaydevice/types.h index eb29fb3..850b0c4 100644 --- a/src/common/include/displaydevice/types.h +++ b/src/common/include/displaydevice/types.h @@ -3,6 +3,7 @@ // system includes #include <optional> #include <string> +#include <variant> #include <vector> namespace display_device { @@ -42,6 +43,25 @@ namespace display_device { operator==(const Point &lhs, const Point &rhs); }; + /** + * @brief Floating point stored in a "numerator/denominator" form. + */ + struct Rational { + unsigned int m_numerator {}; + unsigned int m_denominator {}; + + /** + * @brief Comparator for strict equality. + */ + friend bool + operator==(const Rational &lhs, const Rational &rhs); + }; + + /** + * @brief Floating point type. + */ + using FloatingPoint = std::variant<double, Rational>; + /** * @brief Enumerated display device information. */ @@ -51,8 +71,8 @@ namespace display_device { */ struct Info { Resolution m_resolution {}; /**< Resolution of an active device. */ - double m_resolution_scale {}; /**< Resolution scaling of an active device. */ - double m_refresh_rate {}; /**< Refresh rate of an active device. */ + FloatingPoint m_resolution_scale {}; /**< Resolution scaling of an active device. */ + FloatingPoint m_refresh_rate {}; /**< Refresh rate of an active device. */ bool m_primary {}; /**< Indicates whether the device is a primary display. */ Point m_origin_point {}; /**< A starting point of the display. */ std::optional<HdrState> m_hdr_state {}; /**< HDR of an active device. */ @@ -101,7 +121,7 @@ namespace display_device { std::string m_device_id {}; /**< Device to perform configuration for (can be empty if primary device should be used). */ DevicePreparation m_device_prep {}; /**< Instruction on how to prepare device. */ std::optional<Resolution> m_resolution {}; /**< Resolution to configure. */ - std::optional<double> m_refresh_rate {}; /**< Refresh rate to configure. */ + std::optional<FloatingPoint> m_refresh_rate {}; /**< Refresh rate to configure. */ std::optional<HdrState> m_hdr_state {}; /**< HDR state to configure (if supported by the display). */ /** diff --git a/src/common/jsonserializer.cpp b/src/common/jsonserializer.cpp index 97b4302..424d2c5 100644 --- a/src/common/jsonserializer.cpp +++ b/src/common/jsonserializer.cpp @@ -16,6 +16,7 @@ namespace display_device { // Structs DD_JSON_DEFINE_SERIALIZE_STRUCT(Resolution, width, height) + DD_JSON_DEFINE_SERIALIZE_STRUCT(Rational, numerator, denominator) DD_JSON_DEFINE_SERIALIZE_STRUCT(Point, x, y) DD_JSON_DEFINE_SERIALIZE_STRUCT(EnumeratedDevice::Info, resolution, resolution_scale, refresh_rate, primary, origin_point, hdr_state) DD_JSON_DEFINE_SERIALIZE_STRUCT(EnumeratedDevice, device_id, display_name, friendly_name, info) diff --git a/src/common/types.cpp b/src/common/types.cpp index f6901c8..1ea065a 100644 --- a/src/common/types.cpp +++ b/src/common/types.cpp @@ -1,4 +1,4 @@ -// local includes +// header include #include "displaydevice/types.h" namespace { @@ -6,9 +6,25 @@ namespace { fuzzyCompare(const double lhs, const double rhs) { return std::abs(lhs - rhs) * 1000000000000. <= std::min(std::abs(lhs), std::abs(rhs)); } + + bool + fuzzyCompare(const display_device::FloatingPoint &lhs, const display_device::FloatingPoint &rhs) { + if (lhs.index() == rhs.index()) { + if (std::holds_alternative<double>(lhs)) { + return fuzzyCompare(std::get<double>(lhs), std::get<double>(rhs)); + } + return lhs == rhs; + } + return false; + } } // namespace namespace display_device { + bool + operator==(const Rational &lhs, const Rational &rhs) { + return lhs.m_numerator == rhs.m_numerator && lhs.m_denominator == rhs.m_denominator; + } + bool operator==(const Point &lhs, const Point &rhs) { return lhs.m_x == rhs.m_x && lhs.m_y == rhs.m_y; diff --git a/src/windows/include/displaydevice/windows/detail/jsonserializer.h b/src/windows/include/displaydevice/windows/detail/jsonserializer.h index d1574ef..407f92e 100644 --- a/src/windows/include/displaydevice/windows/detail/jsonserializer.h +++ b/src/windows/include/displaydevice/windows/detail/jsonserializer.h @@ -6,7 +6,6 @@ #ifdef DD_JSON_DETAIL namespace display_device { // Structs - DD_JSON_DECLARE_SERIALIZE_TYPE(Rational) DD_JSON_DECLARE_SERIALIZE_TYPE(DisplayMode) DD_JSON_DECLARE_SERIALIZE_TYPE(SingleDisplayConfigState::Initial) DD_JSON_DECLARE_SERIALIZE_TYPE(SingleDisplayConfigState::Modified) diff --git a/src/windows/include/displaydevice/windows/settingsutils.h b/src/windows/include/displaydevice/windows/settingsutils.h index 53e017e..8fef529 100644 --- a/src/windows/include/displaydevice/windows/settingsutils.h +++ b/src/windows/include/displaydevice/windows/settingsutils.h @@ -112,7 +112,7 @@ namespace display_device::win_utils { */ DeviceDisplayModeMap computeNewDisplayModes(const std::optional<Resolution> &resolution, - const std::optional<double> &refresh_rate, + const std::optional<FloatingPoint> &refresh_rate, bool configuring_primary_devices, const std::string &device_to_configure, const std::set<std::string> &additional_devices_to_configure, diff --git a/src/windows/include/displaydevice/windows/types.h b/src/windows/include/displaydevice/windows/types.h index 477360e..ee6ea78 100644 --- a/src/windows/include/displaydevice/windows/types.h +++ b/src/windows/include/displaydevice/windows/types.h @@ -77,26 +77,6 @@ namespace display_device { */ using ActiveTopology = std::vector<std::vector<std::string>>; - /** - * @brief Floating point stored in a "numerator/denominator" form. - */ - struct Rational { - unsigned int m_numerator {}; - unsigned int m_denominator {}; - - /** - * @brief Contruct rational struct from double precission floating point. - */ - static Rational - fromFloatingPoint(double value); - - /** - * @brief Comparator for strict equality. - */ - friend bool - operator==(const Rational &lhs, const Rational &rhs); - }; - /** * @brief Display's mode (resolution + refresh rate). */ diff --git a/src/windows/include/displaydevice/windows/winapilayer.h b/src/windows/include/displaydevice/windows/winapilayer.h index 2b3e071..1cfa626 100644 --- a/src/windows/include/displaydevice/windows/winapilayer.h +++ b/src/windows/include/displaydevice/windows/winapilayer.h @@ -46,7 +46,7 @@ namespace display_device { setHdrState(const DISPLAYCONFIG_PATH_INFO &path, HdrState state) override; /** For details @see WinApiLayerInterface::getDisplayScale */ - [[nodiscard]] std::optional<double> + [[nodiscard]] std::optional<Rational> getDisplayScale(const std::string &display_name, const DISPLAYCONFIG_SOURCE_MODE &source_mode) const override; }; } // namespace display_device diff --git a/src/windows/include/displaydevice/windows/winapilayerinterface.h b/src/windows/include/displaydevice/windows/winapilayerinterface.h index 94d6aad..46acbb0 100644 --- a/src/windows/include/displaydevice/windows/winapilayerinterface.h +++ b/src/windows/include/displaydevice/windows/winapilayerinterface.h @@ -212,7 +212,7 @@ namespace display_device { * const auto scale = iface->getDisplayScale(iface->getDisplayName(path), source_mode); * ``` */ - [[nodiscard]] virtual std::optional<double> + [[nodiscard]] virtual std::optional<Rational> getDisplayScale(const std::string &display_name, const DISPLAYCONFIG_SOURCE_MODE &source_mode) const = 0; }; } // namespace display_device diff --git a/src/windows/jsonserializer.cpp b/src/windows/jsonserializer.cpp index ee9c9e3..019eaa8 100644 --- a/src/windows/jsonserializer.cpp +++ b/src/windows/jsonserializer.cpp @@ -7,7 +7,6 @@ namespace display_device { // Structs - DD_JSON_DEFINE_SERIALIZE_STRUCT(Rational, numerator, denominator) DD_JSON_DEFINE_SERIALIZE_STRUCT(DisplayMode, resolution, refresh_rate) 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) diff --git a/src/windows/settingsutils.cpp b/src/windows/settingsutils.cpp index af089ca..cbd6c88 100644 --- a/src/windows/settingsutils.cpp +++ b/src/windows/settingsutils.cpp @@ -3,6 +3,7 @@ // system includes #include <algorithm> +#include <cmath> #include <thread> // local includes @@ -264,7 +265,7 @@ namespace display_device::win_utils { } DeviceDisplayModeMap - computeNewDisplayModes(const std::optional<Resolution> &resolution, const std::optional<double> &refresh_rate, const bool configuring_primary_devices, const std::string &device_to_configure, const std::set<std::string> &additional_devices_to_configure, const DeviceDisplayModeMap &original_modes) { + computeNewDisplayModes(const std::optional<Resolution> &resolution, const std::optional<FloatingPoint> &refresh_rate, const bool configuring_primary_devices, const std::string &device_to_configure, const std::set<std::string> &additional_devices_to_configure, const DeviceDisplayModeMap &original_modes) { DeviceDisplayModeMap new_modes { original_modes }; if (resolution) { @@ -278,19 +279,32 @@ namespace display_device::win_utils { } if (refresh_rate) { + const auto from_floating_point { [](const FloatingPoint &value) { + if (const auto *rational_value { std::get_if<Rational>(&value) }; rational_value) { + return *rational_value; + } + + // It's hard to deal with floating values, so we just multiply it + // to keep 4 decimal places (if any) and let Windows deal with it! + // Genius idea if I'm being honest. + constexpr auto multiplier { static_cast<unsigned int>(std::pow(10, 4)) }; + const double transformed_value { std::round(std::get<double>(value) * multiplier) }; + return Rational { static_cast<unsigned int>(transformed_value), multiplier }; + } }; + if (configuring_primary_devices) { // No device has been specified, so if they're all are primary devices // we need to apply the refresh rate change to all duplicates. const auto devices { joinConfigurableDevices(device_to_configure, additional_devices_to_configure) }; for (const auto &device_id : devices) { - new_modes[device_id].m_refresh_rate = Rational::fromFloatingPoint(*refresh_rate); + new_modes[device_id].m_refresh_rate = from_floating_point(*refresh_rate); } } else { // Even if we have duplicate devices, their refresh rate may differ // and since the device was specified, let's apply the refresh // rate only to the specified device. - new_modes[device_to_configure].m_refresh_rate = Rational::fromFloatingPoint(*refresh_rate); + new_modes[device_to_configure].m_refresh_rate = from_floating_point(*refresh_rate); } } diff --git a/src/windows/types.cpp b/src/windows/types.cpp index 61b83c0..7ed9808 100644 --- a/src/windows/types.cpp +++ b/src/windows/types.cpp @@ -1,25 +1,7 @@ // header include #include "displaydevice/windows/types.h" -// system includes -#include <cmath> - namespace display_device { - Rational - Rational::fromFloatingPoint(const double value) { - // It's hard to deal with floating values, so we just multiply it - // to keep 4 decimal places (if any) and let Windows deal with it! - // Genius idea if I'm being honest. - constexpr auto multiplier { static_cast<unsigned int>(std::pow(10, 4)) }; - const double transformed_value { std::round(value * multiplier) }; - return Rational { static_cast<unsigned int>(transformed_value), multiplier }; - } - - bool - operator==(const Rational &lhs, const Rational &rhs) { - return lhs.m_numerator == rhs.m_numerator && lhs.m_denominator == rhs.m_denominator; - } - bool operator==(const DisplayMode &lhs, const DisplayMode &rhs) { return lhs.m_refresh_rate == rhs.m_refresh_rate && lhs.m_resolution == rhs.m_resolution; diff --git a/src/windows/winapilayer.cpp b/src/windows/winapilayer.cpp index 3d5b2f7..fcadf79 100644 --- a/src/windows/winapilayer.cpp +++ b/src/windows/winapilayer.cpp @@ -7,6 +7,7 @@ #include <boost/uuid/name_generator_sha1.hpp> #include <boost/uuid/uuid.hpp> #include <boost/uuid/uuid_io.hpp> +#include <cmath> #include <cstdint> #include <iomanip> @@ -575,7 +576,7 @@ namespace display_device { return true; } - std::optional<double> + std::optional<Rational> WinApiLayer::getDisplayScale(const std::string &display_name, const DISPLAYCONFIG_SOURCE_MODE &source_mode) const { // Note: implementation based on https://stackoverflow.com/a/74046173 struct EnumData { @@ -617,6 +618,6 @@ namespace display_device { } const auto width { static_cast<double>(*enum_data.m_width) / static_cast<double>(source_mode.width) }; - return static_cast<double>(GetDpiForSystem()) / 96. / width; + return Rational { static_cast<unsigned int>(std::round((static_cast<double>(GetDpiForSystem()) / 96. / width) * 100)), 100 }; } } // namespace display_device diff --git a/src/windows/windisplaydevicegeneral.cpp b/src/windows/windisplaydevicegeneral.cpp index 06c2a97..8b28c70 100644 --- a/src/windows/windisplaydevicegeneral.cpp +++ b/src/windows/windisplaydevicegeneral.cpp @@ -59,15 +59,15 @@ namespace display_device { } const auto display_name { m_w_api->getDisplayName(best_path) }; - const double refresh_rate { best_path.targetInfo.refreshRate.Denominator > 0 ? - static_cast<double>(best_path.targetInfo.refreshRate.Numerator) / static_cast<double>(best_path.targetInfo.refreshRate.Denominator) : - 0. }; + const Rational refresh_rate { best_path.targetInfo.refreshRate.Denominator > 0 ? + Rational { best_path.targetInfo.refreshRate.Numerator, best_path.targetInfo.refreshRate.Denominator } : + Rational { 0, 1 } }; const EnumeratedDevice::Info info { { source_mode->width, source_mode->height }, - m_w_api->getDisplayScale(display_name, *source_mode).value_or(0.), + m_w_api->getDisplayScale(display_name, *source_mode).value_or(Rational { 0, 1 }), refresh_rate, win_utils::isPrimary(*source_mode), - { source_mode->position.x, source_mode->position.y }, + { static_cast<int>(source_mode->position.x), static_cast<int>(source_mode->position.y) }, m_w_api->getHdrState(best_path) }; diff --git a/tests/fixtures/include/fixtures/jsonconvertertest.h b/tests/fixtures/include/fixtures/jsonconvertertest.h index 809a89a..e528781 100644 --- a/tests/fixtures/include/fixtures/jsonconvertertest.h +++ b/tests/fixtures/include/fixtures/jsonconvertertest.h @@ -14,8 +14,11 @@ class JsonConverterTest: public BaseTest { EXPECT_TRUE(success); EXPECT_EQ(json_string, expected_string); + std::string error_message {}; T defaulted_input {}; - EXPECT_TRUE(display_device::fromJson(json_string, defaulted_input)); + if (!display_device::fromJson(json_string, defaulted_input, &error_message)) { + GTEST_FAIL() << error_message; + } EXPECT_EQ(input, defaulted_input); } }; diff --git a/tests/unit/general/test_comparison.cpp b/tests/unit/general/test_comparison.cpp index 7a8a031..62d6ea7 100644 --- a/tests/unit/general/test_comparison.cpp +++ b/tests/unit/general/test_comparison.cpp @@ -13,6 +13,12 @@ TEST_S(Point) { EXPECT_NE(display_device::Point({ 1, 1 }), display_device::Point({ 1, 0 })); } +TEST_S(Rational) { + EXPECT_EQ(display_device::Rational({ 1, 1 }), display_device::Rational({ 1, 1 })); + EXPECT_NE(display_device::Rational({ 1, 1 }), display_device::Rational({ 0, 1 })); + EXPECT_NE(display_device::Rational({ 1, 1 }), display_device::Rational({ 1, 0 })); +} + TEST_S(Resolution) { EXPECT_EQ(display_device::Resolution({ 1, 1 }), display_device::Resolution({ 1, 1 })); EXPECT_NE(display_device::Resolution({ 1, 1 }), display_device::Resolution({ 0, 1 })); @@ -20,8 +26,19 @@ TEST_S(Resolution) { } TEST_S(EnumeratedDevice, Info) { + using Rat = display_device::Rational; EXPECT_EQ(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., 1., true, { 1, 1 }, std::nullopt }), display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., 1., true, { 1, 1 }, std::nullopt })); + EXPECT_EQ(display_device::EnumeratedDevice::Info({ { 1, 1 }, Rat { 1, 1 }, Rat { 1, 1 }, true, { 1, 1 }, std::nullopt }), + display_device::EnumeratedDevice::Info({ { 1, 1 }, Rat { 1, 1 }, Rat { 1, 1 }, true, { 1, 1 }, std::nullopt })); + EXPECT_EQ(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., Rat { 1, 1 }, true, { 1, 1 }, std::nullopt }), + display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., Rat { 1, 1 }, true, { 1, 1 }, std::nullopt })); + EXPECT_EQ(display_device::EnumeratedDevice::Info({ { 1, 1 }, Rat { 1, 1 }, 1., true, { 1, 1 }, std::nullopt }), + display_device::EnumeratedDevice::Info({ { 1, 1 }, Rat { 1, 1 }, 1., true, { 1, 1 }, std::nullopt })); + EXPECT_NE(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., Rat { 1, 1 }, true, { 1, 1 }, std::nullopt }), + display_device::EnumeratedDevice::Info({ { 1, 1 }, Rat { 1, 1 }, Rat { 1, 1 }, true, { 1, 1 }, std::nullopt })); + EXPECT_NE(display_device::EnumeratedDevice::Info({ { 1, 1 }, Rat { 1, 1 }, 1., true, { 1, 1 }, std::nullopt }), + display_device::EnumeratedDevice::Info({ { 1, 1 }, Rat { 1, 1 }, Rat { 1, 1 }, true, { 1, 1 }, std::nullopt })); EXPECT_NE(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., 1., true, { 1, 1 }, std::nullopt }), display_device::EnumeratedDevice::Info({ { 1, 0 }, 1., 1., true, { 1, 1 }, std::nullopt })); EXPECT_NE(display_device::EnumeratedDevice::Info({ { 1, 1 }, 1., 1., true, { 1, 1 }, std::nullopt }), @@ -51,8 +68,13 @@ TEST_S(EnumeratedDevice) { TEST_S(SingleDisplayConfiguration) { using DevicePrep = display_device::SingleDisplayConfiguration::DevicePreparation; + using Rat = display_device::Rational; EXPECT_EQ(display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1., display_device::HdrState::Disabled }), display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1., display_device::HdrState::Disabled })); + EXPECT_EQ(display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, Rat { 1, 1 }, display_device::HdrState::Disabled }), + display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, Rat { 1, 1 }, display_device::HdrState::Disabled })); + EXPECT_NE(display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1., display_device::HdrState::Disabled }), + display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, Rat { 1, 1 }, display_device::HdrState::Disabled })); EXPECT_NE(display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1., display_device::HdrState::Disabled }), display_device::SingleDisplayConfiguration({ "0", DevicePrep::EnsureActive, { { 1, 1 } }, 1., display_device::HdrState::Disabled })); EXPECT_NE(display_device::SingleDisplayConfiguration({ "1", DevicePrep::EnsureActive, { { 1, 1 } }, 1., display_device::HdrState::Disabled }), diff --git a/tests/unit/general/test_json.cpp b/tests/unit/general/test_json.cpp index 3d90fa6..ce4706d 100644 --- a/tests/unit/general/test_json.cpp +++ b/tests/unit/general/test_json.cpp @@ -25,6 +25,8 @@ namespace display_device { Nested m_b {}; }; + using TestVariant = std::variant<double, Rational>; + bool operator==(const TestStruct::Nested &lhs, const TestStruct::Nested &rhs) { return lhs.m_c == rhs.m_c; @@ -41,6 +43,7 @@ namespace display_device { DD_JSON_DEFINE_CONVERTER(TestEnum) DD_JSON_DEFINE_CONVERTER(TestStruct) + DD_JSON_DEFINE_CONVERTER(TestVariant) } // namespace display_device namespace { @@ -166,3 +169,26 @@ TEST_S(FromJson, Enum, MissingMappingValue) { EXPECT_FALSE(display_device::fromJson(R"("OtherValue")", value, &error_message)); EXPECT_EQ(error_message, "TestEnum is missing enum mapping!"); } + +TEST_S(ToJson, TestVariant) { + EXPECT_EQ(toJson(display_device::TestVariant { 123. }, std::nullopt, nullptr), R"({"type":"double","value":123.0})"); + EXPECT_EQ(toJson(display_device::TestVariant { display_device::Rational { 1, 2 } }, std::nullopt, nullptr), R"({"type":"rational","value":{"denominator":2,"numerator":1}})"); +} + +TEST_S(FromJson, TestVariant) { + display_device::TestVariant variant {}; + + EXPECT_TRUE(display_device::fromJson(R"({"type":"double","value":123.0})", variant, nullptr)); + EXPECT_EQ(std::get<double>(variant), 123.0); // Relying on GTest to properly compare floats + + EXPECT_TRUE(display_device::fromJson(R"({"type":"rational","value":{"denominator":2,"numerator":1}})", variant, nullptr)); + EXPECT_EQ(std::get<display_device::Rational>(variant), display_device::Rational({ 1, 2 })); +} + +TEST_S(FromJson, TestVariant, UnknownVariantType) { + display_device::TestVariant variant {}; + std::string error_message {}; + + 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!"); +} diff --git a/tests/unit/general/test_jsonconverter.cpp b/tests/unit/general/test_jsonconverter.cpp index 355ce50..d1a975c 100644 --- a/tests/unit/general/test_jsonconverter.cpp +++ b/tests/unit/general/test_jsonconverter.cpp @@ -13,7 +13,7 @@ TEST_F_S(EnumeratedDeviceList) { "FU_NAME_3", display_device::EnumeratedDevice::Info { { 1920, 1080 }, - 1.75, + display_device::Rational { 175, 100 }, 119.9554, false, { 1, 2 }, @@ -26,7 +26,7 @@ TEST_F_S(EnumeratedDeviceList) { display_device::EnumeratedDevice::Info { { 1920, 1080 }, 1.75, - 119.9554, + display_device::Rational { 1199554, 10000 }, true, { 0, 0 }, display_device::HdrState::Disabled } @@ -35,20 +35,20 @@ TEST_F_S(EnumeratedDeviceList) { executeTestCase(display_device::EnumeratedDeviceList {}, R"([])"); executeTestCase(display_device::EnumeratedDeviceList { item_1, item_2, item_3 }, - R"([{"device_id":"ID_1","display_name":"NAME_2","friendly_name":"FU_NAME_3","info":{"hdr_state":"Enabled","origin_point":{"x":1,"y":2},"primary":false,"refresh_rate":119.9554,"resolution":{"height":1080,"width":1920},"resolution_scale":1.75}},)" - R"({"device_id":"ID_2","display_name":"NAME_2","friendly_name":"FU_NAME_2","info":{"hdr_state":"Disabled","origin_point":{"x":0,"y":0},"primary":true,"refresh_rate":119.9554,"resolution":{"height":1080,"width":1920},"resolution_scale":1.75}},)" + R"([{"device_id":"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}}}},)" + 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}}},)" R"({"device_id":"","display_name":"","friendly_name":"","info":null}])"); } TEST_F_S(SingleDisplayConfiguration) { display_device::SingleDisplayConfiguration config_1 { "ID_1", display_device::SingleDisplayConfiguration::DevicePreparation::VerifyOnly, { { 156, 123 } }, 85., display_device::HdrState::Enabled }; - display_device::SingleDisplayConfiguration config_2 { "ID_2", display_device::SingleDisplayConfiguration::DevicePreparation::EnsureActive, std::nullopt, 85., display_device::HdrState::Disabled }; + display_device::SingleDisplayConfiguration config_2 { "ID_2", display_device::SingleDisplayConfiguration::DevicePreparation::EnsureActive, std::nullopt, display_device::Rational { 85, 1 }, display_device::HdrState::Disabled }; display_device::SingleDisplayConfiguration config_3 { "ID_3", display_device::SingleDisplayConfiguration::DevicePreparation::EnsureOnlyDisplay, { { 156, 123 } }, std::nullopt, std::nullopt }; display_device::SingleDisplayConfiguration config_4 { "ID_4", display_device::SingleDisplayConfiguration::DevicePreparation::EnsurePrimary, std::nullopt, std::nullopt, std::nullopt }; executeTestCase(display_device::SingleDisplayConfiguration {}, R"({"device_id":"","device_prep":"VerifyOnly","hdr_state":null,"refresh_rate":null,"resolution":null})"); - executeTestCase(config_1, R"({"device_id":"ID_1","device_prep":"VerifyOnly","hdr_state":"Enabled","refresh_rate":85.0,"resolution":{"height":123,"width":156}})"); - executeTestCase(config_2, R"({"device_id":"ID_2","device_prep":"EnsureActive","hdr_state":"Disabled","refresh_rate":85.0,"resolution":null})"); + executeTestCase(config_1, R"({"device_id":"ID_1","device_prep":"VerifyOnly","hdr_state":"Enabled","refresh_rate":{"type":"double","value":85.0},"resolution":{"height":123,"width":156}})"); + executeTestCase(config_2, R"({"device_id":"ID_2","device_prep":"EnsureActive","hdr_state":"Disabled","refresh_rate":{"type":"rational","value":{"denominator":1,"numerator":85}},"resolution":null})"); executeTestCase(config_3, R"({"device_id":"ID_3","device_prep":"EnsureOnlyDisplay","hdr_state":null,"refresh_rate":null,"resolution":{"height":123,"width":156}})"); executeTestCase(config_4, R"({"device_id":"ID_4","device_prep":"EnsurePrimary","hdr_state":null,"refresh_rate":null,"resolution":null})"); } diff --git a/tests/unit/windows/test_comparison.cpp b/tests/unit/windows/test_comparison.cpp index cd87154..0cdea8f 100644 --- a/tests/unit/windows/test_comparison.cpp +++ b/tests/unit/windows/test_comparison.cpp @@ -7,12 +7,6 @@ namespace { #define TEST_S(...) DD_MAKE_TEST(TEST, TypeComparison, __VA_ARGS__) } // namespace -TEST_S(Rational) { - EXPECT_EQ(display_device::Rational({ 1, 1 }), display_device::Rational({ 1, 1 })); - EXPECT_NE(display_device::Rational({ 1, 1 }), display_device::Rational({ 0, 1 })); - EXPECT_NE(display_device::Rational({ 1, 1 }), display_device::Rational({ 1, 0 })); -} - TEST_S(DisplayMode) { EXPECT_EQ(display_device::DisplayMode({ 1, 1 }, { 1, 1 }), display_device::DisplayMode({ 1, 1 }, { 1, 1 })); EXPECT_NE(display_device::DisplayMode({ 1, 1 }, { 1, 1 }), display_device::DisplayMode({ 1, 0 }, { 1, 1 })); diff --git a/tests/unit/windows/test_settingsutils.cpp b/tests/unit/windows/test_settingsutils.cpp index c170a8f..83966de 100644 --- a/tests/unit/windows/test_settingsutils.cpp +++ b/tests/unit/windows/test_settingsutils.cpp @@ -91,7 +91,7 @@ TEST_F_S_MOCKED(ComputeNewTopology, EnsurePrimary) { (display_device::ActiveTopology { { "DeviceId3" }, { "DeviceId4" } })); } -TEST_F_S_MOCKED(ComputeNewDisplayModes, PrimaryDevices) { +TEST_F_S_MOCKED(ComputeNewDisplayModes, PrimaryDevices, DoubleFloatType) { auto expected_modes { DEFAULT_CURRENT_MODES }; expected_modes["DeviceId1"] = { { 1920, 1080 }, { 1200000, 10000 } }; expected_modes["DeviceId2"] = { { 1920, 1080 }, { 1200000, 10000 } }; @@ -99,7 +99,7 @@ TEST_F_S_MOCKED(ComputeNewDisplayModes, PrimaryDevices) { EXPECT_EQ(display_device::win_utils::computeNewDisplayModes({ { 1920, 1080 } }, { 120. }, true, "DeviceId1", { "DeviceId2" }, DEFAULT_CURRENT_MODES), expected_modes); } -TEST_F_S_MOCKED(ComputeNewDisplayModes, NonPrimaryDevices) { +TEST_F_S_MOCKED(ComputeNewDisplayModes, NonPrimaryDevices, DoubleFloatType) { auto expected_modes { DEFAULT_CURRENT_MODES }; expected_modes["DeviceId1"] = { { 1920, 1080 }, { 1200000, 10000 } }; expected_modes["DeviceId2"] = { { 1920, 1080 }, expected_modes["DeviceId2"].m_refresh_rate }; @@ -107,6 +107,22 @@ 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(ComputeNewDisplayModes, PrimaryDevices, RationalFloatType) { + auto expected_modes { DEFAULT_CURRENT_MODES }; + expected_modes["DeviceId1"] = { { 1920, 1080 }, { 120, 1 } }; + expected_modes["DeviceId2"] = { { 1920, 1080 }, { 120, 1 } }; + + EXPECT_EQ(display_device::win_utils::computeNewDisplayModes({ { 1920, 1080 } }, { display_device::Rational { 120, 1 } }, true, "DeviceId1", { "DeviceId2" }, DEFAULT_CURRENT_MODES), expected_modes); +} + +TEST_F_S_MOCKED(ComputeNewDisplayModes, NonPrimaryDevices, RationalFloatType) { + auto expected_modes { DEFAULT_CURRENT_MODES }; + expected_modes["DeviceId1"] = { { 1920, 1080 }, { 120, 1 } }; + expected_modes["DeviceId2"] = { { 1920, 1080 }, expected_modes["DeviceId2"].m_refresh_rate }; + + EXPECT_EQ(display_device::win_utils::computeNewDisplayModes({ { 1920, 1080 } }, { display_device::Rational { 120, 1 } }, 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; diff --git a/tests/unit/windows/test_windisplaydevicegeneral.cpp b/tests/unit/windows/test_windisplaydevicegeneral.cpp index 54278d3..20bec15 100644 --- a/tests/unit/windows/test_windisplaydevicegeneral.cpp +++ b/tests/unit/windows/test_windisplaydevicegeneral.cpp @@ -160,7 +160,7 @@ TEST_F_S_MOCKED(EnumAvailableDevices) { .RetiresOnSaturation(); EXPECT_CALL(*m_layer, getDisplayScale(_, _)) .Times(1) - .WillOnce(Return(1.75)) + .WillOnce(Return(display_device::Rational { 175, 100 })) .RetiresOnSaturation(); EXPECT_CALL(*m_layer, getHdrState(_)) .Times(1) @@ -178,8 +178,8 @@ TEST_F_S_MOCKED(EnumAvailableDevices) { "FriendlyName1", display_device::EnumeratedDevice::Info { { 1920, 1080 }, - 0., - 0., + display_device::Rational { 0, 1 }, + display_device::Rational { 0, 1 }, true, { 0, 0 }, std::nullopt } }, @@ -188,8 +188,8 @@ TEST_F_S_MOCKED(EnumAvailableDevices) { "FriendlyName2", display_device::EnumeratedDevice::Info { { 1920, 2160 }, - 1.75, - 119.995, + display_device::Rational { 175, 100 }, + display_device::Rational { 119995, 1000 }, false, { 1921, 0 }, display_device::HdrState::Enabled } }, diff --git a/tests/unit/windows/utils/mockwinapilayer.h b/tests/unit/windows/utils/mockwinapilayer.h index b28f7a3..979861a 100644 --- a/tests/unit/windows/utils/mockwinapilayer.h +++ b/tests/unit/windows/utils/mockwinapilayer.h @@ -18,7 +18,7 @@ namespace display_device { MOCK_METHOD(LONG, setDisplayConfig, (std::vector<DISPLAYCONFIG_PATH_INFO>, std::vector<DISPLAYCONFIG_MODE_INFO>, UINT32), (override)); MOCK_METHOD(std::optional<HdrState>, getHdrState, (const DISPLAYCONFIG_PATH_INFO &), (const, override)); MOCK_METHOD(bool, setHdrState, (const DISPLAYCONFIG_PATH_INFO &, HdrState), (override)); - MOCK_METHOD(std::optional<double>, getDisplayScale, (const std::string &, const DISPLAYCONFIG_SOURCE_MODE &), (const, override)); + MOCK_METHOD(std::optional<Rational>, getDisplayScale, (const std::string &, const DISPLAYCONFIG_SOURCE_MODE &), (const, override)); }; } // namespace display_device