Skip to content

Commit f770754

Browse files
authored
feat: add methods for handling hdr state (#45)
1 parent 5f960ed commit f770754

File tree

12 files changed

+636
-1
lines changed

12 files changed

+636
-1
lines changed

src/common/include/displaydevice/types.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
#pragma once
22

33
namespace display_device {
4+
/**
5+
* @brief The device's HDR state in the operating system.
6+
*/
7+
enum class HdrState {
8+
Disabled,
9+
Enabled
10+
};
11+
412
/**
513
* @brief Display's resolution.
614
*/

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,8 @@ namespace display_device {
9191
*/
9292
using DeviceDisplayModeMap = std::map<std::string, DisplayMode>;
9393

94+
/**
95+
* @brief Ordered map of [DEVICE_ID -> std::optional<HdrState>].
96+
*/
97+
using HdrStateMap = std::map<std::string, std::optional<HdrState>>;
9498
} // namespace display_device

src/windows/include/displaydevice/windows/winapilayer.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,13 @@ namespace display_device {
3636
/** For details @see WinApiLayerInterface::setDisplayConfig */
3737
[[nodiscard]] LONG
3838
setDisplayConfig(std::vector<DISPLAYCONFIG_PATH_INFO> paths, std::vector<DISPLAYCONFIG_MODE_INFO> modes, UINT32 flags) override;
39+
40+
/** For details @see WinApiLayerInterface::getHdrState */
41+
[[nodiscard]] std::optional<HdrState>
42+
getHdrState(const DISPLAYCONFIG_PATH_INFO &path) const override;
43+
44+
/** For details @see WinApiLayerInterface::setHdrState */
45+
[[nodiscard]] bool
46+
setHdrState(const DISPLAYCONFIG_PATH_INFO &path, HdrState state) override;
3947
};
4048
} // namespace display_device

src/windows/include/displaydevice/windows/winapilayerinterface.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,5 +166,36 @@ namespace display_device {
166166
*/
167167
[[nodiscard]] virtual LONG
168168
setDisplayConfig(std::vector<DISPLAYCONFIG_PATH_INFO> paths, std::vector<DISPLAYCONFIG_MODE_INFO> modes, UINT32 flags) = 0;
169+
170+
/**
171+
* @brief Get the HDR state the path.
172+
* @param path Path to get HDR state for.
173+
* @returns std::nullopt if the state could not be retrieved, or other enum values describing the state otherwise.
174+
*
175+
* EXAMPLES:
176+
* ```cpp
177+
* DISPLAYCONFIG_PATH_INFO path;
178+
* const WinApiLayerInterface* iface = getIface(...);
179+
* const auto hdr_state = iface->getHdrState(path);
180+
* ```
181+
*/
182+
[[nodiscard]] virtual std::optional<HdrState>
183+
getHdrState(const DISPLAYCONFIG_PATH_INFO &path) const = 0;
184+
185+
/**
186+
* @brief Set the HDR state for the path.
187+
* @param path Path to set HDR state for.
188+
* @param state Specify new HDR state.
189+
* @returns True if the device is in the new state, false otherwise.
190+
*
191+
* EXAMPLES:
192+
* ```cpp
193+
* DISPLAYCONFIG_PATH_INFO path;
194+
* const WinApiLayerInterface* iface = getIface(...);
195+
* const bool success = iface->setHdrState(path, HdrState::Enabled);
196+
* ```
197+
*/
198+
[[nodiscard]] virtual bool
199+
setHdrState(const DISPLAYCONFIG_PATH_INFO &path, HdrState state) = 0;
169200
};
170201
} // namespace display_device

src/windows/include/displaydevice/windows/windisplaydevice.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ namespace display_device {
5151
[[nodiscard]] bool
5252
setAsPrimary(const std::string &device_id) override;
5353

54+
/** For details @see WinDisplayDeviceInterface::getCurrentHdrStates */
55+
[[nodiscard]] HdrStateMap
56+
getCurrentHdrStates(const std::set<std::string> &device_ids) const override;
57+
58+
/** For details @see WinDisplayDeviceInterface::setHdrStates */
59+
[[nodiscard]] bool
60+
setHdrStates(const HdrStateMap &states) override;
61+
5462
private:
5563
std::shared_ptr<WinApiLayerInterface> m_w_api;
5664
};

src/windows/include/displaydevice/windows/windisplaydeviceinterface.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,5 +150,40 @@ namespace display_device {
150150
*/
151151
[[nodiscard]] virtual bool
152152
setAsPrimary(const std::string &device_id) = 0;
153+
154+
/**
155+
* @brief Get HDR state for the devices.
156+
* @param device_ids A list of devices to get the HDR states for.
157+
* @returns A map of HDR states per a device or an empty map if an error has occurred.
158+
* @note On Windows the state cannot be retrieved until the device is active even if it supports it.
159+
*
160+
* EXAMPLES:
161+
* ```cpp
162+
* const WinDisplayDeviceInterface* iface = getIface(...);
163+
* const std::unordered_set<std::string> device_ids { "DEVICE_ID_1", "DEVICE_ID_2" };
164+
* const auto current_hdr_states = iface->getCurrentHdrStates(device_ids);
165+
* ```
166+
*/
167+
[[nodiscard]] virtual HdrStateMap
168+
getCurrentHdrStates(const std::set<std::string> &device_ids) const = 0;
169+
170+
/**
171+
* @brief Set HDR states for the devices.
172+
* @param modes A map of HDR states to set.
173+
* @returns True if HDR states were set, false otherwise.
174+
* @note If `unknown` states are provided, they will be silently ignored
175+
* and current state will not be changed.
176+
*
177+
* EXAMPLES:
178+
* ```cpp
179+
* const WinDisplayDeviceInterface* iface = getIface(...);
180+
* const std::string display_a { "MY_ID_1" };
181+
* const std::string display_b { "MY_ID_2" };
182+
* const auto success = iface->setHdrStates({ { display_a, HdrState::Enabled },
183+
* { display_b, HdrState::Disabled } });
184+
* ```
185+
*/
186+
[[nodiscard]] virtual bool
187+
setHdrStates(const HdrStateMap &states) = 0;
153188
};
154189
} // namespace display_device

src/windows/winapilayer.cpp

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ namespace display_device {
522522

523523
LONG result { DisplayConfigGetDeviceInfo(&source_name.header) };
524524
if (result != ERROR_SUCCESS) {
525-
DD_LOG(error) << getErrorString(result) << " failed to get display name! ";
525+
DD_LOG(error) << getErrorString(result) << " failed to get display name!";
526526
return {};
527527
}
528528

@@ -539,4 +539,39 @@ namespace display_device {
539539
modes.empty() ? nullptr : modes.data(),
540540
flags);
541541
}
542+
543+
std::optional<HdrState>
544+
WinApiLayer::getHdrState(const DISPLAYCONFIG_PATH_INFO &path) const {
545+
DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO color_info = {};
546+
color_info.header.adapterId = path.targetInfo.adapterId;
547+
color_info.header.id = path.targetInfo.id;
548+
color_info.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO;
549+
color_info.header.size = sizeof(color_info);
550+
551+
LONG result { DisplayConfigGetDeviceInfo(&color_info.header) };
552+
if (result != ERROR_SUCCESS) {
553+
DD_LOG(error) << getErrorString(result) << " failed to get advanced color info!";
554+
return std::nullopt;
555+
}
556+
557+
return color_info.advancedColorSupported ? std::make_optional(color_info.advancedColorEnabled ? HdrState::Enabled : HdrState::Disabled) : std::nullopt;
558+
}
559+
560+
bool
561+
WinApiLayer::setHdrState(const DISPLAYCONFIG_PATH_INFO &path, HdrState state) {
562+
DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE color_state = {};
563+
color_state.header.adapterId = path.targetInfo.adapterId;
564+
color_state.header.id = path.targetInfo.id;
565+
color_state.header.type = DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE;
566+
color_state.header.size = sizeof(color_state);
567+
color_state.enableAdvancedColor = state == HdrState::Enabled ? 1 : 0;
568+
569+
LONG result { DisplayConfigSetDeviceInfo(&color_state.header) };
570+
if (result != ERROR_SUCCESS) {
571+
DD_LOG(error) << getErrorString(result) << " failed to set advanced color info!";
572+
return false;
573+
}
574+
575+
return true;
576+
}
542577
} // namespace display_device
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// class header include
2+
#include "displaydevice/windows/windisplaydevice.h"
3+
4+
// system includes
5+
#include <ranges>
6+
7+
// local includes
8+
#include "displaydevice/logging.h"
9+
#include "displaydevice/windows/winapiutils.h"
10+
11+
namespace display_device {
12+
namespace {
13+
/** @brief HDR state map without optional values. */
14+
using HdrStateMapNoOpt = std::map<std::string, HdrState>;
15+
16+
/**
17+
* @see setHdrStates for a description as this was split off to reduce cognitive complexity.
18+
*/
19+
bool
20+
doSetHdrStates(WinApiLayerInterface &w_api, const PathAndModeData &display_data, const HdrStateMapNoOpt &states, HdrStateMapNoOpt *changed_states) {
21+
const auto try_set_state {
22+
[&w_api, &display_data](const auto &device_id, const auto &state, auto &current_state) {
23+
const auto path { win_utils::getActivePath(w_api, device_id, display_data.m_paths) };
24+
if (!path) {
25+
DD_LOG(error) << "Failed to find device for " << device_id << "!";
26+
return false;
27+
}
28+
29+
const auto current_state_int { w_api.getHdrState(*path) };
30+
if (!current_state_int) {
31+
DD_LOG(error) << "HDR state cannot be changed for " << device_id << "!";
32+
return false;
33+
}
34+
35+
if (state != *current_state_int) {
36+
if (!w_api.setHdrState(*path, state)) {
37+
// Error already logged
38+
return false;
39+
}
40+
41+
current_state = current_state_int;
42+
}
43+
44+
return true;
45+
}
46+
};
47+
48+
for (const auto &[device_id, state] : states) {
49+
std::optional<HdrState> current_state;
50+
if (try_set_state(device_id, state, current_state)) {
51+
if (current_state && changed_states != nullptr) {
52+
(*changed_states)[device_id] = *current_state;
53+
}
54+
}
55+
// If we are undoing changes we don't want to return early and continue regardless of what error we get.
56+
else if (changed_states != nullptr) {
57+
return false;
58+
}
59+
}
60+
61+
return true;
62+
}
63+
64+
} // namespace
65+
66+
HdrStateMap
67+
WinDisplayDevice::getCurrentHdrStates(const std::set<std::string> &device_ids) const {
68+
if (device_ids.empty()) {
69+
DD_LOG(error) << "Device id set is empty!";
70+
return {};
71+
}
72+
73+
const auto display_data { m_w_api->queryDisplayConfig(QueryType::Active) };
74+
if (!display_data) {
75+
// Error already logged
76+
return {};
77+
}
78+
79+
HdrStateMap states;
80+
for (const auto &device_id : device_ids) {
81+
const auto path { win_utils::getActivePath(*m_w_api, device_id, display_data->m_paths) };
82+
if (!path) {
83+
DD_LOG(error) << "Failed to find device for " << device_id << "!";
84+
return {};
85+
}
86+
87+
states[device_id] = m_w_api->getHdrState(*path);
88+
}
89+
90+
return states;
91+
}
92+
93+
bool
94+
WinDisplayDevice::setHdrStates(const HdrStateMap &states) {
95+
if (states.empty()) {
96+
DD_LOG(error) << "States map is empty!";
97+
return false;
98+
}
99+
100+
HdrStateMapNoOpt states_without_opt;
101+
std::ranges::copy(states |
102+
std::ranges::views::filter([](const auto &entry) { return static_cast<bool>(entry.second); }) |
103+
std::views::transform([](const auto &entry) { return std::make_pair(entry.first, *entry.second); }),
104+
std::inserter(states_without_opt, std::begin(states_without_opt)));
105+
106+
if (states_without_opt.empty()) {
107+
// Return early as there is nothing to do...
108+
return true;
109+
}
110+
111+
const auto display_data { m_w_api->queryDisplayConfig(QueryType::Active) };
112+
if (!display_data) {
113+
// Error already logged
114+
return {};
115+
}
116+
117+
HdrStateMapNoOpt changed_states;
118+
if (!doSetHdrStates(*m_w_api, *display_data, states_without_opt, &changed_states)) {
119+
if (!changed_states.empty()) {
120+
doSetHdrStates(*m_w_api, *display_data, changed_states, nullptr); // return value does not matter
121+
}
122+
return false;
123+
}
124+
125+
return true;
126+
}
127+
} // namespace display_device

tests/unit/windows/test_winapilayer.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,11 @@ TEST_F_S(GetDisplayName) {
204204
TEST_F_S(GetDisplayName, InvalidPath) {
205205
EXPECT_EQ(m_layer.getDisplayName(INVALID_PATH), std::string {});
206206
}
207+
208+
TEST_F_S(GetHdrState, InvalidPath) {
209+
EXPECT_EQ(m_layer.getHdrState(INVALID_PATH), std::nullopt);
210+
}
211+
212+
TEST_F_S(SetHdrState, InvalidPath) {
213+
EXPECT_FALSE(m_layer.setHdrState(INVALID_PATH, display_device::HdrState::Enabled));
214+
}

0 commit comments

Comments
 (0)