HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
os_settings_win32_impl.hpp
1// Copyright Take Vos 2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
8
9#include "os_settings_intf.hpp"
10#include "../win32/win32.hpp"
11#include "../telemetry/telemetry.hpp"
12#include "../utility/utility.hpp"
13#include "../path/path.hpp"
14#include "../macros.hpp"
15
16hi_export_module(hikogui.settings.os_settings : impl);
17
18hi_export namespace hi { inline namespace v1 {
19
20[[nodiscard]] hi_inline std::vector<uuid> os_settings::preferred_gpus(hi::policy performance_policy) noexcept
21{
22 auto r = std::vector<uuid>{};
23
24 auto actual_policy = os_settings::gpu_policy();
25 if (actual_policy == hi::policy::unspecified) {
27 }
28 if (actual_policy == hi::policy::unspecified) {
30 }
31 auto const actual_policy_ = actual_policy == hi::policy::low_power ? DXGI_GPU_PREFERENCE_MINIMUM_POWER :
32 actual_policy == hi::policy::high_performance ? DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE :
34
35 IDXGIFactory *factory = nullptr;
37 hi_log_error("Could not IDXGIFactory. {}", get_last_error_message());
38 return r;
39 }
40 hi_assert_not_null(factory);
41 auto const d1 = defer([&] {
42 factory->Release();
43 });
44
45 IDXGIFactory6 *factory6 = nullptr;
46 if (FAILED(factory->QueryInterface(__uuidof(IDXGIFactory6), (void **)&factory6))) {
47 hi_log_error("Could not IDXGIFactory::QueryInterface(IDXGIFactory6). {}", get_last_error_message());
48 return r;
49 }
50 hi_assert_not_null(factory6);
51 auto const d2 = defer([&] {
52 factory6->Release();
53 });
54
55 IDXGIAdapter1 *adapter = nullptr;
56 for (UINT i = 0;
57 SUCCEEDED(factory6->EnumAdapterByGpuPreference(i, actual_policy_, __uuidof(IDXGIAdapter1), (void **)&adapter));
58 ++i) {
59 auto const d3 = defer([&] {
60 adapter->Release();
61 });
62
64 if (FAILED(adapter->GetDesc1(&description))) {
65 hi_log_error("Could not IDXGIAdapter1::GetDesc1(). {}", get_last_error_message());
66 return r;
67 }
68
69 static_assert(sizeof(description.AdapterLuid) <= sizeof(uuid));
70 r.emplace_back();
71 std::memcpy(std::addressof(r.back()), std::addressof(description.AdapterLuid), sizeof(description.AdapterLuid));
72 }
73
74 return r;
75}
76
86[[nodiscard]] hi_inline std::vector<language_tag> os_settings::gather_languages() noexcept
87{
89
90 // The official APIs to get the current languages do not work.
91 // Either they return the languages in a random order.
92 // Or they return only three languages, but not nessarily the first three
93 // Or they do not update at runtime.
94 // The only way that works is to get the registry from the Control Panel application.
96 HKEY_CURRENT_USER, "Control Panel\\International\\User Profile", "Languages")) {
97 r.reserve(languages->size());
98 for (auto const& language : *languages) {
99 r.push_back(language_tag{language});
100 }
101 } else {
102 hi_log_error("Could not read languages: {}", std::error_code{languages.error()}.message());
103 r.push_back(language_tag{"en"});
104 }
105
106 return r;
107}
108
109[[nodiscard]] hi_inline std::expected<std::locale, std::error_code> os_settings::gather_locale() noexcept
110{
111 if (auto name = win32_GetUserDefaultLocaleName()) {
112 return std::locale(*name);
113
114 } else {
115 return std::unexpected{std::error_code{name.error()}};
116 }
117}
118
119[[nodiscard]] hi_inline bool os_settings::gather_left_to_right() noexcept
120{
121 if (auto locale = gather_locale()) {
122 try {
123 // The locale name on windows is the <language-tag>.<collation>.
124 auto locale_name = locale->name();
125
126 // Strip off the optional collation algorithm.
127 if (auto i = locale_name.find('.'); i != locale_name.npos) {
128 locale_name = locale_name.substr(0, i);
129 }
130
131 auto locale_tag = language_tag{locale->name()};
132
133 // Expanding will complete the script part of the tag
134 // that is needed to get left-to-right.
135 return locale_tag.expand().left_to_right();
136
137 } catch (...) {
138 // The locale-name may be different from the language-tag
139 // So fallthrough here.
140 }
141 }
142
143 // Use the left-to-right direction of the first configured language.
144 if (auto languages = gather_languages(); not languages.empty()) {
145 return languages.front().expand().left_to_right();
146 }
147
148 // Most languages are left-to-right so it is a good guess.
149 return true;
150}
151
152[[nodiscard]] hi_inline hi::theme_mode os_settings::gather_theme_mode()
153{
154 if (auto const result = win32_RegGetValue<uint32_t>(
156 "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
157 "AppsUseLightTheme")) {
158 return *result ? theme_mode::light : theme_mode::dark;
159
160 } else {
161 hi_log_error("Could not read theme mode: {}", std::error_code{result.error()}.message());
162 return theme_mode::light;
163 }
164}
165
166[[nodiscard]] hi_inline hi::subpixel_orientation os_settings::gather_subpixel_orientation()
167{
168 {
171 throw os_error(std::format("Could not get system parameter SPI_GETFONTSMOOTHING: {}", get_last_error_message()));
172 }
173
174 if (has_font_smoothing == FALSE) {
175 // Font smoothing is disabled.
176 return hi::subpixel_orientation::unknown;
177 }
178 }
179
180 {
183 throw os_error(std::format("Could not get system parameter SPI_GETFONTSMOOTHINGTYPE: {}", get_last_error_message()));
184 }
185
187 // Font smoothing is not clear type.
188 return hi::subpixel_orientation::unknown;
189 }
190 }
191
192 {
195 throw os_error(std::format("Could not get system parameter SPI_GETCLEARTYPE: {}", get_last_error_message()));
196 }
197
198 if (has_clear_type == FALSE) {
199 // ClearType is disabled.
200 return hi::subpixel_orientation::unknown;
201 }
202 }
203
204 {
207 throw os_error(
208 std::format("Could not get system parameter SPI_GETFONTSMOOTHINGORIENTATION: {}", get_last_error_message()));
209 }
210
212 // Font smoothing is not clear type.
213 return hi::subpixel_orientation::horizontal_bgr;
215 // Font smoothing is not clear type.
216 return hi::subpixel_orientation::horizontal_rgb;
217 } else {
218 throw os_error(std::format("Unknown result from SPI_GETFONTSMOOTHINGORIENTATION: {}", font_smooth_orientation));
219 }
220 }
221}
222
223[[nodiscard]] hi_inline bool os_settings::gather_uniform_HDR()
224{
225 // Microsoft Windows 10 switches display mode when getting a HDR surface
226 // The switching causes all application to display using a different color and brightness calibration.
227 return false;
228}
229
230[[nodiscard]] hi_inline std::chrono::milliseconds os_settings::gather_double_click_interval()
231{
233}
234
235[[nodiscard]] hi_inline float os_settings::gather_double_click_distance()
236{
237 auto const width = GetSystemMetrics(SM_CXDOUBLECLK);
238 if (width <= 0) {
239 throw os_error("Could not retrieve SM_CXDOUBLECLK");
240 }
241
242 auto const height = GetSystemMetrics(SM_CYDOUBLECLK);
243 if (height <= 0) {
244 throw os_error("Could not retrieve SM_CYDOUBLECLK");
245 }
246
247 auto const diameter = std::max(width, height);
248 return diameter * 0.5f;
249}
250
251[[nodiscard]] hi_inline std::chrono::milliseconds os_settings::gather_keyboard_repeat_delay()
252{
253 using namespace std::literals::chrono_literals;
254
255 INT r;
257 throw os_error(std::format("Could not get system parameter SPI_GETKEYBOARDDELAY: {}", get_last_error_message()));
258 }
259
260 // SPI_GETKEYBOARDDELAY values are between 0 (250ms) to 3 (1s).
261 constexpr auto bias = 250ms;
262 constexpr auto gain = 250ms;
263
264 return bias + r * gain;
265}
266
267[[nodiscard]] hi_inline std::chrono::milliseconds os_settings::gather_keyboard_repeat_interval()
268{
269 using namespace std::literals::chrono_literals;
270
271 INT r;
273 throw os_error(std::format("Could not get system parameter SPI_GETKEYBOARDSPEED: {}", get_last_error_message()));
274 }
275
276 // SPI_GETKEYBOARDSPEED values are between 0 (2.5 per/sec) to 31 (30 per/sec).
277 constexpr auto bias = 2.5f;
278 constexpr auto gain = 0.887f;
279 auto const rate = bias + r * gain;
280 return std::chrono::duration_cast<std::chrono::milliseconds>(1000ms / rate);
281}
282
283[[nodiscard]] hi_inline std::chrono::milliseconds os_settings::gather_cursor_blink_interval()
284{
285 using namespace std::literals::chrono_literals;
286
287 auto const r = GetCaretBlinkTime();
288 if (r == 0) {
289 throw os_error(std::format("Could not get caret blink time: {}", get_last_error_message()));
290
291 } else if (r == INFINITE) {
293
294 } else {
295 // GetGaretBlinkTime() gives the time for a half-period.
296 return std::chrono::milliseconds{r} * 2;
297 }
298}
299
300[[nodiscard]] hi_inline std::chrono::milliseconds os_settings::gather_cursor_blink_delay()
301{
302 // The blink delay is not available in the OS, we can use the keyboard repeat delay.
303 return std::max(gather_keyboard_repeat_delay(), gather_keyboard_repeat_interval());
304}
305
306[[nodiscard]] hi_inline float os_settings::gather_minimum_window_width()
307{
308 auto const width = GetSystemMetrics(SM_CXMINTRACK);
309 if (width == 0) {
310 throw os_error("Could not retrieve SM_CXMINTRACK");
311 }
312 return narrow_cast<float>(width);
313}
314
315[[nodiscard]] hi_inline float os_settings::gather_minimum_window_height()
316{
317 auto const height = GetSystemMetrics(SM_CYMINTRACK);
318 if (height == 0) {
319 throw os_error("Could not retrieve SM_CYMINTRACK");
320 }
321
322 return narrow_cast<float>(height);
323}
324
325[[nodiscard]] hi_inline float os_settings::gather_maximum_window_width()
326{
327 auto const width = GetSystemMetrics(SM_CXMAXTRACK);
328 if (width == 0) {
329 throw os_error("Could not retrieve SM_CXMAXTRACK");
330 }
331 return narrow_cast<float>(width);
332}
333
334[[nodiscard]] hi_inline float os_settings::gather_maximum_window_height()
335{
336 auto const height = GetSystemMetrics(SM_CYMAXTRACK);
337 if (height == 0) {
338 throw os_error("Could not retrieve SM_CYMAXTRACK");
339 }
340
341 return narrow_cast<float>(height);
342}
343
344[[nodiscard]] hi_inline uintptr_t os_settings::gather_primary_monitor_id()
345{
346 auto const origin = POINT{0, 0};
348 return std::bit_cast<uintptr_t>(monitor);
349}
350
351[[nodiscard]] hi_inline aarectangle os_settings::gather_primary_monitor_rectangle()
352{
353 auto const width = GetSystemMetrics(SM_CXSCREEN);
354 if (width == 0) {
355 throw os_error("Could not retrieve SM_CXSCREEN");
356 }
357
358 auto const height = GetSystemMetrics(SM_CYSCREEN);
359 if (height == 0) {
360 throw os_error("Could not retrieve SM_CYSCREEN");
361 }
362
363 // The origin of the primary monitor is also the origin of the desktop.
364 return aarectangle{extent2{narrow_cast<float>(width), narrow_cast<float>(height)}};
365}
366
367[[nodiscard]] hi_inline aarectangle os_settings::gather_desktop_rectangle()
368{
370 if (primary_monitor_height == 0) {
371 throw os_error("Could not retrieve SM_CYSCREEN");
372 }
373
376
377 auto const width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
378 if (width == 0) {
379 throw os_error("Could not retrieve SM_CXVIRTUALSCREEN");
380 }
381
382 auto const height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
383 if (height == 0) {
384 throw os_error("Could not retrieve SM_CYVIRTUALSCREEN");
385 }
386
387 auto const bottom = top + height;
388
389 // Calculate the bottom as compared to a y-axis up coordinate system.
390 auto const inv_bottom = primary_monitor_height - bottom; // 0, 600
391 return aarectangle{
393}
394
395[[nodiscard]] hi_inline policy os_settings::gather_gpu_policy()
396{
397 using namespace std::literals;
398
399 auto const executable_path = executable_file().string();
400 auto const user_gpu_preferences_key = "Software\\Microsoft\\DirectX\\UserGpuPreferences";
401
403 for (auto entry : std::views::split(std::string_view{*result}, ";"sv)) {
404 auto entry_sv = std::string_view{entry};
405 if (entry_sv.starts_with("GpuPreference=")) {
406 if (entry_sv.ends_with("=0")) {
407 return policy::unspecified;
408 } else if (entry_sv.ends_with("=1")) {
409 return policy::low_power;
410 } else if (entry_sv.ends_with("=2")) {
411 return policy::high_performance;
412 } else {
413 hi_log_error("Unexpected GpuPreference value \"{}\".", entry_sv);
414 return policy::unspecified;
415 }
416 }
417 }
418
419 hi_log_error("Could not find GpuPreference entry.");
420 return policy::unspecified;
421
422 } else if (result.error() == win32_error::file_not_found) {
423 return policy::unspecified;
424
425 } else{
426 hi_log_error("Could not read gpu profile policy: {}", std::error_code{result.error()}.message());
427 return policy::unspecified;
428 }
429}
430
431}} // namespace hi::v1
Rules for working with win32 headers.
@ bottom
Align to the bottom.
@ top
Align to the top.
@ left
Align the text to the left side.
std::filesystem::path executable_file() noexcept
Get the full path to this executable.
STL namespace.
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
The HikoGUI namespace.
Definition recursive_iterator.hpp:15
std::expected< T, win32_error > win32_RegGetValue(HKEY key, std::string_view path, std::string_view name)=delete
Read from the registry value.
Definition winreg.hpp:282
hi_export std::string get_last_error_message()
Get the OS error message from the last error received on this thread.
Definition exception_win32_impl.hpp:30
policy
The performance policy to use.
Definition policy.hpp:18
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:378
T addressof(T... args)
T max(T... args)
T memcpy(T... args)
T message(T... args)
T reserve(T... args)
T unexpected(T... args)