internal/nativewin/icon_windows.go
Rama main · réplica de lectura del repositorio original.
//go:build windows
package nativewin
import "syscall"
// Icon plumbing.
//
// The .exe carries its icon as a Win32 resource (see versioninfo.json, compiled
// into resource_windows_amd64.syso by `make versioninfo-setup`). Explorer picks
// that up on its own; the WINDOW does not. A window's icon comes from its class
// or from WM_SETICON, and go-webview2 registers its class with IDI_APPLICATION,
// so without this the taskbar button and Alt-Tab entry show the generic Windows
// icon while the file on disk shows ours.
const (
imageIcon = 1
lrShared = 0x8000
wmSetIcon = 0x0080
iconSmall = 0
iconBig = 1
gclpHIcon = -14
gclpHIconSm = -34
smCXIcon = 11
smCYIcon = 12
smCXSmIcon = 49
smCYSmIcon = 50
// The id goversioninfo gives the RT_GROUP_ICON it writes from -icon.
// RT_ICON (the individual frames) is a different resource TYPE with its own
// id space, so however many sizes the .ico carries, the group stays 1.
// Verified against the generated resource_windows_amd64.syso.
iconResourceID = 1
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
getModuleHandleW = kernel32.NewProc("GetModuleHandleW")
loadImageW = user32.NewProc("LoadImageW")
setClassLongPtrW = user32.NewProc("SetClassLongPtrW")
getSystemMetrics = user32.NewProc("GetSystemMetrics")
)
// applyWindowIcon points the window at the icon compiled into this binary.
//
// Both sizes are set explicitly rather than letting Windows scale one: the big
// icon is the Alt-Tab and taskbar-preview one, the small icon is the taskbar
// button, and a downscaled 256px icon in a 16px slot is visibly mushy next to
// the properly authored 16px frame that is already in the .ico.
//
// Silently doing nothing is the correct failure mode. A dev build has no .syso,
// and a window with the default icon is a cosmetic problem, not a reason to
// refuse to open.
func ApplyIcon(hwnd uintptr) {
if hwnd == 0 {
return
}
hinst, _, _ := getModuleHandleW.Call(0)
big := loadIconResource(hinst, hwnd, smCXIcon, smCYIcon)
small := loadIconResource(hinst, hwnd, smCXSmIcon, smCYSmIcon)
if big == 0 && small == 0 {
return
}
if big == 0 {
big = small
}
if small == 0 {
small = big
}
// WM_SETICON covers this window; the class icon covers any window the class
// creates later and is what some shells read instead.
_, _, _ = sendMessageW.Call(hwnd, wmSetIcon, iconBig, big)
_, _, _ = sendMessageW.Call(hwnd, wmSetIcon, iconSmall, small)
_, _, _ = setClassLongPtrW.Call(hwnd, winIndex(gclpHIcon), big)
_, _, _ = setClassLongPtrW.Call(hwnd, winIndex(gclpHIconSm), small)
}
// loadIconResource loads the binary's icon at the size Windows wants for a
// given slot, picking the closest frame in the .ico rather than rescaling one.
//
// The sizes are read for the window's own display, not the primary one: a
// per-monitor-aware process gets 32px from GetSystemMetrics whatever the scale,
// and a 32px frame blown up to the 48px slot a 150% display actually draws is
// the mushy taskbar icon this function exists to avoid.
//
// Returns 0 in a dev build, which has no .syso linked in at all.
func loadIconResource(hinst, hwnd uintptr, cxMetric, cyMetric int) uintptr {
cx := uintptr(systemMetric(hwnd, cxMetric))
cy := uintptr(systemMetric(hwnd, cyMetric))
// LR_SHARED: the handle is owned by the module and must not be destroyed,
// which suits an icon that lives as long as the process.
h, _, _ := loadImageW.Call(hinst, iconResourceID, imageIcon, cx, cy, lrShared)
return h
}