Ctrl K
ComunidadGuías…

internal/nativewin/icon_windows.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
//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
}