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())
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
}
+89 -55
View File
@@ -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")
}
+29 -24
View File
@@ -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)
}
+4 -4
View File
@@ -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,
+2 -2
View File
@@ -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 {