cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

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}