Commit 805ffcfd for guacamole.apache.org

commit 805ffcfd9c4477c4fb982052b9d25b5cb3acd167
Merge: e775052a 889ad1cd
Author: Virtually Nick <vnick@apache.org>
Date:   Thu Jun 18 15:47:58 2026 -0400

    Merge patch branch changes to main.

diff --cc src/protocols/kubernetes/kubernetes.c
index c18f1518,af2005f7..fc142abc
--- a/src/protocols/kubernetes/kubernetes.c
+++ b/src/protocols/kubernetes/kubernetes.c
@@@ -254,7 -287,7 +289,8 @@@ void* guac_kubernetes_client_thread(voi
      options->font_size = settings->font_size;
      options->color_scheme = settings->color_scheme;
      options->backspace = settings->backspace;
 +    options->func_keys_and_keypad = settings->func_keys_and_keypad;
+     options->linux_console_keys = strcmp(settings->terminal_type, "linux") == 0;

      /* Create terminal */
      kubernetes_client->term = guac_terminal_create(client, options);
diff --cc src/protocols/kubernetes/settings.c
index 710f2cc4,f77c6766..2b39da4f
--- a/src/protocols/kubernetes/settings.c
+++ b/src/protocols/kubernetes/settings.c
@@@ -58,10 -57,9 +58,11 @@@ const char* GUAC_KUBERNETES_CLIENT_ARGS
      "read-only",
      "backspace",
      "scrollback",
 +    "func-keys-and-keypad",
 +    "clipboard-buffer-size",
      "disable-copy",
      "disable-paste",
+     "terminal-type",
      NULL
  };

diff --cc src/protocols/kubernetes/settings.h
index 01db2420,50e80612..fed7f584
--- a/src/protocols/kubernetes/settings.h
+++ b/src/protocols/kubernetes/settings.h
@@@ -268,12 -259,12 +274,18 @@@ typedef struct guac_kubernetes_setting
       */
      int backspace;

 +    /**
 +     * The family of codes (e.g. vt100) which will be used when you push
 +     * the function and keypad keys.
 +     */
 +    char* func_keys_and_keypad;
 +
+     /**
+      * The terminal emulator type that is connected to this server (e.g.
+      * "xterm" or "xterm-256color"). "linux" is used if unspecified.
+      */
+     char* terminal_type;
+
  } guac_kubernetes_settings;

  /**
diff --cc src/protocols/ssh/ssh.c
index 5bf38633,cd91bd9f..4f1631c9
--- a/src/protocols/ssh/ssh.c
+++ b/src/protocols/ssh/ssh.c
@@@ -308,7 -315,7 +317,8 @@@ void* ssh_client_thread(void* data)
      options->font_size = settings->font_size;
      options->color_scheme = settings->color_scheme;
      options->backspace = settings->backspace;
 +    options->func_keys_and_keypad = settings->func_keys_and_keypad;
+     options->linux_console_keys = (strcmp(settings->terminal_type, "linux") == 0);

      /* Create terminal */
      ssh_client->term = guac_terminal_create(client, options);
diff --cc src/protocols/telnet/telnet.c
index 5ffc683a,948cdc4a..bbcd740a
--- a/src/protocols/telnet/telnet.c
+++ b/src/protocols/telnet/telnet.c
@@@ -564,7 -576,7 +578,8 @@@ void* guac_telnet_client_thread(void* d
      options->font_size = settings->font_size;
      options->color_scheme = settings->color_scheme;
      options->backspace = settings->backspace;
 +    options->func_keys_and_keypad = settings->func_keys_and_keypad;
+     options->linux_console_keys = (strcmp(settings->terminal_type, "linux") == 0);

      /* Create terminal */
      telnet_client->term = guac_terminal_create(client, options);
diff --cc src/terminal/terminal.c
index 5cfa6917,a8ab2306..cc8b4b6b
--- a/src/terminal/terminal.c
+++ b/src/terminal/terminal.c
@@@ -423,8 -567,9 +569,9 @@@ guac_terminal* guac_terminal_create(gua
      /* Init terminal state */
      term->current_attributes = default_char.attributes;
      term->default_char = default_char;
 -    term->clipboard = guac_common_clipboard_alloc();
 +    term->clipboard = guac_common_clipboard_alloc(options->clipboard_buffer_size);
      term->disable_copy = options->disable_copy;
+     term->linux_console_keys = options->linux_console_keys;

      /* Calculate available text display area by character size */
      int rows, columns;
@@@ -1451,16 -1807,26 +1819,35 @@@ static int __guac_terminal_send_key(gua
          guac_terminal_notify(term);
      }

 +    /* Track modifiers */
 +    if (keysym == GUAC_TERMINAL_KEY_CTRL_L || keysym == GUAC_TERMINAL_KEY_CTRL_R)
 +        term->mod_ctrl = pressed;
 +    else if (keysym == GUAC_TERMINAL_KEY_META_L || keysym == GUAC_TERMINAL_KEY_META_R)
 +        term->mod_meta = pressed;
 +    else if (keysym == GUAC_TERMINAL_KEY_ALT_L || keysym == GUAC_TERMINAL_KEY_ALT_R)
 +        term->mod_alt = pressed;
 +    else if (keysym == GUAC_TERMINAL_KEY_SHIFT_L || keysym == GUAC_TERMINAL_KEY_SHIFT_R)
++
+     /*
+      * Super (Windows/Command) and Hyper are treated as Meta since terminals don't
+      * have separate modifier bits for them.
+      *
+      * MODE_SWITCH and the ISO level selectors are intentionally not treated as
+      * Alt. On international layouts these are often used to produce printable
+      * third-/fifth-level characters, and folding them into Alt would inject an
+      * unwanted ESC prefix into normal text input.
+      */
+     if (keysym == GUAC_KEYSYM_CTRL_L || keysym == GUAC_KEYSYM_CTRL_R)
+         term->mod_ctrl = pressed;
+     else if (keysym == GUAC_KEYSYM_META_L || keysym == GUAC_KEYSYM_META_R
+             || keysym == GUAC_KEYSYM_SUPER_L || keysym == GUAC_KEYSYM_SUPER_R
+             || keysym == GUAC_KEYSYM_HYPER_L || keysym == GUAC_KEYSYM_HYPER_R)
+         term->mod_meta = pressed;
+     else if (keysym == GUAC_KEYSYM_ALT_L || keysym == GUAC_KEYSYM_ALT_R)
+         term->mod_alt = pressed;
+     else if (keysym == GUAC_KEYSYM_SHIFT_L || keysym == GUAC_KEYSYM_SHIFT_R)
          term->mod_shift = pressed;
-
+
      /* If key pressed */
      else if (pressed) {

@@@ -1527,11 -1869,18 +1914,11 @@@
          if (term->scroll_offset != 0)
              guac_terminal_scroll_display_down(term, term->scroll_offset);

 -        /*
 -         * Modified arrows, function keys, and cursor editing keys encode Alt
 -         * and Meta directly in CSI (below). For all other keys, Alt/Meta are
 -         * represented by prefixing ESC.
 -         */
 -        if ((term->mod_alt || term->mod_meta)
 -                && !__guac_terminal_is_arrow_keysym(keysym)
 -                && !__guac_terminal_is_function_keysym(keysym)
 -                && !__guac_terminal_is_editing_keysym(keysym))
 -            guac_terminal_send_string(term, "\x1B");
 +        /* If alt being held, also send escape character */
 +        if (term->mod_alt)
 +            guac_terminal_send_string(term, GUAC_TERMINAL_ASCII_ESCAPE);

-         /* Translate Ctrl+letter to control code */
+         /* Translate Ctrl+letter to control code */
          if (term->mod_ctrl) {

              char data;
@@@ -1556,25 -1905,16 +1943,26 @@@
              else if (keysym >= '3' && keysym <= '7')
                  data = (char) (keysym - '3' + 0x1B);

 -            else {
 -                /* No C0 mapping: encode as CSI modifier sequence if applicable */
 -                if (__guac_terminal_is_arrow_keysym(keysym))
 -                    return __guac_terminal_send_modified_arrow(term, keysym);
 -                if (__guac_terminal_is_function_keysym(keysym))
 -                    return __guac_terminal_send_modified_function(term, keysym);
 -                if (__guac_terminal_is_editing_keysym(keysym))
 -                    return __guac_terminal_send_modified_editing(term, keysym);
 +            /* CTRL+Left: return to previous word  */
 +            else if (keysym == GUAC_TERMINAL_KEY_LEFT || keysym == GUAC_TERMINAL_KEY_KP_LEFT)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_PREV_WORD);
 +
 +            /* CTRL+Right: go to next word */
 +            else if (keysym == GUAC_TERMINAL_KEY_RIGHT || keysym == GUAC_TERMINAL_KEY_KP_RIGHT)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_NEXT_WORD);
 +
 +            /* CTRL+Backspace: remove word (map to CTRL+w) */
 +            else if (keysym == GUAC_TERMINAL_KEY_BACKSPACE)
 +                data = (char) 23;
 +
 +            /* CTRL+Supr: remove word to right */
 +            else if (keysym == GUAC_TERMINAL_KEY_DELETE || keysym == GUAC_TERMINAL_KEY_KP_DELETE)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_DELETE_WORD);
 +
 +            /* Otherwise ignore */
 +            else
                  return 0;
+             }

              return guac_terminal_send_data(term, &data, 1);

@@@ -1645,144 -1947,73 +2034,154 @@@
                  char backspace_str[] = { term->backspace, '\0' };
                  return guac_terminal_send_string(term, backspace_str);
              }
 -            if (keysym == GUAC_KEYSYM_TAB || keysym == GUAC_KEYSYM_KP_TAB) return guac_terminal_send_string(term, "\x09");
 -            if (keysym == GUAC_KEYSYM_LINE_FEED) return guac_terminal_send_string(term, "\x0A");
 -            if (keysym == GUAC_KEYSYM_ENTER || keysym == GUAC_KEYSYM_KP_ENTER) return guac_terminal_send_string(term, "\x0D");
 -            if (keysym == GUAC_KEYSYM_ESCAPE) return guac_terminal_send_string(term, "\x1B");

 -            /* Cursor editing keys (Home, End, Insert, Delete, Page Up, Page Down) w/ modifiers */
 -            if (__guac_terminal_any_modifier(term) && __guac_terminal_is_editing_keysym(keysym))
 -                return __guac_terminal_send_modified_editing(term, keysym);
 +            if (keysym == 0xFF09 || keysym == 0xFF89) return guac_terminal_send_string(term, "\x09"); /* Tab */
 +            if (keysym == 0xFF0A) return guac_terminal_send_string(term, "\x0A");                     /* Line Feed */
 +            if (keysym == 0xFF0D || keysym == 0xFF8D) return guac_terminal_send_string(term, "\x0D"); /* Enter */
 +            if (keysym == 0xFF1B) return guac_terminal_send_string(term, "\x1B");                     /* Esc */
 +
 +            /* Tab */
 +            if (keysym == GUAC_TERMINAL_KEY_TAB || keysym == GUAC_TERMINAL_KEY_KP_TAB)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ASCII_TAB);
 +
 +            /* Enter */
 +            if (keysym == GUAC_TERMINAL_KEY_ENTER || keysym == GUAC_TERMINAL_KEY_KP_ENTER)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ASCII_CR);
 +
 +            /* Esc */
 +            if (keysym == GUAC_TERMINAL_KEY_ESCAPE)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ASCII_ESCAPE);
 +
 +            /* Home */
 +            if (keysym == GUAC_TERMINAL_KEY_HOME || keysym == GUAC_TERMINAL_KEY_KP_HOME)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_HOME);

-             /* Arrow keys w/ application cursor */
-             if (term->application_cursor_keys) {
+             if (keysym == GUAC_KEYSYM_HOME || keysym == GUAC_KEYSYM_KP_HOME)
+                 return guac_terminal_send_string(term, term->linux_console_keys ? "\x1B[1~" : "\x1B[H");
+
+             if (keysym == GUAC_KEYSYM_END || keysym == GUAC_KEYSYM_KP_END)
+                 return guac_terminal_send_string(term, term->linux_console_keys ? "\x1B[4~" : "\x1B[F");
+
+             /* Arrow keys w/ modifiers */
+             if (__guac_terminal_any_modifier(term) && __guac_terminal_is_arrow_keysym(keysym))
+                 return __guac_terminal_send_modified_arrow(term, keysym);

+             /* Application cursor mode (DECCKM) affects only arrow keys.
+              * Home and End use their own sequences above and are unaffected. */
+             if (term->application_cursor_keys) {
 -                if (keysym == GUAC_KEYSYM_LEFT || keysym == GUAC_KEYSYM_KP_LEFT) return guac_terminal_send_string(term, "\x1BOD");
 -                if (keysym == GUAC_KEYSYM_UP || keysym == GUAC_KEYSYM_KP_UP) return guac_terminal_send_string(term, "\x1BOA");
 -                if (keysym == GUAC_KEYSYM_RIGHT || keysym == GUAC_KEYSYM_KP_RIGHT) return guac_terminal_send_string(term, "\x1BOC");
 -                if (keysym == GUAC_KEYSYM_DOWN || keysym == GUAC_KEYSYM_KP_DOWN) return guac_terminal_send_string(term, "\x1BOB");
 +                /* Left */
 +                if (keysym == GUAC_TERMINAL_KEY_LEFT || keysym == GUAC_TERMINAL_KEY_KP_LEFT)
 +                    return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_APP_CURSOR_LEFT);
 +
 +                /* Up */
 +                if (keysym == GUAC_TERMINAL_KEY_UP || keysym == GUAC_TERMINAL_KEY_KP_UP)
 +                    return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_APP_CURSOR_UP);
 +
 +                /* Right */
 +                if (keysym == GUAC_TERMINAL_KEY_RIGHT || keysym == GUAC_TERMINAL_KEY_KP_RIGHT)
 +                    return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_APP_CURSOR_RIGHT);
 +
 +                /* Down */
 +                if (keysym == GUAC_TERMINAL_KEY_DOWN || keysym == GUAC_TERMINAL_KEY_KP_DOWN)
 +                    return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_APP_CURSOR_DOWN);
              }
              else {
 -                if (keysym == GUAC_KEYSYM_LEFT || keysym == GUAC_KEYSYM_KP_LEFT) return guac_terminal_send_string(term, "\x1B[D");
 -                if (keysym == GUAC_KEYSYM_UP || keysym == GUAC_KEYSYM_KP_UP) return guac_terminal_send_string(term, "\x1B[A");
 -                if (keysym == GUAC_KEYSYM_RIGHT || keysym == GUAC_KEYSYM_KP_RIGHT) return guac_terminal_send_string(term, "\x1B[C");
 -                if (keysym == GUAC_KEYSYM_DOWN || keysym == GUAC_KEYSYM_KP_DOWN) return guac_terminal_send_string(term, "\x1B[B");
 -            }

 -            if (keysym == GUAC_KEYSYM_PAGE_UP || keysym == GUAC_KEYSYM_KP_PAGE_UP) return guac_terminal_send_string(term, "\x1B[5~");
 -            if (keysym == GUAC_KEYSYM_PAGE_DOWN || keysym == GUAC_KEYSYM_KP_PAGE_DOWN) return guac_terminal_send_string(term, "\x1B[6~");
 +                /* Left */
 +                if (keysym == GUAC_TERMINAL_KEY_LEFT || keysym == GUAC_TERMINAL_KEY_KP_LEFT)
 +                    return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_CURSOR_LEFT);

 -            if (keysym == GUAC_KEYSYM_INSERT || keysym == GUAC_KEYSYM_KP_INSERT) return guac_terminal_send_string(term, "\x1B[2~");
 +                /* Up */
 +                if (keysym == GUAC_TERMINAL_KEY_UP || keysym == GUAC_TERMINAL_KEY_KP_UP)
 +                    return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_CURSOR_UP);

 -            if (__guac_terminal_any_modifier(term) && __guac_terminal_is_function_keysym(keysym))
 -                return __guac_terminal_send_modified_function(term, keysym);
 +                /* Right */
 +                if (keysym == GUAC_TERMINAL_KEY_RIGHT || keysym == GUAC_TERMINAL_KEY_KP_RIGHT)
 +                    return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_CURSOR_RIGHT);

 -            /* F1-F5: Linux console uses ESC [ [ A-E; xterm/VT220 uses SS3 and CSI tilde */
 -            if (term->linux_console_keys) {
 -                if (keysym == GUAC_KEYSYM_F1 || keysym == GUAC_KEYSYM_KP_F1) return guac_terminal_send_string(term, "\x1B[[A");
 -                if (keysym == GUAC_KEYSYM_F2 || keysym == GUAC_KEYSYM_KP_F2) return guac_terminal_send_string(term, "\x1B[[B");
 -                if (keysym == GUAC_KEYSYM_F3 || keysym == GUAC_KEYSYM_KP_F3) return guac_terminal_send_string(term, "\x1B[[C");
 -                if (keysym == GUAC_KEYSYM_F4 || keysym == GUAC_KEYSYM_KP_F4) return guac_terminal_send_string(term, "\x1B[[D");
 -                if (keysym == GUAC_KEYSYM_F5) return guac_terminal_send_string(term, "\x1B[[E");
 +                /* Down */
 +                if (keysym == GUAC_TERMINAL_KEY_DOWN || keysym == GUAC_TERMINAL_KEY_KP_DOWN)
 +                    return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_CURSOR_DOWN);
 +            }
 +
 +            /* Page up */
 +            if (keysym == GUAC_TERMINAL_KEY_PAGEUP || keysym == GUAC_TERMINAL_KEY_KP_PAGEUP)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_PAGEUP);
 +
 +            /* Page down */
 +            if (keysym == GUAC_TERMINAL_KEY_PAGEDOWN || keysym == GUAC_TERMINAL_KEY_KP_PAGEDOWN)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_PAGEDOWN);
 +
 +            /* End */
 +            if (keysym == GUAC_TERMINAL_KEY_END || keysym == GUAC_TERMINAL_KEY_KP_END)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_END);
 +
 +            /* Insert */
 +            if (keysym == GUAC_TERMINAL_KEY_INSERT || keysym == GUAC_TERMINAL_KEY_KP_INSERT)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_INSERT);
 +
 +            /* F1  */
 +            if (term->func_keys_and_keypad == GUAC_TERMINAL_FUNC_KEYS_AND_KEYPAD_VT100) {
 +                /* https://vt100.net/docs/vt100-ug/chapter3.html */
 +                if (keysym == 0xFFBE || keysym == 0xFF91) return guac_terminal_send_string(term, "\x1BOP"); /* F1  */
 +                if (keysym == 0xFFBF || keysym == 0xFF92) return guac_terminal_send_string(term, "\x1BOQ"); /* F2  */
 +                if (keysym == 0xFFC0 || keysym == 0xFF93) return guac_terminal_send_string(term, "\x1BOR"); /* F3  */
 +                if (keysym == 0xFFC1 || keysym == 0xFF94) return guac_terminal_send_string(term, "\x1BOS"); /* F4  */
 +                /* Send this escape code, although the original VT100 did not have F5 */
 +                if (keysym == 0xFFC2) return guac_terminal_send_string(term, "\x1B[15~"); /* F5  */
              }
              else {
 -                if (keysym == GUAC_KEYSYM_F1 || keysym == GUAC_KEYSYM_KP_F1) return guac_terminal_send_string(term, "\x1BOP");
 -                if (keysym == GUAC_KEYSYM_F2 || keysym == GUAC_KEYSYM_KP_F2) return guac_terminal_send_string(term, "\x1BOQ");
 -                if (keysym == GUAC_KEYSYM_F3 || keysym == GUAC_KEYSYM_KP_F3) return guac_terminal_send_string(term, "\x1BOR");
 -                if (keysym == GUAC_KEYSYM_F4 || keysym == GUAC_KEYSYM_KP_F4) return guac_terminal_send_string(term, "\x1BOS");
 -                if (keysym == GUAC_KEYSYM_F5) return guac_terminal_send_string(term, "\x1B[15~");
 +                if (keysym == GUAC_TERMINAL_KEY_F1 || keysym == GUAC_TERMINAL_KEY_KP_F1)
 +                    return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_F1);
 +
 +                /* F2  */
 +                if (keysym == GUAC_TERMINAL_KEY_F2 || keysym == GUAC_TERMINAL_KEY_KP_F2)
 +                    return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_F2);
 +
 +                /* F3  */
 +                if (keysym == GUAC_TERMINAL_KEY_F3 || keysym == GUAC_TERMINAL_KEY_KP_F3)
 +                    return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_F3);
 +
 +                /* F4  */
 +                if (keysym == GUAC_TERMINAL_KEY_F4 || keysym == GUAC_TERMINAL_KEY_KP_F4)
 +                    return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_F4);
 +
 +                /* F5  */
 +                if (keysym == GUAC_TERMINAL_KEY_F5)
 +                    return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_F5);
 +
              }
 +            /* F6  */
 +            if (keysym == GUAC_TERMINAL_KEY_F6)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_F6);
 +
 +            /* F7  */
 +            if (keysym == GUAC_TERMINAL_KEY_F7)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_F7);

 -            if (keysym == GUAC_KEYSYM_F6) return guac_terminal_send_string(term, "\x1B[17~");
 -            if (keysym == GUAC_KEYSYM_F7) return guac_terminal_send_string(term, "\x1B[18~");
 -            if (keysym == GUAC_KEYSYM_F8) return guac_terminal_send_string(term, "\x1B[19~");
 -            if (keysym == GUAC_KEYSYM_F9) return guac_terminal_send_string(term, "\x1B[20~");
 -            if (keysym == GUAC_KEYSYM_F10) return guac_terminal_send_string(term, "\x1B[21~");
 -            if (keysym == GUAC_KEYSYM_F11) return guac_terminal_send_string(term, "\x1B[23~");
 -            if (keysym == GUAC_KEYSYM_F12) return guac_terminal_send_string(term, "\x1B[24~");
 +            /* F8  */
 +            if (keysym == GUAC_TERMINAL_KEY_F8)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_F8);

 -            if (keysym == GUAC_KEYSYM_DELETE || keysym == GUAC_KEYSYM_KP_DELETE) return guac_terminal_send_string(term, "\x1B[3~");
 +            /* F9  */
 +            if (keysym == GUAC_TERMINAL_KEY_F9)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_F9);
 +
 +            /* F10 */
 +            if (keysym == GUAC_TERMINAL_KEY_F10)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_F10);
 +
 +            /* F11 */
 +            if (keysym == GUAC_TERMINAL_KEY_F11)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_F11);
 +
 +            /* F12 */
 +            if (keysym == GUAC_TERMINAL_KEY_F12)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_F12);
 +
 +            /* Delete */
 +            if (keysym == GUAC_TERMINAL_KEY_DELETE || keysym == GUAC_TERMINAL_KEY_KP_DELETE)
 +                return guac_terminal_send_string(term, GUAC_TERMINAL_ESC_SEQ_DELETE);

              /* Ignore unknown keys */
              guac_client_log(term->client, GUAC_LOG_DEBUG,
@@@ -1820,12 -2051,12 +2219,12 @@@ int guac_terminal_send_key(guac_termina
   *     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') ||
+     return ((ascii_char >= '0' && ascii_char <= '9') ||
+             (ascii_char >= 'A' && ascii_char <= 'Z') ||
              (ascii_char >= 'a' && ascii_char <= 'z') ||
 +            (ascii_char >= GUAC_TERMINAL_LATIN1_CAPITAL_AGRAVE &&
 +             ascii_char <= GUAC_TERMINAL_LATIN1_Y_UMLAUT) ||
              (ascii_char == '$') ||
 -            (ascii_char == '%') ||
 -            (ascii_char == '&') ||
              (ascii_char == '-') ||
              (ascii_char == '.') ||
              (ascii_char == '/') ||
@@@ -2103,182 -2098,58 +2502,182 @@@ static void guac_terminal_get_word_boun
   *
   * @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.
 + *
 + * @param hold
 + *      True when user hold left click, false otherwise.
   */
 -static void guac_terminal_double_click(guac_terminal* terminal, int row, int col) {
 +static void guac_terminal_double_click(guac_terminal* terminal, int row, int col, bool hold) {

 +    /* To store buffer row characters */
      guac_terminal_char* characters;
 -    int length = guac_terminal_buffer_get_columns(terminal->current_buffer, &characters, NULL, row);
 +    /* To get wrapped buffer row status */
 +    bool is_wrapped;
 +    /* Length of the buffer row */
 +    int length;
 +    /* Character read at a position */
 +    int current_char;
 +
 +    /* Position of the detected word. Default = col/row required to select
 +     * a char if not a word and not blank */
 +    int word_col_head = col;
 +    int word_col_tail = col;
 +    int word_row_head = row;
 +    int word_row_tail = row;
 +
 +    /* Position of the detected URI */
 +    int uri_col_head;
 +    int uri_col_tail;
 +    int uri_row_head;
 +    int uri_row_tail;
 +
 +    /* User holds left click: update default selection boundaries */
 +    if (hold)
 +        guac_terminal_word_initial_position(terminal, col, row, &word_col_head,
 +                &word_col_tail, &word_row_head, &word_row_tail);
 +
 +    /* Try to get boundaries of a word */
 +    guac_terminal_get_word_bounds(terminal->current_buffer, guac_terminal_is_part_of_word,
 +            &word_col_head, &word_col_tail, &word_row_head, &word_row_tail);
 +
 +    /* Search for URI only when user don't hold left click
 +     * to unconditionally extend selection to word pattern */
 +    if (!hold) {
 +
 +        /* Begin uri search on previously found word position,
 +        * this avoids going through the same characters twice */
 +        uri_col_head = word_col_head;
 +        uri_col_tail = word_col_tail;
 +        uri_row_head = word_row_head;
 +        uri_row_tail = word_row_tail;
 +
 +        /* Get boundaries of potential URI */
 +        guac_terminal_get_word_bounds(terminal->current_buffer, guac_terminal_is_part_of_uri,
 +            &uri_col_head, &uri_col_tail, &uri_row_head, &uri_row_tail);
 +
 +        /* Check if uri dected */
 +        if ((uri_col_head != word_col_head || uri_col_tail != word_col_tail ||
 +             uri_row_head != word_row_head || uri_row_tail != word_row_tail)) {
 +
 +            /* Temp vars to avoid overwrite uri_row_head and uri_col_head values */
 +            int tmp_row = uri_row_head;
 +            int tmp_col = uri_col_head;
 +
 +            /* Check for the presence of a uri scheme like /^[a-z]*\:\/{2}/ */
 +            do {
 +
 +                /* Get first char of first row */
 +                length = guac_terminal_buffer_get_columns(terminal->current_buffer,
 +                        &characters, &is_wrapped, tmp_row);
 +                current_char = characters[tmp_col].value;
 +
 +                /* [a-z]+ part */
 +                if (current_char >= 'a' && current_char <= 'z') {
 +
 +                    /* Go to next col on current row */
 +                    if (tmp_col != length-1) {
 +                        tmp_col++;
 +                        continue;
 +                    }
 +
 +                    /* Go to first col of next row */
 +                    tmp_col = 0;
 +                    tmp_row++;
 +                    continue;

 -    if (col >= length)
 -        return;
 +                }

 -    /* (char)10 behind cursor */
 -    int current_char = characters[col].value;
 +                /* Search for URI scheme delimiter `://` */
 +                if (current_char == ':' &&
 +                    tmp_col + 2 < length &&
 +                    characters[tmp_col + 1].value == '/' &&
 +                    characters[tmp_col + 2].value == '/') {
 +
 +                    /* Use URI limits instead of word limits */
 +                    word_col_head = uri_col_head;
 +                    word_col_tail = uri_col_tail;
 +                    word_row_head = uri_row_head;
 +                    word_row_tail = uri_row_tail;
 +                }

 -    /* Position of the word behind cursor.
 -     * Default = col required to select a char if not a word and not blank. */
 +                /* Always exit after non-letter char */
 +                break;

 -    /* The function used to calculate the word borders */
 -    bool (*is_part_of_word)(int) = NULL;
 +            } while (tmp_row < uri_row_tail || tmp_col < uri_col_tail);
 +        }
 +    }

 -    /* If selection is on a word, get its borders */
 -    if (guac_terminal_is_part_of_word(current_char))
 -        is_part_of_word = guac_terminal_is_part_of_word;
 +    /* Select and add to clipboard the "word" */
 +    guac_terminal_select_start(terminal, word_row_head, word_col_head, GUAC_TERMINAL_COLUMN_SIDE_LEFT);
 +    guac_terminal_select_update(terminal, word_row_tail, word_col_tail, GUAC_TERMINAL_COLUMN_SIDE_RIGHT);

 -    /* If selection is on a blank, get its borders */
 -    else if (guac_terminal_is_blank(current_char))
 -        is_part_of_word = guac_terminal_is_blank;
 +}
 +
 +/**
 + * Selection of a line during a triple click event.
 + *  - Get buffer row boundaries if it has been wrapped.
 + *  - Visual selection of the line.
 + *  - Adding it to clipboard.
 + *
 + * @param terminal
 + *     The terminal that received a triple click event.
 + *
 + * @param row
 + *     The row where is the mouse at the triple click event.
 + *
 + * @param hold
 + *      True when user hold left click.
 + */
 +static void guac_terminal_triple_click(guac_terminal* terminal, int row, bool hold) {

 -    int word_head = col;
 -    int word_tail = col;
 +    /* Temporarily reading previous and next lines */
 +    guac_terminal_char* characters;
 +    bool is_wrapped;
 +    int length;

 -    if (is_part_of_word != NULL) {
 +    /* Final boundary rows */
 +    int top_row = row;
 +    int bottom_row = row;

 -        /* Get word head*/
 -        for (; word_head - 1 >= 0; word_head--) {
 -            if (!is_part_of_word(characters[word_head - 1].value))
 -                break;
 -        }
 +    /* User holds left click */
 +    if (hold) {

 -        /* Get word tail */
 -        for (; word_tail + 1 < terminal->display->width && word_tail + 1 < length; word_tail++) {
 -            if (!is_part_of_word(characters[word_tail + 1].value))
 -                break;
 -        }
 +        /* Use initial row as bottom of the selection and go up */
 +        if (row <= terminal->selection_initial_row)
 +            bottom_row = terminal->selection_initial_row;

 +        /* Use initial row as top of the selection and go down */
 +        if (row > terminal->selection_initial_row)
 +            top_row = terminal->selection_initial_row;
      }

 -    /* Select and add to clipboard the "word" */
 -    guac_terminal_select_start(terminal, row, word_head, GUAC_TERMINAL_COLUMN_SIDE_LEFT);
 -    guac_terminal_select_update(terminal, row, word_tail, GUAC_TERMINAL_COLUMN_SIDE_RIGHT);
 +    /* Get top boundary */
 +    do {
 +
 +        /* Read previous buffer row */
 +        length = guac_terminal_buffer_get_columns(terminal->current_buffer,
 +                &characters, &is_wrapped, top_row - 1);

 +    /* Go to the previous row if it is wrapped */
 +    } while (is_wrapped && top_row--);
 +
 +    /* Get bottom boundary */
 +    do {
 +
 +        /* Read current buffer row */
 +        length = guac_terminal_buffer_get_columns(terminal->current_buffer,
 +                &characters, &is_wrapped, bottom_row);
 +
 +    /* Go to the next row if current row is wrapped */
 +    } while (is_wrapped && bottom_row++);
 +
 +    /* Start selection on first col of top_row */
 +    guac_terminal_select_start(terminal, top_row, 0, GUAC_TERMINAL_COLUMN_SIDE_LEFT);
 +
 +    /* End selection on last col of bottom_row */
 +    guac_terminal_select_update(terminal, bottom_row, length - 1, GUAC_TERMINAL_COLUMN_SIDE_RIGHT);
  }

  static int __guac_terminal_send_mouse(guac_terminal* term, guac_user* user,
@@@ -2364,21 -2235,12 +2763,21 @@@

                      /* First click = start selection */
                      case 0:
 +                        /* The rectangular selection is requested by pressing
 +                         * the ALT key at the start of the selection */
 +                        term->rectangle_selection = term->mod_alt;
 +
 +                        /* Save initial mouse position */
 +                        term->selection_initial_row = row;
 +                        term->selection_initial_column = col;
 +
 +                        /* Start selection */
                          guac_terminal_select_start(term, row, col, side);
                          break;
-
+
                      /* Second click = word selection */
                      case 1:
 -                        guac_terminal_double_click(term, row, col);
 +                        guac_terminal_double_click(term, row, col, false);
                          break;

                      /* third click or more = line selection */
diff --cc src/terminal/terminal/terminal.h
index 399be145,dbb6be19..d1a0bae6
--- a/src/terminal/terminal/terminal.h
+++ b/src/terminal/terminal/terminal.h
@@@ -681,12 -238,13 +681,19 @@@ typedef struct guac_terminal_options
       */
      int backspace;

 +    /**
 +     * The family of codes (e.g. vt100) which will be used when you push
 +     * the function and keypad keys.
 +     */
 +    char* func_keys_and_keypad;
 +
+     /**
+      * Whether to use Linux terminal type compatibility mode for key encoding.
+      * When true, key sequences match those expected by a Linux console
+      * ($TERM=linux). When false, standard xterm/VT220 sequences are used.
+      */
+     bool linux_console_keys;
+
  } guac_terminal_options;

  /**