Files
OpsLog/windowpos_windows.go
T
rouggy 8bc4ed68d4 fix(window): place the window in absolute desktop coordinates
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.
2026-08-28 12:48:03 +02:00

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
}