178 lines
4.2 KiB
Go
178 lines
4.2 KiB
Go
package input
|
|
|
|
import (
|
|
"errors"
|
|
"runtime"
|
|
"strings"
|
|
"syscall"
|
|
"unicode/utf16"
|
|
"unsafe"
|
|
)
|
|
|
|
const (
|
|
inputMouse = 0
|
|
inputKeyboard = 1
|
|
|
|
mouseMove = 0x0001
|
|
mouseLeftDown = 0x0002
|
|
mouseLeftUp = 0x0004
|
|
mouseRightDown = 0x0008
|
|
mouseRightUp = 0x0010
|
|
mouseAbsolute = 0x8000
|
|
mouseVirtualDesk = 0x4000
|
|
desktopAllAccess = 0x01FF
|
|
keyeventfKeyup = 0x0002
|
|
keyeventfUnicode = 0x0004
|
|
smXVirtualScreen = 76
|
|
smYVirtualScreen = 77
|
|
smCXVirtualScreen = 78
|
|
smCYVirtualScreen = 79
|
|
)
|
|
|
|
var (
|
|
ErrBadButton = errors.New("button must be left or right")
|
|
ErrEmptyText = errors.New("text must not be empty")
|
|
|
|
user32 = syscall.NewLazyDLL("user32.dll")
|
|
procSetCursorPos = user32.NewProc("SetCursorPos")
|
|
procSendInput = user32.NewProc("SendInput")
|
|
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 uint32
|
|
_ uint32
|
|
Dx int32
|
|
Dy int32
|
|
MouseData uint32
|
|
Flags uint32
|
|
Time uint32
|
|
_ uint32
|
|
ExtraInfo uintptr
|
|
}
|
|
|
|
type keybdInput struct {
|
|
Type uint32
|
|
_ uint32
|
|
Vk uint16
|
|
Scan uint16
|
|
Flags uint32
|
|
Time uint32
|
|
_ uint32
|
|
ExtraInfo uintptr
|
|
}
|
|
|
|
func EnableDPIAwareness() {
|
|
_, _, _ = procSetProcessDPIAware.Call()
|
|
}
|
|
|
|
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)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
absX, absY := absoluteCoords(x, y)
|
|
inputs := []mouseInput{
|
|
{Type: inputMouse, Dx: absX, Dy: absY, Flags: mouseMove | mouseAbsolute | mouseVirtualDesk},
|
|
{Type: inputMouse, Flags: down},
|
|
{Type: inputMouse, Flags: up},
|
|
}
|
|
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 {
|
|
if text == "" {
|
|
return ErrEmptyText
|
|
}
|
|
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
if err := attachInputDesktop(); err != nil {
|
|
return err
|
|
}
|
|
|
|
units := utf16.Encode([]rune(text))
|
|
inputs := make([]keybdInput, 0, len(units)*2)
|
|
for _, unit := range units {
|
|
inputs = append(inputs,
|
|
keybdInput{Type: inputKeyboard, Scan: unit, Flags: keyeventfUnicode},
|
|
keybdInput{Type: inputKeyboard, Scan: unit, Flags: keyeventfUnicode | keyeventfKeyup},
|
|
)
|
|
}
|
|
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) {
|
|
switch strings.ToLower(strings.TrimSpace(button)) {
|
|
case "", "left":
|
|
return mouseLeftDown, mouseLeftUp, nil
|
|
case "right":
|
|
return mouseRightDown, mouseRightUp, nil
|
|
default:
|
|
return 0, 0, ErrBadButton
|
|
}
|
|
}
|
|
|
|
func sendMouse(inputs []mouseInput) error {
|
|
n, _, err := procSendInput.Call(uintptr(len(inputs)), uintptr(unsafe.Pointer(&inputs[0])), uintptr(unsafe.Sizeof(inputs[0])))
|
|
if n == 0 {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func sendKeys(inputs []keybdInput) error {
|
|
n, _, err := procSendInput.Call(uintptr(len(inputs)), uintptr(unsafe.Pointer(&inputs[0])), uintptr(unsafe.Sizeof(inputs[0])))
|
|
if n == 0 {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|