From c0d323828e1db5c3068c5d9e7730690dcad80460 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 16 Jun 2018 19:43:26 -0700 Subject: [PATCH] GUACAMOLE-573: Copy terminal data directly into clipboard. Do not assume selected region will be strictly visible. --- src/terminal/select.c | 148 ++++++++++++++++++++------------- src/terminal/terminal.c | 26 +----- src/terminal/terminal/select.h | 13 ++- 3 files changed, 96 insertions(+), 91 deletions(-) diff --git a/src/terminal/select.c b/src/terminal/select.c index b9e8474b..ad7f5ac2 100644 --- a/src/terminal/select.c +++ b/src/terminal/select.c @@ -20,33 +20,15 @@ #include "config.h" #include "common/clipboard.h" -#include "common/cursor.h" #include "terminal/buffer.h" -#include "terminal/common.h" #include "terminal/display.h" -#include "terminal/palette.h" +#include "terminal/select.h" #include "terminal/terminal.h" -#include "terminal/terminal_handlers.h" #include "terminal/types.h" -#include "terminal/typescript.h" -#include "terminal/xparsecolor.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include #include -#include +#include void guac_terminal_select_redraw(guac_terminal* terminal) { @@ -135,37 +117,96 @@ void guac_terminal_select_update(guac_terminal* terminal, int row, int column) { } -int __guac_terminal_buffer_string(guac_terminal_buffer_row* row, int start, int end, char* string) { +/** + * Appends the text within the given subsection of a terminal row to the + * clipboard. The provided coordinates are considered inclusiveley (the + * characters at the start and end column are included in the copied + * text). Any out-of-bounds coordinates will be automatically clipped within + * the bounds of the given row. + * + * @param terminal + * The guac_terminal instance associated with the buffer containing the + * text being copied and the clipboard receiving the copied text. + * + * @param row + * The row number of the text within the terminal to be copied into the + * clipboard, where the first (top-most) row in the terminal is row 0. Rows + * within the scrollback buffer (above the top-most row of the terminal) + * will be negative. + * + * @param start + * The first column of the text to be copied from the given row into the + * clipboard associated with the given terminal, where 0 is the first + * (left-most) column within the row. + * + * @param end + * The last column of the text to be copied from the given row into the + * clipboard associated with the given terminal, where 0 is the first + * (left-most) column within the row. + */ +static void guac_terminal_clipboard_append_row(guac_terminal* terminal, + int row, int start, int end) { - int length = 0; - int i; - for (i=start; i<=end; i++) { + char buffer[1024]; + int i = start; - int codepoint = row->characters[i].value; + guac_terminal_buffer_row* buffer_row = + guac_terminal_buffer_get_row(terminal->buffer, row, 0); + + /* If selection is entirely outside the bounds of the row, then there is + * nothing to append */ + if (start > buffer_row->length - 1) + return; + + /* Clip given range to actual bounds of row */ + if (end == -1 || end > buffer_row->length - 1) + end = buffer_row->length - 1; + + /* Repeatedly convert chunks of terminal buffer rows until entire specified + * region has been appended to clipboard */ + while (i <= end) { + + int remaining = sizeof(buffer); + char* current = buffer; + + /* Convert as many codepoints within the given range as possible */ + for (i = start; i <= end; i++) { + + int codepoint = buffer_row->characters[i].value; + + /* Ignore null (blank) characters */ + if (codepoint == 0 || codepoint == GUAC_CHAR_CONTINUATION) + continue; + + /* Encode current codepoint as UTF-8 */ + int bytes = guac_utf8_write(codepoint, current, remaining); + if (bytes == 0) + break; + + current += bytes; + remaining -= bytes; - /* If not null (blank), add to string */ - if (codepoint != 0 && codepoint != GUAC_CHAR_CONTINUATION) { - int bytes = guac_terminal_encode_utf8(codepoint, string); - string += bytes; - length += bytes; } + /* Append converted buffer to clipboard */ + guac_common_clipboard_append(terminal->clipboard, buffer, current - buffer); + } - return length; - } -void guac_terminal_select_end(guac_terminal* terminal, char* string) { +void guac_terminal_select_end(guac_terminal* terminal) { + + guac_client* client = terminal->client; + guac_socket* socket = client->socket; + + /* Reset current clipboard contents */ + guac_common_clipboard_reset(terminal->clipboard, "text/plain"); /* Deselect */ terminal->text_selected = false; guac_terminal_display_commit_select(terminal->display); - guac_terminal_buffer_row* buffer_row; - - int row; - int start_row, start_col; int end_row, end_col; @@ -188,41 +229,30 @@ void guac_terminal_select_end(guac_terminal* terminal, char* string) { } /* If only one row, simply copy */ - buffer_row = guac_terminal_buffer_get_row(terminal->buffer, start_row, 0); - if (end_row == start_row) { - if (buffer_row->length - 1 < end_col) - end_col = buffer_row->length - 1; - string += __guac_terminal_buffer_string(buffer_row, start_col, end_col, string); - } + if (end_row == start_row) + guac_terminal_clipboard_append_row(terminal, start_row, start_col, end_col); /* Otherwise, copy multiple rows */ else { /* Store first row */ - string += __guac_terminal_buffer_string(buffer_row, start_col, buffer_row->length - 1, string); + guac_terminal_clipboard_append_row(terminal, start_row, start_col, -1); /* Store all middle rows */ - for (row=start_row+1; rowbuffer, row, 0); - - *(string++) = '\n'; - string += __guac_terminal_buffer_string(buffer_row, 0, buffer_row->length - 1, string); - + for (int row = start_row + 1; row < end_row; row++) { + guac_common_clipboard_append(terminal->clipboard, "\n", 1); + guac_terminal_clipboard_append_row(terminal, row, 0, -1); } /* Store last row */ - buffer_row = guac_terminal_buffer_get_row(terminal->buffer, end_row, 0); - if (buffer_row->length - 1 < end_col) - end_col = buffer_row->length - 1; - - *(string++) = '\n'; - string += __guac_terminal_buffer_string(buffer_row, 0, end_col, string); + guac_common_clipboard_append(terminal->clipboard, "\n", 1); + guac_terminal_clipboard_append_row(terminal, end_row, 0, end_col); } - /* Null terminator */ - *string = 0; + /* Send data */ + guac_common_clipboard_send(terminal->clipboard, client); + guac_socket_flush(socket); } diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index e6c99d43..8f3e9180 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -1729,9 +1729,6 @@ int guac_terminal_send_key(guac_terminal* term, int keysym, int pressed) { static int __guac_terminal_send_mouse(guac_terminal* term, guac_user* user, int x, int y, int mask) { - guac_client* client = term->client; - guac_socket* socket = client->socket; - /* Determine which buttons were just released and pressed */ int released_mask = term->mouse_mask & ~mask; int pressed_mask = ~term->mouse_mask & mask; @@ -1771,27 +1768,8 @@ static int __guac_terminal_send_mouse(guac_terminal* term, guac_user* user, if (term->text_selected) { /* If mouse button released, stop selection */ - if (released_mask & GUAC_CLIENT_MOUSE_LEFT) { - - int selected_length; - - /* End selection and get selected text */ - int selectable_size = term->term_width * term->term_height * sizeof(char); - char* string = malloc(selectable_size); - guac_terminal_select_end(term, string); - - selected_length = strnlen(string, selectable_size); - - /* Store new data */ - guac_common_clipboard_reset(term->clipboard, "text/plain"); - guac_common_clipboard_append(term->clipboard, string, selected_length); - free(string); - - /* Send data */ - guac_common_clipboard_send(term->clipboard, client); - guac_socket_flush(socket); - - } + if (released_mask & GUAC_CLIENT_MOUSE_LEFT) + guac_terminal_select_end(term); /* Otherwise, just update */ else diff --git a/src/terminal/terminal/select.h b/src/terminal/terminal/select.h index 936ae7a4..b6e70090 100644 --- a/src/terminal/terminal/select.h +++ b/src/terminal/terminal/select.h @@ -67,19 +67,16 @@ void guac_terminal_select_update(guac_terminal* terminal, int row, int column); /** * Ends text selection, removing any highlight and storing the selected - * character data within the provided string buffer. This function should only - * be invoked while the guac_terminal is locked through a call to + * character data within the clipboard associated with the given terminal. If + * more text is selected than can fit within the clipboard, text at the end of + * the selected area will be dropped as necessary. This function should only be + * invoked while the guac_terminal is locked through a call to * guac_terminal_lock(). * * @param terminal * The guac_terminal instance associated with the text being selected. - * - * @param string - * The buffer which should receive the characters within the selected - * area. This buffer must already have been allocated with sufficient - * space to store the selected text. */ -void guac_terminal_select_end(guac_terminal* terminal, char* string); +void guac_terminal_select_end(guac_terminal* terminal); #endif