Skip to content

Commit ebfb139

Browse files
committed
fix win icon
1 parent e7cbd46 commit ebfb139

File tree

3 files changed

+132
-72
lines changed

3 files changed

+132
-72
lines changed

v2/internal/frontend/desktop/linux/notifications.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ func (f *Frontend) loadCategories() error {
391391
}
392392

393393
_categories := make(map[string]frontend.NotificationCategory)
394-
if err := json.Unmarshal(categoriesData, &categories); err != nil {
394+
if err := json.Unmarshal(categoriesData, &_categories); err != nil {
395395
return fmt.Errorf("failed to unmarshal notification categories: %w", err)
396396
}
397397

v2/internal/frontend/desktop/windows/notifications.go

+11
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
wintoast "git.sr.ht/~jackmordaunt/go-toast/v2/wintoast"
1212
"github.com/google/uuid"
1313
"github.com/wailsapp/wails/v2/internal/frontend"
14+
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
15+
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
1416

1517
"fmt"
1618
"os"
@@ -292,6 +294,15 @@ func (f *Frontend) OnNotificationResponse(callback func(result frontend.Notifica
292294
}
293295

294296
func (f *Frontend) saveIconToDir() error {
297+
hIcon := w32.ExtractIcon(exePath, 0)
298+
if hIcon == 0 {
299+
return fmt.Errorf("ExtractIcon failed for %s", exePath)
300+
}
301+
302+
if err := winc.SaveHIconAsPNG(hIcon, iconPath); err != nil {
303+
return fmt.Errorf("SaveHIconAsPNG failed: %w", err)
304+
}
305+
295306
return nil
296307
}
297308

v2/internal/frontend/desktop/windows/winc/icon.go

+120-71
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"errors"
1212
"fmt"
1313
"image"
14+
"image/color"
1415
"image/png"
1516
"os"
1617
"syscall"
@@ -37,36 +38,44 @@ type ICONINFO struct {
3738
HbmColor uintptr
3839
}
3940

40-
// BITMAP mirrors the Win32 BITMAP struct for GetObject
41-
type BITMAP struct {
42-
Type int32
43-
Width int32
44-
Height int32
45-
WidthBytes int32
46-
Planes uint16
47-
BitsPixel uint16
48-
Bits uintptr
41+
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183376.aspx
42+
type BITMAPINFOHEADER struct {
43+
BiSize uint32
44+
BiWidth int32
45+
BiHeight int32
46+
BiPlanes uint16
47+
BiBitCount uint16
48+
BiCompression uint32
49+
BiSizeImage uint32
50+
BiXPelsPerMeter int32
51+
BiYPelsPerMeter int32
52+
BiClrUsed uint32
53+
BiClrImportant uint32
4954
}
5055

51-
// BITMAPINFOHEADER mirrors the Win32 BITMAPINFOHEADER
52-
type BITMAPINFOHEADER struct {
53-
Size uint32
54-
Width int32
55-
Height int32
56-
Planes uint16
57-
BitCount uint16
58-
Compression uint32
59-
SizeImage uint32
60-
XPelsPerMeter int32
61-
YPelsPerMeter int32
62-
ClrUsed uint32
63-
ClrImportant uint32
56+
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162938.aspx
57+
type RGBQUAD struct {
58+
RgbBlue byte
59+
RgbGreen byte
60+
RgbRed byte
61+
RgbReserved byte
6462
}
6563

66-
// BITMAPINFO wraps a header plus color table
64+
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183375.aspx
6765
type BITMAPINFO struct {
68-
Header BITMAPINFOHEADER
69-
Colors [1]uint32
66+
BmiHeader BITMAPINFOHEADER
67+
BmiColors *RGBQUAD
68+
}
69+
70+
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183371.aspx
71+
type BITMAP struct {
72+
BmType int32
73+
BmWidth int32
74+
BmHeight int32
75+
BmWidthBytes int32
76+
BmPlanes uint16
77+
BmBitsPixel uint16
78+
BmBits unsafe.Pointer
7079
}
7180

7281
const (
@@ -105,71 +114,111 @@ func ExtractIcon(fileName string, index int) (*Icon, error) {
105114
return ico, err
106115
}
107116

108-
// SaveHIconAsPNG extracts the color bitmap from an HICON and writes it to a PNG file.
109-
func SaveHIconAsPNG(hIcon w32.HICON, destPath string) error {
110-
// 1) Get the ICONINFO structure (which contains two HBITMAPs)
111-
var ii ICONINFO
112-
if ret, _, err := procGetIconInfo.Call(
117+
func SaveHIconAsPNG(hIcon w32.HICON, filePath string) error {
118+
// Load necessary DLLs
119+
user32 := syscall.NewLazyDLL("user32.dll")
120+
gdi32 := syscall.NewLazyDLL("gdi32.dll")
121+
122+
// Get procedures
123+
getIconInfo := user32.NewProc("GetIconInfo")
124+
getObject := gdi32.NewProc("GetObjectW")
125+
createCompatibleDC := gdi32.NewProc("CreateCompatibleDC")
126+
selectObject := gdi32.NewProc("SelectObject")
127+
getDIBits := gdi32.NewProc("GetDIBits")
128+
deleteObject := gdi32.NewProc("DeleteObject")
129+
deleteDC := gdi32.NewProc("DeleteDC")
130+
131+
// Get icon info
132+
var iconInfo ICONINFO
133+
ret, _, err := getIconInfo.Call(
113134
uintptr(hIcon),
114-
uintptr(unsafe.Pointer(&ii)),
115-
); ret == 0 {
135+
uintptr(unsafe.Pointer(&iconInfo)),
136+
)
137+
if ret == 0 {
116138
return err
117139
}
118-
// Make sure we free the bitmaps when done
119-
defer procDeleteObject.Call(ii.HbmMask)
120-
defer procDeleteObject.Call(ii.HbmColor)
121-
122-
// 2) Render the color bitmap (HbmColor) to RGBA pixels and save
123-
return saveHBitmapAsPNG(w32.HBITMAP(ii.HbmColor), destPath)
124-
}
140+
defer deleteObject.Call(uintptr(iconInfo.HbmMask))
141+
defer deleteObject.Call(uintptr(iconInfo.HbmColor))
125142

126-
func saveHBitmapAsPNG(hBmp w32.HBITMAP, destPath string) error {
127-
// 1) Fetch the BITMAP header
143+
// Get bitmap info
128144
var bmp BITMAP
129-
if ret, _, err := procGetObject.Call(
130-
uintptr(hBmp),
145+
ret, _, err = getObject.Call(
146+
uintptr(iconInfo.HbmColor),
131147
unsafe.Sizeof(bmp),
132148
uintptr(unsafe.Pointer(&bmp)),
133-
); ret == 0 {
149+
)
150+
if ret == 0 {
134151
return err
135152
}
136-
w, h := int(bmp.Width), int(bmp.Height)
137-
138-
// 2) Prepare BITMAPINFO for 32-bit, top-down DIB
139-
var bmi BITMAPINFO
140-
bmi.Header = BITMAPINFOHEADER{
141-
Size: uint32(unsafe.Sizeof(BITMAPINFOHEADER{})),
142-
Width: int32(w),
143-
Height: -int32(h), // negative = top-down
144-
Planes: 1,
145-
BitCount: 32,
146-
Compression: BI_RGB,
147-
}
148153

149-
// 3) Allocate a buffer and pull the bits
150-
buf := make([]byte, w*h*4)
151-
if ret, _, err := procGetDIBits.Call(
152-
0,
153-
uintptr(hBmp),
154+
// Create DC
155+
hdc, _, _ := createCompatibleDC.Call(0)
156+
if hdc == 0 {
157+
return syscall.EINVAL
158+
}
159+
defer deleteDC.Call(hdc)
160+
161+
// Select bitmap into DC
162+
oldBitmap, _, _ := selectObject.Call(hdc, uintptr(iconInfo.HbmColor))
163+
defer selectObject.Call(hdc, oldBitmap)
164+
165+
// Prepare bitmap info header
166+
var bi BITMAPINFO
167+
bi.BmiHeader.BiSize = uint32(unsafe.Sizeof(bi.BmiHeader))
168+
bi.BmiHeader.BiWidth = bmp.BmWidth
169+
bi.BmiHeader.BiHeight = bmp.BmHeight
170+
bi.BmiHeader.BiPlanes = 1
171+
bi.BmiHeader.BiBitCount = 32
172+
bi.BmiHeader.BiCompression = BI_RGB
173+
174+
// Allocate memory for bitmap bits
175+
width, height := int(bmp.BmWidth), int(bmp.BmHeight)
176+
bufferSize := width * height * 4
177+
bits := make([]byte, bufferSize)
178+
179+
// Get bitmap bits
180+
ret, _, err = getDIBits.Call(
181+
hdc,
182+
uintptr(iconInfo.HbmColor),
154183
0,
155-
uintptr(h),
156-
uintptr(unsafe.Pointer(&buf[0])),
157-
uintptr(unsafe.Pointer(&bmi)),
184+
uintptr(bmp.BmHeight),
185+
uintptr(unsafe.Pointer(&bits[0])),
186+
uintptr(unsafe.Pointer(&bi)),
158187
DIB_RGB_COLORS,
159-
); ret == 0 {
188+
)
189+
if ret == 0 {
160190
return err
161191
}
162192

163-
// 4) Copy into an image.RGBA and write PNG
164-
img := image.NewRGBA(image.Rect(0, 0, w, h))
165-
copy(img.Pix, buf)
193+
// Create Go image
194+
img := image.NewRGBA(image.Rect(0, 0, width, height))
195+
196+
// Convert DIB to RGBA
197+
for y := 0; y < height; y++ {
198+
for x := 0; x < width; x++ {
199+
// DIB is bottom-up, so we need to invert Y
200+
dibIndex := ((height-1-y)*width + x) * 4
166201

167-
f, err := os.Create(destPath)
202+
// BGRA to RGBA
203+
b := bits[dibIndex]
204+
g := bits[dibIndex+1]
205+
r := bits[dibIndex+2]
206+
a := bits[dibIndex+3]
207+
208+
// Set pixel in the image
209+
img.Set(x, y, color.RGBA{R: r, G: g, B: b, A: a})
210+
}
211+
}
212+
213+
// Create output file
214+
outFile, err := os.Create(filePath)
168215
if err != nil {
169216
return err
170217
}
171-
defer f.Close()
172-
return png.Encode(f, img)
218+
defer outFile.Close()
219+
220+
// Encode and save the image
221+
return png.Encode(outFile, img)
173222
}
174223

175224
func (ic *Icon) Destroy() bool {

0 commit comments

Comments
 (0)