vtinput

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 24, 2026 License: BSD-3-Clause Imports: 16 Imported by: 5

README

vtinput

Advanced Terminal Input Parsing for Go

Built from ground up for vtui and f4.

vtinput is a standalone, cross-platform Go library designed to solve the historical mess of terminal keyboard input. It provides robust parsing for modern terminal input protocols, allowing TUI applications to handle complex key combinations, mouse events, and bracketed paste with absolute precision.

This library was initially developed for the f4 project (a Far Manager clone in Go) because standard Go terminal libraries (like term or tcell) often rely on legacy terminfo mappings only and cannot reliably distinguish between combinations like Enter and Ctrl+Enter, or Tab and Shift+Tab.

Why vtinput?

For decades, terminals have communicated keystrokes using ambiguous ANSI escape sequences. A classic terminal emulator sends the exact same bytes for Ctrl+I as it does for Tab, and the same bytes for Ctrl+M as for Enter.

Modern terminal emulators have solved this by introducing advanced input protocols. vtinput speaks these protocols natively:

  • kitty keyboard protocol: Fully supported. Provides granular modifier states (Shift, Ctrl, Alt, Super, CapsLock, NumLock) and differentiates all keystrokes.
  • win32 input mode: Fully supported. Used by modern Windows Terminal and some Unix terminals to pass exact Windows Virtual Key Codes and states.
  • SGR 1006 Mouse Protocol: For high-coordinate and sub-cell mouse tracking, including scroll wheels.
  • Bracketed Paste (2004) & Focus Tracking (1004): Native support for detecting when the terminal gains/loses focus and for fast accepting large blocks of pasted text.
  • Legacy CSI / SS3 Fallback: If the terminal does not support modern protocols, vtinput gracefully falls back to parsing standard VT100/xterm sequences with high accuracy and a built-in timeout mechanism for the ESC key.

Key Features

  • No CGO: 100% pure Go.
  • Protocol Agnostic Interface: Your application receives a unified InputEvent struct, regardless of whether the terminal used kitty, win32, or legacy protocols.
  • Accurate Modifiers: Accurately reports LeftCtrl vs RightCtrl, Alt, Shift, and the state of lock keys.
  • Zero-Dependency Core: Only depends on golang.org/x/sys and golang.org/x/term for putting the terminal into raw mode.

Usage

vtinput handles both the "activation" of these protocols (sending the correct initialization sequences to the terminal) and the parsing of the incoming byte stream.

package main

import (
	"fmt"
	"os"
	"github.com/unxed/vtinput"
)

func main() {
	// 1. Put terminal in Raw Mode and enable Kitty, Win32, Mouse, and Paste protocols
	restore, err := vtinput.Enable()
	if err != nil {
		panic(err)
	}
	defer restore() // Crucial: restores terminal to normal state on exit

	// 2. Create a reader wrapped around Stdin
	reader := vtinput.NewReader(os.Stdin)

	fmt.Print("\033[2J\033[H") // Clear screen
	fmt.Println("Listening for events. Press Ctrl+C to exit.\r\n")

	for {
		// 3. Block and wait for the next parsed event
		event, err := reader.ReadEvent()
		if err != nil {
			break
		}

		fmt.Printf("Received: %s\r\n", event.String())

		// Exit condition
		if event.Type == vtinput.KeyEventType && event.VirtualKeyCode == vtinput.VK_C && 
		   (event.ControlKeyState & vtinput.LeftCtrlPressed) != 0 {
			break
		}
	}
}

Testing & Diagnostics

The repository includes a diagnostic tool. Run it to see exactly what vtinput sees when you type:

go run ./cmd/input-check

Documentation

Index

Constants

View Source
const (
	NoKeyPressed     = winkeys.NoKeyPressed
	RightAltPressed  = winkeys.RightAltPressed
	LeftAltPressed   = winkeys.LeftAltPressed
	RightCtrlPressed = winkeys.RightCtrlPressed
	LeftCtrlPressed  = winkeys.LeftCtrlPressed
	ShiftPressed     = winkeys.ShiftPressed
	NumLockOn        = winkeys.NumLockOn
	ScrollLockOn     = winkeys.ScrollLockOn
	CapsLockOn       = winkeys.CapsLockOn
	EnhancedKey      = winkeys.EnhancedKey

	FromLeft1stButtonPressed = winkeys.FromLeft1stButtonPressed
	RightmostButtonPressed   = winkeys.RightmostButtonPressed
	FromLeft2ndButtonPressed = winkeys.FromLeft2ndButtonPressed
	FromLeft3rdButtonPressed = winkeys.FromLeft3rdButtonPressed
	FromLeft4thButtonPressed = winkeys.FromLeft4thButtonPressed

	MouseMoved    = winkeys.MouseMoved
	DoubleClick   = winkeys.DoubleClick
	MouseWheeled  = winkeys.MouseWheeled
	MouseHWheeled = winkeys.MouseHWheeled

	KeyEventType    = winkeys.KeyEventType
	MouseEventType  = winkeys.MouseEventType
	FocusEventType  = winkeys.FocusEventType
	PasteEventType  = winkeys.PasteEventType
	Far2lEventType  = winkeys.Far2lEventType
	ResizeEventType = winkeys.ResizeEventType
)

Re-export modifier and mouse constants.

View Source
const (
	VK_LBUTTON    = winkeys.VK_LBUTTON
	VK_RBUTTON    = winkeys.VK_RBUTTON
	VK_CANCEL     = winkeys.VK_CANCEL
	VK_MBUTTON    = winkeys.VK_MBUTTON
	VK_XBUTTON1   = winkeys.VK_XBUTTON1
	VK_XBUTTON2   = winkeys.VK_XBUTTON2
	VK_BACK       = winkeys.VK_BACK
	VK_TAB        = winkeys.VK_TAB
	VK_CLEAR      = winkeys.VK_CLEAR
	VK_RETURN     = winkeys.VK_RETURN
	VK_SHIFT      = winkeys.VK_SHIFT
	VK_CONTROL    = winkeys.VK_CONTROL
	VK_MENU       = winkeys.VK_MENU
	VK_PAUSE      = winkeys.VK_PAUSE
	VK_CAPITAL    = winkeys.VK_CAPITAL
	VK_KANA       = winkeys.VK_KANA
	VK_HANGUL     = winkeys.VK_HANGUL
	VK_IME_ON     = winkeys.VK_IME_ON
	VK_JUNJA      = winkeys.VK_JUNJA
	VK_FINAL      = winkeys.VK_FINAL
	VK_HANJA      = winkeys.VK_HANJA
	VK_KANJI      = winkeys.VK_KANJI
	VK_IME_OFF    = winkeys.VK_IME_OFF
	VK_CONVERT    = winkeys.VK_CONVERT
	VK_NONCONVERT = winkeys.VK_NONCONVERT
	VK_ACCEPT     = winkeys.VK_ACCEPT
	VK_MODECHANGE = winkeys.VK_MODECHANGE
	VK_ESCAPE     = winkeys.VK_ESCAPE
	VK_SPACE      = winkeys.VK_SPACE
	VK_PRIOR      = winkeys.VK_PRIOR
	VK_NEXT       = winkeys.VK_NEXT
	VK_END        = winkeys.VK_END
	VK_HOME       = winkeys.VK_HOME
	VK_LEFT       = winkeys.VK_LEFT
	VK_UP         = winkeys.VK_UP
	VK_RIGHT      = winkeys.VK_RIGHT
	VK_DOWN       = winkeys.VK_DOWN
	VK_SELECT     = winkeys.VK_SELECT
	VK_PRINT      = winkeys.VK_PRINT
	VK_EXECUTE    = winkeys.VK_EXECUTE
	VK_SNAPSHOT   = winkeys.VK_SNAPSHOT
	VK_INSERT     = winkeys.VK_INSERT
	VK_DELETE     = winkeys.VK_DELETE
	VK_HELP       = winkeys.VK_HELP

	VK_0 = winkeys.VK_0
	VK_1 = winkeys.VK_1
	VK_2 = winkeys.VK_2
	VK_3 = winkeys.VK_3
	VK_4 = winkeys.VK_4
	VK_5 = winkeys.VK_5
	VK_6 = winkeys.VK_6
	VK_7 = winkeys.VK_7
	VK_8 = winkeys.VK_8
	VK_9 = winkeys.VK_9

	VK_A = winkeys.VK_A
	VK_B = winkeys.VK_B
	VK_C = winkeys.VK_C
	VK_D = winkeys.VK_D
	VK_E = winkeys.VK_E
	VK_F = winkeys.VK_F
	VK_G = winkeys.VK_G
	VK_H = winkeys.VK_H
	VK_I = winkeys.VK_I
	VK_J = winkeys.VK_J
	VK_K = winkeys.VK_K
	VK_L = winkeys.VK_L
	VK_M = winkeys.VK_M
	VK_N = winkeys.VK_N
	VK_O = winkeys.VK_O
	VK_P = winkeys.VK_P
	VK_Q = winkeys.VK_Q
	VK_R = winkeys.VK_R
	VK_S = winkeys.VK_S
	VK_T = winkeys.VK_T
	VK_U = winkeys.VK_U
	VK_V = winkeys.VK_V
	VK_W = winkeys.VK_W
	VK_X = winkeys.VK_X
	VK_Y = winkeys.VK_Y
	VK_Z = winkeys.VK_Z

	VK_LWIN       = winkeys.VK_LWIN
	VK_RWIN       = winkeys.VK_RWIN
	VK_APPS       = winkeys.VK_APPS
	VK_SLEEP      = winkeys.VK_SLEEP
	VK_NUMPAD0    = winkeys.VK_NUMPAD0
	VK_NUMPAD1    = winkeys.VK_NUMPAD1
	VK_NUMPAD2    = winkeys.VK_NUMPAD2
	VK_NUMPAD3    = winkeys.VK_NUMPAD3
	VK_NUMPAD4    = winkeys.VK_NUMPAD4
	VK_NUMPAD5    = winkeys.VK_NUMPAD5
	VK_NUMPAD6    = winkeys.VK_NUMPAD6
	VK_NUMPAD7    = winkeys.VK_NUMPAD7
	VK_NUMPAD8    = winkeys.VK_NUMPAD8
	VK_NUMPAD9    = winkeys.VK_NUMPAD9
	VK_MULTIPLY   = winkeys.VK_MULTIPLY
	VK_ADD        = winkeys.VK_ADD
	VK_SEPARATOR  = winkeys.VK_SEPARATOR
	VK_SUBTRACT   = winkeys.VK_SUBTRACT
	VK_DECIMAL    = winkeys.VK_DECIMAL
	VK_DIVIDE     = winkeys.VK_DIVIDE
	VK_F1         = winkeys.VK_F1
	VK_F2         = winkeys.VK_F2
	VK_F3         = winkeys.VK_F3
	VK_F4         = winkeys.VK_F4
	VK_F5         = winkeys.VK_F5
	VK_F6         = winkeys.VK_F6
	VK_F7         = winkeys.VK_F7
	VK_F8         = winkeys.VK_F8
	VK_F9         = winkeys.VK_F9
	VK_F10        = winkeys.VK_F10
	VK_F11        = winkeys.VK_F11
	VK_F12        = winkeys.VK_F12
	VK_F13        = winkeys.VK_F13
	VK_F14        = winkeys.VK_F14
	VK_F15        = winkeys.VK_F15
	VK_F16        = winkeys.VK_F16
	VK_F17        = winkeys.VK_F17
	VK_F18        = winkeys.VK_F18
	VK_F19        = winkeys.VK_F19
	VK_F20        = winkeys.VK_F20
	VK_F21        = winkeys.VK_F21
	VK_F22        = winkeys.VK_F22
	VK_F23        = winkeys.VK_F23
	VK_F24        = winkeys.VK_F24
	VK_NUMLOCK    = winkeys.VK_NUMLOCK
	VK_SCROLL     = winkeys.VK_SCROLL
	VK_LSHIFT     = winkeys.VK_LSHIFT
	VK_RSHIFT     = winkeys.VK_RSHIFT
	VK_LCONTROL   = winkeys.VK_LCONTROL
	VK_RCONTROL   = winkeys.VK_RCONTROL
	VK_LMENU      = winkeys.VK_LMENU
	VK_RMENU      = winkeys.VK_RMENU
	VK_OEM_1      = winkeys.VK_OEM_1
	VK_OEM_PLUS   = winkeys.VK_OEM_PLUS
	VK_OEM_COMMA  = winkeys.VK_OEM_COMMA
	VK_OEM_MINUS  = winkeys.VK_OEM_MINUS
	VK_OEM_PERIOD = winkeys.VK_OEM_PERIOD
	VK_OEM_2      = winkeys.VK_OEM_2
	VK_OEM_3      = winkeys.VK_OEM_3
	VK_OEM_4      = winkeys.VK_OEM_4
	VK_OEM_5      = winkeys.VK_OEM_5
	VK_OEM_6      = winkeys.VK_OEM_6
	VK_OEM_7      = winkeys.VK_OEM_7
	VK_OEM_102    = winkeys.VK_OEM_102
	VK_UNASSIGNED = winkeys.VK_UNASSIGNED

	ScanCodeLeftShift  = winkeys.ScanCodeLeftShift
	ScanCodeRightShift = winkeys.ScanCodeRightShift
)

Forward virtual key code constants to the new winkeys package

Variables

View Source
var (
	// ErrInvalidSequence indicates the byte slice is not a valid Win32 input sequence.
	ErrInvalidSequence = errors.New("invalid win32 input sequence")
	// ErrIncomplete indicates the sequence might be valid but is incomplete (needs more bytes).
	ErrIncomplete = errors.New("incomplete sequence")
)
View Source
var InputMode string

InputMode defines the preferred input parser method. Valid values: "", "ansi", "winapi".

View Source
var Logger func(format string, a ...any)

Logger is a global function that can be set by the application to receive debug logs. This prevents vtinput from having a direct dependency on a specific logging implementation.

Functions

func Enable

func Enable() (func(), error)

Enable puts the terminal into Raw Mode and enables all supported protocols.

func EnableProtocols

func EnableProtocols(p Protocol) (func(), error)

EnableProtocols puts the terminal into Raw Mode and enables specific protocols.

func Log

func Log(format string, a ...any)

Log writes a debug message if the Logger is set.

func VKString

func VKString(vk uint16) string

VKString outputs mapped names from winkeys implementation

Types

type ControlKeyState

type ControlKeyState = winkeys.ControlKeyState

Re-export core types for backward compatibility using Go type aliases.

type EventType

type EventType = winkeys.EventType

type Far2lStack

type Far2lStack []byte

Far2lStack provides serialization and deserialization matching far2l's StackSerializer. It acts as a LIFO stack for popping (reading from the end) and a sequential buffer for pushing.

func (*Far2lStack) PopBytes

func (s *Far2lStack) PopBytes(n int) []byte

func (*Far2lStack) PopString

func (s *Far2lStack) PopString() string

func (*Far2lStack) PopU8

func (s *Far2lStack) PopU8() uint8

func (*Far2lStack) PopU16

func (s *Far2lStack) PopU16() uint16

func (*Far2lStack) PopU32

func (s *Far2lStack) PopU32() uint32

func (*Far2lStack) PopU64

func (s *Far2lStack) PopU64() uint64

func (*Far2lStack) PushBytes

func (s *Far2lStack) PushBytes(b []byte)

func (*Far2lStack) PushString

func (s *Far2lStack) PushString(str string)

func (*Far2lStack) PushU8

func (s *Far2lStack) PushU8(v uint8)

func (*Far2lStack) PushU16

func (s *Far2lStack) PushU16(v uint16)

func (*Far2lStack) PushU32

func (s *Far2lStack) PushU32(v uint32)

func (*Far2lStack) PushU64

func (s *Far2lStack) PushU64(v uint64)

type InputEvent

type InputEvent = winkeys.InputEvent

func ParseDSRReply

func ParseDSRReply(data []byte) (*InputEvent, int, error)

ParseDSRReply parses Device Status Report replies (ESC [ <params> n). These are not input events for the application, but must be consumed to keep the buffer clean.

func ParseFar2lAPC

func ParseFar2lAPC(data []byte) (*InputEvent, int, error)

ParseFar2lAPC parses Far2l specific APC sequences.

func ParseKitty

func ParseKitty(data []byte) (*InputEvent, int, error)

ParseKitty handles the Kitty Keyboard Protocol sequence format. Based on far2l's robust parsing logic and workarounds.

func ParseLegacyCSI

func ParseLegacyCSI(data []byte) (*InputEvent, int, error)

ParseLegacyCSI handles standard ANSI sequences (ESC [ ...).

func ParseLegacySS3

func ParseLegacySS3(data []byte) (*InputEvent, int, error)

ParseLegacySS3 handles standard SS3 sequences (ESC O ...). These are common for F1-F4 and Home/End on some terminals. Supports both simple (ESC O P) and modified (ESC O 3 P) formats.

func ParseMouseLegacy

func ParseMouseLegacy(data []byte) (*InputEvent, int, error)

ParseMouseLegacy handles standard X10 mouse sequences (ESC [ M Cb Cx Cy).

func ParseMouseSGR

func ParseMouseSGR(data []byte) (*InputEvent, int, error)

ParseMouseSGR handles modern SGR mouse sequences (ESC [ < ...).

func ParseMouseURXVT

func ParseMouseURXVT(data []byte) (*InputEvent, int, error)

ParseMouseURXVT handles URXVT 1015 mouse sequences (ESC [ Cb ; Cx ; Cy M).

func ParseWin32InputEvent

func ParseWin32InputEvent(data []byte) (*InputEvent, int, error)

ParseWin32InputEvent attempts to parse a byte slice containing a Win32 Input Mode sequence. Format: CSI Vk ; Sc ; Uc ; Kd ; Cs ; Rc _

Returns: - event: The parsed InputEvent (pointer). - n: The number of bytes consumed from the slice. - err: ErrInvalidSequence, ErrIncomplete, or nil.

type Protocol

type Protocol uint32

Protocol flags to selectively enable features.

const (
	Win32InputMode Protocol = 1 << iota
	KittyKeyboard
	MouseSupport
	FocusAndPaste
	Far2lExtensions

	// DefaultProtocols enables all supported features.
	DefaultProtocols = Win32InputMode | KittyKeyboard | MouseSupport | FocusAndPaste | Far2lExtensions
)

type Reader

type Reader struct {
	EventChan chan *InputEvent // need to be public for vtui

	MetricsEnabled bool
	// contains filtered or unexported fields
}

Reader reads input synchronously without a background goroutine. It measures the time from receiving raw bytes to generating an InputEvent.

func NewReader

func NewReader(in io.Reader, bypassReadEvent bool) *Reader

NewReader creates a synchronous input reader.

func (*Reader) Close

func (r *Reader) Close()

Close stops reading.

func (*Reader) GetEventChan

func (r *Reader) GetEventChan() chan *InputEvent

GetEventChan returns a channel that yields input events. It starts a background goroutine that calls ReadEvent() in a loop. The channel is closed when the reader is closed or an error occurs.

func (*Reader) Metrics

func (r *Reader) Metrics() (last time.Duration, avg time.Duration, count int64)

Metrics returns the last event latency, average latency, and total event count.

func (*Reader) ReadEvent

func (r *Reader) ReadEvent() (*InputEvent, error)

ReadEvent reads the next input event.

func (*Reader) ReadEventTimeout

func (r *Reader) ReadEventTimeout(timeout time.Duration) (*InputEvent, error)

ReadEventTimeout reads the next input event with an optional timeout.

Directories

Path Synopsis
cmd
input-check command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL