terminal-handlers.c (42190B)
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20#include "config.h" 21 22#include "terminal/char-mappings.h" 23#include "terminal/palette.h" 24#include "terminal/terminal.h" 25#include "terminal/terminal-handlers.h" 26#include "terminal/terminal-priv.h" 27#include "terminal/types.h" 28#include "terminal/xparsecolor.h" 29 30#include <guacamole/client.h> 31#include <guacamole/protocol.h> 32#include <guacamole/socket.h> 33 34#include <stdbool.h> 35#include <stdlib.h> 36#include <wchar.h> 37 38/** 39 * Response string sent when identification is requested. 40 */ 41#define GUAC_TERMINAL_VT102_ID "\x1B[?6c" 42 43/** 44 * Arbitrary response to ENQ control character. 45 */ 46#define GUAC_TERMINAL_ANSWERBACK "GUACAMOLE" 47 48/** 49 * Response which indicates the terminal is alive. 50 */ 51#define GUAC_TERMINAL_OK "\x1B[0n" 52 53/** 54 * Advances the cursor to the next row, scrolling if the cursor would otherwise 55 * leave the scrolling region. If the cursor is already outside the scrolling 56 * region, the cursor is prevented from leaving the terminal bounds. 57 * 58 * @param term 59 * The guac_terminal whose cursor should be advanced to the next row. 60 */ 61static void guac_terminal_linefeed(guac_terminal* term) { 62 63 /* Scroll up if necessary */ 64 if (term->cursor_row == term->scroll_end) 65 guac_terminal_scroll_up(term, term->scroll_start, 66 term->scroll_end, 1); 67 68 /* Otherwise, just advance to next row if space remains */ 69 else if (term->cursor_row < term->term_height - 1) 70 term->cursor_row++; 71 72} 73 74/** 75 * Moves the cursor backward to the previous row, scrolling if the cursor would 76 * otherwise leave the scrolling region. If the cursor is already outside the 77 * scrolling region, the cursor is prevented from leaving the terminal bounds. 78 * 79 * @param term 80 * The guac_terminal whose cursor should be moved backward by one row. 81 */ 82static void guac_terminal_reverse_linefeed(guac_terminal* term) { 83 84 /* Scroll down if necessary */ 85 if (term->cursor_row == term->scroll_start) 86 guac_terminal_scroll_down(term, term->scroll_start, 87 term->scroll_end, 1); 88 89 /* Otherwise, move back one row if space remains */ 90 else if (term->cursor_row > 0) 91 term->cursor_row--; 92 93} 94 95/** 96 * Sets the position of the cursor without exceeding terminal bounds. Values 97 * which are out of bounds will be shifted to the nearest legal boundary. 98 * 99 * @param term 100 * The guac_terminal whose cursor position is being set. 101 * 102 * @param row 103 * The desired new row position. 104 * 105 * @param col 106 * The desired new column position. 107 */ 108static void guac_terminal_move_cursor(guac_terminal* term, int row, int col) { 109 110 /* Ensure cursor row is within terminal bounds */ 111 if (row >= term->term_height) 112 row = term->term_height - 1; 113 else if (row < 0) 114 row = 0; 115 116 /* Ensure cursor column is within terminal bounds */ 117 if (col >= term->term_width) 118 col = term->term_width - 1; 119 else if (col < 0) 120 col = 0; 121 122 /* Update cursor position */ 123 term->cursor_row = row; 124 term->cursor_col = col; 125 126} 127 128int guac_terminal_echo(guac_terminal* term, unsigned char c) { 129 130 int width; 131 132 static int bytes_remaining = 0; 133 static int codepoint = 0; 134 135 const int* char_mapping = term->char_mapping[term->active_char_set]; 136 137 /* Echo to pipe stream if open and not starting an ESC sequence */ 138 if (term->pipe_stream != NULL && c != 0x1B) { 139 140 guac_terminal_pipe_stream_write(term, c); 141 142 /* Do not render output while pipe is open unless explicitly requested 143 * via flags */ 144 if (!(term->pipe_stream_flags & GUAC_TERMINAL_PIPE_INTERPRET_OUTPUT)) 145 return 0; 146 147 } 148 149 /* If using non-Unicode mapping, just map straight bytes */ 150 if (char_mapping != NULL) { 151 codepoint = c; 152 bytes_remaining = 0; 153 } 154 155 /* 1-byte UTF-8 codepoint */ 156 else if ((c & 0x80) == 0x00) { /* 0xxxxxxx */ 157 codepoint = c & 0x7F; 158 bytes_remaining = 0; 159 } 160 161 /* 2-byte UTF-8 codepoint */ 162 else if ((c & 0xE0) == 0xC0) { /* 110xxxxx */ 163 codepoint = c & 0x1F; 164 bytes_remaining = 1; 165 } 166 167 /* 3-byte UTF-8 codepoint */ 168 else if ((c & 0xF0) == 0xE0) { /* 1110xxxx */ 169 codepoint = c & 0x0F; 170 bytes_remaining = 2; 171 } 172 173 /* 4-byte UTF-8 codepoint */ 174 else if ((c & 0xF8) == 0xF0) { /* 11110xxx */ 175 codepoint = c & 0x07; 176 bytes_remaining = 3; 177 } 178 179 /* Continuation of UTF-8 codepoint */ 180 else if ((c & 0xC0) == 0x80) { /* 10xxxxxx */ 181 codepoint = (codepoint << 6) | (c & 0x3F); 182 bytes_remaining--; 183 } 184 185 /* Unrecognized prefix */ 186 else { 187 codepoint = '?'; 188 bytes_remaining = 0; 189 } 190 191 /* If we need more bytes, wait for more bytes */ 192 if (bytes_remaining != 0) 193 return 0; 194 195 switch (codepoint) { 196 197 /* Enquiry */ 198 case 0x05: 199 guac_terminal_send_string(term, GUAC_TERMINAL_ANSWERBACK); 200 break; 201 202 /* Bell */ 203 case 0x07: 204 break; 205 206 /* Backspace */ 207 case 0x08: 208 guac_terminal_move_cursor(term, term->cursor_row, 209 term->cursor_col - 1); 210 break; 211 212 /* Tab */ 213 case 0x09: 214 guac_terminal_move_cursor(term, term->cursor_row, 215 guac_terminal_next_tab(term, term->cursor_col)); 216 break; 217 218 /* Line feed / VT / FF */ 219 case '\n': 220 case 0x0B: /* VT */ 221 case 0x0C: /* FF */ 222 223 /* Advance to next row */ 224 guac_terminal_linefeed(term); 225 226 /* If automatic carriage return, fall through to CR handler */ 227 if (!term->automatic_carriage_return) 228 break; 229 230 /* Carriage return */ 231 case '\r': 232 guac_terminal_move_cursor(term, term->cursor_row, 0); 233 break; 234 235 /* SO (activates character set G1) */ 236 case 0x0E: 237 term->active_char_set = 1; 238 break; 239 240 /* SI (activates character set G0) */ 241 case 0x0F: 242 term->active_char_set = 0; 243 break; 244 245 /* ESC */ 246 case 0x1B: 247 term->char_handler = guac_terminal_escape; 248 break; 249 250 /* CSI */ 251 case 0x9B: 252 term->char_handler = guac_terminal_csi; 253 break; 254 255 /* DEL (ignored) */ 256 case 0x7F: 257 break; 258 259 /* Displayable chars */ 260 default: 261 262 /* Don't bother handling control chars if unknown */ 263 if (codepoint < 0x20) 264 break; 265 266 /* Translate mappable codepoints to whatever codepoint is mapped */ 267 if (codepoint >= 0x20 && codepoint <= 0xFF && char_mapping != NULL) 268 codepoint = char_mapping[codepoint - 0x20]; 269 270 /* Wrap if necessary */ 271 if (term->cursor_col >= term->term_width) { 272 term->cursor_col = 0; 273 guac_terminal_linefeed(term); 274 } 275 276 /* If insert mode, shift other characters right by 1 */ 277 if (term->insert_mode) 278 guac_terminal_copy_columns(term, term->cursor_row, 279 term->cursor_col, term->term_width-2, 1); 280 281 /* Write character */ 282 guac_terminal_set(term, 283 term->cursor_row, 284 term->cursor_col, 285 codepoint); 286 287 width = wcwidth(codepoint); 288 if (width < 0) 289 width = 1; 290 291 /* Advance cursor */ 292 term->cursor_col += width; 293 294 } 295 296 return 0; 297 298} 299 300int guac_terminal_escape(guac_terminal* term, unsigned char c) { 301 302 switch (c) { 303 304 case '(': 305 term->char_handler = guac_terminal_g0_charset; 306 break; 307 308 case ')': 309 term->char_handler = guac_terminal_g1_charset; 310 break; 311 312 case ']': 313 term->char_handler = guac_terminal_osc; 314 break; 315 316 case '[': 317 term->char_handler = guac_terminal_csi; 318 break; 319 320 case '#': 321 term->char_handler = guac_terminal_ctrl_func; 322 break; 323 324 /* Save Cursor (DECSC) */ 325 case '7': 326 term->saved_cursor_row = term->cursor_row; 327 term->saved_cursor_col = term->cursor_col; 328 329 term->char_handler = guac_terminal_echo; 330 break; 331 332 /* Restore Cursor (DECRC) */ 333 case '8': 334 guac_terminal_move_cursor(term, 335 term->saved_cursor_row, 336 term->saved_cursor_col); 337 338 term->char_handler = guac_terminal_echo; 339 break; 340 341 /* Index (IND) */ 342 case 'D': 343 guac_terminal_linefeed(term); 344 term->char_handler = guac_terminal_echo; 345 break; 346 347 /* Next Line (NEL) */ 348 case 'E': 349 guac_terminal_move_cursor(term, term->cursor_row, 0); 350 guac_terminal_linefeed(term); 351 term->char_handler = guac_terminal_echo; 352 break; 353 354 /* Set Tab (HTS) */ 355 case 'H': 356 guac_terminal_set_tab(term, term->cursor_col); 357 term->char_handler = guac_terminal_echo; 358 break; 359 360 /* Reverse Linefeed */ 361 case 'M': 362 guac_terminal_reverse_linefeed(term); 363 term->char_handler = guac_terminal_echo; 364 break; 365 366 /* DEC Identify */ 367 case 'Z': 368 guac_terminal_send_string(term, GUAC_TERMINAL_VT102_ID); 369 term->char_handler = guac_terminal_echo; 370 break; 371 372 /* Reset */ 373 case 'c': 374 guac_terminal_reset(term); 375 break; 376 377 case '_': 378 term->char_handler = guac_terminal_apc; 379 break; 380 381 default: 382 guac_client_log(term->client, GUAC_LOG_DEBUG, 383 "Unhandled ESC sequence: %c", c); 384 term->char_handler = guac_terminal_echo; 385 386 } 387 388 return 0; 389 390} 391 392/** 393 * Given a character mapping specifier (such as B, 0, U, or K), 394 * returns the corresponding character mapping. 395 */ 396static const int* __guac_terminal_get_char_mapping(char c) { 397 398 /* Translate character specifier to actual mapping */ 399 switch (c) { 400 case 'B': return NULL; 401 case '0': return vt100_map; 402 case 'U': return null_map; 403 case 'K': return user_map; 404 } 405 406 /* Default to Unicode */ 407 return NULL; 408 409} 410 411int guac_terminal_g0_charset(guac_terminal* term, unsigned char c) { 412 413 term->char_mapping[0] = __guac_terminal_get_char_mapping(c); 414 term->char_handler = guac_terminal_echo; 415 return 0; 416 417} 418 419int guac_terminal_g1_charset(guac_terminal* term, unsigned char c) { 420 421 term->char_mapping[1] = __guac_terminal_get_char_mapping(c); 422 term->char_handler = guac_terminal_echo; 423 return 0; 424 425} 426 427/** 428 * Looks up the flag specified by the given number and mode. Used by the Set/Reset Mode 429 * functions of the terminal. 430 */ 431static bool* __guac_terminal_get_flag(guac_terminal* term, int num, char private_mode) { 432 433 if (private_mode == '?') { 434 switch (num) { 435 case 1: return &(term->application_cursor_keys); /* DECCKM */ 436 case 25: return &(term->cursor_visible); /* DECTECM */ 437 } 438 } 439 440 else if (private_mode == 0) { 441 switch (num) { 442 case 4: return &(term->insert_mode); /* DECIM */ 443 case 20: return &(term->automatic_carriage_return); /* LF/NL */ 444 } 445 } 446 447 /* Unknown flag */ 448 return NULL; 449 450} 451 452/** 453 * Parses an xterm SGR sequence specifying the RGB values of a color. 454 * 455 * @param argc 456 * The number of arguments within the argv array. 457 * 458 * @param argv 459 * The SGR arguments to parse, with the first relevant argument the 460 * red component of the RGB color. 461 * 462 * @param color 463 * The guac_terminal_color structure which should receive the parsed 464 * color values. 465 * 466 * @return 467 * The number of arguments parsed, or zero if argv does not contain 468 * enough elements to represent an RGB color. 469 */ 470static int guac_terminal_parse_xterm256_rgb(int argc, const int* argv, 471 guac_terminal_color* color) { 472 473 /* RGB color palette entries require three arguments */ 474 if (argc < 3) 475 return 0; 476 477 /* Read RGB components from arguments */ 478 int red = argv[0]; 479 int green = argv[1]; 480 int blue = argv[2]; 481 482 /* Ignore if components are out of range */ 483 if ( red < 0 || red > 255 484 || green < 0 || green > 255 485 || blue < 0 || blue > 255) 486 return 3; 487 488 /* Store RGB components */ 489 color->red = (uint8_t) red; 490 color->green = (uint8_t) green; 491 color->blue = (uint8_t) blue; 492 493 /* Color is not from the palette */ 494 color->palette_index = -1; 495 496 /* Done */ 497 return 3; 498 499} 500 501/** 502 * Parses an xterm SGR sequence specifying the index of a color within the 503 * 256-color palette. 504 * 505 * @param terminal 506 * The terminal associated with the palette. 507 * 508 * @param argc 509 * The number of arguments within the argv array. 510 * 511 * @param argv 512 * The SGR arguments to parse, with the first relevant argument being 513 * the index of the color. 514 * 515 * @param color 516 * The guac_terminal_color structure which should receive the parsed 517 * color values. 518 * 519 * @return 520 * The number of arguments parsed, or zero if the palette index is 521 * absent. 522 */ 523static int guac_terminal_parse_xterm256_index(guac_terminal* terminal, 524 int argc, const int* argv, guac_terminal_color* color) { 525 526 /* 256-color palette entries require only one argument */ 527 if (argc < 1) 528 return 0; 529 530 /* Copy palette entry */ 531 guac_terminal_display_lookup_color(terminal->display, argv[0], color); 532 533 /* Done */ 534 return 1; 535 536} 537 538/** 539 * Parses an xterm SGR sequence specifying the index of a color within the 540 * 256-color palette, or specfying the RGB values of a color. The number of 541 * arguments required by these sequences varies. If a 256-color sequence is 542 * recognized, the number of arguments parsed is returned. 543 * 544 * @param terminal 545 * The terminal whose palette state should be used when parsing the xterm 546 * 256-color SGR sequence. 547 * 548 * @param argc 549 * The number of arguments within the argv array. 550 * 551 * @param argv 552 * The SGR arguments to parse, with the first relevant argument being 553 * the first element of the array. In the case of an xterm 256-color 554 * SGR sequence, the first element here will be either 2, for an 555 * RGB color, or 5, for a 256-color palette index. All other values 556 * are invalid and will not be parsed. 557 * 558 * @param color 559 * The guac_terminal_color structure which should receive the parsed 560 * color values. 561 * 562 * @return 563 * The number of arguments parsed, or zero if argv does not point to 564 * the first element of an xterm 256-color SGR sequence. 565 */ 566static int guac_terminal_parse_xterm256(guac_terminal* terminal, 567 int argc, const int* argv, guac_terminal_color* color) { 568 569 /* All 256-color codes must have at least a type */ 570 if (argc < 1) 571 return 0; 572 573 switch (argv[0]) { 574 575 /* RGB */ 576 case 2: 577 return guac_terminal_parse_xterm256_rgb( 578 argc - 1, &argv[1], color) + 1; 579 580 /* Palette index */ 581 case 5: 582 return guac_terminal_parse_xterm256_index(terminal, 583 argc - 1, &argv[1], color) + 1; 584 585 } 586 587 /* Invalid type */ 588 return 0; 589 590} 591 592int guac_terminal_csi(guac_terminal* term, unsigned char c) { 593 594 /* CSI function arguments */ 595 static int argc = 0; 596 static int argv[16] = {0}; 597 598 /* Sequence prefix, if any */ 599 static char private_mode_character = 0; 600 601 /* Argument building counter and buffer */ 602 static int argv_length = 0; 603 static char argv_buffer[256]; 604 605 /* Digits get concatenated into argv */ 606 if (c >= '0' && c <= '9') { 607 608 /* Concatenate digit if there is space in buffer */ 609 if (argv_length < sizeof(argv_buffer)-1) 610 argv_buffer[argv_length++] = c; 611 612 } 613 614 /* Specific non-digits stop the parameter, and possibly the sequence */ 615 else if ((c >= 0x40 && c <= 0x7E) || c == ';') { 616 617 int i, row, col, amount; 618 bool* flag; 619 620 /* At most 16 parameters */ 621 if (argc < 16) { 622 623 /* Finish parameter */ 624 argv_buffer[argv_length] = 0; 625 argv[argc++] = atoi(argv_buffer); 626 627 /* Prepare for next parameter */ 628 argv_length = 0; 629 630 } 631 632 /* Handle CSI functions */ 633 switch (c) { 634 635 /* @: Insert characters (scroll right) */ 636 case '@': 637 638 amount = argv[0]; 639 if (amount == 0) amount = 1; 640 641 /* Scroll right by amount */ 642 if (term->cursor_col + amount < term->term_width) 643 guac_terminal_copy_columns(term, term->cursor_row, 644 term->cursor_col, term->term_width - amount - 1, 645 amount); 646 647 /* Clear left */ 648 guac_terminal_clear_columns(term, term->cursor_row, 649 term->cursor_col, term->cursor_col + amount - 1); 650 651 break; 652 653 /* A: Move up */ 654 case 'A': 655 656 /* Get move amount */ 657 amount = argv[0]; 658 if (amount == 0) amount = 1; 659 660 /* Move cursor */ 661 guac_terminal_move_cursor(term, 662 term->cursor_row - amount, 663 term->cursor_col); 664 665 break; 666 667 /* B: Move down */ 668 case 'e': 669 case 'B': 670 671 /* Get move amount */ 672 amount = argv[0]; 673 if (amount == 0) amount = 1; 674 675 /* Move cursor */ 676 guac_terminal_move_cursor(term, 677 term->cursor_row + amount, 678 term->cursor_col); 679 680 break; 681 682 /* C: Move right */ 683 case 'a': 684 case 'C': 685 686 /* Get move amount */ 687 amount = argv[0]; 688 if (amount == 0) amount = 1; 689 690 /* Move cursor */ 691 guac_terminal_move_cursor(term, 692 term->cursor_row, 693 term->cursor_col + amount); 694 695 break; 696 697 /* D: Move left */ 698 case 'D': 699 700 /* Get move amount */ 701 amount = argv[0]; 702 if (amount == 0) amount = 1; 703 704 /* Move cursor */ 705 guac_terminal_move_cursor(term, 706 term->cursor_row, 707 term->cursor_col - amount); 708 709 break; 710 711 /* E: Move cursor down given number rows, column 1 */ 712 case 'E': 713 714 /* Get move amount */ 715 amount = argv[0]; 716 if (amount == 0) amount = 1; 717 718 /* Move cursor down, reset to column 1 */ 719 guac_terminal_move_cursor(term, 720 term->cursor_row + amount, 721 0); 722 723 break; 724 725 /* F: Move cursor up given number rows, column 1 */ 726 case 'F': 727 728 /* Get move amount */ 729 amount = argv[0]; 730 if (amount == 0) amount = 1; 731 732 /* Move cursor up , reset to column 1 */ 733 guac_terminal_move_cursor(term, 734 term->cursor_row - amount, 735 0); 736 737 break; 738 739 /* G: Move cursor, current row */ 740 case '`': 741 case 'G': 742 col = argv[0]; if (col != 0) col--; 743 guac_terminal_move_cursor(term, term->cursor_row, col); 744 break; 745 746 /* H: Move cursor */ 747 case 'f': 748 case 'H': 749 750 row = argv[0]; if (row != 0) row--; 751 col = argv[1]; if (col != 0) col--; 752 753 guac_terminal_move_cursor(term, row, col); 754 break; 755 756 /* J: Erase display */ 757 case 'J': 758 759 /* Erase from cursor to end of display */ 760 if (argv[0] == 0) 761 guac_terminal_clear_range(term, 762 term->cursor_row, term->cursor_col, 763 term->term_height-1, term->term_width-1); 764 765 /* Erase from start to cursor */ 766 else if (argv[0] == 1) 767 guac_terminal_clear_range(term, 768 0, 0, 769 term->cursor_row, term->cursor_col); 770 771 /* Entire screen */ 772 else if (argv[0] == 2 || argv[0] == 3) 773 guac_terminal_clear_range(term, 774 0, 0, term->term_height - 1, term->term_width - 1); 775 776 break; 777 778 /* K: Erase line */ 779 case 'K': 780 781 /* Erase from cursor to end of line */ 782 if (argv[0] == 0) 783 guac_terminal_clear_columns(term, term->cursor_row, 784 term->cursor_col, term->term_width - 1); 785 786 /* Erase from start to cursor */ 787 else if (argv[0] == 1) 788 guac_terminal_clear_columns(term, term->cursor_row, 789 0, term->cursor_col); 790 791 /* Erase line */ 792 else if (argv[0] == 2) 793 guac_terminal_clear_columns(term, term->cursor_row, 794 0, term->term_width - 1); 795 796 break; 797 798 /* L: Insert blank lines (scroll down) */ 799 case 'L': 800 801 amount = argv[0]; 802 if (amount == 0) amount = 1; 803 804 guac_terminal_scroll_down(term, 805 term->cursor_row, term->scroll_end, amount); 806 807 break; 808 809 /* M: Delete lines (scroll up) */ 810 case 'M': 811 812 amount = argv[0]; 813 if (amount == 0) amount = 1; 814 815 guac_terminal_scroll_up(term, 816 term->cursor_row, term->scroll_end, amount); 817 818 break; 819 820 /* P: Delete characters (scroll left) */ 821 case 'P': 822 823 amount = argv[0]; 824 if (amount == 0) amount = 1; 825 826 /* Scroll left by amount */ 827 if (term->cursor_col + amount < term->term_width) 828 guac_terminal_copy_columns(term, term->cursor_row, 829 term->cursor_col + amount, term->term_width - 1, 830 -amount); 831 832 /* Clear right */ 833 guac_terminal_clear_columns(term, term->cursor_row, 834 term->term_width - amount, term->term_width - 1); 835 836 break; 837 838 /* X: Erase characters (no scroll) */ 839 case 'X': 840 841 amount = argv[0]; 842 if (amount == 0) amount = 1; 843 844 /* Clear characters */ 845 guac_terminal_clear_columns(term, term->cursor_row, 846 term->cursor_col, term->cursor_col + amount - 1); 847 848 break; 849 850 /* ]: Linux Private CSI */ 851 case ']': 852 /* Explicitly ignored */ 853 break; 854 855 /* c: Identify */ 856 case 'c': 857 if (argv[0] == 0 && private_mode_character == 0) 858 guac_terminal_send_string(term, GUAC_TERMINAL_VT102_ID); 859 break; 860 861 /* d: Move cursor, current col */ 862 case 'd': 863 row = argv[0]; if (row != 0) row--; 864 guac_terminal_move_cursor(term, row, term->cursor_col); 865 break; 866 867 /* g: Clear tab */ 868 case 'g': 869 870 /* Clear tab at current location */ 871 if (argv[0] == 0) 872 guac_terminal_unset_tab(term, term->cursor_col); 873 874 /* Clear all tabs */ 875 else if (argv[0] == 3) 876 guac_terminal_clear_tabs(term); 877 878 break; 879 880 /* h: Set Mode */ 881 case 'h': 882 883 /* Look up flag and set */ 884 flag = __guac_terminal_get_flag(term, argv[0], private_mode_character); 885 if (flag != NULL) 886 *flag = true; 887 888 break; 889 890 /* l: Reset Mode */ 891 case 'l': 892 893 /* Look up flag and clear */ 894 flag = __guac_terminal_get_flag(term, argv[0], private_mode_character); 895 if (flag != NULL) 896 *flag = false; 897 898 break; 899 900 /* m: Set graphics rendition */ 901 case 'm': 902 903 for (i=0; i<argc; i++) { 904 905 int value = argv[i]; 906 907 /* Reset attributes */ 908 if (value == 0) 909 term->current_attributes = term->default_char.attributes; 910 911 /* Bold */ 912 else if (value == 1) 913 term->current_attributes.bold = true; 914 915 /* Faint (low intensity) */ 916 else if (value == 2) 917 term->current_attributes.half_bright = true; 918 919 /* Underscore on */ 920 else if (value == 4) 921 term->current_attributes.underscore = true; 922 923 /* Reverse video */ 924 else if (value == 7) 925 term->current_attributes.reverse = true; 926 927 /* Normal intensity (not bold) */ 928 else if (value == 21 || value == 22) { 929 term->current_attributes.bold = false; 930 term->current_attributes.half_bright = false; 931 } 932 933 /* Reset underscore */ 934 else if (value == 24) 935 term->current_attributes.underscore = false; 936 937 /* Reset reverse video */ 938 else if (value == 27) 939 term->current_attributes.reverse = false; 940 941 /* Foreground */ 942 else if (value >= 30 && value <= 37) 943 guac_terminal_display_lookup_color(term->display, 944 value - 30, 945 &term->current_attributes.foreground); 946 947 /* Underscore on, default foreground OR 256-color 948 * foreground */ 949 else if (value == 38) { 950 951 /* Attempt to set foreground with 256-color entry */ 952 int xterm256_length = 953 guac_terminal_parse_xterm256(term, 954 argc - i - 1, &argv[i + 1], 955 &term->current_attributes.foreground); 956 957 /* If valid 256-color entry, foreground has been set */ 958 if (xterm256_length > 0) 959 i += xterm256_length; 960 961 /* Otherwise interpret as underscore and default 962 * foreground */ 963 else { 964 term->current_attributes.underscore = true; 965 term->current_attributes.foreground = 966 term->default_char.attributes.foreground; 967 } 968 969 } 970 971 /* Underscore off, default foreground */ 972 else if (value == 39) { 973 term->current_attributes.underscore = false; 974 term->current_attributes.foreground = 975 term->default_char.attributes.foreground; 976 } 977 978 /* Background */ 979 else if (value >= 40 && value <= 47) 980 guac_terminal_display_lookup_color(term->display, 981 value - 40, 982 &term->current_attributes.background); 983 984 /* 256-color background */ 985 else if (value == 48) 986 i += guac_terminal_parse_xterm256(term, 987 argc - i - 1, &argv[i + 1], 988 &term->current_attributes.background); 989 990 /* Reset background */ 991 else if (value == 49) 992 term->current_attributes.background = 993 term->default_char.attributes.background; 994 995 /* Intense foreground */ 996 else if (value >= 90 && value <= 97) 997 guac_terminal_display_lookup_color(term->display, 998 value - 90 + GUAC_TERMINAL_FIRST_INTENSE, 999 &term->current_attributes.foreground); 1000 1001 /* Intense background */ 1002 else if (value >= 100 && value <= 107) 1003 guac_terminal_display_lookup_color(term->display, 1004 value - 100 + GUAC_TERMINAL_FIRST_INTENSE, 1005 &term->current_attributes.background); 1006 1007 } 1008 1009 break; 1010 1011 /* n: Status report */ 1012 case 'n': 1013 1014 /* Device status report */ 1015 if (argv[0] == 5 && private_mode_character == 0) 1016 guac_terminal_send_string(term, GUAC_TERMINAL_OK); 1017 1018 /* Cursor position report */ 1019 else if (argv[0] == 6 && private_mode_character == 0) 1020 guac_terminal_sendf(term, "\x1B[%i;%iR", term->cursor_row+1, term->cursor_col+1); 1021 1022 break; 1023 1024 /* q: Set keyboard LEDs */ 1025 case 'q': 1026 /* Explicitly ignored */ 1027 break; 1028 1029 /* r: Set scrolling region */ 1030 case 'r': 1031 1032 /* If parameters given, set region */ 1033 if (argc == 2) { 1034 term->scroll_start = argv[0]-1; 1035 term->scroll_end = argv[1]-1; 1036 } 1037 1038 /* Otherwise, reset scrolling region */ 1039 else { 1040 term->scroll_start = 0; 1041 term->scroll_end = term->term_height - 1; 1042 } 1043 1044 break; 1045 1046 /* Save Cursor */ 1047 case 's': 1048 term->saved_cursor_row = term->cursor_row; 1049 term->saved_cursor_col = term->cursor_col; 1050 break; 1051 1052 /* Restore Cursor */ 1053 case 'u': 1054 guac_terminal_move_cursor(term, 1055 term->saved_cursor_row, 1056 term->saved_cursor_col); 1057 break; 1058 1059 /* Warn of unhandled codes */ 1060 default: 1061 if (c != ';') { 1062 1063 guac_client_log(term->client, GUAC_LOG_DEBUG, 1064 "Unhandled CSI sequence: %c", c); 1065 1066 for (i=0; i<argc; i++) 1067 guac_client_log(term->client, GUAC_LOG_DEBUG, 1068 " -> argv[%i] = %i", i, argv[i]); 1069 1070 } 1071 1072 } 1073 1074 /* If not a semicolon, end of CSI sequence */ 1075 if (c != ';') { 1076 term->char_handler = guac_terminal_echo; 1077 1078 /* Reset parameters */ 1079 for (i=0; i<argc; i++) 1080 argv[i] = 0; 1081 1082 /* Reset private mode character */ 1083 private_mode_character = 0; 1084 1085 /* Reset argument counters */ 1086 argc = 0; 1087 argv_length = 0; 1088 } 1089 1090 } 1091 1092 /* Set private mode character if given and unset */ 1093 else if (c >= 0x3A && c <= 0x3F && private_mode_character == 0) 1094 private_mode_character = c; 1095 1096 return 0; 1097 1098} 1099 1100int guac_terminal_set_directory(guac_terminal* term, unsigned char c) { 1101 1102 static char filename[2048]; 1103 static int length = 0; 1104 1105 /* Stop on ECMA-48 ST (String Terminator */ 1106 if (c == 0x9C || c == 0x5C || c == 0x07) { 1107 filename[length++] = '\0'; 1108 term->char_handler = guac_terminal_echo; 1109 if (term->upload_path_handler) 1110 term->upload_path_handler(term->client, filename); 1111 else 1112 guac_client_log(term->client, GUAC_LOG_DEBUG, 1113 "Cannot set upload path. File transfer is not enabled."); 1114 length = 0; 1115 } 1116 1117 /* Otherwise, store character */ 1118 else if (length < sizeof(filename)-1) 1119 filename[length++] = c; 1120 1121 return 0; 1122 1123} 1124 1125int guac_terminal_download(guac_terminal* term, unsigned char c) { 1126 1127 static char filename[2048]; 1128 static int length = 0; 1129 1130 /* Stop on ECMA-48 ST (String Terminator */ 1131 if (c == 0x9C || c == 0x5C || c == 0x07) { 1132 filename[length++] = '\0'; 1133 term->char_handler = guac_terminal_echo; 1134 if (term->file_download_handler) 1135 term->file_download_handler(term->client, filename); 1136 else 1137 guac_client_log(term->client, GUAC_LOG_DEBUG, 1138 "Cannot send file. File transfer is not enabled."); 1139 length = 0; 1140 } 1141 1142 /* Otherwise, store character */ 1143 else if (length < sizeof(filename)-1) 1144 filename[length++] = c; 1145 1146 return 0; 1147 1148} 1149 1150int guac_terminal_open_pipe_stream(guac_terminal* term, unsigned char c) { 1151 1152 static char param[2048]; 1153 static int length = 0; 1154 static int flags = 0; 1155 1156 /* Open pipe on ECMA-48 ST (String Terminator) */ 1157 if (c == 0x9C || c == 0x5C || c == 0x07) { 1158 1159 /* End parameter string */ 1160 param[length++] = '\0'; 1161 length = 0; 1162 1163 /* Open new pipe stream using final parameter as name */ 1164 guac_terminal_pipe_stream_open(term, param, flags); 1165 1166 /* Return to echo mode */ 1167 term->char_handler = guac_terminal_echo; 1168 1169 /* Reset tracked flags for sake of any future pipe streams */ 1170 flags = 0; 1171 1172 } 1173 1174 /* Interpret all parameters prior to the final parameter as integer 1175 * flags which should affect the pipe stream when opened */ 1176 else if (c == ';') { 1177 1178 /* End parameter string */ 1179 param[length++] = '\0'; 1180 length = 0; 1181 1182 /* Parse parameter string as integer flags */ 1183 flags |= atoi(param); 1184 1185 } 1186 1187 /* Otherwise, store character within parameter */ 1188 else if (length < sizeof(param)-1) 1189 param[length++] = c; 1190 1191 return 0; 1192 1193} 1194 1195int guac_terminal_close_pipe_stream(guac_terminal* term, unsigned char c) { 1196 1197 /* Handle closure on ECMA-48 ST (String Terminator) */ 1198 if (c == 0x9C || c == 0x5C || c == 0x07) { 1199 1200 /* Close any existing pipe */ 1201 guac_terminal_pipe_stream_close(term); 1202 1203 /* Return to echo mode */ 1204 term->char_handler = guac_terminal_echo; 1205 1206 } 1207 1208 /* Ignore all other characters */ 1209 return 0; 1210 1211} 1212 1213int guac_terminal_set_scrollback(guac_terminal* term, unsigned char c) { 1214 1215 static char param[16]; 1216 static int length = 0; 1217 1218 /* Open pipe on ECMA-48 ST (String Terminator) */ 1219 if (c == 0x9C || c == 0x5C || c == 0x07) { 1220 1221 /* End parameter string */ 1222 param[length++] = '\0'; 1223 length = 0; 1224 1225 /* Assign scrollback size */ 1226 term->requested_scrollback = atoi(param); 1227 1228 /* Update scrollbar bounds */ 1229 guac_terminal_scrollbar_set_bounds(term->scrollbar, 1230 -guac_terminal_get_available_scroll(term), 0); 1231 1232 /* Return to echo mode */ 1233 term->char_handler = guac_terminal_echo; 1234 1235 } 1236 1237 /* Otherwise, store character within parameter */ 1238 else if (length < sizeof(param) - 1) 1239 param[length++] = c; 1240 1241 return 0; 1242 1243} 1244 1245 1246int guac_terminal_window_title(guac_terminal* term, unsigned char c) { 1247 1248 static int position = 0; 1249 static char title[4096]; 1250 1251 guac_socket* socket = term->client->socket; 1252 1253 /* Stop on ECMA-48 ST (String Terminator */ 1254 if (c == 0x9C || c == 0x5C || c == 0x07) { 1255 1256 /* Terminate and reset stored title */ 1257 title[position] = '\0'; 1258 position = 0; 1259 1260 /* Send title as connection name */ 1261 guac_protocol_send_name(socket, title); 1262 guac_socket_flush(socket); 1263 1264 term->char_handler = guac_terminal_echo; 1265 1266 } 1267 1268 /* Store all other characters within title, space permitting */ 1269 else if (position < sizeof(title) - 1) 1270 title[position++] = (char) c; 1271 1272 return 0; 1273 1274} 1275 1276int guac_terminal_xterm_palette(guac_terminal* term, unsigned char c) { 1277 1278 /** 1279 * Whether we are currently reading the color spec. If false, we are 1280 * currently reading the color index. 1281 */ 1282 static bool read_color_spec = false; 1283 1284 /** 1285 * The index of the palette entry being modified. 1286 */ 1287 static int index = 0; 1288 1289 /** 1290 * The color spec string, valid only if read_color_spec is true. 1291 */ 1292 static char color_spec[256]; 1293 1294 /** 1295 * The current position within the color spec string, valid only if 1296 * read_color_spec is true. 1297 */ 1298 static int color_spec_pos = 0; 1299 1300 /* If not reading the color spec, parse the index */ 1301 if (!read_color_spec) { 1302 1303 /* If digit, append to index */ 1304 if (c >= '0' && c <= '9') 1305 index = index * 10 + c - '0'; 1306 1307 /* If end of parameter, switch to reading the color */ 1308 else if (c == ';') { 1309 read_color_spec = true; 1310 color_spec_pos = 0; 1311 } 1312 1313 } 1314 1315 /* Once the index has been parsed, read the color spec */ 1316 else { 1317 1318 /* Modify palette once index/spec pair has been read */ 1319 if (c == ';' || c == 0x9C || c == 0x5C || c == 0x07) { 1320 1321 guac_terminal_color color; 1322 1323 /* Terminate color spec string */ 1324 color_spec[color_spec_pos] = '\0'; 1325 1326 /* Modify palette if color spec is valid */ 1327 if (!guac_terminal_xparsecolor(color_spec, &color)) 1328 guac_terminal_display_assign_color(term->display, 1329 index, &color); 1330 else 1331 guac_client_log(term->client, GUAC_LOG_DEBUG, 1332 "Invalid XParseColor() color spec: \"%s\"", 1333 color_spec); 1334 1335 /* Resume parsing index */ 1336 read_color_spec = false; 1337 index = 0; 1338 1339 } 1340 1341 /* Append characters to color spec as long as available space is not 1342 * exceeded */ 1343 else if (color_spec_pos < 255) { 1344 color_spec[color_spec_pos++] = c; 1345 } 1346 1347 } 1348 1349 /* Stop on ECMA-48 ST (String Terminator */ 1350 if (c == 0x9C || c == 0x5C || c == 0x07) 1351 term->char_handler = guac_terminal_echo; 1352 1353 return 0; 1354 1355} 1356 1357int guac_terminal_osc(guac_terminal* term, unsigned char c) { 1358 1359 static int operation = 0; 1360 1361 /* If digit, append to operation */ 1362 if (c >= '0' && c <= '9') 1363 operation = operation * 10 + c - '0'; 1364 1365 /* If end of parameter, check value */ 1366 else if (c == ';') { 1367 1368 /* Download OSC */ 1369 if (operation == 482200) 1370 term->char_handler = guac_terminal_download; 1371 1372 /* Set upload directory OSC */ 1373 else if (operation == 482201) 1374 term->char_handler = guac_terminal_set_directory; 1375 1376 /* Open and redirect output to pipe stream OSC */ 1377 else if (operation == 482202) 1378 term->char_handler = guac_terminal_open_pipe_stream; 1379 1380 /* Close pipe stream OSC */ 1381 else if (operation == 482203) 1382 term->char_handler = guac_terminal_close_pipe_stream; 1383 1384 /* Set scrollback size OSC */ 1385 else if (operation == 482204) 1386 term->char_handler = guac_terminal_set_scrollback; 1387 1388 /* Set window title OSC */ 1389 else if (operation == 0 || operation == 2) 1390 term->char_handler = guac_terminal_window_title; 1391 1392 /* xterm 256-color palette redefinition */ 1393 else if (operation == 4) 1394 term->char_handler = guac_terminal_xterm_palette; 1395 1396 /* Reset parameter for next OSC */ 1397 operation = 0; 1398 1399 } 1400 1401 /* Stop on ECMA-48 ST (String Terminator */ 1402 else if (c == 0x9C || c == 0x5C || c == 0x07) 1403 term->char_handler = guac_terminal_echo; 1404 1405 /* Stop on unrecognized character */ 1406 else { 1407 guac_client_log(term->client, GUAC_LOG_DEBUG, 1408 "Unexpected character in OSC: 0x%X", c); 1409 term->char_handler = guac_terminal_echo; 1410 } 1411 1412 return 0; 1413} 1414 1415int guac_terminal_ctrl_func(guac_terminal* term, unsigned char c) { 1416 1417 int row; 1418 1419 /* Build character with current attributes */ 1420 guac_terminal_char guac_char; 1421 guac_char.value = 'E'; 1422 guac_char.attributes = term->current_attributes; 1423 1424 switch (c) { 1425 1426 /* Alignment test (fill screen with E's) */ 1427 case '8': 1428 1429 for (row=0; row<term->term_height; row++) 1430 guac_terminal_set_columns(term, row, 0, term->term_width-1, &guac_char); 1431 1432 break; 1433 1434 } 1435 1436 term->char_handler = guac_terminal_echo; 1437 1438 return 0; 1439 1440} 1441 1442int guac_terminal_apc(guac_terminal* term, unsigned char c) { 1443 1444 /* xterm does not implement APC functions and neither do we. Look for the 1445 * "ESC \" (string terminator) sequence, while ignoring other chars. */ 1446 static bool escaping = false; 1447 1448 if (escaping) { 1449 if (c == '\\') 1450 term->char_handler = guac_terminal_echo; 1451 escaping = false; 1452 } 1453 1454 if (c == 0x1B) 1455 escaping = true; 1456 return 0; 1457}