Wails saves and restores in two different coordinate systems. WindowGetPosition
returns GetWindowRect — absolute. WindowSetPosition is winc's ControlBase.SetPos,
which adds the CURRENT monitor's work-area origin:
w32.SetWindowPos(hwnd, HWND_TOP, int(info.RcWork.Left)+x, ...)
On the primary monitor that origin is 0 and nothing shows. With a monitor to the
LEFT it compounds: a station with its left screen at x=-3840 saved -3844,
reopened there, had -3840 added, and stored -7684 — then -11524, one screen
further out at every launch, until the window was off the desktop entirely.
So the placement is done here, with SetWindowPos and no offset, falling back to
the toolkit when the handle cannot be found. Compact mode had the same pairing
and is fixed with it.
94 lines
3.2 KiB
Go
94 lines
3.2 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"hamlog/internal/applog"
|
|
)
|
|
|
|
// Moving the window to an ABSOLUTE virtual-desktop position.
|
|
//
|
|
// Wails' own WindowSetPosition cannot do it. Its Windows implementation reads:
|
|
//
|
|
// func (cba *ControlBase) SetPos(x, y int) {
|
|
// info := getMonitorInfo(cba.hwnd)
|
|
// w32.SetWindowPos(cba.hwnd, HWND_TOP, int(info.RcWork.Left)+x, int(info.RcWork.Top)+y, ...)
|
|
// }
|
|
//
|
|
// — the coordinates are relative to the CURRENT monitor's work area, while
|
|
// WindowGetPosition returns GetWindowRect, which is absolute. Saving one and
|
|
// restoring the other is only harmless on the primary monitor, where the work
|
|
// area starts at 0.
|
|
//
|
|
// On a second monitor to the LEFT it compounds at every launch. Reported from a
|
|
// two-screen station with the left monitor at x = -3840: OpsLog saved -3844,
|
|
// reopened on that monitor, added the monitor's own origin, and stored -7684 —
|
|
// then -11524, each launch one screen further into nowhere.
|
|
//
|
|
// So we place the window ourselves. Same call the toolkit makes, without the
|
|
// offset.
|
|
const (
|
|
swpNoSize = 0x0001
|
|
swpNoZOrder = 0x0004
|
|
swpNoActivate = 0x0010
|
|
)
|
|
|
|
var (
|
|
procSetWindowPos = user32Dll.NewProc("SetWindowPos")
|
|
procEnumWindows = user32Dll.NewProc("EnumWindows")
|
|
procGetWindowThreadProcessID = user32Dll.NewProc("GetWindowThreadProcessId")
|
|
procGetWindowTextLengthW = user32Dll.NewProc("GetWindowTextLengthW")
|
|
procGetWindow = user32Dll.NewProc("GetWindow")
|
|
kernel32Dll = syscall.NewLazyDLL("kernel32.dll")
|
|
procGetCurrentProcessIDWinPos = kernel32Dll.NewProc("GetCurrentProcessId")
|
|
)
|
|
|
|
// mainWindowHandle finds this process's own top-level window.
|
|
//
|
|
// Wails does not expose the handle, so it is looked up: the first top-level
|
|
// window belonging to this process id that has no owner and a title. The window
|
|
// is created hidden (StartHidden), and EnumWindows lists hidden windows too,
|
|
// which is what makes this usable before the window is shown.
|
|
func mainWindowHandle() uintptr {
|
|
self, _, _ := procGetCurrentProcessIDWinPos.Call()
|
|
var found uintptr
|
|
cb := syscall.NewCallback(func(hwnd uintptr, _ uintptr) uintptr {
|
|
var pid uint32
|
|
procGetWindowThreadProcessID.Call(hwnd, uintptr(unsafe.Pointer(&pid)))
|
|
if uintptr(pid) != self {
|
|
return 1 // keep going
|
|
}
|
|
// GW_OWNER = 4: skip tool windows and dialogs owned by the main one.
|
|
if owner, _, _ := procGetWindow.Call(hwnd, 4); owner != 0 {
|
|
return 1
|
|
}
|
|
if n, _, _ := procGetWindowTextLengthW.Call(hwnd); n == 0 {
|
|
return 1
|
|
}
|
|
found = hwnd
|
|
return 0 // stop
|
|
})
|
|
procEnumWindows.Call(cb, 0)
|
|
return found
|
|
}
|
|
|
|
// setWindowPosAbsolute moves the window to a virtual-desktop coordinate.
|
|
// Reports whether it could; the caller falls back to the toolkit's own call.
|
|
func setWindowPosAbsolute(x, y int) bool {
|
|
hwnd := mainWindowHandle()
|
|
if hwnd == 0 {
|
|
applog.Printf("window: could not find our own window handle — falling back to the toolkit's placement")
|
|
return false
|
|
}
|
|
r, _, err := procSetWindowPos.Call(hwnd, 0, uintptr(int32(x)), uintptr(int32(y)), 0, 0,
|
|
swpNoSize|swpNoZOrder|swpNoActivate)
|
|
if r == 0 {
|
|
applog.Printf("window: SetWindowPos(%d,%d) failed: %v", x, y, err)
|
|
return false
|
|
}
|
|
return true
|
|
}
|