terminal_reader_windows.go

  1//go:build windows
  2// +build windows
  3
  4package uv
  5
  6import (
  7	"context"
  8	"errors"
  9	"fmt"
 10	"strings"
 11	"unicode"
 12	"unicode/utf16"
 13	"unicode/utf8"
 14
 15	"github.com/charmbracelet/x/ansi"
 16	xwindows "github.com/charmbracelet/x/windows"
 17	"github.com/muesli/cancelreader"
 18	"golang.org/x/sys/windows"
 19)
 20
 21// ReceiveEvents reads input events from the terminal and sends them to the
 22// given event channel.
 23func (d *TerminalReader) ReceiveEvents(ctx context.Context, events chan<- Event) error {
 24	for {
 25		evs, err := d.handleConInput(readConsoleInput)
 26		if errors.Is(err, errNotConInputReader) {
 27			return d.receiveEvents(ctx, events)
 28		}
 29		if err != nil {
 30			return fmt.Errorf("read coninput events: %w", err)
 31		}
 32		for _, ev := range evs {
 33			select {
 34			case <-ctx.Done():
 35				return nil
 36			case events <- ev:
 37			}
 38		}
 39	}
 40}
 41
 42var errNotConInputReader = fmt.Errorf("handleConInput: not a conInputReader")
 43
 44func (d *TerminalReader) handleConInput(
 45	finput func(windows.Handle, []xwindows.InputRecord) (uint32, error),
 46) ([]Event, error) {
 47	cc, ok := d.rd.(*conInputReader)
 48	if !ok {
 49		return nil, errNotConInputReader
 50	}
 51
 52	// read up to 256 events, this is to allow for sequences events reported as
 53	// key events.
 54	var events [256]xwindows.InputRecord
 55	_, err := finput(cc.conin, events[:])
 56	if err != nil {
 57		if cc.isCanceled() {
 58			return nil, cancelreader.ErrCanceled
 59		}
 60		return nil, fmt.Errorf("read coninput events: %w", err)
 61	}
 62
 63	var evs []Event
 64	for _, event := range events {
 65		if e := d.SequenceParser.parseConInputEvent(event, &d.keyState, d.MouseMode, d.logger); e != nil {
 66			if e == nil {
 67				continue
 68			}
 69			if multi, ok := e.(MultiEvent); ok {
 70				if d.logger != nil {
 71					for _, ev := range multi {
 72						d.logf("input: %T %v", ev, ev)
 73					}
 74				}
 75				evs = append(evs, multi...)
 76			} else {
 77				d.logf("input: %T %v", e, e)
 78				evs = append(evs, e)
 79			}
 80		}
 81	}
 82
 83	return evs, nil
 84}
 85
 86func (p *SequenceParser) parseConInputEvent(event xwindows.InputRecord, keyState *win32InputState, mouseMode *MouseMode, logger Logger) Event {
 87	switch event.EventType {
 88	case xwindows.KEY_EVENT:
 89		kevent := event.KeyEvent()
 90		return p.parseWin32InputKeyEvent(keyState, kevent.VirtualKeyCode, kevent.VirtualScanCode,
 91			kevent.Char, kevent.KeyDown, kevent.ControlKeyState, kevent.RepeatCount, logger)
 92
 93	case xwindows.WINDOW_BUFFER_SIZE_EVENT:
 94		wevent := event.WindowBufferSizeEvent()
 95		if wevent.Size.X != keyState.lastWinsizeX || wevent.Size.Y != keyState.lastWinsizeY {
 96			keyState.lastWinsizeX, keyState.lastWinsizeY = wevent.Size.X, wevent.Size.Y
 97			return WindowSizeEvent{
 98				Width:  int(wevent.Size.X),
 99				Height: int(wevent.Size.Y),
100			}
101		}
102	case xwindows.MOUSE_EVENT:
103		if mouseMode == nil || *mouseMode == 0 {
104			return nil
105		}
106		mevent := event.MouseEvent()
107		event := mouseEvent(keyState.lastMouseBtns, mevent)
108		// We emulate mouse mode levels on Windows. This is because Windows
109		// doesn't have a concept of different mouse modes. We use the mouse mode to determine
110		switch m := event.(type) {
111		case MouseMotionEvent:
112			if m.Button == MouseNone && (*mouseMode)&AllMouseMode == 0 {
113				return nil
114			}
115			if m.Button != MouseNone && (*mouseMode)&DragMouseMode == 0 {
116				return nil
117			}
118		}
119		keyState.lastMouseBtns = mevent.ButtonState
120		return event
121	case xwindows.FOCUS_EVENT:
122		fevent := event.FocusEvent()
123		if fevent.SetFocus {
124			return FocusEvent{}
125		}
126		return BlurEvent{}
127	case xwindows.MENU_EVENT:
128		// ignore
129	}
130	return nil
131}
132
133func mouseEventButton(p, s uint32) (MouseButton, bool) {
134	var isRelease bool
135	button := MouseNone
136	btn := p ^ s
137	if btn&s == 0 {
138		isRelease = true
139	}
140
141	if btn == 0 {
142		switch {
143		case s&xwindows.FROM_LEFT_1ST_BUTTON_PRESSED > 0:
144			button = MouseLeft
145		case s&xwindows.FROM_LEFT_2ND_BUTTON_PRESSED > 0:
146			button = MouseMiddle
147		case s&xwindows.RIGHTMOST_BUTTON_PRESSED > 0:
148			button = MouseRight
149		case s&xwindows.FROM_LEFT_3RD_BUTTON_PRESSED > 0:
150			button = MouseBackward
151		case s&xwindows.FROM_LEFT_4TH_BUTTON_PRESSED > 0:
152			button = MouseForward
153		}
154		return button, isRelease
155	}
156
157	switch btn {
158	case xwindows.FROM_LEFT_1ST_BUTTON_PRESSED: // left button
159		button = MouseLeft
160	case xwindows.RIGHTMOST_BUTTON_PRESSED: // right button
161		button = MouseRight
162	case xwindows.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button
163		button = MouseMiddle
164	case xwindows.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward)
165		button = MouseBackward
166	case xwindows.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward)
167		button = MouseForward
168	}
169
170	return button, isRelease
171}
172
173func mouseEvent(p uint32, e xwindows.MouseEventRecord) (ev Event) {
174	var mod KeyMod
175	var isRelease bool
176	if e.ControlKeyState&(xwindows.LEFT_ALT_PRESSED|xwindows.RIGHT_ALT_PRESSED) != 0 {
177		mod |= ModAlt
178	}
179	if e.ControlKeyState&(xwindows.LEFT_CTRL_PRESSED|xwindows.RIGHT_CTRL_PRESSED) != 0 {
180		mod |= ModCtrl
181	}
182	if e.ControlKeyState&(xwindows.SHIFT_PRESSED) != 0 {
183		mod |= ModShift
184	}
185
186	m := Mouse{
187		X:   int(e.MousePositon.X),
188		Y:   int(e.MousePositon.Y),
189		Mod: mod,
190	}
191
192	wheelDirection := int16(highWord(e.ButtonState)) //nolint:gosec
193	switch e.EventFlags {
194	case 0, xwindows.DOUBLE_CLICK:
195		m.Button, isRelease = mouseEventButton(p, e.ButtonState)
196	case xwindows.MOUSE_WHEELED:
197		if wheelDirection > 0 {
198			m.Button = MouseWheelUp
199		} else {
200			m.Button = MouseWheelDown
201		}
202	case xwindows.MOUSE_HWHEELED:
203		if wheelDirection > 0 {
204			m.Button = MouseWheelRight
205		} else {
206			m.Button = MouseWheelLeft
207		}
208	case xwindows.MOUSE_MOVED:
209		m.Button, _ = mouseEventButton(p, e.ButtonState)
210		return MouseMotionEvent(m)
211	}
212
213	if isWheel(m.Button) {
214		return MouseWheelEvent(m)
215	} else if isRelease {
216		return MouseReleaseEvent(m)
217	}
218
219	return MouseClickEvent(m)
220}
221
222func highWord(data uint32) uint16 {
223	return uint16((data & 0xFFFF0000) >> 16) //nolint:gosec
224}
225
226func readConsoleInput(console windows.Handle, inputRecords []xwindows.InputRecord) (uint32, error) {
227	if len(inputRecords) == 0 {
228		return 0, fmt.Errorf("size of input record buffer cannot be zero")
229	}
230
231	var read uint32
232
233	err := xwindows.ReadConsoleInput(console, &inputRecords[0], uint32(len(inputRecords)), &read) //nolint:gosec
234
235	return read, err //nolint:wrapcheck
236}
237
238//nolint:unused
239func peekConsoleInput(console windows.Handle, inputRecords []xwindows.InputRecord) (uint32, error) {
240	if len(inputRecords) == 0 {
241		return 0, fmt.Errorf("size of input record buffer cannot be zero")
242	}
243
244	var read uint32
245
246	err := xwindows.PeekConsoleInput(console, &inputRecords[0], uint32(len(inputRecords)), &read) //nolint:gosec
247
248	return read, err //nolint:wrapcheck
249}
250
251// parseWin32InputKeyEvent parses a single key event from either the Windows
252// Console API or win32-input-mode events. When state is nil, it means this is
253// an event from win32-input-mode. Otherwise, it's a key event from the Windows
254// Console API and needs a state to decode ANSI escape sequences and utf16
255// runes.
256func (p *SequenceParser) parseWin32InputKeyEvent(state *win32InputState, vkc uint16, _ uint16, r rune, keyDown bool, cks uint32, repeatCount uint16, logger Logger) (event Event) {
257	defer func() {
258		// Respect the repeat count.
259		if repeatCount > 1 {
260			var multi MultiEvent
261			for i := 0; i < int(repeatCount); i++ {
262				multi = append(multi, event)
263			}
264			event = multi
265		}
266	}()
267	if state != nil {
268		defer func() {
269			state.lastCks = cks
270		}()
271	}
272
273	var utf8Buf [utf8.UTFMax]byte
274	var key Key
275	if state != nil && state.utf16Half {
276		state.utf16Half = false
277		state.utf16Buf[1] = r
278		codepoint := utf16.DecodeRune(state.utf16Buf[0], state.utf16Buf[1])
279		rw := utf8.EncodeRune(utf8Buf[:], codepoint)
280		r, _ = utf8.DecodeRune(utf8Buf[:rw])
281		key.Code = r
282		key.Text = string(r)
283		key.Mod = translateControlKeyState(cks)
284		key = ensureKeyCase(key, cks)
285		if keyDown {
286			return KeyPressEvent(key)
287		}
288		return KeyReleaseEvent(key)
289	}
290
291	var baseCode rune
292	switch {
293	case vkc == 0:
294		// Zero means this event is either an escape code or a unicode
295		// codepoint.
296		if state != nil && state.ansiIdx == 0 && r != ansi.ESC {
297			if logger != nil {
298				logger.Printf("input: received unicode codepoint instead of sequence %q", r)
299			}
300			// This is a unicode codepoint.
301			baseCode = r
302			break
303		}
304
305		if state != nil {
306			// Collect ANSI escape code.
307			state.ansiBuf[state.ansiIdx] = byte(r)
308			state.ansiIdx++
309			if state.ansiIdx <= 2 {
310				// We haven't received enough bytes to determine if this is an
311				// ANSI escape code.
312				return nil
313			}
314			if r == ansi.ESC {
315				// We're expecting a closing String Terminator [ansi.ST].
316				return nil
317			}
318
319			n, event := p.parseSequence(state.ansiBuf[:state.ansiIdx])
320			if n == 0 {
321				return nil
322			}
323			if _, ok := event.(UnknownEvent); ok {
324				return nil
325			}
326
327			if logger != nil {
328				logger.Printf("input: parsed sequence %q, %d bytes", state.ansiBuf[:n], n)
329			}
330
331			state.ansiIdx = 0
332			return event
333		}
334	case vkc == xwindows.VK_BACK:
335		baseCode = KeyBackspace
336	case vkc == xwindows.VK_TAB:
337		baseCode = KeyTab
338	case vkc == xwindows.VK_RETURN:
339		baseCode = KeyEnter
340	case vkc == xwindows.VK_SHIFT:
341		//nolint:nestif
342		if cks&xwindows.SHIFT_PRESSED != 0 {
343			if cks&xwindows.ENHANCED_KEY != 0 {
344				baseCode = KeyRightShift
345			} else {
346				baseCode = KeyLeftShift
347			}
348		} else if state != nil {
349			if state.lastCks&xwindows.SHIFT_PRESSED != 0 {
350				if state.lastCks&xwindows.ENHANCED_KEY != 0 {
351					baseCode = KeyRightShift
352				} else {
353					baseCode = KeyLeftShift
354				}
355			}
356		}
357	case vkc == xwindows.VK_CONTROL:
358		if cks&xwindows.LEFT_CTRL_PRESSED != 0 {
359			baseCode = KeyLeftCtrl
360		} else if cks&xwindows.RIGHT_CTRL_PRESSED != 0 {
361			baseCode = KeyRightCtrl
362		} else if state != nil {
363			if state.lastCks&xwindows.LEFT_CTRL_PRESSED != 0 {
364				baseCode = KeyLeftCtrl
365			} else if state.lastCks&xwindows.RIGHT_CTRL_PRESSED != 0 {
366				baseCode = KeyRightCtrl
367			}
368		}
369	case vkc == xwindows.VK_MENU:
370		if cks&xwindows.LEFT_ALT_PRESSED != 0 {
371			baseCode = KeyLeftAlt
372		} else if cks&xwindows.RIGHT_ALT_PRESSED != 0 {
373			baseCode = KeyRightAlt
374		} else if state != nil {
375			if state.lastCks&xwindows.LEFT_ALT_PRESSED != 0 {
376				baseCode = KeyLeftAlt
377			} else if state.lastCks&xwindows.RIGHT_ALT_PRESSED != 0 {
378				baseCode = KeyRightAlt
379			}
380		}
381	case vkc == xwindows.VK_PAUSE:
382		baseCode = KeyPause
383	case vkc == xwindows.VK_CAPITAL:
384		baseCode = KeyCapsLock
385	case vkc == xwindows.VK_ESCAPE:
386		baseCode = KeyEscape
387	case vkc == xwindows.VK_SPACE:
388		baseCode = KeySpace
389	case vkc == xwindows.VK_PRIOR:
390		baseCode = KeyPgUp
391	case vkc == xwindows.VK_NEXT:
392		baseCode = KeyPgDown
393	case vkc == xwindows.VK_END:
394		baseCode = KeyEnd
395	case vkc == xwindows.VK_HOME:
396		baseCode = KeyHome
397	case vkc == xwindows.VK_LEFT:
398		baseCode = KeyLeft
399	case vkc == xwindows.VK_UP:
400		baseCode = KeyUp
401	case vkc == xwindows.VK_RIGHT:
402		baseCode = KeyRight
403	case vkc == xwindows.VK_DOWN:
404		baseCode = KeyDown
405	case vkc == xwindows.VK_SELECT:
406		baseCode = KeySelect
407	case vkc == xwindows.VK_SNAPSHOT:
408		baseCode = KeyPrintScreen
409	case vkc == xwindows.VK_INSERT:
410		baseCode = KeyInsert
411	case vkc == xwindows.VK_DELETE:
412		baseCode = KeyDelete
413	case vkc >= '0' && vkc <= '9':
414		baseCode = rune(vkc)
415	case vkc >= 'A' && vkc <= 'Z':
416		// Convert to lowercase.
417		baseCode = rune(vkc) + 32
418	case vkc == xwindows.VK_LWIN:
419		baseCode = KeyLeftSuper
420	case vkc == xwindows.VK_RWIN:
421		baseCode = KeyRightSuper
422	case vkc == xwindows.VK_APPS:
423		baseCode = KeyMenu
424	case vkc >= xwindows.VK_NUMPAD0 && vkc <= xwindows.VK_NUMPAD9:
425		baseCode = rune(vkc-xwindows.VK_NUMPAD0) + KeyKp0
426	case vkc == xwindows.VK_MULTIPLY:
427		baseCode = KeyKpMultiply
428	case vkc == xwindows.VK_ADD:
429		baseCode = KeyKpPlus
430	case vkc == xwindows.VK_SEPARATOR:
431		baseCode = KeyKpComma
432	case vkc == xwindows.VK_SUBTRACT:
433		baseCode = KeyKpMinus
434	case vkc == xwindows.VK_DECIMAL:
435		baseCode = KeyKpDecimal
436	case vkc == xwindows.VK_DIVIDE:
437		baseCode = KeyKpDivide
438	case vkc >= xwindows.VK_F1 && vkc <= xwindows.VK_F24:
439		baseCode = rune(vkc-xwindows.VK_F1) + KeyF1
440	case vkc == xwindows.VK_NUMLOCK:
441		baseCode = KeyNumLock
442	case vkc == xwindows.VK_SCROLL:
443		baseCode = KeyScrollLock
444	case vkc == xwindows.VK_LSHIFT:
445		baseCode = KeyLeftShift
446	case vkc == xwindows.VK_RSHIFT:
447		baseCode = KeyRightShift
448	case vkc == xwindows.VK_LCONTROL:
449		baseCode = KeyLeftCtrl
450	case vkc == xwindows.VK_RCONTROL:
451		baseCode = KeyRightCtrl
452	case vkc == xwindows.VK_LMENU:
453		baseCode = KeyLeftAlt
454	case vkc == xwindows.VK_RMENU:
455		baseCode = KeyRightAlt
456	case vkc == xwindows.VK_VOLUME_MUTE:
457		baseCode = KeyMute
458	case vkc == xwindows.VK_VOLUME_DOWN:
459		baseCode = KeyLowerVol
460	case vkc == xwindows.VK_VOLUME_UP:
461		baseCode = KeyRaiseVol
462	case vkc == xwindows.VK_MEDIA_NEXT_TRACK:
463		baseCode = KeyMediaNext
464	case vkc == xwindows.VK_MEDIA_PREV_TRACK:
465		baseCode = KeyMediaPrev
466	case vkc == xwindows.VK_MEDIA_STOP:
467		baseCode = KeyMediaStop
468	case vkc == xwindows.VK_MEDIA_PLAY_PAUSE:
469		baseCode = KeyMediaPlayPause
470	case vkc == xwindows.VK_OEM_1:
471		baseCode = ';'
472	case vkc == xwindows.VK_OEM_PLUS:
473		baseCode = '+'
474	case vkc == xwindows.VK_OEM_COMMA:
475		baseCode = ','
476	case vkc == xwindows.VK_OEM_MINUS:
477		baseCode = '-'
478	case vkc == xwindows.VK_OEM_PERIOD:
479		baseCode = '.'
480	case vkc == xwindows.VK_OEM_2:
481		baseCode = '/'
482	case vkc == xwindows.VK_OEM_3:
483		baseCode = '`'
484	case vkc == xwindows.VK_OEM_4:
485		baseCode = '['
486	case vkc == xwindows.VK_OEM_5:
487		baseCode = '\\'
488	case vkc == xwindows.VK_OEM_6:
489		baseCode = ']'
490	case vkc == xwindows.VK_OEM_7:
491		baseCode = '\''
492	}
493
494	if utf16.IsSurrogate(r) {
495		if state != nil {
496			state.utf16Buf[0] = r
497			state.utf16Half = true
498		}
499		return nil
500	}
501
502	// AltGr is left ctrl + right alt. On non-US keyboards, this is used to type
503	// special characters and produce printable events.
504	// XXX: Should this be a KeyMod?
505	altGr := cks&(xwindows.LEFT_CTRL_PRESSED|xwindows.RIGHT_ALT_PRESSED) == xwindows.LEFT_CTRL_PRESSED|xwindows.RIGHT_ALT_PRESSED
506
507	var text string
508	keyCode := baseCode
509	if !unicode.IsControl(r) {
510		rw := utf8.EncodeRune(utf8Buf[:], r)
511		keyCode, _ = utf8.DecodeRune(utf8Buf[:rw])
512		if unicode.IsPrint(keyCode) && (cks == 0 ||
513			cks == xwindows.SHIFT_PRESSED ||
514			cks == xwindows.CAPSLOCK_ON ||
515			altGr) {
516			// If the control key state is 0, shift is pressed, or caps lock
517			// then the key event is a printable event i.e. [text] is not empty.
518			text = string(keyCode)
519		}
520	}
521
522	key.Code = keyCode
523	key.Text = text
524	key.Mod = translateControlKeyState(cks)
525	key.BaseCode = baseCode
526	key = ensureKeyCase(key, cks)
527	if keyDown {
528		return KeyPressEvent(key)
529	}
530
531	return KeyReleaseEvent(key)
532}
533
534// ensureKeyCase ensures that the key's text is in the correct case based on the
535// control key state.
536func ensureKeyCase(key Key, cks uint32) Key {
537	if len(key.Text) == 0 {
538		return key
539	}
540
541	hasShift := cks&xwindows.SHIFT_PRESSED != 0
542	hasCaps := cks&xwindows.CAPSLOCK_ON != 0
543	if hasShift || hasCaps {
544		if unicode.IsLower(key.Code) {
545			key.ShiftedCode = unicode.ToUpper(key.Code)
546			key.Text = string(key.ShiftedCode)
547		}
548	} else {
549		if unicode.IsUpper(key.Code) {
550			key.ShiftedCode = unicode.ToLower(key.Code)
551			key.Text = string(key.ShiftedCode)
552		}
553	}
554
555	return key
556}
557
558// translateControlKeyState translates the control key state from the Windows
559// Console API into a Mod bitmask.
560func translateControlKeyState(cks uint32) (m KeyMod) {
561	if cks&xwindows.LEFT_CTRL_PRESSED != 0 || cks&xwindows.RIGHT_CTRL_PRESSED != 0 {
562		m |= ModCtrl
563	}
564	if cks&xwindows.LEFT_ALT_PRESSED != 0 || cks&xwindows.RIGHT_ALT_PRESSED != 0 {
565		m |= ModAlt
566	}
567	if cks&xwindows.SHIFT_PRESSED != 0 {
568		m |= ModShift
569	}
570	if cks&xwindows.CAPSLOCK_ON != 0 {
571		m |= ModCapsLock
572	}
573	if cks&xwindows.NUMLOCK_ON != 0 {
574		m |= ModNumLock
575	}
576	if cks&xwindows.SCROLLLOCK_ON != 0 {
577		m |= ModScrollLock
578	}
579	return
580}
581
582//nolint:unused
583func keyEventString(vkc, sc uint16, r rune, keyDown bool, cks uint32, repeatCount uint16) string {
584	var s strings.Builder
585	s.WriteString("vkc: ")
586	s.WriteString(fmt.Sprintf("%d, 0x%02x", vkc, vkc))
587	s.WriteString(", sc: ")
588	s.WriteString(fmt.Sprintf("%d, 0x%02x", sc, sc))
589	s.WriteString(", r: ")
590	s.WriteString(fmt.Sprintf("%q", r))
591	s.WriteString(", down: ")
592	s.WriteString(fmt.Sprintf("%v", keyDown))
593	s.WriteString(", cks: [")
594	if cks&xwindows.LEFT_ALT_PRESSED != 0 {
595		s.WriteString("left alt, ")
596	}
597	if cks&xwindows.RIGHT_ALT_PRESSED != 0 {
598		s.WriteString("right alt, ")
599	}
600	if cks&xwindows.LEFT_CTRL_PRESSED != 0 {
601		s.WriteString("left ctrl, ")
602	}
603	if cks&xwindows.RIGHT_CTRL_PRESSED != 0 {
604		s.WriteString("right ctrl, ")
605	}
606	if cks&xwindows.SHIFT_PRESSED != 0 {
607		s.WriteString("shift, ")
608	}
609	if cks&xwindows.CAPSLOCK_ON != 0 {
610		s.WriteString("caps lock, ")
611	}
612	if cks&xwindows.NUMLOCK_ON != 0 {
613		s.WriteString("num lock, ")
614	}
615	if cks&xwindows.SCROLLLOCK_ON != 0 {
616		s.WriteString("scroll lock, ")
617	}
618	if cks&xwindows.ENHANCED_KEY != 0 {
619		s.WriteString("enhanced key, ")
620	}
621	s.WriteString("], repeat count: ")
622	s.WriteString(fmt.Sprintf("%d", repeatCount))
623	return s.String()
624}