From ea71090a53a8f5e66b8a6763f96f3d7c58fa1766 Mon Sep 17 00:00:00 2001 From: Daniel Legt Date: Fri, 28 Aug 2026 13:20:04 +0300 Subject: [PATCH] Refactor input handling to unify mouse and keyboard input structures. Update key event processing to support extended key flags and improve error logging for input operations. Adjust key mappings for consistency across input types. --- lib/agent/handlers.go | 2 +- lib/input/input.go | 144 ++++++++++++++++++++++++--------------- lib/input/key.go | 53 +++++++------- lib/input/keymap.go | 8 +-- lib/input/keymap_test.go | 4 +- 5 files changed, 125 insertions(+), 86 deletions(-) diff --git a/lib/agent/handlers.go b/lib/agent/handlers.go index 66801cd..4c32b31 100644 --- a/lib/agent/handlers.go +++ b/lib/agent/handlers.go @@ -409,7 +409,7 @@ func (a *Agent) handleKey(w http.ResponseWriter, r *http.Request) { helpers.WriteError(w, http.StatusBadRequest, err.Error()) return } - helpers.Log.Printf("key: %v", err) + helpers.Log.Printf("key %q action=%s mods=%v: %v", request.Key, action, request.Modifiers, err) helpers.WriteError(w, http.StatusInternalServerError, "could not send key") return } diff --git a/lib/input/input.go b/lib/input/input.go index 258ea79..ac362e1 100644 --- a/lib/input/input.go +++ b/lib/input/input.go @@ -2,6 +2,7 @@ package input import ( "errors" + "fmt" "runtime" "strings" "syscall" @@ -10,46 +11,69 @@ import ( "unsafe" "tea.chunkbyte.com/kato/go-worm/lib/config" + "tea.chunkbyte.com/kato/go-worm/lib/helpers" ) const ( - inputMouse = 0 + 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 + 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") + 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") + procOpenInputDesktop = user32.NewProc("OpenInputDesktop") + procSetThreadDesktop = user32.NewProc("SetThreadDesktop") + procCloseDesktop = user32.NewProc("CloseDesktop") + procGetSystemMetrics = user32.NewProc("GetSystemMetrics") + + inputStructSize = int(unsafe.Sizeof(winInput{})) ) -// ponytail: INPUT must be 40 bytes on amd64; wrong size yields SendInput failures. -type mouseInput struct { - Type uint32 +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 @@ -59,15 +83,16 @@ type mouseInput struct { ExtraInfo uintptr } -type keybdInput struct { - Type uint32 - _ uint32 - Vk uint16 - Scan uint16 - 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() { @@ -87,16 +112,15 @@ func Click(x, y int, button string) error { } 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}, + 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 := sendMouse(inputs); err != nil { + if err := sendMouseInputs(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 } @@ -135,18 +159,17 @@ func resolveKeyDelay(ms int) int { return ms } -// ResolveKeyDelay returns the effective per-key delay for API responses. func ResolveKeyDelay(ms int) int { return resolveKeyDelay(ms) } func sendUnicodeRune(r rune) error { for _, unit := range utf16.Encode([]rune{r}) { - inputs := []keybdInput{ - {Type: inputKeyboard, Scan: unit, Flags: keyeventfUnicode}, - {Type: inputKeyboard, Scan: unit, Flags: keyeventfUnicode | keyeventfKeyup}, + inputs := []winInput{ + {Type: inputKeyboard, Ki: keyboardData{Scan: unit, Flags: keyeventfUnicode}}, + {Type: inputKeyboard, Ki: keyboardData{Scan: unit, Flags: keyeventfUnicode | keyeventfKeyup}}, } - if err := sendKeys(inputs); err != nil { + if err := sendKeyboardInputs(inputs); err != nil { return err } } @@ -193,18 +216,29 @@ func mouseFlags(button string) (down, up uint32, err error) { } } -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 sendMouseInputs(inputs []winMouseInput) error { + return sendInput(len(inputs), unsafe.Pointer(&inputs[0]), unsafe.Sizeof(inputs[0]), "mouse") } -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 { +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 } - return nil + helpers.Log.Printf("SendInput %s count=%d cbSize=%d failed (n=0)", kind, count, elemSize) + return fmt.Errorf("SendInput failed") } diff --git a/lib/input/key.go b/lib/input/key.go index 5f04425..e43b6be 100644 --- a/lib/input/key.go +++ b/lib/input/key.go @@ -26,43 +26,48 @@ func PressKey(key, action string, modifiers []string) error { modVKs = append(modVKs, modVK) } + var inputs []winInput for _, modVK := range modVKs { - if err := sendKeyEvent(modVK, false); err != nil { - return err - } + inputs = append(inputs, keyInput(modVK, false)) } switch strings.ToLower(strings.TrimSpace(action)) { case "down": - if err := sendKeyEvent(vk, false); err != nil { - return err - } + inputs = append(inputs, keyInput(vk, false)) case "up": - if err := sendKeyEvent(vk, true); err != nil { - return err - } + inputs = append(inputs, keyInput(vk, true)) default: - if err := sendKeyEvent(vk, false); err != nil { - return err - } - if err := sendKeyEvent(vk, true); err != nil { - return err - } + inputs = append(inputs, keyInput(vk, false), keyInput(vk, true)) for i := len(modVKs) - 1; i >= 0; i-- { - if err := sendKeyEvent(modVKs[i], true); err != nil { - return err - } + inputs = append(inputs, keyInput(modVKs[i], true)) } - return nil } - return nil + + return sendKeyboardInputs(inputs) } -func sendKeyEvent(vk uint16, keyUp bool) error { +func keyInput(vk uint16, keyUp bool) winInput { flags := uint32(0) if keyUp { - flags = keyeventfKeyup + flags |= keyeventfKeyup + } + if isExtendedVK(vk) { + flags |= keyeventfExtended + } + return winInput{ + Type: inputKeyboard, + Ki: keyboardData{ + Vk: vk, + Flags: flags, + }, + } +} + +func isExtendedVK(vk uint16) bool { + switch vk { + case 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x2D, 0x2E, 0x5B, 0x5C, 0x5D: + return true + default: + return false } - inputs := []keybdInput{{Type: inputKeyboard, Vk: vk, Flags: flags}} - return sendKeys(inputs) } diff --git a/lib/input/keymap.go b/lib/input/keymap.go index f95f8f8..fbf7862 100644 --- a/lib/input/keymap.go +++ b/lib/input/keymap.go @@ -12,10 +12,10 @@ var vkNames = map[string]uint16{ "backspace": 0x08, "tab": 0x09, "enter": 0x0D, - "shift": 0x10, - "ctrl": 0x11, - "control": 0x11, - "alt": 0x12, + "shift": 0xA0, + "ctrl": 0xA2, + "control": 0xA2, + "alt": 0xA4, "pause": 0x13, "capslock": 0x14, "escape": 0x1B, diff --git a/lib/input/keymap_test.go b/lib/input/keymap_test.go index 9883e56..2ddc0a8 100644 --- a/lib/input/keymap_test.go +++ b/lib/input/keymap_test.go @@ -8,8 +8,8 @@ func TestVkCode(t *testing.T) { "a": 0x41, "A": 0x41, "enter": 0x0D, - "ctrl": 0x11, - "win": 0x5B, + "ctrl": 0xA2, + "win": 0x5B, "f5": 0x74, } for name, want := range cases {