diff --git a/src/protocols/ssh/input.c b/src/protocols/ssh/input.c index ba8867b47f..f45c7ba838 100644 --- a/src/protocols/ssh/input.c +++ b/src/protocols/ssh/input.c @@ -21,8 +21,10 @@ #include "common/cursor.h" #include "common/display.h" +#include "common/recording.h" #include "ssh.h" #include "terminal/terminal.h" +#include "terminal/terminal-priv.h" #include #include @@ -37,17 +39,117 @@ int guac_ssh_user_mouse_handler(guac_user* user, int x, int y, int mask) { guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; guac_terminal* term = ssh_client->term; + /* Get mouse information */ + term->select_row = y / term->display->char_height - term->scroll_offset; + term->select_col = x / term->display->char_width; + term->select_head = term->select_col; + term->select_tail = term->select_col; + int released_mask = term->mouse_mask & ~mask; + int pressed_mask = ~term->mouse_mask & mask; + /* Skip if terminal not yet ready */ if (term == NULL) return 0; /* Report mouse position within recording */ if (ssh_client->recording != NULL) - guac_recording_report_mouse(ssh_client->recording, x, y, mask); + guac_common_recording_report_mouse(ssh_client->recording, x, y, mask); + + /* Clear the selection effect if not on selecting*/ + if (term->mouse_state == 0 && (pressed_mask & GUAC_CLIENT_MOUSE_LEFT)) { + + /* Clear selection effect */ + guac_terminal_draw_blank(term); + + /* Send mouse event */ + guac_terminal_send_mouse(term, user, x, y, mask); + return 0; + } + + /* Start time recoding when mouse first pressed */ + else if (term->mouse_state == 0 && (released_mask & GUAC_CLIENT_MOUSE_LEFT)) { + + /* Start timer */ + term->start_1 = guac_timestamp_current(); + term->mouse_state = 1; + + /* Send mouse event */ + guac_terminal_send_mouse(term, user, x, y, mask); + return 0; + } + + /* Second click events */ + else if (term->mouse_state == 1 && (pressed_mask & GUAC_CLIENT_MOUSE_LEFT)) { + + term->end_1 = guac_timestamp_current(); + guac_timestamp interval_1 = term->end_1 - term->start_1; + + /* Time interval determination */ + if (interval_1 < 300){ + guac_terminal_double_click(term); + term->start_2 = guac_timestamp_current(); + } + else + term->mouse_state = 0; + + /* Send mouse event */ + guac_terminal_send_mouse(term, user, x, y, mask); + return 0; + } + + /* After second events */ + else if (term->mouse_state == 1 && (released_mask & GUAC_CLIENT_MOUSE_LEFT)) { + term->end_2 = guac_timestamp_current(); + guac_timestamp interval_2 = term->end_2 - term->start_2; + + /* Time interval determination */ + if (interval_2 < 300){ + term->start_3 = guac_timestamp_current(); + term->mouse_state = 2; + } + else + term->mouse_state = 0; + + /* Send mouse event */ + guac_terminal_send_mouse(term, user, x, y, mask); + return 0; + } + + /* Third click events */ + else if (term->mouse_state == 2 && (pressed_mask & GUAC_CLIENT_MOUSE_LEFT)) { + term->end_3 = guac_timestamp_current(); + guac_timestamp interval_3 = term->end_3 - term->start_3; + + /* Time interval determination */ + if (interval_3 < 300){ + guac_terminal_triple_click(term); + } + else { + guac_terminal_draw_blank(term); + term->mouse_state = 0; + } + + /* Send mouse event */ + guac_terminal_send_mouse(term, user, x, y, mask); + return 0; + } + + /* Other conditions */ + else if (term->mouse_state == 2 && (released_mask & GUAC_CLIENT_MOUSE_LEFT)) { + term->mouse_state = 0; + + /* Send mouse event */ + guac_terminal_send_mouse(term, user, x, y, mask); + return 0; + } + + else{ + + /* Send mouse event */ + guac_terminal_send_mouse(term, user, x, y, mask); + return 0; + } - /* Send mouse event */ - guac_terminal_send_mouse(term, user, x, y, mask); - return 0; } int guac_ssh_user_key_handler(guac_user* user, int keysym, int pressed) { diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index cb05ee5d32..a801361a09 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -49,6 +49,7 @@ #include #include #include +#include /** * Sets the given range of columns to the given character. @@ -588,6 +589,21 @@ guac_terminal* guac_terminal_create(guac_client* client, /* Configure backspace */ term->backspace = options->backspace; + /* Initialize mouse event related value */ + term->mouse_state = 0; + term->start_1 = 0; + term->start_2 = 0; + term->start_3 = 0; + term->end_1 = 0; + term->end_2 = 0; + term->end_3 = 0; + + /* Initialize selection related value */ + term->select_row = 0; + term->select_col = 0; + term->select_head = 0; + term->select_tail = 0; + return term; } @@ -2183,3 +2199,427 @@ void guac_terminal_remove_user(guac_terminal* terminal, guac_user* user) { /* Remove the user from the terminal cursor */ guac_common_cursor_remove_user(terminal->cursor, user); } + +void guac_terminal_draw_select(guac_terminal* terminal) { + + int height = terminal->display->char_height; + int width = terminal->display->char_width; + + guac_protocol_send_rect(terminal->display->client->socket, terminal->display->select_layer, + terminal->select_head * width, terminal->select_row * height, + (terminal->select_tail - terminal->select_head + 1) * width, height); + + guac_protocol_send_cfill(terminal->display->client->socket, GUAC_COMP_SRC, terminal->display->select_layer, + 0x00, 0x80, 0xFF, 0x60); + +} + +void guac_terminal_draw_blank(guac_terminal* terminal) { + + guac_protocol_send_rect(terminal->display->client->socket, terminal->display->select_layer, 0, 0, 1, 1); + + guac_protocol_send_cfill(terminal->display->client->socket, GUAC_COMP_SRC, terminal->display->select_layer, + 0x00, 0x00, 0x00, 0x00); + +} + +void guac_terminal_select_word(guac_terminal* terminal) { + + int head = terminal->select_head; + int tail = terminal->select_tail; + + /* Clear clipboard for new selection */ + guac_common_clipboard_reset(terminal->clipboard, "text/plain"); + char buffer[1024]; + int i = head; + + /* Get information of selected row */ + guac_terminal_buffer_row* buffer_row = guac_terminal_buffer_get_row(terminal->buffer, terminal->select_row, 0); + int index = (terminal->buffer->top + terminal->select_row) % terminal->buffer->available; + int length = buffer_row->length; + + if(index < 0) + index += terminal->buffer->available; + + if(head < 0 || head > length - 1) + return; + + if(tail < 0 || tail > length - 1) + tail = length - 1; + + /* Write each character to clipboard */ + + + while (i <= tail) { + + int remaining = sizeof(buffer); + char* current = buffer; + + for(i = head; i <= tail; i++) { + int codepoint = buffer_row->characters[i].value; + + if (codepoint == 0 || codepoint == GUAC_CHAR_CONTINUATION) + continue; + + int bytes = guac_utf8_write(codepoint, current, remaining); + + if (bytes == 0) + break; + + current += bytes; + remaining -= bytes; + } + guac_common_clipboard_append(terminal->clipboard, buffer, current - buffer); + guac_common_clipboard_send(terminal->clipboard, terminal->client); + } +} + +void guac_terminal_select_blank(guac_terminal* terminal) { + + int head = terminal->select_head; + int tail = terminal->select_tail; + + /* Clear clipboard for new selection */ + guac_common_clipboard_reset(terminal->clipboard, "text/plain"); + char buffer[1024]; + + /* Get information of selected row */ + guac_terminal_buffer_row* buffer_row = guac_terminal_buffer_get_row(terminal->buffer, terminal->select_row, 0); + int index = (terminal->buffer->top + terminal->select_row) % terminal->buffer->available; + int l = buffer_row->length; + + if(index < 0) + index += terminal->buffer->available; + + if(head < 0 || head > l - 1) + return; + + if(tail < 0 || tail > l - 1) + tail = l - 1; + + int i = head; + + /* Write blank to clipboard */ + while (i <= tail) + { + int remaining = sizeof(buffer); + char* current = buffer; + + for(i = head; i <= tail; i++) { + int codepoint = 32; + + int bytes = guac_utf8_write(codepoint, current, remaining); + + if (bytes == 0) + break; + + current += bytes; + remaining -= bytes; + } + guac_common_clipboard_append(terminal->clipboard, buffer, current - buffer); + guac_common_clipboard_send(terminal->clipboard, terminal->client); + } +} + +void guac_terminal_select_punc(guac_terminal* terminal) { + + /* Clear clipboard for new selection */ + guac_common_clipboard_reset(terminal->clipboard, "text/plain"); + char buffer[1024]; + char* current = buffer; + + /* Get information of selected row */ + guac_terminal_buffer_row* buffer_row = guac_terminal_buffer_get_row(terminal->buffer, terminal->select_row, 0); + + /* Write char to clipboard */ + int bytes = guac_utf8_write(buffer_row->characters[terminal->select_col].value, current, sizeof(buffer)); + current += bytes; + + guac_common_clipboard_append(terminal->clipboard, buffer, current - buffer); + guac_common_clipboard_send(terminal->clipboard, terminal->client); + +} + +void guac_terminal_select_line(guac_terminal* terminal) { + + /* Clear clipboard for new selection */ + guac_common_clipboard_reset(terminal->clipboard, "text/plain"); + char buffer[1024]; + + /* Get information of selected row */ + guac_terminal_buffer_row* buffer_row = guac_terminal_buffer_get_row(terminal->buffer, terminal->select_row, 0); + int index = (terminal->buffer->top + terminal->select_row) % terminal->buffer->available; + int i = 0; + int length = buffer_row->length; + + if(index < 0) + index += terminal->buffer->available; + + /* Write each character to clipboard */ + + while (i <= length) { + + int remaining = sizeof(buffer); + char* current = buffer; + + for(i = 0; i <= length; i++) { + int codepoint = buffer_row->characters[i].value; + + if (codepoint == 0) + codepoint = 32; + + if (codepoint == GUAC_CHAR_CONTINUATION) + continue; + + int bytes = guac_utf8_write(codepoint, current, remaining); + + if (bytes == 0) + break; + + current += bytes; + remaining -= bytes; + } + + guac_common_clipboard_append(terminal->clipboard, buffer, current - buffer); + guac_common_clipboard_send(terminal->clipboard, terminal->client); + } +} + +bool guac_terminal_is_part_of_word(int col){ + if ((col > GUAC_TERMINAL_ASCII_SLASH && col < GUAC_TERMINAL_ASCII_COLON) || + (col > GUAC_TERMINAL_ASCII_AT && col < GUAC_TERMINAL_ASCII_SQRBRK) || + (col > GUAC_TERMINAL_ASCII_SIGQUO && col < GUAC_TERMINAL_ASCII_BRACE) || + (col == GUAC_TERMINAL_ASCII_UDL) ) + return true; + else + return false; +} + +bool guac_terminal_is_blank(int col){ + if (col == GUAC_TERMINAL_ASCII_NULL || col == GUAC_TERMINAL_ASCII_SPACE) + return true; + else + return false; +} + +bool guac_terminal_is_punc(int col){ + if ((col > GUAC_TERMINAL_ASCII_SPACE && col < GUAC_TERMINAL_ASCII_0) || + (col > GUAC_TERMINAL_ASCII_9 && col < GUAC_TERMINAL_ASCII_A) || + (col > GUAC_TERMINAL_ASCII_Z && col < GUAC_TERMINAL_ASCII_UDL) || + (col > GUAC_TERMINAL_ASCII_z && col < GUAC_TERMINAL_ASCII_DEL) || + (col == GUAC_TERMINAL_ASCII_SIGQUO)) + return true; + else + return false; +} + +guac_terminal* guac_terminal_get_word_border(guac_terminal* terminal) { + + /* Set select information */ + int row = terminal->select_row; + int col = terminal->select_col; + int head = terminal->select_head; + int tail = terminal->select_tail; + int sentinel = terminal->display->operations[row * terminal->display->width + col].character.value; + int flag = sentinel; + + /* Get head*/ + while (guac_terminal_is_part_of_word(flag) && (head >= 0 && head <= terminal->display->width)) { + flag = terminal->display->operations[row * terminal->display->width + head].character.value; + head--; + } + + /* Reset falg */ + flag = sentinel; + + /* Get tail */ + while (guac_terminal_is_part_of_word(flag) && (tail >= 0 && tail <= terminal->display->width)) { + flag = terminal->display->operations[row * terminal->display->width + tail].character.value; + tail++; + } + + head += 2; + tail -= 2; + + /* Decide if head reaches first column */ + int h = terminal->display->operations[row * terminal->display->width + head - 1].character.value; + if (head == 1 && guac_terminal_is_part_of_word(h)) + head = 0; + + terminal->select_head = head; + terminal->select_tail = tail; + + return terminal; + +} + +guac_terminal* guac_terminal_get_blank_border(guac_terminal* terminal) { + + /* Set select information */ + int row = terminal->select_row; + int col = terminal->select_col; + int head = terminal->select_head; + int tail = terminal->select_tail; + int sentinel = terminal->display->operations[row * terminal->display->width + col].character.value; + int flag = sentinel; + + /* Get head*/ + while (guac_terminal_is_blank(flag) && (head >= 0 && head <= terminal->display->width)) { + flag = terminal->display->operations[row * terminal->display->width + head].character.value; + head--; + } + + /* Reset falg */ + flag = sentinel; + + /* Get tail */ + while (guac_terminal_is_blank(flag) && (tail >= 0 && tail <= terminal->display->width)) { + flag = terminal->display->operations[row * terminal->display->width + tail].character.value; + tail++; + } + + head += 2; + tail -= 2; + + /* Decide if head reaches first column */ + int h = terminal->display->operations[row * terminal->display->width + head - 1].character.value; + if (head == 1 && guac_terminal_is_blank(h)) + head = 0; + + terminal->select_head = head; + terminal->select_tail = tail; + + return terminal; +} + +void guac_terminal_double_click(guac_terminal* terminal) { + + /* Set selection variables */ + int row = terminal->select_row; + int col = terminal->select_col; + int sentinel = terminal->display->operations[row * terminal->display->width + col].character.value; + + /* Determination of which kind of selection */ + + /* Word */ + if(guac_terminal_is_part_of_word(sentinel)) { + + /* Get border */ + terminal = guac_terminal_get_word_border(terminal); + + /* Copy to clipboard */ + guac_terminal_select_word(terminal); + + /* Draw selection */ + guac_terminal_draw_select(terminal); + + return; + } + + /* Punctuation Marks */ + else if (guac_terminal_is_punc(sentinel)) { + + /* Set selection border */ + terminal->select_head = terminal->select_col; + terminal->select_tail = terminal->select_col; + + /* Copy to clipboard */ + guac_terminal_select_punc(terminal); + + /* Draw selection */ + guac_terminal_draw_select(terminal); + + return; + } + + /* Blank */ + else if (guac_terminal_is_blank(sentinel)) { + + /* Get blank border */ + terminal = guac_terminal_get_blank_border(terminal); + + /* Get border information */ + int head = terminal->select_head; + int tail = terminal->select_tail; + int width = terminal->display->width; + + /* Blank before text ends */ + if (head == 0 || tail < width - 1){ + + /* Get border */ + terminal = guac_terminal_get_blank_border(terminal); + + /* Copy to clipboard */ + guac_terminal_select_blank(terminal); + + /* Draw selection */ + guac_terminal_draw_select(terminal); + + return; + } + + /* Blank at end of text ends */ + else if (head > 0 && tail == width - 1){ + + /*Move selected column*/ + sentinel = terminal->display->operations[row * terminal->display->width + col].character.value; + while(guac_terminal_is_blank(sentinel)) { + col--; + sentinel = terminal->display->operations[row * terminal->display->width + col].character.value; + } + + /* Set information and make selection at the end of text */ + terminal->select_col = col; + terminal->select_head = col; + terminal->select_tail = col; + sentinel = terminal->display->operations[row * terminal->display->width + col].character.value; + + /* Determination of which kind of selection */ + + /* Word */ + if (guac_terminal_is_part_of_word(sentinel)) { + + /* Get word border */ + terminal = guac_terminal_get_word_border(terminal); + + /* Copy to clipboard */ + guac_terminal_select_word(terminal); + + /* Draw selection */ + guac_terminal_draw_select(terminal); + + return; + } + + /* Punctuation marks */ + else if (guac_terminal_is_punc(sentinel)) { + + /* Set selection border */ + terminal->select_head = terminal->select_col; + terminal->select_tail = terminal->select_col; + + /* Copy to clipboard */ + guac_terminal_select_punc(terminal); + + /* Draw selection */ + guac_terminal_draw_select(terminal); + + return; + } + } + } +} + +void guac_terminal_triple_click(guac_terminal* terminal){ + + /* Set selection border */ + terminal->select_head = 0; + terminal->select_tail = terminal->display->width; + + /* Make selection */ + guac_terminal_select_line(terminal); + guac_terminal_draw_select(terminal); + + return; +} + diff --git a/src/terminal/terminal/terminal-priv.h b/src/terminal/terminal/terminal-priv.h index 4fe8550073..6926536818 100644 --- a/src/terminal/terminal/terminal-priv.h +++ b/src/terminal/terminal/terminal-priv.h @@ -29,6 +29,101 @@ #include "terminal.h" #include "typescript.h" +/** + * The ASCII number of NULL + */ +#define GUAC_TERMINAL_ASCII_NULL 0 + +/** + * The ASCII number of space + */ +#define GUAC_TERMINAL_ASCII_SPACE 32 + +/** + * The ASCII number of ! + */ +#define GUAC_TERMINAL_ASCII_EXCLAMATION 33 + +/** + * The ASCII number of / + */ +#define GUAC_TERMINAL_ASCII_SLASH 47 + +/** + * The ASCII number of 0 + */ +#define GUAC_TERMINAL_ASCII_0 48 + +/** + * The ASCII number of 9 + */ +#define GUAC_TERMINAL_ASCII_9 57 + +/** + * The ASCII number of : + */ +#define GUAC_TERMINAL_ASCII_COLON 58 + +/** + * The ASCII number of @ + */ +#define GUAC_TERMINAL_ASCII_AT 64 + +/** + * The ASCII number of A + */ +#define GUAC_TERMINAL_ASCII_A 65 + +/** + * The ASCII number of Z + */ +#define GUAC_TERMINAL_ASCII_Z 90 + +/** + * The ASCII number of [ + */ +#define GUAC_TERMINAL_ASCII_SQRBRK 91 + +/** + * The ASCII number of ^ + */ +#define GUAC_TERMINAL_ASCII_CARET 94 + +/** + * The ASCII number of _ + */ +#define GUAC_TERMINAL_ASCII_UDL 95 + +/** + * The ASCII number of ` + */ +#define GUAC_TERMINAL_ASCII_SIGQUO 96 + +/** + * The ASCII number of a + */ +#define GUAC_TERMINAL_ASCII_a 97 + +/** + * The ASCII number of z + */ +#define GUAC_TERMINAL_ASCII_z 122 + +/** + * The ASCII number of { + */ +#define GUAC_TERMINAL_ASCII_BRACE 123 + +/** + * The ASCII number of ~ + */ +#define GUAC_TERMINAL_ASCII_WAVE 126 + +/** + * The ASCII number of DEL + */ +#define GUAC_TERMINAL_ASCII_DEL 127 + struct guac_terminal { /** @@ -636,5 +731,70 @@ void guac_terminal_copy_rows(guac_terminal* terminal, */ void guac_terminal_flush(guac_terminal* terminal); +/** + * Make visual effect of selection. + */ +void guac_terminal_draw_select(guac_terminal* terminal); + +/** + * Discard visual effect when not on selection. + */ +void guac_terminal_draw_blank(guac_terminal* terminal); + +/** + * Write selected word to clipboard. + */ +void guac_terminal_select_word(guac_terminal* terminal); + +/** + * Write selected blank to clipboard. + */ +void guac_terminal_select_blank(guac_terminal* terminal); + +/** + * Write selected punctuation mark to clipboard. + */ +void guac_terminal_select_punc(guac_terminal* terminal); + +/** + * Write selected line to clipboard. + */ +void guac_terminal_select_line(guac_terminal* terminal); + +/** + * Determination of part of word + */ +bool guac_terminal_is_part_of_word(int col); + +/** + * Determination of blank + */ +bool guac_terminal_is_blank(int col); + +/** + * Determination of part of punctuation mark + */ +bool guac_terminal_is_punc(int col); + +/** + * Get the border of the word to be selected. + */ +guac_terminal* guac_terminal_get_word_border(guac_terminal* terminal); + +/** + * Get the border of the blank to be selected. + */ +guac_terminal* guac_terminal_get_blank_border(guac_terminal* terminal); + +/** + * Double click event + */ +void guac_terminal_double_click(guac_terminal* terminal); + +/** + * Triple click event + */ +void guac_terminal_triple_click(guac_terminal* terminal); + #endif