//go:build windows package main import ( "unsafe" "github.com/jchv/go-webview2" "golang.org/x/sys/windows" ) // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = (DPI_AWARENESS_CONTEXT)-4 const dpiAwarenessContextPerMonitorAwareV2 = ^uintptr(3) const ( spiGetWorkArea = 0x0030 monitorDefaultToNearest = 2 ) type monitorInfo struct { CbSize uint32 RcMonitor windows.Rect RcWork windows.Rect DwFlags uint32 } // enableProcessDPIAware opts into per-monitor DPI before any HWND is created. // The embedded app.manifest already declares PerMonitorV2; this is a belt-and- // suspenders call for builds that lose the manifest resource. func enableProcessDPIAware() { user32 := windows.NewLazySystemDLL("user32.dll") if p := user32.NewProc("SetProcessDpiAwarenessContext"); p.Find() == nil { _, _, _ = p.Call(dpiAwarenessContextPerMonitorAwareV2) return } if p := user32.NewProc("SetProcessDPIAware"); p.Find() == nil { _, _, _ = p.Call() } } func systemDPI() int { user32 := windows.NewLazySystemDLL("user32.dll") if p := user32.NewProc("GetDpiForSystem"); p.Find() == nil { if r, _, _ := p.Call(); r >= 72 && r <= 480 { return int(r) } } return 96 } func windowDPI(hwnd uintptr) int { if hwnd != 0 { user32 := windows.NewLazySystemDLL("user32.dll") if p := user32.NewProc("GetDpiForWindow"); p.Find() == nil { if r, _, _ := p.Call(hwnd); r >= 72 && r <= 480 { return int(r) } } } return systemDPI() } func dipsToPhysical(dips, dpi int) int { if dpi <= 0 { dpi = 96 } return (dips*dpi + 48) / 96 } func physicalToDIPs(px, dpi int) int { if dpi <= 0 { dpi = 96 } return (px*96 + dpi/2) / dpi } func primaryWorkAreaPhysical() (w, h int, ok bool) { var r windows.Rect user32 := windows.NewLazySystemDLL("user32.dll") p := user32.NewProc("SystemParametersInfoW") ret, _, _ := p.Call(spiGetWorkArea, 0, uintptr(unsafe.Pointer(&r)), 0) if ret == 0 { return 0, 0, false } return int(r.Right - r.Left), int(r.Bottom - r.Top), true } func monitorWorkAreaPhysical(hwnd uintptr) (w, h int, ok bool) { if hwnd == 0 { return primaryWorkAreaPhysical() } user32 := windows.NewLazySystemDLL("user32.dll") fromWin := user32.NewProc("MonitorFromWindow") getInfo := user32.NewProc("GetMonitorInfoW") if fromWin.Find() != nil || getInfo.Find() != nil { return primaryWorkAreaPhysical() } hmon, _, _ := fromWin.Call(hwnd, monitorDefaultToNearest) if hmon == 0 { return primaryWorkAreaPhysical() } var mi monitorInfo mi.CbSize = uint32(unsafe.Sizeof(mi)) ret, _, _ := getInfo.Call(hmon, uintptr(unsafe.Pointer(&mi))) if ret == 0 { return primaryWorkAreaPhysical() } return int(mi.RcWork.Right - mi.RcWork.Left), int(mi.RcWork.Bottom - mi.RcWork.Top), true } // computeClientSizeDIPs picks a comfortable client size in DIPs from the // monitor work area (also expressed as physical px + dpi). // Target: ~min(1100, workW*0.72) x min(780, workH*0.78), floored near // 960x680 when the screen allows, always capped with margins. func computeClientSizeDIPs(workWPhys, workHPhys, dpi int) (w, h int) { if dpi <= 0 { dpi = 96 } workW := physicalToDIPs(workWPhys, dpi) workH := physicalToDIPs(workHPhys, dpi) if workW < 320 { workW = 320 } if workH < 240 { workH = 240 } const marginX, marginY = 64, 64 maxW := workW - marginX maxH := workH - marginY if maxW < 480 { maxW = workW } if maxH < 360 { maxH = workH } if maxW < 480 { maxW = 480 } if maxH < 360 { maxH = 360 } prefW := int(float64(workW) * 0.72) if prefW > 1100 { prefW = 1100 } prefH := int(float64(workH) * 0.78) if prefH > 780 { prefH = 780 } if prefW < 960 && maxW >= 960 { prefW = 960 } if prefH < 680 && maxH >= 680 { prefH = 680 } if prefW > maxW { prefW = maxW } if prefH > maxH { prefH = maxH } if prefW < 640 && maxW >= 640 { prefW = 640 } if prefH < 480 && maxH >= 480 { prefH = 480 } return prefW, prefH } // initialWindowPhysicalSize estimates CreateWindow size before an HWND exists // (primary work area + system DPI). go-webview2 treats Width/Height as physical // pixels when the process is DPI-aware. func initialWindowPhysicalSize() (w, h int) { dpi := systemDPI() ww, wh, ok := primaryWorkAreaPhysical() if !ok || ww <= 0 || wh <= 0 { return dipsToPhysical(960, dpi), dipsToPhysical(680, dpi) } dipW, dipH := computeClientSizeDIPs(ww, wh, dpi) return dipsToPhysical(dipW, dpi), dipsToPhysical(dipH, dpi) } // applyAdaptiveWindowSize sets HintMin (~720x520 DIPs) then the preferred // client size from the window's monitor work area, both in physical pixels. func applyAdaptiveWindowSize(w webview2.WebView) { if w == nil { return } hwnd := uintptr(w.Window()) dpi := windowDPI(hwnd) ww, wh, ok := monitorWorkAreaPhysical(hwnd) if !ok || ww <= 0 || wh <= 0 { ww, wh, _ = primaryWorkAreaPhysical() } dipW, dipH := computeClientSizeDIPs(ww, wh, dpi) physW := dipsToPhysical(dipW, dpi) physH := dipsToPhysical(dipH, dpi) minW := dipsToPhysical(720, dpi) minH := dipsToPhysical(520, dpi) if minW > physW { minW = physW } if minH > physH { minH = physH } w.SetSize(minW, minH, webview2.HintMin) w.SetSize(physW, physH, webview2.HintNone) }