Enhance input handling by adding support for absolute mouse coordinates and desktop attachment. Implement runtime locking for thread safety during input operations. Update mouse input flags and add new functions for desktop management.

This commit is contained in:
2026-08-28 12:47:14 +03:00
parent d48c2044bc
commit d7274f34e0
+77 -16
View File
@@ -2,6 +2,7 @@ package input
import ( import (
"errors" "errors"
"runtime"
"strings" "strings"
"syscall" "syscall"
"unicode/utf16" "unicode/utf16"
@@ -12,25 +13,37 @@ const (
inputMouse = 0 inputMouse = 0
inputKeyboard = 1 inputKeyboard = 1
mouseLeftDown = 0x0002 mouseMove = 0x0001
mouseLeftUp = 0x0004 mouseLeftDown = 0x0002
mouseRightDown = 0x0008 mouseLeftUp = 0x0004
mouseRightUp = 0x0010 mouseRightDown = 0x0008
mouseRightUp = 0x0010
keyeventfKeyup = 0x0002 mouseAbsolute = 0x8000
keyeventfUnicode = 0x0004 mouseVirtualDesk = 0x4000
desktopAllAccess = 0x01FF
keyeventfKeyup = 0x0002
keyeventfUnicode = 0x0004
smXVirtualScreen = 76
smYVirtualScreen = 77
smCXVirtualScreen = 78
smCYVirtualScreen = 79
) )
var ( var (
ErrBadButton = errors.New("button must be left or right") ErrBadButton = errors.New("button must be left or right")
ErrEmptyText = errors.New("text must not be empty") ErrEmptyText = errors.New("text must not be empty")
user32 = syscall.NewLazyDLL("user32.dll") user32 = syscall.NewLazyDLL("user32.dll")
procSetCursorPos = user32.NewProc("SetCursorPos") procSetCursorPos = user32.NewProc("SetCursorPos")
procSendInput = user32.NewProc("SendInput") procSendInput = user32.NewProc("SendInput")
procSetProcessDPIAware = user32.NewProc("SetProcessDPIAware") procSetProcessDPIAware = user32.NewProc("SetProcessDPIAware")
procOpenInputDesktop = user32.NewProc("OpenInputDesktop")
procSetThreadDesktop = user32.NewProc("SetThreadDesktop")
procCloseDesktop = user32.NewProc("CloseDesktop")
procGetSystemMetrics = user32.NewProc("GetSystemMetrics")
) )
// ponytail: INPUT must be 40 bytes on amd64; wrong size yields SendInput failures.
type mouseInput struct { type mouseInput struct {
Type uint32 Type uint32
_ uint32 _ uint32
@@ -39,6 +52,7 @@ type mouseInput struct {
MouseData uint32 MouseData uint32
Flags uint32 Flags uint32
Time uint32 Time uint32
_ uint32
ExtraInfo uintptr ExtraInfo uintptr
} }
@@ -49,8 +63,8 @@ type keybdInput struct {
Scan uint16 Scan uint16
Flags uint32 Flags uint32
Time uint32 Time uint32
_ uint32
ExtraInfo uintptr ExtraInfo uintptr
_ [8]byte
} }
func EnableDPIAwareness() { func EnableDPIAwareness() {
@@ -58,25 +72,43 @@ func EnableDPIAwareness() {
} }
func Click(x, y int, button string) error { func Click(x, y int, button string) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if err := attachInputDesktop(); err != nil {
return err
}
down, up, err := mouseFlags(button) down, up, err := mouseFlags(button)
if err != nil { if err != nil {
return err return err
} }
ok, _, callErr := procSetCursorPos.Call(uintptr(x), uintptr(y))
if ok == 0 { absX, absY := absoluteCoords(x, y)
return callErr
}
inputs := []mouseInput{ inputs := []mouseInput{
{Type: inputMouse, Dx: absX, Dy: absY, Flags: mouseMove | mouseAbsolute | mouseVirtualDesk},
{Type: inputMouse, Flags: down}, {Type: inputMouse, Flags: down},
{Type: inputMouse, Flags: up}, {Type: inputMouse, Flags: up},
} }
return sendMouse(inputs) if err := sendMouse(inputs); err != nil {
return err
}
// ponytail: best-effort cursor sync for the local user; ignore denial after SendInput click.
_, _, _ = procSetCursorPos.Call(uintptr(x), uintptr(y))
return nil
} }
func TypeText(text string) error { func TypeText(text string) error {
if text == "" { if text == "" {
return ErrEmptyText return ErrEmptyText
} }
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if err := attachInputDesktop(); err != nil {
return err
}
units := utf16.Encode([]rune(text)) units := utf16.Encode([]rune(text))
inputs := make([]keybdInput, 0, len(units)*2) inputs := make([]keybdInput, 0, len(units)*2)
for _, unit := range units { for _, unit := range units {
@@ -88,6 +120,35 @@ func TypeText(text string) error {
return sendKeys(inputs) return sendKeys(inputs)
} }
func attachInputDesktop() error {
handle, _, err := procOpenInputDesktop.Call(0, 0, desktopAllAccess)
if handle == 0 {
return err
}
defer procCloseDesktop.Call(handle)
ok, _, err := procSetThreadDesktop.Call(handle)
if ok == 0 {
return err
}
return nil
}
func absoluteCoords(x, y int) (int32, int32) {
left, _, _ := procGetSystemMetrics.Call(smXVirtualScreen)
top, _, _ := procGetSystemMetrics.Call(smYVirtualScreen)
width, _, _ := procGetSystemMetrics.Call(smCXVirtualScreen)
height, _, _ := procGetSystemMetrics.Call(smCYVirtualScreen)
if width <= 1 {
width = 1
}
if height <= 1 {
height = 1
}
absX := int32((int64(x-int(left)) * 65535) / int64(width-1))
absY := int32((int64(y-int(top)) * 65535) / int64(height-1))
return absX, absY
}
func mouseFlags(button string) (down, up uint32, err error) { func mouseFlags(button string) (down, up uint32, err error) {
switch strings.ToLower(strings.TrimSpace(button)) { switch strings.ToLower(strings.TrimSpace(button)) {
case "", "left": case "", "left":