@@ -590,6 +590,10 @@ guac_terminal* guac_terminal_create(guac_client* client,
590590 /* Configure backspace */
591591 term -> backspace = options -> backspace ;
592592
593+ /* Initialize mouse latest click time and counter */
594+ term -> click_timer = 0 ;
595+ term -> click_counter = 0 ;
596+
593597 return term ;
594598
595599}
@@ -1748,6 +1752,132 @@ int guac_terminal_send_key(guac_terminal* term, int keysym, int pressed) {
17481752
17491753}
17501754
1755+ /**
1756+ * Determines if the given character is part of a word.
1757+ * Match these chars :[0-9A-Za-z\$\%\&\-\.\/\:\=\?\\_~]
1758+ * This allows a path, URL, variable name or IP address to be treated as a word.
1759+ *
1760+ * @param ascii_char
1761+ * The character to check.
1762+ *
1763+ * @return
1764+ * true if match a "word" char,
1765+ * false otherwise.
1766+ */
1767+ static bool guac_terminal_is_part_of_word (int ascii_char ) {
1768+ return ((ascii_char >= '0' && ascii_char <= '9' ) ||
1769+ (ascii_char >= 'A' && ascii_char <= 'Z' ) ||
1770+ (ascii_char >= 'a' && ascii_char <= 'z' ) ||
1771+ (ascii_char == '$' ) ||
1772+ (ascii_char == '%' ) ||
1773+ (ascii_char == '&' ) ||
1774+ (ascii_char == '-' ) ||
1775+ (ascii_char == '.' ) ||
1776+ (ascii_char == '/' ) ||
1777+ (ascii_char == ':' ) ||
1778+ (ascii_char == '=' ) ||
1779+ (ascii_char == '?' ) ||
1780+ (ascii_char == '\\' ) ||
1781+ (ascii_char == '_' ) ||
1782+ (ascii_char == '~' ));
1783+ }
1784+
1785+ /**
1786+ * Determines if the given character is part of blank block.
1787+ *
1788+ * @param ascii_char
1789+ * The character to check.
1790+ *
1791+ * @return
1792+ * true if match space (char 0x20) or NULL (char 0x00),
1793+ * false otherwise.
1794+ */
1795+ static bool guac_terminal_is_blank (int ascii_char ) {
1796+ return (ascii_char == '\0' || ascii_char == ' ' );
1797+ }
1798+
1799+ /**
1800+ * Get the char (int ASCII code) at a specific row/col of the display.
1801+ *
1802+ * @param terminal
1803+ * The terminal on which we want to read a character.
1804+ *
1805+ * @param row
1806+ * The row where to read the character.
1807+ *
1808+ * @param col
1809+ * The column where to read the character.
1810+ *
1811+ * @return
1812+ * The ASCII code of the character at the given row/col.
1813+ */
1814+ static int guac_terminal_get_char (guac_terminal * terminal , int row , int col ) {
1815+ guac_terminal_buffer_row * buffer_row = guac_terminal_buffer_get_row (terminal -> buffer , row , 0 );
1816+ guac_terminal_char * ascii_char = & (buffer_row -> characters [col ]);
1817+
1818+ return ascii_char -> value ;
1819+ }
1820+
1821+ /**
1822+ * Selection of a word during a double click event.
1823+ * - Fetching the character under the mouse cursor.
1824+ * - Determining the type of character :
1825+ * Letter, digit, acceptable symbol within a word,
1826+ * or space/NULL,
1827+ * all other chars are treated as single.
1828+ * - Calculating the word boundaries.
1829+ * - Visual selection of the found word.
1830+ * - Adding it to clipboard.
1831+ *
1832+ * @param terminal
1833+ * The terminal that received a double click event.
1834+ *
1835+ * @param row
1836+ * The row where is the mouse at the double click event.
1837+ *
1838+ * @param col
1839+ * The column where is the mouse at the double click event.
1840+ */
1841+ static void guac_terminal_double_click (guac_terminal * terminal , int row , int col ) {
1842+
1843+ /* (char)10 behind cursor */
1844+ int cursor_char = guac_terminal_get_char (terminal , row , col );
1845+
1846+ /* Position of the word behind cursor.
1847+ * Default = col required to select a char if not a word and not blank. */
1848+ int word_head = col ;
1849+ int word_tail = col ;
1850+ int flag ;
1851+
1852+ /* The function used to calculate the word borders */
1853+ bool (* is_part_of_word )(int ) = NULL ;
1854+
1855+ /* If selection is on a word, get its borders */
1856+ if (guac_terminal_is_part_of_word (cursor_char ))
1857+ is_part_of_word = guac_terminal_is_part_of_word ;
1858+
1859+ /* If selection is on a blank, get its borders */
1860+ else if (guac_terminal_is_blank (cursor_char ))
1861+ is_part_of_word = guac_terminal_is_blank ;
1862+
1863+ if (is_part_of_word != NULL ) {
1864+ /* Get word head*/
1865+ do {
1866+ flag = guac_terminal_get_char (terminal , row , word_head - 1 );
1867+ } while (is_part_of_word (flag ) && (word_head >= 0 && word_head <= terminal -> display -> width ) && word_head -- );
1868+
1869+ /* Get word tail */
1870+ do {
1871+ flag = guac_terminal_get_char (terminal , row , word_tail + 1 );
1872+ } while (is_part_of_word (flag ) && (word_tail >= 0 && word_tail <= terminal -> display -> width ) && word_tail ++ );
1873+ }
1874+
1875+ /* Select and add to clipboard the "word" */
1876+ guac_terminal_select_start (terminal , row , word_head );
1877+ guac_terminal_select_update (terminal , row , word_tail );
1878+
1879+ }
1880+
17511881static int __guac_terminal_send_mouse (guac_terminal * term , guac_user * user ,
17521882 int x , int y , int mask ) {
17531883
@@ -1813,8 +1943,34 @@ static int __guac_terminal_send_mouse(guac_terminal* term, guac_user* user,
18131943 if (pressed_mask & GUAC_CLIENT_MOUSE_LEFT ) {
18141944 if (term -> mod_shift )
18151945 guac_terminal_select_resume (term , row , col );
1816- else
1817- guac_terminal_select_start (term , row , col );
1946+ else {
1947+
1948+ /* Reset click counter if last click was 300ms before */
1949+ if (guac_timestamp_current () - term -> click_timer > 300 )
1950+ term -> click_counter = 0 ;
1951+
1952+ /* New click time */
1953+ term -> click_timer = guac_timestamp_current ();
1954+
1955+ switch (term -> click_counter ++ ) {
1956+
1957+ /* First click = start selection */
1958+ case 0 :
1959+ guac_terminal_select_start (term , row , col );
1960+ break ;
1961+
1962+ /* Second click = word selection */
1963+ case 1 :
1964+ guac_terminal_double_click (term , row , col );
1965+ break ;
1966+
1967+ /* third click or more = line selection */
1968+ default :
1969+ guac_terminal_select_start (term , row , 0 );
1970+ guac_terminal_select_update (term , row , term -> display -> width );
1971+ break ;
1972+ }
1973+ }
18181974 }
18191975
18201976 /* In all other cases, simply update the existing selection as long as
0 commit comments