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") }