/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include "config.h" #include "terminal/char_mappings.h" #include "terminal/palette.h" #include "terminal/terminal.h" #include "terminal/terminal_handlers.h" #include "terminal/types.h" #include "terminal/xparsecolor.h" #include #include #include #include #include #include /** * Response string sent when identification is requested. */ #define GUAC_TERMINAL_VT102_ID "\x1B[?6c" /** * Arbitrary response to ENQ control character. */ #define GUAC_TERMINAL_ANSWERBACK "GUACAMOLE" /** * Response which indicates the terminal is alive. */ #define GUAC_TERMINAL_OK "\x1B[0n" /** * Advances the cursor to the next row, scrolling if the cursor would otherwise * leave the scrolling region. If the cursor is already outside the scrolling * region, the cursor is prevented from leaving the terminal bounds. * * @param term * The guac_terminal whose cursor should be advanced to the next row. */ static void guac_terminal_linefeed(guac_terminal* term) { /* Scroll up if necessary */ if (term->cursor_row == term->scroll_end) guac_terminal_scroll_up(term, term->scroll_start, term->scroll_end, 1); /* Otherwise, just advance to next row if space remains */ else if (term->cursor_row < term->term_height - 1) term->cursor_row++; } /** * Moves the cursor backward to the previous row, scrolling if the cursor would * otherwise leave the scrolling region. If the cursor is already outside the * scrolling region, the cursor is prevented from leaving the terminal bounds. * * @param term * The guac_terminal whose cursor should be moved backward by one row. */ static void guac_terminal_reverse_linefeed(guac_terminal* term) { /* Scroll down if necessary */ if (term->cursor_row == term->scroll_start) guac_terminal_scroll_down(term, term->scroll_start, term->scroll_end, 1); /* Otherwise, move back one row if space remains */ else if (term->cursor_row > 0) term->cursor_row--; } /** * Sets the position of the cursor without exceeding terminal bounds. Values * which are out of bounds will be shifted to the nearest legal boundary. * * @param term * The guac_terminal whose cursor position is being set. * * @param row * The desired new row position. * * @param col * The desired new column position. */ static void guac_terminal_move_cursor(guac_terminal* term, int row, int col) { /* Ensure cursor row is within terminal bounds */ if (row >= term->term_height) row = term->term_height - 1; else if (row < 0) row = 0; /* Ensure cursor column is within terminal bounds */ if (col >= term->term_width) col = term->term_width - 1; else if (col < 0) col = 0; /* Update cursor position */ term->cursor_row = row; term->cursor_col = col; } int guac_terminal_echo(guac_terminal* term, unsigned char c) { int width; static int bytes_remaining = 0; static int codepoint = 0; const int* char_mapping = term->char_mapping[term->active_char_set]; /* Echo to pipe stream if open and not starting an ESC sequence */ if (term->pipe_stream != NULL && c != 0x1B) { guac_terminal_pipe_stream_write(term, c); /* Do not render output while pipe is open unless explicitly requested * via flags */ if (!(term->pipe_stream_flags & GUAC_TERMINAL_PIPE_INTERPRET_OUTPUT)) return 0; } /* If using non-Unicode mapping, just map straight bytes */ if (char_mapping != NULL) { codepoint = c; bytes_remaining = 0; } /* 1-byte UTF-8 codepoint */ else if ((c & 0x80) == 0x00) { /* 0xxxxxxx */ codepoint = c & 0x7F; bytes_remaining = 0; } /* 2-byte UTF-8 codepoint */ else if ((c & 0xE0) == 0xC0) { /* 110xxxxx */ codepoint = c & 0x1F; bytes_remaining = 1; } /* 3-byte UTF-8 codepoint */ else if ((c & 0xF0) == 0xE0) { /* 1110xxxx */ codepoint = c & 0x0F; bytes_remaining = 2; } /* 4-byte UTF-8 codepoint */ else if ((c & 0xF8) == 0xF0) { /* 11110xxx */ codepoint = c & 0x07; bytes_remaining = 3; } /* Continuation of UTF-8 codepoint */ else if ((c & 0xC0) == 0x80) { /* 10xxxxxx */ codepoint = (codepoint << 6) | (c & 0x3F); bytes_remaining--; } /* Unrecognized prefix */ else { codepoint = '?'; bytes_remaining = 0; } /* If we need more bytes, wait for more bytes */ if (bytes_remaining != 0) return 0; switch (codepoint) { /* Enquiry */ case 0x05: guac_terminal_send_string(term, GUAC_TERMINAL_ANSWERBACK); break; /* Bell */ case 0x07: break; /* Backspace */ case 0x08: guac_terminal_move_cursor(term, term->cursor_row, term->cursor_col - 1); break; /* Tab */ case 0x09: guac_terminal_move_cursor(term, term->cursor_row, guac_terminal_next_tab(term, term->cursor_col)); break; /* Line feed / VT / FF */ case '\n': case 0x0B: /* VT */ case 0x0C: /* FF */ /* Advance to next row */ guac_terminal_linefeed(term); /* If automatic carriage return, fall through to CR handler */ if (!term->automatic_carriage_return) break; /* Carriage return */ case '\r': guac_terminal_move_cursor(term, term->cursor_row, 0); break; /* SO (activates character set G1) */ case 0x0E: term->active_char_set = 1; break; /* SI (activates character set G0) */ case 0x0F: term->active_char_set = 0; break; /* ESC */ case 0x1B: term->char_handler = guac_terminal_escape; break; /* CSI */ case 0x9B: term->char_handler = guac_terminal_csi; break; /* DEL (ignored) */ case 0x7F: break; /* Displayable chars */ default: /* Don't bother handling control chars if unknown */ if (codepoint < 0x20) break; /* Translate mappable codepoints to whatever codepoint is mapped */ if (codepoint >= 0x20 && codepoint <= 0xFF && char_mapping != NULL) codepoint = char_mapping[codepoint - 0x20]; /* Wrap if necessary */ if (term->cursor_col >= term->term_width) { term->cursor_col = 0; guac_terminal_linefeed(term); } /* If insert mode, shift other characters right by 1 */ if (term->insert_mode) guac_terminal_copy_columns(term, term->cursor_row, term->cursor_col, term->term_width-2, 1); /* Write character */ guac_terminal_set(term, term->cursor_row, term->cursor_col, codepoint); width = wcwidth(codepoint); if (width < 0) width = 1; /* Advance cursor */ term->cursor_col += width; } return 0; } int guac_terminal_escape(guac_terminal* term, unsigned char c) { switch (c) { case '(': term->char_handler = guac_terminal_g0_charset; break; case ')': term->char_handler = guac_terminal_g1_charset; break; case ']': term->char_handler = guac_terminal_osc; break; case '[': term->char_handler = guac_terminal_csi; break; case '#': term->char_handler = guac_terminal_ctrl_func; break; /* Save Cursor (DECSC) */ case '7': term->saved_cursor_row = term->cursor_row; term->saved_cursor_col = term->cursor_col; term->char_handler = guac_terminal_echo; break; /* Restore Cursor (DECRC) */ case '8': guac_terminal_move_cursor(term, term->saved_cursor_row, term->saved_cursor_col); term->char_handler = guac_terminal_echo; break; /* Index (IND) */ case 'D': guac_terminal_linefeed(term); term->char_handler = guac_terminal_echo; break; /* Next Line (NEL) */ case 'E': guac_terminal_move_cursor(term, term->cursor_row, 0); guac_terminal_linefeed(term); term->char_handler = guac_terminal_echo; break; /* Set Tab (HTS) */ case 'H': guac_terminal_set_tab(term, term->cursor_col); term->char_handler = guac_terminal_echo; break; /* Reverse Linefeed */ case 'M': guac_terminal_reverse_linefeed(term); term->char_handler = guac_terminal_echo; break; /* DEC Identify */ case 'Z': guac_terminal_send_string(term, GUAC_TERMINAL_VT102_ID); term->char_handler = guac_terminal_echo; break; /* Reset */ case 'c': guac_terminal_reset(term); break; case '_': term->char_handler = guac_terminal_apc; break; default: guac_client_log(term->client, GUAC_LOG_DEBUG, "Unhandled ESC sequence: %c", c); term->char_handler = guac_terminal_echo; } return 0; } /** * Given a character mapping specifier (such as B, 0, U, or K), * returns the corresponding character mapping. */ static const int* __guac_terminal_get_char_mapping(char c) { /* Translate character specifier to actual mapping */ switch (c) { case 'B': return NULL; case '0': return vt100_map; case 'U': return null_map; case 'K': return user_map; } /* Default to Unicode */ return NULL; } int guac_terminal_g0_charset(guac_terminal* term, unsigned char c) { term->char_mapping[0] = __guac_terminal_get_char_mapping(c); term->char_handler = guac_terminal_echo; return 0; } int guac_terminal_g1_charset(guac_terminal* term, unsigned char c) { term->char_mapping[1] = __guac_terminal_get_char_mapping(c); term->char_handler = guac_terminal_echo; return 0; } /** * Looks up the flag specified by the given number and mode. Used by the Set/Reset Mode * functions of the terminal. */ static bool* __guac_terminal_get_flag(guac_terminal* term, int num, char private_mode) { if (private_mode == '?') { switch (num) { case 1: return &(term->application_cursor_keys); /* DECCKM */ case 25: return &(term->cursor_visible); /* DECTECM */ } } else if (private_mode == 0) { switch (num) { case 4: return &(term->insert_mode); /* DECIM */ case 20: return &(term->automatic_carriage_return); /* LF/NL */ } } /* Unknown flag */ return NULL; } /** * Parses an xterm SGR sequence specifying the RGB values of a color. * * @param argc * The number of arguments within the argv array. * * @param argv * The SGR arguments to parse, with the first relevant argument the * red component of the RGB color. * * @param color * The guac_terminal_color structure which should receive the parsed * color values. * * @return * The number of arguments parsed, or zero if argv does not contain * enough elements to represent an RGB color. */ static int guac_terminal_parse_xterm256_rgb(int argc, const int* argv, guac_terminal_color* color) { /* RGB color palette entries require three arguments */ if (argc < 3) return 0; /* Read RGB components from arguments */ int red = argv[0]; int green = argv[1]; int blue = argv[2]; /* Ignore if components are out of range */ if ( red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) return 3; /* Store RGB components */ color->red = (uint8_t) red; color->green = (uint8_t) green; color->blue = (uint8_t) blue; /* Color is not from the palette */ color->palette_index = -1; /* Done */ return 3; } /** * Parses an xterm SGR sequence specifying the index of a color within the * 256-color palette. * * @param terminal * The terminal associated with the palette. * * @param argc * The number of arguments within the argv array. * * @param argv * The SGR arguments to parse, with the first relevant argument being * the index of the color. * * @param color * The guac_terminal_color structure which should receive the parsed * color values. * * @return * The number of arguments parsed, or zero if the palette index is * absent. */ static int guac_terminal_parse_xterm256_index(guac_terminal* terminal, int argc, const int* argv, guac_terminal_color* color) { /* 256-color palette entries require only one argument */ if (argc < 1) return 0; /* Copy palette entry */ guac_terminal_display_lookup_color(terminal->display, argv[0], color); /* Done */ return 1; } /** * Parses an xterm SGR sequence specifying the index of a color within the * 256-color palette, or specfying the RGB values of a color. The number of * arguments required by these sequences varies. If a 256-color sequence is * recognized, the number of arguments parsed is returned. * * @param terminal * The terminal whose palette state should be used when parsing the xterm * 256-color SGR sequence. * * @param argc * The number of arguments within the argv array. * * @param argv * The SGR arguments to parse, with the first relevant argument being * the first element of the array. In the case of an xterm 256-color * SGR sequence, the first element here will be either 2, for an * RGB color, or 5, for a 256-color palette index. All other values * are invalid and will not be parsed. * * @param color * The guac_terminal_color structure which should receive the parsed * color values. * * @return * The number of arguments parsed, or zero if argv does not point to * the first element of an xterm 256-color SGR sequence. */ static int guac_terminal_parse_xterm256(guac_terminal* terminal, int argc, const int* argv, guac_terminal_color* color) { /* All 256-color codes must have at least a type */ if (argc < 1) return 0; switch (argv[0]) { /* RGB */ case 2: return guac_terminal_parse_xterm256_rgb( argc - 1, &argv[1], color) + 1; /* Palette index */ case 5: return guac_terminal_parse_xterm256_index(terminal, argc - 1, &argv[1], color) + 1; } /* Invalid type */ return 0; } int guac_terminal_csi(guac_terminal* term, unsigned char c) { /* CSI function arguments */ static int argc = 0; static int argv[16] = {0}; /* Sequence prefix, if any */ static char private_mode_character = 0; /* Argument building counter and buffer */ static int argv_length = 0; static char argv_buffer[256]; /* Digits get concatenated into argv */ if (c >= '0' && c <= '9') { /* Concatenate digit if there is space in buffer */ if (argv_length < sizeof(argv_buffer)-1) argv_buffer[argv_length++] = c; } /* Specific non-digits stop the parameter, and possibly the sequence */ else if ((c >= 0x40 && c <= 0x7E) || c == ';') { int i, row, col, amount; bool* flag; /* At most 16 parameters */ if (argc < 16) { /* Finish parameter */ argv_buffer[argv_length] = 0; argv[argc++] = atoi(argv_buffer); /* Prepare for next parameter */ argv_length = 0; } /* Handle CSI functions */ switch (c) { /* @: Insert characters (scroll right) */ case '@': amount = argv[0]; if (amount == 0) amount = 1; /* Scroll right by amount */ if (term->cursor_col + amount < term->term_width) guac_terminal_copy_columns(term, term->cursor_row, term->cursor_col, term->term_width - amount - 1, amount); /* Clear left */ guac_terminal_clear_columns(term, term->cursor_row, term->cursor_col, term->cursor_col + amount - 1); break; /* A: Move up */ case 'A': /* Get move amount */ amount = argv[0]; if (amount == 0) amount = 1; /* Move cursor */ guac_terminal_move_cursor(term, term->cursor_row - amount, term->cursor_col); break; /* B: Move down */ case 'e': case 'B': /* Get move amount */ amount = argv[0]; if (amount == 0) amount = 1; /* Move cursor */ guac_terminal_move_cursor(term, term->cursor_row + amount, term->cursor_col); break; /* C: Move right */ case 'a': case 'C': /* Get move amount */ amount = argv[0]; if (amount == 0) amount = 1; /* Move cursor */ guac_terminal_move_cursor(term, term->cursor_row, term->cursor_col + amount); break; /* D: Move left */ case 'D': /* Get move amount */ amount = argv[0]; if (amount == 0) amount = 1; /* Move cursor */ guac_terminal_move_cursor(term, term->cursor_row, term->cursor_col - amount); break; /* E: Move cursor down given number rows, column 1 */ case 'E': /* Get move amount */ amount = argv[0]; if (amount == 0) amount = 1; /* Move cursor down, reset to column 1 */ guac_terminal_move_cursor(term, term->cursor_row + amount, 0); break; /* F: Move cursor up given number rows, column 1 */ case 'F': /* Get move amount */ amount = argv[0]; if (amount == 0) amount = 1; /* Move cursor up , reset to column 1 */ guac_terminal_move_cursor(term, term->cursor_row - amount, 0); break; /* G: Move cursor, current row */ case '`': case 'G': col = argv[0]; if (col != 0) col--; guac_terminal_move_cursor(term, term->cursor_row, col); break; /* H: Move cursor */ case 'f': case 'H': row = argv[0]; if (row != 0) row--; col = argv[1]; if (col != 0) col--; guac_terminal_move_cursor(term, row, col); break; /* J: Erase display */ case 'J': /* Erase from cursor to end of display */ if (argv[0] == 0) guac_terminal_clear_range(term, term->cursor_row, term->cursor_col, term->term_height-1, term->term_width-1); /* Erase from start to cursor */ else if (argv[0] == 1) guac_terminal_clear_range(term, 0, 0, term->cursor_row, term->cursor_col); /* Entire screen */ else if (argv[0] == 2 || argv[0] == 3) guac_terminal_clear_range(term, 0, 0, term->term_height - 1, term->term_width - 1); break; /* K: Erase line */ case 'K': /* Erase from cursor to end of line */ if (argv[0] == 0) guac_terminal_clear_columns(term, term->cursor_row, term->cursor_col, term->term_width - 1); /* Erase from start to cursor */ else if (argv[0] == 1) guac_terminal_clear_columns(term, term->cursor_row, 0, term->cursor_col); /* Erase line */ else if (argv[0] == 2) guac_terminal_clear_columns(term, term->cursor_row, 0, term->term_width - 1); break; /* L: Insert blank lines (scroll down) */ case 'L': amount = argv[0]; if (amount == 0) amount = 1; guac_terminal_scroll_down(term, term->cursor_row, term->scroll_end, amount); break; /* M: Delete lines (scroll up) */ case 'M': amount = argv[0]; if (amount == 0) amount = 1; guac_terminal_scroll_up(term, term->cursor_row, term->scroll_end, amount); break; /* P: Delete characters (scroll left) */ case 'P': amount = argv[0]; if (amount == 0) amount = 1; /* Scroll left by amount */ if (term->cursor_col + amount < term->term_width) guac_terminal_copy_columns(term, term->cursor_row, term->cursor_col + amount, term->term_width - 1, -amount); /* Clear right */ guac_terminal_clear_columns(term, term->cursor_row, term->term_width - amount, term->term_width - 1); break; /* X: Erase characters (no scroll) */ case 'X': amount = argv[0]; if (amount == 0) amount = 1; /* Clear characters */ guac_terminal_clear_columns(term, term->cursor_row, term->cursor_col, term->cursor_col + amount - 1); break; /* ]: Linux Private CSI */ case ']': /* Explicitly ignored */ break; /* c: Identify */ case 'c': if (argv[0] == 0 && private_mode_character == 0) guac_terminal_send_string(term, GUAC_TERMINAL_VT102_ID); break; /* d: Move cursor, current col */ case 'd': row = argv[0]; if (row != 0) row--; guac_terminal_move_cursor(term, row, term->cursor_col); break; /* g: Clear tab */ case 'g': /* Clear tab at current location */ if (argv[0] == 0) guac_terminal_unset_tab(term, term->cursor_col); /* Clear all tabs */ else if (argv[0] == 3) guac_terminal_clear_tabs(term); break; /* h: Set Mode */ case 'h': /* Look up flag and set */ flag = __guac_terminal_get_flag(term, argv[0], private_mode_character); if (flag != NULL) *flag = true; break; /* l: Reset Mode */ case 'l': /* Look up flag and clear */ flag = __guac_terminal_get_flag(term, argv[0], private_mode_character); if (flag != NULL) *flag = false; break; /* m: Set graphics rendition */ case 'm': for (i=0; icurrent_attributes = term->default_char.attributes; /* Bold */ else if (value == 1) term->current_attributes.bold = true; /* Faint (low intensity) */ else if (value == 2) term->current_attributes.half_bright = true; /* Underscore on */ else if (value == 4) term->current_attributes.underscore = true; /* Reverse video */ else if (value == 7) term->current_attributes.reverse = true; /* Normal intensity (not bold) */ else if (value == 21 || value == 22) { term->current_attributes.bold = false; term->current_attributes.half_bright = false; } /* Reset underscore */ else if (value == 24) term->current_attributes.underscore = false; /* Reset reverse video */ else if (value == 27) term->current_attributes.reverse = false; /* Foreground */ else if (value >= 30 && value <= 37) guac_terminal_display_lookup_color(term->display, value - 30, &term->current_attributes.foreground); /* Underscore on, default foreground OR 256-color * foreground */ else if (value == 38) { /* Attempt to set foreground with 256-color entry */ int xterm256_length = guac_terminal_parse_xterm256(term, argc - i - 1, &argv[i + 1], &term->current_attributes.foreground); /* If valid 256-color entry, foreground has been set */ if (xterm256_length > 0) i += xterm256_length; /* Otherwise interpret as underscore and default * foreground */ else { term->current_attributes.underscore = true; term->current_attributes.foreground = term->default_char.attributes.foreground; } } /* Underscore off, default foreground */ else if (value == 39) { term->current_attributes.underscore = false; term->current_attributes.foreground = term->default_char.attributes.foreground; } /* Background */ else if (value >= 40 && value <= 47) guac_terminal_display_lookup_color(term->display, value - 40, &term->current_attributes.background); /* 256-color background */ else if (value == 48) i += guac_terminal_parse_xterm256(term, argc - i - 1, &argv[i + 1], &term->current_attributes.background); /* Reset background */ else if (value == 49) term->current_attributes.background = term->default_char.attributes.background; /* Intense foreground */ else if (value >= 90 && value <= 97) guac_terminal_display_lookup_color(term->display, value - 90 + GUAC_TERMINAL_FIRST_INTENSE, &term->current_attributes.foreground); /* Intense background */ else if (value >= 100 && value <= 107) guac_terminal_display_lookup_color(term->display, value - 100 + GUAC_TERMINAL_FIRST_INTENSE, &term->current_attributes.background); } break; /* n: Status report */ case 'n': /* Device status report */ if (argv[0] == 5 && private_mode_character == 0) guac_terminal_send_string(term, GUAC_TERMINAL_OK); /* Cursor position report */ else if (argv[0] == 6 && private_mode_character == 0) guac_terminal_sendf(term, "\x1B[%i;%iR", term->cursor_row+1, term->cursor_col+1); break; /* q: Set keyboard LEDs */ case 'q': /* Explicitly ignored */ break; /* r: Set scrolling region */ case 'r': /* If parameters given, set region */ if (argc == 2) { term->scroll_start = argv[0]-1; term->scroll_end = argv[1]-1; } /* Otherwise, reset scrolling region */ else { term->scroll_start = 0; term->scroll_end = term->term_height - 1; } break; /* Save Cursor */ case 's': term->saved_cursor_row = term->cursor_row; term->saved_cursor_col = term->cursor_col; break; /* Restore Cursor */ case 'u': guac_terminal_move_cursor(term, term->saved_cursor_row, term->saved_cursor_col); break; /* Warn of unhandled codes */ default: if (c != ';') { guac_client_log(term->client, GUAC_LOG_DEBUG, "Unhandled CSI sequence: %c", c); for (i=0; iclient, GUAC_LOG_DEBUG, " -> argv[%i] = %i", i, argv[i]); } } /* If not a semicolon, end of CSI sequence */ if (c != ';') { term->char_handler = guac_terminal_echo; /* Reset parameters */ for (i=0; i= 0x3A && c <= 0x3F && private_mode_character == 0) private_mode_character = c; return 0; } int guac_terminal_set_directory(guac_terminal* term, unsigned char c) { static char filename[2048]; static int length = 0; /* Stop on ECMA-48 ST (String Terminator */ if (c == 0x9C || c == 0x5C || c == 0x07) { filename[length++] = '\0'; term->char_handler = guac_terminal_echo; if (term->upload_path_handler) term->upload_path_handler(term->client, filename); else guac_client_log(term->client, GUAC_LOG_DEBUG, "Cannot set upload path. File transfer is not enabled."); length = 0; } /* Otherwise, store character */ else if (length < sizeof(filename)-1) filename[length++] = c; return 0; } int guac_terminal_download(guac_terminal* term, unsigned char c) { static char filename[2048]; static int length = 0; /* Stop on ECMA-48 ST (String Terminator */ if (c == 0x9C || c == 0x5C || c == 0x07) { filename[length++] = '\0'; term->char_handler = guac_terminal_echo; if (term->file_download_handler) term->file_download_handler(term->client, filename); else guac_client_log(term->client, GUAC_LOG_DEBUG, "Cannot send file. File transfer is not enabled."); length = 0; } /* Otherwise, store character */ else if (length < sizeof(filename)-1) filename[length++] = c; return 0; } int guac_terminal_open_pipe_stream(guac_terminal* term, unsigned char c) { static char param[2048]; static int length = 0; static int flags = 0; /* Open pipe on ECMA-48 ST (String Terminator) */ if (c == 0x9C || c == 0x5C || c == 0x07) { /* End parameter string */ param[length++] = '\0'; length = 0; /* Open new pipe stream using final parameter as name */ guac_terminal_pipe_stream_open(term, param, flags); /* Return to echo mode */ term->char_handler = guac_terminal_echo; /* Reset tracked flags for sake of any future pipe streams */ flags = 0; } /* Interpret all parameters prior to the final parameter as integer * flags which should affect the pipe stream when opened */ else if (c == ';') { /* End parameter string */ param[length++] = '\0'; length = 0; /* Parse parameter string as integer flags */ flags |= atoi(param); } /* Otherwise, store character within parameter */ else if (length < sizeof(param)-1) param[length++] = c; return 0; } int guac_terminal_close_pipe_stream(guac_terminal* term, unsigned char c) { /* Handle closure on ECMA-48 ST (String Terminator) */ if (c == 0x9C || c == 0x5C || c == 0x07) { /* Close any existing pipe */ guac_terminal_pipe_stream_close(term); /* Return to echo mode */ term->char_handler = guac_terminal_echo; } /* Ignore all other characters */ return 0; } int guac_terminal_set_scrollback(guac_terminal* term, unsigned char c) { static char param[16]; static int length = 0; /* Open pipe on ECMA-48 ST (String Terminator) */ if (c == 0x9C || c == 0x5C || c == 0x07) { /* End parameter string */ param[length++] = '\0'; length = 0; /* Assign scrollback size */ term->requested_scrollback = atoi(param); /* Update scrollbar bounds */ guac_terminal_scrollbar_set_bounds(term->scrollbar, -guac_terminal_available_scroll(term), 0); /* Return to echo mode */ term->char_handler = guac_terminal_echo; } /* Otherwise, store character within parameter */ else if (length < sizeof(param) - 1) param[length++] = c; return 0; } int guac_terminal_window_title(guac_terminal* term, unsigned char c) { static int position = 0; static char title[4096]; guac_socket* socket = term->client->socket; /* Stop on ECMA-48 ST (String Terminator */ if (c == 0x9C || c == 0x5C || c == 0x07) { /* Terminate and reset stored title */ title[position] = '\0'; position = 0; /* Send title as connection name */ guac_protocol_send_name(socket, title); guac_socket_flush(socket); term->char_handler = guac_terminal_echo; } /* Store all other characters within title, space permitting */ else if (position < sizeof(title) - 1) title[position++] = (char) c; return 0; } int guac_terminal_xterm_palette(guac_terminal* term, unsigned char c) { /** * Whether we are currently reading the color spec. If false, we are * currently reading the color index. */ static bool read_color_spec = false; /** * The index of the palette entry being modified. */ static int index = 0; /** * The color spec string, valid only if read_color_spec is true. */ static char color_spec[256]; /** * The current position within the color spec string, valid only if * read_color_spec is true. */ static int color_spec_pos = 0; /* If not reading the color spec, parse the index */ if (!read_color_spec) { /* If digit, append to index */ if (c >= '0' && c <= '9') index = index * 10 + c - '0'; /* If end of parameter, switch to reading the color */ else if (c == ';') { read_color_spec = true; color_spec_pos = 0; } } /* Once the index has been parsed, read the color spec */ else { /* Modify palette once index/spec pair has been read */ if (c == ';' || c == 0x9C || c == 0x5C || c == 0x07) { guac_terminal_color color; /* Terminate color spec string */ color_spec[color_spec_pos] = '\0'; /* Modify palette if color spec is valid */ if (!guac_terminal_xparsecolor(color_spec, &color)) guac_terminal_display_assign_color(term->display, index, &color); else guac_client_log(term->client, GUAC_LOG_DEBUG, "Invalid XParseColor() color spec: \"%s\"", color_spec); /* Resume parsing index */ read_color_spec = false; index = 0; } /* Append characters to color spec as long as available space is not * exceeded */ else if (color_spec_pos < 255) { color_spec[color_spec_pos++] = c; } } /* Stop on ECMA-48 ST (String Terminator */ if (c == 0x9C || c == 0x5C || c == 0x07) term->char_handler = guac_terminal_echo; return 0; } int guac_terminal_osc(guac_terminal* term, unsigned char c) { static int operation = 0; /* If digit, append to operation */ if (c >= '0' && c <= '9') operation = operation * 10 + c - '0'; /* If end of parameter, check value */ else if (c == ';') { /* Download OSC */ if (operation == 482200) term->char_handler = guac_terminal_download; /* Set upload directory OSC */ else if (operation == 482201) term->char_handler = guac_terminal_set_directory; /* Open and redirect output to pipe stream OSC */ else if (operation == 482202) term->char_handler = guac_terminal_open_pipe_stream; /* Close pipe stream OSC */ else if (operation == 482203) term->char_handler = guac_terminal_close_pipe_stream; /* Set scrollback size OSC */ else if (operation == 482204) term->char_handler = guac_terminal_set_scrollback; /* Set window title OSC */ else if (operation == 0 || operation == 2) term->char_handler = guac_terminal_window_title; /* xterm 256-color palette redefinition */ else if (operation == 4) term->char_handler = guac_terminal_xterm_palette; /* Reset parameter for next OSC */ operation = 0; } /* Stop on ECMA-48 ST (String Terminator */ else if (c == 0x9C || c == 0x5C || c == 0x07) term->char_handler = guac_terminal_echo; /* Stop on unrecognized character */ else { guac_client_log(term->client, GUAC_LOG_DEBUG, "Unexpected character in OSC: 0x%X", c); term->char_handler = guac_terminal_echo; } return 0; } int guac_terminal_ctrl_func(guac_terminal* term, unsigned char c) { int row; /* Build character with current attributes */ guac_terminal_char guac_char; guac_char.value = 'E'; guac_char.attributes = term->current_attributes; switch (c) { /* Alignment test (fill screen with E's) */ case '8': for (row=0; rowterm_height; row++) guac_terminal_set_columns(term, row, 0, term->term_width-1, &guac_char); break; } term->char_handler = guac_terminal_echo; return 0; } int guac_terminal_apc(guac_terminal* term, unsigned char c) { /* xterm does not implement APC functions and neither do we. Look for the * "ESC \" (string terminator) sequence, while ignoring other chars. */ static bool escaping = false; if (escaping) { if (c == '\\') term->char_handler = guac_terminal_echo; escaping = false; } if (c == 0x1B) escaping = true; return 0; }