Files
go-worm/lib/input/input.go
T

178 lines
4.2 KiB
Go
Raw Normal View History

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
}