diff --git a/mods/change-tray-icons.wh.cpp b/mods/change-tray-icons.wh.cpp index 546b42034d..e944479010 100644 --- a/mods/change-tray-icons.wh.cpp +++ b/mods/change-tray-icons.wh.cpp @@ -2,7 +2,7 @@ // @id change-tray-icons // @name Change Tray Icons // @description Change all tray icons for an application to a specific other icon -// @version 1.0.0 +// @version 1.0.1 // @author aubymori // @github https://github.com/aubymori // @compilerOptions -lshell32 -lgdi32 @@ -10,129 +10,339 @@ // ==WindhawkModReadme== /* -# Force Tray Icons -This mod will change all tray icons for an application to a specific other icon. - -**Example**: Steam with its pre-2011 icon - -![Example: *Steam with its pre-2011 icon](https://raw.githubusercontent.com/aubymori/images/refs/heads/main/change-tray-icons-example.png) - -## How to use -1. Go to the "Advanced" tab of this mod -2. Find the "Custom process inclusion list" -3. Add the name of the EXE you want to disable tray icons for. - - If you don't know the name of the EXE, you can find it using - Task Manager. -4. Click Save. -5. Configure the mod as you want it to be. -6. If the program is open already, restart it. +# Change Tray Icons - Sharp + +Changes notification-area icons created by selected applications. + +Compared with the original mod, replacement icons are loaded at twice the +small-icon dimensions. This avoids supplying the Windows notification area +with an icon that has already been rasterized directly to 16x16 pixels. + +## Configuration + +For every application, specify: + +- **Application:** The executable name, such as `steam.exe`, or its full path. +- **Icon:** The full path to a valid `.ico` file. + +For best results, use a multi-resolution ICO containing at least: + +- 16x16 +- 20x20 +- 24x24 +- 32x32 +- 40x40 +- 48x48 +- 64x64 +- 256x256 + +Restart the affected application after enabling the mod or changing its icon. */ // ==/WindhawkModReadme== // ==WindhawkModSettings== /* - icons: - - - path: Discord.exe - $name: Application name or path - - icon: C:\Classic\mIRC.ico - $name: ICO file path + - - path: steam.exe + $name: Application + $description: Executable name, such as steam.exe, or its complete path + - icon: C:\Icons\steam.ico + $name: Icon + $description: Complete path to the replacement .ico file + $name: Applications + $description: Applications and their replacement notification-area icons */ // ==/WindhawkModSettings== #include #include +#include -HICON g_hIcon = NULL; +HICON g_hIcon = nullptr; using Shell_NotifyIconW_t = decltype(&Shell_NotifyIconW); -Shell_NotifyIconW_t Shell_NotifyIconW_orig; +Shell_NotifyIconW_t Shell_NotifyIconW_orig = nullptr; + BOOL WINAPI Shell_NotifyIconW_hook( - DWORD dwMessage, PNOTIFYICONDATAW lpData + DWORD dwMessage, + PNOTIFYICONDATAW lpData ) { - if (g_hIcon - && lpData - && lpData->cbSize >= offsetof(NOTIFYICONDATAW, hIcon) - && (lpData->uFlags & NIF_ICON)) + if (g_hIcon && + lpData && + lpData->cbSize >= + offsetof(NOTIFYICONDATAW, hIcon) + sizeof(lpData->hIcon) && + (lpData->uFlags & NIF_ICON)) { lpData->hIcon = g_hIcon; } + return Shell_NotifyIconW_orig(dwMessage, lpData); } -void Wh_ModSettingsChanged(void) +static void DestroyReplacementIcon() { if (g_hIcon) { DestroyIcon(g_hIcon); - g_hIcon = NULL; + g_hIcon = nullptr; + } +} + +static bool ApplicationMatches( + const WCHAR* applicationPath, + const WCHAR* configuredPath +) +{ + if (!applicationPath || !configuredPath || !*configuredPath) + { + return false; + } + + // Match the complete executable path. + if (_wcsicmp(applicationPath, configuredPath) == 0) + { + return true; + } + + // Also allow matching only the executable name, for example steam.exe. + const WCHAR* fileName = wcsrchr(applicationPath, L'\\'); + + if (fileName) + { + fileName++; + } + else + { + fileName = applicationPath; + } + + return _wcsicmp(fileName, configuredPath) == 0; +} + +static UINT GetCurrentSystemDpi() +{ + using GetDpiForSystem_t = UINT(WINAPI*)(); + + static GetDpiForSystem_t GetDpiForSystem_func = + reinterpret_cast( + GetProcAddress( + GetModuleHandleW(L"user32.dll"), + "GetDpiForSystem" + ) + ); + + if (GetDpiForSystem_func) + { + UINT dpi = GetDpiForSystem_func(); + + if (dpi) + { + return dpi; + } + } + + HDC hdc = GetDC(nullptr); + + if (!hdc) + { + return 96; + } + + int dpi = GetDeviceCaps(hdc, LOGPIXELSX); + ReleaseDC(nullptr, hdc); + + return dpi > 0 ? static_cast(dpi) : 96; +} + +static void GetSourceIconSize(int* width, int* height) +{ + UINT dpi = GetCurrentSystemDpi(); + + using GetSystemMetricsForDpi_t = + int(WINAPI*)(int nIndex, UINT dpi); + + static GetSystemMetricsForDpi_t GetSystemMetricsForDpi_func = + reinterpret_cast( + GetProcAddress( + GetModuleHandleW(L"user32.dll"), + "GetSystemMetricsForDpi" + ) + ); + + int smallWidth; + int smallHeight; + + if (GetSystemMetricsForDpi_func) + { + smallWidth = GetSystemMetricsForDpi_func( + SM_CXSMICON, + dpi + ); + + smallHeight = GetSystemMetricsForDpi_func( + SM_CYSMICON, + dpi + ); + } + else + { + smallWidth = GetSystemMetrics(SM_CXSMICON); + smallHeight = GetSystemMetrics(SM_CYSMICON); + } + + if (smallWidth <= 0) + { + smallWidth = 16; + } + + if (smallHeight <= 0) + { + smallHeight = 16; + } + + // Do not give the tray an icon already rasterized directly to 16x16. + // At 100% scaling this loads 32x32; at 150%, typically 48x48. + *width = smallWidth * 2; + *height = smallHeight * 2; +} + +static HICON LoadReplacementIcon(const WCHAR* iconPath) +{ + int sourceWidth; + int sourceHeight; + + GetSourceIconSize(&sourceWidth, &sourceHeight); + + SetLastError(ERROR_SUCCESS); + + HICON icon = reinterpret_cast( + LoadImageW( + nullptr, + iconPath, + IMAGE_ICON, + sourceWidth, + sourceHeight, + LR_LOADFROMFILE | LR_DEFAULTCOLOR + ) + ); + + if (!icon) + { + Wh_Log( + L"Failed to load icon \"%s\" at %dx%d, error: %lu", + iconPath, + sourceWidth, + sourceHeight, + GetLastError() + ); + + return nullptr; } - WCHAR szAppPath[MAX_PATH]; - GetModuleFileNameW(GetModuleHandleW(NULL), szAppPath, MAX_PATH); + Wh_Log( + L"Loaded replacement icon \"%s\" at %dx%d", + iconPath, + sourceWidth, + sourceHeight + ); + + return icon; +} + +void Wh_ModSettingsChanged() +{ + DestroyReplacementIcon(); + + WCHAR applicationPath[MAX_PATH] = {}; + + DWORD pathLength = GetModuleFileNameW( + nullptr, + applicationPath, + ARRAYSIZE(applicationPath) + ); + + if (pathLength == 0 || pathLength >= ARRAYSIZE(applicationPath)) + { + Wh_Log( + L"GetModuleFileNameW failed, error: %lu", + GetLastError() + ); + + return; + } + + Wh_Log(L"Current application: %s", applicationPath); for (int i = 0;; i++) { - LPCWSTR pszPath = Wh_GetStringSetting(L"icons[%d].path", i); + PCWSTR configuredApplication = + Wh_GetStringSetting(L"icons[%d].path", i); - // Stop enumerating at an empty string - if (!*pszPath) + if (!configuredApplication) { - Wh_FreeStringSetting(pszPath); break; } - // Does the current application path or name match the spoof? - WCHAR *pBackslash = wcsrchr(szAppPath, L'\\'); - if (0 == wcsicmp(szAppPath, pszPath) - || (pBackslash && 0 == wcsicmp(pBackslash + 1, pszPath))) + // An empty path marks the end of the settings array. + if (!*configuredApplication) { - int cxIcon, cyIcon; + Wh_FreeStringSetting(configuredApplication); + break; + } - typedef UINT (WINAPI *GetSystemMetricsForDpi_t)(int nIndex, UINT dpi); - static GetSystemMetricsForDpi_t pfnGetSystemMetricsForDpi - = (GetSystemMetricsForDpi_t)GetProcAddress(GetModuleHandleW(L"user32.dll"), "GetSystemMetricsForDpi"); - if (pfnGetSystemMetricsForDpi) - { - HDC hdc = GetDC(NULL); - int dpi = GetDeviceCaps(hdc, LOGPIXELSX); - ReleaseDC(NULL, hdc); + bool matches = ApplicationMatches( + applicationPath, + configuredApplication + ); + + if (matches) + { + PCWSTR configuredIcon = + Wh_GetStringSetting(L"icons[%d].icon", i); - cxIcon = pfnGetSystemMetricsForDpi(SM_CXSMICON, dpi); - cyIcon = pfnGetSystemMetricsForDpi(SM_CYSMICON, dpi); + if (configuredIcon && *configuredIcon) + { + g_hIcon = LoadReplacementIcon(configuredIcon); } else { - cxIcon = GetSystemMetrics(SM_CXSMICON); - cyIcon = GetSystemMetrics(SM_CYSMICON); + Wh_Log( + L"Application matched, but its icon path is empty" + ); + } + + if (configuredIcon) + { + Wh_FreeStringSetting(configuredIcon); } - LPCWSTR pszIconPath = Wh_GetStringSetting(L"icons[%d].icon", i); - g_hIcon = (HICON)LoadImageW( - NULL, - pszIconPath, - IMAGE_ICON, - cxIcon, cyIcon, - LR_LOADFROMFILE | LR_DEFAULTCOLOR); - Wh_FreeStringSetting(pszIconPath); + Wh_FreeStringSetting(configuredApplication); + + // Only one replacement is needed for the current process. + break; } - Wh_FreeStringSetting(pszPath); + + Wh_FreeStringSetting(configuredApplication); } } -BOOL Wh_ModInit(void) +BOOL Wh_ModInit() { + Wh_Log(L"Initializing Change Tray Icons - Sharp"); + Wh_ModSettingsChanged(); return Wh_SetFunctionHook( - (void *)Shell_NotifyIconW, - (void *)Shell_NotifyIconW_hook, - (void **)&Shell_NotifyIconW_orig + reinterpret_cast(Shell_NotifyIconW), + reinterpret_cast(Shell_NotifyIconW_hook), + reinterpret_cast(&Shell_NotifyIconW_orig) ); } -void Wh_ModUninit(void) +void Wh_ModUninit() { - if (g_hIcon) - DestroyIcon(g_hIcon); -} \ No newline at end of file + Wh_Log(L"Uninitializing Change Tray Icons - Sharp"); + + DestroyReplacementIcon(); +}