diff --git a/go.mod b/go.mod index 171f9d1..57984ef 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/google/uuid v1.6.0 github.com/jezek/xgb v1.3.1 github.com/jezek/xgbutil v0.0.0-20260124183602-9fd151d6a51a + golang.org/x/sys v0.41.0 ) require ( @@ -36,7 +37,6 @@ require ( golang.org/x/arch v0.22.0 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/net v0.51.0 // indirect - golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect google.golang.org/protobuf v1.36.10 // indirect ) diff --git a/internal/winapi/winapi.go b/internal/winapi/winapi.go new file mode 100644 index 0000000..ffea79c --- /dev/null +++ b/internal/winapi/winapi.go @@ -0,0 +1,70 @@ +//go:build windows + +package winapi + +import ( + "unsafe" + + "golang.org/x/sys/windows" +) + +var ( + user32 = windows.NewLazySystemDLL("user32.dll") + procGetWindowTextW = user32.NewProc("GetWindowTextW") + procShowWindow = user32.NewProc("ShowWindow") +) + +// ForegroundWindow returns the current foreground window's handle (as a uintptr +// so platform-neutral callers need not import windows), its title, and its +// on-task class (process exe base name; see ClassFromImagePath). ok is false +// when there is no foreground window (e.g. secure desktop / lock screen). +func ForegroundWindow() (hwnd uintptr, title, class string, ok bool) { + h := windows.GetForegroundWindow() + if h == 0 { + return 0, "", "", false + } + return uintptr(h), windowTitle(h), windowClass(h), true +} + +func windowTitle(h windows.HWND) string { + const max = 512 + buf := make([]uint16, max) + n, _, _ := procGetWindowTextW.Call( + uintptr(h), + uintptr(unsafe.Pointer(&buf[0])), + uintptr(max), + ) + return windows.UTF16ToString(buf[:n]) +} + +func windowClass(h windows.HWND) string { + var pid uint32 + if _, err := windows.GetWindowThreadProcessId(h, &pid); err != nil || pid == 0 { + return "" + } + proc, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid) + if err != nil { + return "" + } + defer windows.CloseHandle(proc) + + buf := make([]uint16, windows.MAX_PATH) + size := uint32(len(buf)) + if err := windows.QueryFullProcessImageName(proc, 0, &buf[0], &size); err != nil { + return "" + } + return ClassFromImagePath(windows.UTF16ToString(buf[:size])) +} + +// MinimizeForeground minimizes the current foreground window. It returns nil +// when nothing is focused. ShowWindow's BOOL return reports prior visibility, +// not success, so there is nothing to error-check; minimize is best-effort by +// the Guard contract. +func MinimizeForeground() error { + h := windows.GetForegroundWindow() + if h == 0 { + return nil + } + procShowWindow.Call(uintptr(h), uintptr(windows.SW_MINIMIZE)) + return nil +}