diff --git a/src/terminal/select.c b/src/terminal/select.c index fdb5a7cf..2cbe9d4d 100644 --- a/src/terminal/select.c +++ b/src/terminal/select.c @@ -194,7 +194,7 @@ 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 || column >= terminal->selection_end_column + terminal->selection_end_width) { int width = guac_terminal_find_char(terminal, row, &column); diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index 68b3ad27..83ac15df 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -590,6 +590,10 @@ guac_terminal* guac_terminal_create(guac_client* client, /* Configure backspace */ term->backspace = options->backspace; + /* Initialize mouse latest click time and counter */ + term->click_timer = 0; + term->click_counter = 0; + return term; } @@ -1748,6 +1752,132 @@ int guac_terminal_send_key(guac_terminal* term, int keysym, int pressed) { } +/** + * Determines if the given character is part of a word. + * Match these chars :[0-9A-Za-z\$\%\&\-\.\/\:\=\?\\_~] + * This allows a path, URL, variable name or IP address to be treated as a word. + * + * @param ascii_char + * The character to check. + * + * @return + * true if match a "word" char, + * false otherwise. + */ +static bool guac_terminal_is_part_of_word(int ascii_char) { + return ((ascii_char >= '0' && ascii_char <= '9') || + (ascii_char >= 'A' && ascii_char <= 'Z') || + (ascii_char >= 'a' && ascii_char <= 'z') || + (ascii_char == '$') || + (ascii_char == '%') || + (ascii_char == '&') || + (ascii_char == '-') || + (ascii_char == '.') || + (ascii_char == '/') || + (ascii_char == ':') || + (ascii_char == '=') || + (ascii_char == '?') || + (ascii_char == '\\') || + (ascii_char == '_') || + (ascii_char == '~')); +} + +/** + * Determines if the given character is part of blank block. + * + * @param ascii_char + * The character to check. + * + * @return + * true if match space (char 0x20) or NULL (char 0x00), + * false otherwise. + */ +static bool guac_terminal_is_blank(int ascii_char) { + return (ascii_char == '\0' || ascii_char == ' '); +} + +/** + * Get the char (int ASCII code) at a specific row/col of the display. + * + * @param terminal + * The terminal on which we want to read a character. + * + * @param row + * The row where to read the character. + * + * @param col + * The column where to read the character. + * + * @return + * The ASCII code of the character at the given row/col. + */ +static int guac_terminal_get_char(guac_terminal* terminal, int row, int col) { + guac_terminal_buffer_row* buffer_row = guac_terminal_buffer_get_row(terminal->buffer, row, 0); + guac_terminal_char* ascii_char = &(buffer_row->characters[col]); + + return ascii_char->value; +} + +/** + * Selection of a word during a double click event. + * - Fetching the character under the mouse cursor. + * - Determining the type of character : + * Letter, digit, acceptable symbol within a word, + * or space/NULL, + * all other chars are treated as single. + * - Calculating the word boundaries. + * - Visual selection of the found word. + * - Adding it to clipboard. + * + * @param terminal + * The terminal that received a double click event. + * + * @param row + * The row where is the mouse at the double click event. + * + * @param col + * The column where is the mouse at the double click event. + */ +static void guac_terminal_double_click(guac_terminal* terminal, int row, int col) { + + /* (char)10 behind cursor */ + int cursor_char = guac_terminal_get_char(terminal, row, col); + + /* Position of the word behind cursor. + * Default = col required to select a char if not a word and not blank. */ + int word_head = col; + int word_tail = col; + int flag; + + /* The function used to calculate the word borders */ + bool (*is_part_of_word)(int) = NULL; + + /* If selection is on a word, get its borders */ + if (guac_terminal_is_part_of_word(cursor_char)) + is_part_of_word = guac_terminal_is_part_of_word; + + /* If selection is on a blank, get its borders */ + else if (guac_terminal_is_blank(cursor_char)) + is_part_of_word = guac_terminal_is_blank; + + if (is_part_of_word != NULL) { + /* Get word head*/ + do { + flag = guac_terminal_get_char(terminal, row, word_head-1); + } while (is_part_of_word(flag) && (word_head >= 0 && word_head <= terminal->display->width) && word_head--); + + /* Get word tail */ + do { + flag = guac_terminal_get_char(terminal, row, word_tail+1); + } while (is_part_of_word(flag) && (word_tail >= 0 && word_tail <= terminal->display->width) && word_tail++); + } + + /* Select and add to clipboard the "word" */ + guac_terminal_select_start(terminal, row, word_head); + guac_terminal_select_update(terminal, row, word_tail); + +} + static int __guac_terminal_send_mouse(guac_terminal* term, guac_user* user, int x, int y, int mask) { @@ -1813,8 +1943,34 @@ static int __guac_terminal_send_mouse(guac_terminal* term, guac_user* user, 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); + else { + + /* Reset click counter if last click was 300ms before */ + if (guac_timestamp_current() - term->click_timer > 300) + term->click_counter = 0; + + /* New click time */ + term->click_timer = guac_timestamp_current(); + + switch (term->click_counter++) { + + /* First click = start selection */ + case 0: + guac_terminal_select_start(term, row, col); + break; + + /* Second click = word selection */ + case 1: + guac_terminal_double_click(term, row, col); + break; + + /* third click or more = line selection */ + default: + guac_terminal_select_start(term, row, 0); + guac_terminal_select_update(term, row, term->display->width); + break; + } + } } /* In all other cases, simply update the existing selection as long as diff --git a/src/terminal/terminal/terminal-priv.h b/src/terminal/terminal/terminal-priv.h index f34715f3..1098c141 100644 --- a/src/terminal/terminal/terminal-priv.h +++ b/src/terminal/terminal/terminal-priv.h @@ -465,6 +465,16 @@ struct guac_terminal { */ bool disable_copy; + /** + * The time betwen two left clicks. + */ + guac_timestamp click_timer; + + /** + * Counter for left clicks. + */ + int click_counter; + }; /**