From 61de596daa2bf9171f8f9c20b0a537b42b38523e Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 15 Aug 2024 15:52:32 -0400 Subject: [PATCH] feat(windows): add windows console read api This adds Windows Console API to eventually replace erikgeiser/coninput library. --- windows/syscall_windows.go | 6 +- windows/types_windows.go | 389 ++++++++++++++++++++++++++++++++++++ windows/zsyscall_windows.go | 40 +++- 3 files changed, 424 insertions(+), 11 deletions(-) create mode 100644 windows/types_windows.go diff --git a/windows/syscall_windows.go b/windows/syscall_windows.go index f57cbd9b..b7f3cf88 100644 --- a/windows/syscall_windows.go +++ b/windows/syscall_windows.go @@ -6,5 +6,7 @@ var NewLazySystemDLL = windows.NewLazySystemDLL type Handle = windows.Handle -//sys GetKeyboardLayout(threadId uint32) (hkl Handle) = user32.GetKeyboardLayout -//sys ToUnicodeEx(vkey uint32, scancode uint32, keystate *byte, pwszBuff *uint16, cchBuff int32, flags uint32, hkl Handle) (ret int32) = user32.ToUnicodeEx +//sys ReadConsoleInput(console Handle, buf *InputRecord, toread uint32, read *uint32) (err error) = kernel32.ReadConsoleInputW +//sys PeekConsoleInput(console Handle, buf *InputRecord, toread uint32, read *uint32) (err error) = kernel32.PeekConsoleInputW +//sys GetNumberOfConsoleInputEvents(console Handle, numevents *uint32) (err error) = kernel32.GetNumberOfConsoleInputEvents +//sys FlushConsoleInputBuffer(console Handle) (err error) = kernel32.FlushConsoleInputBuffer diff --git a/windows/types_windows.go b/windows/types_windows.go new file mode 100644 index 00000000..71b66e34 --- /dev/null +++ b/windows/types_windows.go @@ -0,0 +1,389 @@ +package windows + +import ( + "encoding/binary" + + "golang.org/x/sys/windows" +) + +// Virtual Key codes +// https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes +const ( + VK_LBUTTON = 0x01 + VK_RBUTTON = 0x02 + VK_CANCEL = 0x03 + VK_MBUTTON = 0x04 + VK_XBUTTON1 = 0x05 + VK_XBUTTON2 = 0x06 + VK_BACK = 0x08 + VK_TAB = 0x09 + VK_CLEAR = 0x0C + VK_RETURN = 0x0D + VK_SHIFT = 0x10 + VK_CONTROL = 0x11 + VK_MENU = 0x12 + VK_PAUSE = 0x13 + VK_CAPITAL = 0x14 + VK_KANA = 0x15 + VK_HANGEUL = 0x15 + VK_HANGUL = 0x15 + VK_IME_ON = 0x16 + VK_JUNJA = 0x17 + VK_FINAL = 0x18 + VK_HANJA = 0x19 + VK_KANJI = 0x19 + VK_IME_OFF = 0x1A + VK_ESCAPE = 0x1B + VK_CONVERT = 0x1C + VK_NONCONVERT = 0x1D + VK_ACCEPT = 0x1E + VK_MODECHANGE = 0x1F + VK_SPACE = 0x20 + VK_PRIOR = 0x21 + VK_NEXT = 0x22 + VK_END = 0x23 + VK_HOME = 0x24 + VK_LEFT = 0x25 + VK_UP = 0x26 + VK_RIGHT = 0x27 + VK_DOWN = 0x28 + VK_SELECT = 0x29 + VK_PRINT = 0x2A + VK_EXECUTE = 0x2B + VK_SNAPSHOT = 0x2C + VK_INSERT = 0x2D + VK_DELETE = 0x2E + VK_HELP = 0x2F + VK_0 = 0x30 + VK_1 = 0x31 + VK_2 = 0x32 + VK_3 = 0x33 + VK_4 = 0x34 + VK_5 = 0x35 + VK_6 = 0x36 + VK_7 = 0x37 + VK_8 = 0x38 + VK_9 = 0x39 + VK_A = 0x41 + VK_B = 0x42 + VK_C = 0x43 + VK_D = 0x44 + VK_E = 0x45 + VK_F = 0x46 + VK_G = 0x47 + VK_H = 0x48 + VK_I = 0x49 + VK_J = 0x4A + VK_K = 0x4B + VK_L = 0x4C + VK_M = 0x4D + VK_N = 0x4E + VK_O = 0x4F + VK_P = 0x50 + VK_Q = 0x51 + VK_R = 0x52 + VK_S = 0x53 + VK_T = 0x54 + VK_U = 0x55 + VK_V = 0x56 + VK_W = 0x57 + VK_X = 0x58 + VK_Y = 0x59 + VK_Z = 0x5A + VK_LWIN = 0x5B + VK_RWIN = 0x5C + VK_APPS = 0x5D + VK_SLEEP = 0x5F + VK_NUMPAD0 = 0x60 + VK_NUMPAD1 = 0x61 + VK_NUMPAD2 = 0x62 + VK_NUMPAD3 = 0x63 + VK_NUMPAD4 = 0x64 + VK_NUMPAD5 = 0x65 + VK_NUMPAD6 = 0x66 + VK_NUMPAD7 = 0x67 + VK_NUMPAD8 = 0x68 + VK_NUMPAD9 = 0x69 + VK_MULTIPLY = 0x6A + VK_ADD = 0x6B + VK_SEPARATOR = 0x6C + VK_SUBTRACT = 0x6D + VK_DECIMAL = 0x6E + VK_DIVIDE = 0x6F + VK_F1 = 0x70 + VK_F2 = 0x71 + VK_F3 = 0x72 + VK_F4 = 0x73 + VK_F5 = 0x74 + VK_F6 = 0x75 + VK_F7 = 0x76 + VK_F8 = 0x77 + VK_F9 = 0x78 + VK_F10 = 0x79 + VK_F11 = 0x7A + VK_F12 = 0x7B + VK_F13 = 0x7C + VK_F14 = 0x7D + VK_F15 = 0x7E + VK_F16 = 0x7F + VK_F17 = 0x80 + VK_F18 = 0x81 + VK_F19 = 0x82 + VK_F20 = 0x83 + VK_F21 = 0x84 + VK_F22 = 0x85 + VK_F23 = 0x86 + VK_F24 = 0x87 + VK_NUMLOCK = 0x90 + VK_SCROLL = 0x91 + VK_OEM_NEC_EQUAL = 0x92 + VK_OEM_FJ_JISHO = 0x92 + VK_OEM_FJ_MASSHOU = 0x93 + VK_OEM_FJ_TOUROKU = 0x94 + VK_OEM_FJ_LOYA = 0x95 + VK_OEM_FJ_ROYA = 0x96 + VK_LSHIFT = 0xA0 + VK_RSHIFT = 0xA1 + VK_LCONTROL = 0xA2 + VK_RCONTROL = 0xA3 + VK_LMENU = 0xA4 + VK_RMENU = 0xA5 + VK_BROWSER_BACK = 0xA6 + VK_BROWSER_FORWARD = 0xA7 + VK_BROWSER_REFRESH = 0xA8 + VK_BROWSER_STOP = 0xA9 + VK_BROWSER_SEARCH = 0xAA + VK_BROWSER_FAVORITES = 0xAB + VK_BROWSER_HOME = 0xAC + VK_VOLUME_MUTE = 0xAD + VK_VOLUME_DOWN = 0xAE + VK_VOLUME_UP = 0xAF + VK_MEDIA_NEXT_TRACK = 0xB0 + VK_MEDIA_PREV_TRACK = 0xB1 + VK_MEDIA_STOP = 0xB2 + VK_MEDIA_PLAY_PAUSE = 0xB3 + VK_LAUNCH_MAIL = 0xB4 + VK_LAUNCH_MEDIA_SELECT = 0xB5 + VK_LAUNCH_APP1 = 0xB6 + VK_LAUNCH_APP2 = 0xB7 + VK_OEM_1 = 0xBA + VK_OEM_PLUS = 0xBB + VK_OEM_COMMA = 0xBC + VK_OEM_MINUS = 0xBD + VK_OEM_PERIOD = 0xBE + VK_OEM_2 = 0xBF + VK_OEM_3 = 0xC0 + VK_OEM_4 = 0xDB + VK_OEM_5 = 0xDC + VK_OEM_6 = 0xDD + VK_OEM_7 = 0xDE + VK_OEM_8 = 0xDF + VK_OEM_AX = 0xE1 + VK_OEM_102 = 0xE2 + VK_ICO_HELP = 0xE3 + VK_ICO_00 = 0xE4 + VK_PROCESSKEY = 0xE5 + VK_ICO_CLEAR = 0xE6 + VK_OEM_RESET = 0xE9 + VK_OEM_JUMP = 0xEA + VK_OEM_PA1 = 0xEB + VK_OEM_PA2 = 0xEC + VK_OEM_PA3 = 0xED + VK_OEM_WSCTRL = 0xEE + VK_OEM_CUSEL = 0xEF + VK_OEM_ATTN = 0xF0 + VK_OEM_FINISH = 0xF1 + VK_OEM_COPY = 0xF2 + VK_OEM_AUTO = 0xF3 + VK_OEM_ENLW = 0xF4 + VK_OEM_BACKTAB = 0xF5 + VK_ATTN = 0xF6 + VK_CRSEL = 0xF7 + VK_EXSEL = 0xF8 + VK_EREOF = 0xF9 + VK_PLAY = 0xFA + VK_ZOOM = 0xFB + VK_NONAME = 0xFC + VK_PA1 = 0xFD + VK_OEM_CLEAR = 0xFE +) + +// Mouse button constants. +// https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str +const ( + FROM_LEFT_1ST_BUTTON_PRESSED = 0x0001 + RIGHTMOST_BUTTON_PRESSED = 0x0002 + FROM_LEFT_2ND_BUTTON_PRESSED = 0x0004 + FROM_LEFT_3RD_BUTTON_PRESSED = 0x0008 + FROM_LEFT_4TH_BUTTON_PRESSED = 0x0010 +) + +// Control key state constaints. +// https://docs.microsoft.com/en-us/windows/console/key-event-record-str +// https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str +const ( + CAPSLOCK_ON = 0x0080 + ENHANCED_KEY = 0x0100 + LEFT_ALT_PRESSED = 0x0002 + LEFT_CTRL_PRESSED = 0x0008 + NUMLOCK_ON = 0x0020 + RIGHT_ALT_PRESSED = 0x0001 + RIGHT_CTRL_PRESSED = 0x0004 + SCROLLLOCK_ON = 0x0040 + SHIFT_PRESSED = 0x0010 + NO_CONTROL_KEY = 0x0000 +) + +// Mouse event record event flags. +// https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str +const ( + CLICK = 0x0000 + MOUSE_MOVED = 0x0001 + DOUBLE_CLICK = 0x0002 + MOUSE_WHEELED = 0x0004 + MOUSE_HWHEELED = 0x0008 +) + +// Input Record Event Types +// https://learn.microsoft.com/en-us/windows/console/input-record-str +const ( + FOCUS_EVENT = 0x0010 + KEY_EVENT = 0x0001 + MENU_EVENT = 0x0008 + MOUSE_EVENT = 0x0002 + WINDOW_BUFFER_SIZE_EVENT = 0x0004 +) + +// FocusEventRecord corresponds to the FocusEventRecord structure from the +// Windows console API. +// https://docs.microsoft.com/en-us/windows/console/focus-event-record-str +type FocusEventRecord struct { + // SetFocus is reserved and should not be used. + SetFocus bool +} + +// KeyEventRecord corresponds to the KeyEventRecord structure from the Windows +// console API. +// https://docs.microsoft.com/en-us/windows/console/key-event-record-str +type KeyEventRecord struct { + // KeyDown specified whether the key is pressed or released. + KeyDown bool + + // RepeatCount indicates that a key is being held down. For example, when a + // key is held down, five events with RepeatCount equal to 1 may be + // generated, one event with RepeatCount equal to 5, or multiple events + // with RepeatCount greater than or equal to 1. + RepeatCount uint16 + + // VirtualKeyCode identifies the given key in a device-independent manner + // (see + // https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes). + VirtualKeyCode uint16 + + // VirtualScanCode represents the device-dependent value generated by the + // keyboard hardware. + VirtualScanCode uint16 + + // Char is the character that corresponds to the pressed key. Char can be + // zero for some keys. + Char rune + + // ControlKeyState holds the state of the control keys. + ControlKeyState uint32 +} + +// MenuEventRecord corresponds to the MenuEventRecord structure from the +// Windows console API. +// https://docs.microsoft.com/en-us/windows/console/menu-event-record-str +type MenuEventRecord struct { + CommandID uint32 +} + +// MouseEventRecord corresponds to the MouseEventRecord structure from the +// Windows console API. +// https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str +type MouseEventRecord struct { + // MousePosition contains the location of the cursor, in terms of the + // console screen buffer's character-cell coordinates. + MousePositon windows.Coord + + // ButtonState holds the status of the mouse buttons. + ButtonState uint32 + + // ControlKeyState holds the state of the control keys. + ControlKeyState uint32 + + // EventFlags specify the type of mouse event. + EventFlags uint32 +} + +// WindowBufferSizeRecord corresponds to the WindowBufferSizeRecord structure +// from the Windows console API. +// https://docs.microsoft.com/en-us/windows/console/window-buffer-size-record-str +type WindowBufferSizeRecord struct { + // Size contains the size of the console screen buffer, in character cell + // columns and rows. + Size windows.Coord +} + +// InputRecord corresponds to the INPUT_RECORD structure from the Windows +// console API. +// +// https://docs.microsoft.com/en-us/windows/console/input-record-str +type InputRecord struct { + // EventType specifies the type of event that helt in Event. + EventType uint16 + + // Padding of the 16-bit EventType to a whole 32-bit dword. + _ [2]byte + + // Event holds the actual event data. + Event [16]byte +} + +// FocusEvent returns the event as a FOCUS_EVENT_RECORD. +func (ir InputRecord) FocusEvent() FocusEventRecord { + return FocusEventRecord{SetFocus: ir.Event[0] > 0} +} + +// KeyEvent returns the event as a KEY_EVENT_RECORD. +func (ir InputRecord) KeyEvent() KeyEventRecord { + return KeyEventRecord{ + KeyDown: binary.LittleEndian.Uint32(ir.Event[0:4]) > 0, + RepeatCount: binary.LittleEndian.Uint16(ir.Event[4:6]), + VirtualKeyCode: binary.LittleEndian.Uint16(ir.Event[6:8]), + VirtualScanCode: binary.LittleEndian.Uint16(ir.Event[8:10]), + Char: rune(binary.LittleEndian.Uint16(ir.Event[10:12])), + ControlKeyState: binary.LittleEndian.Uint32(ir.Event[12:16]), + } +} + +// MouseEvent returns the event as a MOUSE_EVENT_RECORD. +func (ir InputRecord) MouseEvent() MouseEventRecord { + return MouseEventRecord{ + MousePositon: windows.Coord{ + X: int16(binary.LittleEndian.Uint16(ir.Event[0:2])), + Y: int16(binary.LittleEndian.Uint16(ir.Event[2:4])), + }, + ButtonState: binary.LittleEndian.Uint32(ir.Event[4:8]), + ControlKeyState: binary.LittleEndian.Uint32(ir.Event[8:12]), + EventFlags: binary.LittleEndian.Uint32(ir.Event[12:16]), + } +} + +// WindowBufferSizeEvent returns the event as a WINDOW_BUFFER_SIZE_RECORD. +func (ir InputRecord) WindowBufferSizeEvent() WindowBufferSizeRecord { + return WindowBufferSizeRecord{ + Size: windows.Coord{ + X: int16(binary.LittleEndian.Uint16(ir.Event[0:2])), + Y: int16(binary.LittleEndian.Uint16(ir.Event[2:4])), + }, + } +} + +// MenuEvent returns the event as a MENU_EVENT_RECORD. +func (ir InputRecord) MenuEvent() MenuEventRecord { + return MenuEventRecord{ + CommandID: binary.LittleEndian.Uint32(ir.Event[0:4]), + } +} diff --git a/windows/zsyscall_windows.go b/windows/zsyscall_windows.go index a58f4600..43412b84 100644 --- a/windows/zsyscall_windows.go +++ b/windows/zsyscall_windows.go @@ -36,20 +36,42 @@ func errnoErr(e syscall.Errno) error { } var ( - moduser32 = NewLazySystemDLL("user32.dll") + modkernel32 = NewLazySystemDLL("kernel32.dll") - procGetKeyboardLayout = moduser32.NewProc("GetKeyboardLayout") - procToUnicodeEx = moduser32.NewProc("ToUnicodeEx") + procFlushConsoleInputBuffer = modkernel32.NewProc("FlushConsoleInputBuffer") + procGetNumberOfConsoleInputEvents = modkernel32.NewProc("GetNumberOfConsoleInputEvents") + procPeekConsoleInputW = modkernel32.NewProc("PeekConsoleInputW") + procReadConsoleInputW = modkernel32.NewProc("ReadConsoleInputW") ) -func GetKeyboardLayout(threadId uint32) (hkl Handle) { - r0, _, _ := syscall.Syscall(procGetKeyboardLayout.Addr(), 1, uintptr(threadId), 0, 0) - hkl = Handle(r0) +func FlushConsoleInputBuffer(console Handle) (err error) { + r1, _, e1 := syscall.Syscall(procFlushConsoleInputBuffer.Addr(), 1, uintptr(console), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func GetNumberOfConsoleInputEvents(console Handle, numevents *uint32) (err error) { + r1, _, e1 := syscall.Syscall(procGetNumberOfConsoleInputEvents.Addr(), 2, uintptr(console), uintptr(unsafe.Pointer(numevents)), 0) + if r1 == 0 { + err = errnoErr(e1) + } return } -func ToUnicodeEx(vkey uint32, scancode uint32, keystate *byte, pwszBuff *uint16, cchBuff int32, flags uint32, hkl Handle) (ret int32) { - r0, _, _ := syscall.Syscall9(procToUnicodeEx.Addr(), 7, uintptr(vkey), uintptr(scancode), uintptr(unsafe.Pointer(keystate)), uintptr(unsafe.Pointer(pwszBuff)), uintptr(cchBuff), uintptr(flags), uintptr(hkl), 0, 0) - ret = int32(r0) +func PeekConsoleInput(console Handle, buf *InputRecord, toread uint32, read *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procPeekConsoleInputW.Addr(), 4, uintptr(console), uintptr(unsafe.Pointer(buf)), uintptr(toread), uintptr(unsafe.Pointer(read)), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func ReadConsoleInput(console Handle, buf *InputRecord, toread uint32, read *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procReadConsoleInputW.Addr(), 4, uintptr(console), uintptr(unsafe.Pointer(buf)), uintptr(toread), uintptr(unsafe.Pointer(read)), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } return }