@@ -196,9 +196,39 @@ struct Window {
196
196
private:
197
197
long dpi = 96 ;
198
198
int metrics [SM_CMETRICS] = { 0 };
199
- HICON icons [IconSizesCount] = { NULL };
200
199
HCURSOR cursor = NULL ;
201
-
200
+
201
+ struct {
202
+ HICON standard [IconSizesCount] = { NULL };
203
+
204
+ // cache for icons of different DPI
205
+
206
+ struct PerDpiIcon {
207
+ WPARAM type = 0 ;
208
+ LPARAM dpi = 0 ;
209
+ HICON icon = NULL ;
210
+ };
211
+ struct {
212
+ PerDpiIcon * data = nullptr ;
213
+ std::size_t n = 0 ;
214
+
215
+ struct {
216
+ bool found;
217
+ PerDpiIcon * icon;
218
+
219
+ } find (WPARAM type, LPARAM dpi) {
220
+ for (std::size_t i = 0 ; i != n; ++i) {
221
+ if ((this ->data [i].type == type) && (this ->data [i].dpi == dpi))
222
+ return { true , &this ->data [i] };
223
+
224
+ if (this ->data [i].dpi == 0 )
225
+ return { false , &this ->data [i] };
226
+ }
227
+ return { false , nullptr };
228
+ }
229
+ } dpi;
230
+ } icons;
231
+
202
232
explicit Window (HWND hWnd)
203
233
: hWnd (hWnd)
204
234
, dpi (GetDPI (hWnd)) {};
@@ -207,7 +237,7 @@ struct Window {
207
237
// - we want crisp icons wherever possible
208
238
// - including the larger sizes is just flexing
209
239
//
210
- SIZE GetIconMetrics (IconSize size, UINT dpiSystem) {
240
+ SIZE GetIconMetrics (IconSize size, UINT dpiSystem = 0 ) {
211
241
switch (size) {
212
242
case SmallIconSize:
213
243
return { metrics [SM_CXSMICON], metrics [SM_CYSMICON] };
@@ -222,6 +252,9 @@ struct Window {
222
252
223
253
case ShellIconSize:
224
254
case JumboIconSize:
255
+ if (dpiSystem == 0 ) {
256
+ dpiSystem = GetDPI (NULL );
257
+ }
225
258
if (IsWindowsVistaOrGreater () || (size == ShellIconSize)) { // XP doesn't have Jumbo
226
259
if (HMODULE hShell32 = GetModuleHandle (L" SHELL32" )) {
227
260
HRESULT (WINAPI * ptrSHGetImageList) (int , const GUID &, void **) = NULL ;
@@ -254,6 +287,26 @@ struct Window {
254
287
}
255
288
}
256
289
290
+ // MapIconSize
291
+ // - selecting proper IconSize from WM_GETICON/WM_SETICON wParam
292
+ // - using proper size for Windows 10 taskbar
293
+ //
294
+ IconSize MapIconSize (WPARAM type) {
295
+ switch (type) {
296
+ case ICON_BIG:
297
+ if (IsWindows10OrGreater ()) {
298
+ return StartIconSize;
299
+ } else {
300
+ return LargeIconSize;
301
+ }
302
+ case ICON_SMALL:
303
+ case ICON_SMALL2:
304
+ return SmallIconSize;
305
+ default :
306
+ return LargeIconSize;
307
+ }
308
+ }
309
+
257
310
// RefreshVisualMetrics
258
311
// - enables us to use 'this->metrics [SM_xxx]' whenever we need some metrics
259
312
// instead of calling the function at every spot
@@ -347,6 +400,33 @@ struct Window {
347
400
}
348
401
break ;
349
402
403
+ case WM_GETICON:
404
+ if (lParam && (lParam != this ->dpi )) {
405
+
406
+ // OS (taskbars on different displays) or other app asked for icon in different DPI
407
+ if (auto [found, data] = this ->icons .dpi .find (wParam, lParam); found) {
408
+ return (LRESULT) data->icon ;
409
+
410
+ } else {
411
+ auto ndpi = (long ) lParam;
412
+ auto size = GetIconMetrics (this ->MapIconSize (wParam));
413
+ auto icon = LoadBestIcon (reinterpret_cast <HINSTANCE> (&__ImageBase), MAKEINTRESOURCE (1 ),
414
+ { ndpi * size.cx / 96 , ndpi * size.cy / 96 });
415
+ if (data) {
416
+ data->type = wParam;
417
+ data->dpi = ndpi;
418
+ data->icon = icon;
419
+ }
420
+ return (LRESULT) icon;
421
+ }
422
+ } else {
423
+ switch (wParam) {
424
+ case ICON_SMALL2:
425
+ return (LRESULT) this ->icons .standard [this ->MapIconSize (wParam)];
426
+ }
427
+ }
428
+ break ;
429
+
350
430
case WM_DPICHANGED:
351
431
return this ->OnDpiChange (wParam, reinterpret_cast <const RECT *> (lParam));
352
432
case WM_WINDOWPOSCHANGED:
@@ -393,7 +473,7 @@ struct Window {
393
473
return 0 ;
394
474
}
395
475
LRESULT OnDestroy () {
396
- for (auto icon : this ->icons ) {
476
+ for (auto icon : this ->icons . standard ) {
397
477
DestroyIcon (icon);
398
478
}
399
479
PostQuitMessage (0 );
@@ -477,10 +557,19 @@ struct Window {
477
557
for (auto i = 0u ; i != IconSizesCount; ++i) {
478
558
if (auto icon = LoadBestIcon (reinterpret_cast <HINSTANCE> (&__ImageBase), MAKEINTRESOURCE (1 ),
479
559
GetIconMetrics ((IconSize) i, dpiSystem))) {
480
- if (this ->icons [i]) {
481
- DestroyIcon (this ->icons [i]);
560
+ if (this ->icons . standard [i]) {
561
+ DestroyIcon (this ->icons . standard [i]);
482
562
}
483
- this ->icons [i] = icon;
563
+ this ->icons .standard [i] = icon;
564
+ }
565
+ }
566
+ for (auto i = 0u ; i != this ->icons .dpi .n ; ++i) {
567
+ if (this ->icons .dpi .data [i].icon ) {
568
+ DestroyIcon (this ->icons .dpi .data [i].icon );
569
+
570
+ this ->icons .dpi .data [i].type = 0 ;
571
+ this ->icons .dpi .data [i].dpi = 0 ;
572
+ this ->icons .dpi .data [i].icon = NULL ;
484
573
}
485
574
}
486
575
@@ -490,8 +579,8 @@ struct Window {
490
579
// and surprisingly pinning it explicitly with 24x24 icon won't work either, such
491
580
// icon will be scaled up to 32x32 and then down to 24x24 resulting in blurry mess
492
581
493
- SendMessage (hWnd, WM_SETICON, ICON_SMALL, (LPARAM) this ->icons [SmallIconSize ]);
494
- SendMessage (hWnd, WM_SETICON, ICON_BIG, (LPARAM) this ->icons [ IsWindows10OrGreater () ? StartIconSize : LargeIconSize ]);
582
+ SendMessage (hWnd, WM_SETICON, ICON_SMALL, (LPARAM) this ->icons . standard [ MapIconSize (ICON_SMALL) ]);
583
+ SendMessage (hWnd, WM_SETICON, ICON_BIG, (LPARAM) this ->icons . standard [ MapIconSize (ICON_BIG) ]);
495
584
496
585
return 0 ;
497
586
}
@@ -596,7 +685,7 @@ struct Window {
596
685
597
686
int CALLBACK wWinMain (_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE, _In_ LPWSTR, _In_ int nCmdShow) {
598
687
InitCommonControls ();
599
-
688
+
600
689
if (HMODULE hUser32 = GetModuleHandle (L" USER32" )) {
601
690
Symbol (hUser32, ptrEnableNonClientDpiScaling, " EnableNonClientDpiScaling" );
602
691
Symbol (hUser32, pfnGetDpiForSystem, " GetDpiForSystem" );
0 commit comments