Compare commits

...

2 Commits

Author SHA1 Message Date
Dustin L. Howett
23925cbc11 try to cache icons for differetnt DPIs I guess? 2025-08-19 18:30:38 -05:00
Dustin L. Howett
79cf5e2c74 Dramatically simplify Icon 2025-08-19 16:48:35 -05:00
5 changed files with 78 additions and 273 deletions

View File

@@ -8,6 +8,7 @@
#include "window.hpp" #include "window.hpp"
#include "../inc/ServiceLocator.hpp" #include "../inc/ServiceLocator.hpp"
#include "windowdpiapi.hpp"
using namespace Microsoft::Console::Interactivity::Win32; using namespace Microsoft::Console::Interactivity::Win32;
@@ -21,7 +22,7 @@ static constexpr uint32_t LR_EXACTSIZEONLY{ 0x10000 };
// larger than 256 returns the input value, which would result in the 256px icon being used. // larger than 256 returns the input value, which would result in the 256px icon being used.
static int SnapIconSize(int cx) static int SnapIconSize(int cx)
{ {
static constexpr int rgSizes[] = { 16, 32, 48, 256 }; static constexpr int rgSizes[] = { 16, 24, 32, 48, 64, 256 };
for (auto sz : rgSizes) for (auto sz : rgSizes)
{ {
if (cx <= sz) if (cx <= sz)
@@ -240,14 +241,14 @@ static UINT ConExtractIcons(PCWSTR szFileName, int nIconIndex, int cxIcon, int c
return result; return result;
} }
static UINT ConExtractIconInBothSizesW(PCWSTR szFileName, int nIconIndex, HICON* phiconLarge, HICON* phiconSmall) static UINT ConExtractIconInBothSizesW(int dpi, PCWSTR szFileName, int nIconIndex, HICON* phiconLarge, HICON* phiconSmall)
{ {
HICON ahicon[2] = { nullptr, nullptr }; HICON ahicon[2] = { nullptr, nullptr };
auto result = ConExtractIcons( auto result = ConExtractIcons(
szFileName, szFileName,
nIconIndex, nIconIndex,
MAKELONG(GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CXSMICON)), MAKELONG(GetSystemMetricsForDpi(dpi, SM_CXICON), GetSystemMetricsForDpi(dpi, SM_CXSMICON)),
MAKELONG(GetSystemMetrics(SM_CYICON), GetSystemMetrics(SM_CYSMICON)), MAKELONG(GetSystemMetricsForDpi(dpi, SM_CYICON), GetSystemMetricsForDpi(dpi, SM_CYSMICON)),
ahicon, ahicon,
2, 2,
0); 0);
@@ -259,23 +260,18 @@ static UINT ConExtractIconInBothSizesW(PCWSTR szFileName, int nIconIndex, HICON*
} }
// Excerpted Region Ends // Excerpted Region Ends
Icon::Icon() : Icon::Icon()
_fInitialized(false),
_hDefaultIcon(nullptr),
_hDefaultSmIcon(nullptr),
_hIcon(nullptr),
_hSmIcon(nullptr)
{ {
} #pragma warning(push)
#pragma warning(disable : 4302) // typecast warning from MAKEINTRESOURCE
Icon::~Icon() _hDefaultIcon = LoadIconW(nullptr, MAKEINTRESOURCE(IDI_APPLICATION));
{ _hDefaultSmIcon = (HICON)LoadImageW(nullptr,
// Do not destroy icon handles. They're shared icons as they were loaded from LoadIcon/LoadImage. MAKEINTRESOURCE(IDI_APPLICATION),
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms648063(v=vs.85).aspx IMAGE_ICON,
GetSystemMetrics(SM_CXSMICON),
// Do destroy icons from ExtractIconEx. They're not shared. GetSystemMetrics(SM_CYSMICON),
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms648069(v=vs.85).aspx LR_SHARED);
_DestroyNonDefaultIcons(); #pragma warning(pop)
} }
// Routine Description: // Routine Description:
@@ -297,64 +293,48 @@ Icon& Icon::Instance()
// - phSmIcon - The small icon representation. // - phSmIcon - The small icon representation.
// Return Value: // Return Value:
// - S_OK or HRESULT failure code. // - S_OK or HRESULT failure code.
[[nodiscard]] HRESULT Icon::GetIcons(_Out_opt_ HICON* const phIcon, _Out_opt_ HICON* const phSmIcon) [[nodiscard]] HRESULT Icon::GetIcons(int dpi, _Out_opt_ HICON* const phIcon, _Out_opt_ HICON* const phSmIcon)
{ {
auto hr = S_OK; auto found{ _iconHandlesPerDpi.find(dpi) };
if (found == _iconHandlesPerDpi.end())
if (nullptr != phIcon)
{ {
hr = _GetAvailableIconFromReference(_hIcon, _hDefaultIcon, phIcon); std::ignore = LoadIconsForDpi(dpi);
found = _iconHandlesPerDpi.find(dpi);
} }
if (SUCCEEDED(hr)) if (phIcon)
{ {
if (nullptr != phSmIcon) if (found != _iconHandlesPerDpi.end())
{ {
hr = _GetAvailableIconFromReference(_hSmIcon, _hDefaultSmIcon, phSmIcon); *phIcon = found->second.first.get();
}
if (!*phIcon)
{
*phIcon = _hDefaultIcon;
}
if (!*phIcon)
{
return E_FAIL;
} }
} }
return hr; if (phSmIcon)
}
// Routine Description:
// - Sets custom icons onto the class or resets the icons to defaults. Use a null handle to reset an icon to its default value.
// Arguments:
// - hIcon - The large icon handle or null to reset to default
// - hSmIcon - The small icon handle or null to reset to default
// Return Value:
// - S_OK or HRESULT failure code.
[[nodiscard]] HRESULT Icon::SetIcons(const HICON hIcon, const HICON hSmIcon)
{
auto hr = _SetIconFromReference(_hIcon, hIcon);
if (SUCCEEDED(hr))
{ {
hr = _SetIconFromReference(_hSmIcon, hSmIcon); if (found != _iconHandlesPerDpi.end())
}
if (SUCCEEDED(hr))
{
HICON hNewIcon;
HICON hNewSmIcon;
hr = GetIcons(&hNewIcon, &hNewSmIcon);
if (SUCCEEDED(hr))
{ {
// Special case. If we had a non-default big icon and a default small icon, set the small icon to null when updating the window. *phSmIcon = found->second.second.get();
// This will cause the large one to be stretched and used as the small one. }
if (hNewIcon != _hDefaultIcon && hNewSmIcon == _hDefaultSmIcon) if (!*phSmIcon)
{ {
hNewSmIcon = nullptr; *phSmIcon = _hDefaultSmIcon;
} }
if (!*phSmIcon)
PostMessageW(ServiceLocator::LocateConsoleWindow()->GetWindowHandle(), WM_SETICON, ICON_BIG, (LPARAM)hNewIcon); {
PostMessageW(ServiceLocator::LocateConsoleWindow()->GetWindowHandle(), WM_SETICON, ICON_SMALL, (LPARAM)hNewSmIcon); return E_FAIL;
} }
} }
return hr; return S_OK;
} }
// Routine Description: // Routine Description:
@@ -367,20 +347,22 @@ Icon& Icon::Instance()
// - S_OK or HRESULT failure code. // - S_OK or HRESULT failure code.
[[nodiscard]] HRESULT Icon::LoadIconsFromPath(_In_ PCWSTR pwszIconLocation, const int nIconIndex) [[nodiscard]] HRESULT Icon::LoadIconsFromPath(_In_ PCWSTR pwszIconLocation, const int nIconIndex)
{ {
auto hr = S_OK; _iconPathAndIndex = { std::wstring{ pwszIconLocation }, nIconIndex };
_iconHandlesPerDpi.clear();
return LoadIconsForDpi(96);
}
// Return value is count of icons extracted, which is redundant with filling the pointers. [[nodiscard]] HRESULT Icon::LoadIconsForDpi(int dpi)
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms648069(v=vs.85).aspx {
ConExtractIconInBothSizesW(pwszIconLocation, nIconIndex, &_hIcon, &_hSmIcon); wil::unique_hicon icon, smIcon;
ConExtractIconInBothSizesW(dpi, _iconPathAndIndex.first.c_str(), _iconPathAndIndex.second, &icon, &smIcon);
// If the large icon failed, then ensure that we use the defaults. if (!icon)
if (_hIcon == nullptr)
{ {
_DestroyNonDefaultIcons(); // ensure handles are destroyed/null return E_FAIL;
hr = E_FAIL;
} }
return hr; _iconHandlesPerDpi.try_emplace(dpi, std::pair<wil::unique_hicon, wil::unique_hicon>{ std::move(icon), std::move(smIcon) });
return S_OK;
} }
// Routine Description: // Routine Description:
@@ -392,184 +374,15 @@ Icon& Icon::Instance()
// - hwnd - Handle to apply message workaround to. // - hwnd - Handle to apply message workaround to.
// Return Value: // Return Value:
// - S_OK or HRESULT failure code. // - S_OK or HRESULT failure code.
[[nodiscard]] HRESULT Icon::ApplyWindowMessageWorkaround(const HWND hwnd) [[nodiscard]] HRESULT Icon::ApplyIconsToWindow(const HWND hwnd)
{ {
HICON hIcon; HICON hIcon, hSmIcon;
HICON hSmIcon;
auto hr = GetIcons(&hIcon, &hSmIcon); const auto dpi = ServiceLocator::LocateHighDpiApi<WindowDpiApi>()->GetDpiForWindow(hwnd);
RETURN_IF_FAILED(GetIcons(dpi, &hIcon, &hSmIcon));
if (SUCCEEDED(hr)) SendMessageW(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
{ SendMessageW(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hSmIcon);
SendMessageW(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
SendMessageW(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hSmIcon);
}
return hr; return S_OK;
}
// Routine Description:
// - Initializes the icon class by loading the default icons.
// Arguments:
// - <none>
// Return Value:
// - S_OK or HRESULT failure code.
[[nodiscard]] HRESULT Icon::_Initialize()
{
auto hr = S_OK;
if (!_fInitialized)
{
#pragma warning(push)
#pragma warning(disable : 4302) // typecast warning from MAKEINTRESOURCE
_hDefaultIcon = LoadIconW(nullptr, MAKEINTRESOURCE(IDI_APPLICATION));
#pragma warning(pop)
if (_hDefaultIcon == nullptr)
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
if (SUCCEEDED(hr))
{
#pragma warning(push)
#pragma warning(disable : 4302) // typecast warning from MAKEINTRESOURCE
_hDefaultSmIcon = (HICON)LoadImageW(nullptr,
MAKEINTRESOURCE(IDI_APPLICATION),
IMAGE_ICON,
GetSystemMetrics(SM_CXSMICON),
GetSystemMetrics(SM_CYSMICON),
LR_SHARED);
#pragma warning(pop)
if (_hDefaultSmIcon == nullptr)
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
if (SUCCEEDED(hr))
{
_fInitialized = true;
}
}
return hr;
}
// Routine Description:
// - Frees any non-default icon handles we may have loaded from a path on the file system
// Arguments:
// - <none>
// Return Value:
// - <none>
void Icon::_DestroyNonDefaultIcons()
{
_FreeIconFromReference(_hIcon);
_FreeIconFromReference(_hSmIcon);
}
// Routine Description:
// - Helper method to choose one of the two given references to fill the pointer with.
// - It will choose the specific icon if it is available and otherwise fall back to the default icon.
// Arguments:
// - hIconRef - reference to the specific icon handle inside this class
// - hDefaultIconRef - reference to the default icon handle inside this class
// - phIcon - pointer to receive the chosen icon handle
// Return Value:
// - S_OK or HRESULT failure code.
[[nodiscard]] HRESULT Icon::_GetAvailableIconFromReference(_In_ HICON& hIconRef, _In_ HICON& hDefaultIconRef, _Out_ HICON* const phIcon)
{
auto hr = S_OK;
// expecting hIconRef to be pointing to either the regular or small custom handles
FAIL_FAST_IF(!(&hIconRef == &_hIcon || &hIconRef == &_hSmIcon));
// expecting hDefaultIconRef to be pointing to either the regular or small default handles
FAIL_FAST_IF(!(&hDefaultIconRef == &_hDefaultIcon || &hDefaultIconRef == &_hDefaultSmIcon));
if (hIconRef != nullptr)
{
*phIcon = hIconRef;
}
else
{
hr = _GetDefaultIconFromReference(hDefaultIconRef, phIcon);
}
return hr;
}
// Routine Description:
// - Helper method to initialize and retrieve a default icon.
// Arguments:
// - hIconRef - Either the small or large icon handle references within this class
// - phIcon - The pointer to fill with the icon if it is available.
// Return Value:
// - S_OK or HRESULT failure code.
[[nodiscard]] HRESULT Icon::_GetDefaultIconFromReference(_In_ HICON& hIconRef, _Out_ HICON* const phIcon)
{
// expecting hIconRef to be pointing to either the regular or small default handles
FAIL_FAST_IF(!(&hIconRef == &_hDefaultIcon || &hIconRef == &_hDefaultSmIcon));
auto hr = _Initialize();
if (SUCCEEDED(hr))
{
*phIcon = hIconRef;
}
return hr;
}
// Routine Description:
// - Helper method to set an icon handle into the given reference to an icon within this class.
// - This will appropriately call to free existing custom icons.
// Arguments:
// - hIconRef - Either the small or large icon handle references within this class
// - hNewIcon - The new icon handle to replace the reference with.
// Return Value:
// - S_OK or HRESULT failure code.
[[nodiscard]] HRESULT Icon::_SetIconFromReference(_In_ HICON& hIconRef, const HICON hNewIcon)
{
// expecting hIconRef to be pointing to either the regular or small custom handles
FAIL_FAST_IF(!(&hIconRef == &_hIcon || &hIconRef == &_hSmIcon));
auto hr = S_OK;
// Only set icon if something changed
if (hNewIcon != hIconRef)
{
// If we had an existing custom icon, free it.
_FreeIconFromReference(hIconRef);
// If we were given a non-null icon, store it.
if (hNewIcon != nullptr)
{
hIconRef = hNewIcon;
}
// Otherwise, we'll default back to using the default icon. Get method will handle this.
}
return hr;
}
// Routine Description:
// - Helper method to free a specific icon reference to a specific icon within this class.
// - WARNING: Do not use with the default icons. They do not need to be released.
// Arguments:
// - hIconRef - Either the small or large specific icon handle references within this class
// Return Value:
// - None
void Icon::_FreeIconFromReference(_In_ HICON& hIconRef)
{
// expecting hIconRef to be pointing to either the regular or small custom handles
FAIL_FAST_IF(!(&hIconRef == &_hIcon || &hIconRef == &_hSmIcon));
if (hIconRef != nullptr)
{
DestroyIcon(hIconRef);
hIconRef = nullptr;
}
} }

View File

@@ -22,34 +22,23 @@ namespace Microsoft::Console::Interactivity::Win32
public: public:
static Icon& Instance(); static Icon& Instance();
[[nodiscard]] HRESULT GetIcons(_Out_opt_ HICON* const phIcon, _Out_opt_ HICON* const phSmIcon); [[nodiscard]] HRESULT GetIcons(int dpi, _Out_opt_ HICON* const phIcon, _Out_opt_ HICON* const phSmIcon);
[[nodiscard]] HRESULT SetIcons(const HICON hIcon, const HICON hSmIcon);
[[nodiscard]] HRESULT LoadIconsFromPath(_In_ PCWSTR pwszIconLocation, const int nIconIndex); [[nodiscard]] HRESULT LoadIconsFromPath(_In_ PCWSTR pwszIconLocation, const int nIconIndex);
[[nodiscard]] HRESULT ApplyIconsToWindow(const HWND hwnd);
[[nodiscard]] HRESULT ApplyWindowMessageWorkaround(const HWND hwnd);
protected: protected:
Icon(); Icon();
~Icon(); ~Icon() = default;
Icon(const Icon&) = delete; Icon(const Icon&) = delete;
void operator=(const Icon&) = delete; void operator=(const Icon&) = delete;
private: private:
[[nodiscard]] HRESULT _Initialize(); [[nodiscard]] HRESULT LoadIconsForDpi(int dpi);
// We are not using unique_hicon for these, as they are loaded from our mapped executable image.
HICON _hDefaultIcon{ nullptr };
HICON _hDefaultSmIcon{ nullptr };
void _DestroyNonDefaultIcons(); std::unordered_map<int, std::pair<wil::unique_hicon, wil::unique_hicon>> _iconHandlesPerDpi;
std::pair<std::wstring, int> _iconPathAndIndex;
// Helper methods
[[nodiscard]] HRESULT _GetAvailableIconFromReference(_In_ HICON& hIconRef, _In_ HICON& hDefaultIconRef, _Out_ HICON* const phIcon);
[[nodiscard]] HRESULT _GetDefaultIconFromReference(_In_ HICON& hIconRef, _Out_ HICON* const phIcon);
[[nodiscard]] HRESULT _SetIconFromReference(_In_ HICON& hIconRef, const HICON hNewIcon);
void _FreeIconFromReference(_In_ HICON& hIconRef);
bool _fInitialized;
HICON _hDefaultIcon;
HICON _hDefaultSmIcon;
HICON _hIcon;
HICON _hSmIcon;
}; };
} }

View File

@@ -316,7 +316,7 @@ void Menu::s_ShowPropertiesDialog(HWND const hwnd, BOOL const Defaults)
pStateInfo->CursorType = static_cast<unsigned int>(cursor.GetType()); pStateInfo->CursorType = static_cast<unsigned int>(cursor.GetType());
// Retrieve small icon for use in displaying the dialog // Retrieve small icon for use in displaying the dialog
LOG_IF_FAILED(Icon::Instance().GetIcons(nullptr, &pStateInfo->hIcon)); LOG_IF_FAILED(Icon::Instance().GetIcons(96, nullptr, &pStateInfo->hIcon));
pStateInfo->QuickEdit = !!(gci.Flags & CONSOLE_QUICK_EDIT_MODE); pStateInfo->QuickEdit = !!(gci.Flags & CONSOLE_QUICK_EDIT_MODE);
pStateInfo->AutoPosition = !!(gci.Flags & CONSOLE_AUTO_POSITION); pStateInfo->AutoPosition = !!(gci.Flags & CONSOLE_AUTO_POSITION);

View File

@@ -138,7 +138,7 @@ Window::~Window()
wc.lpszClassName = CONSOLE_WINDOW_CLASS; wc.lpszClassName = CONSOLE_WINDOW_CLASS;
// Load icons // Load icons
status = Icon::Instance().GetIcons(&wc.hIcon, &wc.hIconSm); status = Icon::Instance().GetIcons(96, &wc.hIcon, &wc.hIconSm);
if (SUCCEEDED_NTSTATUS(status)) if (SUCCEEDED_NTSTATUS(status))
{ {
@@ -335,7 +335,7 @@ void Window::_UpdateSystemMetrics() const
if (SUCCEEDED_NTSTATUS(status)) if (SUCCEEDED_NTSTATUS(status))
{ {
// Do WM_GETICON workaround. Must call WM_SETICON once or apps calling WM_GETICON will get null. // Do WM_GETICON workaround. Must call WM_SETICON once or apps calling WM_GETICON will get null.
LOG_IF_FAILED(Icon::Instance().ApplyWindowMessageWorkaround(hWnd)); LOG_IF_FAILED(Icon::Instance().ApplyIconsToWindow(hWnd));
// Set up the hot key for this window. // Set up the hot key for this window.
if (gci.GetHotKey() != 0) if (gci.GetHotKey() != 0)

View File

@@ -7,6 +7,7 @@
#include "clipboard.hpp" #include "clipboard.hpp"
#include "find.h" #include "find.h"
#include "menu.hpp" #include "menu.hpp"
#include "icon.hpp"
#include "windowdpiapi.hpp" #include "windowdpiapi.hpp"
#include "windowio.hpp" #include "windowio.hpp"
#include "windowmetrics.hpp" #include "windowmetrics.hpp"
@@ -267,6 +268,8 @@ static constexpr TsfDataProvider s_tsfDataProvider;
RECT_HEIGHT(prcNewScale), RECT_HEIGHT(prcNewScale),
SWP_NOZORDER | SWP_NOACTIVATE); SWP_NOZORDER | SWP_NOACTIVATE);
LOG_IF_FAILED(Icon::Instance().ApplyIconsToWindow(hWnd));
_fInDPIChange = false; _fInDPIChange = false;
break; break;