//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 }