terminal.c (68195B)
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 21#include "common/clipboard.h" 22#include "common/cursor.h" 23#include "terminal/buffer.h" 24#include "terminal/color-scheme.h" 25#include "terminal/common.h" 26#include "terminal/display.h" 27#include "terminal/palette.h" 28#include "terminal/select.h" 29#include "terminal/terminal.h" 30#include "terminal/terminal-handlers.h" 31#include "terminal/terminal-priv.h" 32#include "terminal/types.h" 33#include "terminal/typescript.h" 34 35#include <ctype.h> 36#include <errno.h> 37#include <pthread.h> 38#include <stdarg.h> 39#include <stdio.h> 40#include <stdlib.h> 41#include <string.h> 42#include <sys/time.h> 43#include <unistd.h> 44#include <wchar.h> 45 46#include <guacamole/client.h> 47#include <guacamole/error.h> 48#include <guacamole/mem.h> 49#include <guacamole/protocol.h> 50#include <guacamole/socket.h> 51#include <guacamole/string.h> 52#include <guacamole/timestamp.h> 53 54/** 55 * Sets the given range of columns to the given character. 56 */ 57static void __guac_terminal_set_columns(guac_terminal* terminal, int row, 58 int start_column, int end_column, guac_terminal_char* character) { 59 60 guac_terminal_display_set_columns(terminal->display, row + terminal->scroll_offset, 61 start_column, end_column, character); 62 63 guac_terminal_buffer_set_columns(terminal->buffer, row, 64 start_column, end_column, character); 65 66 /* Clear selection if region is modified */ 67 guac_terminal_select_touch(terminal, row, start_column, row, end_column); 68 69} 70 71/** 72 * Enforces a character break at the given edge, ensuring that the left side 73 * of the edge is the final column of a character, and the right side of the 74 * edge is the initial column of a DIFFERENT character. 75 * 76 * For a character in a column N, the left edge number is N, and the right 77 * edge is N+1. 78 */ 79static void __guac_terminal_force_break(guac_terminal* terminal, int row, int edge) { 80 81 guac_terminal_buffer_row* buffer_row = guac_terminal_buffer_get_row(terminal->buffer, row, 0); 82 83 /* Ensure character to left of edge is unbroken */ 84 if (edge > 0) { 85 86 int end_column = edge - 1; 87 int start_column = end_column; 88 89 guac_terminal_char* start_char = &(buffer_row->characters[start_column]); 90 91 /* Determine start column */ 92 while (start_column > 0 && start_char->value == GUAC_CHAR_CONTINUATION) { 93 start_char--; 94 start_column--; 95 } 96 97 /* Advance to start of broken character if necessary */ 98 if (start_char->value != GUAC_CHAR_CONTINUATION && start_char->width < end_column - start_column + 1) { 99 start_column += start_char->width; 100 start_char += start_char->width; 101 } 102 103 /* Clear character if broken */ 104 if (start_char->value == GUAC_CHAR_CONTINUATION || start_char->width != end_column - start_column + 1) { 105 106 guac_terminal_char cleared_char; 107 cleared_char.value = ' '; 108 cleared_char.attributes = start_char->attributes; 109 cleared_char.width = 1; 110 111 __guac_terminal_set_columns(terminal, row, start_column, end_column, &cleared_char); 112 113 } 114 115 } 116 117 /* Ensure character to right of edge is unbroken */ 118 if (edge >= 0 && edge < buffer_row->length) { 119 120 int start_column = edge; 121 int end_column = start_column; 122 123 guac_terminal_char* start_char = &(buffer_row->characters[start_column]); 124 guac_terminal_char* end_char = &(buffer_row->characters[end_column]); 125 126 /* Determine end column */ 127 while (end_column+1 < buffer_row->length && (end_char+1)->value == GUAC_CHAR_CONTINUATION) { 128 end_char++; 129 end_column++; 130 } 131 132 /* Advance to start of broken character if necessary */ 133 if (start_char->value != GUAC_CHAR_CONTINUATION && start_char->width < end_column - start_column + 1) { 134 start_column += start_char->width; 135 start_char += start_char->width; 136 } 137 138 /* Clear character if broken */ 139 if (start_char->value == GUAC_CHAR_CONTINUATION || start_char->width != end_column - start_column + 1) { 140 141 guac_terminal_char cleared_char; 142 cleared_char.value = ' '; 143 cleared_char.attributes = start_char->attributes; 144 cleared_char.width = 1; 145 146 __guac_terminal_set_columns(terminal, row, start_column, end_column, &cleared_char); 147 148 } 149 150 } 151 152} 153 154/** 155 * Returns the number of rows available within the terminal buffer, taking 156 * changes to the desired scrollback size into account. Regardless of the 157 * true buffer length, only the number of rows that should be made available 158 * will be returned. 159 * 160 * @param term 161 * The terminal whose effective buffer length should be retrieved. 162 * 163 * @return 164 * The number of rows effectively available within the terminal buffer, 165 * taking changes to the desired scrollback size into account. 166 */ 167static int guac_terminal_effective_buffer_length(guac_terminal* term) { 168 169 int scrollback = term->requested_scrollback; 170 171 /* Limit available scrollback to defined maximum */ 172 if (scrollback > term->max_scrollback) 173 scrollback = term->max_scrollback; 174 175 /* There must always be at least enough scrollback to cover the visible 176 * terminal display */ 177 else if (scrollback < term->term_height) 178 scrollback = term->term_height; 179 180 /* If the buffer contains more rows than requested, pretend it only 181 * contains the requested number of rows */ 182 int effective_length = term->buffer->length; 183 if (effective_length > scrollback) 184 effective_length = scrollback; 185 186 return effective_length; 187 188} 189 190int guac_terminal_get_available_scroll(guac_terminal* term) { 191 return guac_terminal_effective_buffer_length(term) - term->term_height; 192} 193 194int guac_terminal_get_rows(guac_terminal* term) { 195 return term->term_height; 196} 197 198int guac_terminal_get_columns(guac_terminal* term) { 199 return term->term_width; 200} 201 202void guac_terminal_reset(guac_terminal* term) { 203 204 int row; 205 206 /* Set current state */ 207 term->char_handler = guac_terminal_echo; 208 term->active_char_set = 0; 209 term->char_mapping[0] = 210 term->char_mapping[1] = NULL; 211 212 /* Reset cursor location */ 213 term->cursor_row = term->visible_cursor_row = term->saved_cursor_row = 0; 214 term->cursor_col = term->visible_cursor_col = term->saved_cursor_col = 0; 215 term->cursor_visible = true; 216 217 /* Clear scrollback, buffer, and scroll region */ 218 term->buffer->top = 0; 219 term->buffer->length = 0; 220 term->scroll_start = 0; 221 term->scroll_end = term->term_height - 1; 222 term->scroll_offset = 0; 223 224 /* Reset scrollbar bounds */ 225 guac_terminal_scrollbar_set_bounds(term->scrollbar, 0, 0); 226 guac_terminal_scrollbar_set_value(term->scrollbar, -term->scroll_offset); 227 228 /* Reset flags */ 229 term->text_selected = false; 230 term->selection_committed = false; 231 term->application_cursor_keys = false; 232 term->automatic_carriage_return = false; 233 term->insert_mode = false; 234 235 /* Reset tabs */ 236 term->tab_interval = 8; 237 memset(term->custom_tabs, 0, sizeof(term->custom_tabs)); 238 239 /* Reset character attributes */ 240 term->current_attributes = term->default_char.attributes; 241 242 /* Reset display palette */ 243 guac_terminal_display_reset_palette(term->display); 244 245 /* Clear terminal */ 246 for (row=0; row<term->term_height; row++) 247 guac_terminal_set_columns(term, row, 0, term->term_width, &(term->default_char)); 248 249} 250 251/** 252 * Paints or repaints the background of the terminal display. This painting 253 * occurs beneath the actual terminal and scrollbar layers, and thus will not 254 * overwrite any text or other content currently on the screen. This is only 255 * necessary to paint over parts of the terminal background which may otherwise 256 * be transparent (the default layer background). 257 * 258 * @param terminal 259 * The terminal whose background should be painted or repainted. 260 * 261 * @param socket 262 * The socket over which instructions required to paint / repaint the 263 * terminal background should be send. 264 */ 265static void guac_terminal_repaint_default_layer(guac_terminal* terminal, 266 guac_socket* socket) { 267 268 int width = terminal->width; 269 int height = terminal->height; 270 guac_terminal_display* display = terminal->display; 271 272 /* Get background color */ 273 const guac_terminal_color* color = &display->default_background; 274 275 /* Reset size */ 276 guac_protocol_send_size(socket, GUAC_DEFAULT_LAYER, width, height); 277 278 /* Paint background color */ 279 guac_protocol_send_rect(socket, GUAC_DEFAULT_LAYER, 0, 0, width, height); 280 guac_protocol_send_cfill(socket, GUAC_COMP_OVER, GUAC_DEFAULT_LAYER, 281 color->red, color->green, color->blue, 0xFF); 282 283} 284 285/** 286 * Automatically and continuously renders frames of terminal data while the 287 * associated guac_client is running. 288 * 289 * @param data 290 * A pointer to the guac_terminal that should be continuously rendered 291 * while its associated guac_client is running. 292 * 293 * @return 294 * Always NULL. 295 */ 296void* guac_terminal_thread(void* data) { 297 298 guac_terminal* terminal = (guac_terminal*) data; 299 guac_client* client = terminal->client; 300 301 /* Render frames only while client is running */ 302 while (client->state == GUAC_CLIENT_RUNNING) { 303 304 /* Stop rendering if an error occurs */ 305 if (guac_terminal_render_frame(terminal)) 306 break; 307 308 /* Signal end of frame */ 309 guac_client_end_frame(client); 310 guac_socket_flush(client->socket); 311 312 } 313 314 /* The client has stopped or an error has occurred */ 315 return NULL; 316 317} 318 319guac_terminal_options* guac_terminal_options_create( 320 int width, int height, int dpi) { 321 322 guac_terminal_options* options = guac_mem_alloc(sizeof(guac_terminal_options)); 323 324 /* Set all required parameters */ 325 options->width = width; 326 options->height = height; 327 options->dpi = dpi; 328 329 /* Set default values for all other parameters */ 330 options->disable_copy = GUAC_TERMINAL_DEFAULT_DISABLE_COPY; 331 options->max_scrollback = GUAC_TERMINAL_DEFAULT_MAX_SCROLLBACK; 332 options->font_name = GUAC_TERMINAL_DEFAULT_FONT_NAME; 333 options->font_size = GUAC_TERMINAL_DEFAULT_FONT_SIZE; 334 options->color_scheme = GUAC_TERMINAL_DEFAULT_COLOR_SCHEME; 335 options->backspace = GUAC_TERMINAL_DEFAULT_BACKSPACE; 336 337 return options; 338} 339 340guac_terminal* guac_terminal_create(guac_client* client, 341 guac_terminal_options* options) { 342 343 /* The width and height may need to be changed from what's requested */ 344 int width = options->width; 345 int height = options->height; 346 347 /* Build default character using default colors */ 348 guac_terminal_char default_char = { 349 .value = 0, 350 .attributes = { 351 .bold = false, 352 .half_bright = false, 353 .reverse = false, 354 .underscore = false 355 }, 356 .width = 1 357 }; 358 359 /* Initialized by guac_terminal_parse_color_scheme. */ 360 guac_terminal_color (*default_palette)[256] = (guac_terminal_color(*)[256]) 361 guac_mem_alloc(sizeof(guac_terminal_color[256])); 362 363 guac_terminal_parse_color_scheme(client, options->color_scheme, 364 &default_char.attributes.foreground, 365 &default_char.attributes.background, 366 default_palette); 367 368 /* Calculate available display area */ 369 int available_width = width - GUAC_TERMINAL_SCROLLBAR_WIDTH; 370 if (available_width < 0) 371 available_width = 0; 372 373 guac_terminal* term = guac_mem_alloc(sizeof(guac_terminal)); 374 term->started = false; 375 term->client = client; 376 term->upload_path_handler = NULL; 377 term->file_download_handler = NULL; 378 379 /* Copy initially-provided color scheme and font details */ 380 term->color_scheme = guac_strdup(options->color_scheme); 381 term->font_name = guac_strdup(options->font_name); 382 term->font_size = options->font_size; 383 384 /* Set size of available screen area */ 385 term->outer_width = width; 386 term->outer_height = height; 387 388 /* Init modified flag and conditional */ 389 term->modified = 0; 390 pthread_cond_init(&(term->modified_cond), NULL); 391 pthread_mutex_init(&(term->modified_lock), NULL); 392 393 /* Maximum and requested scrollback are initially the same */ 394 term->max_scrollback = options->max_scrollback; 395 term->requested_scrollback = options->max_scrollback; 396 397 /* Allocate enough space for maximum scrollback, bumping up internal 398 * storage as necessary to allow screen to be resized to maximum height */ 399 int initial_scrollback = options->max_scrollback; 400 if (initial_scrollback < GUAC_TERMINAL_MAX_ROWS) 401 initial_scrollback = GUAC_TERMINAL_MAX_ROWS; 402 403 /* Init buffer */ 404 term->buffer = guac_terminal_buffer_alloc(initial_scrollback, 405 &default_char); 406 407 /* Init display */ 408 term->display = guac_terminal_display_alloc(client, 409 options->font_name, options->font_size, options->dpi, 410 &default_char.attributes.foreground, 411 &default_char.attributes.background, 412 (guac_terminal_color(*)[256]) default_palette); 413 414 /* Fail if display init failed */ 415 if (term->display == NULL) { 416 guac_client_log(client, GUAC_LOG_DEBUG, "Display initialization failed"); 417 guac_mem_free(term); 418 return NULL; 419 } 420 421 /* Init common cursor */ 422 term->cursor = guac_common_cursor_alloc(client); 423 424 /* Init terminal state */ 425 term->current_attributes = default_char.attributes; 426 term->default_char = default_char; 427 term->clipboard = guac_common_clipboard_alloc(); 428 term->disable_copy = options->disable_copy; 429 430 /* Calculate character size */ 431 int rows = height / term->display->char_height; 432 int columns = available_width / term->display->char_width; 433 434 /* Keep height within predefined maximum */ 435 if (rows > GUAC_TERMINAL_MAX_ROWS) { 436 rows = GUAC_TERMINAL_MAX_ROWS; 437 height = rows * term->display->char_height; 438 } 439 440 /* Keep width within predefined maximum */ 441 if (columns > GUAC_TERMINAL_MAX_COLUMNS) { 442 columns = GUAC_TERMINAL_MAX_COLUMNS; 443 available_width = columns * term->display->char_width; 444 width = available_width + GUAC_TERMINAL_SCROLLBAR_WIDTH; 445 } 446 447 /* Set pixel size */ 448 term->width = width; 449 term->height = height; 450 451 term->term_width = columns; 452 term->term_height = rows; 453 454 /* Open STDIN pipe */ 455 if (pipe(term->stdin_pipe_fd)) { 456 guac_error = GUAC_STATUS_SEE_ERRNO; 457 guac_error_message = "Unable to open pipe for STDIN"; 458 guac_mem_free(term); 459 return NULL; 460 } 461 462 /* Read input from keyboard by default */ 463 term->input_stream = NULL; 464 465 /* Init pipe stream (output to display by default) */ 466 term->pipe_stream = NULL; 467 468 /* No typescript by default */ 469 term->typescript = NULL; 470 471 /* Init terminal lock */ 472 pthread_mutex_init(&(term->lock), NULL); 473 474 /* Repaint and resize overall display */ 475 guac_terminal_repaint_default_layer(term, term->client->socket); 476 guac_terminal_display_resize(term->display, 477 term->term_width, term->term_height); 478 479 /* Allocate scrollbar */ 480 term->scrollbar = guac_terminal_scrollbar_alloc(term->client, GUAC_DEFAULT_LAYER, 481 width, height, term->term_height); 482 483 /* Associate scrollbar with this terminal */ 484 term->scrollbar->data = term; 485 term->scrollbar->scroll_handler = guac_terminal_scroll_handler; 486 487 /* Init terminal */ 488 guac_terminal_reset(term); 489 490 /* All mouse buttons are released */ 491 term->mouse_mask = 0; 492 493 /* All keyboard modifiers are released */ 494 term->mod_alt = 495 term->mod_ctrl = 496 term->mod_shift = 0; 497 498 /* Initialize mouse cursor */ 499 term->current_cursor = GUAC_TERMINAL_CURSOR_BLANK; 500 guac_common_cursor_set_blank(term->cursor); 501 502 /* Start terminal thread */ 503 if (pthread_create(&(term->thread), NULL, 504 guac_terminal_thread, (void*) term)) { 505 guac_terminal_free(term); 506 return NULL; 507 } 508 509 /* Configure backspace */ 510 term->backspace = options->backspace; 511 512 return term; 513 514} 515 516void guac_terminal_start(guac_terminal* term) { 517 term->started = true; 518 guac_terminal_notify(term); 519} 520 521void guac_terminal_stop(guac_terminal* term) { 522 523 /* Close input pipe and set fds to invalid */ 524 if (term->stdin_pipe_fd[1] != -1) { 525 close(term->stdin_pipe_fd[1]); 526 term->stdin_pipe_fd[1] = -1; 527 } 528 if (term->stdin_pipe_fd[0] != -1) { 529 close(term->stdin_pipe_fd[0]); 530 term->stdin_pipe_fd[0] = -1; 531 } 532} 533 534void guac_terminal_free(guac_terminal* term) { 535 536 /* Close user input pipe */ 537 guac_terminal_stop(term); 538 539 /* Wait for render thread to finish */ 540 pthread_join(term->thread, NULL); 541 542 /* Close and flush any open pipe stream */ 543 guac_terminal_pipe_stream_close(term); 544 545 /* Close and flush any active typescript */ 546 guac_terminal_typescript_free(term->typescript); 547 548 /* Free display */ 549 guac_terminal_display_free(term->display); 550 551 /* Free buffer */ 552 guac_terminal_buffer_free(term->buffer); 553 554 /* Free scrollbar */ 555 guac_terminal_scrollbar_free(term->scrollbar); 556 557 /* Free copies of font and color scheme information */ 558 guac_mem_free_const(term->color_scheme); 559 guac_mem_free_const(term->font_name); 560 561 /* Free clipboard */ 562 guac_common_clipboard_free(term->clipboard); 563 564 /* Free the terminal itself */ 565 guac_mem_free(term); 566 567} 568 569/** 570 * Populate the given timespec with the current time, plus the given offset. 571 * 572 * @param ts 573 * The timespec structure to populate. 574 * 575 * @param offset_sec 576 * The offset from the current time to use when populating the given 577 * timespec, in seconds. 578 * 579 * @param offset_usec 580 * The offset from the current time to use when populating the given 581 * timespec, in microseconds. 582 */ 583static void guac_terminal_get_absolute_time(struct timespec* ts, 584 int offset_sec, int offset_usec) { 585 586 /* Get timeval */ 587 struct timeval tv; 588 gettimeofday(&tv, NULL); 589 590 /* Update with offset */ 591 tv.tv_sec += offset_sec; 592 tv.tv_usec += offset_usec; 593 594 /* Wrap to next second if necessary */ 595 if (tv.tv_usec >= 1000000) { 596 tv.tv_sec++; 597 tv.tv_usec -= 1000000; 598 } 599 600 /* Convert to timespec */ 601 ts->tv_sec = tv.tv_sec; 602 ts->tv_nsec = tv.tv_usec * 1000; 603 604} 605 606/** 607 * Waits for the terminal state to be modified, returning only when the 608 * specified timeout has elapsed or a frame flush is desired. Note that the 609 * modified flag of the terminal will only be reset if no data remains to be 610 * read from STDOUT. 611 * 612 * @param terminal 613 * The terminal to wait on. 614 * 615 * @param msec_timeout 616 * The maximum amount of time to wait, in milliseconds. 617 * 618 * @return 619 * Non-zero if the terminal has been modified, zero if the timeout has 620 * elapsed without the terminal being modified. 621 */ 622static int guac_terminal_wait(guac_terminal* terminal, int msec_timeout) { 623 624 int retval = 1; 625 626 pthread_mutex_t* mod_lock = &(terminal->modified_lock); 627 pthread_cond_t* mod_cond = &(terminal->modified_cond); 628 629 /* Split provided milliseconds into microseconds and whole seconds */ 630 int secs = msec_timeout / 1000; 631 int usecs = (msec_timeout % 1000) * 1000; 632 633 /* Calculate absolute timestamp from provided relative timeout */ 634 struct timespec timeout; 635 guac_terminal_get_absolute_time(&timeout, secs, usecs); 636 637 /* Test for terminal modification */ 638 pthread_mutex_lock(mod_lock); 639 if (terminal->modified) 640 goto wait_complete; 641 642 /* If not yet modified, wait for modification condition to be signaled */ 643 retval = pthread_cond_timedwait(mod_cond, mod_lock, &timeout) != ETIMEDOUT; 644 645wait_complete: 646 647 /* Terminal is no longer modified */ 648 terminal->modified = 0; 649 pthread_mutex_unlock(mod_lock); 650 return retval; 651 652} 653 654int guac_terminal_render_frame(guac_terminal* terminal) { 655 656 guac_client* client = terminal->client; 657 658 int wait_result; 659 660 /* Wait for data to be available */ 661 wait_result = guac_terminal_wait(terminal, 1000); 662 if (wait_result || !terminal->started) { 663 664 guac_timestamp frame_start = guac_timestamp_current(); 665 666 do { 667 668 /* Calculate time remaining in frame */ 669 guac_timestamp frame_end = guac_timestamp_current(); 670 int frame_remaining = frame_start + GUAC_TERMINAL_FRAME_DURATION 671 - frame_end; 672 673 /* Wait again if frame remaining */ 674 if (frame_remaining > 0 || !terminal->started) 675 wait_result = guac_terminal_wait(terminal, 676 GUAC_TERMINAL_FRAME_TIMEOUT); 677 else 678 break; 679 680 } while (client->state == GUAC_CLIENT_RUNNING 681 && (wait_result > 0 || !terminal->started)); 682 683 /* Flush terminal */ 684 guac_terminal_lock(terminal); 685 guac_terminal_flush(terminal); 686 guac_terminal_unlock(terminal); 687 688 } 689 690 return 0; 691 692} 693 694int guac_terminal_read_stdin(guac_terminal* terminal, char* c, int size) { 695 int stdin_fd = terminal->stdin_pipe_fd[0]; 696 return read(stdin_fd, c, size); 697} 698 699void guac_terminal_notify(guac_terminal* terminal) { 700 701 pthread_mutex_t* mod_lock = &(terminal->modified_lock); 702 pthread_cond_t* mod_cond = &(terminal->modified_cond); 703 704 pthread_mutex_lock(mod_lock); 705 706 /* Signal modification */ 707 terminal->modified = 1; 708 pthread_cond_signal(mod_cond); 709 710 pthread_mutex_unlock(mod_lock); 711 712} 713 714int guac_terminal_printf(guac_terminal* terminal, const char* format, ...) { 715 716 int written; 717 718 va_list ap; 719 char buffer[1024]; 720 721 /* Print to buffer */ 722 va_start(ap, format); 723 written = vsnprintf(buffer, sizeof(buffer)-1, format, ap); 724 va_end(ap); 725 726 if (written < 0) 727 return written; 728 729 /* Write to STDOUT */ 730 return guac_terminal_write(terminal, buffer, written); 731 732} 733 734char* guac_terminal_prompt(guac_terminal* terminal, const char* title, 735 bool echo) { 736 737 char buffer[1024]; 738 739 int pos; 740 char in_byte; 741 742 /* Prompting implicitly requires user input */ 743 guac_terminal_start(terminal); 744 745 /* Print title */ 746 guac_terminal_printf(terminal, "%s", title); 747 748 /* Read bytes until newline */ 749 pos = 0; 750 while (guac_terminal_read_stdin(terminal, &in_byte, 1) == 1) { 751 752 /* Backspace */ 753 if (in_byte == 0x7F) { 754 if (pos > 0) { 755 guac_terminal_printf(terminal, "\b \b"); 756 pos--; 757 } 758 } 759 760 /* CR (end of input */ 761 else if (in_byte == 0x0D) { 762 guac_terminal_printf(terminal, "\r\n"); 763 break; 764 } 765 766 /* Otherwise, store byte if there is room */ 767 else if (pos < sizeof(buffer) - 1) { 768 769 /* Store character, update buffers */ 770 buffer[pos++] = in_byte; 771 772 /* Print character if echoing */ 773 if (echo) 774 guac_terminal_printf(terminal, "%c", in_byte); 775 else 776 guac_terminal_printf(terminal, "*"); 777 778 } 779 780 /* Ignore all other input */ 781 782 } 783 784 /* Terminate string */ 785 buffer[pos] = 0; 786 787 /* Return newly-allocated string containing result */ 788 return guac_strdup(buffer); 789 790} 791 792int guac_terminal_set(guac_terminal* term, int row, int col, int codepoint) { 793 794 /* Calculate width in columns */ 795 int width = wcwidth(codepoint); 796 if (width < 0) 797 width = 1; 798 799 /* Do nothing if glyph is empty */ 800 else if (width == 0) 801 return 0; 802 803 /* Build character with current attributes */ 804 guac_terminal_char guac_char = { 805 .value = codepoint, 806 .attributes = term->current_attributes, 807 .width = width 808 }; 809 810 guac_terminal_set_columns(term, row, col, col + width - 1, &guac_char); 811 812 return 0; 813 814} 815 816void guac_terminal_commit_cursor(guac_terminal* term) { 817 818 guac_terminal_char* guac_char; 819 820 guac_terminal_buffer_row* row; 821 822 /* If no change, done */ 823 if (term->cursor_visible && term->visible_cursor_row == term->cursor_row && term->visible_cursor_col == term->cursor_col) 824 return; 825 826 /* Clear cursor if it was visible */ 827 if (term->visible_cursor_row != -1 && term->visible_cursor_col != -1) { 828 /* Get old row with cursor */ 829 row = guac_terminal_buffer_get_row(term->buffer, term->visible_cursor_row, term->visible_cursor_col+1); 830 831 guac_char = &(row->characters[term->visible_cursor_col]); 832 guac_char->attributes.cursor = false; 833 guac_terminal_display_set_columns(term->display, term->visible_cursor_row + term->scroll_offset, 834 term->visible_cursor_col, term->visible_cursor_col, guac_char); 835 } 836 837 /* Set cursor if should be visible */ 838 if (term->cursor_visible) { 839 /* Get new row with cursor */ 840 row = guac_terminal_buffer_get_row(term->buffer, term->cursor_row, term->cursor_col+1); 841 842 guac_char = &(row->characters[term->cursor_col]); 843 guac_char->attributes.cursor = true; 844 guac_terminal_display_set_columns(term->display, term->cursor_row + term->scroll_offset, 845 term->cursor_col, term->cursor_col, guac_char); 846 847 term->visible_cursor_row = term->cursor_row; 848 term->visible_cursor_col = term->cursor_col; 849 } 850 851 /* Otherwise set visible position to a sentinel value */ 852 else { 853 term->visible_cursor_row = -1; 854 term->visible_cursor_col = -1; 855 } 856 857 return; 858 859} 860 861int guac_terminal_write(guac_terminal* term, const char* buffer, int length) { 862 863 guac_terminal_lock(term); 864 for (int written = 0; written < length; written++) { 865 866 /* Read and advance to next character */ 867 char current = *(buffer++); 868 869 /* Write character to typescript, if any */ 870 if (term->typescript != NULL) 871 guac_terminal_typescript_write(term->typescript, current); 872 873 /* Handle character and its meaning */ 874 term->char_handler(term, current); 875 876 } 877 guac_terminal_unlock(term); 878 879 guac_terminal_notify(term); 880 return length; 881 882} 883 884int guac_terminal_scroll_up(guac_terminal* term, 885 int start_row, int end_row, int amount) { 886 887 /* If scrolling entire display, update scroll offset */ 888 if (start_row == 0 && end_row == term->term_height - 1) { 889 890 /* Scroll up visibly */ 891 guac_terminal_display_copy_rows(term->display, start_row + amount, end_row, -amount); 892 893 /* Advance by scroll amount */ 894 term->buffer->top += amount; 895 if (term->buffer->top >= term->buffer->available) 896 term->buffer->top -= term->buffer->available; 897 898 term->buffer->length += amount; 899 if (term->buffer->length > term->buffer->available) 900 term->buffer->length = term->buffer->available; 901 902 /* Reset scrollbar bounds */ 903 guac_terminal_scrollbar_set_bounds(term->scrollbar, 904 -guac_terminal_get_available_scroll(term), 0); 905 906 /* Update cursor location if within region */ 907 if (term->visible_cursor_row >= start_row && 908 term->visible_cursor_row <= end_row) 909 term->visible_cursor_row -= amount; 910 911 /* Update selected region */ 912 if (term->text_selected) { 913 term->selection_start_row -= amount; 914 term->selection_end_row -= amount; 915 } 916 917 } 918 919 /* Otherwise, just copy row data upwards */ 920 else 921 guac_terminal_copy_rows(term, start_row + amount, end_row, -amount); 922 923 /* Clear new area */ 924 guac_terminal_clear_range(term, 925 end_row - amount + 1, 0, 926 end_row, term->term_width - 1); 927 928 return 0; 929} 930 931int guac_terminal_scroll_down(guac_terminal* term, 932 int start_row, int end_row, int amount) { 933 934 guac_terminal_copy_rows(term, start_row, end_row - amount, amount); 935 936 /* Clear new area */ 937 guac_terminal_clear_range(term, 938 start_row, 0, 939 start_row + amount - 1, term->term_width - 1); 940 941 return 0; 942} 943 944int guac_terminal_clear_columns(guac_terminal* term, 945 int row, int start_col, int end_col) { 946 947 /* Build space */ 948 guac_terminal_char blank; 949 blank.value = 0; 950 blank.attributes = term->current_attributes; 951 blank.width = 1; 952 953 /* Clear */ 954 guac_terminal_set_columns(term, 955 row, start_col, end_col, &blank); 956 957 return 0; 958 959} 960 961int guac_terminal_clear_range(guac_terminal* term, 962 int start_row, int start_col, 963 int end_row, int end_col) { 964 965 /* If not at far left, must clear sub-region to far right */ 966 if (start_col > 0) { 967 968 /* Clear from start_col to far right */ 969 guac_terminal_clear_columns(term, 970 start_row, start_col, term->term_width - 1); 971 972 /* One less row to clear */ 973 start_row++; 974 } 975 976 /* If not at far right, must clear sub-region to far left */ 977 if (end_col < term->term_width - 1) { 978 979 /* Clear from far left to end_col */ 980 guac_terminal_clear_columns(term, end_row, 0, end_col); 981 982 /* One less row to clear */ 983 end_row--; 984 985 } 986 987 /* Remaining region now guaranteed rectangular. Clear, if possible */ 988 if (start_row <= end_row) { 989 990 int row; 991 for (row=start_row; row<=end_row; row++) { 992 993 /* Clear entire row */ 994 guac_terminal_clear_columns(term, row, 0, term->term_width - 1); 995 996 } 997 998 } 999 1000 return 0; 1001 1002} 1003 1004/** 1005 * Returns whether the given character would be visible relative to the 1006 * background of the given terminal. 1007 * 1008 * @param term 1009 * The guac_terminal to test the character against. 1010 * 1011 * @param c 1012 * The character being tested. 1013 * 1014 * @return 1015 * true if the given character is different from the terminal background, 1016 * false otherwise. 1017 */ 1018static bool guac_terminal_is_visible(guac_terminal* term, 1019 guac_terminal_char* c) { 1020 1021 /* Continuation characters are NEVER visible */ 1022 if (c->value == GUAC_CHAR_CONTINUATION) 1023 return false; 1024 1025 /* Characters with glyphs are ALWAYS visible */ 1026 if (guac_terminal_has_glyph(c->value)) 1027 return true; 1028 1029 const guac_terminal_color* background; 1030 1031 /* Determine actual background color of character */ 1032 if (c->attributes.reverse != c->attributes.cursor) 1033 background = &c->attributes.foreground; 1034 else 1035 background = &c->attributes.background; 1036 1037 /* Blank characters are visible if their background color differs from that 1038 * of the terminal */ 1039 return guac_terminal_colorcmp(background, 1040 &term->default_char.attributes.background) != 0; 1041 1042} 1043 1044void guac_terminal_scroll_display_down(guac_terminal* terminal, 1045 int scroll_amount) { 1046 1047 int start_row, end_row; 1048 int dest_row; 1049 int row, column; 1050 1051 /* Limit scroll amount by size of scrollback buffer */ 1052 if (scroll_amount > terminal->scroll_offset) 1053 scroll_amount = terminal->scroll_offset; 1054 1055 /* If not scrolling at all, don't bother trying */ 1056 if (scroll_amount <= 0) 1057 return; 1058 1059 /* Shift screen up */ 1060 if (terminal->term_height > scroll_amount) 1061 guac_terminal_display_copy_rows(terminal->display, 1062 scroll_amount, terminal->term_height - 1, 1063 -scroll_amount); 1064 1065 /* Advance by scroll amount */ 1066 terminal->scroll_offset -= scroll_amount; 1067 guac_terminal_scrollbar_set_value(terminal->scrollbar, -terminal->scroll_offset); 1068 1069 /* Get row range */ 1070 end_row = terminal->term_height - terminal->scroll_offset - 1; 1071 start_row = end_row - scroll_amount + 1; 1072 dest_row = terminal->term_height - scroll_amount; 1073 1074 /* Draw new rows from scrollback */ 1075 for (row=start_row; row<=end_row; row++) { 1076 1077 /* Get row from scrollback */ 1078 guac_terminal_buffer_row* buffer_row = 1079 guac_terminal_buffer_get_row(terminal->buffer, row, 0); 1080 1081 /* Clear row */ 1082 guac_terminal_display_set_columns(terminal->display, 1083 dest_row, 0, terminal->display->width, &(terminal->default_char)); 1084 1085 /* Draw row */ 1086 guac_terminal_char* current = buffer_row->characters; 1087 for (column=0; column<buffer_row->length; column++) { 1088 1089 /* Only draw if not blank */ 1090 if (guac_terminal_is_visible(terminal, current)) 1091 guac_terminal_display_set_columns(terminal->display, dest_row, column, column, current); 1092 1093 current++; 1094 1095 } 1096 1097 /* Next row */ 1098 dest_row++; 1099 1100 } 1101 1102 guac_terminal_notify(terminal); 1103 1104} 1105 1106void guac_terminal_scroll_display_up(guac_terminal* terminal, 1107 int scroll_amount) { 1108 1109 int start_row, end_row; 1110 int dest_row; 1111 int row, column; 1112 1113 /* Limit scroll amount by size of scrollback buffer */ 1114 int available_scroll = guac_terminal_get_available_scroll(terminal); 1115 if (terminal->scroll_offset + scroll_amount > available_scroll) 1116 scroll_amount = available_scroll - terminal->scroll_offset; 1117 1118 /* If not scrolling at all, don't bother trying */ 1119 if (scroll_amount <= 0) 1120 return; 1121 1122 /* Shift screen down */ 1123 if (terminal->term_height > scroll_amount) 1124 guac_terminal_display_copy_rows(terminal->display, 1125 0, terminal->term_height - scroll_amount - 1, 1126 scroll_amount); 1127 1128 /* Advance by scroll amount */ 1129 terminal->scroll_offset += scroll_amount; 1130 guac_terminal_scrollbar_set_value(terminal->scrollbar, -terminal->scroll_offset); 1131 1132 /* Get row range */ 1133 start_row = -terminal->scroll_offset; 1134 end_row = start_row + scroll_amount - 1; 1135 dest_row = 0; 1136 1137 /* Draw new rows from scrollback */ 1138 for (row=start_row; row<=end_row; row++) { 1139 1140 /* Get row from scrollback */ 1141 guac_terminal_buffer_row* buffer_row = 1142 guac_terminal_buffer_get_row(terminal->buffer, row, 0); 1143 1144 /* Clear row */ 1145 guac_terminal_display_set_columns(terminal->display, 1146 dest_row, 0, terminal->display->width, &(terminal->default_char)); 1147 1148 /* Draw row */ 1149 guac_terminal_char* current = buffer_row->characters; 1150 for (column=0; column<buffer_row->length; column++) { 1151 1152 /* Only draw if not blank */ 1153 if (guac_terminal_is_visible(terminal, current)) 1154 guac_terminal_display_set_columns(terminal->display, dest_row, column, column, current); 1155 1156 current++; 1157 1158 } 1159 1160 /* Next row */ 1161 dest_row++; 1162 1163 } 1164 1165 guac_terminal_notify(terminal); 1166 1167} 1168 1169void guac_terminal_copy_columns(guac_terminal* terminal, int row, 1170 int start_column, int end_column, int offset) { 1171 1172 guac_terminal_display_copy_columns(terminal->display, row + terminal->scroll_offset, 1173 start_column, end_column, offset); 1174 1175 guac_terminal_buffer_copy_columns(terminal->buffer, row, 1176 start_column, end_column, offset); 1177 1178 /* Clear selection if region is modified */ 1179 guac_terminal_select_touch(terminal, row, start_column, row, end_column); 1180 1181 /* Update cursor location if within region */ 1182 if (row == terminal->visible_cursor_row && 1183 terminal->visible_cursor_col >= start_column && 1184 terminal->visible_cursor_col <= end_column) 1185 terminal->visible_cursor_col += offset; 1186 1187 /* Force breaks around destination region */ 1188 __guac_terminal_force_break(terminal, row, start_column + offset); 1189 __guac_terminal_force_break(terminal, row, end_column + offset + 1); 1190 1191} 1192 1193void guac_terminal_copy_rows(guac_terminal* terminal, 1194 int start_row, int end_row, int offset) { 1195 1196 guac_terminal_display_copy_rows(terminal->display, 1197 start_row + terminal->scroll_offset, end_row + terminal->scroll_offset, offset); 1198 1199 guac_terminal_buffer_copy_rows(terminal->buffer, 1200 start_row, end_row, offset); 1201 1202 /* Clear selection if region is modified */ 1203 guac_terminal_select_touch(terminal, start_row, 0, end_row, 1204 terminal->term_width); 1205 1206 /* Update cursor location if within region */ 1207 if (terminal->visible_cursor_row >= start_row && 1208 terminal->visible_cursor_row <= end_row) 1209 terminal->visible_cursor_row += offset; 1210 1211} 1212 1213void guac_terminal_set_columns(guac_terminal* terminal, int row, 1214 int start_column, int end_column, guac_terminal_char* character) { 1215 1216 __guac_terminal_set_columns(terminal, row, start_column, end_column, character); 1217 1218 /* If visible cursor in current row, preserve state */ 1219 if (row == terminal->visible_cursor_row 1220 && terminal->visible_cursor_col >= start_column 1221 && terminal->visible_cursor_col <= end_column) { 1222 1223 /* Create copy of character with cursor attribute set */ 1224 guac_terminal_char cursor_character = *character; 1225 cursor_character.attributes.cursor = true; 1226 1227 __guac_terminal_set_columns(terminal, row, 1228 terminal->visible_cursor_col, terminal->visible_cursor_col, &cursor_character); 1229 1230 } 1231 1232 /* Force breaks around destination region */ 1233 __guac_terminal_force_break(terminal, row, start_column); 1234 __guac_terminal_force_break(terminal, row, end_column + 1); 1235 1236} 1237 1238static void __guac_terminal_redraw_rect(guac_terminal* term, int start_row, int start_col, int end_row, int end_col) { 1239 1240 int row, col; 1241 1242 /* Redraw region */ 1243 for (row=start_row; row<=end_row; row++) { 1244 1245 guac_terminal_buffer_row* buffer_row = 1246 guac_terminal_buffer_get_row(term->buffer, row - term->scroll_offset, 0); 1247 1248 /* Clear row */ 1249 guac_terminal_display_set_columns(term->display, 1250 row, start_col, end_col, &(term->default_char)); 1251 1252 /* Copy characters */ 1253 for (col=start_col; col <= end_col && col < buffer_row->length; col++) { 1254 1255 /* Only redraw if not blank */ 1256 guac_terminal_char* c = &(buffer_row->characters[col]); 1257 if (guac_terminal_is_visible(term, c)) 1258 guac_terminal_display_set_columns(term->display, row, col, col, c); 1259 1260 } 1261 1262 } 1263 1264} 1265 1266/** 1267 * Internal terminal resize routine. Accepts width/height in CHARACTERS 1268 * (not pixels like the public function). 1269 */ 1270static void __guac_terminal_resize(guac_terminal* term, int width, int height) { 1271 1272 /* If height is decreasing, shift display up */ 1273 if (height < term->term_height) { 1274 1275 int shift_amount; 1276 1277 /* Get number of rows actually occupying terminal space */ 1278 int used_height = guac_terminal_effective_buffer_length(term); 1279 if (used_height > term->term_height) 1280 used_height = term->term_height; 1281 1282 shift_amount = used_height - height; 1283 1284 /* If the new terminal bottom covers N rows, shift up N rows */ 1285 if (shift_amount > 0) { 1286 1287 guac_terminal_display_copy_rows(term->display, 1288 shift_amount, term->display->height - 1, -shift_amount); 1289 1290 /* Update buffer top and cursor row based on shift */ 1291 term->buffer->top += shift_amount; 1292 term->cursor_row -= shift_amount; 1293 if (term->visible_cursor_row != -1) 1294 term->visible_cursor_row -= shift_amount; 1295 1296 /* Redraw characters within old region */ 1297 __guac_terminal_redraw_rect(term, height - shift_amount, 0, height-1, width-1); 1298 1299 } 1300 1301 } 1302 1303 /* Resize display */ 1304 guac_terminal_display_flush(term->display); 1305 guac_terminal_display_resize(term->display, width, height); 1306 1307 /* Reraw any characters on right if widening */ 1308 if (width > term->term_width) 1309 __guac_terminal_redraw_rect(term, 0, term->term_width-1, height-1, width-1); 1310 1311 /* If height is increasing, shift display down */ 1312 if (height > term->term_height) { 1313 1314 /* If undisplayed rows exist in the buffer, shift them into view */ 1315 int available_scroll = guac_terminal_get_available_scroll(term); 1316 if (available_scroll > 0) { 1317 1318 /* If the new terminal bottom reveals N rows, shift down N rows */ 1319 int shift_amount = height - term->term_height; 1320 1321 /* The maximum amount we can shift is the number of undisplayed rows */ 1322 if (shift_amount > available_scroll) 1323 shift_amount = available_scroll; 1324 1325 /* Update buffer top and cursor row based on shift */ 1326 term->buffer->top -= shift_amount; 1327 term->cursor_row += shift_amount; 1328 if (term->visible_cursor_row != -1) 1329 term->visible_cursor_row += shift_amount; 1330 1331 /* If scrolled enough, use scroll to fulfill entire resize */ 1332 if (term->scroll_offset >= shift_amount) { 1333 1334 term->scroll_offset -= shift_amount; 1335 guac_terminal_scrollbar_set_value(term->scrollbar, -term->scroll_offset); 1336 1337 /* Draw characters from scroll at bottom */ 1338 __guac_terminal_redraw_rect(term, term->term_height, 0, term->term_height + shift_amount - 1, width-1); 1339 1340 } 1341 1342 /* Otherwise, fulfill with as much scroll as possible */ 1343 else { 1344 1345 /* Draw characters from scroll at bottom */ 1346 __guac_terminal_redraw_rect(term, term->term_height, 0, term->term_height + term->scroll_offset - 1, width-1); 1347 1348 /* Update shift_amount and scroll based on new rows */ 1349 shift_amount -= term->scroll_offset; 1350 term->scroll_offset = 0; 1351 guac_terminal_scrollbar_set_value(term->scrollbar, -term->scroll_offset); 1352 1353 /* If anything remains, move screen as necessary */ 1354 if (shift_amount > 0) { 1355 1356 guac_terminal_display_copy_rows(term->display, 1357 0, term->display->height - shift_amount - 1, shift_amount); 1358 1359 /* Draw characters at top from scroll */ 1360 __guac_terminal_redraw_rect(term, 0, 0, shift_amount - 1, width-1); 1361 1362 } 1363 1364 } 1365 1366 } /* end if undisplayed rows exist */ 1367 1368 } 1369 1370 /* Keep cursor on screen */ 1371 if (term->cursor_row < 0) term->cursor_row = 0; 1372 if (term->cursor_row >= height) term->cursor_row = height-1; 1373 if (term->cursor_col < 0) term->cursor_col = 0; 1374 if (term->cursor_col >= width) term->cursor_col = width-1; 1375 1376 /* Commit new dimensions */ 1377 term->term_width = width; 1378 term->term_height = height; 1379 1380} 1381 1382int guac_terminal_resize(guac_terminal* terminal, int width, int height) { 1383 1384 guac_terminal_display* display = terminal->display; 1385 guac_client* client = display->client; 1386 1387 /* Acquire exclusive access to terminal */ 1388 guac_terminal_lock(terminal); 1389 1390 /* Set size of available screen area */ 1391 terminal->outer_width = width; 1392 terminal->outer_height = height; 1393 1394 /* Calculate available display area */ 1395 int available_width = width - GUAC_TERMINAL_SCROLLBAR_WIDTH; 1396 if (available_width < 0) 1397 available_width = 0; 1398 1399 /* Calculate dimensions */ 1400 int rows = height / display->char_height; 1401 int columns = available_width / display->char_width; 1402 1403 /* Keep height within predefined maximum */ 1404 if (rows > GUAC_TERMINAL_MAX_ROWS) { 1405 rows = GUAC_TERMINAL_MAX_ROWS; 1406 height = rows * display->char_height; 1407 } 1408 1409 /* Keep width within predefined maximum */ 1410 if (columns > GUAC_TERMINAL_MAX_COLUMNS) { 1411 columns = GUAC_TERMINAL_MAX_COLUMNS; 1412 available_width = columns * display->char_width; 1413 width = available_width + GUAC_TERMINAL_SCROLLBAR_WIDTH; 1414 } 1415 1416 /* Set pixel sizes */ 1417 terminal->width = width; 1418 terminal->height = height; 1419 1420 /* Resize default layer to given pixel dimensions */ 1421 guac_terminal_repaint_default_layer(terminal, client->socket); 1422 1423 /* Resize terminal if row/column dimensions have changed */ 1424 if (columns != terminal->term_width || rows != terminal->term_height) { 1425 1426 guac_client_log(client, GUAC_LOG_DEBUG, 1427 "Resizing terminal to %ix%i", rows, columns); 1428 1429 /* Resize terminal */ 1430 __guac_terminal_resize(terminal, columns, rows); 1431 1432 /* Reset scroll region */ 1433 terminal->scroll_end = rows - 1; 1434 1435 } 1436 1437 /* Notify scrollbar of resize */ 1438 guac_terminal_scrollbar_parent_resized(terminal->scrollbar, width, height, rows); 1439 guac_terminal_scrollbar_set_bounds(terminal->scrollbar, 1440 -guac_terminal_get_available_scroll(terminal), 0); 1441 1442 1443 /* Release terminal */ 1444 guac_terminal_unlock(terminal); 1445 1446 guac_terminal_notify(terminal); 1447 return 0; 1448 1449} 1450 1451void guac_terminal_flush(guac_terminal* terminal) { 1452 1453 /* Flush typescript if in use */ 1454 if (terminal->typescript != NULL) 1455 guac_terminal_typescript_flush(terminal->typescript); 1456 1457 /* Flush pipe stream if automatic flushing is enabled */ 1458 if (terminal->pipe_stream_flags & GUAC_TERMINAL_PIPE_AUTOFLUSH) 1459 guac_terminal_pipe_stream_flush(terminal); 1460 1461 /* Flush display state */ 1462 guac_terminal_select_redraw(terminal); 1463 guac_terminal_commit_cursor(terminal); 1464 guac_terminal_display_flush(terminal->display); 1465 guac_terminal_scrollbar_flush(terminal->scrollbar); 1466 1467} 1468 1469void guac_terminal_lock(guac_terminal* terminal) { 1470 pthread_mutex_lock(&(terminal->lock)); 1471} 1472 1473void guac_terminal_unlock(guac_terminal* terminal) { 1474 pthread_mutex_unlock(&(terminal->lock)); 1475} 1476 1477int guac_terminal_send_data(guac_terminal* term, const char* data, int length) { 1478 1479 /* Block all other sources of input if input is coming from a stream */ 1480 if (term->input_stream != NULL) 1481 return 0; 1482 1483 return guac_terminal_write_all(term->stdin_pipe_fd[1], data, length); 1484 1485} 1486 1487int guac_terminal_send_string(guac_terminal* term, const char* data) { 1488 1489 /* Block all other sources of input if input is coming from a stream */ 1490 if (term->input_stream != NULL) 1491 return 0; 1492 1493 return guac_terminal_write_all(term->stdin_pipe_fd[1], data, strlen(data)); 1494 1495} 1496 1497static int __guac_terminal_send_key(guac_terminal* term, int keysym, int pressed) { 1498 1499 /* Ignore user input if terminal is not started */ 1500 if (!term->started) { 1501 guac_client_log(term->client, GUAC_LOG_DEBUG, "Ignoring user input " 1502 "while terminal has not yet started."); 1503 return 0; 1504 } 1505 1506 /* Hide mouse cursor if not already hidden */ 1507 if (term->current_cursor != GUAC_TERMINAL_CURSOR_BLANK) { 1508 term->current_cursor = GUAC_TERMINAL_CURSOR_BLANK; 1509 guac_common_cursor_set_blank(term->cursor); 1510 guac_terminal_notify(term); 1511 } 1512 1513 /* Track modifiers */ 1514 if (keysym == 0xFFE3) 1515 term->mod_ctrl = pressed; 1516 else if (keysym == 0xFFE9) 1517 term->mod_alt = pressed; 1518 else if (keysym == 0xFFE1) 1519 term->mod_shift = pressed; 1520 1521 /* If key pressed */ 1522 else if (pressed) { 1523 1524 /* Ctrl+Shift+V shortcut for paste */ 1525 if (keysym == 'V' && term->mod_ctrl) 1526 return guac_terminal_send_data(term, term->clipboard->buffer, term->clipboard->length); 1527 1528 /* Shift+PgUp / Shift+PgDown shortcuts for scrolling */ 1529 if (term->mod_shift) { 1530 1531 /* Page up */ 1532 if (keysym == 0xFF55) { 1533 guac_terminal_scroll_display_up(term, term->term_height); 1534 return 0; 1535 } 1536 1537 /* Page down */ 1538 if (keysym == 0xFF56) { 1539 guac_terminal_scroll_display_down(term, term->term_height); 1540 return 0; 1541 } 1542 1543 } 1544 1545 /* Reset scroll */ 1546 if (term->scroll_offset != 0) 1547 guac_terminal_scroll_display_down(term, term->scroll_offset); 1548 1549 /* If alt being held, also send escape character */ 1550 if (term->mod_alt) 1551 guac_terminal_send_string(term, "\x1B"); 1552 1553 /* Translate Ctrl+letter to control code */ 1554 if (term->mod_ctrl) { 1555 1556 char data; 1557 1558 /* Keysyms for '@' through '_' are all conveniently in C0 order */ 1559 if (keysym >= '@' && keysym <= '_') 1560 data = (char) (keysym - '@'); 1561 1562 /* Handle lowercase as well */ 1563 else if (keysym >= 'a' && keysym <= 'z') 1564 data = (char) (keysym - 'a' + 1); 1565 1566 /* Ctrl+? is DEL (0x7f) */ 1567 else if (keysym == '?') 1568 data = 0x7F; 1569 1570 /* Map Ctrl+2 to same result as Ctrl+@ */ 1571 else if (keysym == '2') 1572 data = 0x00; 1573 1574 /* Map Ctrl+3 through Ctrl-7 to the remaining C0 characters such that Ctrl+6 is the same as Ctrl+^ */ 1575 else if (keysym >= '3' && keysym <= '7') 1576 data = (char) (keysym - '3' + 0x1B); 1577 1578 /* Otherwise ignore */ 1579 else 1580 return 0; 1581 1582 return guac_terminal_send_data(term, &data, 1); 1583 1584 } 1585 1586 /* Translate Unicode to UTF-8 */ 1587 else if ((keysym >= 0x00 && keysym <= 0xFF) || ((keysym & 0xFFFF0000) == 0x01000000)) { 1588 1589 int length; 1590 char data[5]; 1591 1592 length = guac_terminal_encode_utf8(keysym & 0xFFFF, data); 1593 return guac_terminal_send_data(term, data, length); 1594 1595 } 1596 1597 /* Typeable keys of number pad */ 1598 else if (keysym >= 0xFFAA && keysym <= 0xFFB9) { 1599 char value = keysym - 0xFF80; 1600 guac_terminal_send_data(term, &value, sizeof(value)); 1601 } 1602 1603 /* Non-printable keys */ 1604 else { 1605 1606 /* Backspace can vary based on configuration of terminal by client. */ 1607 if (keysym == 0xFF08) { 1608 char backspace_str[] = { term->backspace, '\0' }; 1609 return guac_terminal_send_string(term, backspace_str); 1610 } 1611 if (keysym == 0xFF09 || keysym == 0xFF89) return guac_terminal_send_string(term, "\x09"); /* Tab */ 1612 if (keysym == 0xFF0D || keysym == 0xFF8D) return guac_terminal_send_string(term, "\x0D"); /* Enter */ 1613 if (keysym == 0xFF1B) return guac_terminal_send_string(term, "\x1B"); /* Esc */ 1614 1615 if (keysym == 0xFF50 || keysym == 0xFF95) return guac_terminal_send_string(term, "\x1B[1~"); /* Home */ 1616 1617 /* Arrow keys w/ application cursor */ 1618 if (term->application_cursor_keys) { 1619 if (keysym == 0xFF51 || keysym == 0xFF96) return guac_terminal_send_string(term, "\x1BOD"); /* Left */ 1620 if (keysym == 0xFF52 || keysym == 0xFF97) return guac_terminal_send_string(term, "\x1BOA"); /* Up */ 1621 if (keysym == 0xFF53 || keysym == 0xFF98) return guac_terminal_send_string(term, "\x1BOC"); /* Right */ 1622 if (keysym == 0xFF54 || keysym == 0xFF99) return guac_terminal_send_string(term, "\x1BOB"); /* Down */ 1623 } 1624 else { 1625 if (keysym == 0xFF51 || keysym == 0xFF96) return guac_terminal_send_string(term, "\x1B[D"); /* Left */ 1626 if (keysym == 0xFF52 || keysym == 0xFF97) return guac_terminal_send_string(term, "\x1B[A"); /* Up */ 1627 if (keysym == 0xFF53 || keysym == 0xFF98) return guac_terminal_send_string(term, "\x1B[C"); /* Right */ 1628 if (keysym == 0xFF54 || keysym == 0xFF99) return guac_terminal_send_string(term, "\x1B[B"); /* Down */ 1629 } 1630 1631 if (keysym == 0xFF55 || keysym == 0xFF9A) return guac_terminal_send_string(term, "\x1B[5~"); /* Page up */ 1632 if (keysym == 0xFF56 || keysym == 0xFF9B) return guac_terminal_send_string(term, "\x1B[6~"); /* Page down */ 1633 if (keysym == 0xFF57 || keysym == 0xFF9C) return guac_terminal_send_string(term, "\x1B[4~"); /* End */ 1634 1635 if (keysym == 0xFF63 || keysym == 0xFF9E) return guac_terminal_send_string(term, "\x1B[2~"); /* Insert */ 1636 1637 if (keysym == 0xFFBE || keysym == 0xFF91) return guac_terminal_send_string(term, "\x1B[[A"); /* F1 */ 1638 if (keysym == 0xFFBF || keysym == 0xFF92) return guac_terminal_send_string(term, "\x1B[[B"); /* F2 */ 1639 if (keysym == 0xFFC0 || keysym == 0xFF93) return guac_terminal_send_string(term, "\x1B[[C"); /* F3 */ 1640 if (keysym == 0xFFC1 || keysym == 0xFF94) return guac_terminal_send_string(term, "\x1B[[D"); /* F4 */ 1641 if (keysym == 0xFFC2) return guac_terminal_send_string(term, "\x1B[[E"); /* F5 */ 1642 1643 if (keysym == 0xFFC3) return guac_terminal_send_string(term, "\x1B[17~"); /* F6 */ 1644 if (keysym == 0xFFC4) return guac_terminal_send_string(term, "\x1B[18~"); /* F7 */ 1645 if (keysym == 0xFFC5) return guac_terminal_send_string(term, "\x1B[19~"); /* F8 */ 1646 if (keysym == 0xFFC6) return guac_terminal_send_string(term, "\x1B[20~"); /* F9 */ 1647 if (keysym == 0xFFC7) return guac_terminal_send_string(term, "\x1B[21~"); /* F10 */ 1648 if (keysym == 0xFFC8) return guac_terminal_send_string(term, "\x1B[22~"); /* F11 */ 1649 if (keysym == 0xFFC9) return guac_terminal_send_string(term, "\x1B[23~"); /* F12 */ 1650 1651 if (keysym == 0xFFFF || keysym == 0xFF9F) return guac_terminal_send_string(term, "\x1B[3~"); /* Delete */ 1652 1653 /* Ignore unknown keys */ 1654 guac_client_log(term->client, GUAC_LOG_DEBUG, 1655 "Ignoring unknown keysym: 0x%X", keysym); 1656 } 1657 1658 } 1659 1660 return 0; 1661 1662} 1663 1664int guac_terminal_send_key(guac_terminal* term, int keysym, int pressed) { 1665 1666 int result; 1667 1668 guac_terminal_lock(term); 1669 result = __guac_terminal_send_key(term, keysym, pressed); 1670 guac_terminal_unlock(term); 1671 1672 return result; 1673 1674} 1675 1676static int __guac_terminal_send_mouse(guac_terminal* term, guac_user* user, 1677 int x, int y, int mask) { 1678 1679 /* Ignore user input if terminal is not started */ 1680 if (!term->started) { 1681 guac_client_log(term->client, GUAC_LOG_DEBUG, "Ignoring user input " 1682 "while terminal has not yet started."); 1683 return 0; 1684 } 1685 1686 /* Determine which buttons were just released and pressed */ 1687 int released_mask = term->mouse_mask & ~mask; 1688 int pressed_mask = ~term->mouse_mask & mask; 1689 1690 /* Store current mouse location/state */ 1691 guac_common_cursor_update(term->cursor, user, x, y, mask); 1692 1693 /* Notify scrollbar, do not handle anything handled by scrollbar */ 1694 if (guac_terminal_scrollbar_handle_mouse(term->scrollbar, x, y, mask)) { 1695 1696 /* Set pointer cursor if mouse is over scrollbar */ 1697 if (term->current_cursor != GUAC_TERMINAL_CURSOR_POINTER) { 1698 term->current_cursor = GUAC_TERMINAL_CURSOR_POINTER; 1699 guac_common_cursor_set_pointer(term->cursor); 1700 guac_terminal_notify(term); 1701 } 1702 1703 guac_terminal_notify(term); 1704 return 0; 1705 1706 } 1707 1708 term->mouse_mask = mask; 1709 1710 /* Show mouse cursor if not already shown */ 1711 if (term->current_cursor != GUAC_TERMINAL_CURSOR_IBAR) { 1712 term->current_cursor = GUAC_TERMINAL_CURSOR_IBAR; 1713 guac_common_cursor_set_ibar(term->cursor); 1714 guac_terminal_notify(term); 1715 } 1716 1717 /* Paste contents of clipboard on right or middle mouse button up */ 1718 if ((released_mask & GUAC_CLIENT_MOUSE_RIGHT) || (released_mask & GUAC_CLIENT_MOUSE_MIDDLE)) 1719 return guac_terminal_send_data(term, term->clipboard->buffer, term->clipboard->length); 1720 1721 /* If left mouse button was just released, stop selection */ 1722 if (released_mask & GUAC_CLIENT_MOUSE_LEFT) 1723 guac_terminal_select_end(term); 1724 1725 /* Update selection state contextually while the left mouse button is 1726 * pressed */ 1727 else if (mask & GUAC_CLIENT_MOUSE_LEFT) { 1728 1729 int row = y / term->display->char_height - term->scroll_offset; 1730 int col = x / term->display->char_width; 1731 1732 /* If mouse button was already just pressed, start a new selection or 1733 * resume the existing selection depending on whether shift is held */ 1734 if (pressed_mask & GUAC_CLIENT_MOUSE_LEFT) { 1735 if (term->mod_shift) 1736 guac_terminal_select_resume(term, row, col); 1737 else 1738 guac_terminal_select_start(term, row, col); 1739 } 1740 1741 /* In all other cases, simply update the existing selection as long as 1742 * the mouse button is pressed */ 1743 else 1744 guac_terminal_select_update(term, row, col); 1745 1746 } 1747 1748 /* Scroll up if wheel moved up */ 1749 if (released_mask & GUAC_CLIENT_MOUSE_SCROLL_UP) 1750 guac_terminal_scroll_display_up(term, GUAC_TERMINAL_WHEEL_SCROLL_AMOUNT); 1751 1752 /* Scroll down if wheel moved down */ 1753 if (released_mask & GUAC_CLIENT_MOUSE_SCROLL_DOWN) 1754 guac_terminal_scroll_display_down(term, GUAC_TERMINAL_WHEEL_SCROLL_AMOUNT); 1755 1756 return 0; 1757 1758} 1759 1760int guac_terminal_send_mouse(guac_terminal* term, guac_user* user, 1761 int x, int y, int mask) { 1762 1763 int result; 1764 1765 guac_terminal_lock(term); 1766 result = __guac_terminal_send_mouse(term, user, x, y, mask); 1767 guac_terminal_unlock(term); 1768 1769 return result; 1770 1771} 1772 1773void guac_terminal_scroll_handler(guac_terminal_scrollbar* scrollbar, int value) { 1774 1775 guac_terminal* terminal = (guac_terminal*) scrollbar->data; 1776 1777 /* Calculate change in scroll offset */ 1778 int delta = -value - terminal->scroll_offset; 1779 1780 /* Update terminal based on change in scroll offset */ 1781 if (delta < 0) 1782 guac_terminal_scroll_display_down(terminal, -delta); 1783 else if (delta > 0) 1784 guac_terminal_scroll_display_up(terminal, delta); 1785 1786 /* Update scrollbar value */ 1787 guac_terminal_scrollbar_set_value(scrollbar, value); 1788 1789} 1790 1791int guac_terminal_sendf(guac_terminal* term, const char* format, ...) { 1792 1793 int written; 1794 1795 va_list ap; 1796 char buffer[1024]; 1797 1798 /* Block all other sources of input if input is coming from a stream */ 1799 if (term->input_stream != NULL) 1800 return 0; 1801 1802 /* Print to buffer */ 1803 va_start(ap, format); 1804 written = vsnprintf(buffer, sizeof(buffer)-1, format, ap); 1805 va_end(ap); 1806 1807 if (written < 0) 1808 return written; 1809 1810 /* Write to STDIN */ 1811 return guac_terminal_write_all(term->stdin_pipe_fd[1], buffer, written); 1812 1813} 1814 1815void guac_terminal_set_tab(guac_terminal* term, int column) { 1816 1817 int i; 1818 1819 /* Search for available space, set if available */ 1820 for (i=0; i<GUAC_TERMINAL_MAX_TABS; i++) { 1821 1822 /* Set tab if space free */ 1823 if (term->custom_tabs[i] == 0) { 1824 term->custom_tabs[i] = column+1; 1825 break; 1826 } 1827 1828 } 1829 1830} 1831 1832void guac_terminal_unset_tab(guac_terminal* term, int column) { 1833 1834 int i; 1835 1836 /* Search for given tab, unset if found */ 1837 for (i=0; i<GUAC_TERMINAL_MAX_TABS; i++) { 1838 1839 /* Unset tab if found */ 1840 if (term->custom_tabs[i] == column+1) { 1841 term->custom_tabs[i] = 0; 1842 break; 1843 } 1844 1845 } 1846 1847} 1848 1849void guac_terminal_clear_tabs(guac_terminal* term) { 1850 term->tab_interval = 0; 1851 memset(term->custom_tabs, 0, sizeof(term->custom_tabs)); 1852} 1853 1854int guac_terminal_next_tab(guac_terminal* term, int column) { 1855 1856 int i; 1857 1858 /* Determine tab stop from interval */ 1859 int tabstop; 1860 if (term->tab_interval != 0) 1861 tabstop = (column / term->tab_interval + 1) * term->tab_interval; 1862 else 1863 tabstop = term->term_width - 1; 1864 1865 /* Walk custom tabs, trying to find an earlier occurrence */ 1866 for (i=0; i<GUAC_TERMINAL_MAX_TABS; i++) { 1867 1868 int custom_tabstop = term->custom_tabs[i] - 1; 1869 if (custom_tabstop != -1 && custom_tabstop > column && custom_tabstop < tabstop) 1870 tabstop = custom_tabstop; 1871 1872 } 1873 1874 return tabstop; 1875} 1876 1877void guac_terminal_pipe_stream_open(guac_terminal* term, const char* name, 1878 int flags) { 1879 1880 guac_client* client = term->client; 1881 guac_socket* socket = client->socket; 1882 1883 /* Close existing stream, if any */ 1884 guac_terminal_pipe_stream_close(term); 1885 1886 /* Allocate and assign new pipe stream */ 1887 term->pipe_stream = guac_client_alloc_stream(client); 1888 term->pipe_buffer_length = 0; 1889 term->pipe_stream_flags = flags; 1890 1891 /* Open new pipe stream */ 1892 guac_protocol_send_pipe(socket, term->pipe_stream, "text/plain", name); 1893 1894 /* Log redirect at debug level */ 1895 guac_client_log(client, GUAC_LOG_DEBUG, "Terminal output now directed to " 1896 "pipe \"%s\" (flags=%i).", name, flags); 1897 1898} 1899 1900void guac_terminal_pipe_stream_write(guac_terminal* term, char c) { 1901 1902 /* Append byte to buffer only if pipe is open */ 1903 if (term->pipe_stream != NULL) { 1904 1905 /* Flush buffer if no space is available */ 1906 if (term->pipe_buffer_length == sizeof(term->pipe_buffer)) 1907 guac_terminal_pipe_stream_flush(term); 1908 1909 /* Append single byte to buffer */ 1910 term->pipe_buffer[term->pipe_buffer_length++] = c; 1911 1912 } 1913 1914} 1915 1916void guac_terminal_pipe_stream_flush(guac_terminal* term) { 1917 1918 guac_client* client = term->client; 1919 guac_socket* socket = client->socket; 1920 guac_stream* pipe_stream = term->pipe_stream; 1921 1922 /* Write blob if data exists in buffer */ 1923 if (pipe_stream != NULL && term->pipe_buffer_length > 0) { 1924 guac_protocol_send_blob(socket, pipe_stream, 1925 term->pipe_buffer, term->pipe_buffer_length); 1926 term->pipe_buffer_length = 0; 1927 } 1928 1929} 1930 1931void guac_terminal_pipe_stream_close(guac_terminal* term) { 1932 1933 guac_client* client = term->client; 1934 guac_socket* socket = client->socket; 1935 guac_stream* pipe_stream = term->pipe_stream; 1936 1937 /* Close any existing pipe */ 1938 if (pipe_stream != NULL) { 1939 1940 /* Write end of stream */ 1941 guac_terminal_pipe_stream_flush(term); 1942 guac_protocol_send_end(socket, pipe_stream); 1943 1944 /* Destroy stream */ 1945 guac_client_free_stream(client, pipe_stream); 1946 term->pipe_stream = NULL; 1947 1948 /* Log redirect at debug level */ 1949 guac_client_log(client, GUAC_LOG_DEBUG, 1950 "Terminal output now redirected to display."); 1951 1952 } 1953 1954} 1955 1956int guac_terminal_create_typescript(guac_terminal* term, const char* path, 1957 const char* name, int create_path) { 1958 1959 /* Create typescript */ 1960 term->typescript = guac_terminal_typescript_alloc(path, name, create_path); 1961 1962 /* Log failure */ 1963 if (term->typescript == NULL) { 1964 guac_client_log(term->client, GUAC_LOG_ERROR, 1965 "Creation of typescript failed: %s", strerror(errno)); 1966 return 1; 1967 } 1968 1969 /* If typescript was successfully created, log filenames */ 1970 guac_client_log(term->client, GUAC_LOG_INFO, 1971 "Typescript of terminal session will be saved to \"%s\". " 1972 "Timing file is \"%s\".", 1973 term->typescript->data_filename, 1974 term->typescript->timing_filename); 1975 1976 /* Typescript creation succeeded */ 1977 return 0; 1978 1979} 1980 1981/** 1982 * Synchronize the state of the provided terminal to a subset of users of 1983 * the provided guac_client using the provided socket. 1984 * 1985 * @param client 1986 * The client whose users should be synchronized. 1987 * 1988 * @param term 1989 * The terminal state that should be synchronized to the users. 1990 * 1991 * @param socket 1992 * The socket that should be used to communicate with the users. 1993 */ 1994static void __guac_terminal_sync_socket( 1995 guac_client* client, guac_terminal* term, guac_socket* socket) { 1996 1997 /* Synchronize display state with new user */ 1998 guac_terminal_repaint_default_layer(term, socket); 1999 guac_terminal_display_dup(term->display, client, socket); 2000 2001 /* Synchronize mouse cursor */ 2002 guac_common_cursor_dup(term->cursor, client, socket); 2003 2004 /* Paint scrollbar for joining users */ 2005 guac_terminal_scrollbar_dup(term->scrollbar, client, socket); 2006 2007} 2008 2009void guac_terminal_dup(guac_terminal* term, guac_user* user, 2010 guac_socket* socket) { 2011 2012 /* Ignore the user and just use the provided socket directly */ 2013 __guac_terminal_sync_socket(user->client, term, socket); 2014 2015} 2016 2017void guac_terminal_sync_users( 2018 guac_terminal* term, guac_client* client, guac_socket* socket) { 2019 2020 /* Use the provided socket to synchronize state to the users */ 2021 __guac_terminal_sync_socket(client, term, socket); 2022 2023} 2024 2025void guac_terminal_apply_color_scheme(guac_terminal* terminal, 2026 const char* color_scheme) { 2027 2028 guac_client* client = terminal->client; 2029 guac_terminal_char* default_char = &terminal->default_char; 2030 guac_terminal_display* display = terminal->display; 2031 2032 /* Reinitialize default terminal colors with values from color scheme */ 2033 guac_terminal_parse_color_scheme(client, color_scheme, 2034 &default_char->attributes.foreground, 2035 &default_char->attributes.background, 2036 display->default_palette); 2037 2038 /* Reinitialize default attributes of buffer and display */ 2039 guac_terminal_display_reset_palette(display); 2040 display->default_foreground = default_char->attributes.foreground; 2041 display->default_background = default_char->attributes.background; 2042 2043 /* Redraw terminal text and background */ 2044 guac_terminal_repaint_default_layer(terminal, client->socket); 2045 __guac_terminal_redraw_rect(terminal, 0, 0, 2046 terminal->term_height - 1, 2047 terminal->term_width - 1); 2048 2049 /* Acquire exclusive access to terminal */ 2050 guac_terminal_lock(terminal); 2051 2052 /* Update stored copy of color scheme */ 2053 guac_mem_free_const(terminal->color_scheme); 2054 terminal->color_scheme = guac_strdup(color_scheme); 2055 2056 /* Release terminal */ 2057 guac_terminal_unlock(terminal); 2058 2059 guac_terminal_notify(terminal); 2060 2061} 2062 2063const char* guac_terminal_get_color_scheme(guac_terminal* terminal) { 2064 return terminal->color_scheme; 2065} 2066 2067void guac_terminal_apply_font(guac_terminal* terminal, const char* font_name, 2068 int font_size, int dpi) { 2069 2070 guac_client* client = terminal->client; 2071 guac_terminal_display* display = terminal->display; 2072 2073 if (guac_terminal_display_set_font(display, font_name, font_size, dpi)) 2074 return; 2075 2076 /* Resize terminal to fit available region, now that font metrics may be 2077 * different */ 2078 guac_terminal_resize(terminal, terminal->outer_width, 2079 terminal->outer_height); 2080 2081 /* Redraw terminal text and background */ 2082 guac_terminal_repaint_default_layer(terminal, client->socket); 2083 __guac_terminal_redraw_rect(terminal, 0, 0, 2084 terminal->term_height - 1, 2085 terminal->term_width - 1); 2086 2087 /* Acquire exclusive access to terminal */ 2088 guac_terminal_lock(terminal); 2089 2090 /* Update stored copy of font name, if changed */ 2091 if (font_name != NULL) 2092 terminal->font_name = guac_strdup(font_name); 2093 2094 /* Update stored copy of font size, if changed */ 2095 if (font_size != -1) 2096 terminal->font_size = font_size; 2097 2098 /* Release terminal */ 2099 guac_terminal_unlock(terminal); 2100 2101 guac_terminal_notify(terminal); 2102 2103} 2104 2105void guac_terminal_set_upload_path_handler(guac_terminal* terminal, 2106 guac_terminal_upload_path_handler* upload_path_handler) { 2107 terminal->upload_path_handler = upload_path_handler; 2108} 2109 2110void guac_terminal_set_file_download_handler(guac_terminal* terminal, 2111 guac_terminal_file_download_handler* file_download_handler) { 2112 terminal->file_download_handler = file_download_handler; 2113} 2114 2115const char* guac_terminal_get_font_name(guac_terminal* terminal) { 2116 return terminal->font_name; 2117} 2118 2119int guac_terminal_get_font_size(guac_terminal* terminal) { 2120 return terminal->font_size; 2121} 2122 2123int guac_terminal_get_mod_ctrl(guac_terminal* terminal) { 2124 return terminal->mod_ctrl; 2125} 2126 2127void guac_terminal_clipboard_reset(guac_terminal* terminal, 2128 const char* mimetype) { 2129 guac_common_clipboard_reset(terminal->clipboard, mimetype); 2130} 2131 2132void guac_terminal_clipboard_append(guac_terminal* terminal, 2133 const char* data, int length) { 2134 guac_common_clipboard_append(terminal->clipboard, data, length); 2135} 2136 2137void guac_terminal_remove_user(guac_terminal* terminal, guac_user* user) { 2138 2139 /* Remove the user from the terminal cursor */ 2140 guac_common_cursor_remove_user(terminal->cursor, user); 2141}