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 := KeyPressEvent{Code: rune(b[1]), Mod: ModAlt}
752 if len(b) == 2 && b[0] == ansi.ESC {
753 // short cut if this is an alt+] key
754 return 2, defaultKey
755 }
756
757 var i int
758 if b[i] == ansi.OSC || b[i] == ansi.ESC {
759 i++
760 }
761 if i < len(b) && b[i-1] == ansi.ESC && b[i] == ']' {
762 i++
763 }
764
765 // Parse OSC command
766 // An OSC sequence is terminated by a BEL, ESC, or ST character
767 var start, end int
768 cmd := -1
769 for ; i < len(b) && b[i] >= '0' && b[i] <= '9'; i++ {
770 if cmd == -1 {
771 cmd = 0
772 } else {
773 cmd *= 10
774 }
775 cmd += int(b[i]) - '0'
776 }
777
778 if i < len(b) && b[i] == ';' {
779 // mark the start of the sequence data
780 i++
781 start = i
782 }
783
784 for ; i < len(b); i++ {
785 // advance to the end of the sequence
786 if slices.Contains([]byte{ansi.BEL, ansi.ESC, ansi.ST, ansi.CAN, ansi.SUB}, b[i]) {
787 break
788 }
789 }
790
791 if i >= len(b) {
792 return i, UnknownEvent(b[:i])
793 }
794
795 end = i // end of the sequence data
796 i++
797
798 // Check 7-bit ST (string terminator) character
799 switch b[i-1] {
800 case ansi.CAN, ansi.SUB:
801 return i, ignoredEvent(b[:i])
802 case ansi.ESC:
803 if i >= len(b) || b[i] != '\\' {
804 if cmd == -1 || (start == 0 && end == 2) {
805 return 2, defaultKey
806 }
807
808 // If we don't have a valid ST terminator, then this is a
809 // cancelled sequence and should be ignored.
810 return i, ignoredEvent(b[:i])
811 }
812
813 i++
814 }
815
816 if end <= start {
817 return i, UnknownEvent(b[:i])
818 }
819
820 data := string(b[start:end])
821 switch cmd {
822 case 10:
823 return i, ForegroundColorEvent{ansi.XParseColor(data)}
824 case 11:
825 return i, BackgroundColorEvent{ansi.XParseColor(data)}
826 case 12:
827 return i, CursorColorEvent{ansi.XParseColor(data)}
828 case 52:
829 parts := strings.Split(data, ";")
830 if len(parts) == 0 {
831 return i, ClipboardEvent{}
832 }
833 if len(parts) != 2 || len(parts[0]) < 1 {
834 break
835 }
836
837 b64 := parts[1]
838 bts, err := base64.StdEncoding.DecodeString(b64)
839 if err != nil {
840 break
841 }
842
843 sel := ClipboardSelection(parts[0][0]) //nolint:unconvert
844 return i, ClipboardEvent{Selection: sel, Content: string(bts)}
845 }
846
847 return i, UnknownOscEvent(b[:i])
848}
849
850// parseStTerminated parses a control sequence that gets terminated by a ST character.
851func (p *SequenceParser) parseStTerminated(intro8, intro7 byte, fn func([]byte) Event) func([]byte) (int, Event) {
852 defaultKey := func(b []byte) (int, Event) {
853 switch intro8 {
854 case ansi.SOS:
855 return 2, KeyPressEvent{Code: 'x', Mod: ModShift | ModAlt}
856 case ansi.PM, ansi.APC:
857 return 2, KeyPressEvent{Code: rune(b[1]), Mod: ModAlt}
858 }
859 return 0, nil
860 }
861 return func(b []byte) (int, Event) {
862 if len(b) == 2 && b[0] == ansi.ESC {
863 return defaultKey(b)
864 }
865
866 var i int
867 if b[i] == intro8 || b[i] == ansi.ESC {
868 i++
869 }
870 if i < len(b) && b[i-1] == ansi.ESC && b[i] == intro7 {
871 i++
872 }
873
874 // Scan control sequence
875 // Most common control sequence is terminated by a ST character
876 // ST is a 7-bit string terminator character is (ESC \)
877 start := i
878 for ; i < len(b); i++ {
879 if slices.Contains([]byte{ansi.ESC, ansi.ST, ansi.CAN, ansi.SUB}, b[i]) {
880 break
881 }
882 }
883
884 if i >= len(b) {
885 return i, UnknownEvent(b[:i])
886 }
887
888 end := i // end of the sequence data
889 i++
890
891 // Check 7-bit ST (string terminator) character
892 switch b[i-1] {
893 case ansi.CAN, ansi.SUB:
894 return i, ignoredEvent(b[:i])
895 case ansi.ESC:
896 if i >= len(b) || b[i] != '\\' {
897 if start == end {
898 return defaultKey(b)
899 }
900
901 // If we don't have a valid ST terminator, then this is a
902 // cancelled sequence and should be ignored.
903 return i, ignoredEvent(b[:i])
904 }
905
906 i++
907 }
908
909 // Call the function to parse the sequence and return the result
910 if fn != nil {
911 if e := fn(b[start:end]); e != nil {
912 return i, e
913 }
914 }
915
916 switch intro8 {
917 case ansi.PM:
918 return i, UnknownPmEvent(b[:i])
919 case ansi.SOS:
920 return i, UnknownSosEvent(b[:i])
921 case ansi.APC:
922 return i, UnknownApcEvent(b[:i])
923 default:
924 return i, UnknownEvent(b[:i])
925 }
926 }
927}
928
929func (p *SequenceParser) parseDcs(b []byte) (int, Event) {
930 if len(b) == 2 && b[0] == ansi.ESC {
931 // short cut if this is an alt+P key
932 return 2, KeyPressEvent{Code: 'p', Mod: ModShift | ModAlt}
933 }
934
935 var params [16]ansi.Param
936 var paramsLen int
937 var cmd ansi.Cmd
938
939 // DCS sequences are introduced by DCS (0x90) or ESC P (0x1b 0x50)
940 var i int
941 if b[i] == ansi.DCS || b[i] == ansi.ESC {
942 i++
943 }
944 if i < len(b) && b[i-1] == ansi.ESC && b[i] == 'P' {
945 i++
946 }
947
948 // initial DCS byte
949 if i < len(b) && b[i] >= '<' && b[i] <= '?' {
950 cmd |= ansi.Cmd(b[i]) << parser.PrefixShift
951 }
952
953 // Scan parameter bytes in the range 0x30-0x3F
954 var j int
955 for j = 0; i < len(b) && paramsLen < len(params) && b[i] >= 0x30 && b[i] <= 0x3F; i, j = i+1, j+1 {
956 if b[i] >= '0' && b[i] <= '9' {
957 if params[paramsLen] == parser.MissingParam {
958 params[paramsLen] = 0
959 }
960 params[paramsLen] *= 10
961 params[paramsLen] += ansi.Param(b[i]) - '0'
962 }
963 if b[i] == ':' {
964 params[paramsLen] |= parser.HasMoreFlag
965 }
966 if b[i] == ';' || b[i] == ':' {
967 paramsLen++
968 if paramsLen < len(params) {
969 // Don't overflow the params slice
970 params[paramsLen] = parser.MissingParam
971 }
972 }
973 }
974
975 if j > 0 && paramsLen < len(params) {
976 // has parameters
977 paramsLen++
978 }
979
980 // Scan intermediate bytes in the range 0x20-0x2F
981 var intermed byte
982 for j := 0; i < len(b) && b[i] >= 0x20 && b[i] <= 0x2F; i, j = i+1, j+1 {
983 intermed = b[i]
984 }
985
986 // set intermediate byte
987 cmd |= ansi.Cmd(intermed) << parser.IntermedShift
988
989 // Scan final byte in the range 0x40-0x7E
990 if i >= len(b) || b[i] < 0x40 || b[i] > 0x7E {
991 return i, UnknownEvent(b[:i])
992 }
993
994 // Add the final byte
995 cmd |= ansi.Cmd(b[i])
996 i++
997
998 start := i // start of the sequence data
999 for ; i < len(b); i++ {
1000 if b[i] == ansi.ST || b[i] == ansi.ESC {
1001 break
1002 }
1003 }
1004
1005 if i >= len(b) {
1006 return i, UnknownEvent(b[:i])
1007 }
1008
1009 end := i // end of the sequence data
1010 i++
1011
1012 // Check 7-bit ST (string terminator) character
1013 if i < len(b) && b[i-1] == ansi.ESC && b[i] == '\\' {
1014 i++
1015 }
1016
1017 pa := ansi.Params(params[:paramsLen])
1018 switch cmd {
1019 case 'r' | '+'<<parser.IntermedShift:
1020 // XTGETTCAP responses
1021 param, _, _ := pa.Param(0, 0)
1022 switch param {
1023 case 1: // 1 means valid response, 0 means invalid response
1024 tc := parseTermcap(b[start:end])
1025 // XXX: some terminals like KiTTY report invalid responses with
1026 // their queries i.e. sending a query for "Tc" using "\x1bP+q5463\x1b\\"
1027 // returns "\x1bP0+r5463\x1b\\".
1028 // The specs says that invalid responses should be in the form of
1029 // DCS 0 + r ST "\x1bP0+r\x1b\\"
1030 // We ignore invalid responses and only send valid ones to the program.
1031 //
1032 // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
1033 return i, tc
1034 }
1035 case '|' | '>'<<parser.PrefixShift:
1036 // XTVersion response
1037 return i, TerminalVersionEvent(b[start:end])
1038 }
1039
1040 return i, UnknownDcsEvent(b[:i])
1041}
1042
1043func (p *SequenceParser) parseApc(b []byte) (int, Event) {
1044 if len(b) == 2 && b[0] == ansi.ESC {
1045 // short cut if this is an alt+_ key
1046 return 2, KeyPressEvent{Code: rune(b[1]), Mod: ModAlt}
1047 }
1048
1049 // APC sequences are introduced by APC (0x9f) or ESC _ (0x1b 0x5f)
1050 return p.parseStTerminated(ansi.APC, '_', func(b []byte) Event {
1051 if len(b) == 0 {
1052 return nil
1053 }
1054
1055 switch b[0] {
1056 case 'G': // Kitty Graphics Protocol
1057 var g KittyGraphicsEvent
1058 parts := bytes.Split(b[1:], []byte{';'})
1059 g.Options.UnmarshalText(parts[0]) //nolint:errcheck,gosec
1060 if len(parts) > 1 {
1061 g.Payload = parts[1]
1062 }
1063 return g
1064 }
1065
1066 return nil
1067 })(b)
1068}
1069
1070func (p *SequenceParser) parseUtf8(b []byte) (int, Event) {
1071 if len(b) == 0 {
1072 return 0, nil
1073 }
1074
1075 c := b[0]
1076 if c <= ansi.US || c == ansi.DEL {
1077 // Control codes get handled by parseControl
1078 return 1, p.parseControl(c)
1079 } else if c > ansi.US && c < ansi.DEL {
1080 // ASCII printable characters
1081 code := rune(c)
1082 k := KeyPressEvent{Code: code, Text: string(code)}
1083 if unicode.IsUpper(code) {
1084 // Convert upper case letters to lower case + shift modifier
1085 k.Code = unicode.ToLower(code)
1086 k.ShiftedCode = code
1087 k.Mod |= ModShift
1088 }
1089
1090 return 1, k
1091 }
1092
1093 code, _ := utf8.DecodeRune(b)
1094 if code == utf8.RuneError {
1095 return 1, UnknownEvent(b[0])
1096 }
1097
1098 cluster, _, _, _ := uniseg.FirstGraphemeCluster(b, -1)
1099 text := string(cluster)
1100 for i := range text {
1101 if i > 0 {
1102 // Use [KeyExtended] for multi-rune graphemes
1103 code = KeyExtended
1104 break
1105 }
1106 }
1107
1108 return len(cluster), KeyPressEvent{Code: code, Text: text}
1109}
1110
1111func (p *SequenceParser) parseControl(b byte) Event {
1112 switch b {
1113 case ansi.NUL:
1114 if p.Legacy&flagCtrlAt != 0 {
1115 return KeyPressEvent{Code: '@', Mod: ModCtrl}
1116 }
1117 return KeyPressEvent{Code: KeySpace, Mod: ModCtrl}
1118 case ansi.BS:
1119 return KeyPressEvent{Code: 'h', Mod: ModCtrl}
1120 case ansi.HT:
1121 if p.Legacy&flagCtrlI != 0 {
1122 return KeyPressEvent{Code: 'i', Mod: ModCtrl}
1123 }
1124 return KeyPressEvent{Code: KeyTab}
1125 case ansi.CR:
1126 if p.Legacy&flagCtrlM != 0 {
1127 return KeyPressEvent{Code: 'm', Mod: ModCtrl}
1128 }
1129 return KeyPressEvent{Code: KeyEnter}
1130 case ansi.ESC:
1131 if p.Legacy&flagCtrlOpenBracket != 0 {
1132 return KeyPressEvent{Code: '[', Mod: ModCtrl}
1133 }
1134 return KeyPressEvent{Code: KeyEscape}
1135 case ansi.DEL:
1136 if p.Legacy&flagBackspace != 0 {
1137 return KeyPressEvent{Code: KeyDelete}
1138 }
1139 return KeyPressEvent{Code: KeyBackspace}
1140 case ansi.SP:
1141 return KeyPressEvent{Code: KeySpace, Text: " "}
1142 default:
1143 if b >= ansi.SOH && b <= ansi.SUB {
1144 // Use lower case letters for control codes
1145 code := rune(b + 0x60)
1146 return KeyPressEvent{Code: code, Mod: ModCtrl}
1147 } else if b >= ansi.FS && b <= ansi.US {
1148 code := rune(b + 0x40)
1149 return KeyPressEvent{Code: code, Mod: ModCtrl}
1150 }
1151 return UnknownEvent(b)
1152 }
1153}
1154
1155func parseXTermModifyOtherKeys(params ansi.Params) Event {
1156 // XTerm modify other keys starts with ESC [ 27 ; <modifier> ; <code> ~
1157 xmod, _, _ := params.Param(1, 1)
1158 xrune, _, _ := params.Param(2, 1)
1159 mod := KeyMod(xmod - 1)
1160 r := rune(xrune)
1161
1162 switch r {
1163 case ansi.BS:
1164 return KeyPressEvent{Mod: mod, Code: KeyBackspace}
1165 case ansi.HT:
1166 return KeyPressEvent{Mod: mod, Code: KeyTab}
1167 case ansi.CR:
1168 return KeyPressEvent{Mod: mod, Code: KeyEnter}
1169 case ansi.ESC:
1170 return KeyPressEvent{Mod: mod, Code: KeyEscape}
1171 case ansi.DEL:
1172 return KeyPressEvent{Mod: mod, Code: KeyBackspace}
1173 }
1174
1175 // CSI 27 ; <modifier> ; <code> ~ keys defined in XTerm modifyOtherKeys
1176 k := KeyPressEvent{Code: r, Mod: mod}
1177 if k.Mod <= ModShift {
1178 k.Text = string(r)
1179 }
1180
1181 return k
1182}
1183
1184// Kitty Clipboard Control Sequences.
1185var kittyKeyMap = map[int]Key{
1186 ansi.BS: {Code: KeyBackspace},
1187 ansi.HT: {Code: KeyTab},
1188 ansi.CR: {Code: KeyEnter},
1189 ansi.ESC: {Code: KeyEscape},
1190 ansi.DEL: {Code: KeyBackspace},
1191
1192 57344: {Code: KeyEscape},
1193 57345: {Code: KeyEnter},
1194 57346: {Code: KeyTab},
1195 57347: {Code: KeyBackspace},
1196 57348: {Code: KeyInsert},
1197 57349: {Code: KeyDelete},
1198 57350: {Code: KeyLeft},
1199 57351: {Code: KeyRight},
1200 57352: {Code: KeyUp},
1201 57353: {Code: KeyDown},
1202 57354: {Code: KeyPgUp},
1203 57355: {Code: KeyPgDown},
1204 57356: {Code: KeyHome},
1205 57357: {Code: KeyEnd},
1206 57358: {Code: KeyCapsLock},
1207 57359: {Code: KeyScrollLock},
1208 57360: {Code: KeyNumLock},
1209 57361: {Code: KeyPrintScreen},
1210 57362: {Code: KeyPause},
1211 57363: {Code: KeyMenu},
1212 57364: {Code: KeyF1},
1213 57365: {Code: KeyF2},
1214 57366: {Code: KeyF3},
1215 57367: {Code: KeyF4},
1216 57368: {Code: KeyF5},
1217 57369: {Code: KeyF6},
1218 57370: {Code: KeyF7},
1219 57371: {Code: KeyF8},
1220 57372: {Code: KeyF9},
1221 57373: {Code: KeyF10},
1222 57374: {Code: KeyF11},
1223 57375: {Code: KeyF12},
1224 57376: {Code: KeyF13},
1225 57377: {Code: KeyF14},
1226 57378: {Code: KeyF15},
1227 57379: {Code: KeyF16},
1228 57380: {Code: KeyF17},
1229 57381: {Code: KeyF18},
1230 57382: {Code: KeyF19},
1231 57383: {Code: KeyF20},
1232 57384: {Code: KeyF21},
1233 57385: {Code: KeyF22},
1234 57386: {Code: KeyF23},
1235 57387: {Code: KeyF24},
1236 57388: {Code: KeyF25},
1237 57389: {Code: KeyF26},
1238 57390: {Code: KeyF27},
1239 57391: {Code: KeyF28},
1240 57392: {Code: KeyF29},
1241 57393: {Code: KeyF30},
1242 57394: {Code: KeyF31},
1243 57395: {Code: KeyF32},
1244 57396: {Code: KeyF33},
1245 57397: {Code: KeyF34},
1246 57398: {Code: KeyF35},
1247 57399: {Code: KeyKp0},
1248 57400: {Code: KeyKp1},
1249 57401: {Code: KeyKp2},
1250 57402: {Code: KeyKp3},
1251 57403: {Code: KeyKp4},
1252 57404: {Code: KeyKp5},
1253 57405: {Code: KeyKp6},
1254 57406: {Code: KeyKp7},
1255 57407: {Code: KeyKp8},
1256 57408: {Code: KeyKp9},
1257 57409: {Code: KeyKpDecimal},
1258 57410: {Code: KeyKpDivide},
1259 57411: {Code: KeyKpMultiply},
1260 57412: {Code: KeyKpMinus},
1261 57413: {Code: KeyKpPlus},
1262 57414: {Code: KeyKpEnter},
1263 57415: {Code: KeyKpEqual},
1264 57416: {Code: KeyKpSep},
1265 57417: {Code: KeyKpLeft},
1266 57418: {Code: KeyKpRight},
1267 57419: {Code: KeyKpUp},
1268 57420: {Code: KeyKpDown},
1269 57421: {Code: KeyKpPgUp},
1270 57422: {Code: KeyKpPgDown},
1271 57423: {Code: KeyKpHome},
1272 57424: {Code: KeyKpEnd},
1273 57425: {Code: KeyKpInsert},
1274 57426: {Code: KeyKpDelete},
1275 57427: {Code: KeyKpBegin},
1276 57428: {Code: KeyMediaPlay},
1277 57429: {Code: KeyMediaPause},
1278 57430: {Code: KeyMediaPlayPause},
1279 57431: {Code: KeyMediaReverse},
1280 57432: {Code: KeyMediaStop},
1281 57433: {Code: KeyMediaFastForward},
1282 57434: {Code: KeyMediaRewind},
1283 57435: {Code: KeyMediaNext},
1284 57436: {Code: KeyMediaPrev},
1285 57437: {Code: KeyMediaRecord},
1286 57438: {Code: KeyLowerVol},
1287 57439: {Code: KeyRaiseVol},
1288 57440: {Code: KeyMute},
1289 57441: {Code: KeyLeftShift},
1290 57442: {Code: KeyLeftCtrl},
1291 57443: {Code: KeyLeftAlt},
1292 57444: {Code: KeyLeftSuper},
1293 57445: {Code: KeyLeftHyper},
1294 57446: {Code: KeyLeftMeta},
1295 57447: {Code: KeyRightShift},
1296 57448: {Code: KeyRightCtrl},
1297 57449: {Code: KeyRightAlt},
1298 57450: {Code: KeyRightSuper},
1299 57451: {Code: KeyRightHyper},
1300 57452: {Code: KeyRightMeta},
1301 57453: {Code: KeyIsoLevel3Shift},
1302 57454: {Code: KeyIsoLevel5Shift},
1303}
1304
1305func init() {
1306 // These are some faulty C0 mappings some terminals such as WezTerm have
1307 // and doesn't follow the specs.
1308 kittyKeyMap[ansi.NUL] = Key{Code: KeySpace, Mod: ModCtrl}
1309 for i := ansi.SOH; i <= ansi.SUB; i++ {
1310 if _, ok := kittyKeyMap[i]; !ok {
1311 kittyKeyMap[i] = Key{Code: rune(i + 0x60), Mod: ModCtrl}
1312 }
1313 }
1314 for i := ansi.FS; i <= ansi.US; i++ {
1315 if _, ok := kittyKeyMap[i]; !ok {
1316 kittyKeyMap[i] = Key{Code: rune(i + 0x40), Mod: ModCtrl}
1317 }
1318 }
1319}
1320
1321const (
1322 kittyShift = 1 << iota
1323 kittyAlt
1324 kittyCtrl
1325 kittySuper
1326 kittyHyper
1327 kittyMeta
1328 kittyCapsLock
1329 kittyNumLock
1330)
1331
1332func fromKittyMod(mod int) KeyMod {
1333 var m KeyMod
1334 if mod&kittyShift != 0 {
1335 m |= ModShift
1336 }
1337 if mod&kittyAlt != 0 {
1338 m |= ModAlt
1339 }
1340 if mod&kittyCtrl != 0 {
1341 m |= ModCtrl
1342 }
1343 if mod&kittySuper != 0 {
1344 m |= ModSuper
1345 }
1346 if mod&kittyHyper != 0 {
1347 m |= ModHyper
1348 }
1349 if mod&kittyMeta != 0 {
1350 m |= ModMeta
1351 }
1352 if mod&kittyCapsLock != 0 {
1353 m |= ModCapsLock
1354 }
1355 if mod&kittyNumLock != 0 {
1356 m |= ModNumLock
1357 }
1358 return m
1359}
1360
1361// parseKittyKeyboard parses a Kitty Keyboard Protocol sequence.
1362//
1363// In `CSI u`, this is parsed as:
1364//
1365// CSI codepoint ; modifiers u
1366// codepoint: ASCII Dec value
1367//
1368// The Kitty Keyboard Protocol extends this with optional components that can be
1369// enabled progressively. The full sequence is parsed as:
1370//
1371// CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u
1372//
1373// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/
1374func parseKittyKeyboard(params ansi.Params) (Event Event) {
1375 var isRelease bool
1376 var key Key
1377
1378 // The index of parameters separated by semicolons ';'. Sub parameters are
1379 // separated by colons ':'.
1380 var paramIdx int
1381 var sudIdx int // The sub parameter index
1382 for _, p := range params {
1383 // Kitty Keyboard Protocol has 3 optional components.
1384 switch paramIdx {
1385 case 0:
1386 switch sudIdx {
1387 case 0:
1388 var foundKey bool
1389 code := p.Param(1) // CSI u has a default value of 1
1390 key, foundKey = kittyKeyMap[code]
1391 if !foundKey {
1392 r := rune(code)
1393 if !utf8.ValidRune(r) {
1394 r = utf8.RuneError
1395 }
1396
1397 key.Code = r
1398 }
1399
1400 case 2:
1401 // shifted key + base key
1402 if b := rune(p.Param(1)); unicode.IsPrint(b) {
1403 // XXX: When alternate key reporting is enabled, the protocol
1404 // can return 3 things, the unicode codepoint of the key,
1405 // the shifted codepoint of the key, and the standard
1406 // PC-101 key layout codepoint.
1407 // This is useful to create an unambiguous mapping of keys
1408 // when using a different language layout.
1409 key.BaseCode = b
1410 }
1411 fallthrough
1412
1413 case 1:
1414 // shifted key
1415 if s := rune(p.Param(1)); unicode.IsPrint(s) {
1416 // XXX: We swap keys here because we want the shifted key
1417 // to be the Rune that is returned by the event.
1418 // For example, shift+a should produce "A" not "a".
1419 // In such a case, we set AltRune to the original key "a"
1420 // and Rune to "A".
1421 key.ShiftedCode = s
1422 }
1423 }
1424 case 1:
1425 switch sudIdx {
1426 case 0:
1427 mod := p.Param(1)
1428 if mod > 1 {
1429 key.Mod = fromKittyMod(mod - 1)
1430 if key.Mod > ModShift {
1431 // XXX: We need to clear the text if we have a modifier key
1432 // other than a [ModShift] key.
1433 key.Text = ""
1434 }
1435 }
1436
1437 case 1:
1438 switch p.Param(1) {
1439 case 2:
1440 key.IsRepeat = true
1441 case 3:
1442 isRelease = true
1443 }
1444 case 2:
1445 }
1446 case 2:
1447 if code := p.Param(0); code != 0 {
1448 key.Text += string(rune(code))
1449 }
1450 }
1451
1452 sudIdx++
1453 if !p.HasMore() {
1454 paramIdx++
1455 sudIdx = 0
1456 }
1457 }
1458
1459 //nolint:nestif
1460 if len(key.Text) == 0 && unicode.IsPrint(key.Code) &&
1461 (key.Mod <= ModShift || key.Mod == ModCapsLock || key.Mod == ModShift|ModCapsLock) {
1462 if key.Mod == 0 {
1463 key.Text = string(key.Code)
1464 } else {
1465 desiredCase := unicode.ToLower
1466 if key.Mod.Contains(ModShift) || key.Mod.Contains(ModCapsLock) {
1467 desiredCase = unicode.ToUpper
1468 }
1469 if key.ShiftedCode != 0 {
1470 key.Text = string(key.ShiftedCode)
1471 } else {
1472 key.Text = string(desiredCase(key.Code))
1473 }
1474 }
1475 }
1476
1477 if isRelease {
1478 return KeyReleaseEvent(key)
1479 }
1480
1481 return KeyPressEvent(key)
1482}
1483
1484// parseKittyKeyboardExt parses a Kitty Keyboard Protocol sequence extensions
1485// for non CSI u sequences. This includes things like CSI A, SS3 A and others,
1486// and CSI ~.
1487func parseKittyKeyboardExt(params ansi.Params, k KeyPressEvent) Event {
1488 // Handle Kitty keyboard protocol
1489 if len(params) > 2 && // We have at least 3 parameters
1490 params[0].Param(1) == 1 && // The first parameter is 1 (defaults to 1)
1491 params[1].HasMore() { // The second parameter is a subparameter (separated by a ":")
1492 switch params[2].Param(1) { // The third parameter is the event type (defaults to 1)
1493 case 2:
1494 k.IsRepeat = true
1495 case 3:
1496 return KeyReleaseEvent(k)
1497 }
1498 }
1499 return k
1500}
1501
1502func parsePrimaryDevAttrs(params ansi.Params) Event {
1503 // Primary Device Attributes
1504 da1 := make(PrimaryDeviceAttributesEvent, len(params))
1505 for i, p := range params {
1506 if !p.HasMore() {
1507 da1[i] = p.Param(0)
1508 }
1509 }
1510 return da1
1511}
1512
1513// Parse SGR-encoded mouse events; SGR extended mouse events. SGR mouse events
1514// look like:
1515//
1516// ESC [ < Cb ; Cx ; Cy (M or m)
1517//
1518// where:
1519//
1520// Cb is the encoded button code
1521// Cx is the x-coordinate of the mouse
1522// Cy is the y-coordinate of the mouse
1523// M is for button press, m is for button release
1524//
1525// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
1526func parseSGRMouseEvent(cmd ansi.Cmd, params ansi.Params) Event {
1527 x, _, ok := params.Param(1, 1)
1528 if !ok {
1529 x = 1
1530 }
1531 y, _, ok := params.Param(2, 1)
1532 if !ok {
1533 y = 1
1534 }
1535 release := cmd.Final() == 'm'
1536 b, _, _ := params.Param(0, 0)
1537 mod, btn, _, isMotion := parseMouseButton(b)
1538
1539 // (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
1540 x--
1541 y--
1542
1543 m := Mouse{X: x, Y: y, Button: btn, Mod: mod}
1544
1545 // Wheel buttons don't have release events
1546 // Motion can be reported as a release event in some terminals (Windows Terminal)
1547 if isWheel(m.Button) {
1548 return MouseWheelEvent(m)
1549 } else if !isMotion && release {
1550 return MouseReleaseEvent(m)
1551 } else if isMotion {
1552 return MouseMotionEvent(m)
1553 }
1554 return MouseClickEvent(m)
1555}
1556
1557const x10MouseByteOffset = 32
1558
1559// Parse X10-encoded mouse events; the simplest kind. The last release of X10
1560// was December 1986, by the way. The original X10 mouse protocol limits the Cx
1561// and Cy coordinates to 223 (=255-032).
1562//
1563// X10 mouse events look like:
1564//
1565// ESC [M Cb Cx Cy
1566//
1567// See: http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking
1568func parseX10MouseEvent(buf []byte) Event {
1569 v := buf[3:6]
1570 b := int(v[0])
1571 if b >= x10MouseByteOffset {
1572 // XXX: b < 32 should be impossible, but we're being defensive.
1573 b -= x10MouseByteOffset
1574 }
1575
1576 mod, btn, isRelease, isMotion := parseMouseButton(b)
1577
1578 // (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
1579 x := int(v[1]) - x10MouseByteOffset - 1
1580 y := int(v[2]) - x10MouseByteOffset - 1
1581
1582 m := Mouse{X: x, Y: y, Button: btn, Mod: mod}
1583 if isWheel(m.Button) {
1584 return MouseWheelEvent(m)
1585 } else if isMotion {
1586 return MouseMotionEvent(m)
1587 } else if isRelease {
1588 return MouseReleaseEvent(m)
1589 }
1590 return MouseClickEvent(m)
1591}
1592
1593// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
1594func parseMouseButton(b int) (mod KeyMod, btn MouseButton, isRelease bool, isMotion bool) {
1595 // mouse bit shifts
1596 const (
1597 bitShift = 0b0000_0100
1598 bitAlt = 0b0000_1000
1599 bitCtrl = 0b0001_0000
1600 bitMotion = 0b0010_0000
1601 bitWheel = 0b0100_0000
1602 bitAdd = 0b1000_0000 // additional buttons 8-11
1603
1604 bitsMask = 0b0000_0011
1605 )
1606
1607 // Modifiers
1608 if b&bitAlt != 0 {
1609 mod |= ModAlt
1610 }
1611 if b&bitCtrl != 0 {
1612 mod |= ModCtrl
1613 }
1614 if b&bitShift != 0 {
1615 mod |= ModShift
1616 }
1617
1618 if b&bitAdd != 0 {
1619 btn = MouseBackward + MouseButton(b&bitsMask)
1620 } else if b&bitWheel != 0 {
1621 btn = MouseWheelUp + MouseButton(b&bitsMask)
1622 } else {
1623 btn = MouseLeft + MouseButton(b&bitsMask)
1624 // X10 reports a button release as 0b0000_0011 (3)
1625 if b&bitsMask == bitsMask {
1626 btn = MouseNone
1627 isRelease = true
1628 }
1629 }
1630
1631 // Motion bit doesn't get reported for wheel events.
1632 if b&bitMotion != 0 && !isWheel(btn) {
1633 isMotion = true
1634 }
1635
1636 return //nolint:nakedret
1637}
1638
1639// isWheel returns true if the mouse event is a wheel event.
1640func isWheel(btn MouseButton) bool {
1641 return btn >= MouseWheelUp && btn <= MouseWheelRight
1642}
1643
1644type shiftable interface {
1645 ~uint | ~uint16 | ~uint32 | ~uint64
1646}
1647
1648func shift[T shiftable](x T) T {
1649 if x > 0xff {
1650 x >>= 8
1651 }
1652 return x
1653}
1654
1655func colorToHex(c color.Color) string {
1656 if c == nil {
1657 return ""
1658 }
1659 r, g, b, _ := c.RGBA()
1660 return fmt.Sprintf("#%02x%02x%02x", shift(r), shift(g), shift(b))
1661}
1662
1663func getMaxMin(a, b, c float64) (ma, mi float64) {
1664 if a > b {
1665 ma = a
1666 mi = b
1667 } else {
1668 ma = b
1669 mi = a
1670 }
1671 if c > ma {
1672 ma = c
1673 } else if c < mi {
1674 mi = c
1675 }
1676 return ma, mi
1677}
1678
1679func round(x float64) float64 {
1680 return math.Round(x*1000) / 1000
1681}
1682
1683// rgbToHSL converts an RGB triple to an HSL triple.
1684func rgbToHSL(r, g, b uint8) (h, s, l float64) {
1685 // convert uint32 pre-multiplied value to uint8
1686 // The r,g,b values are divided by 255 to change the range from 0..255 to 0..1:
1687 Rnot := float64(r) / 255
1688 Gnot := float64(g) / 255
1689 Bnot := float64(b) / 255
1690 Cmax, Cmin := getMaxMin(Rnot, Gnot, Bnot)
1691 Δ := Cmax - Cmin
1692 // Lightness calculation:
1693 l = (Cmax + Cmin) / 2
1694 // Hue and Saturation Calculation:
1695 if Δ == 0 {
1696 h = 0
1697 s = 0
1698 } else {
1699 switch Cmax {
1700 case Rnot:
1701 h = 60 * (math.Mod((Gnot-Bnot)/Δ, 6))
1702 case Gnot:
1703 h = 60 * (((Bnot - Rnot) / Δ) + 2)
1704 case Bnot:
1705 h = 60 * (((Rnot - Gnot) / Δ) + 4)
1706 }
1707 if h < 0 {
1708 h += 360
1709 }
1710
1711 s = Δ / (1 - math.Abs((2*l)-1))
1712 }
1713
1714 return h, round(s), round(l)
1715}
1716
1717// isDarkColor returns whether the given color is dark.
1718func isDarkColor(c color.Color) bool {
1719 if c == nil {
1720 return true
1721 }
1722
1723 r, g, b, _ := c.RGBA()
1724 _, _, l := rgbToHSL(uint8(r>>8), uint8(g>>8), uint8(b>>8)) //nolint:gosec
1725 return l < 0.5
1726}
1727
1728func parseTermcap(data []byte) CapabilityEvent {
1729 // XTGETTCAP
1730 if len(data) == 0 {
1731 return CapabilityEvent("")
1732 }
1733
1734 var tc strings.Builder
1735 split := bytes.Split(data, []byte{';'})
1736 for _, s := range split {
1737 parts := bytes.SplitN(s, []byte{'='}, 2)
1738 if len(parts) == 0 {
1739 return CapabilityEvent("")
1740 }
1741
1742 name, err := hex.DecodeString(string(parts[0]))
1743 if err != nil || len(name) == 0 {
1744 continue
1745 }
1746
1747 var value []byte
1748 if len(parts) > 1 {
1749 value, err = hex.DecodeString(string(parts[1]))
1750 if err != nil {
1751 continue
1752 }
1753 }
1754
1755 if tc.Len() > 0 {
1756 tc.WriteByte(';')
1757 }
1758 tc.WriteString(string(name))
1759 if len(value) > 0 {
1760 tc.WriteByte('=')
1761 tc.WriteString(string(value))
1762 }
1763 }
1764
1765 return CapabilityEvent(tc.String())
1766}