okami.mom/ultraviolet

git clone https://git.nkp.pet/okami.mom/ultraviolet

nekomimi.petfix(input): keep control strings whole when they span reads7bfbaab

main
53.9 KiB2117 linesraw
1package uv
2
3import (
4	"bytes"
5	"encoding/base64"
6	"encoding/hex"
7	"fmt"
8	"image/color"
9	"math"
10	"slices"
11	"strings"
12	"unicode"
13	"unicode/utf8"
14
15	"github.com/charmbracelet/x/ansi"
16	"github.com/charmbracelet/x/ansi/parser"
17	xwindows "github.com/charmbracelet/x/windows"
18	"github.com/rivo/uniseg"
19)
20
21// Flags to control the behavior of the parser.
22const (
23	// When this flag is set, the driver will treat both Ctrl+Space and Ctrl+@
24	// as the same key sequence.
25	//
26	// Historically, the ANSI specs generate NUL (0x00) on both the Ctrl+Space
27	// and Ctrl+@ key sequences. This flag allows the driver to treat both as
28	// the same key sequence.
29	flagCtrlAt = 1 << iota
30
31	// When this flag is set, the driver will treat the Tab key and Ctrl+I as
32	// the same key sequence.
33	//
34	// Historically, the ANSI specs generate HT (0x09) on both the Tab key and
35	// Ctrl+I. This flag allows the driver to treat both as the same key
36	// sequence.
37	flagCtrlI
38
39	// When this flag is set, the driver will treat the Enter key and Ctrl+M as
40	// the same key sequence.
41	//
42	// Historically, the ANSI specs generate CR (0x0D) on both the Enter key
43	// and Ctrl+M. This flag allows the driver to treat both as the same key.
44	flagCtrlM
45
46	// When this flag is set, the driver will treat Escape and Ctrl+[ as
47	// the same key sequence.
48	//
49	// Historically, the ANSI specs generate ESC (0x1B) on both the Escape key
50	// and Ctrl+[. This flag allows the driver to treat both as the same key
51	// sequence.
52	flagCtrlOpenBracket
53
54	// When this flag is set, the driver will send a BS (0x08 byte) character
55	// instead of a DEL (0x7F byte) character when the Backspace key is
56	// pressed.
57	//
58	// The VT100 terminal has both a Backspace and a Delete key. The VT220
59	// terminal dropped the Backspace key and replaced it with the Delete key.
60	// Both terminals send a DEL character when the Delete key is pressed.
61	// Modern terminals and PCs later readded the Delete key but used a
62	// different key sequence, and the Backspace key was standardized to send a
63	// DEL character.
64	flagBackspace
65
66	// When this flag is set, the driver will recognize the Find key instead of
67	// treating it as a Home key.
68	//
69	// The Find key was part of the VT220 keyboard, and is no longer used in
70	// modern day PCs.
71	flagFind
72
73	// When this flag is set, the driver will recognize the Select key instead
74	// of treating it as a End key.
75	//
76	// The Symbol key was part of the VT220 keyboard, and is no longer used in
77	// modern day PCs.
78	flagSelect
79
80	// When this flag is set, the driver will preserve function keys (F13-F63)
81	// as symbols.
82	//
83	// Since these keys are not part of today's standard 20th century keyboard,
84	// we treat them as F1-F12 modifier keys i.e. ctrl/shift/alt + Fn combos.
85	// Key definitions come from Terminfo, this flag is only useful when
86	// FlagTerminfo is not set.
87	flagFKeys
88)
89
90// LegacyKeyEncoding is a set of flags that control the behavior of legacy
91// terminal key encodings. Historically, ctrl+<key> input events produce
92// control characters (0x00-0x1F) that collide with some special keys like Tab,
93// Enter, and Escape. This controls the expected behavior of encoding these
94// keys.
95//
96// This type has the following default values:
97//   - CtrlAt maps 0x00 [ansi.NUL] to ctrl+space instead of ctrl+@.
98//   - CtrlI maps 0x09 [ansi.HT] to the tab key instead of ctrl+i.
99//   - CtrlM maps 0x0D [ansi.CR] to the enter key instead of ctrl+m.
100//   - CtrlOpenBracket maps 0x1B [ansi.ESC] to the escape key instead of ctrl+[.
101//   - Backspace maps the backspace key to 0x08 [ansi.BS] instead of 0x7F [ansi.DEL].
102//   - Find maps the legacy find key to the home key.
103//   - Select maps the legacy select key to the end key.
104//   - FKeys maps high function keys instead of treating them as Function+<modifiers>.
105type LegacyKeyEncoding uint32
106
107// CtrlAt returns a [LegacyKeyEncoding] with whether [ansi.NUL] (0x00)
108// is mapped to ctrl+at instead of ctrl+space.
109func (l LegacyKeyEncoding) CtrlAt(v bool) LegacyKeyEncoding {
110	if v {
111		l |= flagCtrlAt
112	} else {
113		l &^= flagCtrlAt
114	}
115	return l
116}
117
118// CtrlI returns a [LegacyKeyEncoding] with whether [ansi.HT] (0x09)
119// is mapped to ctrl+i instead of the tab key.
120func (l LegacyKeyEncoding) CtrlI(v bool) LegacyKeyEncoding {
121	if v {
122		l |= flagCtrlI
123	} else {
124		l &^= flagCtrlI
125	}
126	return l
127}
128
129// CtrlM returns a [LegacyKeyEncoding] with whether [ansi.CR] (0x0D)
130// is mapped to ctrl+m instead of the enter key.
131func (l LegacyKeyEncoding) CtrlM(v bool) LegacyKeyEncoding {
132	if v {
133		l |= flagCtrlM
134	} else {
135		l &^= flagCtrlM
136	}
137	return l
138}
139
140// CtrlOpenBracket returns a [LegacyKeyEncoding] with whether [ansi.ESC] (0x1B)
141// is mapped to ctrl+[ instead of the escape key.
142func (l LegacyKeyEncoding) CtrlOpenBracket(v bool) LegacyKeyEncoding {
143	if v {
144		l |= flagCtrlOpenBracket
145	} else {
146		l &^= flagCtrlOpenBracket
147	}
148	return l
149}
150
151// Backspace returns a [LegacyKeyEncoding] with whether the backspace key is
152// mapped to [ansi.BS] (0x08) instead of [ansi.DEL] (0x7F).
153func (l LegacyKeyEncoding) Backspace(v bool) LegacyKeyEncoding {
154	if v {
155		l |= flagBackspace
156	} else {
157		l &^= flagBackspace
158	}
159	return l
160}
161
162// Find returns a [LegacyKeyEncoding] with whether the legacy find key
163// is mapped to the home key.
164func (l LegacyKeyEncoding) Find(v bool) LegacyKeyEncoding {
165	if v {
166		l |= flagFind
167	} else {
168		l &^= flagFind
169	}
170	return l
171}
172
173// Select returns a [LegacyKeyEncoding] with whether the legacy select key
174// is mapped to the end key.
175func (l LegacyKeyEncoding) Select(v bool) LegacyKeyEncoding {
176	if v {
177		l |= flagSelect
178	} else {
179		l &^= flagSelect
180	}
181	return l
182}
183
184// FKeys returns a [LegacyKeyEncoding] with whether high function keys are
185// mapped to high function keys (beyond F20) instead of treating them as
186// Function+<modifiers> keys.
187func (l LegacyKeyEncoding) FKeys(v bool) LegacyKeyEncoding {
188	if v {
189		l |= flagFKeys
190	} else {
191		l &^= flagFKeys
192	}
193	return l
194}
195
196// EventDecoder decodes terminal input events from a byte buffer. Terminal
197// input events are typically encoded as Unicode or ASCII characters, control
198// codes, or ANSI escape sequences.
199type EventDecoder struct {
200	// Legacy is the legacy key encoding flags. These flags control the
201	// behavior of legacy terminal key encodings. See [LegacyKeyEncoding] for
202	// more details.
203	Legacy LegacyKeyEncoding
204	// UseTerminfo is a flag that controls whether to use the terminal type
205	// Terminfo database to map escape sequences to key events. This will
206	// override the default key sequences handled by the parser.
207	UseTerminfo bool
208
209	lastCks uint32 // the last control key state for the previous key event record.
210}
211
212// Decode finds the first recognized event sequence and returns it along
213// with its length.
214//
215// It will return zero and nil no sequence is recognized or when the buffer is
216// empty. If a sequence is not supported, an [UnknownEvent] is returned.
217//
218// Example:
219//
220//	```go
221//	var decoder EventDecoder
222//	var events []Event
223//	buf := []byte("\x00" + // Ctrl+Space
224//	    "\x1b[A" + // Up arrow
225//	    "Hello") // Text input
226//	for len(buf) > 0 {
227//	    n, ev := decoder.Decode(buf)
228//	    if ev != nil {
229//	        events = append(events, ev)
230//	    }
231//	    buf = buf[n:]
232//	}
233//	```
234func (p *EventDecoder) Decode(buf []byte) (n int, Event Event) {
235	if len(buf) == 0 {
236		return 0, nil
237	}
238
239	switch b := buf[0]; b {
240	case ansi.ESC:
241		if len(buf) == 1 {
242			// Escape key
243			return 1, KeyPressEvent{Code: KeyEscape}
244		}
245
246		switch bPrime := buf[1]; bPrime {
247		case 'O': // Esc-prefixed SS3
248			return p.parseSs3(buf)
249		case 'P': // Esc-prefixed DCS
250			return p.parseDcs(buf)
251		case '[': // Esc-prefixed CSI
252			return p.parseCsi(buf)
253		case ']': // Esc-prefixed OSC
254			return p.parseOsc(buf)
255		case '_': // Esc-prefixed APC
256			return p.parseApc(buf)
257		case '^': // Esc-prefixed PM
258			return p.parseStTerminated(ansi.PM, '^', nil)(buf)
259		case 'X': // Esc-prefixed SOS
260			return p.parseStTerminated(ansi.SOS, 'X', nil)(buf)
261		default:
262			n, e := p.Decode(buf[1:])
263			if k, ok := e.(KeyPressEvent); ok {
264				k.Text = ""
265				k.Mod |= ModAlt
266				return n + 1, k
267			}
268
269			// Not a key sequence, nor an alt modified key sequence. In that
270			// case, just report a single escape key.
271			return 1, KeyPressEvent{Code: KeyEscape}
272		}
273	case ansi.SS3:
274		return p.parseSs3(buf)
275	case ansi.DCS:
276		return p.parseDcs(buf)
277	case ansi.CSI:
278		return p.parseCsi(buf)
279	case ansi.OSC:
280		return p.parseOsc(buf)
281	case ansi.APC:
282		return p.parseApc(buf)
283	case ansi.PM:
284		return p.parseStTerminated(ansi.PM, '^', nil)(buf)
285	case ansi.SOS:
286		return p.parseStTerminated(ansi.SOS, 'X', nil)(buf)
287	default:
288		if b <= ansi.US || b == ansi.DEL || b == ansi.SP {
289			return 1, p.parseControl(b)
290		} else if b >= ansi.PAD && b <= ansi.APC {
291			// C1 control code
292			// UTF-8 never starts with a C1 control code
293			// Encode these as Ctrl+Alt+<code - 0x40>
294			code := rune(b) - 0x40
295			return 1, KeyPressEvent{Code: code, Mod: ModCtrl | ModAlt}
296		}
297		return p.parseUtf8(buf)
298	}
299}
300
301func (p *EventDecoder) parseCsi(b []byte) (int, Event) {
302	if len(b) == 2 && b[0] == ansi.ESC {
303		// short cut if this is an alt+[ key
304		return 2, KeyPressEvent{Code: rune(b[1]), Mod: ModAlt}
305	}
306
307	var cmd ansi.Cmd
308	var params [parser.MaxParamsSize]ansi.Param
309	var paramsLen int
310
311	var i int
312	if b[i] == ansi.CSI || b[i] == ansi.ESC {
313		i++
314	}
315	if i < len(b) && b[i-1] == ansi.ESC && b[i] == '[' {
316		i++
317	}
318
319	// Initial CSI byte
320	if i < len(b) && b[i] >= '<' && b[i] <= '?' {
321		cmd |= ansi.Cmd(b[i]) << parser.PrefixShift
322	}
323
324	// Scan parameter bytes in the range 0x30-0x3F
325	var j int
326	for j = 0; i < len(b) && paramsLen < len(params) && b[i] >= 0x30 && b[i] <= 0x3F; i, j = i+1, j+1 {
327		if b[i] >= '0' && b[i] <= '9' {
328			if params[paramsLen] == parser.MissingParam {
329				params[paramsLen] = 0
330			}
331			params[paramsLen] *= 10
332			params[paramsLen] += ansi.Param(b[i]) - '0'
333		}
334		if b[i] == ':' {
335			params[paramsLen] |= parser.HasMoreFlag
336		}
337		if b[i] == ';' || b[i] == ':' {
338			paramsLen++
339			if paramsLen < len(params) {
340				// Don't overflow the params slice
341				params[paramsLen] = parser.MissingParam
342			}
343		}
344	}
345
346	if j > 0 && paramsLen < len(params) {
347		// has parameters
348		paramsLen++
349	}
350
351	// Scan intermediate bytes in the range 0x20-0x2F
352	var intermed byte
353	for ; i < len(b) && b[i] >= 0x20 && b[i] <= 0x2F; i++ {
354		intermed = b[i]
355	}
356
357	// Set the intermediate byte
358	cmd |= ansi.Cmd(intermed) << parser.IntermedShift
359
360	// Scan final byte in the range 0x40-0x7E
361	if i >= len(b) || b[i] < 0x40 || b[i] > 0x7E {
362		// Special case for URxvt keys
363		// CSI <number> $ is an invalid sequence, but URxvt uses it for
364		// shift modified keys.
365		if intermed == '$' && b[i-1] == '$' {
366			buf := slices.Clone(b[:i-1])
367			n, ev := p.parseCsi(append(buf, '~'))
368			if k, ok := ev.(KeyPressEvent); ok {
369				k.Mod |= ModShift
370				return n, k
371			}
372		}
373		return i, UnknownEvent(b[:i])
374	}
375
376	// Add the final byte
377	cmd |= ansi.Cmd(b[i])
378	i++
379
380	pa := ansi.Params(params[:paramsLen])
381	switch cmd {
382	case 'y' | '?'<<parser.PrefixShift | '$'<<parser.IntermedShift:
383		// Report Mode (DECRPM)
384		mode, _, ok := pa.Param(0, -1)
385		if !ok || mode == -1 {
386			break
387		}
388		value, _, ok := pa.Param(1, 0)
389		if !ok {
390			break
391		}
392		return i, ModeReportEvent{Mode: ansi.DECMode(mode), Value: ansi.ModeSetting(value)}
393	case 'c' | '?'<<parser.PrefixShift:
394		// Primary Device Attributes
395		return i, parsePrimaryDevAttrs(pa)
396	case 'c' | '>'<<parser.PrefixShift:
397		// Secondary Device Attributes
398		return i, parseSecondaryDevAttrs(pa)
399	case 'u' | '?'<<parser.PrefixShift:
400		// Kitty keyboard flags
401		flags, _, _ := pa.Param(0, -1)
402		return i, KeyboardEnhancementsEvent{flags}
403	case 'R' | '?'<<parser.PrefixShift:
404		// This report may return a third parameter representing the page
405		// number, but we don't really need it.
406		row, _, _ := pa.Param(0, 1)
407		col, _, ok := pa.Param(1, 1)
408		if !ok {
409			break
410		}
411		return i, CursorPositionEvent{Y: row - 1, X: col - 1}
412	case 'm' | '<'<<parser.PrefixShift, 'M' | '<'<<parser.PrefixShift:
413		// Handle SGR mouse
414		if paramsLen == 3 {
415			return i, parseSGRMouseEvent(cmd, pa)
416		}
417	case 'm' | '>'<<parser.PrefixShift:
418		// XTerm modifyOtherKeys
419		mok, _, ok := pa.Param(0, 0)
420		if !ok || mok != 4 {
421			break
422		}
423		val, _, ok := pa.Param(1, -1)
424		if !ok || val == -1 {
425			break
426		}
427		return i, ModifyOtherKeysEvent{val}
428	case 'n' | '?'<<parser.PrefixShift:
429		report, _, _ := pa.Param(0, -1)
430		darkLight, _, _ := pa.Param(1, -1)
431		switch report {
432		case 997: // [ansi.LightDarkReport]
433			switch darkLight {
434			case 1:
435				return i, DarkColorSchemeEvent{}
436			case 2:
437				return i, LightColorSchemeEvent{}
438			}
439		}
440	case 'I':
441		return i, FocusEvent{}
442	case 'O':
443		return i, BlurEvent{}
444	case 'R':
445		// Cursor position report OR modified F3
446		row, _, rok := pa.Param(0, 1)
447		col, _, cok := pa.Param(1, 1)
448		if paramsLen == 2 && rok && cok {
449			m := CursorPositionEvent{Y: row - 1, X: col - 1}
450			if row == 1 && col-1 <= int(ModMeta|ModShift|ModAlt|ModCtrl) {
451				// XXX: We cannot differentiate between cursor position report and
452				// CSI 1 ; <mod> R (which is modified F3) when the cursor is at the
453				// row 1. In this case, we report both messages.
454				//
455				// For a non ambiguous cursor position report, use
456				// [ansi.RequestExtendedCursorPosition] (DECXCPR) instead.
457				return i, MultiEvent{KeyPressEvent{Code: KeyF3, Mod: KeyMod(col - 1)}, m}
458			}
459
460			return i, m
461		}
462
463		if paramsLen != 0 {
464			break
465		}
466
467		// Unmodified key F3 (CSI R)
468		fallthrough
469	case 'a', 'b', 'c', 'd', 'A', 'B', 'C', 'D', 'E', 'F', 'H', 'P', 'Q', 'S', 'Z':
470		var k KeyPressEvent
471		switch cmd {
472		case 'a', 'b', 'c', 'd':
473			k = KeyPressEvent{Code: KeyUp + rune(cmd-'a'), Mod: ModShift}
474		case 'A', 'B', 'C', 'D':
475			k = KeyPressEvent{Code: KeyUp + rune(cmd-'A')}
476		case 'E':
477			k = KeyPressEvent{Code: KeyBegin}
478		case 'F':
479			k = KeyPressEvent{Code: KeyEnd}
480		case 'H':
481			k = KeyPressEvent{Code: KeyHome}
482		case 'P', 'Q', 'R', 'S':
483			k = KeyPressEvent{Code: KeyF1 + rune(cmd-'P')}
484		case 'Z':
485			k = KeyPressEvent{Code: KeyTab, Mod: ModShift}
486		}
487		id, _, _ := pa.Param(0, 1)
488		mod, _, _ := pa.Param(1, 1)
489		if paramsLen > 2 && !pa[1].HasMore() || id != 1 {
490			break
491		}
492		if paramsLen > 1 && id == 1 && mod != -1 {
493			// CSI 1 ; <modifiers> A
494			k.Mod |= KeyMod(mod - 1)
495		}
496		// Don't forget to handle Kitty keyboard protocol
497		return i, parseKittyKeyboardExt(pa, k)
498	case 'M':
499		// Handle X10 mouse
500		if i+3 > len(b) {
501			return i, UnknownCsiEvent(b[:i])
502		}
503		return i + 3, parseX10MouseEvent(append(b[:i], b[i:i+3]...))
504	case 'y' | '$'<<parser.IntermedShift:
505		// Report Mode (DECRPM)
506		mode, _, ok := pa.Param(0, -1)
507		if !ok || mode == -1 {
508			break
509		}
510		val, _, ok := pa.Param(1, 0)
511		if !ok {
512			break
513		}
514		return i, ModeReportEvent{Mode: ansi.ANSIMode(mode), Value: ansi.ModeSetting(val)}
515	case 'u':
516		// Kitty keyboard protocol & CSI u (fixterms)
517		if paramsLen == 0 {
518			return i, UnknownCsiEvent(b[:i])
519		}
520		return i, parseKittyKeyboard(pa)
521	case '_':
522		// Win32 Input Mode
523		if paramsLen != 6 {
524			return i, UnknownCsiEvent(b[:i])
525		}
526
527		vk, _, _ := pa.Param(0, 0)
528		sc, _, _ := pa.Param(1, 0)
529		uc, _, _ := pa.Param(2, 0)
530		kd, _, _ := pa.Param(3, 0)
531		cs, _, _ := pa.Param(4, 0)
532		rc, _, _ := pa.Param(5, 0)
533		event := p.parseWin32InputKeyEvent(
534			uint16(vk),         //nolint:gosec // Vk wVirtualKeyCode
535			uint16(sc),         //nolint:gosec // Sc wVirtualScanCode
536			rune(uc),           // Uc UnicodeChar
537			kd == 1,            // Kd bKeyDown
538			uint32(cs),         //nolint:gosec // Cs dwControlKeyState
539			max(1, uint16(rc)), //nolint:gosec // Rc wRepeatCount
540		)
541
542		return i, event
543	case '@', '^', '~':
544		if paramsLen == 0 {
545			return i, UnknownCsiEvent(b[:i])
546		}
547
548		param, _, _ := pa.Param(0, 0)
549		switch cmd {
550		case '~':
551			switch param {
552			case 27:
553				// XTerm modifyOtherKeys 2
554				if paramsLen != 3 {
555					return i, UnknownCsiEvent(b[:i])
556				}
557				return i, parseXTermModifyOtherKeys(pa)
558			case 200:
559				// bracketed-paste start
560				return i, PasteStartEvent{}
561			case 201:
562				// bracketed-paste end
563				return i, PasteEndEvent{}
564			}
565		}
566
567		switch param {
568		case 1, 2, 3, 4, 5, 6, 7, 8,
569			11, 12, 13, 14, 15,
570			17, 18, 19, 20, 21,
571			23, 24, 25, 26,
572			28, 29, 31, 32, 33, 34:
573			var k KeyPressEvent
574			switch param {
575			case 1:
576				if p.Legacy&flagFind != 0 {
577					k = KeyPressEvent{Code: KeyFind}
578				} else {
579					k = KeyPressEvent{Code: KeyHome}
580				}
581			case 2:
582				k = KeyPressEvent{Code: KeyInsert}
583			case 3:
584				k = KeyPressEvent{Code: KeyDelete}
585			case 4:
586				if p.Legacy&flagSelect != 0 {
587					k = KeyPressEvent{Code: KeySelect}
588				} else {
589					k = KeyPressEvent{Code: KeyEnd}
590				}
591			case 5:
592				k = KeyPressEvent{Code: KeyPgUp}
593			case 6:
594				k = KeyPressEvent{Code: KeyPgDown}
595			case 7:
596				k = KeyPressEvent{Code: KeyHome}
597			case 8:
598				k = KeyPressEvent{Code: KeyEnd}
599			case 11, 12, 13, 14, 15:
600				k = KeyPressEvent{Code: KeyF1 + rune(param-11)}
601			case 17, 18, 19, 20, 21:
602				k = KeyPressEvent{Code: KeyF6 + rune(param-17)}
603			case 23, 24, 25, 26:
604				k = KeyPressEvent{Code: KeyF11 + rune(param-23)}
605			case 28, 29:
606				k = KeyPressEvent{Code: KeyF15 + rune(param-28)}
607			case 31, 32, 33, 34:
608				k = KeyPressEvent{Code: KeyF17 + rune(param-31)}
609			}
610
611			// modifiers
612			mod, _, _ := pa.Param(1, -1)
613			if paramsLen > 1 && mod != -1 {
614				k.Mod |= KeyMod(mod - 1)
615			}
616
617			// Handle URxvt weird keys
618			switch cmd {
619			case '~':
620				// Don't forget to handle Kitty keyboard protocol
621				return i, parseKittyKeyboardExt(pa, k)
622			case '^':
623				k.Mod |= ModCtrl
624			case '@':
625				k.Mod |= ModCtrl | ModShift
626			}
627
628			return i, k
629		}
630
631	case 't':
632		param, _, ok := pa.Param(0, 0)
633		if !ok {
634			break
635		}
636
637		switch param {
638		case 4: // Report Terminal window size in pixels.
639			if paramsLen == 3 {
640				height, _, hOk := pa.Param(1, 0)
641				width, _, wOk := pa.Param(2, 0)
642				if !hOk || !wOk {
643					break
644				}
645				return i, PixelSizeEvent{Width: width, Height: height}
646			}
647		case 6: // Report Terminal character cell size.
648			if paramsLen == 3 {
649				height, _, hOk := pa.Param(1, 0)
650				width, _, wOk := pa.Param(2, 0)
651				if !hOk || !wOk {
652					break
653				}
654				return i, CellSizeEvent{Width: width, Height: height}
655			}
656		case 8: // Report Terminal Window size in cells.
657			if paramsLen == 3 {
658				height, _, hOk := pa.Param(1, 0)
659				width, _, wOk := pa.Param(2, 0)
660				if !hOk || !wOk {
661					break
662				}
663				return i, WindowSizeEvent{Width: width, Height: height}
664			}
665		case 48: // In band terminal size report.
666			if paramsLen == 5 {
667				cellHeight, _, chOk := pa.Param(1, 0)
668				cellWidth, _, cwOk := pa.Param(2, 0)
669				pixelHeight, _, phOk := pa.Param(3, 0)
670				pixelWidth, _, pwOk := pa.Param(4, 0)
671				if !chOk || !cwOk || !phOk || !pwOk {
672					break
673				}
674				return i, MultiEvent{
675					WindowSizeEvent{Width: cellWidth, Height: cellHeight},
676					PixelSizeEvent{Width: pixelWidth, Height: pixelHeight},
677				}
678			}
679		}
680
681		// Any other window operation event.
682
683		var winop WindowOpEvent
684		winop.Op = param
685		for j := 1; j < paramsLen; j++ {
686			val, _, ok := pa.Param(j, 0)
687			if ok {
688				winop.Args = append(winop.Args, val)
689			}
690		}
691
692		return i, winop
693	}
694	return i, UnknownCsiEvent(b[:i])
695}
696
697// parseSs3 parses a SS3 sequence.
698// See https://vt100.net/docs/vt220-rm/chapter4.html#S4.4.4.2
699func (p *EventDecoder) parseSs3(b []byte) (int, Event) {
700	if len(b) == 2 && b[0] == ansi.ESC {
701		// short cut if this is an alt+O key
702		return 2, KeyPressEvent{Code: unicode.ToLower(rune(b[1])), Mod: ModShift | ModAlt}
703	}
704
705	var i int
706	if b[i] == ansi.SS3 || b[i] == ansi.ESC {
707		i++
708	}
709	if i < len(b) && b[i-1] == ansi.ESC && b[i] == 'O' {
710		i++
711	}
712
713	// Scan numbers from 0-9
714	var mod int
715	for ; i < len(b) && b[i] >= '0' && b[i] <= '9'; i++ {
716		mod *= 10
717		mod += int(b[i]) - '0'
718	}
719
720	// Scan a GL character
721	// A GL character is a single byte in the range 0x21-0x7E
722	// See https://vt100.net/docs/vt220-rm/chapter2.html#S2.3.2
723	if i >= len(b) || b[i] < 0x21 || b[i] > 0x7E {
724		return i, UnknownEvent(b[:i])
725	}
726
727	// GL character(s)
728	gl := b[i]
729	i++
730
731	var k KeyPressEvent
732	switch gl {
733	case 'a', 'b', 'c', 'd':
734		k = KeyPressEvent{Code: KeyUp + rune(gl-'a'), Mod: ModCtrl}
735	case 'A', 'B', 'C', 'D':
736		k = KeyPressEvent{Code: KeyUp + rune(gl-'A')}
737	case 'E':
738		k = KeyPressEvent{Code: KeyBegin}
739	case 'F':
740		k = KeyPressEvent{Code: KeyEnd}
741	case 'H':
742		k = KeyPressEvent{Code: KeyHome}
743	case 'P', 'Q', 'R', 'S':
744		k = KeyPressEvent{Code: KeyF1 + rune(gl-'P')}
745	case 'M':
746		k = KeyPressEvent{Code: KeyKpEnter}
747	case 'X':
748		k = KeyPressEvent{Code: KeyKpEqual}
749	case 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y':
750		k = KeyPressEvent{Code: KeyKpMultiply + rune(gl-'j')}
751	default:
752		return i, UnknownSs3Event(b[:i])
753	}
754
755	// Handle weird SS3 <modifier> Func
756	if mod > 0 {
757		k.Mod |= KeyMod(mod - 1)
758	}
759
760	return i, k
761}
762
763func (p *EventDecoder) parseOsc(b []byte) (int, Event) {
764	defaultKey := func() KeyPressEvent {
765		return KeyPressEvent{Code: rune(b[1]), Mod: ModAlt}
766	}
767	if len(b) == 2 && b[0] == ansi.ESC {
768		// short cut if this is an alt+] key
769		return 2, defaultKey()
770	}
771
772	var i int
773	if b[i] == ansi.OSC || b[i] == ansi.ESC {
774		i++
775	}
776	if i < len(b) && b[i-1] == ansi.ESC && b[i] == ']' {
777		i++
778	}
779
780	// Parse OSC command
781	// An OSC sequence is terminated by a BEL, ESC, or ST character
782	var start, end int
783	cmd := -1
784	for ; i < len(b) && b[i] >= '0' && b[i] <= '9'; i++ {
785		if cmd == -1 {
786			cmd = 0
787		} else {
788			cmd *= 10
789		}
790		cmd += int(b[i]) - '0'
791	}
792
793	if i < len(b) && b[i] == ';' {
794		// mark the start of the sequence data
795		i++
796		start = i
797	}
798
799	for ; i < len(b); i++ {
800		// advance to the end of the sequence
801		if slices.Contains([]byte{ansi.BEL, ansi.ESC, ansi.ST, ansi.CAN, ansi.SUB}, b[i]) {
802			break
803		}
804	}
805
806	if i >= len(b) {
807		return i, UnknownEvent(b[:i])
808	}
809
810	end = i // end of the sequence data
811	i++
812
813	// Check 7-bit ST (string terminator) character
814	switch b[i-1] {
815	case ansi.CAN, ansi.SUB:
816		return i, ignoredEvent(b[:i])
817	case ansi.ESC:
818		if i >= len(b) || b[i] != '\\' {
819			if cmd == -1 || (start == 0 && end == 2) {
820				return 2, defaultKey()
821			}
822			if i >= len(b) {
823				// The read ended between ESC and the backslash of ST: wait
824				// for it instead of dropping the sequence and typing a \.
825				return i, UnknownEvent(b[:i])
826			}
827
828			// If we don't have a valid ST terminator, then this is a
829			// cancelled sequence and should be ignored.
830			return i, ignoredEvent(b[:i])
831		}
832
833		i++
834	}
835
836	if end <= start {
837		return i, UnknownEvent(b[:i])
838	}
839
840	data := string(b[start:end])
841	switch cmd {
842	case 10:
843		return i, ForegroundColorEvent{ansi.XParseColor(data)}
844	case 11:
845		return i, BackgroundColorEvent{ansi.XParseColor(data)}
846	case 12:
847		return i, CursorColorEvent{ansi.XParseColor(data)}
848	case 52:
849		parts := strings.Split(data, ";")
850		if len(parts) != 2 || len(parts[0]) < 1 {
851			return i, ClipboardEvent{}
852		}
853
854		b64 := parts[1]
855		bts, err := base64.StdEncoding.DecodeString(b64)
856		if err != nil {
857			return i, ClipboardEvent{Content: parts[1]}
858		}
859
860		sel := ClipboardSelection(parts[0][0]) //nolint:unconvert
861		return i, ClipboardEvent{Selection: sel, Content: string(bts)}
862	}
863
864	return i, UnknownOscEvent(b[:i])
865}
866
867// parseStTerminated parses a control sequence that gets terminated by a ST character.
868func (p *EventDecoder) parseStTerminated(intro8, intro7 byte, fn func([]byte) Event) func([]byte) (int, Event) {
869	defaultKey := func(b []byte) (int, Event) {
870		switch intro8 {
871		case ansi.SOS:
872			return 2, KeyPressEvent{Code: unicode.ToLower(rune(b[1])), Mod: ModShift | ModAlt}
873		case ansi.PM, ansi.APC:
874			return 2, KeyPressEvent{Code: rune(b[1]), Mod: ModAlt}
875		}
876		return 0, nil
877	}
878	return func(b []byte) (int, Event) {
879		if len(b) == 2 && b[0] == ansi.ESC {
880			return defaultKey(b)
881		}
882
883		var i int
884		if b[i] == intro8 || b[i] == ansi.ESC {
885			i++
886		}
887		if i < len(b) && b[i-1] == ansi.ESC && b[i] == intro7 {
888			i++
889		}
890
891		// Scan control sequence
892		// Most common control sequence is terminated by a ST character
893		// ST is a 7-bit string terminator character is (ESC \)
894		start := i
895		for ; i < len(b); i++ {
896			if slices.Contains([]byte{ansi.ESC, ansi.ST, ansi.CAN, ansi.SUB}, b[i]) {
897				break
898			}
899		}
900
901		if i >= len(b) {
902			return i, UnknownEvent(b[:i])
903		}
904
905		end := i // end of the sequence data
906		i++
907
908		// Check 7-bit ST (string terminator) character
909		switch b[i-1] {
910		case ansi.CAN, ansi.SUB:
911			return i, ignoredEvent(b[:i])
912		case ansi.ESC:
913			if i >= len(b) || b[i] != '\\' {
914				if start == end {
915					return defaultKey(b)
916				}
917				if i >= len(b) {
918					// ST may still be arriving; see parseOsc.
919					return i, UnknownEvent(b[:i])
920				}
921
922				// If we don't have a valid ST terminator, then this is a
923				// cancelled sequence and should be ignored.
924				return i, ignoredEvent(b[:i])
925			}
926
927			i++
928		}
929
930		// Call the function to parse the sequence and return the result
931		if fn != nil {
932			if e := fn(b[start:end]); e != nil {
933				return i, e
934			}
935		}
936
937		switch intro8 {
938		case ansi.PM:
939			return i, UnknownPmEvent(b[:i])
940		case ansi.SOS:
941			return i, UnknownSosEvent(b[:i])
942		case ansi.APC:
943			return i, UnknownApcEvent(b[:i])
944		default:
945			return i, UnknownEvent(b[:i])
946		}
947	}
948}
949
950func (p *EventDecoder) parseDcs(b []byte) (int, Event) {
951	if len(b) == 2 && b[0] == ansi.ESC {
952		// short cut if this is an alt+P key
953		return 2, KeyPressEvent{Code: unicode.ToLower(rune(b[1])), Mod: ModShift | ModAlt}
954	}
955
956	var params [16]ansi.Param
957	var paramsLen int
958	var cmd ansi.Cmd
959
960	// DCS sequences are introduced by DCS (0x90) or ESC P (0x1b 0x50)
961	var i int
962	if b[i] == ansi.DCS || b[i] == ansi.ESC {
963		i++
964	}
965	if i < len(b) && b[i-1] == ansi.ESC && b[i] == 'P' {
966		i++
967	}
968
969	// initial DCS byte
970	if i < len(b) && b[i] >= '<' && b[i] <= '?' {
971		cmd |= ansi.Cmd(b[i]) << parser.PrefixShift
972	}
973
974	// Scan parameter bytes in the range 0x30-0x3F
975	var j int
976	for j = 0; i < len(b) && paramsLen < len(params) && b[i] >= 0x30 && b[i] <= 0x3F; i, j = i+1, j+1 {
977		if b[i] >= '0' && b[i] <= '9' {
978			if params[paramsLen] == parser.MissingParam {
979				params[paramsLen] = 0
980			}
981			params[paramsLen] *= 10
982			params[paramsLen] += ansi.Param(b[i]) - '0'
983		}
984		if b[i] == ':' {
985			params[paramsLen] |= parser.HasMoreFlag
986		}
987		if b[i] == ';' || b[i] == ':' {
988			paramsLen++
989			if paramsLen < len(params) {
990				// Don't overflow the params slice
991				params[paramsLen] = parser.MissingParam
992			}
993		}
994	}
995
996	if j > 0 && paramsLen < len(params) {
997		// has parameters
998		paramsLen++
999	}
1000
1001	// Scan intermediate bytes in the range 0x20-0x2F
1002	var intermed byte
1003	for j := 0; i < len(b) && b[i] >= 0x20 && b[i] <= 0x2F; i, j = i+1, j+1 {
1004		intermed = b[i]
1005	}
1006
1007	// set intermediate byte
1008	cmd |= ansi.Cmd(intermed) << parser.IntermedShift
1009
1010	// Scan final byte in the range 0x40-0x7E
1011	if i >= len(b) || b[i] < 0x40 || b[i] > 0x7E {
1012		return i, UnknownEvent(b[:i])
1013	}
1014
1015	// Add the final byte
1016	cmd |= ansi.Cmd(b[i])
1017	i++
1018
1019	start := i // start of the sequence data
1020	for ; i < len(b); i++ {
1021		if b[i] == ansi.ST || b[i] == ansi.ESC {
1022			break
1023		}
1024	}
1025
1026	if i >= len(b) {
1027		return i, UnknownEvent(b[:i])
1028	}
1029
1030	end := i // end of the sequence data
1031	i++
1032
1033	// Check 7-bit ST (string terminator) character
1034	if b[i-1] == ansi.ESC {
1035		if i >= len(b) {
1036			// ST may still be arriving; see parseOsc.
1037			return i, UnknownEvent(b[:i])
1038		}
1039		if b[i] == '\\' {
1040			i++
1041		}
1042	}
1043
1044	pa := ansi.Params(params[:paramsLen])
1045	switch cmd {
1046	case 'r' | '+'<<parser.IntermedShift:
1047		// XTGETTCAP responses
1048		param, _, _ := pa.Param(0, 0)
1049		switch param {
1050		case 1: // 1 means valid response, 0 means invalid response
1051			tc := parseTermcap(b[start:end])
1052			// XXX: some terminals like KiTTY report invalid responses with
1053			// their queries i.e. sending a query for "Tc" using "\x1bP+q5463\x1b\\"
1054			// returns "\x1bP0+r5463\x1b\\".
1055			// The specs says that invalid responses should be in the form of
1056			// DCS 0 + r ST "\x1bP0+r\x1b\\"
1057			// We ignore invalid responses and only send valid ones to the program.
1058			//
1059			// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
1060			return i, tc
1061		}
1062	case '|' | '>'<<parser.PrefixShift:
1063		// XTVersion response
1064		return i, TerminalVersionEvent{string(b[start:end])}
1065	case '|' | '!'<<parser.IntermedShift:
1066		// Teritary Device Attributes
1067		return i, parseTertiaryDevAttrs(b[start:end])
1068	}
1069
1070	return i, UnknownDcsEvent(b[:i])
1071}
1072
1073func (p *EventDecoder) parseApc(b []byte) (int, Event) {
1074	if len(b) == 2 && b[0] == ansi.ESC {
1075		// short cut if this is an alt+_ key
1076		return 2, KeyPressEvent{Code: rune(b[1]), Mod: ModAlt}
1077	}
1078
1079	// APC sequences are introduced by APC (0x9f) or ESC _ (0x1b 0x5f)
1080	return p.parseStTerminated(ansi.APC, '_', func(b []byte) Event {
1081		if len(b) == 0 {
1082			return nil
1083		}
1084
1085		switch b[0] {
1086		case 'G': // Kitty Graphics Protocol
1087			var g KittyGraphicsEvent
1088			parts := bytes.Split(b[1:], []byte{';'})
1089			g.Options.UnmarshalText(parts[0]) //nolint:errcheck,gosec
1090			if len(parts) > 1 {
1091				g.Payload = parts[1]
1092			}
1093			return g
1094		}
1095
1096		return nil
1097	})(b)
1098}
1099
1100func (p *EventDecoder) parseUtf8(b []byte) (int, Event) {
1101	if len(b) == 0 {
1102		return 0, nil
1103	}
1104
1105	c := b[0]
1106	if c <= ansi.US || c == ansi.DEL {
1107		// Control codes get handled by parseControl
1108		return 1, p.parseControl(c)
1109	} else if c > ansi.US && c < ansi.DEL {
1110		// ASCII printable characters
1111		code := rune(c)
1112		k := KeyPressEvent{Code: code, Text: string(code)}
1113		if unicode.IsUpper(code) {
1114			// Convert upper case letters to lower case + shift modifier
1115			k.Code = unicode.ToLower(code)
1116			k.ShiftedCode = code
1117			k.Mod |= ModShift
1118		}
1119
1120		return 1, k
1121	}
1122
1123	code, _ := utf8.DecodeRune(b)
1124	if code == utf8.RuneError {
1125		return 1, UnknownEvent(b[0])
1126	}
1127
1128	cluster, _, _, _ := uniseg.FirstGraphemeCluster(b, -1)
1129	text := string(cluster)
1130	for i := range text {
1131		if i > 0 {
1132			// Use [KeyExtended] for multi-rune graphemes
1133			code = KeyExtended
1134			break
1135		}
1136	}
1137
1138	return len(cluster), KeyPressEvent{Code: code, Text: text}
1139}
1140
1141func (p *EventDecoder) parseControl(b byte) Event {
1142	switch b {
1143	case ansi.NUL:
1144		if p.Legacy&flagCtrlAt != 0 {
1145			return KeyPressEvent{Code: '@', Mod: ModCtrl}
1146		}
1147		return KeyPressEvent{Code: KeySpace, Mod: ModCtrl}
1148	case ansi.BS:
1149		return KeyPressEvent{Code: 'h', Mod: ModCtrl}
1150	case ansi.HT:
1151		if p.Legacy&flagCtrlI != 0 {
1152			return KeyPressEvent{Code: 'i', Mod: ModCtrl}
1153		}
1154		return KeyPressEvent{Code: KeyTab}
1155	case ansi.CR:
1156		if p.Legacy&flagCtrlM != 0 {
1157			return KeyPressEvent{Code: 'm', Mod: ModCtrl}
1158		}
1159		return KeyPressEvent{Code: KeyEnter}
1160	case ansi.ESC:
1161		if p.Legacy&flagCtrlOpenBracket != 0 {
1162			return KeyPressEvent{Code: '[', Mod: ModCtrl}
1163		}
1164		return KeyPressEvent{Code: KeyEscape}
1165	case ansi.DEL:
1166		if p.Legacy&flagBackspace != 0 {
1167			return KeyPressEvent{Code: KeyDelete}
1168		}
1169		return KeyPressEvent{Code: KeyBackspace}
1170	case ansi.SP:
1171		return KeyPressEvent{Code: KeySpace, Text: " "}
1172	default:
1173		if b >= ansi.SOH && b <= ansi.SUB {
1174			// Use lower case letters for control codes
1175			code := rune(b + 0x60)
1176			return KeyPressEvent{Code: code, Mod: ModCtrl}
1177		} else if b >= ansi.FS && b <= ansi.US {
1178			code := rune(b + 0x40)
1179			return KeyPressEvent{Code: code, Mod: ModCtrl}
1180		}
1181		return UnknownEvent(b)
1182	}
1183}
1184
1185func parseXTermModifyOtherKeys(params ansi.Params) Event {
1186	// XTerm modify other keys starts with ESC [ 27 ; <modifier> ; <code> ~
1187	xmod, _, _ := params.Param(1, 1)
1188	xrune, _, _ := params.Param(2, 1)
1189	mod := KeyMod(xmod - 1)
1190	r := rune(xrune)
1191
1192	switch r {
1193	case ansi.BS:
1194		return KeyPressEvent{Mod: mod, Code: KeyBackspace}
1195	case ansi.HT:
1196		return KeyPressEvent{Mod: mod, Code: KeyTab}
1197	case ansi.CR:
1198		return KeyPressEvent{Mod: mod, Code: KeyEnter}
1199	case ansi.ESC:
1200		return KeyPressEvent{Mod: mod, Code: KeyEscape}
1201	case ansi.DEL:
1202		return KeyPressEvent{Mod: mod, Code: KeyBackspace}
1203	}
1204
1205	// CSI 27 ; <modifier> ; <code> ~ keys defined in XTerm modifyOtherKeys
1206	k := KeyPressEvent{Code: r, Mod: mod}
1207	if k.Mod <= ModShift {
1208		k.Text = string(r)
1209	}
1210
1211	return k
1212}
1213
1214// Kitty Clipboard Control Sequences.
1215var kittyKeyMap = map[int]Key{
1216	ansi.BS:  {Code: KeyBackspace},
1217	ansi.HT:  {Code: KeyTab},
1218	ansi.CR:  {Code: KeyEnter},
1219	ansi.ESC: {Code: KeyEscape},
1220	ansi.DEL: {Code: KeyBackspace},
1221
1222	57344: {Code: KeyEscape},
1223	57345: {Code: KeyEnter},
1224	57346: {Code: KeyTab},
1225	57347: {Code: KeyBackspace},
1226	57348: {Code: KeyInsert},
1227	57349: {Code: KeyDelete},
1228	57350: {Code: KeyLeft},
1229	57351: {Code: KeyRight},
1230	57352: {Code: KeyUp},
1231	57353: {Code: KeyDown},
1232	57354: {Code: KeyPgUp},
1233	57355: {Code: KeyPgDown},
1234	57356: {Code: KeyHome},
1235	57357: {Code: KeyEnd},
1236	57358: {Code: KeyCapsLock},
1237	57359: {Code: KeyScrollLock},
1238	57360: {Code: KeyNumLock},
1239	57361: {Code: KeyPrintScreen},
1240	57362: {Code: KeyPause},
1241	57363: {Code: KeyMenu},
1242	57364: {Code: KeyF1},
1243	57365: {Code: KeyF2},
1244	57366: {Code: KeyF3},
1245	57367: {Code: KeyF4},
1246	57368: {Code: KeyF5},
1247	57369: {Code: KeyF6},
1248	57370: {Code: KeyF7},
1249	57371: {Code: KeyF8},
1250	57372: {Code: KeyF9},
1251	57373: {Code: KeyF10},
1252	57374: {Code: KeyF11},
1253	57375: {Code: KeyF12},
1254	57376: {Code: KeyF13},
1255	57377: {Code: KeyF14},
1256	57378: {Code: KeyF15},
1257	57379: {Code: KeyF16},
1258	57380: {Code: KeyF17},
1259	57381: {Code: KeyF18},
1260	57382: {Code: KeyF19},
1261	57383: {Code: KeyF20},
1262	57384: {Code: KeyF21},
1263	57385: {Code: KeyF22},
1264	57386: {Code: KeyF23},
1265	57387: {Code: KeyF24},
1266	57388: {Code: KeyF25},
1267	57389: {Code: KeyF26},
1268	57390: {Code: KeyF27},
1269	57391: {Code: KeyF28},
1270	57392: {Code: KeyF29},
1271	57393: {Code: KeyF30},
1272	57394: {Code: KeyF31},
1273	57395: {Code: KeyF32},
1274	57396: {Code: KeyF33},
1275	57397: {Code: KeyF34},
1276	57398: {Code: KeyF35},
1277	57399: {Code: KeyKp0},
1278	57400: {Code: KeyKp1},
1279	57401: {Code: KeyKp2},
1280	57402: {Code: KeyKp3},
1281	57403: {Code: KeyKp4},
1282	57404: {Code: KeyKp5},
1283	57405: {Code: KeyKp6},
1284	57406: {Code: KeyKp7},
1285	57407: {Code: KeyKp8},
1286	57408: {Code: KeyKp9},
1287	57409: {Code: KeyKpDecimal},
1288	57410: {Code: KeyKpDivide},
1289	57411: {Code: KeyKpMultiply},
1290	57412: {Code: KeyKpMinus},
1291	57413: {Code: KeyKpPlus},
1292	57414: {Code: KeyKpEnter},
1293	57415: {Code: KeyKpEqual},
1294	57416: {Code: KeyKpSep},
1295	57417: {Code: KeyKpLeft},
1296	57418: {Code: KeyKpRight},
1297	57419: {Code: KeyKpUp},
1298	57420: {Code: KeyKpDown},
1299	57421: {Code: KeyKpPgUp},
1300	57422: {Code: KeyKpPgDown},
1301	57423: {Code: KeyKpHome},
1302	57424: {Code: KeyKpEnd},
1303	57425: {Code: KeyKpInsert},
1304	57426: {Code: KeyKpDelete},
1305	57427: {Code: KeyKpBegin},
1306	57428: {Code: KeyMediaPlay},
1307	57429: {Code: KeyMediaPause},
1308	57430: {Code: KeyMediaPlayPause},
1309	57431: {Code: KeyMediaReverse},
1310	57432: {Code: KeyMediaStop},
1311	57433: {Code: KeyMediaFastForward},
1312	57434: {Code: KeyMediaRewind},
1313	57435: {Code: KeyMediaNext},
1314	57436: {Code: KeyMediaPrev},
1315	57437: {Code: KeyMediaRecord},
1316	57438: {Code: KeyLowerVol},
1317	57439: {Code: KeyRaiseVol},
1318	57440: {Code: KeyMute},
1319	57441: {Code: KeyLeftShift},
1320	57442: {Code: KeyLeftCtrl},
1321	57443: {Code: KeyLeftAlt},
1322	57444: {Code: KeyLeftSuper},
1323	57445: {Code: KeyLeftHyper},
1324	57446: {Code: KeyLeftMeta},
1325	57447: {Code: KeyRightShift},
1326	57448: {Code: KeyRightCtrl},
1327	57449: {Code: KeyRightAlt},
1328	57450: {Code: KeyRightSuper},
1329	57451: {Code: KeyRightHyper},
1330	57452: {Code: KeyRightMeta},
1331	57453: {Code: KeyIsoLevel3Shift},
1332	57454: {Code: KeyIsoLevel5Shift},
1333}
1334
1335func init() {
1336	// These are some faulty C0 mappings some terminals such as WezTerm have
1337	// and doesn't follow the specs.
1338	kittyKeyMap[ansi.NUL] = Key{Code: KeySpace, Mod: ModCtrl}
1339	for i := ansi.SOH; i <= ansi.SUB; i++ {
1340		if _, ok := kittyKeyMap[i]; !ok {
1341			kittyKeyMap[i] = Key{Code: rune(i + 0x60), Mod: ModCtrl}
1342		}
1343	}
1344	for i := ansi.FS; i <= ansi.US; i++ {
1345		if _, ok := kittyKeyMap[i]; !ok {
1346			kittyKeyMap[i] = Key{Code: rune(i + 0x40), Mod: ModCtrl}
1347		}
1348	}
1349}
1350
1351const (
1352	kittyShift = 1 << iota
1353	kittyAlt
1354	kittyCtrl
1355	kittySuper
1356	kittyHyper
1357	kittyMeta
1358	kittyCapsLock
1359	kittyNumLock
1360)
1361
1362func fromKittyMod(mod int) KeyMod {
1363	var m KeyMod
1364	if mod&kittyShift != 0 {
1365		m |= ModShift
1366	}
1367	if mod&kittyAlt != 0 {
1368		m |= ModAlt
1369	}
1370	if mod&kittyCtrl != 0 {
1371		m |= ModCtrl
1372	}
1373	if mod&kittySuper != 0 {
1374		m |= ModSuper
1375	}
1376	if mod&kittyHyper != 0 {
1377		m |= ModHyper
1378	}
1379	if mod&kittyMeta != 0 {
1380		m |= ModMeta
1381	}
1382	if mod&kittyCapsLock != 0 {
1383		m |= ModCapsLock
1384	}
1385	if mod&kittyNumLock != 0 {
1386		m |= ModNumLock
1387	}
1388	return m
1389}
1390
1391// parseKittyKeyboard parses a Kitty Keyboard Protocol sequence.
1392//
1393// In `CSI u`, this is parsed as:
1394//
1395//	CSI codepoint ; modifiers u
1396//	codepoint: ASCII Dec value
1397//
1398// The Kitty Keyboard Protocol extends this with optional components that can be
1399// enabled progressively. The full sequence is parsed as:
1400//
1401//	CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u
1402//
1403// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/
1404func parseKittyKeyboard(params ansi.Params) (Event Event) {
1405	var isRelease bool
1406	var key Key
1407
1408	// The index of parameters separated by semicolons ';'. Sub parameters are
1409	// separated by colons ':'.
1410	var paramIdx int
1411	var sudIdx int // The sub parameter index
1412	for _, p := range params {
1413		// Kitty Keyboard Protocol has 3 optional components.
1414		switch paramIdx {
1415		case 0:
1416			switch sudIdx {
1417			case 0:
1418				var foundKey bool
1419				code := p.Param(1) // CSI u has a default value of 1
1420				key, foundKey = kittyKeyMap[code]
1421				if !foundKey {
1422					r := rune(code)
1423					if !utf8.ValidRune(r) {
1424						r = utf8.RuneError
1425					}
1426
1427					key.Code = r
1428				}
1429
1430			case 2:
1431				// shifted key + base key
1432				if b := rune(p.Param(1)); unicode.IsPrint(b) {
1433					// XXX: When alternate key reporting is enabled, the protocol
1434					// can return 3 things, the unicode codepoint of the key,
1435					// the shifted codepoint of the key, and the standard
1436					// PC-101 key layout codepoint.
1437					// This is useful to create an unambiguous mapping of keys
1438					// when using a different language layout.
1439					key.BaseCode = b
1440				}
1441				fallthrough
1442
1443			case 1:
1444				// shifted key
1445				if s := rune(p.Param(1)); unicode.IsPrint(s) {
1446					// XXX: We swap keys here because we want the shifted key
1447					// to be the Rune that is returned by the event.
1448					// For example, shift+a should produce "A" not "a".
1449					// In such a case, we set AltRune to the original key "a"
1450					// and Rune to "A".
1451					key.ShiftedCode = s
1452				}
1453			}
1454		case 1:
1455			switch sudIdx {
1456			case 0:
1457				mod := p.Param(1)
1458				if mod > 1 {
1459					key.Mod = fromKittyMod(mod - 1)
1460					if key.Mod > ModShift {
1461						// XXX: We need to clear the text if we have a modifier key
1462						// other than a [ModShift] key.
1463						key.Text = ""
1464					}
1465				}
1466
1467			case 1:
1468				switch p.Param(1) {
1469				case 2:
1470					key.IsRepeat = true
1471				case 3:
1472					isRelease = true
1473				}
1474			case 2:
1475			}
1476		case 2:
1477			if code := p.Param(0); code != 0 {
1478				key.Text += string(rune(code))
1479			}
1480		}
1481
1482		sudIdx++
1483		if !p.HasMore() {
1484			paramIdx++
1485			sudIdx = 0
1486		}
1487	}
1488
1489	keyMod := key.Mod
1490
1491	// Remove these lock modifiers from now on since they don't affect the text.
1492	keyMod &^= ModNumLock
1493	// keyMod &^= ModScrollLock // Kitty doesn't support scroll lock
1494
1495	printMod := keyMod <= ModShift || keyMod == ModCapsLock || keyMod == (ModShift|ModCapsLock)
1496	printKeyPad := key.Code >= KeyKpEqual && key.Code <= KeyKpSep
1497	if len(key.Text) == 0 && printKeyPad && printMod {
1498		switch {
1499		case key.Code >= KeyKp0 && key.Code <= KeyKp9:
1500			key.Text = string('0' + key.Code - KeyKp0)
1501		case key.Code == KeyKpEqual:
1502			key.Text = "="
1503		case key.Code == KeyKpMultiply:
1504			key.Text = "*"
1505		case key.Code == KeyKpPlus:
1506			key.Text = "+"
1507		case key.Code == KeyKpMinus:
1508			key.Text = "-"
1509		case key.Code == KeyKpDecimal:
1510			key.Text = "."
1511		case key.Code == KeyKpDivide:
1512			key.Text = "/"
1513		case key.Code == KeyKpSep:
1514			key.Text = ","
1515		}
1516	}
1517
1518	//nolint:nestif
1519	if len(key.Text) == 0 && unicode.IsPrint(key.Code) && printMod {
1520		if keyMod == 0 {
1521			key.Text = string(key.Code)
1522		} else {
1523			desiredCase := unicode.ToLower
1524			if keyMod.Contains(ModShift) || keyMod.Contains(ModCapsLock) {
1525				desiredCase = unicode.ToUpper
1526			}
1527			if key.ShiftedCode != 0 {
1528				key.Text = string(key.ShiftedCode)
1529			} else {
1530				key.Text = string(desiredCase(key.Code))
1531			}
1532		}
1533	}
1534
1535	if isRelease {
1536		return KeyReleaseEvent(key)
1537	}
1538
1539	return KeyPressEvent(key)
1540}
1541
1542// parseKittyKeyboardExt parses a Kitty Keyboard Protocol sequence extensions
1543// for non CSI u sequences. This includes things like CSI A, SS3 A and others,
1544// and CSI ~.
1545func parseKittyKeyboardExt(params ansi.Params, k KeyPressEvent) Event {
1546	// Handle Kitty keyboard protocol
1547	if len(params) > 2 && // We have at least 3 parameters
1548		params[1].HasMore() { // The second parameter is a subparameter (separated by a ":")
1549		switch params[2].Param(1) { // The third parameter is the event type (defaults to 1)
1550		case 2:
1551			k.IsRepeat = true
1552		case 3:
1553			return KeyReleaseEvent(k)
1554		}
1555	}
1556	return k
1557}
1558
1559func parsePrimaryDevAttrs(params ansi.Params) Event {
1560	// Primary Device Attributes
1561	da1 := make(PrimaryDeviceAttributesEvent, len(params))
1562	for i, p := range params {
1563		if !p.HasMore() {
1564			da1[i] = p.Param(0)
1565		}
1566	}
1567	return da1
1568}
1569
1570func parseSecondaryDevAttrs(params ansi.Params) Event {
1571	// Secondary Device Attributes
1572	da2 := make(SecondaryDeviceAttributesEvent, len(params))
1573	for i, p := range params {
1574		if !p.HasMore() {
1575			da2[i] = p.Param(0)
1576		}
1577	}
1578	return da2
1579}
1580
1581func parseTertiaryDevAttrs(b []byte) Event {
1582	// Tertiary Device Attributes
1583	// The response is a 4-digit hexadecimal number.
1584	bts, err := hex.DecodeString(string(b))
1585	if err != nil {
1586		return UnknownDcsEvent(fmt.Sprintf("\x1bP!|%s\x1b\\", b))
1587	}
1588	return TertiaryDeviceAttributesEvent(bts)
1589}
1590
1591// Parse SGR-encoded mouse events; SGR extended mouse events. SGR mouse events
1592// look like:
1593//
1594//	ESC [ < Cb ; Cx ; Cy (M or m)
1595//
1596// where:
1597//
1598//	Cb is the encoded button code
1599//	Cx is the x-coordinate of the mouse
1600//	Cy is the y-coordinate of the mouse
1601//	M is for button press, m is for button release
1602//
1603// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
1604func parseSGRMouseEvent(cmd ansi.Cmd, params ansi.Params) Event {
1605	x, _, ok := params.Param(1, 1)
1606	if !ok {
1607		x = 1
1608	}
1609	y, _, ok := params.Param(2, 1)
1610	if !ok {
1611		y = 1
1612	}
1613	release := cmd.Final() == 'm'
1614	b, _, _ := params.Param(0, 0)
1615	mod, btn, _, isMotion := parseMouseButton(b)
1616
1617	// (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
1618	x--
1619	y--
1620
1621	m := Mouse{X: x, Y: y, Button: btn, Mod: mod}
1622
1623	// Wheel buttons don't have release events
1624	// Motion can be reported as a release event in some terminals (Windows Terminal)
1625	if isWheel(m.Button) {
1626		return MouseWheelEvent(m)
1627	} else if !isMotion && release {
1628		return MouseReleaseEvent(m)
1629	} else if isMotion {
1630		return MouseMotionEvent(m)
1631	}
1632	return MouseClickEvent(m)
1633}
1634
1635const x10MouseByteOffset = 32
1636
1637// Parse X10-encoded mouse events; the simplest kind. The last release of X10
1638// was December 1986, by the way. The original X10 mouse protocol limits the Cx
1639// and Cy coordinates to 223 (=255-032).
1640//
1641// X10 mouse events look like:
1642//
1643//	ESC [M Cb Cx Cy
1644//
1645// See: http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking
1646func parseX10MouseEvent(buf []byte) Event {
1647	v := buf[3:6]
1648	b := int(v[0])
1649	if b >= x10MouseByteOffset {
1650		// XXX: b < 32 should be impossible, but we're being defensive.
1651		b -= x10MouseByteOffset
1652	}
1653
1654	mod, btn, isRelease, isMotion := parseMouseButton(b)
1655
1656	// (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
1657	x := int(v[1]) - x10MouseByteOffset - 1
1658	y := int(v[2]) - x10MouseByteOffset - 1
1659
1660	m := Mouse{X: x, Y: y, Button: btn, Mod: mod}
1661	if isWheel(m.Button) {
1662		return MouseWheelEvent(m)
1663	} else if isMotion {
1664		return MouseMotionEvent(m)
1665	} else if isRelease {
1666		return MouseReleaseEvent(m)
1667	}
1668	return MouseClickEvent(m)
1669}
1670
1671// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
1672func parseMouseButton(b int) (mod KeyMod, btn MouseButton, isRelease bool, isMotion bool) {
1673	// mouse bit shifts
1674	const (
1675		bitShift  = 0b0000_0100
1676		bitAlt    = 0b0000_1000
1677		bitCtrl   = 0b0001_0000
1678		bitMotion = 0b0010_0000
1679		bitWheel  = 0b0100_0000
1680		bitAdd    = 0b1000_0000 // additional buttons 8-11
1681
1682		bitsMask = 0b0000_0011
1683	)
1684
1685	// Modifiers
1686	if b&bitAlt != 0 {
1687		mod |= ModAlt
1688	}
1689	if b&bitCtrl != 0 {
1690		mod |= ModCtrl
1691	}
1692	if b&bitShift != 0 {
1693		mod |= ModShift
1694	}
1695
1696	if b&bitAdd != 0 {
1697		btn = MouseBackward + MouseButton(b&bitsMask)
1698	} else if b&bitWheel != 0 {
1699		btn = MouseWheelUp + MouseButton(b&bitsMask)
1700	} else {
1701		btn = MouseLeft + MouseButton(b&bitsMask)
1702		// X10 reports a button release as 0b0000_0011 (3)
1703		if b&bitsMask == bitsMask {
1704			btn = MouseNone
1705			isRelease = true
1706		}
1707	}
1708
1709	// Motion bit doesn't get reported for wheel events.
1710	if b&bitMotion != 0 && !isWheel(btn) {
1711		isMotion = true
1712	}
1713
1714	return //nolint:nakedret
1715}
1716
1717// isWheel returns true if the mouse event is a wheel event.
1718func isWheel(btn MouseButton) bool {
1719	return btn >= MouseWheelUp && btn <= MouseWheelRight
1720}
1721
1722type shiftable interface {
1723	~uint | ~uint16 | ~uint32 | ~uint64
1724}
1725
1726func shift[T shiftable](x T) T {
1727	if x > 0xff {
1728		x >>= 8
1729	}
1730	return x
1731}
1732
1733func colorToHex(c color.Color) string {
1734	if c == nil {
1735		return ""
1736	}
1737	r, g, b, _ := c.RGBA()
1738	return fmt.Sprintf("#%02x%02x%02x", shift(r), shift(g), shift(b))
1739}
1740
1741func getMaxMin(a, b, c float64) (ma, mi float64) {
1742	if a > b {
1743		ma = a
1744		mi = b
1745	} else {
1746		ma = b
1747		mi = a
1748	}
1749	if c > ma {
1750		ma = c
1751	} else if c < mi {
1752		mi = c
1753	}
1754	return ma, mi
1755}
1756
1757func round(x float64) float64 {
1758	return math.Round(x*1000) / 1000
1759}
1760
1761// rgbToHSL converts an RGB triple to an HSL triple.
1762func rgbToHSL(r, g, b uint8) (h, s, l float64) {
1763	// convert uint32 pre-multiplied value to uint8
1764	// The r,g,b values are divided by 255 to change the range from 0..255 to 0..1:
1765	Rnot := float64(r) / 255
1766	Gnot := float64(g) / 255
1767	Bnot := float64(b) / 255
1768	Cmax, Cmin := getMaxMin(Rnot, Gnot, Bnot)
1769	Δ := Cmax - Cmin
1770	// Lightness calculation:
1771	l = (Cmax + Cmin) / 2
1772	// Hue and Saturation Calculation:
1773	if Δ == 0 {
1774		h = 0
1775		s = 0
1776	} else {
1777		switch Cmax {
1778		case Rnot:
1779			h = 60 * math.Mod((Gnot-Bnot)/Δ, 6)
1780		case Gnot:
1781			h = 60 * (((Bnot - Rnot) / Δ) + 2)
1782		case Bnot:
1783			h = 60 * (((Rnot - Gnot) / Δ) + 4)
1784		}
1785		if h < 0 {
1786			h += 360
1787		}
1788
1789		s = Δ / (1 - math.Abs((2*l)-1))
1790	}
1791
1792	return h, round(s), round(l)
1793}
1794
1795// isDarkColor returns whether the given color is dark.
1796func isDarkColor(c color.Color) bool {
1797	if c == nil {
1798		return true
1799	}
1800
1801	r, g, b, _ := c.RGBA()
1802	_, _, l := rgbToHSL(uint8(r>>8), uint8(g>>8), uint8(b>>8)) //nolint:gosec
1803	return l < 0.5
1804}
1805
1806func parseTermcap(data []byte) CapabilityEvent {
1807	// XTGETTCAP
1808	if len(data) == 0 {
1809		return CapabilityEvent{""}
1810	}
1811
1812	var tc strings.Builder
1813	split := bytes.Split(data, []byte{';'})
1814	for _, s := range split {
1815		parts := bytes.SplitN(s, []byte{'='}, 2)
1816		if len(parts) == 0 {
1817			return CapabilityEvent{""}
1818		}
1819
1820		name, err := hex.DecodeString(string(parts[0]))
1821		if err != nil || len(name) == 0 {
1822			continue
1823		}
1824
1825		var value []byte
1826		if len(parts) > 1 {
1827			value, err = hex.DecodeString(string(parts[1]))
1828			if err != nil {
1829				continue
1830			}
1831		}
1832
1833		if tc.Len() > 0 {
1834			tc.WriteByte(';')
1835		}
1836		tc.WriteString(string(name))
1837		if len(value) > 0 {
1838			tc.WriteByte('=')
1839			tc.WriteString(string(value))
1840		}
1841	}
1842
1843	return CapabilityEvent{tc.String()}
1844}
1845
1846// parseWin32InputKeyEvent converts a Windows Input Record Key Event into a
1847// KeyPressEvent, KeyReleaseEvent, or MultiEvent including multiple of the
1848// former type.
1849// A special case is when the key is either part of VtInputMode which produces
1850// vkc == 0, or when a key is a UTF-16 surrogate pair.  In both of these cases,
1851// we need to handle key encoding and properly parse the key event.
1852func (p *EventDecoder) parseWin32InputKeyEvent(vkc uint16, _ uint16, r rune, keyDown bool, cks uint32, repeatCount uint16) (event Event) {
1853	defer func() {
1854		// Respect the repeat count.
1855		if repeatCount > 1 {
1856			var multi MultiEvent
1857			for i := 0; i < int(repeatCount); i++ {
1858				multi = append(multi, event)
1859			}
1860			event = multi
1861		}
1862	}()
1863	defer func() {
1864		if vkc != 0 {
1865			p.lastCks = cks
1866		}
1867	}()
1868
1869	var key Key
1870	switch {
1871	case vkc == 0:
1872		// This is either a UTF-16 encoded pair, or an escape sequence waiting
1873		// to be decoded.
1874		if keyDown {
1875			return KeyPressEvent{Code: 0, BaseCode: r, Mod: translateControlKeyState(cks)}
1876		}
1877		return KeyReleaseEvent{Code: 0, BaseCode: r, Mod: translateControlKeyState(cks)}
1878	case vkc == xwindows.VK_BACK:
1879		key.BaseCode = KeyBackspace
1880	case vkc == xwindows.VK_TAB:
1881		key.BaseCode = KeyTab
1882	case vkc == xwindows.VK_RETURN:
1883		key.BaseCode = KeyEnter
1884	case vkc == xwindows.VK_SHIFT:
1885		//nolint:nestif
1886		if cks&xwindows.SHIFT_PRESSED != 0 {
1887			if cks&xwindows.ENHANCED_KEY != 0 {
1888				key.BaseCode = KeyRightShift
1889			} else {
1890				key.BaseCode = KeyLeftShift
1891			}
1892		} else if p.lastCks&xwindows.SHIFT_PRESSED != 0 {
1893			if p.lastCks&xwindows.ENHANCED_KEY != 0 {
1894				key.BaseCode = KeyRightShift
1895			} else {
1896				key.BaseCode = KeyLeftShift
1897			}
1898		}
1899	case vkc == xwindows.VK_CONTROL:
1900		if cks&xwindows.LEFT_CTRL_PRESSED != 0 {
1901			key.BaseCode = KeyLeftCtrl
1902		} else if cks&xwindows.RIGHT_CTRL_PRESSED != 0 {
1903			key.BaseCode = KeyRightCtrl
1904		} else if p.lastCks&xwindows.LEFT_CTRL_PRESSED != 0 {
1905			key.BaseCode = KeyLeftCtrl
1906		} else if p.lastCks&xwindows.RIGHT_CTRL_PRESSED != 0 {
1907			key.BaseCode = KeyRightCtrl
1908		}
1909	case vkc == xwindows.VK_MENU:
1910		if cks&xwindows.LEFT_ALT_PRESSED != 0 {
1911			key.BaseCode = KeyLeftAlt
1912		} else if cks&xwindows.RIGHT_ALT_PRESSED != 0 {
1913			key.BaseCode = KeyRightAlt
1914		} else if p.lastCks&xwindows.LEFT_ALT_PRESSED != 0 {
1915			key.BaseCode = KeyLeftAlt
1916		} else if p.lastCks&xwindows.RIGHT_ALT_PRESSED != 0 {
1917			key.BaseCode = KeyRightAlt
1918		}
1919	case vkc == xwindows.VK_PAUSE:
1920		key.BaseCode = KeyPause
1921	case vkc == xwindows.VK_CAPITAL:
1922		key.BaseCode = KeyCapsLock
1923	case vkc == xwindows.VK_ESCAPE:
1924		key.BaseCode = KeyEscape
1925	case vkc == xwindows.VK_SPACE:
1926		key.BaseCode = KeySpace
1927	case vkc == xwindows.VK_PRIOR:
1928		key.BaseCode = KeyPgUp
1929	case vkc == xwindows.VK_NEXT:
1930		key.BaseCode = KeyPgDown
1931	case vkc == xwindows.VK_END:
1932		key.BaseCode = KeyEnd
1933	case vkc == xwindows.VK_HOME:
1934		key.BaseCode = KeyHome
1935	case vkc == xwindows.VK_LEFT:
1936		key.BaseCode = KeyLeft
1937	case vkc == xwindows.VK_UP:
1938		key.BaseCode = KeyUp
1939	case vkc == xwindows.VK_RIGHT:
1940		key.BaseCode = KeyRight
1941	case vkc == xwindows.VK_DOWN:
1942		key.BaseCode = KeyDown
1943	case vkc == xwindows.VK_SELECT:
1944		key.BaseCode = KeySelect
1945	case vkc == xwindows.VK_SNAPSHOT:
1946		key.BaseCode = KeyPrintScreen
1947	case vkc == xwindows.VK_INSERT:
1948		key.BaseCode = KeyInsert
1949	case vkc == xwindows.VK_DELETE:
1950		key.BaseCode = KeyDelete
1951	case vkc >= '0' && vkc <= '9':
1952		key.BaseCode = rune(vkc)
1953	case vkc >= 'A' && vkc <= 'Z':
1954		// Convert to lowercase.
1955		key.BaseCode = rune(vkc) + 32
1956	case vkc == xwindows.VK_LWIN:
1957		key.BaseCode = KeyLeftSuper
1958	case vkc == xwindows.VK_RWIN:
1959		key.BaseCode = KeyRightSuper
1960	case vkc == xwindows.VK_APPS:
1961		key.BaseCode = KeyMenu
1962	case vkc >= xwindows.VK_NUMPAD0 && vkc <= xwindows.VK_NUMPAD9:
1963		key.BaseCode = rune(vkc-xwindows.VK_NUMPAD0) + KeyKp0
1964		key.Text = string('0' + (rune(vkc) - xwindows.VK_NUMPAD0))
1965	case vkc == xwindows.VK_MULTIPLY:
1966		key.BaseCode = KeyKpMultiply
1967		key.Text = "*"
1968	case vkc == xwindows.VK_ADD:
1969		key.BaseCode = KeyKpPlus
1970		key.Text = "+"
1971	case vkc == xwindows.VK_SEPARATOR:
1972		key.BaseCode = KeyKpComma
1973		key.Text = ","
1974	case vkc == xwindows.VK_SUBTRACT:
1975		key.BaseCode = KeyKpMinus
1976		key.Text = "-"
1977	case vkc == xwindows.VK_DECIMAL:
1978		key.BaseCode = KeyKpDecimal
1979		key.Text = "."
1980	case vkc == xwindows.VK_DIVIDE:
1981		key.BaseCode = KeyKpDivide
1982		key.Text = "/"
1983	case vkc >= xwindows.VK_F1 && vkc <= xwindows.VK_F24:
1984		key.BaseCode = rune(vkc-xwindows.VK_F1) + KeyF1
1985	case vkc == xwindows.VK_NUMLOCK:
1986		key.BaseCode = KeyNumLock
1987	case vkc == xwindows.VK_SCROLL:
1988		key.BaseCode = KeyScrollLock
1989	case vkc == xwindows.VK_LSHIFT:
1990		key.BaseCode = KeyLeftShift
1991	case vkc == xwindows.VK_RSHIFT:
1992		key.BaseCode = KeyRightShift
1993	case vkc == xwindows.VK_LCONTROL:
1994		key.BaseCode = KeyLeftCtrl
1995	case vkc == xwindows.VK_RCONTROL:
1996		key.BaseCode = KeyRightCtrl
1997	case vkc == xwindows.VK_LMENU:
1998		key.BaseCode = KeyLeftAlt
1999	case vkc == xwindows.VK_RMENU:
2000		key.BaseCode = KeyRightAlt
2001	case vkc == xwindows.VK_VOLUME_MUTE:
2002		key.BaseCode = KeyMute
2003	case vkc == xwindows.VK_VOLUME_DOWN:
2004		key.BaseCode = KeyLowerVol
2005	case vkc == xwindows.VK_VOLUME_UP:
2006		key.BaseCode = KeyRaiseVol
2007	case vkc == xwindows.VK_MEDIA_NEXT_TRACK:
2008		key.BaseCode = KeyMediaNext
2009	case vkc == xwindows.VK_MEDIA_PREV_TRACK:
2010		key.BaseCode = KeyMediaPrev
2011	case vkc == xwindows.VK_MEDIA_STOP:
2012		key.BaseCode = KeyMediaStop
2013	case vkc == xwindows.VK_MEDIA_PLAY_PAUSE:
2014		key.BaseCode = KeyMediaPlayPause
2015	case vkc == xwindows.VK_OEM_1:
2016		key.BaseCode = ';'
2017	case vkc == xwindows.VK_OEM_PLUS:
2018		key.BaseCode = '+'
2019	case vkc == xwindows.VK_OEM_COMMA:
2020		key.BaseCode = ','
2021	case vkc == xwindows.VK_OEM_MINUS:
2022		key.BaseCode = '-'
2023	case vkc == xwindows.VK_OEM_PERIOD:
2024		key.BaseCode = '.'
2025	case vkc == xwindows.VK_OEM_2:
2026		key.BaseCode = '/'
2027	case vkc == xwindows.VK_OEM_3:
2028		key.BaseCode = '`'
2029	case vkc == xwindows.VK_OEM_4:
2030		key.BaseCode = '['
2031	case vkc == xwindows.VK_OEM_5:
2032		key.BaseCode = '\\'
2033	case vkc == xwindows.VK_OEM_6:
2034		key.BaseCode = ']'
2035	case vkc == xwindows.VK_OEM_7:
2036		key.BaseCode = '\''
2037	}
2038
2039	// AltGr is left ctrl + right alt. On non-US keyboards, this is used to type
2040	// special characters and produce printable events.
2041	// XXX: Should this be a KeyMod?
2042	const altGrPressed = xwindows.LEFT_CTRL_PRESSED | xwindows.RIGHT_ALT_PRESSED
2043	altGr := cks&altGrPressed == altGrPressed
2044
2045	// Remove these lock keys from the control key state from now on.
2046	cks &^= xwindows.NUMLOCK_ON
2047	cks &^= xwindows.SCROLLLOCK_ON
2048	key.Code = key.BaseCode
2049	if !unicode.IsControl(r) {
2050		key.Code = r
2051		if unicode.IsPrint(key.Code) && (cks == 0 || // no modifiers pressed
2052			cks == xwindows.SHIFT_PRESSED || // shift pressed
2053			cks == xwindows.CAPSLOCK_ON || // caps lock on
2054			cks == (xwindows.SHIFT_PRESSED|xwindows.CAPSLOCK_ON) || // Shift + caps lock pressed
2055			altGr) { // AltGr pressed
2056			// If the control key state is 0, shift is pressed, or caps lock
2057			// then the key event is a printable event i.e. [text] is not empty.
2058			key.Text = string(key.Code)
2059		}
2060	}
2061
2062	key.Mod = translateControlKeyState(cks)
2063	key = ensureKeyCase(key, cks)
2064	if keyDown {
2065		return KeyPressEvent(key)
2066	}
2067
2068	return KeyReleaseEvent(key)
2069}
2070
2071// ensureKeyCase ensures that the key's text is in the correct case based on the
2072// control key state.
2073func ensureKeyCase(key Key, cks uint32) Key {
2074	if len(key.Text) == 0 {
2075		return key
2076	}
2077
2078	hasShift := cks&xwindows.SHIFT_PRESSED != 0
2079	hasCaps := cks&xwindows.CAPSLOCK_ON != 0
2080	if hasShift || hasCaps {
2081		if unicode.IsLower(key.Code) {
2082			key.ShiftedCode = unicode.ToUpper(key.Code)
2083			key.Text = string(key.ShiftedCode)
2084		}
2085	} else {
2086		if unicode.IsUpper(key.Code) {
2087			key.ShiftedCode = unicode.ToLower(key.Code)
2088			key.Text = string(key.ShiftedCode)
2089		}
2090	}
2091
2092	return key
2093}
2094
2095// translateControlKeyState translates the control key state from the Windows
2096// Console API into a Mod bitmask.
2097func translateControlKeyState(cks uint32) (m KeyMod) {
2098	if cks&xwindows.LEFT_CTRL_PRESSED != 0 || cks&xwindows.RIGHT_CTRL_PRESSED != 0 {
2099		m |= ModCtrl
2100	}
2101	if cks&xwindows.LEFT_ALT_PRESSED != 0 || cks&xwindows.RIGHT_ALT_PRESSED != 0 {
2102		m |= ModAlt
2103	}
2104	if cks&xwindows.SHIFT_PRESSED != 0 {
2105		m |= ModShift
2106	}
2107	if cks&xwindows.CAPSLOCK_ON != 0 {
2108		m |= ModCapsLock
2109	}
2110	if cks&xwindows.NUMLOCK_ON != 0 {
2111		m |= ModNumLock
2112	}
2113	if cks&xwindows.SCROLLLOCK_ON != 0 {
2114		m |= ModScrollLock
2115	}
2116	return
2117}