diff --git a/protocols/ssh/include/terminal.h b/protocols/ssh/include/terminal.h index 32bd7596..13a54165 100644 --- a/protocols/ssh/include/terminal.h +++ b/protocols/ssh/include/terminal.h @@ -207,6 +207,63 @@ typedef struct guac_terminal_delta { } guac_terminal_delta; +/** + * A single variable-length row of terminal data. + */ +typedef struct guac_terminal_scrollback_row { + + /** + * Array of guac_terminal_char representing the contents of the row. + */ + guac_terminal_char* characters; + + /** + * The length of this row in characters. This is the number of initialized + * characters in the buffer, usually equal to the number of characters + * in the screen width at the time this row was created. + */ + int length; + + /** + * The number of elements in the characters array. After the length + * equals this value, the array must be resized. + */ + int available; + +} guac_terminal_scrollback_row; + +/** + * A scrollback buffer containing a constant number of arbitrary-length rows. + * New rows can be appended to the buffer, with the oldest row replaced with + * the new row. + */ +typedef struct guac_terminal_scrollback_buffer { + + /** + * Array of scrollback buffer rows. This array functions as a ring buffer. + * When a new row needs to be appended, the top reference is moved down + * and the old top row is replaced. + */ + guac_terminal_scrollback_row* scrollback; + + /** + * The number of rows in the scrollback buffer. This is the total capacity + * of the buffer. + */ + int rows; + + /** + * The row to replace when adding a new row to the scrollback. + */ + int top; + + /** + * The number of rows currently stored in the scrollback buffer. + */ + int length; + +} guac_terminal_scrollback_buffer; + /** * Dynamically-resizable character buffer. */ @@ -278,10 +335,16 @@ struct guac_terminal { guac_layer* filled_glyphs; /** - * Array of scrollback buffer rows, where each row is an array of - * characters. + * The scrollback buffer. */ - guac_terminal_char** scrollback; + guac_terminal_scrollback_buffer* scrollback; + + /** + * The relative offset of the display. A positive value indicates that + * many rows have been scrolled into view, zero indicates that no + * scrolling has occurred. Negative values are illegal. + */ + int scroll_offset; /** * The width of each character, in pixels. @@ -501,5 +564,37 @@ void guac_terminal_buffer_set_rect(guac_terminal_buffer* buffer, */ void guac_terminal_buffer_free(guac_terminal_buffer* buffer); +/** + * Allocates a new scrollback buffer having the given number of rows. + */ +guac_terminal_scrollback_buffer* + guac_terminal_scrollback_buffer_alloc(int rows); + +/** + * Frees the given scrollback buffer. + */ +void guac_terminal_scrollback_buffer_free( + guac_terminal_scrollback_buffer* buffer); + +/** + * Pushes the given number of rows into the scrollback, maintaining display + * position within the scrollback as possible. + */ +void guac_terminal_scrollback_buffer_append( + guac_terminal_scrollback_buffer* buffer, + guac_terminal* terminal, int rows); + +/** + * Scroll down the display by one row, replacing the new space with data from + * the scrollback. + */ +void guac_terminal_scroll_display_down(guac_terminal* terminal); + +/** + * Scroll up the display by one row, replacing the new space with data from + * either the scrollback or the terminal buffer. + */ +void guac_terminal_scroll_display_up(guac_terminal* terminal); + #endif diff --git a/protocols/ssh/src/ssh_handlers.c b/protocols/ssh/src/ssh_handlers.c index 5c491d47..89e6922e 100644 --- a/protocols/ssh/src/ssh_handlers.c +++ b/protocols/ssh/src/ssh_handlers.c @@ -138,6 +138,7 @@ int ssh_guac_client_clipboard_handler(guac_client* client, char* data) { int ssh_guac_client_mouse_handler(guac_client* client, int x, int y, int mask) { ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; + guac_terminal* term = client_data->term; /* Determine which buttons were just released */ int released_mask = client_data->mouse_mask & ~mask; @@ -156,14 +157,12 @@ int ssh_guac_client_mouse_handler(guac_client* client, int x, int y, int mask) { /* Scroll up if wheel moved up */ if (released_mask & GUAC_CLIENT_MOUSE_SCROLL_UP) { - /* STUB */ - guac_client_log_info(client, "stub: scroll up"); + guac_terminal_scroll_display_up(term); } /* Scroll down if wheel moved down */ if (released_mask & GUAC_CLIENT_MOUSE_SCROLL_DOWN) { - /* STUB */ - guac_client_log_info(client, "stub: scroll down"); + guac_terminal_scroll_display_down(term); } return 0; diff --git a/protocols/ssh/src/terminal.c b/protocols/ssh/src/terminal.c index 7faddc88..dc32bbd0 100644 --- a/protocols/ssh/src/terminal.c +++ b/protocols/ssh/src/terminal.c @@ -84,8 +84,6 @@ guac_terminal* guac_terminal_create(guac_client* client, .underscore = false }; - int row, col; - PangoFontMap* font_map; PangoFont* font; PangoFontMetrics* metrics; @@ -142,26 +140,9 @@ guac_terminal* guac_terminal_create(guac_client* client, term->scroll_start = 0; term->scroll_end = term->term_height - 1; - /* Create scrollback buffer */ - term->scrollback = malloc(term->term_height * sizeof(guac_terminal_char*)); - - /* Init buffer */ - for (row = 0; row < term->term_height; row++) { - - /* Create row */ - guac_terminal_char* current_row = - term->scrollback[row] = malloc(term->term_width * sizeof(guac_terminal_char)); - - /* Init row */ - for (col = 0; col < term->term_width; col++) { - - /* Empty character, default colors */ - current_row[col].value = '\0'; - current_row[col].attributes = term->default_attributes; - - } - - } + /* Init scrollback buffer */ + term->scrollback = guac_terminal_scrollback_buffer_alloc(1000); + term->scroll_offset = 0; /* Init delta */ term->delta = guac_terminal_delta_alloc(term->term_width, @@ -182,10 +163,7 @@ guac_terminal* guac_terminal_create(guac_client* client, void guac_terminal_free(guac_terminal* term) { /* Free scrollback buffer */ - for (int row = 0; row < term->term_height; row++) - free(term->scrollback[row]); - - free(term->scrollback); + guac_terminal_scrollback_buffer_free(term->scrollback); /* Free delta */ guac_terminal_delta_free(term->delta); @@ -467,26 +445,8 @@ int guac_terminal_scroll_up(guac_terminal* term, int height = end_row - start_row + 1; /* If scroll region is entire screen, push rows into scrollback */ - if (start_row == 0 && end_row == term->term_height-1) { - - /* STUB: Test, for sake of logging */ - char test_str[1024]; - int column; - - /* Generate test string */ - guac_terminal_char* current = term->buffer->characters; - for (column=0; column < term->buffer->width; column++) { - test_str[column] = current->value; - current++; - } - test_str[column] = 0; - - /* Log string version of row that WOULD have been scrolled into the - * scrollback */ - guac_client_log_info(term->client, - "scroll: %s", test_str); - - } + if (start_row == 0 && end_row == term->term_height-1) + guac_terminal_scrollback_buffer_append(term->scrollback, term, amount); return @@ -630,11 +590,16 @@ void guac_terminal_delta_copy(guac_terminal_delta* delta, /* FIXME: Handle intersections between src and dst rects */ + guac_terminal_operation* src_copy = malloc( + sizeof(guac_terminal_operation) * delta->width * delta->height); + memcpy(src_copy, delta->operations, + sizeof(guac_terminal_operation) * delta->width * delta->height); + guac_terminal_operation* current_row = &(delta->operations[dst_row*delta->width + dst_column]); guac_terminal_operation* src_current_row = - &(delta->operations[src_row*delta->width + src_column]); + &(src_copy[src_row*delta->width + src_column]); /* Set rectangle to copy operations */ for (row=0; rowrows = rows; + buffer->top = 0; + buffer->length = 0; + buffer->scrollback = malloc(sizeof(guac_terminal_scrollback_row) * + buffer->rows); + + /* Init scrollback rows */ + row = buffer->scrollback; + for (i=0; iavailable = 256; + row->length = 0; + row->characters = malloc(sizeof(guac_terminal_char) * row->available); + + /* Next row */ + row++; + + } + + return buffer; + +} + +void guac_terminal_scrollback_buffer_free( + guac_terminal_scrollback_buffer* buffer) { + + int i; + guac_terminal_scrollback_row* row = buffer->scrollback; + + /* Free all rows */ + for (i=0; irows; i++) { + free(row->characters); + row++; + } + + /* Free actual buffer */ + free(buffer->scrollback); + free(buffer); + +} + +void guac_terminal_scrollback_buffer_append( + guac_terminal_scrollback_buffer* buffer, + guac_terminal* terminal, int rows) { + + int row, column; + + /* Copy data into scrollback */ + guac_terminal_scrollback_row* scrollback_row = + &(buffer->scrollback[buffer->top]); + guac_terminal_char* current = terminal->buffer->characters; + + for (row=0; rowcharacters; + for (column=0; column < terminal->buffer->width; column++) + *(dest++) = *(current++); + + scrollback_row->length = terminal->buffer->width; + + /* Next scrollback row */ + scrollback_row++; + buffer->top++; + + /* Wrap around when bottom reached */ + if (buffer->top == buffer->rows) { + buffer->top = 0; + scrollback_row = buffer->scrollback; + } + + } /* end for each row */ + + /* Increment row count */ + buffer->length += rows; + if (buffer->length > buffer->rows) + buffer->length = buffer->rows; + + /* Log string version of row that WOULD have been scrolled into the + * scrollback */ + guac_client_log_info(terminal->client, + "scrollback->top=%i (length=%i/%i)", buffer->top, buffer->length, buffer->rows); + +} + +void guac_terminal_scroll_display_down(guac_terminal* terminal) { + + int scroll_amount = 3; + + int row, column; + + int scrollback_row_index; + guac_terminal_scrollback_row* scrollback_row; + + /* Limit scroll amount by size of scrollback buffer */ + if (scroll_amount > terminal->scroll_offset) + scroll_amount = terminal->scroll_offset; + + /* If not scrolling at all, don't bother trying */ + if (scroll_amount == 0) + return; + + /* Shift screen up */ + if (terminal->term_height > scroll_amount) + guac_terminal_delta_copy(terminal->delta, + 0, 0, /* Destination row, col */ + scroll_amount, 0, /* source row,col */ + terminal->term_width, terminal->term_height - scroll_amount); + + /* Advance by scroll amount */ + terminal->scroll_offset -= scroll_amount; + guac_client_log_info(terminal->client, "scrolling to %i", terminal->scroll_offset); + + /* Get corresponding scrollback row */ + scrollback_row_index = terminal->scrollback->top - terminal->scroll_offset + terminal->term_height - 1; + scrollback_row = &(terminal->scrollback->scrollback[scrollback_row_index]); + + /* Draw new rows from scrollback */ + for (row=terminal->term_height - scroll_amount; row < terminal->term_height; row++) { + + /* FIXME: Clear row first */ + + /* Draw row */ + guac_terminal_char* current = scrollback_row->characters; + for (column=0; columnlength; column++) + guac_terminal_delta_set(terminal->delta, row, column, current++); + + /* Next row */ + scrollback_row_index++; + + /* Wrap if at end of scrollback */ + if (scrollback_row_index == terminal->scrollback->rows) { + scrollback_row_index = 0; + scrollback_row = terminal->scrollback->scrollback; + } + + /* Otherwise, just advance to next row */ + else + scrollback_row++; + + } + + /* FIXME: Should flush somewhere more sensible */ + guac_terminal_delta_flush(terminal->delta, terminal); + guac_socket_flush(terminal->client->socket); + +} + +void guac_terminal_scroll_display_up(guac_terminal* terminal) { + + int scroll_amount = 3; + + int row, column; + + int scrollback_row_index; + guac_terminal_scrollback_row* scrollback_row; + + /* Limit scroll amount by size of scrollback buffer */ + if (terminal->scroll_offset + scroll_amount > terminal->scrollback->length) + scroll_amount = terminal->scrollback->length - terminal->scroll_offset; + + /* If not scrolling at all, don't bother trying */ + if (scroll_amount == 0) + return; + + /* Shift screen down */ + if (terminal->term_height > scroll_amount) + guac_terminal_delta_copy(terminal->delta, + scroll_amount, 0, /* Destination row,col */ + 0, 0, /* Source row, col */ + terminal->term_width, terminal->term_height - scroll_amount); + + /* Advance by scroll amount */ + terminal->scroll_offset += scroll_amount; + guac_client_log_info(terminal->client, "scrolling to %i", terminal->scroll_offset); + + /* Get corresponding scrollback row */ + scrollback_row_index = terminal->scrollback->top - terminal->scroll_offset; + scrollback_row = &(terminal->scrollback->scrollback[scrollback_row_index]); + + /* Draw new rows from scrollback */ + for (row=0; row < scroll_amount; row++) { + + /* FIXME: Clear row first */ + + /* Draw row */ + guac_terminal_char* current = scrollback_row->characters; + for (column=0; columnlength; column++) + guac_terminal_delta_set(terminal->delta, row, column, current++); + + /* Next row */ + scrollback_row_index++; + + /* Wrap if at end of scrollback */ + if (scrollback_row_index == terminal->scrollback->rows) { + scrollback_row_index = 0; + scrollback_row = terminal->scrollback->scrollback; + } + + /* Otherwise, just advance to next row */ + else + scrollback_row++; + + } + + /* FIXME: Should flush somewhere more sensible */ + guac_terminal_delta_flush(terminal->delta, terminal); + guac_socket_flush(terminal->client->socket); + +} +