Skip to content

Commit 5838919

Browse files
authored
feat: port more win utils (#38)
1 parent 7db4252 commit 5838919

File tree

7 files changed

+434
-16
lines changed

7 files changed

+434
-16
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#pragma once
2+
3+
namespace display_device {
4+
/**
5+
* @brief Display's resolution.
6+
*/
7+
struct Resolution {
8+
unsigned int m_width;
9+
unsigned int m_height;
10+
};
11+
12+
/**
13+
* @brief Floating point stored in a "numerator/denominator" form.
14+
*/
15+
struct Rational {
16+
unsigned int m_numerator;
17+
unsigned int m_denominator;
18+
};
19+
} // namespace display_device

src/windows/include/displaydevice/windows/types.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
#include <string>
1010
#include <vector>
1111

12+
// local includes
13+
#include "displaydevice/types.h"
14+
1215
namespace display_device {
1316
/**
1417
* @brief Type of query the OS should perform while searching for display devices.
@@ -26,6 +29,14 @@ namespace display_device {
2629
std::vector<DISPLAYCONFIG_MODE_INFO> m_modes; /**< Display modes for ACTIVE displays. */
2730
};
2831

32+
/**
33+
* @brief Specifies additional constraints for the validated device.
34+
*/
35+
enum class ValidatedPathType {
36+
Active, /**< The device path must be active. */
37+
Any /**< The device path can be active or inactive. */
38+
};
39+
2940
/**
3041
* @brief Contains the device path and the id for a VALID device.
3142
* @see win_utils::getDeviceInfoForValidPath for what is considered a valid device.
@@ -67,4 +78,12 @@ namespace display_device {
6778
*/
6879
using ActiveTopology = std::vector<std::vector<std::string>>;
6980

81+
/**
82+
* @brief Display's mode (resolution + refresh rate).
83+
*/
84+
struct DisplayMode {
85+
Resolution m_resolution;
86+
Rational m_refresh_rate;
87+
};
88+
7089
} // namespace display_device

src/windows/include/displaydevice/windows/winapiutils.h

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#pragma once
22

3+
// system includes
4+
#include <set>
5+
36
// local includes
47
#include "winapilayerinterface.h"
58

@@ -187,19 +190,43 @@ namespace display_device::win_utils {
187190
*
188191
* @param w_api Reference to the Windows API layer.
189192
* @param path Path to validate and get info for.
190-
* @param must_be_active Optionally request that the valid path must also be active.
193+
* @param type Additional constraints for the path.
191194
* @returns Commonly used info for the path, or empty optional if the path is invalid.
192195
* @see WinApiLayerInterface::queryDisplayConfig on how to get paths and modes from the system.
193196
*
194197
* EXAMPLES:
195198
* ```cpp
196199
* DISPLAYCONFIG_PATH_INFO path;
197200
* const WinApiLayerInterface* iface = getIface(...);
198-
* const auto device_info = getDeviceInfoForValidPath(*iface, path, true);
201+
* const auto device_info = getDeviceInfoForValidPath(*iface, path, ValidatedPathType::Active);
199202
* ```
200203
*/
201204
[[nodiscard]] std::optional<ValidatedDeviceInfo>
202-
getDeviceInfoForValidPath(const WinApiLayerInterface &w_api, const DISPLAYCONFIG_PATH_INFO &path, bool must_be_active);
205+
getDeviceInfoForValidPath(const WinApiLayerInterface &w_api, const DISPLAYCONFIG_PATH_INFO &path, ValidatedPathType type);
206+
207+
/**
208+
* @brief Get the active path matching the device id.
209+
* @param w_api Reference to the Windows API layer.
210+
* @param device_id Id to search for in the the list.
211+
* @param paths List to be searched.
212+
* @returns A pointer to an active path matching our id, nullptr otherwise.
213+
* @see WinApiLayerInterface::queryDisplayConfig on how to get paths and modes from the system.
214+
*
215+
* EXAMPLES:
216+
* ```cpp
217+
* const std::vector<DISPLAYCONFIG_PATH_INFO> paths;
218+
* const WinApiLayerInterface* iface = getIface(...);
219+
* const DISPLAYCONFIG_PATH_INFO* active_path = get_active_path(*iface, "MY_DEVICE_ID", paths);
220+
* ```
221+
*/
222+
[[nodiscard]] const DISPLAYCONFIG_PATH_INFO *
223+
getActivePath(const WinApiLayerInterface &w_api, const std::string &device_id, const std::vector<DISPLAYCONFIG_PATH_INFO> &paths);
224+
225+
/**
226+
* @see getActivePath (const version) for the description.
227+
*/
228+
[[nodiscard]] DISPLAYCONFIG_PATH_INFO *
229+
getActivePath(const WinApiLayerInterface &w_api, const std::string &device_id, std::vector<DISPLAYCONFIG_PATH_INFO> &paths);
203230

204231
/**
205232
* @brief Collect arbitrary source data from provided paths.
@@ -242,4 +269,52 @@ namespace display_device::win_utils {
242269
*/
243270
[[nodiscard]] std::vector<DISPLAYCONFIG_PATH_INFO>
244271
makePathsForNewTopology(const ActiveTopology &new_topology, const PathSourceIndexDataMap &path_source_data, const std::vector<DISPLAYCONFIG_PATH_INFO> &paths);
272+
273+
/**
274+
* @brief Get all the missing duplicate device ids for the provided device ids.
275+
* @param w_api Reference to the Windows API layer.
276+
* @param device_ids Device ids to find the missing duplicate ids for.
277+
* @returns A list of device ids containing the provided device ids and all unspecified ids
278+
* for duplicated displays.
279+
*
280+
* EXAMPLES:
281+
* ```cpp
282+
* const WinApiLayerInterface* iface = getIface(...);
283+
* const auto device_ids_with_duplicates = getAllDeviceIdsAndMatchingDuplicates(*iface, { "MY_ID1" });
284+
* ```
285+
*/
286+
[[nodiscard]] std::set<std::string>
287+
getAllDeviceIdsAndMatchingDuplicates(const WinApiLayerInterface &w_api, const std::set<std::string> &device_ids);
288+
289+
/**
290+
* @brief Check if the refresh rates are almost equal.
291+
* @param lhs First refresh rate.
292+
* @param rhs Second refresh rate.
293+
* @return True if refresh rates are almost equal, false otherwise.
294+
*
295+
* EXAMPLES:
296+
* ```cpp
297+
* const bool almost_equal = fuzzyCompareRefreshRates(Rational { 60, 1 }, Rational { 5985, 100 });
298+
* const bool not_equal = fuzzyCompareRefreshRates(Rational { 60, 1 }, Rational { 5585, 100 });
299+
* ```
300+
*/
301+
[[nodiscard]] bool
302+
fuzzyCompareRefreshRates(const Rational &lhs, const Rational &rhs);
303+
304+
/**
305+
* @brief Check if the display modes are almost equal.
306+
* @param lhs First mode.
307+
* @param rhs Second mode.
308+
* @return True if display modes are almost equal, false otherwise.
309+
*
310+
* EXAMPLES:
311+
* ```cpp
312+
* const bool almost_equal = fuzzyCompareModes(DisplayMode { { 1920, 1080 }, { 60, 1 } },
313+
* DisplayMode { { 1920, 1080 }, { 5985, 100 } });
314+
* const bool not_equal = fuzzyCompareModes(DisplayMode { { 1920, 1080 }, { 60, 1 } },
315+
* DisplayMode { { 1920, 1080 }, { 5585, 100 } });
316+
* ```
317+
*/
318+
[[nodiscard]] bool
319+
fuzzyCompareModes(const DisplayMode &lhs, const DisplayMode &rhs);
245320
} // namespace display_device::win_utils

src/windows/winapiutils.cpp

Lines changed: 119 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,26 @@ namespace {
3838
toString(const LUID &id) {
3939
return std::to_string(id.HighPart) + std::to_string(id.LowPart);
4040
}
41+
42+
/**
43+
* @brief Check if the source modes are duplicated (cloned).
44+
* @param lhs First mode to check.
45+
* @param rhs Second mode to check.
46+
* @returns True if both mode have the same origin point, false otherwise.
47+
* @note Windows enforces the behaviour that only the duplicate devices can
48+
* have the same origin point as otherwise the configuration is considered invalid by the OS.
49+
*
50+
* EXAMPLES:
51+
* ```cpp
52+
* DISPLAYCONFIG_SOURCE_MODE mode_a;
53+
* DISPLAYCONFIG_SOURCE_MODE mode_b;
54+
* const bool are_duplicated = are_modes_duplicated(mode_a, mode_b);
55+
* ```
56+
*/
57+
bool
58+
are_modes_duplicated(const DISPLAYCONFIG_SOURCE_MODE &lhs, const DISPLAYCONFIG_SOURCE_MODE &rhs) {
59+
return lhs.position.x == rhs.position.x && lhs.position.y == rhs.position.y;
60+
}
4161
} // namespace
4262

4363
namespace display_device::win_utils {
@@ -169,13 +189,13 @@ namespace display_device::win_utils {
169189
}
170190

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

178-
if (must_be_active) {
198+
if (type == ValidatedPathType::Active) {
179199
if (!isActive(path)) {
180200
return std::nullopt;
181201
}
@@ -199,6 +219,27 @@ namespace display_device::win_utils {
199219
return ValidatedDeviceInfo { device_path, device_id };
200220
}
201221

222+
const DISPLAYCONFIG_PATH_INFO *
223+
getActivePath(const WinApiLayerInterface &w_api, const std::string &device_id, const std::vector<DISPLAYCONFIG_PATH_INFO> &paths) {
224+
for (const auto &path : paths) {
225+
const auto device_info { getDeviceInfoForValidPath(w_api, path, ValidatedPathType::Active) };
226+
if (!device_info) {
227+
continue;
228+
}
229+
230+
if (device_info->m_device_id == device_id) {
231+
return &path;
232+
}
233+
}
234+
235+
return nullptr;
236+
}
237+
238+
DISPLAYCONFIG_PATH_INFO *
239+
getActivePath(const WinApiLayerInterface &w_api, const std::string &device_id, std::vector<DISPLAYCONFIG_PATH_INFO> &paths) {
240+
return const_cast<DISPLAYCONFIG_PATH_INFO *>(getActivePath(w_api, device_id, const_cast<const std::vector<DISPLAYCONFIG_PATH_INFO> &>(paths)));
241+
}
242+
202243
PathSourceIndexDataMap
203244
collectSourceDataForMatchingPaths(const WinApiLayerInterface &w_api, const std::vector<DISPLAYCONFIG_PATH_INFO> &paths) {
204245
PathSourceIndexDataMap path_data;
@@ -207,7 +248,7 @@ namespace display_device::win_utils {
207248
for (std::size_t index = 0; index < paths.size(); ++index) {
208249
const auto &path { paths[index] };
209250

210-
const auto device_info { getDeviceInfoForValidPath(w_api, path, false) };
251+
const auto device_info { getDeviceInfoForValidPath(w_api, path, ValidatedPathType::Any) };
211252
if (!device_info) {
212253
// Path is not valid
213254
continue;
@@ -376,4 +417,79 @@ namespace display_device::win_utils {
376417
}
377418
return new_paths;
378419
}
420+
421+
std::set<std::string>
422+
getAllDeviceIdsAndMatchingDuplicates(const WinApiLayerInterface &w_api, const std::set<std::string> &device_ids) {
423+
const auto display_data { w_api.queryDisplayConfig(QueryType::Active) };
424+
if (!display_data) {
425+
// Error already logged
426+
return {};
427+
}
428+
429+
std::set<std::string> all_device_ids;
430+
for (const auto &device_id : device_ids) {
431+
if (device_id.empty()) {
432+
DD_LOG(error) << "Device it is empty!";
433+
return {};
434+
}
435+
436+
const auto provided_path { getActivePath(w_api, device_id, display_data->m_paths) };
437+
if (!provided_path) {
438+
DD_LOG(warning) << "Failed to find device for " << device_id << "!";
439+
return {};
440+
}
441+
442+
const auto provided_path_source_mode { getSourceMode(getSourceIndex(*provided_path, display_data->m_modes), display_data->m_modes) };
443+
if (!provided_path_source_mode) {
444+
DD_LOG(error) << "Active device does not have a source mode: " << device_id << "!";
445+
return {};
446+
}
447+
448+
// We will now iterate over all the active paths (provided path included) and check if
449+
// any of them are duplicated.
450+
for (const auto &path : display_data->m_paths) {
451+
const auto device_info { getDeviceInfoForValidPath(w_api, path, ValidatedPathType::Active) };
452+
if (!device_info) {
453+
continue;
454+
}
455+
456+
if (all_device_ids.contains(device_info->m_device_id)) {
457+
// Already checked
458+
continue;
459+
}
460+
461+
const auto source_mode { getSourceMode(getSourceIndex(path, display_data->m_modes), display_data->m_modes) };
462+
if (!source_mode) {
463+
DD_LOG(error) << "Active device does not have a source mode: " << device_info->m_device_id << "!";
464+
return {};
465+
}
466+
467+
if (!are_modes_duplicated(*provided_path_source_mode, *source_mode)) {
468+
continue;
469+
}
470+
471+
all_device_ids.insert(device_info->m_device_id);
472+
}
473+
}
474+
475+
return all_device_ids;
476+
}
477+
478+
bool
479+
fuzzyCompareRefreshRates(const Rational &lhs, const Rational &rhs) {
480+
if (lhs.m_denominator > 0 && rhs.m_denominator > 0) {
481+
const float lhs_f { static_cast<float>(lhs.m_numerator) / static_cast<float>(lhs.m_denominator) };
482+
const float rhs_f { static_cast<float>(rhs.m_numerator) / static_cast<float>(rhs.m_denominator) };
483+
return (std::abs(lhs_f - rhs_f) <= 0.9f);
484+
}
485+
486+
return false;
487+
}
488+
489+
bool
490+
fuzzyCompareModes(const DisplayMode &lhs, const DisplayMode &rhs) {
491+
return lhs.m_resolution.m_width == rhs.m_resolution.m_width &&
492+
lhs.m_resolution.m_height == rhs.m_resolution.m_height &&
493+
fuzzyCompareRefreshRates(lhs.m_refresh_rate, rhs.m_refresh_rate);
494+
}
379495
} // namespace display_device::win_utils

src/windows/windisplaydevicetopology.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ namespace display_device {
6969
std::unordered_map<std::string, std::size_t> position_to_topology_index;
7070
ActiveTopology topology;
7171
for (const auto &path : display_data->m_paths) {
72-
const auto device_info { win_utils::getDeviceInfoForValidPath(*m_w_api, path, true) };
72+
const auto device_info { win_utils::getDeviceInfoForValidPath(*m_w_api, path, display_device::ValidatedPathType::Active) };
7373
if (!device_info) {
7474
continue;
7575
}

0 commit comments

Comments
 (0)