package main import ( "fmt" "os/exec" "runtime" "syscall" "unsafe" ) var user32 = syscall.NewLazyDLL("user32.dll") var kernel32 = syscall.NewLazyDLL("kernel32.dll") func foregroundWindow() int64 { hwnd, _, _ := user32.NewProc("GetForegroundWindow").Call() return int64(hwnd) } func configureProcess(c *exec.Cmd) { c.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} } func windowOwned(pid, hwnd int64) bool { var actual uint32 r, _, _ := user32.NewProc("IsWindow").Call(uintptr(hwnd)) if r == 0 { return false } user32.NewProc("GetWindowThreadProcessId").Call(uintptr(hwnd), uintptr(unsafe.Pointer(&actual))) return int64(actual) == pid } func verifyWindow(pid, hwnd int64, b obj) (obj, bool) { // DPI context belongs to an OS thread, not a Go goroutine. Apply it around // the actual geometry reads; setting it once while starting a child leaks // context and later GetSystemMetrics can return virtualized dimensions. runtime.LockOSThread() defer runtime.UnlockOSThread() setDPI := user32.NewProc("SetThreadDpiAwarenessContext") previous, _, _ := setDPI.Call(^uintptr(3)) if previous == 0 { return nil, false } defer setDPI.Call(previous) if !windowOwned(pid, hwnd) { return nil, false } var r struct{ L, T, R, B int32 } code, _, _ := syscall.NewLazyDLL("dwmapi.dll").NewProc("DwmGetWindowAttribute").Call(uintptr(hwnd), 9, uintptr(unsafe.Pointer(&r)), unsafe.Sizeof(r)) if code != 0 { return nil, false } // Cua uses DWM physical bounds; refuse evidence after a window move/resize. if int64(r.L) != num(b["left"]) || int64(r.T) != num(b["top"]) || int64(r.R-r.L) != num(b["width"]) || int64(r.B-r.T) != num(b["height"]) { return nil, false } metric := func(n uintptr) int64 { x, _, _ := user32.NewProc("GetSystemMetrics").Call(n); return int64(int32(x)) } return obj{"left": metric(76), "top": metric(77), "width": metric(78), "height": metric(79)}, true } func ownProcess(pid int) (uintptr, error) { job, _, e := kernel32.NewProc("CreateJobObjectW").Call(0, 0) if job == 0 { return 0, e } // JOBOBJECT_EXTENDED_LIMIT_INFORMATION, Windows x64. Kill the entire owned // child tree when the adapter dies, including Cua's UIA helper. data := make([]byte, 144) *(*uint32)(unsafe.Pointer(&data[16])) = 0x2000 ok, _, _ := kernel32.NewProc("SetInformationJobObject").Call(job, 9, uintptr(unsafe.Pointer(&data[0])), uintptr(len(data))) h, _, _ := kernel32.NewProc("OpenProcess").Call(0x0101, 0, uintptr(pid)) if h == 0 || ok == 0 { closeJob(job) return 0, fmt.Errorf("cannot own driver process") } defer kernel32.NewProc("CloseHandle").Call(h) ok, _, e = kernel32.NewProc("AssignProcessToJobObject").Call(job, h) if ok == 0 { closeJob(job) return 0, e } return job, nil } func closeJob(h uintptr) { if h != 0 { kernel32.NewProc("CloseHandle").Call(h) } }