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 {