okami.mom/ultraviolet

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

Kieran Klukaschore: satisfy the newer gofumpt6d3a341

upstream
53.5 KiB2102 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
823			// If we don't have a valid ST terminator, then this is a
824			// cancelled sequence and should be ignored.
825			return i, ignoredEvent(b[:i])
826		}
827
828		i++
829	}
830
831	if end <= start {
832		return i, UnknownEvent(b[:i])
833	}
834
835	data := string(b[start:end])
836	switch cmd {
837	case 10:
838		return i, ForegroundColorEvent{ansi.XParseColor(data)}
839	case 11:
840		return i, BackgroundColorEvent{ansi.XParseColor(data)}
841	case 12:
842		return i, CursorColorEvent{ansi.XParseColor(data)}
843	case 52:
844		parts := strings.Split(data, ";")
845		if len(parts) != 2 || len(parts[0]) < 1 {
846			return i, ClipboardEvent{}
847		}
848
849		b64 := parts[1]
850		bts, err := base64.StdEncoding.DecodeString(b64)
851		if err != nil {
852			return i, ClipboardEvent{Content: parts[1]}
853		}
854
855		sel := ClipboardSelection(parts[0][0]) //nolint:unconvert
856		return i, ClipboardEvent{Selection: sel, Content: string(bts)}
857	}
858
859	return i, UnknownOscEvent(b[:i])
860}
861
862// parseStTerminated parses a control sequence that gets terminated by a ST character.
863func (p *EventDecoder) parseStTerminated(intro8, intro7 byte, fn func([]byte) Event) func([]byte) (int, Event) {
864	defaultKey := func(b []byte) (int, Event) {
865		switch intro8 {
866		case ansi.SOS:
867			return 2, KeyPressEvent{Code: unicode.ToLower(rune(b[1])), Mod: ModShift | ModAlt}
868		case ansi.PM, ansi.APC:
869			return 2, KeyPressEvent{Code: rune(b[1]), Mod: ModAlt}
870		}
871		return 0, nil
872	}
873	return func(b []byte) (int, Event) {
874		if len(b) == 2 && b[0] == ansi.ESC {
875			return defaultKey(b)
876		}
877
878		var i int
879		if b[i] == intro8 || b[i] == ansi.ESC {
880			i++
881		}
882		if i < len(b) && b[i-1] == ansi.ESC && b[i] == intro7 {
883			i++
884		}
885
886		// Scan control sequence
887		// Most common control sequence is terminated by a ST character
888		// ST is a 7-bit string terminator character is (ESC \)
889		start := i
890		for ; i < len(b); i++ {
891			if slices.Contains([]byte{ansi.ESC, ansi.ST, ansi.CAN, ansi.SUB}, b[i]) {
892				break
893			}
894		}
895
896		if i >= len(b) {
897			return i, UnknownEvent(b[:i])
898		}
899
900		end := i // end of the sequence data
901		i++
902
903		// Check 7-bit ST (string terminator) character
904		switch b[i-1] {
905		case ansi.CAN, ansi.SUB:
906			return i, ignoredEvent(b[:i])
907		case ansi.ESC:
908			if i >= len(b) || b[i] != '\\' {
909				if start == end {
910					return defaultKey(b)
911				}
912
913				// If we don't have a valid ST terminator, then this is a
914				// cancelled sequence and should be ignored.
915				return i, ignoredEvent(b[:i])
916			}
917
918			i++
919		}
920
921		// Call the function to parse the sequence and return the result
922		if fn != nil {
923			if e := fn(b[start:end]); e != nil {
924				return i, e
925			}
926		}
927
928		switch intro8 {
929		case ansi.PM:
930			return i, UnknownPmEvent(b[:i])
931		case ansi.SOS:
932			return i, UnknownSosEvent(b[:i])
933		case ansi.APC:
934			return i, UnknownApcEvent(b[:i])
935		default:
936			return i, UnknownEvent(b[:i])
937		}
938	}
939}
940
941func (p *EventDecoder) parseDcs(b []byte) (int, Event) {
942	if len(b) == 2 && b[0] == ansi.ESC {
943		// short cut if this is an alt+P key
944		return 2, KeyPressEvent{Code: unicode.ToLower(rune(b[1])), Mod: ModShift | ModAlt}
945	}
946
947	var params [16]ansi.Param
948	var paramsLen int
949	var cmd ansi.Cmd
950
951	// DCS sequences are introduced by DCS (0x90) or ESC P (0x1b 0x50)
952	var i int
953	if b[i] == ansi.DCS || b[i] == ansi.ESC {
954		i++
955	}
956	if i < len(b) && b[i-1] == ansi.ESC && b[i] == 'P' {
957		i++
958	}
959
960	// initial DCS byte
961	if i < len(b) && b[i] >= '<' && b[i] <= '?' {
962		cmd |= ansi.Cmd(b[i]) << parser.PrefixShift
963	}
964
965	// Scan parameter bytes in the range 0x30-0x3F
966	var j int
967	for j = 0; i < len(b) && paramsLen < len(params) && b[i] >= 0x30 && b[i] <= 0x3F; i, j = i+1, j+1 {
968		if b[i] >= '0' && b[i] <= '9' {
969			if params[paramsLen] == parser.MissingParam {
970				params[paramsLen] = 0
971			}
972			params[paramsLen] *= 10
973			params[paramsLen] += ansi.Param(b[i]) - '0'
974		}
975		if b[i] == ':' {
976			params[paramsLen] |= parser.HasMoreFlag
977		}
978		if b[i] == ';' || b[i] == ':' {
979			paramsLen++
980			if paramsLen < len(params) {
981				// Don't overflow the params slice
982				params[paramsLen] = parser.MissingParam
983			}
984		}
985	}
986
987	if j > 0 && paramsLen < len(params) {
988		// has parameters
989		paramsLen++
990	}
991
992	// Scan intermediate bytes in the range 0x20-0x2F
993	var intermed byte
994	for j := 0; i < len(b) && b[i] >= 0x20 && b[i] <= 0x2F; i, j = i+1, j+1 {
995		intermed = b[i]
996	}
997
998	// set intermediate byte
999	cmd |= ansi.Cmd(intermed) << parser.IntermedShift
1000
1001	// Scan final byte in the range 0x40-0x7E
1002	if i >= len(b) || b[i] < 0x40 || b[i] > 0x7E {
1003		return i, UnknownEvent(b[:i])
1004	}
1005
1006	// Add the final byte
1007	cmd |= ansi.Cmd(b[i])
1008	i++
1009
1010	start := i // start of the sequence data
1011	for ; i < len(b); i++ {
1012		if b[i] == ansi.ST || b[i] == ansi.ESC {
1013			break
1014		}
1015	}
1016
1017	if i >= len(b) {
1018		return i, UnknownEvent(b[:i])
1019	}
1020
1021	end := i // end of the sequence data
1022	i++
1023
1024	// Check 7-bit ST (string terminator) character
1025	if i < len(b) && b[i-1] == ansi.ESC && b[i] == '\\' {
1026		i++
1027	}
1028
1029	pa := ansi.Params(params[:paramsLen])
1030	switch cmd {
1031	case 'r' | '+'<<parser.IntermedShift:
1032		// XTGETTCAP responses
1033		param, _, _ := pa.Param(0, 0)
1034		switch param {
1035		case 1: // 1 means valid response, 0 means invalid response
1036			tc := parseTermcap(b[start:end])
1037			// XXX: some terminals like KiTTY report invalid responses with
1038			// their queries i.e. sending a query for "Tc" using "\x1bP+q5463\x1b\\"
1039			// returns "\x1bP0+r5463\x1b\\".
1040			// The specs says that invalid responses should be in the form of
1041			// DCS 0 + r ST "\x1bP0+r\x1b\\"
1042			// We ignore invalid responses and only send valid ones to the program.
1043			//
1044			// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
1045			return i, tc
1046		}
1047	case '|' | '>'<<parser.PrefixShift:
1048		// XTVersion response
1049		return i, TerminalVersionEvent{string(b[start:end])}
1050	case '|' | '!'<<parser.IntermedShift:
1051		// Teritary Device Attributes
1052		return i, parseTertiaryDevAttrs(b[start:end])
1053	}
1054
1055	return i, UnknownDcsEvent(b[:i])
1056}
1057
1058func (p *EventDecoder) parseApc(b []byte) (int, Event) {
1059	if len(b) == 2 && b[0] == ansi.ESC {
1060		// short cut if this is an alt+_ key
1061		return 2, KeyPressEvent{Code: rune(b[1]), Mod: ModAlt}
1062	}
1063
1064	// APC sequences are introduced by APC (0x9f) or ESC _ (0x1b 0x5f)
1065	return p.parseStTerminated(ansi.APC, '_', func(b []byte) Event {
1066		if len(b) == 0 {
1067			return nil
1068		}
1069
1070		switch b[0] {
1071		case 'G': // Kitty Graphics Protocol
1072			var g KittyGraphicsEvent
1073			parts := bytes.Split(b[1:], []byte{';'})
1074			g.Options.UnmarshalText(parts[0]) //nolint:errcheck,gosec
1075			if len(parts) > 1 {
1076				g.Payload = parts[1]
1077			}
1078			return g
1079		}
1080
1081		return nil
1082	})(b)
1083}
1084
1085func (p *EventDecoder) parseUtf8(b []byte) (int, Event) {
1086	if len(b) == 0 {
1087		return 0, nil
1088	}
1089
1090	c := b[0]
1091	if c <= ansi.US || c == ansi.DEL {
1092		// Control codes get handled by parseControl
1093		return 1, p.parseControl(c)
1094	} else if c > ansi.US && c < ansi.DEL {
1095		// ASCII printable characters
1096		code := rune(c)
1097		k := KeyPressEvent{Code: code, Text: string(code)}
1098		if unicode.IsUpper(code) {
1099			// Convert upper case letters to lower case + shift modifier
1100			k.Code = unicode.ToLower(code)
1101			k.ShiftedCode = code
1102			k.Mod |= ModShift
1103		}
1104
1105		return 1, k
1106	}
1107
1108	code, _ := utf8.DecodeRune(b)
1109	if code == utf8.RuneError {
1110		return 1, UnknownEvent(b[0])
1111	}
1112
1113	cluster, _, _, _ := uniseg.FirstGraphemeCluster(b, -1)
1114	text := string(cluster)
1115	for i := range text {
1116		if i > 0 {
1117			// Use [KeyExtended] for multi-rune graphemes
1118			code = KeyExtended
1119			break
1120		}
1121	}
1122
1123	return len(cluster), KeyPressEvent{Code: code, Text: text}
1124}
1125
1126func (p *EventDecoder) parseControl(b byte) Event {
1127	switch b {
1128	case ansi.NUL:
1129		if p.Legacy&flagCtrlAt != 0 {
1130			return KeyPressEvent{Code: '@', Mod: ModCtrl}
1131		}
1132		return KeyPressEvent{Code: KeySpace, Mod: ModCtrl}
1133	case ansi.BS:
1134		return KeyPressEvent{Code: 'h', Mod: ModCtrl}
1135	case ansi.HT:
1136		if p.Legacy&flagCtrlI != 0 {
1137			return KeyPressEvent{Code: 'i', Mod: ModCtrl}
1138		}
1139		return KeyPressEvent{Code: KeyTab}
1140	case ansi.CR:
1141		if p.Legacy&flagCtrlM != 0 {
1142			return KeyPressEvent{Code: 'm', Mod: ModCtrl}
1143		}
1144		return KeyPressEvent{Code: KeyEnter}
1145	case ansi.ESC:
1146		if p.Legacy&flagCtrlOpenBracket != 0 {
1147			return KeyPressEvent{Code: '[', Mod: ModCtrl}
1148		}
1149		return KeyPressEvent{Code: KeyEscape}
1150	case ansi.DEL:
1151		if p.Legacy&flagBackspace != 0 {
1152			return KeyPressEvent{Code: KeyDelete}
1153		}
1154		return KeyPressEvent{Code: KeyBackspace}
1155	case ansi.SP:
1156		return KeyPressEvent{Code: KeySpace, Text: " "}
1157	default:
1158		if b >= ansi.SOH && b <= ansi.SUB {
1159			// Use lower case letters for control codes
1160			code := rune(b + 0x60)
1161			return KeyPressEvent{Code: code, Mod: ModCtrl}
1162		} else if b >= ansi.FS && b <= ansi.US {
1163			code := rune(b + 0x40)
1164			return KeyPressEvent{Code: code, Mod: ModCtrl}
1165		}
1166		return UnknownEvent(b)
1167	}
1168}
1169
1170func parseXTermModifyOtherKeys(params ansi.Params) Event {
1171	// XTerm modify other keys starts with ESC [ 27 ; <modifier> ; <code> ~
1172	xmod, _, _ := params.Param(1, 1)
1173	xrune, _, _ := params.Param(2, 1)
1174	mod := KeyMod(xmod - 1)
1175	r := rune(xrune)
1176
1177	switch r {
1178	case ansi.BS:
1179		return KeyPressEvent{Mod: mod, Code: KeyBackspace}
1180	case ansi.HT:
1181		return KeyPressEvent{Mod: mod, Code: KeyTab}
1182	case ansi.CR:
1183		return KeyPressEvent{Mod: mod, Code: KeyEnter}
1184	case ansi.ESC:
1185		return KeyPressEvent{Mod: mod, Code: KeyEscape}
1186	case ansi.DEL:
1187		return KeyPressEvent{Mod: mod, Code: KeyBackspace}
1188	}
1189
1190	// CSI 27 ; <modifier> ; <code> ~ keys defined in XTerm modifyOtherKeys
1191	k := KeyPressEvent{Code: r, Mod: mod}
1192	if k.Mod <= ModShift {
1193		k.Text = string(r)
1194	}
1195
1196	return k
1197}
1198
1199// Kitty Clipboard Control Sequences.
1200var kittyKeyMap = map[int]Key{
1201	ansi.BS:  {Code: KeyBackspace},
1202	ansi.HT:  {Code: KeyTab},
1203	ansi.CR:  {Code: KeyEnter},
1204	ansi.ESC: {Code: KeyEscape},
1205	ansi.DEL: {Code: KeyBackspace},
1206
1207	57344: {Code: KeyEscape},
1208	57345: {Code: KeyEnter},
1209	57346: {Code: KeyTab},
1210	57347: {Code: KeyBackspace},
1211	57348: {Code: KeyInsert},
1212	57349: {Code: KeyDelete},
1213	57350: {Code: KeyLeft},
1214	57351: {Code: KeyRight},
1215	57352: {Code: KeyUp},
1216	57353: {Code: KeyDown},
1217	57354: {Code: KeyPgUp},
1218	57355: {Code: KeyPgDown},
1219	57356: {Code: KeyHome},
1220	57357: {Code: KeyEnd},
1221	57358: {Code: KeyCapsLock},
1222	57359: {Code: KeyScrollLock},
1223	57360: {Code: KeyNumLock},
1224	57361: {Code: KeyPrintScreen},
1225	57362: {Code: KeyPause},
1226	57363: {Code: KeyMenu},
1227	57364: {Code: KeyF1},
1228	57365: {Code: KeyF2},
1229	57366: {Code: KeyF3},
1230	57367: {Code: KeyF4},
1231	57368: {Code: KeyF5},
1232	57369: {Code: KeyF6},
1233	57370: {Code: KeyF7},
1234	57371: {Code: KeyF8},
1235	57372: {Code: KeyF9},
1236	57373: {Code: KeyF10},
1237	57374: {Code: KeyF11},
1238	57375: {Code: KeyF12},
1239	57376: {Code: KeyF13},
1240	57377: {Code: KeyF14},
1241	57378: {Code: KeyF15},
1242	57379: {Code: KeyF16},
1243	57380: {Code: KeyF17},
1244	57381: {Code: KeyF18},
1245	57382: {Code: KeyF19},
1246	57383: {Code: KeyF20},
1247	57384: {Code: KeyF21},
1248	57385: {Code: KeyF22},
1249	57386: {Code: KeyF23},
1250	57387: {Code: KeyF24},
1251	57388: {Code: KeyF25},
1252	57389: {Code: KeyF26},
1253	57390: {Code: KeyF27},
1254	57391: {Code: KeyF28},
1255	57392: {Code: KeyF29},
1256	57393: {Code: KeyF30},
1257	57394: {Code: KeyF31},
1258	57395: {Code: KeyF32},
1259	57396: {Code: KeyF33},
1260	57397: {Code: KeyF34},
1261	57398: {Code: KeyF35},
1262	57399: {Code: KeyKp0},
1263	57400: {Code: KeyKp1},
1264	57401: {Code: KeyKp2},
1265	57402: {Code: KeyKp3},
1266	57403: {Code: KeyKp4},
1267	57404: {Code: KeyKp5},
1268	57405: {Code: KeyKp6},
1269	57406: {Code: KeyKp7},
1270	57407: {Code: KeyKp8},
1271	57408: {Code: KeyKp9},
1272	57409: {Code: KeyKpDecimal},
1273	57410: {Code: KeyKpDivide},
1274	57411: {Code: KeyKpMultiply},
1275	57412: {Code: KeyKpMinus},
1276	57413: {Code: KeyKpPlus},
1277	57414: {Code: KeyKpEnter},
1278	57415: {Code: KeyKpEqual},
1279	57416: {Code: KeyKpSep},
1280	57417: {Code: KeyKpLeft},
1281	57418: {Code: KeyKpRight},
1282	57419: {Code: KeyKpUp},
1283	57420: {Code: KeyKpDown},
1284	57421: {Code: KeyKpPgUp},
1285	57422: {Code: KeyKpPgDown},
1286	57423: {Code: KeyKpHome},
1287	57424: {Code: KeyKpEnd},
1288	57425: {Code: KeyKpInsert},
1289	57426: {Code: KeyKpDelete},
1290	57427: {Code: KeyKpBegin},
1291	57428: {Code: KeyMediaPlay},
1292	57429: {Code: KeyMediaPause},
1293	57430: {Code: KeyMediaPlayPause},
1294	57431: {Code: KeyMediaReverse},
1295	57432: {Code: KeyMediaStop},
1296	57433: {Code: KeyMediaFastForward},
1297	57434: {Code: KeyMediaRewind},
1298	57435: {Code: KeyMediaNext},
1299	57436: {Code: KeyMediaPrev},
1300	57437: {Code: KeyMediaRecord},
1301	57438: {Code: KeyLowerVol},
1302	57439: {Code: KeyRaiseVol},
1303	57440: {Code: KeyMute},
1304	57441: {Code: KeyLeftShift},
1305	57442: {Code: KeyLeftCtrl},
1306	57443: {Code: KeyLeftAlt},
1307	57444: {Code: KeyLeftSuper},
1308	57445: {Code: KeyLeftHyper},
1309	57446: {Code: KeyLeftMeta},
1310	57447: {Code: KeyRightShift},
1311	57448: {Code: KeyRightCtrl},
1312	57449: {Code: KeyRightAlt},
1313	57450: {Code: KeyRightSuper},
1314	57451: {Code: KeyRightHyper},
1315	57452: {Code: KeyRightMeta},
1316	57453: {Code: KeyIsoLevel3Shift},
1317	57454: {Code: KeyIsoLevel5Shift},
1318}
1319
1320func init() {
1321	// These are some faulty C0 mappings some terminals such as WezTerm have
1322	// and doesn't follow the specs.
1323	kittyKeyMap[ansi.NUL] = Key{Code: KeySpace, Mod: ModCtrl}
1324	for i := ansi.SOH; i <= ansi.SUB; i++ {
1325		if _, ok := kittyKeyMap[i]; !ok {
1326			kittyKeyMap[i] = Key{Code: rune(i + 0x60), Mod: ModCtrl}
1327		}
1328	}
1329	for i := ansi.FS; i <= ansi.US; i++ {
1330		if _, ok := kittyKeyMap[i]; !ok {
1331			kittyKeyMap[i] = Key{Code: rune(i + 0x40), Mod: ModCtrl}
1332		}
1333	}
1334}
1335
1336const (
1337	kittyShift = 1 << iota
1338	kittyAlt
1339	kittyCtrl
1340	kittySuper
1341	kittyHyper
1342	kittyMeta
1343	kittyCapsLock
1344	kittyNumLock
1345)
1346
1347func fromKittyMod(mod int) KeyMod {
1348	var m KeyMod
1349	if mod&kittyShift != 0 {
1350		m |= ModShift
1351	}
1352	if mod&kittyAlt != 0 {
1353		m |= ModAlt
1354	}
1355	if mod&kittyCtrl != 0 {
1356		m |= ModCtrl
1357	}
1358	if mod&kittySuper != 0 {
1359		m |= ModSuper
1360	}
1361	if mod&kittyHyper != 0 {
1362		m |= ModHyper
1363	}
1364	if mod&kittyMeta != 0 {
1365		m |= ModMeta
1366	}
1367	if mod&kittyCapsLock != 0 {
1368		m |= ModCapsLock
1369	}
1370	if mod&kittyNumLock != 0 {
1371		m |= ModNumLock
1372	}
1373	return m
1374}
1375
1376// parseKittyKeyboard parses a Kitty Keyboard Protocol sequence.
1377//
1378// In `CSI u`, this is parsed as:
1379//
1380//	CSI codepoint ; modifiers u
1381//	codepoint: ASCII Dec value
1382//
1383// The Kitty Keyboard Protocol extends this with optional components that can be
1384// enabled progressively. The full sequence is parsed as:
1385//
1386//	CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u
1387//
1388// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/
1389func parseKittyKeyboard(params ansi.Params) (Event Event) {
1390	var isRelease bool
1391	var key Key
1392
1393	// The index of parameters separated by semicolons ';'. Sub parameters are
1394	// separated by colons ':'.
1395	var paramIdx int
1396	var sudIdx int // The sub parameter index
1397	for _, p := range params {
1398		// Kitty Keyboard Protocol has 3 optional components.
1399		switch paramIdx {
1400		case 0:
1401			switch sudIdx {
1402			case 0:
1403				var foundKey bool
1404				code := p.Param(1) // CSI u has a default value of 1
1405				key, foundKey = kittyKeyMap[code]
1406				if !foundKey {
1407					r := rune(code)
1408					if !utf8.ValidRune(r) {
1409						r = utf8.RuneError
1410					}
1411
1412					key.Code = r
1413				}
1414
1415			case 2:
1416				// shifted key + base key
1417				if b := rune(p.Param(1)); unicode.IsPrint(b) {
1418					// XXX: When alternate key reporting is enabled, the protocol
1419					// can return 3 things, the unicode codepoint of the key,
1420					// the shifted codepoint of the key, and the standard
1421					// PC-101 key layout codepoint.
1422					// This is useful to create an unambiguous mapping of keys
1423					// when using a different language layout.
1424					key.BaseCode = b
1425				}
1426				fallthrough
1427
1428			case 1:
1429				// shifted key
1430				if s := rune(p.Param(1)); unicode.IsPrint(s) {
1431					// XXX: We swap keys here because we want the shifted key
1432					// to be the Rune that is returned by the event.
1433					// For example, shift+a should produce "A" not "a".
1434					// In such a case, we set AltRune to the original key "a"
1435					// and Rune to "A".
1436					key.ShiftedCode = s
1437				}
1438			}
1439		case 1:
1440			switch sudIdx {
1441			case 0:
1442				mod := p.Param(1)
1443				if mod > 1 {
1444					key.Mod = fromKittyMod(mod - 1)
1445					if key.Mod > ModShift {
1446						// XXX: We need to clear the text if we have a modifier key
1447						// other than a [ModShift] key.
1448						key.Text = ""
1449					}
1450				}
1451
1452			case 1:
1453				switch p.Param(1) {
1454				case 2:
1455					key.IsRepeat = true
1456				case 3:
1457					isRelease = true
1458				}
1459			case 2:
1460			}
1461		case 2:
1462			if code := p.Param(0); code != 0 {
1463				key.Text += string(rune(code))
1464			}
1465		}
1466
1467		sudIdx++
1468		if !p.HasMore() {
1469			paramIdx++
1470			sudIdx = 0
1471		}
1472	}
1473
1474	keyMod := key.Mod
1475
1476	// Remove these lock modifiers from now on since they don't affect the text.
1477	keyMod &^= ModNumLock
1478	// keyMod &^= ModScrollLock // Kitty doesn't support scroll lock
1479
1480	printMod := keyMod <= ModShift || keyMod == ModCapsLock || keyMod == (ModShift|ModCapsLock)
1481	printKeyPad := key.Code >= KeyKpEqual && key.Code <= KeyKpSep
1482	if len(key.Text) == 0 && printKeyPad && printMod {
1483		switch {
1484		case key.Code >= KeyKp0 && key.Code <= KeyKp9:
1485			key.Text = string('0' + key.Code - KeyKp0)
1486		case key.Code == KeyKpEqual:
1487			key.Text = "="
1488		case key.Code == KeyKpMultiply:
1489			key.Text = "*"
1490		case key.Code == KeyKpPlus:
1491			key.Text = "+"
1492		case key.Code == KeyKpMinus:
1493			key.Text = "-"
1494		case key.Code == KeyKpDecimal:
1495			key.Text = "."
1496		case key.Code == KeyKpDivide:
1497			key.Text = "/"
1498		case key.Code == KeyKpSep:
1499			key.Text = ","
1500		}
1501	}
1502
1503	//nolint:nestif
1504	if len(key.Text) == 0 && unicode.IsPrint(key.Code) && printMod {
1505		if keyMod == 0 {
1506			key.Text = string(key.Code)
1507		} else {
1508			desiredCase := unicode.ToLower
1509			if keyMod.Contains(ModShift) || keyMod.Contains(ModCapsLock) {
1510				desiredCase = unicode.ToUpper
1511			}
1512			if key.ShiftedCode != 0 {
1513				key.Text = string(key.ShiftedCode)
1514			} else {
1515				key.Text = string(desiredCase(key.Code))
1516			}
1517		}
1518	}
1519
1520	if isRelease {
1521		return KeyReleaseEvent(key)
1522	}
1523
1524	return KeyPressEvent(key)
1525}
1526
1527// parseKittyKeyboardExt parses a Kitty Keyboard Protocol sequence extensions
1528// for non CSI u sequences. This includes things like CSI A, SS3 A and others,
1529// and CSI ~.
1530func parseKittyKeyboardExt(params ansi.Params, k KeyPressEvent) Event {
1531	// Handle Kitty keyboard protocol
1532	if len(params) > 2 && // We have at least 3 parameters
1533		params[1].HasMore() { // The second parameter is a subparameter (separated by a ":")
1534		switch params[2].Param(1) { // The third parameter is the event type (defaults to 1)
1535		case 2:
1536			k.IsRepeat = true
1537		case 3:
1538			return KeyReleaseEvent(k)
1539		}
1540	}
1541	return k
1542}
1543
1544func parsePrimaryDevAttrs(params ansi.Params) Event {
1545	// Primary Device Attributes
1546	da1 := make(PrimaryDeviceAttributesEvent, len(params))
1547	for i, p := range params {
1548		if !p.HasMore() {
1549			da1[i] = p.Param(0)
1550		}
1551	}
1552	return da1
1553}
1554
1555func parseSecondaryDevAttrs(params ansi.Params) Event {
1556	// Secondary Device Attributes
1557	da2 := make(SecondaryDeviceAttributesEvent, len(params))
1558	for i, p := range params {
1559		if !p.HasMore() {
1560			da2[i] = p.Param(0)
1561		}
1562	}
1563	return da2
1564}
1565
1566func parseTertiaryDevAttrs(b []byte) Event {
1567	// Tertiary Device Attributes
1568	// The response is a 4-digit hexadecimal number.
1569	bts, err := hex.DecodeString(string(b))
1570	if err != nil {
1571		return UnknownDcsEvent(fmt.Sprintf("\x1bP!|%s\x1b\\", b))
1572	}
1573	return TertiaryDeviceAttributesEvent(bts)
1574}
1575
1576// Parse SGR-encoded mouse events; SGR extended mouse events. SGR mouse events
1577// look like:
1578//
1579//	ESC [ < Cb ; Cx ; Cy (M or m)
1580//
1581// where:
1582//
1583//	Cb is the encoded button code
1584//	Cx is the x-coordinate of the mouse
1585//	Cy is the y-coordinate of the mouse
1586//	M is for button press, m is for button release
1587//
1588// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
1589func parseSGRMouseEvent(cmd ansi.Cmd, params ansi.Params) Event {
1590	x, _, ok := params.Param(1, 1)
1591	if !ok {
1592		x = 1
1593	}
1594	y, _, ok := params.Param(2, 1)
1595	if !ok {
1596		y = 1
1597	}
1598	release := cmd.Final() == 'm'
1599	b, _, _ := params.Param(0, 0)
1600	mod, btn, _, isMotion := parseMouseButton(b)
1601
1602	// (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
1603	x--
1604	y--
1605
1606	m := Mouse{X: x, Y: y, Button: btn, Mod: mod}
1607
1608	// Wheel buttons don't have release events
1609	// Motion can be reported as a release event in some terminals (Windows Terminal)
1610	if isWheel(m.Button) {
1611		return MouseWheelEvent(m)
1612	} else if !isMotion && release {
1613		return MouseReleaseEvent(m)
1614	} else if isMotion {
1615		return MouseMotionEvent(m)
1616	}
1617	return MouseClickEvent(m)
1618}
1619
1620const x10MouseByteOffset = 32
1621
1622// Parse X10-encoded mouse events; the simplest kind. The last release of X10
1623// was December 1986, by the way. The original X10 mouse protocol limits the Cx
1624// and Cy coordinates to 223 (=255-032).
1625//
1626// X10 mouse events look like:
1627//
1628//	ESC [M Cb Cx Cy
1629//
1630// See: http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking
1631func parseX10MouseEvent(buf []byte) Event {
1632	v := buf[3:6]
1633	b := int(v[0])
1634	if b >= x10MouseByteOffset {
1635		// XXX: b < 32 should be impossible, but we're being defensive.
1636		b -= x10MouseByteOffset
1637	}
1638
1639	mod, btn, isRelease, isMotion := parseMouseButton(b)
1640
1641	// (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
1642	x := int(v[1]) - x10MouseByteOffset - 1
1643	y := int(v[2]) - x10MouseByteOffset - 1
1644
1645	m := Mouse{X: x, Y: y, Button: btn, Mod: mod}
1646	if isWheel(m.Button) {
1647		return MouseWheelEvent(m)
1648	} else if isMotion {
1649		return MouseMotionEvent(m)
1650	} else if isRelease {
1651		return MouseReleaseEvent(m)
1652	}
1653	return MouseClickEvent(m)
1654}
1655
1656// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
1657func parseMouseButton(b int) (mod KeyMod, btn MouseButton, isRelease bool, isMotion bool) {
1658	// mouse bit shifts
1659	const (
1660		bitShift  = 0b0000_0100
1661		bitAlt    = 0b0000_1000
1662		bitCtrl   = 0b0001_0000
1663		bitMotion = 0b0010_0000
1664		bitWheel  = 0b0100_0000
1665		bitAdd    = 0b1000_0000 // additional buttons 8-11
1666
1667		bitsMask = 0b0000_0011
1668	)
1669
1670	// Modifiers
1671	if b&bitAlt != 0 {
1672		mod |= ModAlt
1673	}
1674	if b&bitCtrl != 0 {
1675		mod |= ModCtrl
1676	}
1677	if b&bitShift != 0 {
1678		mod |= ModShift
1679	}
1680
1681	if b&bitAdd != 0 {
1682		btn = MouseBackward + MouseButton(b&bitsMask)
1683	} else if b&bitWheel != 0 {
1684		btn = MouseWheelUp + MouseButton(b&bitsMask)
1685	} else {
1686		btn = MouseLeft + MouseButton(b&bitsMask)
1687		// X10 reports a button release as 0b0000_0011 (3)
1688		if b&bitsMask == bitsMask {
1689			btn = MouseNone
1690			isRelease = true
1691		}
1692	}
1693
1694	// Motion bit doesn't get reported for wheel events.
1695	if b&bitMotion != 0 && !isWheel(btn) {
1696		isMotion = true
1697	}
1698
1699	return //nolint:nakedret
1700}
1701
1702// isWheel returns true if the mouse event is a wheel event.
1703func isWheel(btn MouseButton) bool {
1704	return btn >= MouseWheelUp && btn <= MouseWheelRight
1705}
1706
1707type shiftable interface {
1708	~uint | ~uint16 | ~uint32 | ~uint64
1709}
1710
1711func shift[T shiftable](x T) T {
1712	if x > 0xff {
1713		x >>= 8
1714	}
1715	return x
1716}
1717
1718func colorToHex(c color.Color) string {
1719	if c == nil {
1720		return ""
1721	}
1722	r, g, b, _ := c.RGBA()
1723	return fmt.Sprintf("#%02x%02x%02x", shift(r), shift(g), shift(b))
1724}
1725
1726func getMaxMin(a, b, c float64) (ma, mi float64) {
1727	if a > b {
1728		ma = a
1729		mi = b
1730	} else {
1731		ma = b
1732		mi = a
1733	}
1734	if c > ma {
1735		ma = c
1736	} else if c < mi {
1737		mi = c
1738	}
1739	return ma, mi
1740}
1741
1742func round(x float64) float64 {
1743	return math.Round(x*1000) / 1000
1744}
1745
1746// rgbToHSL converts an RGB triple to an HSL triple.
1747func rgbToHSL(r, g, b uint8) (h, s, l float64) {
1748	// convert uint32 pre-multiplied value to uint8
1749	// The r,g,b values are divided by 255 to change the range from 0..255 to 0..1:
1750	Rnot := float64(r) / 255
1751	Gnot := float64(g) / 255
1752	Bnot := float64(b) / 255
1753	Cmax, Cmin := getMaxMin(Rnot, Gnot, Bnot)
1754	Δ := Cmax - Cmin
1755	// Lightness calculation:
1756	l = (Cmax + Cmin) / 2
1757	// Hue and Saturation Calculation:
1758	if Δ == 0 {
1759		h = 0
1760		s = 0
1761	} else {
1762		switch Cmax {
1763		case Rnot:
1764			h = 60 * math.Mod((Gnot-Bnot)/Δ, 6)
1765		case Gnot:
1766			h = 60 * (((Bnot - Rnot) / Δ) + 2)
1767		case Bnot:
1768			h = 60 * (((Rnot - Gnot) / Δ) + 4)
1769		}
1770		if h < 0 {
1771			h += 360
1772		}
1773
1774		s = Δ / (1 - math.Abs((2*l)-1))
1775	}
1776
1777	return h, round(s), round(l)
1778}
1779
1780// isDarkColor returns whether the given color is dark.
1781func isDarkColor(c color.Color) bool {
1782	if c == nil {
1783		return true
1784	}
1785
1786	r, g, b, _ := c.RGBA()
1787	_, _, l := rgbToHSL(uint8(r>>8), uint8(g>>8), uint8(b>>8)) //nolint:gosec
1788	return l < 0.5
1789}
1790
1791func parseTermcap(data []byte) CapabilityEvent {
1792	// XTGETTCAP
1793	if len(data) == 0 {
1794		return CapabilityEvent{""}
1795	}
1796
1797	var tc strings.Builder
1798	split := bytes.Split(data, []byte{';'})
1799	for _, s := range split {
1800		parts := bytes.SplitN(s, []byte{'='}, 2)
1801		if len(parts) == 0 {
1802			return CapabilityEvent{""}
1803		}
1804
1805		name, err := hex.DecodeString(string(parts[0]))
1806		if err != nil || len(name) == 0 {
1807			continue
1808		}
1809
1810		var value []byte
1811		if len(parts) > 1 {
1812			value, err = hex.DecodeString(string(parts[1]))
1813			if err != nil {
1814				continue
1815			}
1816		}
1817
1818		if tc.Len() > 0 {
1819			tc.WriteByte(';')
1820		}
1821		tc.WriteString(string(name))
1822		if len(value) > 0 {
1823			tc.WriteByte('=')
1824			tc.WriteString(string(value))
1825		}
1826	}
1827
1828	return CapabilityEvent{tc.String()}
1829}
1830
1831// parseWin32InputKeyEvent converts a Windows Input Record Key Event into a
1832// KeyPressEvent, KeyReleaseEvent, or MultiEvent including multiple of the
1833// former type.
1834// A special case is when the key is either part of VtInputMode which produces
1835// vkc == 0, or when a key is a UTF-16 surrogate pair.  In both of these cases,
1836// we need to handle key encoding and properly parse the key event.
1837func (p *EventDecoder) parseWin32InputKeyEvent(vkc uint16, _ uint16, r rune, keyDown bool, cks uint32, repeatCount uint16) (event Event) {
1838	defer func() {
1839		// Respect the repeat count.
1840		if repeatCount > 1 {
1841			var multi MultiEvent
1842			for i := 0; i < int(repeatCount); i++ {
1843				multi = append(multi, event)
1844			}
1845			event = multi
1846		}
1847	}()
1848	defer func() {
1849		if vkc != 0 {
1850			p.lastCks = cks
1851		}
1852	}()
1853
1854	var key Key
1855	switch {
1856	case vkc == 0:
1857		// This is either a UTF-16 encoded pair, or an escape sequence waiting
1858		// to be decoded.
1859		if keyDown {
1860			return KeyPressEvent{Code: 0, BaseCode: r, Mod: translateControlKeyState(cks)}
1861		}
1862		return KeyReleaseEvent{Code: 0, BaseCode: r, Mod: translateControlKeyState(cks)}
1863	case vkc == xwindows.VK_BACK:
1864		key.BaseCode = KeyBackspace
1865	case vkc == xwindows.VK_TAB:
1866		key.BaseCode = KeyTab
1867	case vkc == xwindows.VK_RETURN:
1868		key.BaseCode = KeyEnter
1869	case vkc == xwindows.VK_SHIFT:
1870		//nolint:nestif
1871		if cks&xwindows.SHIFT_PRESSED != 0 {
1872			if cks&xwindows.ENHANCED_KEY != 0 {
1873				key.BaseCode = KeyRightShift
1874			} else {
1875				key.BaseCode = KeyLeftShift
1876			}
1877		} else if p.lastCks&xwindows.SHIFT_PRESSED != 0 {
1878			if p.lastCks&xwindows.ENHANCED_KEY != 0 {
1879				key.BaseCode = KeyRightShift
1880			} else {
1881				key.BaseCode = KeyLeftShift
1882			}
1883		}
1884	case vkc == xwindows.VK_CONTROL:
1885		if cks&xwindows.LEFT_CTRL_PRESSED != 0 {
1886			key.BaseCode = KeyLeftCtrl
1887		} else if cks&xwindows.RIGHT_CTRL_PRESSED != 0 {
1888			key.BaseCode = KeyRightCtrl
1889		} else if p.lastCks&xwindows.LEFT_CTRL_PRESSED != 0 {
1890			key.BaseCode = KeyLeftCtrl
1891		} else if p.lastCks&xwindows.RIGHT_CTRL_PRESSED != 0 {
1892			key.BaseCode = KeyRightCtrl
1893		}
1894	case vkc == xwindows.VK_MENU:
1895		if cks&xwindows.LEFT_ALT_PRESSED != 0 {
1896			key.BaseCode = KeyLeftAlt
1897		} else if cks&xwindows.RIGHT_ALT_PRESSED != 0 {
1898			key.BaseCode = KeyRightAlt
1899		} else if p.lastCks&xwindows.LEFT_ALT_PRESSED != 0 {
1900			key.BaseCode = KeyLeftAlt
1901		} else if p.lastCks&xwindows.RIGHT_ALT_PRESSED != 0 {
1902			key.BaseCode = KeyRightAlt
1903		}
1904	case vkc == xwindows.VK_PAUSE:
1905		key.BaseCode = KeyPause
1906	case vkc == xwindows.VK_CAPITAL:
1907		key.BaseCode = KeyCapsLock
1908	case vkc == xwindows.VK_ESCAPE:
1909		key.BaseCode = KeyEscape
1910	case vkc == xwindows.VK_SPACE:
1911		key.BaseCode = KeySpace
1912	case vkc == xwindows.VK_PRIOR:
1913		key.BaseCode = KeyPgUp
1914	case vkc == xwindows.VK_NEXT:
1915		key.BaseCode = KeyPgDown
1916	case vkc == xwindows.VK_END:
1917		key.BaseCode = KeyEnd
1918	case vkc == xwindows.VK_HOME:
1919		key.BaseCode = KeyHome
1920	case vkc == xwindows.VK_LEFT:
1921		key.BaseCode = KeyLeft
1922	case vkc == xwindows.VK_UP:
1923		key.BaseCode = KeyUp
1924	case vkc == xwindows.VK_RIGHT:
1925		key.BaseCode = KeyRight
1926	case vkc == xwindows.VK_DOWN:
1927		key.BaseCode = KeyDown
1928	case vkc == xwindows.VK_SELECT:
1929		key.BaseCode = KeySelect
1930	case vkc == xwindows.VK_SNAPSHOT:
1931		key.BaseCode = KeyPrintScreen
1932	case vkc == xwindows.VK_INSERT:
1933		key.BaseCode = KeyInsert
1934	case vkc == xwindows.VK_DELETE:
1935		key.BaseCode = KeyDelete
1936	case vkc >= '0' && vkc <= '9':
1937		key.BaseCode = rune(vkc)
1938	case vkc >= 'A' && vkc <= 'Z':
1939		// Convert to lowercase.
1940		key.BaseCode = rune(vkc) + 32
1941	case vkc == xwindows.VK_LWIN:
1942		key.BaseCode = KeyLeftSuper
1943	case vkc == xwindows.VK_RWIN:
1944		key.BaseCode = KeyRightSuper
1945	case vkc == xwindows.VK_APPS:
1946		key.BaseCode = KeyMenu
1947	case vkc >= xwindows.VK_NUMPAD0 && vkc <= xwindows.VK_NUMPAD9:
1948		key.BaseCode = rune(vkc-xwindows.VK_NUMPAD0) + KeyKp0
1949		key.Text = string('0' + (rune(vkc) - xwindows.VK_NUMPAD0))
1950	case vkc == xwindows.VK_MULTIPLY:
1951		key.BaseCode = KeyKpMultiply
1952		key.Text = "*"
1953	case vkc == xwindows.VK_ADD:
1954		key.BaseCode = KeyKpPlus
1955		key.Text = "+"
1956	case vkc == xwindows.VK_SEPARATOR:
1957		key.BaseCode = KeyKpComma
1958		key.Text = ","
1959	case vkc == xwindows.VK_SUBTRACT:
1960		key.BaseCode = KeyKpMinus
1961		key.Text = "-"
1962	case vkc == xwindows.VK_DECIMAL:
1963		key.BaseCode = KeyKpDecimal
1964		key.Text = "."
1965	case vkc == xwindows.VK_DIVIDE:
1966		key.BaseCode = KeyKpDivide
1967		key.Text = "/"
1968	case vkc >= xwindows.VK_F1 && vkc <= xwindows.VK_F24:
1969		key.BaseCode = rune(vkc-xwindows.VK_F1) + KeyF1
1970	case vkc == xwindows.VK_NUMLOCK:
1971		key.BaseCode = KeyNumLock
1972	case vkc == xwindows.VK_SCROLL:
1973		key.BaseCode = KeyScrollLock
1974	case vkc == xwindows.VK_LSHIFT:
1975		key.BaseCode = KeyLeftShift
1976	case vkc == xwindows.VK_RSHIFT:
1977		key.BaseCode = KeyRightShift
1978	case vkc == xwindows.VK_LCONTROL:
1979		key.BaseCode = KeyLeftCtrl
1980	case vkc == xwindows.VK_RCONTROL:
1981		key.BaseCode = KeyRightCtrl
1982	case vkc == xwindows.VK_LMENU:
1983		key.BaseCode = KeyLeftAlt
1984	case vkc == xwindows.VK_RMENU:
1985		key.BaseCode = KeyRightAlt
1986	case vkc == xwindows.VK_VOLUME_MUTE:
1987		key.BaseCode = KeyMute
1988	case vkc == xwindows.VK_VOLUME_DOWN:
1989		key.BaseCode = KeyLowerVol
1990	case vkc == xwindows.VK_VOLUME_UP:
1991		key.BaseCode = KeyRaiseVol
1992	case vkc == xwindows.VK_MEDIA_NEXT_TRACK:
1993		key.BaseCode = KeyMediaNext
1994	case vkc == xwindows.VK_MEDIA_PREV_TRACK:
1995		key.BaseCode = KeyMediaPrev
1996	case vkc == xwindows.VK_MEDIA_STOP:
1997		key.BaseCode = KeyMediaStop
1998	case vkc == xwindows.VK_MEDIA_PLAY_PAUSE:
1999		key.BaseCode = KeyMediaPlayPause
2000	case vkc == xwindows.VK_OEM_1:
2001		key.BaseCode = ';'
2002	case vkc == xwindows.VK_OEM_PLUS:
2003		key.BaseCode = '+'
2004	case vkc == xwindows.VK_OEM_COMMA:
2005		key.BaseCode = ','
2006	case vkc == xwindows.VK_OEM_MINUS:
2007		key.BaseCode = '-'
2008	case vkc == xwindows.VK_OEM_PERIOD:
2009		key.BaseCode = '.'
2010	case vkc == xwindows.VK_OEM_2:
2011		key.BaseCode = '/'
2012	case vkc == xwindows.VK_OEM_3:
2013		key.BaseCode = '`'
2014	case vkc == xwindows.VK_OEM_4:
2015		key.BaseCode = '['
2016	case vkc == xwindows.VK_OEM_5:
2017		key.BaseCode = '\\'
2018	case vkc == xwindows.VK_OEM_6:
2019		key.BaseCode = ']'
2020	case vkc == xwindows.VK_OEM_7:
2021		key.BaseCode = '\''
2022	}
2023
2024	// AltGr is left ctrl + right alt. On non-US keyboards, this is used to type
2025	// special characters and produce printable events.
2026	// XXX: Should this be a KeyMod?
2027	const altGrPressed = xwindows.LEFT_CTRL_PRESSED | xwindows.RIGHT_ALT_PRESSED
2028	altGr := cks&altGrPressed == altGrPressed
2029
2030	// Remove these lock keys from the control key state from now on.
2031	cks &^= xwindows.NUMLOCK_ON
2032	cks &^= xwindows.SCROLLLOCK_ON
2033	key.Code = key.BaseCode
2034	if !unicode.IsControl(r) {
2035		key.Code = r
2036		if unicode.IsPrint(key.Code) && (cks == 0 || // no modifiers pressed
2037			cks == xwindows.SHIFT_PRESSED || // shift pressed
2038			cks == xwindows.CAPSLOCK_ON || // caps lock on
2039			cks == (xwindows.SHIFT_PRESSED|xwindows.CAPSLOCK_ON) || // Shift + caps lock pressed
2040			altGr) { // AltGr pressed
2041			// If the control key state is 0, shift is pressed, or caps lock
2042			// then the key event is a printable event i.e. [text] is not empty.
2043			key.Text = string(key.Code)
2044		}
2045	}
2046
2047	key.Mod = translateControlKeyState(cks)
2048	key = ensureKeyCase(key, cks)
2049	if keyDown {
2050		return KeyPressEvent(key)
2051	}
2052
2053	return KeyReleaseEvent(key)
2054}
2055
2056// ensureKeyCase ensures that the key's text is in the correct case based on the
2057// control key state.
2058func ensureKeyCase(key Key, cks uint32) Key {
2059	if len(key.Text) == 0 {
2060		return key
2061	}
2062
2063	hasShift := cks&xwindows.SHIFT_PRESSED != 0
2064	hasCaps := cks&xwindows.CAPSLOCK_ON != 0
2065	if hasShift || hasCaps {
2066		if unicode.IsLower(key.Code) {
2067			key.ShiftedCode = unicode.ToUpper(key.Code)
2068			key.Text = string(key.ShiftedCode)
2069		}
2070	} else {
2071		if unicode.IsUpper(key.Code) {
2072			key.ShiftedCode = unicode.ToLower(key.Code)
2073			key.Text = string(key.ShiftedCode)
2074		}
2075	}
2076
2077	return key
2078}
2079
2080// translateControlKeyState translates the control key state from the Windows
2081// Console API into a Mod bitmask.
2082func translateControlKeyState(cks uint32) (m KeyMod) {
2083	if cks&xwindows.LEFT_CTRL_PRESSED != 0 || cks&xwindows.RIGHT_CTRL_PRESSED != 0 {
2084		m |= ModCtrl
2085	}
2086	if cks&xwindows.LEFT_ALT_PRESSED != 0 || cks&xwindows.RIGHT_ALT_PRESSED != 0 {
2087		m |= ModAlt
2088	}
2089	if cks&xwindows.SHIFT_PRESSED != 0 {
2090		m |= ModShift
2091	}
2092	if cks&xwindows.CAPSLOCK_ON != 0 {
2093		m |= ModCapsLock
2094	}
2095	if cks&xwindows.NUMLOCK_ON != 0 {
2096		m |= ModNumLock
2097	}
2098	if cks&xwindows.SCROLLLOCK_ON != 0 {
2099		m |= ModScrollLock
2100	}
2101	return
2102}