diff --git a/src/protocols/ssh/settings.c b/src/protocols/ssh/settings.c index 7cfe404f..e0be6cf3 100644 --- a/src/protocols/ssh/settings.c +++ b/src/protocols/ssh/settings.c @@ -59,6 +59,7 @@ const char* GUAC_SSH_CLIENT_ARGS[] = { "server-alive-interval", "backspace", "terminal-type", + "scrollback", NULL }; @@ -232,6 +233,11 @@ enum SSH_ARGS_IDX { */ IDX_TERMINAL_TYPE, + /** + * The maximum size of the scrollback buffer in rows. + */ + IDX_SCROLLBACK, + SSH_ARGS_COUNT }; @@ -274,6 +280,11 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user, guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, IDX_PASSPHRASE, NULL); + /* Read maximum scrollback size */ + settings->max_scrollback = + guac_user_parse_args_int(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_SCROLLBACK, GUAC_SSH_DEFAULT_MAX_SCROLLBACK); + /* Read font name */ settings->font_name = guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, diff --git a/src/protocols/ssh/settings.h b/src/protocols/ssh/settings.h index 761239c7..81dcd699 100644 --- a/src/protocols/ssh/settings.h +++ b/src/protocols/ssh/settings.h @@ -58,6 +58,11 @@ */ #define GUAC_SSH_DEFAULT_POLL_TIMEOUT 1000 +/** + * The default maximum scrollback size in rows. + */ +#define GUAC_SSH_DEFAULT_MAX_SCROLLBACK 1000 + /** * Settings for the SSH connection. The values for this structure are parsed * from the arguments given during the Guacamole protocol handshake using the @@ -115,6 +120,11 @@ typedef struct guac_ssh_settings { */ char* command; + /** + * The maximum size of the scrollback buffer in rows. + */ + int max_scrollback; + /** * The name of the font to use for display rendering. */ diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index a1162813..59554fb7 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -207,7 +207,7 @@ void* ssh_client_thread(void* data) { /* Create terminal */ ssh_client->term = guac_terminal_create(client, ssh_client->clipboard, - settings->font_name, settings->font_size, + settings->max_scrollback, settings->font_name, settings->font_size, settings->resolution, settings->width, settings->height, settings->color_scheme, settings->backspace); diff --git a/src/protocols/telnet/settings.c b/src/protocols/telnet/settings.c index 6e4a7730..bef5cfae 100644 --- a/src/protocols/telnet/settings.c +++ b/src/protocols/telnet/settings.c @@ -52,6 +52,7 @@ const char* GUAC_TELNET_CLIENT_ARGS[] = { "read-only", "backspace", "terminal-type", + "scrollback", NULL }; @@ -190,6 +191,11 @@ enum TELNET_ARGS_IDX { */ IDX_TERMINAL_TYPE, + /** + * The maximum size of the scrollback buffer in rows. + */ + IDX_SCROLLBACK, + TELNET_ARGS_COUNT }; @@ -274,6 +280,11 @@ guac_telnet_settings* guac_telnet_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv, IDX_READ_ONLY, false); + /* Read maximum scrollback size */ + settings->max_scrollback = + guac_user_parse_args_int(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_SCROLLBACK, GUAC_TELNET_DEFAULT_MAX_SCROLLBACK); + /* Read font name */ settings->font_name = guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, diff --git a/src/protocols/telnet/settings.h b/src/protocols/telnet/settings.h index 7d8d6eee..83ec4313 100644 --- a/src/protocols/telnet/settings.h +++ b/src/protocols/telnet/settings.h @@ -67,6 +67,11 @@ */ #define GUAC_TELNET_DEFAULT_PASSWORD_REGEX "[Pp]assword:" +/** + * The default maximum scrollback size in rows. + */ +#define GUAC_TELNET_DEFAULT_MAX_SCROLLBACK 1000 + /** * Settings for the telnet connection. The values for this structure are parsed * from the arguments given during the Guacamole protocol handshake using the @@ -117,6 +122,11 @@ typedef struct guac_telnet_settings { */ bool read_only; + /** + * The maximum size of the scrollback buffer in rows. + */ + int max_scrollback; + /** * The name of the font to use for display rendering. */ diff --git a/src/protocols/telnet/telnet.c b/src/protocols/telnet/telnet.c index 93abdd4d..c9a636c2 100644 --- a/src/protocols/telnet/telnet.c +++ b/src/protocols/telnet/telnet.c @@ -479,7 +479,7 @@ void* guac_telnet_client_thread(void* data) { /* Create terminal */ telnet_client->term = guac_terminal_create(client, telnet_client->clipboard, - settings->font_name, settings->font_size, + settings->max_scrollback, settings->font_name, settings->font_size, settings->resolution, settings->width, settings->height, settings->color_scheme, settings->backspace); diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index 44477894..1c105dcf 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -149,6 +149,59 @@ static void __guac_terminal_force_break(guac_terminal* terminal, int row, int ed } +/** + * Returns the number of rows available within the terminal buffer, taking + * changes to the desired scrollback size into account. Regardless of the + * true buffer length, only the number of rows that should be made available + * will be returned. + * + * @param term + * The terminal whose effective buffer length should be retrieved. + * + * @return + * The number of rows effectively available within the terminal buffer, + * taking changes to the desired scrollback size into account. + */ +static int guac_terminal_effective_buffer_length(guac_terminal* term) { + + int scrollback = term->requested_scrollback; + + /* Limit available scrollback to defined maximum */ + if (scrollback > term->max_scrollback) + scrollback = term->max_scrollback; + + /* There must always be at least enough scrollback to cover the visible + * terminal display */ + else if (scrollback < term->term_height) + scrollback = term->term_height; + + /* If the buffer contains more rows than requested, pretend it only + * contains the requested number of rows */ + int effective_length = term->buffer->length; + if (effective_length > scrollback) + effective_length = scrollback; + + return effective_length; + +} + +/** + * Returns the number of rows within the buffer of the given terminal which are + * not currently displayed on screen. Adjustments to the desired scrollback + * size are taken into account, and rows which are within the buffer but + * unavailable due to being outside the desired scrollback range are ignored. + * + * @param term + * The terminal whose off-screen row count should be determined. + * + * @return + * The number of rows within the buffer which are not currently displayed + * on screen. + */ +static int guac_terminal_available_scroll(guac_terminal* term) { + return guac_terminal_effective_buffer_length(term) - term->term_height; +} + void guac_terminal_reset(guac_terminal* term) { int row; @@ -171,7 +224,7 @@ void guac_terminal_reset(guac_terminal* term) { term->scroll_offset = 0; /* Reset scrollbar bounds */ - guac_terminal_scrollbar_set_bounds(term->scrollbar, term->term_height - term->buffer->length, 0); + guac_terminal_scrollbar_set_bounds(term->scrollbar, 0, 0); guac_terminal_scrollbar_set_value(term->scrollbar, -term->scroll_offset); /* Reset flags */ @@ -507,7 +560,7 @@ static void guac_terminal_parse_color_scheme(guac_client* client, } guac_terminal* guac_terminal_create(guac_client* client, - guac_common_clipboard* clipboard, + guac_common_clipboard* clipboard, int max_scrollback, const char* font_name, int font_size, int dpi, int width, int height, const char* color_scheme, const int backspace) { @@ -565,8 +618,19 @@ guac_terminal* guac_terminal_create(guac_client* client, pthread_cond_init(&(term->modified_cond), NULL); pthread_mutex_init(&(term->modified_lock), NULL); + /* Maximum and requested scrollback are initially the same */ + term->max_scrollback = max_scrollback; + term->requested_scrollback = max_scrollback; + + /* Allocate enough space for maximum scrollback, bumping up internal + * storage as necessary to allow screen to be resized to maximum height */ + int initial_scrollback = max_scrollback; + if (initial_scrollback < GUAC_TERMINAL_MAX_ROWS) + initial_scrollback = GUAC_TERMINAL_MAX_ROWS; + /* Init buffer */ - term->buffer = guac_terminal_buffer_alloc(1000, &default_char); + term->buffer = guac_terminal_buffer_alloc(initial_scrollback, + &default_char); /* Init display */ term->display = guac_terminal_display_alloc(client, @@ -1031,7 +1095,8 @@ int guac_terminal_scroll_up(guac_terminal* term, term->buffer->length = term->buffer->available; /* Reset scrollbar bounds */ - guac_terminal_scrollbar_set_bounds(term->scrollbar, term->term_height - term->buffer->length, 0); + guac_terminal_scrollbar_set_bounds(term->scrollbar, + -guac_terminal_available_scroll(term), 0); /* Update cursor location if within region */ if (term->visible_cursor_row >= start_row && @@ -1241,8 +1306,9 @@ void guac_terminal_scroll_display_up(guac_terminal* terminal, int row, column; /* Limit scroll amount by size of scrollback buffer */ - if (terminal->scroll_offset + scroll_amount > terminal->buffer->length - terminal->term_height) - scroll_amount = terminal->buffer->length - terminal->scroll_offset - terminal->term_height; + int available_scroll = guac_terminal_available_scroll(terminal); + if (terminal->scroll_offset + scroll_amount > available_scroll) + scroll_amount = available_scroll - terminal->scroll_offset; /* If not scrolling at all, don't bother trying */ if (scroll_amount <= 0) @@ -1404,7 +1470,7 @@ static void __guac_terminal_resize(guac_terminal* term, int width, int height) { int shift_amount; /* Get number of rows actually occupying terminal space */ - int used_height = term->buffer->length; + int used_height = guac_terminal_effective_buffer_length(term); if (used_height > term->term_height) used_height = term->term_height; @@ -1440,16 +1506,15 @@ static void __guac_terminal_resize(guac_terminal* term, int width, int height) { if (height > term->term_height) { /* If undisplayed rows exist in the buffer, shift them into view */ - if (term->term_height < term->buffer->length) { + int available_scroll = guac_terminal_available_scroll(term); + if (available_scroll > 0) { /* If the new terminal bottom reveals N rows, shift down N rows */ int shift_amount = height - term->term_height; /* The maximum amount we can shift is the number of undisplayed rows */ - int max_shift = term->buffer->length - term->term_height; - - if (shift_amount > max_shift) - shift_amount = max_shift; + if (shift_amount > available_scroll) + shift_amount = available_scroll; /* Update buffer top and cursor row based on shift */ term->buffer->top -= shift_amount; @@ -1544,10 +1609,6 @@ int guac_terminal_resize(guac_terminal* terminal, int width, int height) { /* Resize default layer to given pixel dimensions */ guac_terminal_repaint_default_layer(terminal, client->socket); - /* Notify scrollbar of resize */ - guac_terminal_scrollbar_parent_resized(terminal->scrollbar, width, height, rows); - guac_terminal_scrollbar_set_bounds(terminal->scrollbar, rows - terminal->buffer->length, 0); - /* Resize terminal if row/column dimensions have changed */ if (columns != terminal->term_width || rows != terminal->term_height) { @@ -1562,6 +1623,12 @@ int guac_terminal_resize(guac_terminal* terminal, int width, int height) { } + /* Notify scrollbar of resize */ + guac_terminal_scrollbar_parent_resized(terminal->scrollbar, width, height, rows); + guac_terminal_scrollbar_set_bounds(terminal->scrollbar, + -guac_terminal_available_scroll(terminal), 0); + + /* Release terminal */ guac_terminal_unlock(terminal); diff --git a/src/terminal/terminal/terminal.h b/src/terminal/terminal/terminal.h index 42a741a9..d68405b5 100644 --- a/src/terminal/terminal/terminal.h +++ b/src/terminal/terminal/terminal.h @@ -282,6 +282,26 @@ struct guac_terminal { */ int scroll_offset; + /** + * The maximum number of rows to allow within the terminal buffer. Note + * that while this value is traditionally referred to as the scrollback + * size, it actually encompasses both the display and the off-screen + * region. The terminal will ensure enough buffer space is allocated for + * the on-screen rows, even if this exceeds the defined maximum, however + * additional rows for off-screen data will only be available if the + * display is smaller than this value. + */ + int max_scrollback; + + /** + * The number of rows that the user has requested be avalable within the + * terminal buffer. This value may be adjusted by the user while the + * terminal is running through console codes, and will adjust the number + * of rows available within the terminal buffer, subject to the maximum + * defined at terminal creation and stored within max_scrollback. + */ + int requested_scrollback; + /** * The width of the terminal, in pixels. */ @@ -518,6 +538,16 @@ struct guac_terminal { * clipboard instructions. This clipboard will not be automatically * freed when this terminal is freed. * + * @param max_scrollback + * The maximum number of rows to allow within the scrollback buffer. The + * user may still alter the size of the scrollback buffer using terminal + * codes, however the size can never exceed the maximum size given here. + * Note that this space is shared with the display, with the scrollable + * area actually only containing the given number of rows less the number + * of rows currently displayed, and sufficient buffer space will always be + * allocated to represent the display area of the terminal regardless of + * the value given here. + * * @param font_name * The name of the font to use when rendering glyphs. * @@ -550,7 +580,7 @@ struct guac_terminal { * which renders all text to the given client. */ guac_terminal* guac_terminal_create(guac_client* client, - guac_common_clipboard* clipboard, + guac_common_clipboard* clipboard, int max_scrollback, const char* font_name, int font_size, int dpi, int width, int height, const char* color_scheme, const int backspace);