GUACAMOLE-573: Merge allow selection of text while scrolling.
diff --git a/src/terminal/Makefile.am b/src/terminal/Makefile.am
index 8121bd3..1fbded1 100644
--- a/src/terminal/Makefile.am
+++ b/src/terminal/Makefile.am
@@ -30,6 +30,7 @@
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 @@
named-colors.c \
palette.c \
scrollbar.c \
+ select.c \
terminal.c \
terminal_handlers.c \
typescript.c \
diff --git a/src/terminal/display.c b/src/terminal/display.c
index 9391ec1..c4c7471 100644
--- a/src/terminal/display.c
+++ b/src/terminal/display.c
@@ -37,54 +37,6 @@
#include <guacamole/socket.h>
#include <pango/pangocairo.h>
-/**
- * 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 @@
display->operations = NULL;
/* Initially nothing selected */
- display->text_selected =
- display->selection_committed = false;
+ display->text_selected = false;
return display;
@@ -413,11 +364,6 @@
}
- /* 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 @@
}
- /* 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 @@
}
- /* 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 @@
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_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 @@
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
new file mode 100644
index 0000000..131b5f6
--- /dev/null
+++ b/src/terminal/select.c
@@ -0,0 +1,452 @@
+/*
+ * 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 "terminal/buffer.h"
+#include "terminal/display.h"
+#include "terminal/select.h"
+#include "terminal/terminal.h"
+#include "terminal/types.h"
+
+#include <guacamole/client.h>
+#include <guacamole/socket.h>
+#include <guacamole/unicode.h>
+
+#include <stdbool.h>
+
+/**
+ * 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) {
+
+ /* Update the selected region of the display if text is currently
+ * selected */
+ if (terminal->text_selected) {
+
+ 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);
+
+ }
+
+ /* Clear the display selection if no text is currently selected */
+ else
+ guac_terminal_display_clear_select(terminal->display);
+
+}
+
+/**
+ * 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) {
+
+ 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) {
+
+ /* 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 = false;
+ terminal->selection_committed = false;
+ guac_terminal_notify(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;
+ terminal->text_selected = true;
+
+ guac_terminal_notify(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
+ * 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, 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) {
+
+ 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 < 0 || start > buffer_row->length - 1)
+ return;
+
+ /* Clip given range to actual bounds of row */
+ if (end < 0 || 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;
+
+ }
+
+ /* Append converted buffer to clipboard */
+ guac_common_clipboard_append(terminal->clipboard, buffer, current - buffer);
+
+ }
+
+}
+
+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");
+
+ int start_row, start_col;
+ int end_row, end_col;
+
+ /* Ensure proper ordering of start and end coords */
+ 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)
+ guac_terminal_clipboard_append_row(terminal, start_row, start_col, end_col);
+
+ /* Otherwise, copy multiple rows */
+ else {
+
+ /* Store first row */
+ guac_terminal_clipboard_append_row(terminal, start_row, start_col, -1);
+
+ /* Store all middle rows */
+ 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 */
+ guac_common_clipboard_append(terminal->clipboard, "\n", 1);
+ guac_terminal_clipboard_append_row(terminal, end_row, 0, end_col);
+
+ }
+
+ /* Send data */
+ 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 5ae2ee3..ce89856 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"
@@ -60,6 +61,9 @@
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);
+
}
/**
@@ -172,6 +176,7 @@
/* Reset flags */
term->text_selected = false;
+ term->selection_committed = false;
term->application_cursor_keys = false;
term->automatic_carriage_return = false;
term->insert_mode = false;
@@ -1018,6 +1023,12 @@
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 */
@@ -1269,184 +1280,6 @@
}
-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; row<end_row; row++) {
-
- buffer_row = guac_terminal_buffer_get_row(terminal->buffer, 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) {
@@ -1456,6 +1289,9 @@
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 &&
@@ -1477,6 +1313,10 @@
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)
@@ -1709,6 +1549,7 @@
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);
@@ -1906,9 +1747,6 @@
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;
@@ -1944,47 +1782,33 @@
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) {
+ /* Update selection state contextually while the left mouse button is
+ * pressed */
+ else if (mask & GUAC_CLIENT_MOUSE_LEFT) {
- int selected_length;
+ int row = y / term->display->char_height - term->scroll_offset;
+ int col = x / term->display->char_width;
- /* 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 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);
}
- /* Otherwise, just update */
+ /* 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 98337fd..e54003b 100644
--- a/src/terminal/terminal/display.h
+++ b/src/terminal/terminal/display.h
@@ -182,18 +182,11 @@
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.
*/
int selection_start_row;
@@ -333,10 +326,13 @@
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
new file mode 100644
index 0000000..1ae7d3d
--- /dev/null
+++ b/src/terminal/terminal/select.h
@@ -0,0 +1,179 @@
+/*
+ * 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"
+
+#include <stdbool.h>
+
+/**
+ * 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
+ * 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);
+
+/**
+ * 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
+ * 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.
+ */
+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 6094c39..8d6b48f 100644
--- a/src/terminal/terminal/terminal.h
+++ b/src/terminal/terminal/terminal.h
@@ -367,11 +367,19 @@
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.
*/
int selection_start_row;
@@ -697,23 +705,6 @@
*/
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 */