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 {
|
2026-03-17 15:17:01 -05:00
|
|
|
return DrawCursorOnDCScaled(hdc, captureBounds, 1.0, 1.0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func DrawCursorOnDCScaled(hdc uintptr, captureBounds image.Rectangle, scaleX, scaleY float64) bool {
|
2026-03-05 00:04:26 -06:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-17 15:17:01 -05:00
|
|
|
// Map from screen space into the (possibly scaled) DC space.
|
|
|
|
|
x := int32(float64(ci.ptScreenPos.x-int32(captureBounds.Min.X))*scaleX) - int32(icon.xHotspot)
|
|
|
|
|
y := int32(float64(ci.ptScreenPos.y-int32(captureBounds.Min.Y))*scaleY) - int32(icon.yHotspot)
|
2026-03-05 00:04:26 -06:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|