Files
Artemis-Plugins/plugins/cua-driver/windows.go
T
chenmingxuan 889a951c9b fix: bind Cua pixel actions to current screenshot geometry
Correct native scroll scaling, reject stale or out-of-bounds pixel targets, and scope DPI awareness to geometry reads. Package adapter 0.36.0+artemis.3 and document the high-effort cross-device verification.

Validation: Go tests and vet, packaged MCP schema and handshake, Python checks, and user-run high-effort task completed with a passed verdict and explicit coverage limits.
2026-10-17 19:43:22 +08:00

81 lines
2.7 KiB
Go

package main
import (
"fmt"
"os/exec"
"runtime"
"syscall"
"unsafe"
)
var user32 = syscall.NewLazyDLL("user32.dll")
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
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)
}
}