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.

This commit is contained in:
2026-08-28 13:20:04 +03:00
parent 2a6680c5bf
commit ea71090a53
5 changed files with 125 additions and 86 deletions
+1 -1
View File
@@ -409,7 +409,7 @@ func (a *Agent) handleKey(w http.ResponseWriter, r *http.Request) {
helpers.WriteError(w, http.StatusBadRequest, err.Error()) helpers.WriteError(w, http.StatusBadRequest, err.Error())
return 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") helpers.WriteError(w, http.StatusInternalServerError, "could not send key")
return return
} }
+64 -30
View File
@@ -2,6 +2,7 @@ package input
import ( import (
"errors" "errors"
"fmt"
"runtime" "runtime"
"strings" "strings"
"syscall" "syscall"
@@ -10,6 +11,7 @@ import (
"unsafe" "unsafe"
"tea.chunkbyte.com/kato/go-worm/lib/config" "tea.chunkbyte.com/kato/go-worm/lib/config"
"tea.chunkbyte.com/kato/go-worm/lib/helpers"
) )
const ( const (
@@ -24,8 +26,11 @@ const (
mouseAbsolute = 0x8000 mouseAbsolute = 0x8000
mouseVirtualDesk = 0x4000 mouseVirtualDesk = 0x4000
desktopAllAccess = 0x01FF desktopAllAccess = 0x01FF
keyeventfExtended = 0x0001
keyeventfKeyup = 0x0002 keyeventfKeyup = 0x0002
keyeventfUnicode = 0x0004 keyeventfUnicode = 0x0004
smXVirtualScreen = 76 smXVirtualScreen = 76
smYVirtualScreen = 77 smYVirtualScreen = 77
smCXVirtualScreen = 78 smCXVirtualScreen = 78
@@ -44,12 +49,31 @@ var (
procSetThreadDesktop = user32.NewProc("SetThreadDesktop") procSetThreadDesktop = user32.NewProc("SetThreadDesktop")
procCloseDesktop = user32.NewProc("CloseDesktop") procCloseDesktop = user32.NewProc("CloseDesktop")
procGetSystemMetrics = user32.NewProc("GetSystemMetrics") procGetSystemMetrics = user32.NewProc("GetSystemMetrics")
inputStructSize = int(unsafe.Sizeof(winInput{}))
) )
// ponytail: INPUT must be 40 bytes on amd64; wrong size yields SendInput failures. func init() {
type mouseInput struct { if inputStructSize != 40 {
Type uint32 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 _ uint32
ExtraInfo uintptr
_ [8]byte
}
type mouseData struct {
Dx int32 Dx int32
Dy int32 Dy int32
MouseData uint32 MouseData uint32
@@ -59,15 +83,16 @@ type mouseInput struct {
ExtraInfo uintptr ExtraInfo uintptr
} }
type keybdInput struct { type winInput struct {
Type uint32 Type uint32
_ uint32 _ uint32
Vk uint16 Ki keyboardData
Scan uint16 }
Flags uint32
Time uint32 type winMouseInput struct {
Type uint32
_ uint32 _ uint32
ExtraInfo uintptr Mi mouseData
} }
func EnableDPIAwareness() { func EnableDPIAwareness() {
@@ -87,16 +112,15 @@ func Click(x, y int, button string) error {
} }
absX, absY := absoluteCoords(x, y) absX, absY := absoluteCoords(x, y)
inputs := []mouseInput{ inputs := []winMouseInput{
{Type: inputMouse, Dx: absX, Dy: absY, Flags: mouseMove | mouseAbsolute | mouseVirtualDesk}, {Type: inputMouse, Mi: mouseData{Dx: absX, Dy: absY, Flags: mouseMove | mouseAbsolute | mouseVirtualDesk}},
{Type: inputMouse, Flags: down}, {Type: inputMouse, Mi: mouseData{Flags: down}},
{Type: inputMouse, Flags: up}, {Type: inputMouse, Mi: mouseData{Flags: up}},
} }
if err := sendMouse(inputs); err != nil { if err := sendMouseInputs(inputs); err != nil {
return err return err
} }
// ponytail: best-effort cursor sync for the local user; ignore denial after SendInput click.
_, _, _ = procSetCursorPos.Call(uintptr(x), uintptr(y)) _, _, _ = procSetCursorPos.Call(uintptr(x), uintptr(y))
return nil return nil
} }
@@ -135,18 +159,17 @@ func resolveKeyDelay(ms int) int {
return ms return ms
} }
// ResolveKeyDelay returns the effective per-key delay for API responses.
func ResolveKeyDelay(ms int) int { func ResolveKeyDelay(ms int) int {
return resolveKeyDelay(ms) return resolveKeyDelay(ms)
} }
func sendUnicodeRune(r rune) error { func sendUnicodeRune(r rune) error {
for _, unit := range utf16.Encode([]rune{r}) { for _, unit := range utf16.Encode([]rune{r}) {
inputs := []keybdInput{ inputs := []winInput{
{Type: inputKeyboard, Scan: unit, Flags: keyeventfUnicode}, {Type: inputKeyboard, Ki: keyboardData{Scan: unit, Flags: keyeventfUnicode}},
{Type: inputKeyboard, Scan: unit, Flags: keyeventfUnicode | keyeventfKeyup}, {Type: inputKeyboard, Ki: keyboardData{Scan: unit, Flags: keyeventfUnicode | keyeventfKeyup}},
} }
if err := sendKeys(inputs); err != nil { if err := sendKeyboardInputs(inputs); err != nil {
return err return err
} }
} }
@@ -193,18 +216,29 @@ func mouseFlags(button string) (down, up uint32, err error) {
} }
} }
func sendMouse(inputs []mouseInput) error { func sendMouseInputs(inputs []winMouseInput) error {
n, _, err := procSendInput.Call(uintptr(len(inputs)), uintptr(unsafe.Pointer(&inputs[0])), uintptr(unsafe.Sizeof(inputs[0]))) return sendInput(len(inputs), unsafe.Pointer(&inputs[0]), unsafe.Sizeof(inputs[0]), "mouse")
if n == 0 {
return err
}
return nil
} }
func sendKeys(inputs []keybdInput) error { func sendKeyboardInputs(inputs []winInput) error {
n, _, err := procSendInput.Call(uintptr(len(inputs)), uintptr(unsafe.Pointer(&inputs[0])), uintptr(unsafe.Sizeof(inputs[0]))) return sendInput(len(inputs), unsafe.Pointer(&inputs[0]), unsafe.Sizeof(inputs[0]), "keyboard")
if n == 0 { }
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 err
} }
return nil helpers.Log.Printf("SendInput %s count=%d cbSize=%d failed (n=0)", kind, count, elemSize)
return fmt.Errorf("SendInput failed")
} }
+29 -24
View File
@@ -26,43 +26,48 @@ func PressKey(key, action string, modifiers []string) error {
modVKs = append(modVKs, modVK) modVKs = append(modVKs, modVK)
} }
var inputs []winInput
for _, modVK := range modVKs { for _, modVK := range modVKs {
if err := sendKeyEvent(modVK, false); err != nil { inputs = append(inputs, keyInput(modVK, false))
return err
}
} }
switch strings.ToLower(strings.TrimSpace(action)) { switch strings.ToLower(strings.TrimSpace(action)) {
case "down": case "down":
if err := sendKeyEvent(vk, false); err != nil { inputs = append(inputs, keyInput(vk, false))
return err
}
case "up": case "up":
if err := sendKeyEvent(vk, true); err != nil { inputs = append(inputs, keyInput(vk, true))
return err
}
default: default:
if err := sendKeyEvent(vk, false); err != nil { inputs = append(inputs, keyInput(vk, false), keyInput(vk, true))
return err
}
if err := sendKeyEvent(vk, true); err != nil {
return err
}
for i := len(modVKs) - 1; i >= 0; i-- { for i := len(modVKs) - 1; i >= 0; i-- {
if err := sendKeyEvent(modVKs[i], true); err != nil { inputs = append(inputs, keyInput(modVKs[i], true))
return err
} }
} }
return nil
} return sendKeyboardInputs(inputs)
return nil
} }
func sendKeyEvent(vk uint16, keyUp bool) error { func keyInput(vk uint16, keyUp bool) winInput {
flags := uint32(0) flags := uint32(0)
if keyUp { 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)
} }
+4 -4
View File
@@ -12,10 +12,10 @@ var vkNames = map[string]uint16{
"backspace": 0x08, "backspace": 0x08,
"tab": 0x09, "tab": 0x09,
"enter": 0x0D, "enter": 0x0D,
"shift": 0x10, "shift": 0xA0,
"ctrl": 0x11, "ctrl": 0xA2,
"control": 0x11, "control": 0xA2,
"alt": 0x12, "alt": 0xA4,
"pause": 0x13, "pause": 0x13,
"capslock": 0x14, "capslock": 0x14,
"escape": 0x1B, "escape": 0x1B,
+1 -1
View File
@@ -8,7 +8,7 @@ func TestVkCode(t *testing.T) {
"a": 0x41, "a": 0x41,
"A": 0x41, "A": 0x41,
"enter": 0x0D, "enter": 0x0D,
"ctrl": 0x11, "ctrl": 0xA2,
"win": 0x5B, "win": 0x5B,
"f5": 0x74, "f5": 0x74,
} }