1 /** 2 * Parser module for dcell contains the code for parsing terminfo escapes 3 * as they arrive on /dev/tty. 4 * 5 * Copyright: Copyright 2022 Garrett D'Amore 6 * Authors: Garrett D'Amore 7 * License: 8 * Distributed under the Boost Software License, Version 1.0. 9 * (See accompanying file LICENSE or https://www.boost.org/LICENSE_1_0.txt) 10 * SPDX-License-Identifier: BSL-1.0 11 */ 12 module dcell.parser; 13 14 import core.time; 15 import std.ascii; 16 import std.string; 17 import std.utf; 18 19 import dcell.key; 20 import dcell.mouse; 21 import dcell.event; 22 import dcell.termcap; 23 24 package: 25 26 struct KeyCode 27 { 28 Key key; 29 Modifiers mod; 30 } 31 32 struct ParseKeys 33 { 34 35 immutable bool[Key] exist; 36 immutable KeyCode[string] keys; 37 string pasteStart; 38 string pasteEnd; 39 40 this(const Termcap* caps) 41 { 42 43 bool[Key] ex; 44 KeyCode[string] kc; 45 46 void addKey(Key key, string val, Modifiers mod = Modifiers.none, Key replace = cast(Key) 0) 47 { 48 if (val == "") 49 return; 50 if ((val !in kc) || kc[val].key == replace) 51 { 52 ex[key] = true; 53 kc[val] = KeyCode(key, mod); 54 } 55 } 56 57 void addXTermKey(Key key, string val) 58 { 59 if (val.length > 2 && val[0] == '\x1b' && val[1] == '[' && val[$ - 1] == '~') 60 { 61 // These suffixes are calculated assuming Xterm style modifier suffixes. 62 // Please see https://invisible-island.net/xterm/ctlseqs/ctlseqs.pdf for 63 // more information (specifically "PC-Style Function Keys"). 64 val = val[0 .. $ - 1]; // drop trailing ~ 65 addKey(key, val ~ ";2~", Modifiers.shift, cast(Key)(key + 12)); 66 addKey(key, val ~ ";3~", Modifiers.alt, cast(Key)(key + 48)); 67 addKey(key, val ~ ";4~", Modifiers.alt | Modifiers.shift, cast(Key)(key + 60)); 68 addKey(key, val ~ ";5~", Modifiers.ctrl, cast(Key)(key + 24)); 69 addKey(key, val ~ ";6~", Modifiers.ctrl | Modifiers.shift, cast(Key)(key + 36)); 70 addKey(key, val ~ ";7~", Modifiers.alt | Modifiers.ctrl); 71 addKey(key, val ~ ";8~", Modifiers.shift | Modifiers.alt | Modifiers.ctrl); 72 addKey(key, val ~ ";9~", Modifiers.meta); 73 addKey(key, val ~ ";10~", Modifiers.meta | Modifiers.shift); 74 addKey(key, val ~ ";11~", Modifiers.meta | Modifiers.alt); 75 addKey(key, val ~ ";12~", Modifiers.meta | Modifiers.shift | Modifiers.alt); 76 addKey(key, val ~ ";13~", Modifiers.meta | Modifiers.ctrl); 77 addKey(key, val ~ ";14~", Modifiers.meta | Modifiers.ctrl | Modifiers.shift); 78 addKey(key, val ~ ";15~", Modifiers.meta | Modifiers.ctrl | Modifiers.alt); 79 addKey(key, val ~ ";16~", 80 Modifiers.meta | Modifiers.ctrl | Modifiers.shift | Modifiers.alt); 81 } 82 else if (val.length == 3 && val[0] == '\x1b' && val[1] == 'O') 83 { 84 val = val[2 .. $]; 85 addKey(key, "\x1b[1;2" ~ val, Modifiers.shift, cast(Key)(key + 12)); 86 addKey(key, "\x1b[1;3" ~ val, Modifiers.alt, cast(Key)(key + 48)); 87 addKey(key, "\x1b[1;5" ~ val, Modifiers.ctrl, cast(Key)(key + 24)); 88 addKey(key, "\x1b[1;6" ~ val, Modifiers.ctrl | Modifiers.shift, cast(Key)(key + 36)); 89 addKey(key, "\x1b[1;4" ~ val, Modifiers.alt | Modifiers.shift, cast(Key)(key + 60)); 90 addKey(key, "\x1b[1;7" ~ val, Modifiers.alt | Modifiers.ctrl); 91 addKey(key, "\x1b[1;8" ~ val, Modifiers.shift | Modifiers.alt | Modifiers.ctrl); 92 addKey(key, "\x1b[1;9" ~ val, Modifiers.meta,); 93 addKey(key, "\x1b[1;10" ~ val, Modifiers.meta | Modifiers.shift); 94 addKey(key, "\x1b[1;11" ~ val, Modifiers.meta | Modifiers.alt); 95 addKey(key, "\x1b[1;12" ~ val, Modifiers.meta | Modifiers.alt | Modifiers.shift); 96 addKey(key, "\x1b[1;13" ~ val, Modifiers.meta | Modifiers.ctrl); 97 addKey(key, "\x1b[1;14" ~ val, Modifiers.meta | Modifiers.ctrl | Modifiers.shift); 98 addKey(key, "\x1b[1;15" ~ val, Modifiers.meta | Modifiers.ctrl | Modifiers.alt); 99 addKey(key, "\x1b[1;16" ~ val, 100 Modifiers.meta | Modifiers.ctrl | Modifiers.alt | Modifiers.shift); 101 } 102 } 103 104 void addXTermKeys(const Termcap* caps) 105 { 106 if (caps.keyShfRight != "\x1b[1;2C") // does this look "xtermish"? 107 return; 108 addXTermKey(Key.right, caps.keyRight); 109 addXTermKey(Key.left, caps.keyLeft); 110 addXTermKey(Key.up, caps.keyUp); 111 addXTermKey(Key.down, caps.keyDown); 112 addXTermKey(Key.insert, caps.keyInsert); 113 addXTermKey(Key.del, caps.keyDelete); 114 addXTermKey(Key.pgUp, caps.keyPgUp); 115 addXTermKey(Key.pgDn, caps.keyPgDn); 116 addXTermKey(Key.home, caps.keyHome); 117 addXTermKey(Key.end, caps.keyEnd); 118 addXTermKey(Key.f1, caps.keyF1); 119 addXTermKey(Key.f2, caps.keyF2); 120 addXTermKey(Key.f3, caps.keyF3); 121 addXTermKey(Key.f4, caps.keyF4); 122 addXTermKey(Key.f5, caps.keyF5); 123 addXTermKey(Key.f6, caps.keyF6); 124 addXTermKey(Key.f7, caps.keyF7); 125 addXTermKey(Key.f8, caps.keyF8); 126 addXTermKey(Key.f9, caps.keyF9); 127 addXTermKey(Key.f10, caps.keyF10); 128 addXTermKey(Key.f11, caps.keyF11); 129 addXTermKey(Key.f12, caps.keyF12); 130 } 131 132 void addCtrlKeys() 133 { 134 // we look briefly at all the keyCodes we 135 // have, to find their starting character. 136 // the vast majority of these will be escape. 137 bool[char] initials; 138 foreach (esc, _; kc) 139 { 140 if (esc != "") 141 initials[esc[0]] = true; 142 } 143 // Add key mappings for control keys. 144 for (char i = 0; i < ' '; i++) 145 { 146 147 // If this is starting character (typically esc) of other sequences, 148 // then do not set up the fast path mapping for it. 149 // We need to let the read do the whole timeout thing. 150 if (i in initials) 151 continue; 152 153 Key k = cast(Key) i; 154 ex[k] = true; 155 switch (k) 156 { 157 case Key.backspace, Key.tab, Key.esc, Key.enter: 158 // these are directly typeable 159 kc["" ~ i] = KeyCode(k, Modifiers.none); 160 break; 161 default: 162 // these are generally represented as a control sequence 163 kc["" ~ i] = KeyCode(k, Modifiers.ctrl); 164 break; 165 } 166 } 167 168 } 169 170 addKey(Key.backspace, caps.keyBackspace); 171 addKey(Key.f1, caps.keyF1); 172 addKey(Key.f2, caps.keyF2); 173 addKey(Key.f3, caps.keyF3); 174 addKey(Key.f4, caps.keyF4); 175 addKey(Key.f5, caps.keyF5); 176 addKey(Key.f6, caps.keyF6); 177 addKey(Key.f7, caps.keyF7); 178 addKey(Key.f8, caps.keyF8); 179 addKey(Key.f9, caps.keyF9); 180 addKey(Key.f10, caps.keyF10); 181 addKey(Key.f11, caps.keyF11); 182 addKey(Key.f12, caps.keyF12); 183 addKey(Key.f13, caps.keyF13); 184 addKey(Key.f14, caps.keyF14); 185 addKey(Key.f15, caps.keyF15); 186 addKey(Key.f16, caps.keyF16); 187 addKey(Key.f17, caps.keyF17); 188 addKey(Key.f18, caps.keyF18); 189 addKey(Key.f19, caps.keyF19); 190 addKey(Key.f20, caps.keyF20); 191 addKey(Key.f21, caps.keyF21); 192 addKey(Key.f22, caps.keyF22); 193 addKey(Key.f23, caps.keyF23); 194 addKey(Key.f24, caps.keyF24); 195 addKey(Key.f25, caps.keyF25); 196 addKey(Key.f26, caps.keyF26); 197 addKey(Key.f27, caps.keyF27); 198 addKey(Key.f28, caps.keyF28); 199 addKey(Key.f29, caps.keyF29); 200 addKey(Key.f30, caps.keyF30); 201 addKey(Key.f31, caps.keyF31); 202 addKey(Key.f32, caps.keyF32); 203 addKey(Key.f33, caps.keyF33); 204 addKey(Key.f34, caps.keyF34); 205 addKey(Key.f35, caps.keyF35); 206 addKey(Key.f36, caps.keyF36); 207 addKey(Key.f37, caps.keyF37); 208 addKey(Key.f38, caps.keyF38); 209 addKey(Key.f39, caps.keyF39); 210 addKey(Key.f40, caps.keyF40); 211 addKey(Key.f41, caps.keyF41); 212 addKey(Key.f42, caps.keyF42); 213 addKey(Key.f43, caps.keyF43); 214 addKey(Key.f44, caps.keyF44); 215 addKey(Key.f45, caps.keyF45); 216 addKey(Key.f46, caps.keyF46); 217 addKey(Key.f47, caps.keyF47); 218 addKey(Key.f48, caps.keyF48); 219 addKey(Key.f49, caps.keyF49); 220 addKey(Key.f50, caps.keyF50); 221 addKey(Key.f51, caps.keyF51); 222 addKey(Key.f52, caps.keyF52); 223 addKey(Key.f53, caps.keyF53); 224 addKey(Key.f54, caps.keyF54); 225 addKey(Key.f55, caps.keyF55); 226 addKey(Key.f56, caps.keyF56); 227 addKey(Key.f57, caps.keyF57); 228 addKey(Key.f58, caps.keyF58); 229 addKey(Key.f59, caps.keyF59); 230 addKey(Key.f60, caps.keyF60); 231 addKey(Key.f61, caps.keyF61); 232 addKey(Key.f62, caps.keyF62); 233 addKey(Key.f63, caps.keyF63); 234 addKey(Key.f64, caps.keyF64); 235 addKey(Key.insert, caps.keyInsert); 236 addKey(Key.del, caps.keyDelete); 237 addKey(Key.home, caps.keyHome); 238 addKey(Key.end, caps.keyEnd); 239 addKey(Key.up, caps.keyUp); 240 addKey(Key.down, caps.keyDown); 241 addKey(Key.left, caps.keyLeft); 242 addKey(Key.right, caps.keyRight); 243 addKey(Key.pgUp, caps.keyPgUp); 244 addKey(Key.pgDn, caps.keyPgDn); 245 addKey(Key.help, caps.keyHelp); 246 addKey(Key.print, caps.keyPrint); 247 addKey(Key.cancel, caps.keyCancel); 248 addKey(Key.exit, caps.keyExit); 249 addKey(Key.backtab, caps.keyBacktab); 250 251 addKey(Key.right, caps.keyShfRight, Modifiers.shift); 252 addKey(Key.left, caps.keyShfLeft, Modifiers.shift); 253 addKey(Key.up, caps.keyShfUp, Modifiers.shift); 254 addKey(Key.down, caps.keyShfDown, Modifiers.shift); 255 addKey(Key.home, caps.keyShfHome, Modifiers.shift); 256 addKey(Key.end, caps.keyShfEnd, Modifiers.shift); 257 addKey(Key.pgUp, caps.keyShfPgUp, Modifiers.shift); 258 addKey(Key.pgDn, caps.keyShfPgDn, Modifiers.shift); 259 260 addKey(Key.right, caps.keyCtrlRight, Modifiers.ctrl); 261 addKey(Key.left, caps.keyCtrlLeft, Modifiers.ctrl); 262 addKey(Key.up, caps.keyCtrlUp, Modifiers.ctrl); 263 addKey(Key.down, caps.keyCtrlDown, Modifiers.ctrl); 264 addKey(Key.home, caps.keyCtrlHome, Modifiers.ctrl); 265 addKey(Key.end, caps.keyCtrlEnd, Modifiers.ctrl); 266 267 // Sadly, xterm handling of keycodes is somewhat erratic. In 268 // particular, different codes are sent depending on application 269 // mode is in use or not, and the entries for many of these are 270 // simply absent from terminfo on many systems. So we insert 271 // a number of escape sequences if they are not already used, in 272 // order to have the widest correct usage. Note that prepareKey 273 // will not inject codes if the escape sequence is already known. 274 // We also only do this for terminals that have the application 275 // mode present. 276 277 if (caps.enterKeypad != "") 278 { 279 addKey(Key.up, "\x1b[A"); 280 addKey(Key.down, "\x1b[B"); 281 addKey(Key.right, "\x1b[C"); 282 addKey(Key.left, "\x1b[D"); 283 addKey(Key.end, "\x1b[F"); 284 addKey(Key.home, "\x1b[H"); 285 addKey(Key.del2, "\x1b[3~"); 286 addKey(Key.home, "\x1b[1~"); 287 addKey(Key.end, "\x1b[4~"); 288 addKey(Key.pgUp, "\x1b[5~"); 289 addKey(Key.pgDn, "\x1b[6~"); 290 291 // Application mode 292 addKey(Key.up, "\x1bOA"); 293 addKey(Key.down, "\x1bOB"); 294 addKey(Key.right, "\x1bOC"); 295 addKey(Key.left, "\x1bOD"); 296 addKey(Key.home, "\x1bOH"); 297 } 298 299 addXTermKeys(caps); 300 301 pasteStart = caps.pasteStart; 302 pasteEnd = caps.pasteEnd; 303 304 addCtrlKeys(); // do this one last 305 306 // if DELETE didn't make it at 0x7F, add it - seems to be missing 307 // from some of the sequences. 308 addKey(Key.del2, "\x7F"); 309 310 exist = cast(immutable(bool[Key])) ex; 311 keys = cast(immutable(KeyCode[string])) kc; 312 313 } 314 315 bool hasKey(Key k) const pure 316 { 317 if (k == Key.rune) 318 { 319 return true; 320 } 321 return (k in exist ? true : false); 322 } 323 } 324 325 class Parser 326 { 327 328 this(const ParseKeys pk) 329 { 330 keyExist = pk.exist; 331 keyCodes = pk.keys; 332 pasteStart = pk.pasteStart; 333 pasteEnd = pk.pasteEnd; 334 } 335 336 Event[] events() pure 337 { 338 auto res = evs; 339 evs = null; 340 return cast(Event[]) res; 341 } 342 343 bool parse(string b) 344 { 345 auto now = MonoTime.currTime(); 346 if (b.length != 0) 347 { 348 // if we are adding to it, restart the timer 349 keyStart = now; 350 } 351 buf ~= b; 352 353 while (buf.length != 0) 354 { 355 partial = false; 356 357 // we have to parse the paste bit first 358 if (parsePaste() || parseRune() || parseFnKey() || parseSgrMouse() || parseLegacyMouse()) 359 { 360 keyStart = now; 361 continue; 362 } 363 auto expire = ((now - keyStart) > seqTime); 364 365 if (!partial || expire) 366 { 367 if (buf[0] == '\x1b') 368 { 369 if (buf.length == 1) 370 { 371 evs ~= newKeyEvent(Key.esc); 372 escaped = false; 373 } 374 else 375 { 376 escaped = true; 377 } 378 buf = buf[1 .. $]; 379 keyStart = now; 380 continue; 381 } 382 // no matches or timeout waiting for data, yank off the first byte 383 evs ~= newKeyEvent(Key.rune, buf[0], escaped ? Modifiers.alt : Modifiers.none); 384 escaped = false; 385 keyStart = now; 386 continue; 387 } 388 // we must have partial data, so wait and come back in a bit 389 return false; 390 } 391 392 return true; 393 } 394 395 bool empty() const pure 396 { 397 return buf.length == 0; 398 } 399 400 private: 401 402 bool escaped; 403 ubyte[] buf; 404 Event[] evs; 405 const KeyCode[string] keyCodes; 406 const bool[Key] keyExist; 407 bool partial; // record partially parsed sequences 408 MonoTime keyStart; // when the timer started 409 Duration seqTime = msecs(50); // time to fully decode a partial sequence 410 bool buttonDown; // true if buttons were down 411 bool wasButton; // true if we saw a button press for recent mouse event 412 bool pasting; 413 MonoTime pasteTime; 414 dstring pasteBuf; 415 string pasteStart; 416 string pasteEnd; 417 418 Event newKeyEvent(Key k, dchar dch = 0, Modifiers mod = Modifiers.none) 419 { 420 Event ev = { 421 type: EventType.key, when: MonoTime.currTime(), key: { 422 key: k, ch: dch, mod: mod 423 } 424 }; 425 return ev; 426 } 427 428 // NB: it is possible for x and y to be outside the current coordinates 429 // (happens for click drag for example). Consumer of the event should clip 430 // the coordinates as needed. 431 Event newMouseEvent(int x, int y, int btn) 432 { 433 Event ev = { 434 type: EventType.mouse, when: MonoTime.currTime, mouse: { 435 pos: Coord(x, y) 436 } 437 }; 438 439 // Mouse wheel has bit 6 set, no release events. It should be noted 440 // that wheel events are sometimes misdelivered as mouse button events 441 // during a click-drag, so we debounce these, considering them to be 442 // button press events unless we see an intervening release event. 443 444 switch (btn & 0x43) 445 { 446 case 0: 447 ev.mouse.btn = Buttons.button1; 448 wasButton = true; 449 break; 450 case 1: 451 ev.mouse.btn = Buttons.button3; 452 wasButton = true; 453 break; 454 case 2: 455 ev.mouse.btn = Buttons.button2; 456 wasButton = true; 457 break; 458 case 3: 459 ev.mouse.btn = Buttons.none; 460 wasButton = false; 461 break; 462 case 0x40: 463 ev.mouse.btn = wasButton ? Buttons.button1 : Buttons.wheelUp; 464 break; 465 case 0x41: 466 ev.mouse.btn = wasButton ? Buttons.button2 : Buttons.wheelDown; 467 break; 468 default: 469 break; 470 } 471 if (btn & 0x4) 472 ev.mouse.mod |= Modifiers.shift; 473 if (btn & 0x8) 474 ev.mouse.mod |= Modifiers.alt; 475 if (btn & 0x10) 476 ev.mouse.mod |= Modifiers.ctrl; 477 return ev; 478 } 479 480 Event newPasteEvent(dstring buffer) 481 { 482 Event ev = { 483 type: EventType.paste, when: MonoTime.currTime(), paste: { 484 content: buffer 485 } 486 }; 487 return ev; 488 489 } 490 491 bool parseSequence(string seq) 492 { 493 if (startsWith(buf, seq)) 494 { 495 buf = buf[seq.length .. $]; // yank the sequence 496 return true; 497 } 498 if (startsWith(seq, buf)) 499 { 500 partial = true; 501 } 502 return false; 503 } 504 505 bool parsePaste() 506 { 507 bool matches; 508 509 if (pasteStart == "") 510 { 511 return false; 512 } 513 514 auto now = MonoTime.currTime(); 515 if (!pasting) 516 { 517 if (parseSequence(pasteStart)) 518 { 519 pasting = true; 520 pasteBuf = ""; 521 pasteTime = now; 522 return true; 523 } 524 return false; 525 } 526 assert(pasting); 527 528 // we end the sequence if we see it, but also if we timed out looking for it 529 if (parseSequence(pasteEnd) || 530 ((now - pasteTime) > seqTime * 4)) 531 { 532 pasting = false; 533 auto ev = newPasteEvent(pasteBuf); 534 pasteBuf = ""; 535 evs ~= ev; 536 return true; 537 } 538 if (parseRune()) 539 { 540 pasteTime = now; 541 } 542 // we pretend to have eaten the entire thing, even if there is stuff left 543 return true; 544 } 545 546 bool parseRune() 547 { 548 if (buf.length == 0) 549 { 550 return false; 551 } 552 dchar dc; 553 Modifiers mod = Modifiers.none; 554 if ((buf[0] >= ' ' && buf[0] < 0x7F) || 555 (pasting && isWhite(buf[0]))) 556 { 557 dc = buf[0]; 558 buf = buf[1 .. $]; 559 // printable ascii, easy to deal with 560 if (escaped) 561 { 562 escaped = false; 563 mod = Modifiers.alt; 564 } 565 if (pasting) 566 pasteBuf ~= dc; 567 else 568 evs ~= newKeyEvent(Key.rune, dc, mod); 569 return true; 570 } 571 if ((buf[0] < 0x80) || (buf[0] == 0x7F)) // control character, not a rune 572 return false; 573 // unicode bits... 574 size_t index = 0; 575 auto temp = cast(string) buf; 576 try 577 { 578 dc = temp.decode(index); 579 } 580 catch (UTFException e) 581 { 582 return false; 583 } 584 if (pasting) 585 pasteBuf ~= dc; 586 else 587 evs ~= newKeyEvent(Key.rune, dc, mod); 588 buf = buf[index .. $]; 589 return true; 590 } 591 592 bool parseFnKey() 593 { 594 Modifiers mod = Modifiers.none; 595 if (buf.length == 0) 596 { 597 return false; 598 } 599 foreach (seq, kc; keyCodes) 600 { 601 // ideally we'd use a trie to isolate 602 // duplicates for this. for now, we just handle esc. 603 if (seq.length == 1 && seq[0] == '\x1b') 604 { 605 continue; 606 } 607 if (parseSequence(seq)) 608 { 609 mod = kc.mod; 610 if (escaped) 611 { 612 escaped = false; 613 mod = Modifiers.alt; 614 } 615 evs ~= newKeyEvent(kc.key, seq.length == 1 ? seq[0] : 0, mod); 616 return true; 617 } 618 } 619 return false; 620 } 621 622 // look for an SGR mouse record, using a state machine 623 bool parseSgrMouse() 624 { 625 int x, y, btn, mov, state, val; 626 bool dig, neg; 627 628 for (int i = 0; i < buf.length; i++) 629 { 630 switch (buf[i]) 631 { 632 case '\x1b': 633 if (state != 0) 634 return false; 635 state = 1; 636 break; 637 case '\x9b': // 8-bit mode CSI 638 if (state != 0) 639 return false; 640 state = 2; 641 break; 642 case '[': 643 if (state != 1) 644 return false; 645 state = 2; 646 break; 647 case '<': 648 if (state != 2) 649 return false; 650 val = 0; 651 dig = false; 652 neg = false; 653 state = 3; 654 break; 655 case '-': 656 if (state < 3 || state > 5 || dig || neg) 657 return false; 658 neg = true; // no state change 659 break; 660 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 661 if (state < 3 || state > 5) 662 return false; 663 val *= 10; 664 val += int(buf[i] - '0'); 665 dig = true; // no state change 666 break; 667 case ';': 668 if (state != 3 && state != 4) 669 return false; 670 val = neg ? -val : val; 671 if (state == 3) 672 btn = neg ? -val : val; 673 else 674 x = (neg ? -val : val) - 1; 675 state++; 676 neg = false; 677 dig = false; 678 val = 0; 679 break; 680 case 'm', 'M': 681 if (state != 5) 682 return false; 683 y = (neg ? -val : val) - 1; 684 mov = (btn & 0x20) != 0; 685 btn ^= 0x20; 686 if (buf[i] == 'm') 687 { // release 688 btn |= 0x3; 689 btn ^= 0x40; 690 buttonDown = false; 691 } 692 else if (mov) 693 { 694 // Some broken terminals appear to send 695 // mouse button one motion events, instead of 696 // encoding 35 (no buttons) into these events. 697 // We resolve these by looking for a non-motion 698 // event first. 699 if (!buttonDown) 700 { 701 btn |= 0x3; 702 btn ^= 0x40; 703 } 704 } 705 else 706 { 707 buttonDown = true; 708 } 709 buf = buf[i + 1 .. $]; 710 import std.stdio; 711 712 evs ~= newMouseEvent(x, y, btn); 713 return true; 714 default: 715 // definitely not ours 716 return false; 717 } 718 } 719 // might be ours, inconclusive 720 if (state > 0) 721 partial = true; 722 return false; 723 } 724 725 bool parseLegacyMouse() 726 { 727 int x, y, btn; 728 729 enum State 730 { 731 start, 732 bracket, 733 end 734 } 735 736 State state; 737 738 for (int i = 0; i < buf.length; i++) 739 { 740 final switch (state) 741 { 742 case State.start: 743 switch (buf[i]) 744 { 745 case '\x1b': 746 state = State.bracket; 747 break; 748 case '\x9b': 749 state = State.end; 750 break; 751 default: 752 return false; 753 } 754 break; 755 case State.bracket: 756 if (buf[i] != '[') 757 return false; 758 state++; 759 break; 760 case State.end: 761 if (buf[i] != 'M') 762 return false; 763 if (buf.length < i + 4) 764 break; 765 buf = buf[i + 1 .. $]; 766 btn = int(buf[0]); 767 x = int(buf[1]) - 32 - 1; 768 y = int(buf[2]) - 32 - 1; 769 buf = buf[3 .. $]; 770 evs ~= newMouseEvent(x, y, btn); 771 return true; 772 } 773 } 774 if (state > 0) 775 partial = true; 776 return false; 777 } 778 } 779 780 unittest 781 { 782 import core.thread; 783 import dcell.database; 784 785 // taken from xterm, but pared down 786 static immutable Termcap term = { 787 name: "test-term", 788 enterKeypad: "\x1b[?1h\x1b=", 789 exitKeypad: "\x1b[?1l\x1b>", 790 cursorBack1: "\x08", 791 cursorUp1: "\x1b[A", 792 keyBackspace: "\x08", 793 keyF1: "\x1bOP", 794 keyF2: "\x1bOQ", 795 keyF3: "\x1bOR", 796 keyInsert: "\x1b[2~", 797 keyDelete: "\x1b[3~", 798 keyHome: "\x1bOH", 799 keyEnd: "\x1bOF", 800 keyPgUp: "\x1b[5~", 801 keyPgDn: "\x1b[6~", 802 keyUp: "\x1bOA", 803 keyDown: "\x1bOB", 804 keyLeft: "\x1bOD", 805 keyRight: "\x1bOC", 806 keyBacktab: "\x1b[Z", 807 keyShfRight: "\x1b[1;2C", 808 mouse: "\x1b[M", 809 }; 810 Database.put(&term); 811 auto tc = Database.get("test-term"); 812 assert(tc !is null); 813 Parser p = new Parser(ParseKeys(tc)); 814 assert(p.empty()); 815 assert(p.parse("")); // no data, is fine 816 assert(p.parse("\x1bOC")); 817 auto ev = p.events(); 818 819 assert(ev.length == 1); 820 assert(ev[0].type == EventType.key); 821 assert(ev[0].key.key == Key.right); 822 823 // this tests that the timed pase parsing works - 824 // escape sequences are kept partially until we 825 // have a match or we have waited long enough. 826 assert(p.parse(['\x1b', 'O']) == false); 827 ev = p.events(); 828 assert(ev.length == 0); 829 Thread.sleep(msecs(100)); 830 assert(p.parse([]) == true); 831 ev = p.events(); 832 assert(ev.length == 1); 833 assert(ev[0].type == EventType.key); 834 assert(ev[0].key.key == Key.rune); 835 assert(ev[0].key.mod == Modifiers.alt); 836 837 // lone escape 838 assert(p.parse(['\x1b']) == false); 839 ev = p.events(); 840 assert(ev.length == 0); 841 Thread.sleep(msecs(100)); 842 assert(p.parse([]) == true); 843 ev = p.events(); 844 assert(ev.length == 1); 845 assert(ev[0].type == EventType.key); 846 assert(ev[0].key.key == Key.esc); 847 assert(ev[0].key.mod == Modifiers.none); 848 849 // try injecting paste events 850 assert(tc.enablePaste != ""); 851 assert(p.parse(['\x1b', '[', '2', '0', '0', '~'])); 852 assert(p.parse(['A'])); 853 assert(p.parse(['\x1b', '[', '2', '0', '1', '~'])); 854 855 ev = p.events(); 856 assert(ev.length == 1); 857 assert(ev[0].type == EventType.paste); 858 assert(ev[0].paste.content == "A"); 859 860 // mouse events 861 assert(p.parse(['\x1b', '[', '<', '3', ';', '2', ';', '3', 'M'])); 862 ev = p.events(); 863 assert(ev.length == 1); 864 assert(ev[0].type == EventType.mouse); 865 assert(ev[0].mouse.pos.x == 1); 866 assert(ev[0].mouse.pos.y == 2); 867 868 // unicode 869 string b = [0xe2, 0x82, 0xac]; 870 assert(p.parse(b)); 871 ev = p.events(); 872 assert(ev.length == 1); 873 assert(ev[0].type == EventType.key); 874 assert(ev[0].key.key == Key.rune); 875 assert(ev[0].key.ch == '€'); 876 }