Files
overlord/Overlord-Client/cmd/agent/capture/cursor_windows.go
T

246 lines
6.9 KiB
Go
Raw Normal View History

2026-03-05 00:04:26 -06:00
//go:build windows
package capture
import (
"image"
"image/color"
"sync/atomic"
"unsafe"
)
var (
cursorCaptureEnabled atomic.Bool
)
func SetCursorCapture(enabled bool) {
cursorCaptureEnabled.Store(enabled)
}
var (
procGetCursorPos = user32.NewProc("GetCursorPos")
procGetCursorInfo = user32.NewProc("GetCursorInfo")
procGetIconInfo = user32.NewProc("GetIconInfo")
procDrawIconEx = user32.NewProc("DrawIconEx")
)
const (
CURSOR_SHOWING = 0x00000001
DI_NORMAL = 0x0003
DI_DEFAULTSIZE = 0x0008
)
type point struct {
x int32
y int32
}
type cursorInfo struct {
cbSize uint32
flags uint32
hCursor uintptr
ptScreenPos point
}
type iconInfo struct {
fIcon uint32
xHotspot uint32
yHotspot uint32
hbmMask uintptr
hbmColor uintptr
}
func getCursorPosition() (x, y int32, visible bool) {
var ci cursorInfo
ci.cbSize = uint32(unsafe.Sizeof(ci))
ret, _, _ := procGetCursorInfo.Call(uintptr(unsafe.Pointer(&ci)))
if ret == 0 {
return 0, 0, false
}
visible = (ci.flags & CURSOR_SHOWING) != 0
return ci.ptScreenPos.x, ci.ptScreenPos.y, visible
}
func drawCursor(img *image.RGBA, cursorX, cursorY int32, bounds image.Rectangle) {
imgX := int(cursorX) - bounds.Min.X
imgY := int(cursorY) - bounds.Min.Y
if imgX < 0 || imgY < 0 || imgX >= img.Bounds().Dx() || imgY >= img.Bounds().Dy() {
return
}
cursorPattern := []struct {
x, y int
c color.RGBA
}{
{0, 0, color.RGBA{0, 0, 0, 255}},
{0, 1, color.RGBA{0, 0, 0, 255}},
{0, 2, color.RGBA{0, 0, 0, 255}},
{0, 3, color.RGBA{0, 0, 0, 255}},
{0, 4, color.RGBA{0, 0, 0, 255}},
{0, 5, color.RGBA{0, 0, 0, 255}},
{0, 6, color.RGBA{0, 0, 0, 255}},
{0, 7, color.RGBA{0, 0, 0, 255}},
{0, 8, color.RGBA{0, 0, 0, 255}},
{0, 9, color.RGBA{0, 0, 0, 255}},
{0, 10, color.RGBA{0, 0, 0, 255}},
{0, 11, color.RGBA{0, 0, 0, 255}},
{1, 0, color.RGBA{0, 0, 0, 255}},
{1, 10, color.RGBA{0, 0, 0, 255}},
{2, 0, color.RGBA{0, 0, 0, 255}},
{2, 9, color.RGBA{0, 0, 0, 255}},
{3, 0, color.RGBA{0, 0, 0, 255}},
{3, 8, color.RGBA{0, 0, 0, 255}},
{3, 9, color.RGBA{0, 0, 0, 255}},
{4, 0, color.RGBA{0, 0, 0, 255}},
{4, 7, color.RGBA{0, 0, 0, 255}},
{4, 8, color.RGBA{0, 0, 0, 255}},
{5, 0, color.RGBA{0, 0, 0, 255}},
{5, 6, color.RGBA{0, 0, 0, 255}},
{5, 7, color.RGBA{0, 0, 0, 255}},
{5, 13, color.RGBA{0, 0, 0, 255}},
{6, 0, color.RGBA{0, 0, 0, 255}},
{6, 5, color.RGBA{0, 0, 0, 255}},
{6, 6, color.RGBA{0, 0, 0, 255}},
{6, 12, color.RGBA{0, 0, 0, 255}},
{6, 13, color.RGBA{0, 0, 0, 255}},
{7, 0, color.RGBA{0, 0, 0, 255}},
{7, 4, color.RGBA{0, 0, 0, 255}},
{7, 5, color.RGBA{0, 0, 0, 255}},
{7, 11, color.RGBA{0, 0, 0, 255}},
{7, 12, color.RGBA{0, 0, 0, 255}},
{8, 4, color.RGBA{0, 0, 0, 255}},
{8, 10, color.RGBA{0, 0, 0, 255}},
{8, 11, color.RGBA{0, 0, 0, 255}},
{9, 4, color.RGBA{0, 0, 0, 255}},
{9, 9, color.RGBA{0, 0, 0, 255}},
{9, 10, color.RGBA{0, 0, 0, 255}},
{10, 8, color.RGBA{0, 0, 0, 255}},
{10, 9, color.RGBA{0, 0, 0, 255}},
{11, 7, color.RGBA{0, 0, 0, 255}},
{11, 8, color.RGBA{0, 0, 0, 255}},
{12, 6, color.RGBA{0, 0, 0, 255}},
{12, 7, color.RGBA{0, 0, 0, 255}},
{1, 1, color.RGBA{255, 255, 255, 255}},
{1, 2, color.RGBA{255, 255, 255, 255}},
{1, 3, color.RGBA{255, 255, 255, 255}},
{1, 4, color.RGBA{255, 255, 255, 255}},
{1, 5, color.RGBA{255, 255, 255, 255}},
{1, 6, color.RGBA{255, 255, 255, 255}},
{1, 7, color.RGBA{255, 255, 255, 255}},
{1, 8, color.RGBA{255, 255, 255, 255}},
{1, 9, color.RGBA{255, 255, 255, 255}},
{2, 1, color.RGBA{255, 255, 255, 255}},
{2, 2, color.RGBA{255, 255, 255, 255}},
{2, 3, color.RGBA{255, 255, 255, 255}},
{2, 4, color.RGBA{255, 255, 255, 255}},
{2, 5, color.RGBA{255, 255, 255, 255}},
{2, 6, color.RGBA{255, 255, 255, 255}},
{2, 7, color.RGBA{255, 255, 255, 255}},
{2, 8, color.RGBA{255, 255, 255, 255}},
{3, 1, color.RGBA{255, 255, 255, 255}},
{3, 2, color.RGBA{255, 255, 255, 255}},
{3, 3, color.RGBA{255, 255, 255, 255}},
{3, 4, color.RGBA{255, 255, 255, 255}},
{3, 5, color.RGBA{255, 255, 255, 255}},
{3, 6, color.RGBA{255, 255, 255, 255}},
{3, 7, color.RGBA{255, 255, 255, 255}},
{4, 1, color.RGBA{255, 255, 255, 255}},
{4, 2, color.RGBA{255, 255, 255, 255}},
{4, 3, color.RGBA{255, 255, 255, 255}},
{4, 4, color.RGBA{255, 255, 255, 255}},
{4, 5, color.RGBA{255, 255, 255, 255}},
{4, 6, color.RGBA{255, 255, 255, 255}},
{5, 1, color.RGBA{255, 255, 255, 255}},
{5, 2, color.RGBA{255, 255, 255, 255}},
{5, 3, color.RGBA{255, 255, 255, 255}},
{5, 4, color.RGBA{255, 255, 255, 255}},
{5, 5, color.RGBA{255, 255, 255, 255}},
{5, 11, color.RGBA{255, 255, 255, 255}},
{5, 12, color.RGBA{255, 255, 255, 255}},
{6, 1, color.RGBA{255, 255, 255, 255}},
{6, 2, color.RGBA{255, 255, 255, 255}},
{6, 3, color.RGBA{255, 255, 255, 255}},
{6, 4, color.RGBA{255, 255, 255, 255}},
{6, 10, color.RGBA{255, 255, 255, 255}},
{6, 11, color.RGBA{255, 255, 255, 255}},
{7, 1, color.RGBA{255, 255, 255, 255}},
{7, 2, color.RGBA{255, 255, 255, 255}},
{7, 3, color.RGBA{255, 255, 255, 255}},
{7, 9, color.RGBA{255, 255, 255, 255}},
{7, 10, color.RGBA{255, 255, 255, 255}},
{8, 5, color.RGBA{255, 255, 255, 255}},
{8, 6, color.RGBA{255, 255, 255, 255}},
{8, 7, color.RGBA{255, 255, 255, 255}},
{8, 8, color.RGBA{255, 255, 255, 255}},
{8, 9, color.RGBA{255, 255, 255, 255}},
{9, 5, color.RGBA{255, 255, 255, 255}},
{9, 6, color.RGBA{255, 255, 255, 255}},
{9, 7, color.RGBA{255, 255, 255, 255}},
{9, 8, color.RGBA{255, 255, 255, 255}},
{10, 6, color.RGBA{255, 255, 255, 255}},
{10, 7, color.RGBA{255, 255, 255, 255}},
{11, 6, color.RGBA{255, 255, 255, 255}},
}
for _, p := range cursorPattern {
px := imgX + p.x
py := imgY + p.y
if px >= 0 && px < img.Bounds().Dx() && py >= 0 && py < img.Bounds().Dy() {
img.SetRGBA(px, py, p.c)
}
}
}
func DrawCursorOnDC(hdc uintptr, captureBounds image.Rectangle) bool {
if !cursorCaptureEnabled.Load() || hdc == 0 {
return false
}
var ci cursorInfo
ci.cbSize = uint32(unsafe.Sizeof(ci))
ret, _, _ := procGetCursorInfo.Call(uintptr(unsafe.Pointer(&ci)))
if ret == 0 || (ci.flags&CURSOR_SHOWING) == 0 || ci.hCursor == 0 {
return false
}
var icon iconInfo
ret, _, _ = procGetIconInfo.Call(ci.hCursor, uintptr(unsafe.Pointer(&icon)))
if ret == 0 {
return false
}
if icon.hbmMask != 0 {
defer deleteObject(icon.hbmMask)
}
if icon.hbmColor != 0 {
defer deleteObject(icon.hbmColor)
}
x := int32(ci.ptScreenPos.x) - int32(captureBounds.Min.X) - int32(icon.xHotspot)
y := int32(ci.ptScreenPos.y) - int32(captureBounds.Min.Y) - int32(icon.yHotspot)
ret, _, _ = procDrawIconEx.Call(
hdc,
uintptr(x),
uintptr(y),
ci.hCursor,
0,
0,
0,
0,
uintptr(DI_NORMAL),
)
return ret != 0
}
func DrawCursorOnImage(img *image.RGBA, captureBounds image.Rectangle) {
if !cursorCaptureEnabled.Load() {
return
}
x, y, visible := getCursorPosition()
if visible {
drawCursor(img, x, y, captureBounds)
}
}