Ctrl K
ComunidadGuías…

internal/nativewin/placement_windows.go

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

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