From f87af06ad69af16702ceb96477470607be060111 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 16 Jun 2018 18:12:19 -0700 Subject: [PATCH 1/5] GUACAMOLE-573: Move terminal text selection code into own file. --- src/terminal/Makefile.am | 2 + src/terminal/select.c | 228 +++++++++++++++++++++++++++++++ src/terminal/terminal.c | 179 +----------------------- src/terminal/terminal/select.h | 85 ++++++++++++ src/terminal/terminal/terminal.h | 17 --- 5 files changed, 316 insertions(+), 195 deletions(-) create mode 100644 src/terminal/select.c create mode 100644 src/terminal/terminal/select.h diff --git a/src/terminal/Makefile.am b/src/terminal/Makefile.am index 8121bd3e..1fbded1a 100644 --- a/src/terminal/Makefile.am +++ b/src/terminal/Makefile.am @@ -30,6 +30,7 @@ noinst_HEADERS = \ terminal/named-colors.h \ terminal/palette.h \ terminal/scrollbar.h \ + terminal/select.h \ terminal/terminal.h \ terminal/terminal_handlers.h \ terminal/types.h \ @@ -44,6 +45,7 @@ libguac_terminal_la_SOURCES = \ named-colors.c \ palette.c \ scrollbar.c \ + select.c \ terminal.c \ terminal_handlers.c \ typescript.c \ diff --git a/src/terminal/select.c b/src/terminal/select.c new file mode 100644 index 00000000..b9e8474b --- /dev/null +++ b/src/terminal/select.c @@ -0,0 +1,228 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#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/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 + +void guac_terminal_select_redraw(guac_terminal* terminal) { + + int start_row = terminal->selection_start_row + terminal->scroll_offset; + int start_column = terminal->selection_start_column; + + int end_row = terminal->selection_end_row + terminal->scroll_offset; + int end_column = terminal->selection_end_column; + + /* Update start/end columns to include character width */ + if (start_row > end_row || (start_row == end_row && start_column > end_column)) + start_column += terminal->selection_start_width - 1; + else + end_column += terminal->selection_end_width - 1; + + guac_terminal_display_select(terminal->display, start_row, start_column, end_row, end_column); + +} + +/** + * Locates the beginning of the character at the given row and column, updating + * the column to the starting column of that character. The width, if available, + * is returned. If the character has no defined width, 1 is returned. + */ +static int __guac_terminal_find_char(guac_terminal* terminal, int row, int* column) { + + int start_column = *column; + + guac_terminal_buffer_row* buffer_row = guac_terminal_buffer_get_row(terminal->buffer, row, 0); + if (start_column < buffer_row->length) { + + /* Find beginning of character */ + guac_terminal_char* start_char = &(buffer_row->characters[start_column]); + while (start_column > 0 && start_char->value == GUAC_CHAR_CONTINUATION) { + start_char--; + start_column--; + } + + /* Use width, if available */ + if (start_char->value != GUAC_CHAR_CONTINUATION) { + *column = start_column; + return start_char->width; + } + + } + + /* Default to one column wide */ + return 1; + +} + +void guac_terminal_select_start(guac_terminal* terminal, int row, int column) { + + int width = __guac_terminal_find_char(terminal, row, &column); + + terminal->selection_start_row = + terminal->selection_end_row = row; + + terminal->selection_start_column = + terminal->selection_end_column = column; + + terminal->selection_start_width = + terminal->selection_end_width = width; + + terminal->text_selected = true; + + guac_terminal_select_redraw(terminal); + +} + +void guac_terminal_select_update(guac_terminal* terminal, int row, int column) { + + /* Only update if selection has changed */ + if (row != terminal->selection_end_row + || column < terminal->selection_end_column + || column >= terminal->selection_end_column + terminal->selection_end_width) { + + int width = __guac_terminal_find_char(terminal, row, &column); + + terminal->selection_end_row = row; + terminal->selection_end_column = column; + terminal->selection_end_width = width; + + guac_terminal_select_redraw(terminal); + } + +} + +int __guac_terminal_buffer_string(guac_terminal_buffer_row* row, int start, int end, char* string) { + + int length = 0; + int i; + for (i=start; i<=end; i++) { + + int codepoint = row->characters[i].value; + + /* 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; + } + + } + + return length; + +} + +void guac_terminal_select_end(guac_terminal* terminal, char* string) { + + /* 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; + + /* Ensure proper ordering of start and end coords */ + if (terminal->selection_start_row < terminal->selection_end_row + || (terminal->selection_start_row == terminal->selection_end_row + && terminal->selection_start_column < terminal->selection_end_column)) { + + start_row = terminal->selection_start_row; + start_col = terminal->selection_start_column; + end_row = terminal->selection_end_row; + end_col = terminal->selection_end_column + terminal->selection_end_width - 1; + + } + else { + end_row = terminal->selection_start_row; + end_col = terminal->selection_start_column + terminal->selection_start_width - 1; + start_row = terminal->selection_end_row; + start_col = terminal->selection_end_column; + } + + /* 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); + } + + /* Otherwise, copy multiple rows */ + else { + + /* Store first row */ + string += __guac_terminal_buffer_string(buffer_row, start_col, buffer_row->length - 1, string); + + /* 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); + + } + + /* 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); + + } + + /* Null terminator */ + *string = 0; + +} + diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index 5ae2ee3d..e6c99d43 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -25,6 +25,7 @@ #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" @@ -1269,184 +1270,6 @@ void guac_terminal_scroll_display_up(guac_terminal* terminal, } -void guac_terminal_select_redraw(guac_terminal* terminal) { - - int start_row = terminal->selection_start_row + terminal->scroll_offset; - int start_column = terminal->selection_start_column; - - int end_row = terminal->selection_end_row + terminal->scroll_offset; - int end_column = terminal->selection_end_column; - - /* Update start/end columns to include character width */ - if (start_row > end_row || (start_row == end_row && start_column > end_column)) - start_column += terminal->selection_start_width - 1; - else - end_column += terminal->selection_end_width - 1; - - guac_terminal_display_select(terminal->display, start_row, start_column, end_row, end_column); - -} - -/** - * Locates the beginning of the character at the given row and column, updating - * the column to the starting column of that character. The width, if available, - * is returned. If the character has no defined width, 1 is returned. - */ -static int __guac_terminal_find_char(guac_terminal* terminal, int row, int* column) { - - int start_column = *column; - - guac_terminal_buffer_row* buffer_row = guac_terminal_buffer_get_row(terminal->buffer, row, 0); - if (start_column < buffer_row->length) { - - /* Find beginning of character */ - guac_terminal_char* start_char = &(buffer_row->characters[start_column]); - while (start_column > 0 && start_char->value == GUAC_CHAR_CONTINUATION) { - start_char--; - start_column--; - } - - /* Use width, if available */ - if (start_char->value != GUAC_CHAR_CONTINUATION) { - *column = start_column; - return start_char->width; - } - - } - - /* Default to one column wide */ - return 1; - -} - -void guac_terminal_select_start(guac_terminal* terminal, int row, int column) { - - int width = __guac_terminal_find_char(terminal, row, &column); - - terminal->selection_start_row = - terminal->selection_end_row = row; - - terminal->selection_start_column = - terminal->selection_end_column = column; - - terminal->selection_start_width = - terminal->selection_end_width = width; - - terminal->text_selected = true; - - guac_terminal_select_redraw(terminal); - -} - -void guac_terminal_select_update(guac_terminal* terminal, int row, int column) { - - /* Only update if selection has changed */ - if (row != terminal->selection_end_row - || column < terminal->selection_end_column - || column >= terminal->selection_end_column + terminal->selection_end_width) { - - int width = __guac_terminal_find_char(terminal, row, &column); - - terminal->selection_end_row = row; - terminal->selection_end_column = column; - terminal->selection_end_width = width; - - guac_terminal_select_redraw(terminal); - } - -} - -int __guac_terminal_buffer_string(guac_terminal_buffer_row* row, int start, int end, char* string) { - - int length = 0; - int i; - for (i=start; i<=end; i++) { - - int codepoint = row->characters[i].value; - - /* 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; - } - - } - - return length; - -} - -void guac_terminal_select_end(guac_terminal* terminal, char* string) { - - /* 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; - - /* Ensure proper ordering of start and end coords */ - if (terminal->selection_start_row < terminal->selection_end_row - || (terminal->selection_start_row == terminal->selection_end_row - && terminal->selection_start_column < terminal->selection_end_column)) { - - start_row = terminal->selection_start_row; - start_col = terminal->selection_start_column; - end_row = terminal->selection_end_row; - end_col = terminal->selection_end_column + terminal->selection_end_width - 1; - - } - else { - end_row = terminal->selection_start_row; - end_col = terminal->selection_start_column + terminal->selection_start_width - 1; - start_row = terminal->selection_end_row; - start_col = terminal->selection_end_column; - } - - /* 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); - } - - /* Otherwise, copy multiple rows */ - else { - - /* Store first row */ - string += __guac_terminal_buffer_string(buffer_row, start_col, buffer_row->length - 1, string); - - /* 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); - - } - - /* 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); - - } - - /* Null terminator */ - *string = 0; - -} - void guac_terminal_copy_columns(guac_terminal* terminal, int row, int start_column, int end_column, int offset) { diff --git a/src/terminal/terminal/select.h b/src/terminal/terminal/select.h new file mode 100644 index 00000000..936ae7a4 --- /dev/null +++ b/src/terminal/terminal/select.h @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +#ifndef GUAC_TERMINAL_SELECT_H +#define GUAC_TERMINAL_SELECT_H + +#include "config.h" +#include "terminal.h" + +/** + * Marks the start of text selection at the given row and column. Any existing + * selection is cleared. 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 row + * The row number of the character at the start of the text selection, + * 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 column + * The column number of the character at the start of the text selection, + * where the first (left-most) column in the terminal is column 0. + */ +void guac_terminal_select_start(guac_terminal* terminal, int row, int column); + +/** + * Updates the end of text selection at the given row and column. 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 row + * The row number of the character at the current end of the text + * selection, 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 column + * The column number of the character at the current end of the text + * selection, where the first (left-most) column in the terminal is + * column 0. + */ +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 + * 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); + +#endif + diff --git a/src/terminal/terminal/terminal.h b/src/terminal/terminal/terminal.h index 6094c393..67b5b3ee 100644 --- a/src/terminal/terminal/terminal.h +++ b/src/terminal/terminal/terminal.h @@ -697,23 +697,6 @@ void guac_terminal_scroll_display_down(guac_terminal* terminal, int amount); */ void guac_terminal_scroll_display_up(guac_terminal* terminal, int amount); -/** - * Marks the start of text selection at the given row and column. - */ -void guac_terminal_select_start(guac_terminal* terminal, int row, int column); - -/** - * Updates the end of text selection at the given row and column. - */ -void guac_terminal_select_update(guac_terminal* terminal, int row, int column); - -/** - * Ends text selection, removing any highlight. Character data is stored in the - * string buffer provided. - */ -void guac_terminal_select_end(guac_terminal* terminal, char* string); - - /* LOW-LEVEL TERMINAL OPERATIONS */ From c0d323828e1db5c3068c5d9e7730690dcad80460 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 16 Jun 2018 19:43:26 -0700 Subject: [PATCH 2/5] 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 From 6f08ef2a07b8f908cc0ce8a9f82f846f3b73a7cd Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 16 Jun 2018 22:50:20 -0700 Subject: [PATCH 3/5] GUACAMOLE-573: Allow text selection to be expanded using Shift (fixes GUACAMOLE-191). --- src/terminal/display.c | 102 ++++--------- src/terminal/select.c | 255 ++++++++++++++++++++++++++----- src/terminal/terminal.c | 49 ++++-- src/terminal/terminal/display.h | 18 +-- src/terminal/terminal/select.h | 97 ++++++++++++ src/terminal/terminal/terminal.h | 10 +- 6 files changed, 391 insertions(+), 140 deletions(-) diff --git a/src/terminal/display.c b/src/terminal/display.c index 9391ec17..c4c74713 100644 --- a/src/terminal/display.c +++ b/src/terminal/display.c @@ -37,54 +37,6 @@ #include #include -/** - * Clears the currently-selected region, removing the highlight. - */ -static void __guac_terminal_display_clear_select(guac_terminal_display* display) { - - guac_socket* socket = display->client->socket; - guac_layer* select_layer = display->select_layer; - - guac_protocol_send_rect(socket, select_layer, 0, 0, 1, 1); - guac_protocol_send_cfill(socket, GUAC_COMP_SRC, select_layer, - 0x00, 0x00, 0x00, 0x00); - - guac_client_end_frame(display->client); - guac_socket_flush(socket); - - /* Text is no longer selected */ - display->text_selected = - display->selection_committed = false; - -} - -/** - * Returns whether at least one character within the given range is selected. - */ -static bool __guac_terminal_display_selected_contains(guac_terminal_display* display, - int start_row, int start_column, int end_row, int end_column) { - - /* If test range starts after highlight ends, does not intersect */ - if (start_row > display->selection_end_row) - return false; - - if (start_row == display->selection_end_row - && start_column > display->selection_end_column) - return false; - - /* If test range ends before highlight starts, does not intersect */ - if (end_row < display->selection_start_row) - return false; - - if (end_row == display->selection_start_row - && end_column < display->selection_start_column) - return false; - - /* Otherwise, does intersect */ - return true; - -} - /* Maps any codepoint onto a number between 0 and 511 inclusive */ int __guac_terminal_hash_codepoint(int codepoint) { @@ -310,8 +262,7 @@ guac_terminal_display* guac_terminal_display_alloc(guac_client* client, display->operations = NULL; /* Initially nothing selected */ - display->text_selected = - display->selection_committed = false; + display->text_selected = false; return display; @@ -413,11 +364,6 @@ void guac_terminal_display_copy_columns(guac_terminal_display* display, int row, } - /* If selection visible and committed, clear if update touches selection */ - if (display->text_selected && display->selection_committed && - __guac_terminal_display_selected_contains(display, row, start_column, row, end_column)) - __guac_terminal_display_clear_select(display); - } void guac_terminal_display_copy_rows(guac_terminal_display* display, @@ -463,11 +409,6 @@ void guac_terminal_display_copy_rows(guac_terminal_display* display, } - /* If selection visible and committed, clear if update touches selection */ - if (display->text_selected && display->selection_committed && - __guac_terminal_display_selected_contains(display, start_row, 0, end_row, display->width - 1)) - __guac_terminal_display_clear_select(display); - } void guac_terminal_display_set_columns(guac_terminal_display* display, int row, @@ -502,11 +443,6 @@ void guac_terminal_display_set_columns(guac_terminal_display* display, int row, } - /* If selection visible and committed, clear if update touches selection */ - if (display->text_selected && display->selection_committed && - __guac_terminal_display_selected_contains(display, row, start_column, row, end_column)) - __guac_terminal_display_clear_select(display); - } void guac_terminal_display_resize(guac_terminal_display* display, int width, int height) { @@ -570,10 +506,6 @@ void guac_terminal_display_resize(guac_terminal_display* display, int width, int display->char_width * width, display->char_height * height); - /* If selection visible and committed, clear */ - if (display->text_selected && display->selection_committed) - __guac_terminal_display_clear_select(display); - } void __guac_terminal_display_flush_copy(guac_terminal_display* display) { @@ -899,16 +831,20 @@ void guac_terminal_display_dup(guac_terminal_display* display, guac_user* user, } -void guac_terminal_display_commit_select(guac_terminal_display* display) { - display->selection_committed = true; -} - void guac_terminal_display_select(guac_terminal_display* display, int start_row, int start_col, int end_row, int end_col) { guac_socket* socket = display->client->socket; guac_layer* select_layer = display->select_layer; + /* Do nothing if selection is unchanged */ + if (display->text_selected + && display->selection_start_row == start_row + && display->selection_start_column == start_col + && display->selection_end_row == end_row + && display->selection_end_column == end_col) + return; + /* Text is now selected */ display->text_selected = true; @@ -989,8 +925,24 @@ void guac_terminal_display_select(guac_terminal_display* display, guac_protocol_send_cfill(socket, GUAC_COMP_SRC, select_layer, 0x00, 0x80, 0xFF, 0x60); - guac_client_end_frame(display->client); - guac_socket_flush(socket); +} + +void guac_terminal_display_clear_select(guac_terminal_display* display) { + + /* Do nothing if nothing is selected */ + if (!display->text_selected) + return; + + guac_socket* socket = display->client->socket; + guac_layer* select_layer = display->select_layer; + + guac_protocol_send_rect(socket, select_layer, 0, 0, 1, 1); + guac_protocol_send_cfill(socket, GUAC_COMP_SRC, select_layer, + 0x00, 0x00, 0x00, 0x00); + + /* Text is no longer selected */ + display->text_selected = false; } + diff --git a/src/terminal/select.c b/src/terminal/select.c index ad7f5ac2..69bb0bc0 100644 --- a/src/terminal/select.c +++ b/src/terminal/select.c @@ -30,21 +30,93 @@ #include #include +#include + +/** + * Returns the coordinates for the currently-selected range of text within the + * given terminal, normalized such that the start coordinate is before the end + * coordinate and the end coordinate takes into account character width. If no + * text is currently selected, the behavior of this function is undefined. + * + * @param terminal + * The guac_terminal instance whose selected text coordinates should be + * retrieved in normalized form. + * + * @param start_row + * A pointer to an int which should receive the row number of the first + * character of text selected within the terminal, 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_col + * A pointer to an int which should receive the column number of the first + * character of text selected within terminal, where 0 is the first + * (left-most) column within the row. + * + * @param end_row + * A pointer to an int which should receive the row number of the last + * character of text selected within the terminal, 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 end_col + * A pointer to an int which should receive the column number of the first + * character of text selected within terminal, taking into account the + * width of that character, where 0 is the first (left-most) column within + * the row. + */ +static void guac_terminal_select_normalized_range(guac_terminal* terminal, + int* start_row, int* start_col, int* end_row, int* end_col) { + + /* Pass through start/end coordinates if they are already in the expected + * order, adjusting only for final character width */ + if (terminal->selection_start_row < terminal->selection_end_row + || (terminal->selection_start_row == terminal->selection_end_row + && terminal->selection_start_column < terminal->selection_end_column)) { + + *start_row = terminal->selection_start_row; + *start_col = terminal->selection_start_column; + *end_row = terminal->selection_end_row; + *end_col = terminal->selection_end_column + terminal->selection_end_width - 1; + + } + + /* Coordinates must otherwise be swapped in addition to adjusting for + * final character width */ + else { + *end_row = terminal->selection_start_row; + *end_col = terminal->selection_start_column + terminal->selection_start_width - 1; + *start_row = terminal->selection_end_row; + *start_col = terminal->selection_end_column; + } + +} + void guac_terminal_select_redraw(guac_terminal* terminal) { - int start_row = terminal->selection_start_row + terminal->scroll_offset; - int start_column = terminal->selection_start_column; + /* Update the selected region of the display if text is currently + * selected */ + if (terminal->text_selected) { - int end_row = terminal->selection_end_row + terminal->scroll_offset; - int end_column = terminal->selection_end_column; + int start_row = terminal->selection_start_row + terminal->scroll_offset; + int start_column = terminal->selection_start_column; - /* Update start/end columns to include character width */ - if (start_row > end_row || (start_row == end_row && start_column > end_column)) - start_column += terminal->selection_start_width - 1; + int end_row = terminal->selection_end_row + terminal->scroll_offset; + int end_column = terminal->selection_end_column; + + /* Update start/end columns to include character width */ + if (start_row > end_row || (start_row == end_row && start_column > end_column)) + start_column += terminal->selection_start_width - 1; + else + end_column += terminal->selection_end_width - 1; + + guac_terminal_display_select(terminal->display, start_row, start_column, end_row, end_column); + + } + + /* Clear the display selection if no text is currently selected */ else - end_column += terminal->selection_end_width - 1; - - guac_terminal_display_select(terminal->display, start_row, start_column, end_row, end_column); + guac_terminal_display_clear_select(terminal->display); } @@ -52,8 +124,28 @@ void guac_terminal_select_redraw(guac_terminal* terminal) { * Locates the beginning of the character at the given row and column, updating * the column to the starting column of that character. The width, if available, * is returned. If the character has no defined width, 1 is returned. + * + * @param terminal + * The guac_terminal in which the character should be located. + * + * @param row + * The row number of the desired character, 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 column + * A pointer to an int containing the column number of the desired + * character, where 0 is the first (left-most) column within the row. If + * the character is a multi-column character, the value of this int will be + * adjusted as necessary such that it contains the column number of the + * first column containing the character. + * + * @return + * The width of the specified character, in columns, or 1 if the character + * has no defined width. */ -static int __guac_terminal_find_char(guac_terminal* terminal, int row, int* column) { +static int guac_terminal_find_char(guac_terminal* terminal, + int row, int* column) { int start_column = *column; @@ -82,7 +174,7 @@ static int __guac_terminal_find_char(guac_terminal* terminal, int row, int* colu void guac_terminal_select_start(guac_terminal* terminal, int row, int column) { - int width = __guac_terminal_find_char(terminal, row, &column); + int width = guac_terminal_find_char(terminal, row, &column); terminal->selection_start_row = terminal->selection_end_row = row; @@ -93,9 +185,9 @@ void guac_terminal_select_start(guac_terminal* terminal, int row, int column) { terminal->selection_start_width = terminal->selection_end_width = width; - terminal->text_selected = true; - - guac_terminal_select_redraw(terminal); + terminal->text_selected = false; + terminal->selection_committed = false; + guac_terminal_notify(terminal); } @@ -106,17 +198,59 @@ void guac_terminal_select_update(guac_terminal* terminal, int row, int column) { || column < terminal->selection_end_column || column >= terminal->selection_end_column + terminal->selection_end_width) { - int width = __guac_terminal_find_char(terminal, row, &column); + int width = guac_terminal_find_char(terminal, row, &column); terminal->selection_end_row = row; terminal->selection_end_column = column; terminal->selection_end_width = width; + terminal->text_selected = true; + + guac_terminal_notify(terminal); - guac_terminal_select_redraw(terminal); } } +void guac_terminal_select_resume(guac_terminal* terminal, int row, int column) { + + int selection_start_row; + int selection_start_column; + int selection_end_row; + int selection_end_column; + + /* No need to test coordinates if no text is selected at all */ + if (!terminal->text_selected) + return; + + /* Use normalized coordinates for sake of simple comparison */ + guac_terminal_select_normalized_range(terminal, + &selection_start_row, &selection_start_column, + &selection_end_row, &selection_end_column); + + /* Prefer to expand from start, such that attempting to resume a selection + * within the existing selection preserves the top-most portion of the + * selection */ + if (row > selection_start_row || + (row == selection_start_row && column > selection_start_column)) { + terminal->selection_start_row = selection_start_row; + terminal->selection_start_column = selection_start_column; + } + + /* Expand from bottom-most portion of selection if doing otherwise would + * reduce the size of the selection */ + else { + terminal->selection_start_row = selection_end_row; + terminal->selection_start_column = selection_end_column; + } + + /* Selection is again in-progress */ + terminal->selection_committed = false; + + /* Update selection to contain given character */ + guac_terminal_select_update(terminal, row, column); + +} + /** * Appends the text within the given subsection of a terminal row to the * clipboard. The provided coordinates are considered inclusiveley (the @@ -200,33 +334,22 @@ void guac_terminal_select_end(guac_terminal* terminal) { guac_client* client = terminal->client; guac_socket* socket = client->socket; + /* If no text is selected, nothing to do */ + if (!terminal->text_selected) + return; + + /* Selection is now committed */ + terminal->selection_committed = true; + /* Reset current clipboard contents */ guac_common_clipboard_reset(terminal->clipboard, "text/plain"); - /* Deselect */ - terminal->text_selected = false; - guac_terminal_display_commit_select(terminal->display); - int start_row, start_col; int end_row, end_col; /* Ensure proper ordering of start and end coords */ - if (terminal->selection_start_row < terminal->selection_end_row - || (terminal->selection_start_row == terminal->selection_end_row - && terminal->selection_start_column < terminal->selection_end_column)) { - - start_row = terminal->selection_start_row; - start_col = terminal->selection_start_column; - end_row = terminal->selection_end_row; - end_col = terminal->selection_end_column + terminal->selection_end_width - 1; - - } - else { - end_row = terminal->selection_start_row; - end_col = terminal->selection_start_column + terminal->selection_start_width - 1; - start_row = terminal->selection_end_row; - start_col = terminal->selection_end_column; - } + guac_terminal_select_normalized_range(terminal, + &start_row, &start_col, &end_row, &end_col); /* If only one row, simply copy */ if (end_row == start_row) @@ -254,5 +377,63 @@ void guac_terminal_select_end(guac_terminal* terminal) { guac_common_clipboard_send(terminal->clipboard, client); guac_socket_flush(socket); + guac_terminal_notify(terminal); + +} + +bool guac_terminal_select_contains(guac_terminal* terminal, + int start_row, int start_column, int end_row, int end_column) { + + int selection_start_row; + int selection_start_column; + int selection_end_row; + int selection_end_column; + + /* No need to test coordinates if no text is selected at all */ + if (!terminal->text_selected) + return false; + + /* Use normalized coordinates for sake of simple comparison */ + guac_terminal_select_normalized_range(terminal, + &selection_start_row, &selection_start_column, + &selection_end_row, &selection_end_column); + + /* If test range starts after highlight ends, does not intersect */ + if (start_row > selection_end_row) + return false; + + if (start_row == selection_end_row && start_column > selection_end_column) + return false; + + /* If test range ends before highlight starts, does not intersect */ + if (end_row < selection_start_row) + return false; + + if (end_row == selection_start_row && end_column < selection_start_column) + return false; + + /* Otherwise, does intersect */ + return true; + +} + +void guac_terminal_select_touch(guac_terminal* terminal, + int start_row, int start_column, int end_row, int end_column) { + + /* Only clear selection if selection is committed */ + if (!terminal->selection_committed) + return; + + /* Clear selection if it contains any characters within the given region */ + if (guac_terminal_select_contains(terminal, start_row, start_column, + end_row, end_column)) { + + /* Text is no longer selected */ + terminal->text_selected = false; + terminal->selection_committed = false; + guac_terminal_notify(terminal); + + } + } diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index 8f3e9180..40c90995 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -61,6 +61,9 @@ static void __guac_terminal_set_columns(guac_terminal* terminal, int row, guac_terminal_buffer_set_columns(terminal->buffer, row, start_column, end_column, character); + /* Clear selection if region is modified */ + guac_terminal_select_touch(terminal, row, start_column, row, end_column); + } /** @@ -173,6 +176,7 @@ void guac_terminal_reset(guac_terminal* term) { /* Reset flags */ term->text_selected = false; + term->selection_committed = false; term->application_cursor_keys = false; term->automatic_carriage_return = false; term->insert_mode = false; @@ -1279,6 +1283,9 @@ void guac_terminal_copy_columns(guac_terminal* terminal, int row, guac_terminal_buffer_copy_columns(terminal->buffer, row, start_column, end_column, offset); + /* Clear selection if region is modified */ + guac_terminal_select_touch(terminal, row, start_column, row, end_column); + /* Update cursor location if within region */ if (row == terminal->visible_cursor_row && terminal->visible_cursor_col >= start_column && @@ -1300,6 +1307,10 @@ void guac_terminal_copy_rows(guac_terminal* terminal, guac_terminal_buffer_copy_rows(terminal->buffer, start_row, end_row, offset); + /* Clear selection if region is modified */ + guac_terminal_select_touch(terminal, start_row, 0, end_row, + terminal->term_width); + /* Update cursor location if within region */ if (terminal->visible_cursor_row >= start_row && terminal->visible_cursor_row <= end_row) @@ -1532,6 +1543,7 @@ void guac_terminal_flush(guac_terminal* terminal) { guac_terminal_typescript_flush(terminal->typescript); /* Flush display state */ + guac_terminal_select_redraw(terminal); guac_terminal_commit_cursor(terminal); guac_terminal_display_flush(terminal->display); guac_terminal_scrollbar_flush(terminal->scrollbar); @@ -1764,28 +1776,33 @@ static int __guac_terminal_send_mouse(guac_terminal* term, guac_user* user, if ((released_mask & GUAC_CLIENT_MOUSE_RIGHT) || (released_mask & GUAC_CLIENT_MOUSE_MIDDLE)) return guac_terminal_send_data(term, term->clipboard->buffer, term->clipboard->length); - /* If text selected, change state based on left mouse mouse button */ - if (term->text_selected) { + /* If left mouse button was just released, stop selection */ + if (released_mask & GUAC_CLIENT_MOUSE_LEFT) + guac_terminal_select_end(term); - /* If mouse button released, stop selection */ - if (released_mask & GUAC_CLIENT_MOUSE_LEFT) - guac_terminal_select_end(term); + /* Update selection state contextually while the left mouse button is + * pressed */ + else if (mask & GUAC_CLIENT_MOUSE_LEFT) { - /* Otherwise, just update */ + int row = y / term->display->char_height - term->scroll_offset; + int col = x / term->display->char_width; + + /* If mouse button was already just pressed, start a new selection or + * resume the existing selection depending on whether shift is held */ + if (pressed_mask & GUAC_CLIENT_MOUSE_LEFT) { + if (term->mod_shift) + guac_terminal_select_resume(term, row, col); + else + guac_terminal_select_start(term, row, col); + } + + /* In all other cases, simply update the existing selection as long as + * the mouse button is pressed */ else - guac_terminal_select_update(term, - y / term->display->char_height - term->scroll_offset, - x / term->display->char_width); + guac_terminal_select_update(term, row, col); } - /* Otherwise, if mouse button pressed AND moved, start selection */ - else if (!(pressed_mask & GUAC_CLIENT_MOUSE_LEFT) && - mask & GUAC_CLIENT_MOUSE_LEFT) - guac_terminal_select_start(term, - y / term->display->char_height - term->scroll_offset, - x / term->display->char_width); - /* Scroll up if wheel moved up */ if (released_mask & GUAC_CLIENT_MOUSE_SCROLL_UP) guac_terminal_scroll_display_up(term, GUAC_TERMINAL_WHEEL_SCROLL_AMOUNT); diff --git a/src/terminal/terminal/display.h b/src/terminal/terminal/display.h index 98337fd2..e54003bb 100644 --- a/src/terminal/terminal/display.h +++ b/src/terminal/terminal/display.h @@ -182,17 +182,10 @@ typedef struct guac_terminal_display { guac_layer* select_layer; /** - * Whether text is being selected. + * Whether text is currently selected. */ bool text_selected; - /** - * Whether the selection is finished, and will no longer be modified. A - * committed selection remains highlighted for reference, but the - * highlight will be removed when the display changes. - */ - bool selection_committed; - /** * The row that the selection starts at. */ @@ -333,10 +326,13 @@ void guac_terminal_display_select(guac_terminal_display* display, int start_row, int start_col, int end_row, int end_col); /** - * Commits the select rectangle, allowing the display to clear it when - * necessary. + * Clears the currently-selected region, removing the highlight. + * + * @param display + * The guac_terminal_display whose currently-selected region should be + * cleared. */ -void guac_terminal_display_commit_select(guac_terminal_display* display); +void guac_terminal_display_clear_select(guac_terminal_display* display); #endif diff --git a/src/terminal/terminal/select.h b/src/terminal/terminal/select.h index b6e70090..1ae7d3d2 100644 --- a/src/terminal/terminal/select.h +++ b/src/terminal/terminal/select.h @@ -24,6 +24,20 @@ #include "config.h" #include "terminal.h" +#include + +/** + * Forwards the visible portion of the text selection rectangle to the + * underlying terminal display, requesting that it be redrawn. If no + * visible change would result from redrawing the selection rectangle, + * this function may have no effect. + * + * @param terminal + * The guac_terminal whose text selection rectangle should be + * redrawn. + */ +void guac_terminal_select_redraw(guac_terminal* terminal); + /** * Marks the start of text selection at the given row and column. Any existing * selection is cleared. This function should only be invoked while the @@ -65,6 +79,27 @@ void guac_terminal_select_start(guac_terminal* terminal, int row, int column); */ void guac_terminal_select_update(guac_terminal* terminal, int row, int column); +/** + * Resumes selecting text, expanding the existing selected region from the + * closest end to additionally contain the given character. 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 row + * The row number of the character to include within the text selection, + * 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 column + * The column number of the character to include within the text selection, + * where the first (left-most) column in the terminal is column 0. + */ +void guac_terminal_select_resume(guac_terminal* terminal, int row, int column); + /** * Ends text selection, removing any highlight and storing the selected * character data within the clipboard associated with the given terminal. If @@ -78,5 +113,67 @@ void guac_terminal_select_update(guac_terminal* terminal, int row, int column); */ void guac_terminal_select_end(guac_terminal* terminal); +/** + * Returns whether at least one character within the given range is currently + * selected. + * + * @param terminal + * The guac_terminal instance associated with the text being selected. + * + * @param start_row + * The first row of the region to test, inclusive, 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_column + * The first column of the region to test, inclusive, where the first + * (left-most) column in the terminal is column 0. + * + * @param end_row + * The last row of the region to test, inclusive, 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 end_column + * The last column of the region to test, inclusive, where the first + * (left-most) column in the terminal is column 0. + * + * @return + * true if at least one character within the given range is currently + * selected, false otherwise. + */ +bool guac_terminal_select_contains(guac_terminal* terminal, + int start_row, int start_column, int end_row, int end_column); + +/** + * Clears the current selection if it contains at least one character within + * the given region. If no text is currently selected, the selection has not + * yet been committed, or the region does not contain at least one selected + * character, this function has no effect. + * + * @param terminal + * The guac_terminal instance associated with the text being selected. + * + * @param start_row + * The first row of the region, inclusive, 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_column + * The first column of the region, inclusive, where the first (left-most) + * column in the terminal is column 0. + * + * @param end_row + * The last row of the region, inclusive, 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 end_column + * The last column of the region, inclusive, where the first (left-most) + * column in the terminal is column 0. + */ +void guac_terminal_select_touch(guac_terminal* terminal, + int start_row, int start_column, int end_row, int end_column); + #endif diff --git a/src/terminal/terminal/terminal.h b/src/terminal/terminal/terminal.h index 67b5b3ee..8d6b48ff 100644 --- a/src/terminal/terminal/terminal.h +++ b/src/terminal/terminal/terminal.h @@ -367,10 +367,18 @@ struct guac_terminal { int active_char_set; /** - * Whether text is being selected. + * Whether text is currently selected. */ bool text_selected; + /** + * Whether the selection is finished, and will no longer be modified. A + * committed selection remains highlighted for reference, but the + * highlight will be removed if characters within the selected region are + * modified. + */ + bool selection_committed; + /** * The row that the selection starts at. */ From 1756c015228ab528ad65ee20eb6ecc01df2cd390 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 16 Jun 2018 23:09:43 -0700 Subject: [PATCH 4/5] GUACAMOLE-573: Update selected region when terminal scrolls. --- src/terminal/terminal.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index 40c90995..ce89856e 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -1023,6 +1023,12 @@ int guac_terminal_scroll_up(guac_terminal* term, term->visible_cursor_row <= end_row) term->visible_cursor_row -= amount; + /* Update selected region */ + if (term->text_selected) { + term->selection_start_row -= amount; + term->selection_end_row -= amount; + } + } /* Otherwise, just copy row data upwards */ From ecda5c1df958ac9725b0bcbe2897ec43163aea29 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 16 Jun 2018 23:36:01 -0700 Subject: [PATCH 5/5] GUACAMOLE-573: Read selection only within bounds of terminal/scrollback. --- src/terminal/select.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/terminal/select.c b/src/terminal/select.c index 69bb0bc0..131b5f68 100644 --- a/src/terminal/select.c +++ b/src/terminal/select.c @@ -149,6 +149,12 @@ static int guac_terminal_find_char(guac_terminal* terminal, int start_column = *column; + /* If requested row is outside the bounds of the current terminal or + * scrollback, assume the character is 1 column wide */ + if (row >= terminal->term_height + || row < terminal->term_height - terminal->buffer->length) + return 1; + guac_terminal_buffer_row* buffer_row = guac_terminal_buffer_get_row(terminal->buffer, row, 0); if (start_column < buffer_row->length) { @@ -276,7 +282,8 @@ void guac_terminal_select_resume(guac_terminal* terminal, int row, int column) { * @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. + * (left-most) column within the row, or a negative value to denote that + * the last column in the row should be used. */ static void guac_terminal_clipboard_append_row(guac_terminal* terminal, int row, int start, int end) { @@ -284,16 +291,22 @@ static void guac_terminal_clipboard_append_row(guac_terminal* terminal, char buffer[1024]; int i = start; + /* If requested row is outside the bounds of the current terminal or + * scrollback, do nothing */ + if (row >= terminal->term_height + || row < terminal->term_height - terminal->buffer->length) + return; + 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) + if (start < 0 || start > buffer_row->length - 1) return; /* Clip given range to actual bounds of row */ - if (end == -1 || end > buffer_row->length - 1) + if (end < 0 || end > buffer_row->length - 1) end = buffer_row->length - 1; /* Repeatedly convert chunks of terminal buffer rows until entire specified