Skip to content

Commit 522045c

Browse files
committed
Improve context menu icon creation
Fixes #14 - Possible to use taskbar icon for progressive web apps (and shortcuts with custom icons) in the minimize/maximize popup menu list?
1 parent 9b0485f commit 522045c

File tree

6 files changed

+159
-81
lines changed

6 files changed

+159
-81
lines changed

CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,12 @@ add_executable(Finestray WIN32
9696
src/Bitmap.cpp
9797
src/Bitmap.h
9898
src/BitmapHandleWrapper.h
99+
src/BrushHandleWrapper.h
99100
src/COMLibraryWrapper.h
100101
src/ContextMenu.cpp
101102
src/ContextMenu.h
102103
src/DeviceContextHandleWrapper.h
104+
src/ErrorContext.h
103105
src/File.cpp
104106
src/File.h
105107
src/Finestray.cpp
@@ -129,13 +131,13 @@ add_executable(Finestray WIN32
129131
src/StringUtility.h
130132
src/TrayIcon.cpp
131133
src/TrayIcon.h
132-
src/WinEventHookHandleWrapper.h
133134
src/WindowHandleWrapper.h
134135
src/WindowIcon.cpp
135136
src/WindowIcon.h
136137
src/WindowList.cpp
137138
src/WindowList.h
138139
src/WindowMessage.h
140+
src/WinEventHookHandleWrapper.h
139141
${CMAKE_CURRENT_BINARY_DIR}/Finestray.ico
140142
)
141143

src/Bitmap.cpp

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,70 @@ bool replaceBitmapColor(HBITMAP hbmp, COLORREF oldColor, COLORREF newColor)
7777

7878
return replaced;
7979
}
80+
81+
bool replaceBitmapMaskColor(HBITMAP hbmp, HBITMAP hmask, COLORREF newColor)
82+
{
83+
if (!hbmp || !hmask) {
84+
return false;
85+
}
86+
87+
BITMAP maskBitmap;
88+
if (!GetObjectA(hmask, sizeof(BITMAP), &maskBitmap)) {
89+
WARNING_PRINTF(
90+
"failed to get mask bitmap object, GetObject() failed: %s\n",
91+
StringUtility::lastErrorString().c_str());
92+
return false;
93+
}
94+
95+
// BITMAP colorBitmap;
96+
// if (!GetObjectA(hbmp, sizeof(BITMAP), &colorBitmap)) {
97+
// WARNING_PRINTF(
98+
// "failed to get color bitmap object, GetObject() failed: %s\n",
99+
// StringUtility::lastErrorString().c_str());
100+
// return false;
101+
// }
102+
103+
DeviceContextHandleWrapper desktopDC(GetDC(HWND_DESKTOP), DeviceContextHandleWrapper::Referenced);
104+
if (!desktopDC) {
105+
WARNING_PRINTF(
106+
"failed to get desktop device context, GetDC() failed: %s\n",
107+
StringUtility::lastErrorString().c_str());
108+
return false;
109+
}
110+
111+
DeviceContextHandleWrapper maskDC(CreateCompatibleDC(desktopDC), DeviceContextHandleWrapper::Created);
112+
DeviceContextHandleWrapper colorDC(CreateCompatibleDC(desktopDC), DeviceContextHandleWrapper::Created);
113+
if (!maskDC || !colorDC) {
114+
WARNING_PRINTF(
115+
"failed to create compatible device contexts, CreateCompatibleDC() failed: %s\n",
116+
StringUtility::lastErrorString().c_str());
117+
return false;
118+
}
119+
120+
if (!maskDC.selectObject(hmask) || !colorDC.selectObject(hbmp)) {
121+
return false;
122+
}
123+
124+
bool replaced = false;
125+
for (int y = 0; y < maskBitmap.bmHeight; ++y) {
126+
for (int x = 0; x < maskBitmap.bmWidth; ++x) {
127+
COLORREF maskPixel = GetPixel(maskDC, x, y);
128+
if (maskPixel == RGB(255, 255, 255)) {
129+
SetPixel(colorDC, x, y, newColor);
130+
replaced = true;
131+
}
132+
}
133+
}
134+
135+
return replaced;
136+
}
137+
138+
HBITMAP scaleBitmap(HBITMAP hbmp, int width, int height)
139+
{
140+
HBITMAP scaledBitmap = (HBITMAP)CopyImage(hbmp, IMAGE_BITMAP, width, height, LR_COPYDELETEORG);
141+
if (!scaledBitmap) {
142+
WARNING_PRINTF("failed to scale bitmap, CopyImage() failed: %s\n", StringUtility::lastErrorString().c_str());
143+
return nullptr;
144+
}
145+
return scaledBitmap;
146+
}

src/Bitmap.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@
1919

2020
HBITMAP getResourceBitmap(unsigned int id);
2121
bool replaceBitmapColor(HBITMAP hbmp, COLORREF oldColor, COLORREF newColor);
22+
bool replaceBitmapMaskColor(HBITMAP hbmp, HBITMAP hmask, COLORREF newColor);
23+
HBITMAP scaleBitmap(HBITMAP hbmp, int width, int height);

src/BrushHandleWrapper.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2020 Benbuck Nason
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#pragma once
16+
17+
// App
18+
#include "Log.h"
19+
#include "StringUtility.h"
20+
21+
// Windows
22+
#include <Windows.h>
23+
24+
class BrushHandleWrapper
25+
{
26+
public:
27+
explicit BrushHandleWrapper(HBRUSH hbrush)
28+
: hbrush_(hbrush)
29+
{
30+
}
31+
32+
~BrushHandleWrapper()
33+
{
34+
if (hbrush_) {
35+
if (!DeleteObject(hbrush_)) {
36+
WARNING_PRINTF("failed to destroy brush %#x: %s\n", hbrush_, StringUtility::lastErrorString().c_str());
37+
}
38+
}
39+
}
40+
41+
operator HBRUSH() const { return hbrush_; }
42+
43+
operator bool() const { return hbrush_ != nullptr; }
44+
45+
private:
46+
HBRUSH hbrush_ {};
47+
};

src/DeviceContextHandleWrapper.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,8 @@ class DeviceContextHandleWrapper
4545
~DeviceContextHandleWrapper()
4646
{
4747
if (hdc_) {
48-
for (HGDIOBJ object : objects_) {
49-
if (!DeleteObject(object)) {
50-
WARNING_PRINTF("DeleteObject() failed: %s\n", StringUtility::lastErrorString().c_str());
51-
}
48+
for (auto it = objects_.rbegin(); it != objects_.rend(); ++it) {
49+
SelectObject(hdc_, *it);
5250
}
5351

5452
switch (mode_) {

src/WindowIcon.cpp

Lines changed: 38 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -15,45 +15,39 @@
1515

1616
// App
1717
#include "WindowIcon.h"
18+
#include "BrushHandleWrapper.h"
1819
#include "DeviceContextHandleWrapper.h"
1920
#include "Log.h"
2021
#include "StringUtility.h"
2122

2223
namespace WindowIcon
2324
{
2425

25-
namespace
26-
{
27-
28-
bool replaceBitmapMaskColor(HBITMAP hbmp, HBITMAP hmask, COLORREF newColor);
29-
30-
} // anonymous namespace
31-
3226
HICON get(HWND hwnd)
3327
{
3428
HICON hicon;
3529

36-
hicon = (HICON)GetClassLongPtr(hwnd, GCLP_HICONSM);
30+
hicon = (HICON)SendMessage(hwnd, WM_GETICON, ICON_SMALL, 0);
3731
if (hicon) {
3832
return hicon;
3933
}
4034

41-
hicon = (HICON)GetClassLongPtr(hwnd, GCLP_HICON);
35+
hicon = (HICON)SendMessage(hwnd, WM_GETICON, ICON_BIG, 0);
4236
if (hicon) {
4337
return hicon;
4438
}
4539

46-
hicon = (HICON)SendMessage(hwnd, WM_GETICON, ICON_SMALL, 0);
40+
hicon = (HICON)SendMessage(hwnd, WM_GETICON, ICON_SMALL2, 0);
4741
if (hicon) {
4842
return hicon;
4943
}
5044

51-
hicon = (HICON)SendMessage(hwnd, WM_GETICON, ICON_BIG, 0);
45+
hicon = (HICON)GetClassLongPtr(hwnd, GCLP_HICONSM);
5246
if (hicon) {
5347
return hicon;
5448
}
5549

56-
hicon = (HICON)SendMessage(hwnd, WM_GETICON, ICON_SMALL2, 0);
50+
hicon = (HICON)GetClassLongPtr(hwnd, GCLP_HICON);
5751
if (hicon) {
5852
return hicon;
5953
}
@@ -73,95 +67,63 @@ HBITMAP bitmap(HWND hwnd)
7367
return nullptr;
7468
}
7569

76-
// return bitmapFromIcon(hicon);
77-
7870
ICONINFOEXA iconInfo;
71+
memset(&iconInfo, 0, sizeof(iconInfo));
7972
iconInfo.cbSize = sizeof(ICONINFOEXA);
8073
if (!GetIconInfoExA(hicon, &iconInfo)) {
81-
return nullptr;
82-
}
83-
84-
DWORD menuColor = GetSysColor(COLOR_MENU);
85-
COLORREF newColor = RGB(GetBValue(menuColor), GetGValue(menuColor), GetRValue(menuColor));
86-
replaceBitmapMaskColor(iconInfo.hbmColor, iconInfo.hbmMask, newColor);
87-
DeleteObject(iconInfo.hbmMask);
88-
89-
HBITMAP scaledBitmap = (HBITMAP)CopyImage(
90-
iconInfo.hbmColor,
91-
IMAGE_BITMAP,
92-
GetSystemMetrics(SM_CXMENUCHECK),
93-
GetSystemMetrics(SM_CYMENUCHECK),
94-
LR_COPYDELETEORG);
95-
if (!scaledBitmap) {
96-
WARNING_PRINTF("failed to scale bitmap, CopyImage() failed: %s\n", StringUtility::lastErrorString().c_str());
97-
} else {
98-
DeleteObject(iconInfo.hbmColor);
99-
iconInfo.hbmColor = scaledBitmap;
100-
}
101-
102-
return iconInfo.hbmColor;
103-
}
104-
105-
namespace
106-
{
107-
108-
bool replaceBitmapMaskColor(HBITMAP hbmp, HBITMAP hmask, COLORREF newColor)
109-
{
110-
if (!hbmp || !hmask) {
111-
return false;
112-
}
113-
114-
BITMAP maskBitmap;
115-
if (!GetObjectA(hmask, sizeof(BITMAP), &maskBitmap)) {
11674
WARNING_PRINTF(
117-
"failed to get mask bitmap object, GetObject() failed: %s\n",
75+
"failed to get icon info for %#x, GetIconInfoEx() failed: %s\n",
76+
hwnd,
11877
StringUtility::lastErrorString().c_str());
119-
return false;
78+
return nullptr;
12079
}
12180

12281
BITMAP colorBitmap;
123-
if (!GetObjectA(hbmp, sizeof(BITMAP), &colorBitmap)) {
82+
if (!GetObjectA(iconInfo.hbmColor, sizeof(BITMAP), &colorBitmap)) {
12483
WARNING_PRINTF(
12584
"failed to get color bitmap object, GetObject() failed: %s\n",
12685
StringUtility::lastErrorString().c_str());
127-
return false;
86+
return nullptr;
12887
}
12988

130-
DeviceContextHandleWrapper desktopDC(GetDC(HWND_DESKTOP), DeviceContextHandleWrapper::Referenced);
131-
if (!desktopDC) {
89+
DeviceContextHandleWrapper displayDC(
90+
CreateICA("DISPLAY", nullptr, nullptr, nullptr),
91+
DeviceContextHandleWrapper::Created);
92+
if (!displayDC) {
13293
WARNING_PRINTF(
133-
"failed to get desktop device context, GetDC() failed: %s\n",
94+
"failed to get desktop information context, CreateICA() failed: %s\n",
13495
StringUtility::lastErrorString().c_str());
135-
return false;
96+
return nullptr;
13697
}
13798

138-
DeviceContextHandleWrapper maskDC(CreateCompatibleDC(desktopDC), DeviceContextHandleWrapper::Created);
139-
DeviceContextHandleWrapper colorDC(CreateCompatibleDC(desktopDC), DeviceContextHandleWrapper::Created);
140-
if (!maskDC || !colorDC) {
99+
int cx = GetSystemMetrics(SM_CXMENUCHECK);
100+
int cy = GetSystemMetrics(SM_CYMENUCHECK);
101+
102+
HBITMAP hbitmap = CreateCompatibleBitmap(displayDC, cx, cy);
103+
104+
DeviceContextHandleWrapper bitmapDC(CreateCompatibleDC(displayDC), DeviceContextHandleWrapper::Created);
105+
if (!bitmapDC) {
141106
WARNING_PRINTF(
142-
"failed to create compatible device contexts, CreateCompatibleDC() failed: %s\n",
107+
"failed to get desktop device context, CreateCompatibleDC() failed: %s\n",
143108
StringUtility::lastErrorString().c_str());
144-
return false;
109+
return nullptr;
145110
}
146111

147-
if (!maskDC.selectObject(hmask) || !colorDC.selectObject(hbmp)) {
148-
return false;
112+
if (!bitmapDC.selectObject(hbitmap)) {
113+
return nullptr;
149114
}
150115

151-
bool replaced = false;
152-
for (int y = 0; y < maskBitmap.bmHeight; ++y) {
153-
for (int x = 0; x < maskBitmap.bmWidth; ++x) {
154-
COLORREF maskPixel = GetPixel(maskDC, x, y);
155-
if (maskPixel == RGB(255, 255, 255)) {
156-
SetPixel(colorDC, x, y, newColor);
157-
replaced = true;
158-
}
159-
}
116+
RECT rect = { 0, 0, cx, cy };
117+
BrushHandleWrapper brush(CreateSolidBrush(GetSysColor(COLOR_MENU)));
118+
if (!FillRect(bitmapDC, &rect, brush)) {
119+
WARNING_PRINTF("failed to fill background, FillRect() failed: %s\n", StringUtility::lastErrorString().c_str());
160120
}
161121

162-
return replaced;
163-
}
122+
if (!DrawIconEx(bitmapDC, 0, 0, hicon, cx, cy, 0, 0, DI_NORMAL)) {
123+
WARNING_PRINTF("failed to draw icon, DrawIconEx() failed: %s\n", StringUtility::lastErrorString().c_str());
124+
}
164125

165-
} // anonymous namespace
126+
return hbitmap;
127+
}
166128

167129
} // namespace WindowIcon

0 commit comments

Comments
 (0)