From b7fbf5ea0f19cac124818ee8c9a430ff1f42fab0 Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 23 Jun 2024 14:17:25 +0200 Subject: [PATCH 01/80] Move (some) menu command handling to NotifyIcon --- HDRTray/HDRTray.cpp | 8 ++------ HDRTray/NotifyIcon.cpp | 14 ++++++++++++++ HDRTray/NotifyIcon.hpp | 6 ++++-- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/HDRTray/HDRTray.cpp b/HDRTray/HDRTray.cpp index 7fe4c41..50ee074 100644 --- a/HDRTray/HDRTray.cpp +++ b/HDRTray/HDRTray.cpp @@ -185,14 +185,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId = LOWORD(wParam); // Parse the menu selections: + if (notify_icon->HandleCommand(wmId)) + break; switch (wmId) { - case IDM_ENABLE_HDR: - notify_icon->ToggleHDR(); - break; - case IDM_AUTOSTART: - notify_icon->ToggleAutostartEnabled(); - break; case IDM_EXIT: DestroyWindow(hWnd); break; diff --git a/HDRTray/NotifyIcon.cpp b/HDRTray/NotifyIcon.cpp index a1dc817..2b2ed8f 100644 --- a/HDRTray/NotifyIcon.cpp +++ b/HDRTray/NotifyIcon.cpp @@ -189,6 +189,20 @@ LRESULT NotifyIcon::HandleMessage(HWND hWnd, WPARAM wParam, LPARAM lParam) return 0; } +bool NotifyIcon::HandleCommand(int command) +{ + switch (command) + { + case IDM_ENABLE_HDR: + ToggleHDR(); + return true; + case IDM_AUTOSTART: + ToggleAutostartEnabled(); + return true; + } + return false; +} + // Quote the executable path static std::wstring get_autostart_value() { diff --git a/HDRTray/NotifyIcon.hpp b/HDRTray/NotifyIcon.hpp index fcded2b..5ea67d6 100644 --- a/HDRTray/NotifyIcon.hpp +++ b/HDRTray/NotifyIcon.hpp @@ -56,8 +56,8 @@ class NotifyIcon enum { MESSAGE = WM_USER + 11 }; - void ToggleAutostartEnabled(); - void ToggleHDR(); + // Handle (some) context menu commands + bool HandleCommand(int command); protected: void PopupIconMenu(HWND hWnd, POINT pos); @@ -68,6 +68,8 @@ class NotifyIcon void UpdateIcon(); bool IsAutostartEnabled() const; + void ToggleAutostartEnabled(); + void ToggleHDR(); }; #endif // NOTIFYICON_HPP_ From 6e4449f28b09c59351a9ed2737e7a07288832dd2 Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 23 Jun 2024 19:19:02 +0200 Subject: [PATCH 02/80] Internally rename 'autostart' to 'login startup' --- HDRTray/HDRTray.rc | 2 +- HDRTray/NotifyIcon.cpp | 54 +++++++++++++++++++++--------------------- HDRTray/NotifyIcon.hpp | 4 ++-- HDRTray/Resource.h | 2 +- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/HDRTray/HDRTray.rc b/HDRTray/HDRTray.rc index 76eea1d..9f31e6a 100644 --- a/HDRTray/HDRTray.rc +++ b/HDRTray/HDRTray.rc @@ -26,7 +26,7 @@ BEGIN POPUP "Dummy" BEGIN MENUITEM "Enable &HDR", IDM_ENABLE_HDR - MENUITEM "&Start when logging in", IDM_AUTOSTART + MENUITEM "&Start when logging in", IDM_LOGIN_STARTUP MENUITEM "&Quit", IDM_EXIT END END diff --git a/HDRTray/NotifyIcon.cpp b/HDRTray/NotifyIcon.cpp index 2b2ed8f..d0f77ac 100644 --- a/HDRTray/NotifyIcon.cpp +++ b/HDRTray/NotifyIcon.cpp @@ -57,8 +57,8 @@ static void InitDarkModeSupport() has_dark_mode_support = SetPreferredAppMode && FlushMenuThemes; } -static const wchar_t autostart_registry_path[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"; -static const wchar_t autostart_registry_key[] = L"HDRTray"; +static const wchar_t loginstartup_registry_path[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"; +static const wchar_t loginstartup_registry_key[] = L"HDRTray"; // Wraps Shell_NotifyIconW(), prints to debug output in case of a failure static BOOL wrap_Shell_NotifyIconW(DWORD message, NOTIFYICONDATAW* data) @@ -196,15 +196,15 @@ bool NotifyIcon::HandleCommand(int command) case IDM_ENABLE_HDR: ToggleHDR(); return true; - case IDM_AUTOSTART: - ToggleAutostartEnabled(); + case IDM_LOGIN_STARTUP: + ToggleLoginStartupEnabled(); return true; } return false; } // Quote the executable path -static std::wstring get_autostart_value() +static std::wstring get_loginstartup_value() { wchar_t* exe_path = nullptr; _get_wpgmptr(&exe_path); @@ -217,11 +217,11 @@ static std::wstring get_autostart_value() return result; } -static bool is_autostart_enabled(HKEY key_autostart, const wchar_t* autostart_value) +static bool is_loginstartup_enabled(HKEY key_loginstartup, const wchar_t* loginstartup_value) { DWORD value_type = 0; DWORD value_size = 0; - auto query_value_res = RegQueryValueExW(key_autostart, autostart_registry_key, nullptr, &value_type, nullptr, &value_size); + auto query_value_res = RegQueryValueExW(key_loginstartup, loginstartup_registry_key, nullptr, &value_type, nullptr, &value_size); bool has_auto_start = (query_value_res == ERROR_SUCCESS) || (query_value_res == ERROR_MORE_DATA); if(has_auto_start) has_auto_start &= value_type == REG_SZ; @@ -230,11 +230,11 @@ static bool is_autostart_enabled(HKEY key_autostart, const wchar_t* autostart_va DWORD value_len = value_size / sizeof(WCHAR); DWORD buf_size = (value_len + 1) * sizeof(WCHAR); auto* buf = reinterpret_cast(_alloca(buf_size)); - if (RegQueryValueExW(key_autostart, autostart_registry_key, nullptr, nullptr, reinterpret_cast(buf), + if (RegQueryValueExW(key_loginstartup, loginstartup_registry_key, nullptr, nullptr, reinterpret_cast(buf), &buf_size) == ERROR_SUCCESS) { buf[value_len] = 0; - has_auto_start = _wcsicmp(buf, autostart_value) == 0; + has_auto_start = _wcsicmp(buf, loginstartup_value) == 0; } else { has_auto_start = false; } @@ -243,27 +243,27 @@ static bool is_autostart_enabled(HKEY key_autostart, const wchar_t* autostart_va return has_auto_start; } -void NotifyIcon::ToggleAutostartEnabled() +void NotifyIcon::ToggleLoginStartupEnabled() { - HKEY key_autostart = nullptr; - if (RegCreateKeyExW(HKEY_CURRENT_USER, autostart_registry_path, 0, nullptr, 0, - KEY_READ | KEY_WRITE | KEY_QUERY_VALUE | KEY_SET_VALUE, nullptr, &key_autostart, nullptr) + HKEY key_loginstartup = nullptr; + if (RegCreateKeyExW(HKEY_CURRENT_USER, loginstartup_registry_path, 0, nullptr, 0, + KEY_READ | KEY_WRITE | KEY_QUERY_VALUE | KEY_SET_VALUE, nullptr, &key_loginstartup, nullptr) != ERROR_SUCCESS) return; - auto autostart_value = get_autostart_value(); + auto loginstartup_value = get_loginstartup_value(); - auto has_auto_start = is_autostart_enabled(key_autostart, autostart_value.c_str()); + auto has_auto_start = is_loginstartup_enabled(key_loginstartup, loginstartup_value.c_str()); bool new_auto_start = !has_auto_start; if(new_auto_start) { - RegSetValueExW(key_autostart, autostart_registry_key, 0, REG_SZ, reinterpret_cast(autostart_value.c_str()), - static_cast((autostart_value.size() + 1) * sizeof(WCHAR))); + RegSetValueExW(key_loginstartup, loginstartup_registry_key, 0, REG_SZ, reinterpret_cast(loginstartup_value.c_str()), + static_cast((loginstartup_value.size() + 1) * sizeof(WCHAR))); } else { - RegDeleteValueW(key_autostart, autostart_registry_key); + RegDeleteValueW(key_loginstartup, loginstartup_registry_key); } - RegCloseKey(key_autostart); + RegCloseKey(key_loginstartup); } void NotifyIcon::ToggleHDR() @@ -298,8 +298,8 @@ void NotifyIcon::PopupIconMenu(HWND hWnd, POINT pos) MENUITEMINFOW mii = { sizeof(MENUITEMINFOW) }; mii.fMask = MIIM_STATE; - mii.fState = IsAutostartEnabled() ? MFS_CHECKED : MFS_UNCHECKED; - SetMenuItemInfoW(popup_menu, IDM_AUTOSTART, false, &mii); + mii.fState = IsLoginStartupEnabled() ? MFS_CHECKED : MFS_UNCHECKED; + SetMenuItemInfoW(popup_menu, IDM_LOGIN_STARTUP, false, &mii); wchar_t str_hdr_unsupported[256]; mii = { sizeof(MENUITEMINFOW) }; @@ -370,17 +370,17 @@ void NotifyIcon::UpdateIcon() } -bool NotifyIcon::IsAutostartEnabled() const +bool NotifyIcon::IsLoginStartupEnabled() const { - HKEY key_autostart = nullptr; - if (RegOpenKeyExW(HKEY_CURRENT_USER, autostart_registry_path, 0, KEY_READ | KEY_QUERY_VALUE, &key_autostart) != ERROR_SUCCESS) + HKEY key_loginstartup = nullptr; + if (RegOpenKeyExW(HKEY_CURRENT_USER, loginstartup_registry_path, 0, KEY_READ | KEY_QUERY_VALUE, &key_loginstartup) != ERROR_SUCCESS) return false; - auto autostart_value = get_autostart_value(); + auto loginstartup_value = get_loginstartup_value(); - auto has_auto_start = is_autostart_enabled(key_autostart, autostart_value.c_str()); + auto has_auto_start = is_loginstartup_enabled(key_loginstartup, loginstartup_value.c_str()); - RegCloseKey(key_autostart); + RegCloseKey(key_loginstartup); return has_auto_start; } diff --git a/HDRTray/NotifyIcon.hpp b/HDRTray/NotifyIcon.hpp index 5ea67d6..630ed25 100644 --- a/HDRTray/NotifyIcon.hpp +++ b/HDRTray/NotifyIcon.hpp @@ -67,8 +67,8 @@ class NotifyIcon void FetchDarkMode(); void UpdateIcon(); - bool IsAutostartEnabled() const; - void ToggleAutostartEnabled(); + bool IsLoginStartupEnabled() const; + void ToggleLoginStartupEnabled(); void ToggleHDR(); }; diff --git a/HDRTray/Resource.h b/HDRTray/Resource.h index 3d78cb3..cfe5257 100644 --- a/HDRTray/Resource.h +++ b/HDRTray/Resource.h @@ -24,7 +24,7 @@ #define IDS_TOGGLE_HDR_ERROR 106 #define IDM_EXIT 101 -#define IDM_AUTOSTART 102 +#define IDM_LOGIN_STARTUP 102 #define IDM_ENABLE_HDR 103 #define IDI_APP 1 From 3623444a9fc52561d8e5753385118db0d8d10f2a Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 23 Jun 2024 18:19:08 +0200 Subject: [PATCH 03/80] Move 'login startup' config into separate class --- HDRTray/HDRTray.cpp | 1 + HDRTray/NotifyIcon.cpp | 87 +++----------------------- HDRTray/NotifyIcon.hpp | 1 - common/CMakeLists.txt | 3 + common/LoginStartupConfig.cpp | 111 ++++++++++++++++++++++++++++++++++ common/LoginStartupConfig.hpp | 48 +++++++++++++++ common/RegistryKey.hpp | 59 ++++++++++++++++++ 7 files changed, 232 insertions(+), 78 deletions(-) create mode 100644 common/LoginStartupConfig.cpp create mode 100644 common/LoginStartupConfig.hpp create mode 100644 common/RegistryKey.hpp diff --git a/HDRTray/HDRTray.cpp b/HDRTray/HDRTray.cpp index 50ee074..83bf7b9 100644 --- a/HDRTray/HDRTray.cpp +++ b/HDRTray/HDRTray.cpp @@ -19,6 +19,7 @@ #include "framework.h" #include "HDRTray.h" #include "HDR.h" +#include "LoginStartupConfig.hpp" #include "NotifyIcon.hpp" #include "WinVerCheck.hpp" diff --git a/HDRTray/NotifyIcon.cpp b/HDRTray/NotifyIcon.cpp index d0f77ac..01a353d 100644 --- a/HDRTray/NotifyIcon.cpp +++ b/HDRTray/NotifyIcon.cpp @@ -18,6 +18,7 @@ #include "NotifyIcon.hpp" +#include "LoginStartupConfig.hpp" #include "Resource.h" #include "WinVerCheck.hpp" @@ -57,9 +58,6 @@ static void InitDarkModeSupport() has_dark_mode_support = SetPreferredAppMode && FlushMenuThemes; } -static const wchar_t loginstartup_registry_path[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"; -static const wchar_t loginstartup_registry_key[] = L"HDRTray"; - // Wraps Shell_NotifyIconW(), prints to debug output in case of a failure static BOOL wrap_Shell_NotifyIconW(DWORD message, NOTIFYICONDATAW* data) { @@ -203,67 +201,15 @@ bool NotifyIcon::HandleCommand(int command) return false; } -// Quote the executable path -static std::wstring get_loginstartup_value() -{ - wchar_t* exe_path = nullptr; - _get_wpgmptr(&exe_path); - - std::wstring result; - result.reserve(wcslen(exe_path) + 2); - result.push_back('"'); - result.append(exe_path); - result.push_back('"'); - return result; -} - -static bool is_loginstartup_enabled(HKEY key_loginstartup, const wchar_t* loginstartup_value) -{ - DWORD value_type = 0; - DWORD value_size = 0; - auto query_value_res = RegQueryValueExW(key_loginstartup, loginstartup_registry_key, nullptr, &value_type, nullptr, &value_size); - bool has_auto_start = (query_value_res == ERROR_SUCCESS) || (query_value_res == ERROR_MORE_DATA); - if(has_auto_start) - has_auto_start &= value_type == REG_SZ; - if(has_auto_start) - { - DWORD value_len = value_size / sizeof(WCHAR); - DWORD buf_size = (value_len + 1) * sizeof(WCHAR); - auto* buf = reinterpret_cast(_alloca(buf_size)); - if (RegQueryValueExW(key_loginstartup, loginstartup_registry_key, nullptr, nullptr, reinterpret_cast(buf), - &buf_size) - == ERROR_SUCCESS) { - buf[value_len] = 0; - has_auto_start = _wcsicmp(buf, loginstartup_value) == 0; - } else { - has_auto_start = false; - } - } - - return has_auto_start; -} - void NotifyIcon::ToggleLoginStartupEnabled() { - HKEY key_loginstartup = nullptr; - if (RegCreateKeyExW(HKEY_CURRENT_USER, loginstartup_registry_path, 0, nullptr, 0, - KEY_READ | KEY_WRITE | KEY_QUERY_VALUE | KEY_SET_VALUE, nullptr, &key_loginstartup, nullptr) - != ERROR_SUCCESS) - return; - - auto loginstartup_value = get_loginstartup_value(); - - auto has_auto_start = is_loginstartup_enabled(key_loginstartup, loginstartup_value.c_str()); - - bool new_auto_start = !has_auto_start; - if(new_auto_start) { - RegSetValueExW(key_loginstartup, loginstartup_registry_key, 0, REG_SZ, reinterpret_cast(loginstartup_value.c_str()), - static_cast((loginstartup_value.size() + 1) * sizeof(WCHAR))); - } else { - RegDeleteValueW(key_loginstartup, loginstartup_registry_key); - } + // Determine flag based on whether menu item was checked or not + MENUITEMINFOW mii = { sizeof(MENUITEMINFOW) }; + mii.fMask = MIIM_STATE; + GetMenuItemInfoW(popup_menu, IDM_LOGIN_STARTUP, false, &mii); - RegCloseKey(key_loginstartup); + bool loginstartup_enabled = mii.fState == MFS_CHECKED; + LoginStartupConfig::instance().SetEnabled(!loginstartup_enabled); } void NotifyIcon::ToggleHDR() @@ -296,9 +242,11 @@ void NotifyIcon::PopupIconMenu(HWND hWnd, POINT pos) // needed to clicking "outside" the menu works SetForegroundWindow(hWnd); + auto loginstartup_enabled = LoginStartupConfig::instance().IsEnabled(); + MENUITEMINFOW mii = { sizeof(MENUITEMINFOW) }; mii.fMask = MIIM_STATE; - mii.fState = IsLoginStartupEnabled() ? MFS_CHECKED : MFS_UNCHECKED; + mii.fState = (loginstartup_enabled && *loginstartup_enabled) ? MFS_CHECKED : MFS_UNCHECKED; SetMenuItemInfoW(popup_menu, IDM_LOGIN_STARTUP, false, &mii); wchar_t str_hdr_unsupported[256]; @@ -369,18 +317,3 @@ void NotifyIcon::UpdateIcon() Shell_NotifyIconW(NIM_MODIFY, ¬ify_mod); } - -bool NotifyIcon::IsLoginStartupEnabled() const -{ - HKEY key_loginstartup = nullptr; - if (RegOpenKeyExW(HKEY_CURRENT_USER, loginstartup_registry_path, 0, KEY_READ | KEY_QUERY_VALUE, &key_loginstartup) != ERROR_SUCCESS) - return false; - - auto loginstartup_value = get_loginstartup_value(); - - auto has_auto_start = is_loginstartup_enabled(key_loginstartup, loginstartup_value.c_str()); - - RegCloseKey(key_loginstartup); - - return has_auto_start; -} diff --git a/HDRTray/NotifyIcon.hpp b/HDRTray/NotifyIcon.hpp index 630ed25..b041b4a 100644 --- a/HDRTray/NotifyIcon.hpp +++ b/HDRTray/NotifyIcon.hpp @@ -67,7 +67,6 @@ class NotifyIcon void FetchDarkMode(); void UpdateIcon(); - bool IsLoginStartupEnabled() const; void ToggleLoginStartupEnabled(); void ToggleHDR(); }; diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 764e01c..8dcfdc5 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -1,7 +1,10 @@ add_library(common STATIC) target_sources(common PRIVATE + "LoginStartupConfig.hpp" + "LoginStartupConfig.cpp" "HDR.h" "HDR.cpp" + "RegistryKey.hpp" "WinVerCheck.hpp" ) target_compile_definitions(common PRIVATE UNICODE _UNICODE) diff --git a/common/LoginStartupConfig.cpp b/common/LoginStartupConfig.cpp new file mode 100644 index 0000000..b90556d --- /dev/null +++ b/common/LoginStartupConfig.cpp @@ -0,0 +1,111 @@ +/* + HDRTray, a notification icon for the "Use HDR" option + Copyright (C) 2024 Frank Richter + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "LoginStartupConfig.hpp" + +#include "RegistryKey.hpp" + +#include +#include + +static std::unique_ptr loginstartup_singleton; + +LoginStartupConfig& LoginStartupConfig::instance() +{ + if (!loginstartup_singleton) + loginstartup_singleton.reset(new LoginStartupConfig); + return *loginstartup_singleton; +} + +LoginStartupConfig::LoginStartupConfig() +{ + wchar_t* exe_path = nullptr; + _get_wpgmptr(&exe_path); + + loginstartup_exe = exe_path; + // Always use HDRTray.exe as the loginstartup path, so even HDRCmd will control the tray icon behavior + loginstartup_exe = loginstartup_exe.parent_path() / "HDRTray.exe"; +} + +static const wchar_t loginstartup_registry_path[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"; +static const wchar_t loginstartup_registry_key[] = L"HDRTray"; + +std::expected LoginStartupConfig::IsEnabled() const +{ + RegistryKey key_loginstartup; + auto create_result = + key_loginstartup.Create(HKEY_CURRENT_USER, loginstartup_registry_path, 0, KEY_READ | KEY_QUERY_VALUE, nullptr); + if (create_result != ERROR_SUCCESS) + return std::unexpected(create_result); + + DWORD value_type = 0; + DWORD value_size = 0; + auto query_value_res = key_loginstartup.QueryValueEx(loginstartup_registry_key, &value_type, nullptr, &value_size); + if (query_value_res == ERROR_FILE_NOT_FOUND) + return false; + else if (query_value_res != ERROR_SUCCESS && query_value_res != ERROR_MORE_DATA) + return std::unexpected(query_value_res); + if (value_type != REG_SZ) + return false; + + DWORD value_len = value_size / sizeof(WCHAR); + DWORD buf_size = (value_len + 1) * sizeof(WCHAR); + auto* buf = reinterpret_cast(_alloca(buf_size)); + query_value_res = + key_loginstartup.QueryValueEx(loginstartup_registry_key, nullptr, reinterpret_cast(buf), &buf_size); + if (query_value_res != ERROR_SUCCESS) + return std::unexpected(query_value_res); + + buf[value_len] = 0; + auto loginstartup_value = std::wstring_view(buf); + // Strip surrounding '"' + if (loginstartup_value.size() >= 2 && *loginstartup_value.begin() == '"' && *loginstartup_value.rbegin() == '"') + loginstartup_value = loginstartup_value.substr(1, loginstartup_value.size() - 2); + if (loginstartup_value.empty()) + return false; + + std::error_code ec; + bool loginstartup_enabled = std::filesystem::equivalent(loginstartup_exe, loginstartup_value, ec); + if (ec) + return std::unexpected(ec.value()); // Works b/c MSVC std::filesystem returns Windows error codes + return loginstartup_enabled; +} + +std::expected LoginStartupConfig::SetEnabled(bool flag) +{ + RegistryKey key_loginstartup; + auto create_result = key_loginstartup.Create(HKEY_CURRENT_USER, loginstartup_registry_path, 0, + KEY_READ | KEY_WRITE | KEY_QUERY_VALUE | KEY_SET_VALUE, nullptr); + if (create_result != ERROR_SUCCESS) + return std::unexpected(create_result); + + if(flag) { + std::wstring loginstartup_value; + loginstartup_value.reserve(loginstartup_exe.native().size() + 2); + loginstartup_value.push_back('"'); + loginstartup_value.append(loginstartup_exe.native()); + loginstartup_value.push_back('"'); + + key_loginstartup.SetValueEx(loginstartup_registry_key, REG_SZ, reinterpret_cast(loginstartup_value.c_str()), + static_cast((loginstartup_value.size() + 1) * sizeof(WCHAR))); + } else { + key_loginstartup.DeleteValue(loginstartup_registry_key); + } + + return {}; +} diff --git a/common/LoginStartupConfig.hpp b/common/LoginStartupConfig.hpp new file mode 100644 index 0000000..a8f4405 --- /dev/null +++ b/common/LoginStartupConfig.hpp @@ -0,0 +1,48 @@ +/* + HDRTray, a notification icon for the "Use HDR" option + Copyright (C) 2024 Frank Richter + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/**\file + * LoginStartup/"Start when logging in" configuration + */ +#ifndef LOGINSTARTUPCONFIG_HPP_ +#define LOGINSTARTUPCONFIG_HPP_ + +#include "framework.h" + +#include +#include + +class LoginStartupConfig +{ +protected: + /// Path to executable to loginstartup + std::filesystem::path loginstartup_exe; + + LoginStartupConfig(); + +public: + /// Return reference to singleton + static LoginStartupConfig& instance(); + + /// Whether loginstartup is currently enabled + std::expected IsEnabled() const; + /// Enable/disable loginstartup + std::expected SetEnabled(bool flag); +}; + +#endif // LOGINSTARTUPCONFIG_HPP_ diff --git a/common/RegistryKey.hpp b/common/RegistryKey.hpp new file mode 100644 index 0000000..cd47014 --- /dev/null +++ b/common/RegistryKey.hpp @@ -0,0 +1,59 @@ +/* + HDRTray, a notification icon for the "Use HDR" option + Copyright (C) 2024 Frank Richter + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/**\file + * Convenience class for registry access + */ +#ifndef REGISTRYKEY_HPP_ +#define REGISTRYKEY_HPP_ + +#include "framework.h" + +class RegistryKey +{ + HKEY key = nullptr; + +public: + ~RegistryKey() + { + if (key) + RegCloseKey(key); + } + + operator HKEY() const { return key; } + + /// Wrapper around RegCreateKeyExW(), storing the created key internally + LRESULT Create(HKEY key, LPCWSTR subkey, DWORD options, REGSAM samDesired, const LPSECURITY_ATTRIBUTES security_attributes) + { + return RegCreateKeyExW(key, subkey, 0, nullptr, options, samDesired, security_attributes, &this->key, nullptr); + } + /// Wrapper around RegQueryValueExW(), using the internally stored key + LRESULT QueryValueEx(LPCWSTR valueName, LPDWORD type, LPBYTE data, LPDWORD data_size) const + { + return RegQueryValueExW(key, valueName, nullptr, type, data, data_size); + } + /// Wrapper around RegSetValueExW(), using the internally stored key + LSTATUS SetValueEx(LPCWSTR valueName, DWORD type, const BYTE* data, DWORD data_size) const + { + return RegSetValueExW(key, valueName, 0, type, data, data_size); + } + /// Wrapper around RegDeleteValueW(), using the internally stored key + LSTATUS DeleteValue(LPCWSTR valueName) const { return RegDeleteValueW(key, valueName); } +}; + +#endif // REGISTRYKEY_HPP_ From 5ab1e34a438f0d68dcbbfca80457d8d69c68f02a Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 23 Jun 2024 21:20:10 +0200 Subject: [PATCH 04/80] HDRCmd: Add ability to change 'startup when logging in' --- HDRCmd/CMakeLists.txt | 2 + HDRCmd/HDRCmd.cpp | 2 + HDRCmd/subcommand/LoginStartup.cpp | 78 ++++++++++++++++++++++++++++++ HDRCmd/subcommand/LoginStartup.hpp | 41 ++++++++++++++++ 4 files changed, 123 insertions(+) create mode 100644 HDRCmd/subcommand/LoginStartup.cpp create mode 100644 HDRCmd/subcommand/LoginStartup.hpp diff --git a/HDRCmd/CMakeLists.txt b/HDRCmd/CMakeLists.txt index 836c6b6..e3e8644 100644 --- a/HDRCmd/CMakeLists.txt +++ b/HDRCmd/CMakeLists.txt @@ -11,6 +11,8 @@ target_sources(HDRCmd PRIVATE "subcommand/Disable.cpp" "subcommand/Enable.hpp" "subcommand/Enable.cpp" + "subcommand/LoginStartup.hpp" + "subcommand/LoginStartup.cpp" "subcommand/Status.hpp" "subcommand/Status.cpp" ) diff --git a/HDRCmd/HDRCmd.cpp b/HDRCmd/HDRCmd.cpp index bdb7c2f..7bf2b22 100644 --- a/HDRCmd/HDRCmd.cpp +++ b/HDRCmd/HDRCmd.cpp @@ -20,6 +20,7 @@ #include "subcommand/Disable.hpp" #include "subcommand/Enable.hpp" +#include "subcommand/LoginStartup.hpp" #include "subcommand/Status.hpp" #include "version.h" #include "WinVerCheck.hpp" @@ -48,6 +49,7 @@ int wmain(int argc, const wchar_t* const argv[]) subcommand::Status::add(app); subcommand::Enable::add(app); subcommand::Disable::add(app); + subcommand::LoginStartup::add(app); CLI11_PARSE(app, argc, argv); const auto* subcmd = app.get_subcommands()[0]; diff --git a/HDRCmd/subcommand/LoginStartup.cpp b/HDRCmd/subcommand/LoginStartup.cpp new file mode 100644 index 0000000..e059760 --- /dev/null +++ b/HDRCmd/subcommand/LoginStartup.cpp @@ -0,0 +1,78 @@ +/* + HDRCmd - enable/disable "Use HDR" from command line + Copyright (C) 2024 Frank Richter + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "LoginStartup.hpp" + +#include "LoginStartupConfig.hpp" + +#include +#include +#include + +namespace subcommand { + +LoginStartup::LoginStartup(CLI::App* parent) : Base("Change 'Start when logging in'", "startup", parent) +{ + add_subcommand("status", "Print current status of option"); + add_subcommand("on", "Turn 'Start when logging in' on"); + add_subcommand("off", "Turn 'Start when logging in' off"); +} + +int LoginStartup::print_status(bool active) +{ + auto status_result = LoginStartupConfig::instance().IsEnabled(); + if (!status_result) { + std::println("Could not get login startup state, error {}", status_result.error()); + return status_result.error(); + } + + auto status_str = *status_result ? "ON" : "OFF"; + if (active) + std::println("'Start when logging in' is now {}", status_str); + else + std::println("'Start when logging in' is {}", status_str); + return 0; +} + +int LoginStartup::run() const +{ + bool has_status = got_subcommand("status"); + bool has_on = got_subcommand("on"); + bool has_off = got_subcommand("off"); + + if (has_on || has_off) { + auto enable_result = LoginStartupConfig::instance().SetEnabled(has_on); + if (!enable_result) { + std::println("Could not change login startup state, error {}", enable_result.error()); + return enable_result.error(); + } + } + + if (!has_status && !has_on && !has_off) { + // No explicit command given: also include help text + std::cout << help() << std::endl; + } + return print_status(has_on || has_off); +} + +CLI::App* LoginStartup::add(CLI::App& app) +{ + return app.add_subcommand(std::shared_ptr(new LoginStartup(&app))); +} + +} // namespace subcommand diff --git a/HDRCmd/subcommand/LoginStartup.hpp b/HDRCmd/subcommand/LoginStartup.hpp new file mode 100644 index 0000000..5b63dc5 --- /dev/null +++ b/HDRCmd/subcommand/LoginStartup.hpp @@ -0,0 +1,41 @@ +/* + HDRCmd - enable/disable "Use HDR" from command line + Copyright (C) 2024 Frank Richter + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef SUBCOMMAND_LOGINSTARTUP_HPP_ +#define SUBCOMMAND_LOGINSTARTUP_HPP_ + +#include "Base.hpp" + +namespace subcommand { + +class LoginStartup : public Base +{ + static int print_status(bool active); + +protected: + LoginStartup(CLI::App* parent); + +public: + int run() const override; + + static CLI::App* add(CLI::App& app); +}; + +} // namespace subcommand + +#endif // SUBCOMMAND_LOGINSTARTUP_HPP_ From b3054909128e17b1b71989afeb3d47030ee7b3f7 Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Mon, 24 Jun 2024 01:48:52 +0200 Subject: [PATCH 05/80] Add docs for new 'status' HDRCmd command --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 94d032f..cc24616 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,18 @@ Specifies how the status should be reported. Accepts the following values: * `long`, `l`: Print the overall HDR status and status per display. * `exitcode`, `x`: Special mode for scripting. Exit code is 0 if HDR is on, 1 if HDR is off, and 2 if HDR is unsupported. (Other values indicate some error.) +## `startup` command +Prints or changes the 'Start when logging in' option for the notification area icon to the console. + +### `status` subcommand +Prints the status of 'Start when logging in' option for the notification area icon to the console. Default if no other subcommand was specified. + +### `on` subcommand +Turn the 'Start when logging in' option on. HDRTray will be automatically started beginning with the next login. Prints new status to the console. + +### `off` subcommand +Turn the 'Start when logging in' option off. HDRTray will no longer be automatically started upon login. Prints new status to the console. + Contributed scripts ------------------- A number of people shared scripts they created that use `HDRCmd` to automate HDR toggling. Check them out in the [“Show and Tell” discussion category](https://github.com/res2k/HDRTray/discussions/categories/show-and-tell). From 5f2db5424e92a93b3b738e0c6e97648af8b86c25 Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Mon, 24 Jun 2024 01:31:08 +0200 Subject: [PATCH 06/80] Rename hdr::Display to hdr::DisplayInfo --- common/HDR.cpp | 6 +++--- common/HDR.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/HDR.cpp b/common/HDR.cpp index 3843c53..da692a5 100644 --- a/common/HDR.cpp +++ b/common/HDR.cpp @@ -172,12 +172,12 @@ static const wchar_t* GetFallbackDisplayName(const DISPLAYCONFIG_MODE_INFO& mode return L"Unnamed"; } -std::vector GetDisplays() +std::vector GetDisplays() { - std::vector result; + std::vector result; ForEachDisplay([&](const DISPLAYCONFIG_MODE_INFO& mode) { - Display new_disp; + DisplayInfo new_disp; new_disp.status = GetDisplayHDRStatus(mode); diff --git a/common/HDR.h b/common/HDR.h index 8406221..7957138 100644 --- a/common/HDR.h +++ b/common/HDR.h @@ -20,7 +20,7 @@ namespace hdr { enum class Status { Unsupported = 0, Off = 1, On = 2 }; /// Display information -struct Display +struct DisplayInfo { /// Display name std::wstring name; @@ -32,7 +32,7 @@ Status GetWindowsHDRStatus(); std::optional SetWindowsHDRStatus(bool enable); std::optional ToggleHDRStatus(); /// Get information for all displays -std::vector GetDisplays(); +std::vector GetDisplays(); } // namespace hdr From 2fc7cb66f22e4ba2cb68e2b27835eff697aebc89 Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 30 Jun 2024 18:40:55 +0200 Subject: [PATCH 07/80] Add & use hdr::DisplayID internal type --- common/HDR.cpp | 68 ++++++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/common/HDR.cpp b/common/HDR.cpp index da692a5..063feb4 100644 --- a/common/HDR.cpp +++ b/common/HDR.cpp @@ -24,6 +24,22 @@ namespace hdr { static const bool use_win11_24h2_color_functions = IsWindows11_24H2OrGreater(); +struct DisplayID +{ + LUID adapter; + UINT32 id; + + static DisplayID FromMode(const DISPLAYCONFIG_MODE_INFO& mode) + { + return DisplayID { .adapter = mode.adapterId, .id = mode.id }; + } + void ToDeviceInputHeader(DISPLAYCONFIG_DEVICE_INFO_HEADER& header) const + { + header.adapterId = adapter; + header.id = id; + } +}; + template static void ForEachDisplay(F func) { uint32_t pathCount = 0; @@ -41,19 +57,17 @@ template static void ForEachDisplay(F func) for (const auto& path : paths) { const auto& mode = modes.at(path.targetInfo.modeInfoIdx); - func(mode); + func(DisplayID::FromMode(mode)); } } -static Status GetDisplayHDRStatus(const DISPLAYCONFIG_MODE_INFO& mode) +static Status GetDisplayHDRStatus(const DisplayID& display) { // Prefer GET_ADVANCED_COLOR_INFO_2, this reports the actual HDR mode if ACM is enabled DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2 getColorInfo2 = {}; getColorInfo2.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2; getColorInfo2.header.size = sizeof(getColorInfo2); - getColorInfo2.header.adapterId.HighPart = mode.adapterId.HighPart; - getColorInfo2.header.adapterId.LowPart = mode.adapterId.LowPart; - getColorInfo2.header.id = mode.id; + display.ToDeviceInputHeader(getColorInfo2.header); if (use_win11_24h2_color_functions && DisplayConfigGetDeviceInfo(&getColorInfo2.header) == ERROR_SUCCESS) { if (!getColorInfo2.highDynamicRangeSupported) @@ -66,9 +80,7 @@ static Status GetDisplayHDRStatus(const DISPLAYCONFIG_MODE_INFO& mode) DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO getColorInfo = {}; getColorInfo.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO; getColorInfo.header.size = sizeof(getColorInfo); - getColorInfo.header.adapterId.HighPart = mode.adapterId.HighPart; - getColorInfo.header.adapterId.LowPart = mode.adapterId.LowPart; - getColorInfo.header.id = mode.id; + display.ToDeviceInputHeader(getColorInfo.header); if (DisplayConfigGetDeviceInfo(&getColorInfo.header) != ERROR_SUCCESS) return Status::Unsupported; @@ -84,8 +96,8 @@ Status GetWindowsHDRStatus() bool anySupported = false; bool anyEnabled = false; - ForEachDisplay([&](const DISPLAYCONFIG_MODE_INFO& mode) { - Status displayStatus = GetDisplayHDRStatus(mode); + ForEachDisplay([&](const DisplayID& display) { + Status displayStatus = GetDisplayHDRStatus(display); anySupported |= displayStatus != Status::Unsupported; anyEnabled |= displayStatus == Status::On; }); @@ -96,9 +108,9 @@ Status GetWindowsHDRStatus() return Status::Unsupported; } -static std::optional SetDisplayHDRStatus(const DISPLAYCONFIG_MODE_INFO& mode, bool enable) +static std::optional SetDisplayHDRStatus(const DisplayID& display, bool enable) { - if (GetDisplayHDRStatus(mode) == Status::Unsupported) + if (GetDisplayHDRStatus(display) == Status::Unsupported) return std::nullopt; /* Try SET_HDR_STATE first, if available (on Windows 11 >= 24H2). @@ -107,33 +119,29 @@ static std::optional SetDisplayHDRStatus(const DISPLAYCONFIG_MODE_INFO& DISPLAYCONFIG_SET_HDR_STATE setHdrState = {}; setHdrState.header.type = DISPLAYCONFIG_DEVICE_INFO_SET_HDR_STATE; setHdrState.header.size = sizeof(setHdrState); - setHdrState.header.adapterId.HighPart = mode.adapterId.HighPart; - setHdrState.header.adapterId.LowPart = mode.adapterId.LowPart; - setHdrState.header.id = mode.id; + display.ToDeviceInputHeader(setHdrState.header); setHdrState.enableHdr = enable; if (use_win11_24h2_color_functions && DisplayConfigSetDeviceInfo(&setHdrState.header) == ERROR_SUCCESS) - return GetDisplayHDRStatus(mode); + return GetDisplayHDRStatus(display); DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE setColorState = {}; setColorState.header.type = DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE; setColorState.header.size = sizeof(setColorState); - setColorState.header.adapterId.HighPart = mode.adapterId.HighPart; - setColorState.header.adapterId.LowPart = mode.adapterId.LowPart; - setColorState.header.id = mode.id; + display.ToDeviceInputHeader(setColorState.header); setColorState.enableAdvancedColor = enable; if (DisplayConfigSetDeviceInfo(&setColorState.header) != ERROR_SUCCESS) return std::nullopt; // Don't assume changing the HDR mode was successful... re-query the status - return GetDisplayHDRStatus(mode); + return GetDisplayHDRStatus(display); } std::optional SetWindowsHDRStatus(bool enable) { std::optional status; - ForEachDisplay([&](const DISPLAYCONFIG_MODE_INFO& mode) { - auto new_status = SetDisplayHDRStatus(mode, enable); + ForEachDisplay([&](const DisplayID& display) { + auto new_status = SetDisplayHDRStatus(display, enable); if (!new_status) return; @@ -154,14 +162,12 @@ std::optional ToggleHDRStatus() return SetWindowsHDRStatus(status == Status::Off ? true : false); } -static const wchar_t* GetFallbackDisplayName(const DISPLAYCONFIG_MODE_INFO& mode) +static const wchar_t* GetFallbackDisplayName(const DisplayID& display) { DISPLAYCONFIG_TARGET_BASE_TYPE target_base = {}; target_base.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_BASE_TYPE; target_base.header.size = sizeof(target_base); - target_base.header.adapterId.HighPart = mode.adapterId.HighPart; - target_base.header.adapterId.LowPart = mode.adapterId.LowPart; - target_base.header.id = mode.id; + display.ToDeviceInputHeader(target_base.header); if (ERROR_SUCCESS == DisplayConfigGetDeviceInfo(&target_base.header)) { if ((target_base.baseOutputTechnology != DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER) @@ -176,24 +182,22 @@ std::vector GetDisplays() { std::vector result; - ForEachDisplay([&](const DISPLAYCONFIG_MODE_INFO& mode) { + ForEachDisplay([&](const DisplayID& display) { DisplayInfo new_disp; - new_disp.status = GetDisplayHDRStatus(mode); + new_disp.status = GetDisplayHDRStatus(display); DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {}; deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME; deviceName.header.size = sizeof(deviceName); - deviceName.header.adapterId.HighPart = mode.adapterId.HighPart; - deviceName.header.adapterId.LowPart = mode.adapterId.LowPart; - deviceName.header.id = mode.id; + display.ToDeviceInputHeader(deviceName.header); if (DisplayConfigGetDeviceInfo(&deviceName.header) != ERROR_SUCCESS) return; if (deviceName.flags.friendlyNameFromEdid) new_disp.name = deviceName.monitorFriendlyDeviceName; else - new_disp.name = GetFallbackDisplayName(mode); // Seen with eg a laptop display. + new_disp.name = GetFallbackDisplayName(display); // Seen with eg a laptop display. result.emplace_back(std::move(new_disp)); }); From 9afc39ff83a923c6b23a8ebd3779af784a662581 Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 30 Jun 2024 18:44:14 +0200 Subject: [PATCH 08/80] Make hdr::DisplayID public --- common/HDR.cpp | 21 ++++++++------------- common/HDR.h | 11 +++++++++++ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/common/HDR.cpp b/common/HDR.cpp index 063feb4..9102f6d 100644 --- a/common/HDR.cpp +++ b/common/HDR.cpp @@ -24,21 +24,16 @@ namespace hdr { static const bool use_win11_24h2_color_functions = IsWindows11_24H2OrGreater(); -struct DisplayID +DisplayID DisplayID::FromMode(const DISPLAYCONFIG_MODE_INFO& mode) { - LUID adapter; - UINT32 id; + return DisplayID { .adapter = mode.adapterId, .id = mode.id }; +} - static DisplayID FromMode(const DISPLAYCONFIG_MODE_INFO& mode) - { - return DisplayID { .adapter = mode.adapterId, .id = mode.id }; - } - void ToDeviceInputHeader(DISPLAYCONFIG_DEVICE_INFO_HEADER& header) const - { - header.adapterId = adapter; - header.id = id; - } -}; +void DisplayID::ToDeviceInputHeader(DISPLAYCONFIG_DEVICE_INFO_HEADER& header) const +{ + header.adapterId = adapter; + header.id = id; +} template static void ForEachDisplay(F func) { diff --git a/common/HDR.h b/common/HDR.h index 7957138..d3c891c 100644 --- a/common/HDR.h +++ b/common/HDR.h @@ -15,10 +15,21 @@ #include #include +#include "framework.h" + namespace hdr { enum class Status { Unsupported = 0, Off = 1, On = 2 }; +struct DisplayID +{ + LUID adapter; + UINT32 id; + + static DisplayID FromMode(const DISPLAYCONFIG_MODE_INFO& mode); + void ToDeviceInputHeader(DISPLAYCONFIG_DEVICE_INFO_HEADER& header) const; +}; + /// Display information struct DisplayInfo { From 93556a4874db096d071ff4e2a047205840d7d719 Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 30 Jun 2024 19:41:21 +0200 Subject: [PATCH 09/80] Change hdr::DisplayInfo to lazily return status, name --- HDRCmd/subcommand/Status.cpp | 10 +-- common/HDR.cpp | 118 +++++++++++++++++++++++++---------- common/HDR.h | 31 ++++++++- 3 files changed, 119 insertions(+), 40 deletions(-) diff --git a/HDRCmd/subcommand/Status.cpp b/HDRCmd/subcommand/Status.cpp index ebba578..58bdc1c 100644 --- a/HDRCmd/subcommand/Status.cpp +++ b/HDRCmd/subcommand/Status.cpp @@ -82,6 +82,8 @@ void Status::print_status_short() void Status::print_status_long() { auto displays = hdr::GetDisplays(); + // Filter out all displays w/o name or status + std::erase_if(displays, [](const hdr::DisplayInfo& info) { return !info.GetStatus() || !info.GetName(); }); // Tabulate. // Columns: #, Display name, Status @@ -93,8 +95,8 @@ void Status::print_status_long() widths[2] = col_headings[2].size(); for(const auto& disp : displays) { - widths[1] = std::max(widths[1], disp.name.size()); - widths[2] = std::max(widths[2], status_string(disp.status).size()); + widths[1] = std::max(widths[1], disp.GetName()->size()); + widths[2] = std::max(widths[2], status_string(*disp.GetStatus()).size()); } // Print heading @@ -115,8 +117,8 @@ void Status::print_status_long() for (size_t i = 0; i < displays.size(); i++) { const auto& disp = displays[i]; - std::println("{:>{}}\t{:<{}}\t{:<{}}", i, widths[0], CLI::narrow(disp.name), widths[1], - status_string(disp.status), widths[2]); + std::println("{:>{}}\t{:<{}}\t{:<{}}", i, widths[0], CLI::narrow(*disp.GetName()), widths[1], + status_string(*disp.GetStatus()), widths[2]); } } diff --git a/common/HDR.cpp b/common/HDR.cpp index 9102f6d..224eeb8 100644 --- a/common/HDR.cpp +++ b/common/HDR.cpp @@ -35,6 +35,90 @@ void DisplayID::ToDeviceInputHeader(DISPLAYCONFIG_DEVICE_INFO_HEADER& header) co header.id = id; } +//--------------------------------------------------------------------------- + +static const wchar_t* GetFallbackDisplayName(const DisplayID& display) +{ + DISPLAYCONFIG_TARGET_BASE_TYPE target_base = {}; + target_base.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_BASE_TYPE; + target_base.header.size = sizeof(target_base); + display.ToDeviceInputHeader(target_base.header); + + if (ERROR_SUCCESS == DisplayConfigGetDeviceInfo(&target_base.header)) { + if ((target_base.baseOutputTechnology != DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER) + && (target_base.baseOutputTechnology & DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL)) + return L"Internal Display"; + } + + return L"Unnamed"; +} + +DisplayInfo::result_type DisplayInfo::GetName() const +{ + const auto& dev_name = GetCached( + deviceName, [this]() -> result_type { + DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {}; + deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME; + deviceName.header.size = sizeof(deviceName); + id.ToDeviceInputHeader(deviceName.header); + LONG result = DisplayConfigGetDeviceInfo(&deviceName.header); + if (result != ERROR_SUCCESS) + return std::unexpected(result); + return deviceName; + }, ValueFreshness::Cached); + + if (dev_name) { + if (dev_name->flags.friendlyNameFromEdid) + return dev_name->monitorFriendlyDeviceName; + else + return GetFallbackDisplayName(id); // Seen with eg a laptop display. + } + return std::unexpected(dev_name.error()); +} + +DisplayInfo::result_type DisplayInfo::GetStatus(ValueFreshness freshness) const +{ + return GetCached( + status, + [this]() -> result_type { + // Prefer GET_ADVANCED_COLOR_INFO_2, this reports the actual HDR mode if ACM is enabled + DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2 getColorInfo2 = {}; + getColorInfo2.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2; + getColorInfo2.header.size = sizeof(getColorInfo2); + id.ToDeviceInputHeader(getColorInfo2.header); + if (use_win11_24h2_color_functions && DisplayConfigGetDeviceInfo(&getColorInfo2.header) == ERROR_SUCCESS) + { + if (!getColorInfo2.highDynamicRangeSupported) + return Status::Unsupported; + + return getColorInfo2.activeColorMode == DISPLAYCONFIG_ADVANCED_COLOR_MODE_HDR ? Status::On : Status::Off; + } + + DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO getColorInfo = {}; + getColorInfo.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO; + getColorInfo.header.size = sizeof(getColorInfo); + id.ToDeviceInputHeader(getColorInfo.header); + LONG result = DisplayConfigGetDeviceInfo(&getColorInfo.header); + if (result != ERROR_SUCCESS) + return std::unexpected(result); + if (getColorInfo.advancedColorSupported) + return getColorInfo.advancedColorEnabled ? Status::On : Status::Off; + else + return Status::Unsupported; + }, + freshness); +} + +template +const DisplayInfo::result_type& DisplayInfo::GetCached(cache_type& cache, F produce, ValueFreshness freshness) const +{ + if(!cache || freshness == ValueFreshness::ForceRefresh) + cache = produce(); + return *cache; +} + +//--------------------------------------------------------------------------- + template static void ForEachDisplay(F func) { uint32_t pathCount = 0; @@ -157,44 +241,12 @@ std::optional ToggleHDRStatus() return SetWindowsHDRStatus(status == Status::Off ? true : false); } -static const wchar_t* GetFallbackDisplayName(const DisplayID& display) -{ - DISPLAYCONFIG_TARGET_BASE_TYPE target_base = {}; - target_base.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_BASE_TYPE; - target_base.header.size = sizeof(target_base); - display.ToDeviceInputHeader(target_base.header); - - if (ERROR_SUCCESS == DisplayConfigGetDeviceInfo(&target_base.header)) { - if ((target_base.baseOutputTechnology != DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER) - && (target_base.baseOutputTechnology & DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL)) - return L"Internal Display"; - } - - return L"Unnamed"; -} - std::vector GetDisplays() { std::vector result; ForEachDisplay([&](const DisplayID& display) { - DisplayInfo new_disp; - - new_disp.status = GetDisplayHDRStatus(display); - - DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {}; - deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME; - deviceName.header.size = sizeof(deviceName); - display.ToDeviceInputHeader(deviceName.header); - if (DisplayConfigGetDeviceInfo(&deviceName.header) != ERROR_SUCCESS) - return; - - if (deviceName.flags.friendlyNameFromEdid) - new_disp.name = deviceName.monitorFriendlyDeviceName; - else - new_disp.name = GetFallbackDisplayName(display); // Seen with eg a laptop display. - - result.emplace_back(std::move(new_disp)); + result.emplace_back(display); }); return result; diff --git a/common/HDR.h b/common/HDR.h index d3c891c..738b264 100644 --- a/common/HDR.h +++ b/common/HDR.h @@ -10,6 +10,8 @@ #ifndef HDR_H_ #define HDR_H_ +#include +#include #include #include #include @@ -31,12 +33,35 @@ struct DisplayID }; /// Display information -struct DisplayInfo +class DisplayInfo { +public: + DisplayInfo(DisplayID id) : id(id) {} + + template + using result_type = std::expected; + + /// Indicates how "fresh" a queried value should be + enum struct ValueFreshness { Cached, ForceRefresh }; + + /// Get name of display + result_type GetName() const; + /// Get HDR status for display + result_type GetStatus(ValueFreshness freshness = ValueFreshness::Cached) const; + +private: + template + using cache_type = std::optional>; + + /// Display ID + DisplayID id; /// Display name - std::wstring name; + mutable cache_type deviceName; /// HDR status - Status status; + mutable cache_type status; + + template + const result_type& GetCached(cache_type& cache, F produce, ValueFreshness freshness) const; }; Status GetWindowsHDRStatus(); From b5b54816adf614e3d96c6996cc91a0c186145696 Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 30 Jun 2024 19:50:20 +0200 Subject: [PATCH 10/80] Implement hdr::GetWindowsHDRStatus() using GetDisplays() --- common/HDR.cpp | 53 ++++++++++++-------------------------------------- 1 file changed, 12 insertions(+), 41 deletions(-) diff --git a/common/HDR.cpp b/common/HDR.cpp index 224eeb8..1778336 100644 --- a/common/HDR.cpp +++ b/common/HDR.cpp @@ -140,56 +140,27 @@ template static void ForEachDisplay(F func) } } -static Status GetDisplayHDRStatus(const DisplayID& display) -{ - // Prefer GET_ADVANCED_COLOR_INFO_2, this reports the actual HDR mode if ACM is enabled - DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2 getColorInfo2 = {}; - getColorInfo2.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2; - getColorInfo2.header.size = sizeof(getColorInfo2); - display.ToDeviceInputHeader(getColorInfo2.header); - if (use_win11_24h2_color_functions && DisplayConfigGetDeviceInfo(&getColorInfo2.header) == ERROR_SUCCESS) - { - if (!getColorInfo2.highDynamicRangeSupported) - return Status::Unsupported; - - // Only DISPLAYCONFIG_ADVANCED_COLOR_MODE_HDR is true HDR. - return getColorInfo2.activeColorMode == DISPLAYCONFIG_ADVANCED_COLOR_MODE_HDR ? Status::On : Status::Off; - } - - DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO getColorInfo = {}; - getColorInfo.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO; - getColorInfo.header.size = sizeof(getColorInfo); - display.ToDeviceInputHeader(getColorInfo.header); - - if (DisplayConfigGetDeviceInfo(&getColorInfo.header) != ERROR_SUCCESS) - return Status::Unsupported; - - if (!getColorInfo.advancedColorSupported) - return Status::Unsupported; - - return getColorInfo.advancedColorEnabled ? Status::On : Status::Off; -} - Status GetWindowsHDRStatus() { bool anySupported = false; bool anyEnabled = false; - ForEachDisplay([&](const DisplayID& display) { - Status displayStatus = GetDisplayHDRStatus(display); - anySupported |= displayStatus != Status::Unsupported; - anyEnabled |= displayStatus == Status::On; - }); + for (auto& display : GetDisplays()) { + auto status = display.GetStatus(); + if (!status || *status == Status::Unsupported) + continue; + anySupported = true; + anyEnabled |= *status == Status::On; + } - if (anySupported) - return anyEnabled ? Status::On : Status::Off; - else + if (!anySupported) return Status::Unsupported; + return anyEnabled ? Status::On : Status::Off; } static std::optional SetDisplayHDRStatus(const DisplayID& display, bool enable) { - if (GetDisplayHDRStatus(display) == Status::Unsupported) + if (DisplayInfo(display).GetStatus().value_or(Status::Unsupported) == Status::Unsupported) return std::nullopt; /* Try SET_HDR_STATE first, if available (on Windows 11 >= 24H2). @@ -201,7 +172,7 @@ static std::optional SetDisplayHDRStatus(const DisplayID& display, bool display.ToDeviceInputHeader(setHdrState.header); setHdrState.enableHdr = enable; if (use_win11_24h2_color_functions && DisplayConfigSetDeviceInfo(&setHdrState.header) == ERROR_SUCCESS) - return GetDisplayHDRStatus(display); + return DisplayInfo(display).GetStatus().value_or(Status::Unsupported); DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE setColorState = {}; setColorState.header.type = DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE; @@ -212,7 +183,7 @@ static std::optional SetDisplayHDRStatus(const DisplayID& display, bool if (DisplayConfigSetDeviceInfo(&setColorState.header) != ERROR_SUCCESS) return std::nullopt; // Don't assume changing the HDR mode was successful... re-query the status - return GetDisplayHDRStatus(display); + return DisplayInfo(display).GetStatus().value_or(Status::Unsupported); } std::optional SetWindowsHDRStatus(bool enable) From 6aac5387322b70cb572112694ca5f0c26be25f14 Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 30 Jun 2024 19:56:02 +0200 Subject: [PATCH 11/80] Add hdr::DisplayInfo::GetID() --- common/HDR.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/HDR.h b/common/HDR.h index 738b264..411cc18 100644 --- a/common/HDR.h +++ b/common/HDR.h @@ -38,6 +38,9 @@ class DisplayInfo public: DisplayInfo(DisplayID id) : id(id) {} + /// Get display ID + const DisplayID& GetID() const { return id; } + template using result_type = std::expected; From 1033a5d4bc4a60e6f341cfe897e9d07b18d58b11 Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 30 Jun 2024 19:56:12 +0200 Subject: [PATCH 12/80] Implement hdr::SetWindowsHDRStatus() using GetDisplays() --- common/HDR.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/common/HDR.cpp b/common/HDR.cpp index 1778336..64113c8 100644 --- a/common/HDR.cpp +++ b/common/HDR.cpp @@ -158,9 +158,9 @@ Status GetWindowsHDRStatus() return anyEnabled ? Status::On : Status::Off; } -static std::optional SetDisplayHDRStatus(const DisplayID& display, bool enable) +static std::optional SetDisplayHDRStatus(const DisplayInfo& display, bool enable) { - if (DisplayInfo(display).GetStatus().value_or(Status::Unsupported) == Status::Unsupported) + if (display.GetStatus().value_or(Status::Unsupported) == Status::Unsupported) return std::nullopt; /* Try SET_HDR_STATE first, if available (on Windows 11 >= 24H2). @@ -169,39 +169,39 @@ static std::optional SetDisplayHDRStatus(const DisplayID& display, bool DISPLAYCONFIG_SET_HDR_STATE setHdrState = {}; setHdrState.header.type = DISPLAYCONFIG_DEVICE_INFO_SET_HDR_STATE; setHdrState.header.size = sizeof(setHdrState); - display.ToDeviceInputHeader(setHdrState.header); + display.GetID().ToDeviceInputHeader(setHdrState.header); setHdrState.enableHdr = enable; if (use_win11_24h2_color_functions && DisplayConfigSetDeviceInfo(&setHdrState.header) == ERROR_SUCCESS) - return DisplayInfo(display).GetStatus().value_or(Status::Unsupported); + return display.GetStatus(DisplayInfo::ValueFreshness::ForceRefresh).value_or(Status::Unsupported); DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE setColorState = {}; setColorState.header.type = DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE; setColorState.header.size = sizeof(setColorState); - display.ToDeviceInputHeader(setColorState.header); + display.GetID().ToDeviceInputHeader(setColorState.header); setColorState.enableAdvancedColor = enable; if (DisplayConfigSetDeviceInfo(&setColorState.header) != ERROR_SUCCESS) return std::nullopt; // Don't assume changing the HDR mode was successful... re-query the status - return DisplayInfo(display).GetStatus().value_or(Status::Unsupported); + return display.GetStatus(DisplayInfo::ValueFreshness::ForceRefresh).value_or(Status::Unsupported); } std::optional SetWindowsHDRStatus(bool enable) { - std::optional status; + std::optional status_result; - ForEachDisplay([&](const DisplayID& display) { + for (auto& display : GetDisplays()) { auto new_status = SetDisplayHDRStatus(display, enable); if (!new_status) - return; + continue; - if(!status) - status = *new_status; + if(!status_result) + status_result = *new_status; else - status = static_cast(std::max(static_cast(*status), static_cast(*new_status))); - }); + status_result = static_cast(std::max(static_cast(*status_result), static_cast(*new_status))); + } - return status; + return status_result; } std::optional ToggleHDRStatus() From 9eacaf3a30a620cb760a36482bb8af1ad823d842 Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 30 Jun 2024 20:07:26 +0200 Subject: [PATCH 13/80] Change HDR functions to take explicit displays list --- common/HDR.cpp | 15 +++++++-------- common/HDR.h | 22 +++++++++++++++++++--- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/common/HDR.cpp b/common/HDR.cpp index 64113c8..a50744f 100644 --- a/common/HDR.cpp +++ b/common/HDR.cpp @@ -140,12 +140,11 @@ template static void ForEachDisplay(F func) } } -Status GetWindowsHDRStatus() +Status GetWindowsHDRStatus(const DisplayInfo_vec& displays) { bool anySupported = false; bool anyEnabled = false; - - for (auto& display : GetDisplays()) { + for (auto& display : displays) { auto status = display.GetStatus(); if (!status || *status == Status::Unsupported) continue; @@ -186,11 +185,11 @@ static std::optional SetDisplayHDRStatus(const DisplayInfo& display, boo return display.GetStatus(DisplayInfo::ValueFreshness::ForceRefresh).value_or(Status::Unsupported); } -std::optional SetWindowsHDRStatus(bool enable) +std::optional SetWindowsHDRStatus(const DisplayInfo_vec& displays, bool enable) { std::optional status_result; - for (auto& display : GetDisplays()) { + for (auto& display : displays) { auto new_status = SetDisplayHDRStatus(display, enable); if (!new_status) continue; @@ -204,12 +203,12 @@ std::optional SetWindowsHDRStatus(bool enable) return status_result; } -std::optional ToggleHDRStatus() +std::optional ToggleHDRStatus(const DisplayInfo_vec& displays) { - auto status = GetWindowsHDRStatus(); + auto status = GetWindowsHDRStatus(displays); if (status == Status::Unsupported) return Status::Unsupported; - return SetWindowsHDRStatus(status == Status::Off ? true : false); + return SetWindowsHDRStatus(displays, status == Status::Off ? true : false); } std::vector GetDisplays() diff --git a/common/HDR.h b/common/HDR.h index 411cc18..65522ca 100644 --- a/common/HDR.h +++ b/common/HDR.h @@ -67,12 +67,28 @@ class DisplayInfo const result_type& GetCached(cache_type& cache, F produce, ValueFreshness freshness) const; }; -Status GetWindowsHDRStatus(); -std::optional SetWindowsHDRStatus(bool enable); -std::optional ToggleHDRStatus(); +using DisplayInfo_vec = std::vector; + +Status GetWindowsHDRStatus(const DisplayInfo_vec& displays); +std::optional SetWindowsHDRStatus(const DisplayInfo_vec& displays, bool enable); +std::optional ToggleHDRStatus(const DisplayInfo_vec& displays); + /// Get information for all displays std::vector GetDisplays(); +static inline Status GetWindowsHDRStatus() +{ + return GetWindowsHDRStatus(GetDisplays()); +} +static inline std::optional SetWindowsHDRStatus(bool enable) +{ + return SetWindowsHDRStatus(GetDisplays(), enable); +} +static inline std::optional ToggleHDRStatus() +{ + return ToggleHDRStatus(GetDisplays()); +} + } // namespace hdr #endif // HDR_H_ From f00e63057d8689811434a37c5e042e6eb358e0b5 Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 30 Jun 2024 20:21:42 +0200 Subject: [PATCH 14/80] Store DisplayInfo errors as HRESULT --- common/HDR.cpp | 4 ++-- common/HDR.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/HDR.cpp b/common/HDR.cpp index a50744f..aebfc98 100644 --- a/common/HDR.cpp +++ b/common/HDR.cpp @@ -63,7 +63,7 @@ DisplayInfo::result_type DisplayInfo::GetName() const id.ToDeviceInputHeader(deviceName.header); LONG result = DisplayConfigGetDeviceInfo(&deviceName.header); if (result != ERROR_SUCCESS) - return std::unexpected(result); + return std::unexpected(HRESULT_FROM_WIN32(result)); return deviceName; }, ValueFreshness::Cached); @@ -100,7 +100,7 @@ DisplayInfo::result_type DisplayInfo::GetStatus(ValueFreshness freshness id.ToDeviceInputHeader(getColorInfo.header); LONG result = DisplayConfigGetDeviceInfo(&getColorInfo.header); if (result != ERROR_SUCCESS) - return std::unexpected(result); + return std::unexpected(HRESULT_FROM_WIN32(result)); if (getColorInfo.advancedColorSupported) return getColorInfo.advancedColorEnabled ? Status::On : Status::Off; else diff --git a/common/HDR.h b/common/HDR.h index 65522ca..ce68858 100644 --- a/common/HDR.h +++ b/common/HDR.h @@ -42,7 +42,7 @@ class DisplayInfo const DisplayID& GetID() const { return id; } template - using result_type = std::expected; + using result_type = std::expected; /// Indicates how "fresh" a queried value should be enum struct ValueFreshness { Cached, ForceRefresh }; From 24cdeddcae1da72fd039503c67996c116381e22f Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 30 Jun 2024 21:40:12 +0200 Subject: [PATCH 15/80] Add DisplayInfo::GetStableID() --- HDRCmd/HDRCmd.cpp | 6 +++++ common/HDR.cpp | 65 +++++++++++++++++++++++++++++++++++++++-------- common/HDR.h | 5 ++++ 3 files changed, 65 insertions(+), 11 deletions(-) diff --git a/HDRCmd/HDRCmd.cpp b/HDRCmd/HDRCmd.cpp index 7bf2b22..46c7778 100644 --- a/HDRCmd/HDRCmd.cpp +++ b/HDRCmd/HDRCmd.cpp @@ -27,6 +27,8 @@ #include +#include + static std::string failure_message(const CLI::App *app, const CLI::Error &e) { return std::format("Invalid command line arguments: {}\n\n{}", e.what(), app->help()); } @@ -41,6 +43,10 @@ int wmain(int argc, const wchar_t* const argv[]) return -2; } + // Init WinRT. For monitor stable ID. + // FIXME: Obtain lazily? + winrt::init_apartment(); + app.allow_windows_style_options(); app.ignore_case(); app.require_subcommand(1); diff --git a/common/HDR.cpp b/common/HDR.cpp index aebfc98..50b320b 100644 --- a/common/HDR.cpp +++ b/common/HDR.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2005-2018 Team Kodi + * Copyright (C) 2022-2024 Frank Richter * * This file is based on source code from Kodi - https://kodi.tv * @@ -16,10 +17,16 @@ #include "framework.h" #include "WinVerCheck.hpp" +// WinRT stuff, for display stable ID +#include +#include + #if !defined(NTDDI_WIN11_GA) || WDK_NTDDI_VERSION < NTDDI_WIN11_GA #error Windows SDK too old: Version >= 10.0.26100 required #endif +using namespace winrt; + namespace hdr { static const bool use_win11_24h2_color_functions = IsWindows11_24H2OrGreater(); @@ -55,17 +62,7 @@ static const wchar_t* GetFallbackDisplayName(const DisplayID& display) DisplayInfo::result_type DisplayInfo::GetName() const { - const auto& dev_name = GetCached( - deviceName, [this]() -> result_type { - DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {}; - deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME; - deviceName.header.size = sizeof(deviceName); - id.ToDeviceInputHeader(deviceName.header); - LONG result = DisplayConfigGetDeviceInfo(&deviceName.header); - if (result != ERROR_SUCCESS) - return std::unexpected(HRESULT_FROM_WIN32(result)); - return deviceName; - }, ValueFreshness::Cached); + const auto& dev_name = GetCachedDeviceName(); if (dev_name) { if (dev_name->flags.friendlyNameFromEdid) @@ -109,6 +106,37 @@ DisplayInfo::result_type DisplayInfo::GetStatus(ValueFreshness freshness freshness); } +static Windows::Devices::Display::Core::DisplayManager display_mgr = nullptr; + +static DisplayInfo::result_type GetDisplayStableID(const wchar_t* devPath) +{ + try { + if (!display_mgr) + display_mgr = Windows::Devices::Display::Core::DisplayManager::Create( + Windows::Devices::Display::Core::DisplayManagerOptions::None); + auto targets = display_mgr.GetCurrentTargets(); + for (const auto& display_target : targets) { + if (!display_target.IsConnected()) + continue; + if (display_target.DeviceInterfacePath() == devPath) + return std::wstring(display_target.StableMonitorId()); + } + return std::unexpected(HRESULT_FROM_WIN32(ERROR_NOT_FOUND)); + } catch (hresult_error& e) { + return std::unexpected(e.code()); + } +} + +DisplayInfo::result_type DisplayInfo::GetStableID() const +{ + return GetCached(stableID, [this]() -> result_type { + const auto& dev_name = GetCachedDeviceName(); + if (!dev_name) + return std::unexpected(dev_name.error()); + return GetDisplayStableID(dev_name->monitorDevicePath); + }, ValueFreshness::Cached); +} + template const DisplayInfo::result_type& DisplayInfo::GetCached(cache_type& cache, F produce, ValueFreshness freshness) const { @@ -117,6 +145,21 @@ const DisplayInfo::result_type& DisplayInfo::GetCached(cache_type& cache, return *cache; } +const DisplayInfo::result_type& DisplayInfo::GetCachedDeviceName() const +{ + return GetCached( + deviceName, [this]() -> result_type { + DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {}; + deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME; + deviceName.header.size = sizeof(deviceName); + id.ToDeviceInputHeader(deviceName.header); + LONG result = DisplayConfigGetDeviceInfo(&deviceName.header); + if (result != ERROR_SUCCESS) + return std::unexpected(HRESULT_FROM_WIN32(result)); + return deviceName; + }, ValueFreshness::Cached); +} + //--------------------------------------------------------------------------- template static void ForEachDisplay(F func) diff --git a/common/HDR.h b/common/HDR.h index ce68858..442a82d 100644 --- a/common/HDR.h +++ b/common/HDR.h @@ -51,6 +51,8 @@ class DisplayInfo result_type GetName() const; /// Get HDR status for display result_type GetStatus(ValueFreshness freshness = ValueFreshness::Cached) const; + /// Get "stable ID" of display + result_type GetStableID() const; private: template @@ -62,9 +64,12 @@ class DisplayInfo mutable cache_type deviceName; /// HDR status mutable cache_type status; + /// Stable name + mutable cache_type stableID; template const result_type& GetCached(cache_type& cache, F produce, ValueFreshness freshness) const; + const result_type& GetCachedDeviceName() const; }; using DisplayInfo_vec = std::vector; From 01f09ad0f6eadf90e472cb9429cc6c8d86c07ae2 Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 7 Jul 2024 19:39:32 +0200 Subject: [PATCH 16/80] Statically allocate LoginStartupConfig --- common/LoginStartupConfig.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/LoginStartupConfig.cpp b/common/LoginStartupConfig.cpp index b90556d..000cb90 100644 --- a/common/LoginStartupConfig.cpp +++ b/common/LoginStartupConfig.cpp @@ -20,15 +20,15 @@ #include "RegistryKey.hpp" -#include +#include #include -static std::unique_ptr loginstartup_singleton; +static std::optional loginstartup_singleton; LoginStartupConfig& LoginStartupConfig::instance() { if (!loginstartup_singleton) - loginstartup_singleton.reset(new LoginStartupConfig); + loginstartup_singleton.emplace(LoginStartupConfig{}); return *loginstartup_singleton; } From 77cb56191938bc0eae9b92f5da60ce3797ba69fa Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 7 Jul 2024 19:48:38 +0200 Subject: [PATCH 17/80] Add RegistryKey move ctors, assignment --- common/RegistryKey.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/common/RegistryKey.hpp b/common/RegistryKey.hpp index cd47014..b1c75d1 100644 --- a/common/RegistryKey.hpp +++ b/common/RegistryKey.hpp @@ -29,12 +29,22 @@ class RegistryKey HKEY key = nullptr; public: + RegistryKey() = default; + RegistryKey(const RegistryKey&) = delete; + RegistryKey(RegistryKey&& other) { std::swap(key, other.key); } ~RegistryKey() { if (key) RegCloseKey(key); } + RegistryKey& operator=(const RegistryKey&) = delete; + RegistryKey& operator=(RegistryKey&& other) + { + std::swap(key, other.key); + return *this; + } + operator HKEY() const { return key; } /// Wrapper around RegCreateKeyExW(), storing the created key internally From e80390d97265ca4c3635527c51de5074ed9d6b38 Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 7 Jul 2024 20:08:57 +0200 Subject: [PATCH 18/80] Add RegistryKey::GetValue() --- common/RegistryKey.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common/RegistryKey.hpp b/common/RegistryKey.hpp index b1c75d1..b25fa66 100644 --- a/common/RegistryKey.hpp +++ b/common/RegistryKey.hpp @@ -57,6 +57,11 @@ class RegistryKey { return RegQueryValueExW(key, valueName, nullptr, type, data, data_size); } + /// Wrapper around RegGetValueW (), using the internally stored key + LSTATUS GetValue(LPCWSTR subKey, LPCWSTR value, DWORD flags, LPDWORD type, PVOID data, LPDWORD data_size) + { + return RegGetValueW(key, subKey, value, flags, type, data, data_size); + } /// Wrapper around RegSetValueExW(), using the internally stored key LSTATUS SetValueEx(LPCWSTR valueName, DWORD type, const BYTE* data, DWORD data_size) const { From 21e9dcd62f85202591aeafff7e60d5a5b8905152 Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 7 Jul 2024 20:09:08 +0200 Subject: [PATCH 19/80] Add DisplayConfig class --- common/CMakeLists.txt | 2 ++ common/DisplayConfig.cpp | 73 ++++++++++++++++++++++++++++++++++++++++ common/DisplayConfig.hpp | 48 ++++++++++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 common/DisplayConfig.cpp create mode 100644 common/DisplayConfig.hpp diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 8dcfdc5..477a4ab 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -1,5 +1,7 @@ add_library(common STATIC) target_sources(common PRIVATE + "DisplayConfig.hpp" + "DisplayConfig.cpp" "LoginStartupConfig.hpp" "LoginStartupConfig.cpp" "HDR.h" diff --git a/common/DisplayConfig.cpp b/common/DisplayConfig.cpp new file mode 100644 index 0000000..9c51ed7 --- /dev/null +++ b/common/DisplayConfig.cpp @@ -0,0 +1,73 @@ +/* + HDRTray, a notification icon for the "Use HDR" option + Copyright (C) 2024 Frank Richter + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "DisplayConfig.hpp" + +#include "RegistryKey.hpp" + +#include +#include + +static std::optional displayconfig_singleton; + +DisplayConfig& DisplayConfig::instance() +{ + if (!displayconfig_singleton) + displayconfig_singleton.emplace(DisplayConfig{}); + return *displayconfig_singleton; +} + +static const wchar_t displayconfig_registry_path[] = L"SOFTWARE\\HDRTray"; + +static std::wstring DisplayValueName(std::wstring_view display_stable_id) +{ + return std::format(L"Display.{}", display_stable_id); +} + +std::expected DisplayConfig::GetEnabledFlag(std::wstring_view display_stable_id) const +{ + RegistryKey key_displayconfig; + auto create_result = key_displayconfig.Create(HKEY_CURRENT_USER, displayconfig_registry_path, 0, + KEY_READ | KEY_QUERY_VALUE, nullptr); + if (create_result != ERROR_SUCCESS) + return std::unexpected(create_result); + auto value_name = DisplayValueName(display_stable_id); + + DWORD value, value_size = sizeof(value); + auto get_value_res = + key_displayconfig.GetValue(nullptr, value_name.c_str(), RRF_RT_REG_DWORD, nullptr, &value, &value_size); + if (get_value_res != ERROR_SUCCESS) + return std::unexpected(get_value_res); + + return value != 0; +} + +std::expected DisplayConfig::SetEnabledFlag(std::wstring_view display_stable_id, bool flag) +{ + RegistryKey key_displayconfig; + auto create_result = key_displayconfig.Create(HKEY_CURRENT_USER, displayconfig_registry_path, 0, + KEY_READ | KEY_WRITE | KEY_QUERY_VALUE | KEY_SET_VALUE, nullptr); + if (create_result != ERROR_SUCCESS) + return std::unexpected(create_result); + auto value_name = DisplayValueName(display_stable_id); + + DWORD value = flag ? 1 : 0; + key_displayconfig.SetValueEx(value_name.c_str(), REG_DWORD, reinterpret_cast(&value), sizeof(value)); + + return {}; +} diff --git a/common/DisplayConfig.hpp b/common/DisplayConfig.hpp new file mode 100644 index 0000000..ffe6295 --- /dev/null +++ b/common/DisplayConfig.hpp @@ -0,0 +1,48 @@ +/* + HDRTray, a notification icon for the "Use HDR" option + Copyright (C) 2024 Frank Richter + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/**\file + * Per-display configuration + */ +#ifndef DISPLAYCONFIG_HPP_ +#define DISPLAYCONFIG_HPP_ + +#include "framework.h" + +#include +#include + +class DisplayConfig +{ +public: + /// Return reference to singleton + static DisplayConfig& instance(); + + /// Read enable flag from registry + std::expected GetEnabledFlag(std::wstring_view display_stable_id) const; + /// Write enable flag to registry + std::expected SetEnabledFlag(std::wstring_view display_stable_id, bool flag); + + /// Whether given display is currently enabled + bool IsEnabled(std::wstring_view display_stable_id) const + { + return GetEnabledFlag(display_stable_id).value_or(true); + } +}; + +#endif // DISPLAYCONFIG_HPP_ From 06c7db3922c57dc009ff8c4e4496aa07a52aca56 Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 7 Jul 2024 20:14:53 +0200 Subject: [PATCH 20/80] HDRCmd: Move some status functions to separate source --- HDRCmd/CMakeLists.txt | 2 + HDRCmd/subcommand/Status.cpp | 63 +-------------------- HDRCmd/subcommand/Status.hpp | 1 - HDRCmd/subcommand/display_status.cpp | 84 ++++++++++++++++++++++++++++ HDRCmd/subcommand/display_status.hpp | 34 +++++++++++ 5 files changed, 123 insertions(+), 61 deletions(-) create mode 100644 HDRCmd/subcommand/display_status.cpp create mode 100644 HDRCmd/subcommand/display_status.hpp diff --git a/HDRCmd/CMakeLists.txt b/HDRCmd/CMakeLists.txt index e3e8644..98503e6 100644 --- a/HDRCmd/CMakeLists.txt +++ b/HDRCmd/CMakeLists.txt @@ -6,6 +6,8 @@ target_sources(HDRCmd PRIVATE "HDRCmd.cpp" "HDRCmd.manifest" "HDRCmd.rc" + "subcommand/display_status.hpp" + "subcommand/display_status.cpp" "subcommand/Base.hpp" "subcommand/Disable.hpp" "subcommand/Disable.cpp" diff --git a/HDRCmd/subcommand/Status.cpp b/HDRCmd/subcommand/Status.cpp index 58bdc1c..3c06ab9 100644 --- a/HDRCmd/subcommand/Status.cpp +++ b/HDRCmd/subcommand/Status.cpp @@ -18,10 +18,9 @@ #include "Status.hpp" +#include "display_status.hpp" #include "HDR.h" -#include -#include #include namespace subcommand { @@ -60,66 +59,10 @@ Status::Status(CLI::App* parent) : Base("Print current HDR status", "status", pa mode_option->transform(StatusModeValidator()); } -static std::string_view status_string(hdr::Status status) -{ - switch (status) { - case hdr::Status::Off: - return "off"; - case hdr::Status::On: - return "on"; - case hdr::Status::Unsupported: - return "unsupported"; - } - return "???"; -} - void Status::print_status_short() { auto status = hdr::GetWindowsHDRStatus(); - std::println("HDR is {}", status_string(status)); -} - -void Status::print_status_long() -{ - auto displays = hdr::GetDisplays(); - // Filter out all displays w/o name or status - std::erase_if(displays, [](const hdr::DisplayInfo& info) { return !info.GetStatus() || !info.GetName(); }); - - // Tabulate. - // Columns: #, Display name, Status - static constexpr size_t num_cols = 3; - static constexpr std::string_view col_headings[num_cols] = { "Display #", "Name", "Status" }; - std::array widths; - widths[0] = std::max(size_t(ceil(log10(displays.size() + 1))) + 1, col_headings[0].size()); - widths[1] = col_headings[1].size(); - widths[2] = col_headings[2].size(); - for(const auto& disp : displays) - { - widths[1] = std::max(widths[1], disp.GetName()->size()); - widths[2] = std::max(widths[2], status_string(*disp.GetStatus()).size()); - } - - // Print heading - for (size_t i = 0; i < num_cols; i++) - { - if (i > 0) - std::cout << "\t"; - std::print("{:<{}}", col_headings[i], widths[i]); - } - std::cout << std::endl; - for (size_t i = 0; i < num_cols; i++) - { - if (i > 0) - std::cout << "\t"; - std::print("{:-<{}}", "", widths[i]); - } - std::cout << std::endl; - for (size_t i = 0; i < displays.size(); i++) - { - const auto& disp = displays[i]; - std::println("{:>{}}\t{:<{}}\t{:<{}}", i, widths[0], CLI::narrow(*disp.GetName()), widths[1], - status_string(*disp.GetStatus()), widths[2]); - } + std::println("HDR is {}", display_status::status_string(status)); } int Status::run() const @@ -130,7 +73,7 @@ int Status::run() const } else if (stricmp(mode.c_str(), "long") == 0) { print_status_short(); std::cout << std::endl; - print_status_long(); + display_status::print_status_long(); return 0; } else if (stricmp(mode.c_str(), "exitcode") == 0) { auto status = hdr::GetWindowsHDRStatus(); diff --git a/HDRCmd/subcommand/Status.hpp b/HDRCmd/subcommand/Status.hpp index 3207291..b1e00bf 100644 --- a/HDRCmd/subcommand/Status.hpp +++ b/HDRCmd/subcommand/Status.hpp @@ -25,7 +25,6 @@ namespace subcommand { class Status : public Base { static void print_status_short(); - static void print_status_long(); protected: std::string mode; diff --git a/HDRCmd/subcommand/display_status.cpp b/HDRCmd/subcommand/display_status.cpp new file mode 100644 index 0000000..7949906 --- /dev/null +++ b/HDRCmd/subcommand/display_status.cpp @@ -0,0 +1,84 @@ +/* + HDRCmd - enable/disable "Use HDR" from command line + Copyright (C) 2024 Frank Richter + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "display_status.hpp" + +#include "CLI/CLI.hpp" + +#include +#include +#include + +namespace subcommand::display_status { +std::string_view status_string(hdr::Status status) +{ + switch (status) { + case hdr::Status::Off: + return "off"; + case hdr::Status::On: + return "on"; + case hdr::Status::Unsupported: + return "unsupported"; + } + return "???"; +} + +void print_status_long() +{ + auto displays = hdr::GetDisplays(); + // Filter out all displays w/o name or status + std::erase_if(displays, [](const hdr::DisplayInfo& info) { return !info.GetStatus() || !info.GetName(); }); + + // Tabulate. + // Columns: #, Display name, Status + static constexpr size_t num_cols = 3; + static constexpr std::string_view col_headings[num_cols] = { "Display #", "Name", "Status" }; + std::array widths; + widths[0] = std::max(size_t(ceil(log10(displays.size() + 1))) + 1, col_headings[0].size()); + widths[1] = col_headings[1].size(); + widths[2] = col_headings[2].size(); + for(const auto& disp : displays) + { + widths[1] = std::max(widths[1], disp.GetName()->size()); + widths[2] = std::max(widths[2], status_string(*disp.GetStatus()).size()); + } + + // Print heading + for (size_t i = 0; i < num_cols; i++) + { + if (i > 0) + std::cout << "\t"; + std::print("{:<{}}", col_headings[i], widths[i]); + } + std::cout << std::endl; + for (size_t i = 0; i < num_cols; i++) + { + if (i > 0) + std::cout << "\t"; + std::print("{:-<{}}", "", widths[i]); + } + std::cout << std::endl; + for (size_t i = 0; i < displays.size(); i++) + { + const auto& disp = displays[i]; + std::println("{:>{}}\t{:<{}}\t{:<{}}", i, widths[0], CLI::narrow(*disp.GetName()), widths[1], + status_string(*disp.GetStatus()), widths[2]); + } +} + +} // namespace subcommand::display_status diff --git a/HDRCmd/subcommand/display_status.hpp b/HDRCmd/subcommand/display_status.hpp new file mode 100644 index 0000000..7755357 --- /dev/null +++ b/HDRCmd/subcommand/display_status.hpp @@ -0,0 +1,34 @@ +/* + HDRCmd - enable/disable "Use HDR" from command line + Copyright (C) 2024 Frank Richter + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef SUBCOMMAND_DISPLAYSTATUS_HPP_ +#define SUBCOMMAND_DISPLAYSTATUS_HPP_ + +#include "HDR.h" + +#include + +namespace subcommand::display_status { +/// Get string for a display status +std::string_view status_string(hdr::Status status); +/// Print "long" display status +void print_status_long(); + +} // namespace subcommand::display_status + +#endif // SUBCOMMAND_DISPLAYSTATUS_HPP_ From 4eec9882910444310d1c92918d366132f6b41f6c Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 7 Jul 2024 20:20:04 +0200 Subject: [PATCH 21/80] Print whether a display is 'selected' or not in long status --- HDRCmd/subcommand/display_status.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/HDRCmd/subcommand/display_status.cpp b/HDRCmd/subcommand/display_status.cpp index 7949906..07b8e6a 100644 --- a/HDRCmd/subcommand/display_status.cpp +++ b/HDRCmd/subcommand/display_status.cpp @@ -19,6 +19,7 @@ #include "display_status.hpp" #include "CLI/CLI.hpp" +#include "DisplayConfig.hpp" #include #include @@ -38,6 +39,15 @@ std::string_view status_string(hdr::Status status) return "???"; } +static std::string_view selected_str(const hdr::DisplayInfo& disp) +{ + auto stable_id_res = disp.GetStableID(); + bool display_enabled = true; + if (stable_id_res) + display_enabled = DisplayConfig::instance().IsEnabled(*stable_id_res); + return display_enabled ? "yes" : "no"; +} + void print_status_long() { auto displays = hdr::GetDisplays(); @@ -46,16 +56,18 @@ void print_status_long() // Tabulate. // Columns: #, Display name, Status - static constexpr size_t num_cols = 3; - static constexpr std::string_view col_headings[num_cols] = { "Display #", "Name", "Status" }; + static constexpr size_t num_cols = 4; + static constexpr std::string_view col_headings[num_cols] = { "Display #", "Name", "Status", "Selected" }; std::array widths; widths[0] = std::max(size_t(ceil(log10(displays.size() + 1))) + 1, col_headings[0].size()); widths[1] = col_headings[1].size(); widths[2] = col_headings[2].size(); + widths[3] = col_headings[3].size(); for(const auto& disp : displays) { widths[1] = std::max(widths[1], disp.GetName()->size()); widths[2] = std::max(widths[2], status_string(*disp.GetStatus()).size()); + // Don't bother w/ "Selected", it's either "yes" or "no" } // Print heading @@ -76,8 +88,8 @@ void print_status_long() for (size_t i = 0; i < displays.size(); i++) { const auto& disp = displays[i]; - std::println("{:>{}}\t{:<{}}\t{:<{}}", i, widths[0], CLI::narrow(*disp.GetName()), widths[1], - status_string(*disp.GetStatus()), widths[2]); + std::println("{:>{}}\t{:<{}}\t{:<{}}\t{:<{}}", i, widths[0], CLI::narrow(*disp.GetName()), widths[1], + status_string(*disp.GetStatus()), widths[2], selected_str(disp), widths[3]); } } From dcc82be7da711807c061b96f988f88d66cc5e8a9 Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Mon, 8 Jul 2024 00:08:18 +0200 Subject: [PATCH 22/80] Add 'index' to DisplayInfo --- common/HDR.cpp | 3 ++- common/HDR.h | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/common/HDR.cpp b/common/HDR.cpp index 50b320b..d223150 100644 --- a/common/HDR.cpp +++ b/common/HDR.cpp @@ -258,8 +258,9 @@ std::vector GetDisplays() { std::vector result; + size_t index = 0; ForEachDisplay([&](const DisplayID& display) { - result.emplace_back(display); + result.emplace_back(index++, display); }); return result; diff --git a/common/HDR.h b/common/HDR.h index 442a82d..1a7bf12 100644 --- a/common/HDR.h +++ b/common/HDR.h @@ -36,8 +36,10 @@ struct DisplayID class DisplayInfo { public: - DisplayInfo(DisplayID id) : id(id) {} + DisplayInfo(size_t index, DisplayID id) : index(index), id(id) {} + /// Get display index + size_t GetIndex() const { return index; } /// Get display ID const DisplayID& GetID() const { return id; } @@ -58,6 +60,8 @@ class DisplayInfo template using cache_type = std::optional>; + /// Display index + size_t index; /// Display ID DisplayID id; /// Display name From 2991c983969c034e6864f3f03b15a1c0c1350bfb Mon Sep 17 00:00:00 2001 From: Frank Richter Date: Sun, 7 Jul 2024 22:40:17 +0200 Subject: [PATCH 23/80] HDRCmd: Implement 'select' subcommand to toggle which displays to include --- HDRCmd/CMakeLists.txt | 4 + HDRCmd/HDRCmd.cpp | 2 + HDRCmd/subcommand/Select.cpp | 84 ++++++++++++++++++++ HDRCmd/subcommand/Select.hpp | 44 +++++++++++ HDRCmd/subcommand/display_name.hpp | 38 +++++++++ HDRCmd/subcommand/resolve_display.cpp | 107 ++++++++++++++++++++++++++ HDRCmd/subcommand/resolve_display.hpp | 35 +++++++++ 7 files changed, 314 insertions(+) create mode 100644 HDRCmd/subcommand/Select.cpp create mode 100644 HDRCmd/subcommand/Select.hpp create mode 100644 HDRCmd/subcommand/display_name.hpp create mode 100644 HDRCmd/subcommand/resolve_display.cpp create mode 100644 HDRCmd/subcommand/resolve_display.hpp diff --git a/HDRCmd/CMakeLists.txt b/HDRCmd/CMakeLists.txt index 98503e6..27fea3b 100644 --- a/HDRCmd/CMakeLists.txt +++ b/HDRCmd/CMakeLists.txt @@ -8,6 +8,8 @@ target_sources(HDRCmd PRIVATE "HDRCmd.rc" "subcommand/display_status.hpp" "subcommand/display_status.cpp" + "subcommand/resolve_display.hpp" + "subcommand/resolve_display.cpp" "subcommand/Base.hpp" "subcommand/Disable.hpp" "subcommand/Disable.cpp" @@ -15,6 +17,8 @@ target_sources(HDRCmd PRIVATE "subcommand/Enable.cpp" "subcommand/LoginStartup.hpp" "subcommand/LoginStartup.cpp" + "subcommand/Select.hpp" + "subcommand/Select.cpp" "subcommand/Status.hpp" "subcommand/Status.cpp" ) diff --git a/HDRCmd/HDRCmd.cpp b/HDRCmd/HDRCmd.cpp index 46c7778..007daf2 100644 --- a/HDRCmd/HDRCmd.cpp +++ b/HDRCmd/HDRCmd.cpp @@ -21,6 +21,7 @@ #include "subcommand/Disable.hpp" #include "subcommand/Enable.hpp" #include "subcommand/LoginStartup.hpp" +#include "subcommand/Select.hpp" #include "subcommand/Status.hpp" #include "version.h" #include "WinVerCheck.hpp" @@ -55,6 +56,7 @@ int wmain(int argc, const wchar_t* const argv[]) subcommand::Status::add(app); subcommand::Enable::add(app); subcommand::Disable::add(app); + subcommand::Select::add(app); subcommand::LoginStartup::add(app); CLI11_PARSE(app, argc, argv); diff --git a/HDRCmd/subcommand/Select.cpp b/HDRCmd/subcommand/Select.cpp new file mode 100644 index 0000000..010a206 --- /dev/null +++ b/HDRCmd/subcommand/Select.cpp @@ -0,0 +1,84 @@ +/* + HDRCmd - enable/disable "Use HDR" from command line + Copyright (C) 2024 Frank Richter + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "Select.hpp" + +#include "DisplayConfig.hpp" +#include "display_name.hpp" +#include "display_status.hpp" +#include "resolve_display.hpp" + +namespace subcommand { + +Select::Select(CLI::App* parent) : Base("Select displays to include in HDR toggling", "select", parent) +{ + add_subcommand("list", "Print list of displays"); + auto on_cmd = add_subcommand("on", "Include a display in HDR toggling"); + on_cmd->add_option("DISPLAY", display_ids, "displays to include in HDR toggling")->type_name("# or NAME")->required(); + auto off_cmd = add_subcommand("off", "Exclude a display from HDR toggling"); + off_cmd->add_option("DISPLAY", display_ids, "displays to exclude from HDR toggling")->type_name("# or NAME")->required(); +} + +int Select::toggle_displays(bool flag) const +{ + std::optional result; + auto all_displays = hdr::GetDisplays(); + for(const auto& disp_id : display_ids) { + if (const auto* disp = resolve_display(all_displays, disp_id)) { + auto stable_id = disp->GetStableID(); + if (!stable_id) // FIXME print an error? + continue; + auto enable_res = DisplayConfig::instance().SetEnabledFlag(*stable_id, flag); + if (!enable_res) { + if (!result) + *result = int(enable_res.error()); + std::println(std::cerr, "Could not store selection for display '{}': error {}", display_name(*disp), enable_res.error()); + } + } + } + return result.value_or(0); +} + +int Select::run() const +{ + bool has_list = got_subcommand("list"); + bool has_on = got_subcommand("on"); + bool has_off = got_subcommand("off"); + + if (!has_on && !has_off && !has_list) + { + // No explicit command given: also include help text + std::cout << help() << std::endl; + } + + int result = 0; + if (has_on || has_off) { + result = toggle_displays(has_on); + } + + // Always print list at the end + display_status::print_status_long(); + return result; +} + +CLI::App* Select::add(CLI::App& app) +{ + return app.add_subcommand(std::shared_ptr