Skip to content

Commit

Permalink
GUACAMOLE-1973: Add support for XTerm bracketed-paste mode
Browse files Browse the repository at this point in the history
  • Loading branch information
scottp-dpaw committed Jul 30, 2024
1 parent 8651022 commit 6c345ca
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/terminal/terminal-handlers.c
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ static bool* __guac_terminal_get_flag(guac_terminal* term, int num, char private
switch (num) {
case 1: return &(term->application_cursor_keys); /* DECCKM */
case 25: return &(term->cursor_visible); /* DECTECM */
case 2004: return &(term->bracketed_paste_mode); /* XTerm bracketed-paste */
}
}

Expand Down
86 changes: 84 additions & 2 deletions src/terminal/terminal.c
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ void guac_terminal_reset(guac_terminal* term) {
term->application_cursor_keys = false;
term->automatic_carriage_return = false;
term->insert_mode = false;
term->bracketed_paste_mode = false;

/* Reset tabs */
term->tab_interval = 8;
Expand Down Expand Up @@ -1500,6 +1501,87 @@ int guac_terminal_send_string(guac_terminal* term, const char* data) {

}

int guac_terminal_send_clipboard(guac_terminal *term) {
char *filtered = guac_mem_alloc(term->clipboard->length + 12);
uint8_t *src_ptr = (uint8_t *)term->clipboard->buffer;
uint8_t *src_end = (uint8_t *)(term->clipboard->buffer + term->clipboard->length);
uint8_t *dst_ptr = (uint8_t *)filtered;
int filtered_len = 0;

/* Send the paste start sequence */
if (term->bracketed_paste_mode) {
memcpy(dst_ptr, "\x1B[200~", 6);
dst_ptr += 6;
filtered_len += 6;
}

while (src_ptr < src_end) {

/* Exclude Unicode CO and C1 control characters except tab, line feed
* and carriage return. */
bool is_control = (((*src_ptr >= 0x00) && (*src_ptr < 0x20)) ||
((*src_ptr >= 0x80) && (*src_ptr < 0xa0))) &&
(*src_ptr != 0x09) && (*src_ptr != 0x0a) && (*src_ptr != 0x0d);

/* Allow UTF-8 codepoints */
if ((*src_ptr & 0xe0) == 0xc0) {

/* UTF-8 2-byte codepoint */
if ((src_ptr + 1 < src_end) && ((src_ptr[1] & 0xc0) == 0x80)) {
dst_ptr[0] = src_ptr[0];
dst_ptr[1] = src_ptr[1];
dst_ptr += 2;
filtered_len += 2;
src_ptr++;
}
}
else if ((*src_ptr & 0xf0) == 0xe0) {

/* UTF-8 3-byte codepoint */
if ((src_ptr + 2 < src_end) && ((src_ptr[1] & 0xc0) == 0x80) &&
((src_ptr[2] & 0xc0) == 0x80)) {
dst_ptr[0] = src_ptr[0];
dst_ptr[1] = src_ptr[1];
dst_ptr[2] = src_ptr[2];
dst_ptr += 3;
filtered_len += 3;
src_ptr += 2;
}
}
else if ((*src_ptr & 0xf8) == 0xf0) {

/* UTF-8 4-byte codepoint */
if ((src_ptr + 3 < src_end) && ((src_ptr[1] & 0xc0) == 0x80) &&
((src_ptr[2] & 0xc0) == 0x80) && ((src_ptr[3] & 0xc0) == 0x80)) {
dst_ptr[0] = src_ptr[0];
dst_ptr[1] = src_ptr[1];
dst_ptr[2] = src_ptr[2];
dst_ptr[3] = src_ptr[3];
dst_ptr += 4;
filtered_len += 4;
src_ptr += 3;
}
}
else if (!is_control) {
*dst_ptr = *src_ptr;
dst_ptr++;
filtered_len++;
}
src_ptr++;
}

/* Send the paste stop sequence */
if (term->bracketed_paste_mode) {
memcpy(dst_ptr, "\x1B[201~", 6);
dst_ptr += 6;
filtered_len += 6;
}

int result = guac_terminal_send_data(term, filtered, filtered_len);
guac_mem_free(filtered);
return result;
}

static int __guac_terminal_send_key(guac_terminal* term, int keysym, int pressed) {

/* Ignore user input if terminal is not started */
Expand Down Expand Up @@ -1529,7 +1611,7 @@ static int __guac_terminal_send_key(guac_terminal* term, int keysym, int pressed

/* Ctrl+Shift+V shortcut for paste */
if (keysym == 'V' && term->mod_ctrl)
return guac_terminal_send_data(term, term->clipboard->buffer, term->clipboard->length);
return guac_terminal_send_clipboard(term);

/* Shift+PgUp / Shift+PgDown shortcuts for scrolling */
if (term->mod_shift) {
Expand Down Expand Up @@ -1722,7 +1804,7 @@ static int __guac_terminal_send_mouse(guac_terminal* term, guac_user* user,

/* Paste contents of clipboard on right or middle mouse button up */
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);
return guac_terminal_send_clipboard(term);

/* If left mouse button was just released, stop selection */
if (released_mask & GUAC_CLIENT_MOUSE_LEFT)
Expand Down
5 changes: 5 additions & 0 deletions src/terminal/terminal/terminal-priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,11 @@ struct guac_terminal {
*/
bool automatic_carriage_return;

/**
* Whether the current application supports bracketed paste mode.
*/
bool bracketed_paste_mode;

/**
* Whether insert mode is enabled (DECIM).
*/
Expand Down
16 changes: 16 additions & 0 deletions src/terminal/terminal/terminal.h
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,22 @@ int guac_terminal_send_data(guac_terminal* term, const char* data, int length);
*/
int guac_terminal_send_string(guac_terminal* term, const char* data);

/**
* Sends the terminal clipboard contents after sanitisation. If terminal input
* is currently coming from a stream due to a prior call to
* guac_terminal_send_stream(), any input which would normally result from
* invoking this function is dropped.
*
* @param term
* The terminal which should receive the given data on STDIN.
*
* @return
* The number of bytes written to STDIN, or a negative value if an error
* occurs preventing the data from being written. This should always be
* the size of the data given unless data is intentionally dropped.
*/
int guac_terminal_send_clipboard(guac_terminal* term);

/**
* Writes the given buffer to the given terminal's STDOUT. All requested bytes
* will be written unless an error occurs. This function may block until space
Expand Down

0 comments on commit 6c345ca

Please sign in to comment.