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