Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/common/include/displaydevice/types.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

namespace display_device {
/**
* @brief Display's resolution.
*/
struct Resolution {
unsigned int m_width;
unsigned int m_height;
};

/**
* @brief Floating point stored in a "numerator/denominator" form.
*/
struct Rational {
unsigned int m_numerator;
unsigned int m_denominator;
};
} // namespace display_device
19 changes: 19 additions & 0 deletions src/windows/include/displaydevice/windows/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
#include <string>
#include <vector>

// local includes
#include "displaydevice/types.h"

namespace display_device {
/**
* @brief Type of query the OS should perform while searching for display devices.
Expand All @@ -26,6 +29,14 @@ namespace display_device {
std::vector<DISPLAYCONFIG_MODE_INFO> m_modes; /**< Display modes for ACTIVE displays. */
};

/**
* @brief Specifies additional constraints for the validated device.
*/
enum class ValidatedPathType {
Active, /**< The device path must be active. */
Any /**< The device path can be active or inactive. */
};

/**
* @brief Contains the device path and the id for a VALID device.
* @see win_utils::getDeviceInfoForValidPath for what is considered a valid device.
Expand Down Expand Up @@ -67,4 +78,12 @@ namespace display_device {
*/
using ActiveTopology = std::vector<std::vector<std::string>>;

/**
* @brief Display's mode (resolution + refresh rate).
*/
struct DisplayMode {
Resolution m_resolution;
Rational m_refresh_rate;
};

} // namespace display_device
81 changes: 78 additions & 3 deletions src/windows/include/displaydevice/windows/winapiutils.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#pragma once

// system includes
#include <set>

// local includes
#include "winapilayerinterface.h"

Expand Down Expand Up @@ -187,19 +190,43 @@ namespace display_device::win_utils {
*
* @param w_api Reference to the Windows API layer.
* @param path Path to validate and get info for.
* @param must_be_active Optionally request that the valid path must also be active.
* @param type Additional constraints for the path.
* @returns Commonly used info for the path, or empty optional if the path is invalid.
* @see WinApiLayerInterface::queryDisplayConfig on how to get paths and modes from the system.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const auto device_info = getDeviceInfoForValidPath(*iface, path, true);
* const auto device_info = getDeviceInfoForValidPath(*iface, path, ValidatedPathType::Active);
* ```
*/
[[nodiscard]] std::optional<ValidatedDeviceInfo>
getDeviceInfoForValidPath(const WinApiLayerInterface &w_api, const DISPLAYCONFIG_PATH_INFO &path, bool must_be_active);
getDeviceInfoForValidPath(const WinApiLayerInterface &w_api, const DISPLAYCONFIG_PATH_INFO &path, ValidatedPathType type);

/**
* @brief Get the active path matching the device id.
* @param w_api Reference to the Windows API layer.
* @param device_id Id to search for in the the list.
* @param paths List to be searched.
* @returns A pointer to an active path matching our id, nullptr otherwise.
* @see WinApiLayerInterface::queryDisplayConfig on how to get paths and modes from the system.
*
* EXAMPLES:
* ```cpp
* const std::vector<DISPLAYCONFIG_PATH_INFO> paths;
* const WinApiLayerInterface* iface = getIface(...);
* const DISPLAYCONFIG_PATH_INFO* active_path = get_active_path(*iface, "MY_DEVICE_ID", paths);
* ```
*/
[[nodiscard]] const DISPLAYCONFIG_PATH_INFO *
getActivePath(const WinApiLayerInterface &w_api, const std::string &device_id, const std::vector<DISPLAYCONFIG_PATH_INFO> &paths);

/**
* @see getActivePath (const version) for the description.
*/
[[nodiscard]] DISPLAYCONFIG_PATH_INFO *
getActivePath(const WinApiLayerInterface &w_api, const std::string &device_id, std::vector<DISPLAYCONFIG_PATH_INFO> &paths);

/**
* @brief Collect arbitrary source data from provided paths.
Expand Down Expand Up @@ -242,4 +269,52 @@ namespace display_device::win_utils {
*/
[[nodiscard]] std::vector<DISPLAYCONFIG_PATH_INFO>
makePathsForNewTopology(const ActiveTopology &new_topology, const PathSourceIndexDataMap &path_source_data, const std::vector<DISPLAYCONFIG_PATH_INFO> &paths);

/**
* @brief Get all the missing duplicate device ids for the provided device ids.
* @param w_api Reference to the Windows API layer.
* @param device_ids Device ids to find the missing duplicate ids for.
* @returns A list of device ids containing the provided device ids and all unspecified ids
* for duplicated displays.
*
* EXAMPLES:
* ```cpp
* const WinApiLayerInterface* iface = getIface(...);
* const auto device_ids_with_duplicates = getAllDeviceIdsAndMatchingDuplicates(*iface, { "MY_ID1" });
* ```
*/
[[nodiscard]] std::set<std::string>
getAllDeviceIdsAndMatchingDuplicates(const WinApiLayerInterface &w_api, const std::set<std::string> &device_ids);

/**
* @brief Check if the refresh rates are almost equal.
* @param lhs First refresh rate.
* @param rhs Second refresh rate.
* @return True if refresh rates are almost equal, false otherwise.
*
* EXAMPLES:
* ```cpp
* const bool almost_equal = fuzzyCompareRefreshRates(Rational { 60, 1 }, Rational { 5985, 100 });
* const bool not_equal = fuzzyCompareRefreshRates(Rational { 60, 1 }, Rational { 5585, 100 });
* ```
*/
[[nodiscard]] bool
fuzzyCompareRefreshRates(const Rational &lhs, const Rational &rhs);

/**
* @brief Check if the display modes are almost equal.
* @param lhs First mode.
* @param rhs Second mode.
* @return True if display modes are almost equal, false otherwise.
*
* EXAMPLES:
* ```cpp
* const bool almost_equal = fuzzyCompareModes(DisplayMode { { 1920, 1080 }, { 60, 1 } },
* DisplayMode { { 1920, 1080 }, { 5985, 100 } });
* const bool not_equal = fuzzyCompareModes(DisplayMode { { 1920, 1080 }, { 60, 1 } },
* DisplayMode { { 1920, 1080 }, { 5585, 100 } });
* ```
*/
[[nodiscard]] bool
fuzzyCompareModes(const DisplayMode &lhs, const DisplayMode &rhs);
} // namespace display_device::win_utils
122 changes: 119 additions & 3 deletions src/windows/winapiutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,26 @@ namespace {
toString(const LUID &id) {
return std::to_string(id.HighPart) + std::to_string(id.LowPart);
}

/**
* @brief Check if the source modes are duplicated (cloned).
* @param lhs First mode to check.
* @param rhs Second mode to check.
* @returns True if both mode have the same origin point, false otherwise.
* @note Windows enforces the behaviour that only the duplicate devices can
* have the same origin point as otherwise the configuration is considered invalid by the OS.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_SOURCE_MODE mode_a;
* DISPLAYCONFIG_SOURCE_MODE mode_b;
* const bool are_duplicated = are_modes_duplicated(mode_a, mode_b);
* ```
*/
bool
are_modes_duplicated(const DISPLAYCONFIG_SOURCE_MODE &lhs, const DISPLAYCONFIG_SOURCE_MODE &rhs) {
return lhs.position.x == rhs.position.x && lhs.position.y == rhs.position.y;
}
} // namespace

namespace display_device::win_utils {
Expand Down Expand Up @@ -169,13 +189,13 @@ namespace display_device::win_utils {
}

std::optional<ValidatedDeviceInfo>
getDeviceInfoForValidPath(const WinApiLayerInterface &w_api, const DISPLAYCONFIG_PATH_INFO &path, bool must_be_active) {
getDeviceInfoForValidPath(const WinApiLayerInterface &w_api, const DISPLAYCONFIG_PATH_INFO &path, const ValidatedPathType type) {
if (!isAvailable(path)) {
// Could be transient issue according to MSDOCS (no longer available, but still "active")
return std::nullopt;
}

if (must_be_active) {
if (type == ValidatedPathType::Active) {
if (!isActive(path)) {
return std::nullopt;
}
Expand All @@ -199,6 +219,27 @@ namespace display_device::win_utils {
return ValidatedDeviceInfo { device_path, device_id };
}

const DISPLAYCONFIG_PATH_INFO *
getActivePath(const WinApiLayerInterface &w_api, const std::string &device_id, const std::vector<DISPLAYCONFIG_PATH_INFO> &paths) {
for (const auto &path : paths) {
const auto device_info { getDeviceInfoForValidPath(w_api, path, ValidatedPathType::Active) };
if (!device_info) {
continue;
}

if (device_info->m_device_id == device_id) {
return &path;
}
}

return nullptr;
}

DISPLAYCONFIG_PATH_INFO *
getActivePath(const WinApiLayerInterface &w_api, const std::string &device_id, std::vector<DISPLAYCONFIG_PATH_INFO> &paths) {
return const_cast<DISPLAYCONFIG_PATH_INFO *>(getActivePath(w_api, device_id, const_cast<const std::vector<DISPLAYCONFIG_PATH_INFO> &>(paths)));
}

PathSourceIndexDataMap
collectSourceDataForMatchingPaths(const WinApiLayerInterface &w_api, const std::vector<DISPLAYCONFIG_PATH_INFO> &paths) {
PathSourceIndexDataMap path_data;
Expand All @@ -207,7 +248,7 @@ namespace display_device::win_utils {
for (std::size_t index = 0; index < paths.size(); ++index) {
const auto &path { paths[index] };

const auto device_info { getDeviceInfoForValidPath(w_api, path, false) };
const auto device_info { getDeviceInfoForValidPath(w_api, path, ValidatedPathType::Any) };
if (!device_info) {
// Path is not valid
continue;
Expand Down Expand Up @@ -376,4 +417,79 @@ namespace display_device::win_utils {
}
return new_paths;
}

std::set<std::string>
getAllDeviceIdsAndMatchingDuplicates(const WinApiLayerInterface &w_api, const std::set<std::string> &device_ids) {
const auto display_data { w_api.queryDisplayConfig(QueryType::Active) };
if (!display_data) {
// Error already logged
return {};
}

std::set<std::string> all_device_ids;
for (const auto &device_id : device_ids) {
if (device_id.empty()) {
DD_LOG(error) << "Device it is empty!";
return {};
}

const auto provided_path { getActivePath(w_api, device_id, display_data->m_paths) };
if (!provided_path) {
DD_LOG(warning) << "Failed to find device for " << device_id << "!";
return {};
}

const auto provided_path_source_mode { getSourceMode(getSourceIndex(*provided_path, display_data->m_modes), display_data->m_modes) };
if (!provided_path_source_mode) {
DD_LOG(error) << "Active device does not have a source mode: " << device_id << "!";
return {};
}

// We will now iterate over all the active paths (provided path included) and check if
// any of them are duplicated.
for (const auto &path : display_data->m_paths) {
const auto device_info { getDeviceInfoForValidPath(w_api, path, ValidatedPathType::Active) };
if (!device_info) {
continue;
}

if (all_device_ids.contains(device_info->m_device_id)) {
// Already checked
continue;
}

const auto source_mode { getSourceMode(getSourceIndex(path, display_data->m_modes), display_data->m_modes) };
if (!source_mode) {
DD_LOG(error) << "Active device does not have a source mode: " << device_info->m_device_id << "!";
return {};
}

if (!are_modes_duplicated(*provided_path_source_mode, *source_mode)) {
continue;
}

all_device_ids.insert(device_info->m_device_id);
}
}

return all_device_ids;
}

bool
fuzzyCompareRefreshRates(const Rational &lhs, const Rational &rhs) {
if (lhs.m_denominator > 0 && rhs.m_denominator > 0) {
const float lhs_f { static_cast<float>(lhs.m_numerator) / static_cast<float>(lhs.m_denominator) };
const float rhs_f { static_cast<float>(rhs.m_numerator) / static_cast<float>(rhs.m_denominator) };
return (std::abs(lhs_f - rhs_f) <= 0.9f);
}

return false;
}

bool
fuzzyCompareModes(const DisplayMode &lhs, const DisplayMode &rhs) {
return lhs.m_resolution.m_width == rhs.m_resolution.m_width &&
lhs.m_resolution.m_height == rhs.m_resolution.m_height &&
fuzzyCompareRefreshRates(lhs.m_refresh_rate, rhs.m_refresh_rate);
}
} // namespace display_device::win_utils
2 changes: 1 addition & 1 deletion src/windows/windisplaydevicetopology.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ namespace display_device {
std::unordered_map<std::string, std::size_t> position_to_topology_index;
ActiveTopology topology;
for (const auto &path : display_data->m_paths) {
const auto device_info { win_utils::getDeviceInfoForValidPath(*m_w_api, path, true) };
const auto device_info { win_utils::getDeviceInfoForValidPath(*m_w_api, path, display_device::ValidatedPathType::Active) };
if (!device_info) {
continue;
}
Expand Down
Loading