215 lines
5.1 KiB
Go
215 lines
5.1 KiB
Go
//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)
|
|
} |