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

245 lines
5.8 KiB
Go

package input
import (
"errors"
"fmt"
"runtime"
"strings"
"syscall"
"time"
"unicode/utf16"
"unsafe"
"tea.chunkbyte.com/kato/go-worm/lib/config"
"tea.chunkbyte.com/kato/go-worm/lib/helpers"
)
const (
inputMouse = 0
inputKeyboard = 1
mouseMove = 0x0001
mouseLeftDown = 0x0002
mouseLeftUp = 0x0004
mouseRightDown = 0x0008
mouseRightUp = 0x0010
mouseAbsolute = 0x8000
mouseVirtualDesk = 0x4000
desktopAllAccess = 0x01FF
keyeventfExtended = 0x0001
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")
inputStructSize = int(unsafe.Sizeof(winInput{}))
)
func init() {
if inputStructSize != 40 {
panic(fmt.Sprintf("winInput must be 40 bytes on this platform, got %d", inputStructSize))
}
if unsafe.Sizeof(winMouseInput{}) != 40 {
panic(fmt.Sprintf("winMouseInput must be 40 bytes on this platform, got %d", unsafe.Sizeof(winMouseInput{})))
}
}
// winInput matches the 40-byte INPUT struct on amd64 (8-byte header + 32-byte union).
type keyboardData struct {
Vk uint16
Scan uint16
Flags uint32
Time uint32
_ uint32
ExtraInfo uintptr
_ [8]byte
}
type mouseData struct {
Dx int32
Dy int32
MouseData uint32
Flags uint32
Time uint32
_ uint32
ExtraInfo uintptr
}
type winInput struct {
Type uint32
_ uint32
Ki keyboardData
}
type winMouseInput struct {
Type uint32
_ uint32
Mi mouseData
}
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 := []winMouseInput{
{Type: inputMouse, Mi: mouseData{Dx: absX, Dy: absY, Flags: mouseMove | mouseAbsolute | mouseVirtualDesk}},
{Type: inputMouse, Mi: mouseData{Flags: down}},
{Type: inputMouse, Mi: mouseData{Flags: up}},
}
if err := sendMouseInputs(inputs); err != nil {
return err
}
_, _, _ = procSetCursorPos.Call(uintptr(x), uintptr(y))
return nil
}
func TypeText(text string, delayMs int) error {
if text == "" {
return ErrEmptyText
}
delayMs = resolveKeyDelay(delayMs)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if err := attachInputDesktop(); err != nil {
return err
}
runes := []rune(text)
for i, r := range runes {
if err := sendUnicodeRune(r); err != nil {
return err
}
if i < len(runes)-1 && delayMs > 0 {
time.Sleep(time.Duration(delayMs) * time.Millisecond)
}
}
return nil
}
func resolveKeyDelay(ms int) int {
if ms < 0 {
return 0
}
if ms > config.MaxKeyDelayMs {
return config.MaxKeyDelayMs
}
return ms
}
func ResolveKeyDelay(ms int) int {
return resolveKeyDelay(ms)
}
func sendUnicodeRune(r rune) error {
for _, unit := range utf16.Encode([]rune{r}) {
inputs := []winInput{
{Type: inputKeyboard, Ki: keyboardData{Scan: unit, Flags: keyeventfUnicode}},
{Type: inputKeyboard, Ki: keyboardData{Scan: unit, Flags: keyeventfUnicode | keyeventfKeyup}},
}
if err := sendKeyboardInputs(inputs); err != nil {
return err
}
}
return nil
}
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 sendMouseInputs(inputs []winMouseInput) error {
return sendInput(len(inputs), unsafe.Pointer(&inputs[0]), unsafe.Sizeof(inputs[0]), "mouse")
}
func sendKeyboardInputs(inputs []winInput) error {
return sendInput(len(inputs), unsafe.Pointer(&inputs[0]), unsafe.Sizeof(inputs[0]), "keyboard")
}
func sendInput(count int, ptr unsafe.Pointer, elemSize uintptr, kind string) error {
if count == 0 {
return nil
}
if int(elemSize) != inputStructSize {
return fmt.Errorf("input struct size mismatch: %s has %d bytes, want %d", kind, elemSize, inputStructSize)
}
n, _, err := procSendInput.Call(uintptr(count), uintptr(ptr), elemSize)
if n != 0 {
return nil
}
if err != nil && err.Error() != "The operation completed successfully." {
helpers.Log.Printf("SendInput %s count=%d cbSize=%d: %v", kind, count, elemSize, err)
return err
}
helpers.Log.Printf("SendInput %s count=%d cbSize=%d failed (n=0)", kind, count, elemSize)
return fmt.Errorf("SendInput failed")
}