internal/nativewin/placement_windows.go
Rama main · réplica de lectura del repositorio original.
//go:build windows
package nativewin
import "unsafe"
const (
swShowMaximized = 3
// MonitorFromRect with DEFAULTTONULL returns 0 when a rect touches no
// display at all, which is the whole point: it is how a saved position on a
// monitor that has since been unplugged is detected.
monitorDefaultToNull = 0
// A window may not be restored smaller than this. A saved rect can be
// nonsense -- a 0x0 from a race during shutdown, or a size from a display
// scaling the user has since changed -- and a window too small to show its
// own title bar is one the user cannot resize back.
minRestoreW = 640
minRestoreH = 480
)
var (
getWindowPlacement = user32.NewProc("GetWindowPlacement")
monitorFromRect = user32.NewProc("MonitorFromRect")
)
// windowPlacement is WINDOWPLACEMENT. length must be set before the call or the
// API rejects it -- that field is how Windows versions the struct.
type windowPlacement struct {
length uint32
flags uint32
showCmd uint32
ptMinPosition struct{ X, Y int32 }
ptMaxPosition struct{ X, Y int32 }
rcNormalPosition winRect
}
// Placement is where a window was last time, in a form worth writing to disk.
//
// DPI records the scale the rect was measured at. The rect is in PHYSICAL
// pixels, so the same window is 940 wide at 100% and 1410 at 150%: a caller
// reading a file back has no way to tell a genuinely small window from one
// saved on a different display, or by a build that was not DPI-aware, unless
// the number travels with it.
type Placement struct {
X, Y, W, H int32
Maximized bool
DPI int32 `json:"dpi,omitempty"`
}
// GetPlacement reads the window's position for saving.
//
// GetWindowPlacement rather than GetWindowRect, because the two disagree in
// exactly the case that matters: for a maximised window GetWindowRect returns
// the maximised rect, so saving it would lose the size to restore DOWN to, and
// un-maximising after a restart would snap the window to the whole screen.
// rcNormalPosition is always the restored rect regardless of current state.
//
// The rect is in workspace coordinates (taskbar excluded), which is also what
// ApplyPlacement feeds back to SetWindowPos, so the round trip is exact.
func GetPlacement(hwnd uintptr) (Placement, bool) {
if hwnd == 0 {
return Placement{}, false
}
var wp windowPlacement
wp.length = uint32(unsafe.Sizeof(wp))
ok, _, _ := getWindowPlacement.Call(hwnd, uintptr(unsafe.Pointer(&wp)))
if ok == 0 {
return Placement{}, false
}
// showCmd alone is not enough: hide-to-tray leaves it reading SW_HIDE, and
// the placement is read after the window has been hidden, so a maximised
// window would come back restored. IsZoomed reads the WS_MAXIMIZE style bit,
// which ShowWindow(SW_HIDE) does not clear.
zoomed, _, _ := isZoomed.Call(hwnd)
r := wp.rcNormalPosition
return Placement{
DPI: int32(windowDPI(hwnd)),
X: r.Left,
Y: r.Top,
W: r.Right - r.Left,
H: r.Bottom - r.Top,
Maximized: wp.showCmd == swShowMaximized || zoomed != 0,
}, true
}
// ApplyPlacement puts a window back where it was, refusing anything that would
// strand it.
//
// Two ways a saved placement goes bad between runs, both silent and both
// leaving the user with a window they cannot reach: the monitor it was on is
// gone (a laptop undocked), or the size is degenerate. Position and size are
// therefore validated SEPARATELY -- a window whose old monitor vanished keeps
// its remembered size and simply opens where the library centred it, which is
// a better answer than either ignoring the whole record or moving it to a
// corner of a display it never lived on.
//
// Minimised is deliberately never restored. A process that starts up already
// minimised looks like one that failed to start.
func ApplyPlacement(hwnd uintptr, p Placement) {
if hwnd == 0 {
return
}
sizeOK := p.W >= minRestoreW && p.H >= minRestoreH
if sizeOK {
r := winRect{Left: p.X, Top: p.Y, Right: p.X + p.W, Bottom: p.Y + p.H}
if onScreen(r) {
_, _, _ = setWindowPos.Call(hwnd, 0,
uintptr(p.X), uintptr(p.Y), uintptr(p.W), uintptr(p.H),
swpNoZOrder|swpNoActivate)
} else {
// Size only; SWP_NOMOVE leaves the centred position alone.
_, _, _ = setWindowPos.Call(hwnd, 0, 0, 0, uintptr(p.W), uintptr(p.H),
swpNoMove|swpNoZOrder|swpNoActivate)
}
}
if p.Maximized {
showWindow(hwnd, swMaximize)
}
}
// onScreen reports whether a rect overlaps any attached display.
func onScreen(r winRect) bool {
h, _, _ := monitorFromRect.Call(uintptr(unsafe.Pointer(&r)), monitorDefaultToNull)
return h != 0
}