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