| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- //go:build windows
- // +build windows
- package input
- import (
- "errors"
- "fmt"
- "unicode/utf16"
- "github.com/charmbracelet/x/ansi"
- termwindows "github.com/charmbracelet/x/windows"
- "github.com/erikgeiser/coninput"
- "golang.org/x/sys/windows"
- )
- // ReadEvents reads input events from the terminal.
- //
- // It reads the events available in the input buffer and returns them.
- func (d *Driver) ReadEvents() ([]Event, error) {
- events, err := d.handleConInput(coninput.ReadConsoleInput)
- if errors.Is(err, errNotConInputReader) {
- return d.readEvents()
- }
- return events, err
- }
- var errNotConInputReader = fmt.Errorf("handleConInput: not a conInputReader")
- func (d *Driver) handleConInput(
- finput func(windows.Handle, []coninput.InputRecord) (uint32, error),
- ) ([]Event, error) {
- cc, ok := d.rd.(*conInputReader)
- if !ok {
- return nil, errNotConInputReader
- }
- // read up to 256 events, this is to allow for sequences events reported as
- // key events.
- var events [256]coninput.InputRecord
- _, err := finput(cc.conin, events[:])
- if err != nil {
- return nil, fmt.Errorf("read coninput events: %w", err)
- }
- var evs []Event
- for _, event := range events {
- if e := parseConInputEvent(event, &d.prevMouseState, &d.lastWinsizeEvent); e != nil {
- evs = append(evs, e)
- }
- }
- return d.detectConInputQuerySequences(evs), nil
- }
- // Using ConInput API, Windows Terminal responds to sequence query events with
- // KEY_EVENT_RECORDs so we need to collect them and parse them as a single
- // sequence.
- // Is this a hack?
- func (d *Driver) detectConInputQuerySequences(events []Event) []Event {
- var newEvents []Event
- start, end := -1, -1
- loop:
- for i, e := range events {
- switch e := e.(type) {
- case KeyPressEvent:
- switch e.Rune {
- case ansi.ESC, ansi.CSI, ansi.OSC, ansi.DCS, ansi.APC:
- // start of a sequence
- if start == -1 {
- start = i
- }
- }
- default:
- break loop
- }
- end = i
- }
- if start == -1 || end <= start {
- return events
- }
- var seq []byte
- for i := start; i <= end; i++ {
- switch e := events[i].(type) {
- case KeyPressEvent:
- seq = append(seq, byte(e.Rune))
- }
- }
- n, seqevent := ParseSequence(seq)
- switch seqevent.(type) {
- case UnknownEvent:
- // We're not interested in unknown events
- default:
- if start+n > len(events) {
- return events
- }
- newEvents = events[:start]
- newEvents = append(newEvents, seqevent)
- newEvents = append(newEvents, events[start+n:]...)
- return d.detectConInputQuerySequences(newEvents)
- }
- return events
- }
- func parseConInputEvent(event coninput.InputRecord, ps *coninput.ButtonState, ws *coninput.WindowBufferSizeEventRecord) Event {
- switch e := event.Unwrap().(type) {
- case coninput.KeyEventRecord:
- event := parseWin32InputKeyEvent(e.VirtualKeyCode, e.VirtualScanCode,
- e.Char, e.KeyDown, e.ControlKeyState, e.RepeatCount)
- var key Key
- switch event := event.(type) {
- case KeyPressEvent:
- key = Key(event)
- case KeyReleaseEvent:
- key = Key(event)
- default:
- return nil
- }
- // If the key is not printable, return the event as is
- // (e.g. function keys, arrows, etc.)
- // Otherwise, try to translate it to a rune based on the active keyboard
- // layout.
- if key.Rune == 0 {
- return event
- }
- // Always use US layout for translation
- // This is to follow the behavior of the Kitty Keyboard base layout
- // feature :eye_roll:
- // https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-language-pack-default-values?view=windows-11
- const usLayout = 0x409
- // Translate key to rune
- var keyState [256]byte
- var utf16Buf [16]uint16
- const dontChangeKernelKeyboardLayout = 0x4
- ret := termwindows.ToUnicodeEx(
- uint32(e.VirtualKeyCode),
- uint32(e.VirtualScanCode),
- &keyState[0],
- &utf16Buf[0],
- int32(len(utf16Buf)),
- dontChangeKernelKeyboardLayout,
- usLayout,
- )
- // -1 indicates a dead key
- // 0 indicates no translation for this key
- if ret < 1 {
- return event
- }
- runes := utf16.Decode(utf16Buf[:ret])
- if len(runes) != 1 {
- // Key doesn't translate to a single rune
- return event
- }
- key.baseRune = runes[0]
- if e.KeyDown {
- return KeyPressEvent(key)
- }
- return KeyReleaseEvent(key)
- case coninput.WindowBufferSizeEventRecord:
- if e != *ws {
- *ws = e
- return WindowSizeEvent{
- Width: int(e.Size.X),
- Height: int(e.Size.Y),
- }
- }
- case coninput.MouseEventRecord:
- mevent := mouseEvent(*ps, e)
- *ps = e.ButtonState
- return mevent
- case coninput.FocusEventRecord, coninput.MenuEventRecord:
- // ignore
- }
- return nil
- }
- func mouseEventButton(p, s coninput.ButtonState) (button MouseButton, isRelease bool) {
- btn := p ^ s
- if btn&s == 0 {
- isRelease = true
- }
- if btn == 0 {
- switch {
- case s&coninput.FROM_LEFT_1ST_BUTTON_PRESSED > 0:
- button = MouseLeft
- case s&coninput.FROM_LEFT_2ND_BUTTON_PRESSED > 0:
- button = MouseMiddle
- case s&coninput.RIGHTMOST_BUTTON_PRESSED > 0:
- button = MouseRight
- case s&coninput.FROM_LEFT_3RD_BUTTON_PRESSED > 0:
- button = MouseBackward
- case s&coninput.FROM_LEFT_4TH_BUTTON_PRESSED > 0:
- button = MouseForward
- }
- return
- }
- switch btn {
- case coninput.FROM_LEFT_1ST_BUTTON_PRESSED: // left button
- button = MouseLeft
- case coninput.RIGHTMOST_BUTTON_PRESSED: // right button
- button = MouseRight
- case coninput.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button
- button = MouseMiddle
- case coninput.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward)
- button = MouseBackward
- case coninput.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward)
- button = MouseForward
- }
- return
- }
- func mouseEvent(p coninput.ButtonState, e coninput.MouseEventRecord) (ev Event) {
- var mod KeyMod
- var isRelease bool
- if e.ControlKeyState.Contains(coninput.LEFT_ALT_PRESSED | coninput.RIGHT_ALT_PRESSED) {
- mod |= ModAlt
- }
- if e.ControlKeyState.Contains(coninput.LEFT_CTRL_PRESSED | coninput.RIGHT_CTRL_PRESSED) {
- mod |= ModCtrl
- }
- if e.ControlKeyState.Contains(coninput.SHIFT_PRESSED) {
- mod |= ModShift
- }
- m := Mouse{
- X: int(e.MousePositon.X),
- Y: int(e.MousePositon.Y),
- Mod: mod,
- }
- switch e.EventFlags {
- case coninput.CLICK, coninput.DOUBLE_CLICK:
- m.Button, isRelease = mouseEventButton(p, e.ButtonState)
- case coninput.MOUSE_WHEELED:
- if e.WheelDirection > 0 {
- m.Button = MouseWheelUp
- } else {
- m.Button = MouseWheelDown
- }
- case coninput.MOUSE_HWHEELED:
- if e.WheelDirection > 0 {
- m.Button = MouseWheelRight
- } else {
- m.Button = MouseWheelLeft
- }
- case coninput.MOUSE_MOVED:
- m.Button, _ = mouseEventButton(p, e.ButtonState)
- return MouseMotionEvent(m)
- }
- if isWheel(m.Button) {
- return MouseWheelEvent(m)
- } else if isRelease {
- return MouseReleaseEvent(m)
- }
- return MouseClickEvent(m)
- }
|