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