Skip to content

Commit 1a21a56

Browse files
committed
GUACAMOLE-1973: Add support for XTerm bracketed-paste mode
1 parent 8651022 commit 1a21a56

File tree

4 files changed

+106
-2
lines changed

4 files changed

+106
-2
lines changed

src/terminal/terminal-handlers.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,7 @@ static bool* __guac_terminal_get_flag(guac_terminal* term, int num, char private
434434
switch (num) {
435435
case 1: return &(term->application_cursor_keys); /* DECCKM */
436436
case 25: return &(term->cursor_visible); /* DECTECM */
437+
case 2004: return &(term->bracketed_paste_mode); /* XTerm bracketed-paste */
437438
}
438439
}
439440

src/terminal/terminal.c

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ void guac_terminal_reset(guac_terminal* term) {
230230
term->application_cursor_keys = false;
231231
term->automatic_carriage_return = false;
232232
term->insert_mode = false;
233+
term->bracketed_paste_mode = false;
233234

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

15011502
}
15021503

1504+
int guac_terminal_send_clipboard(guac_terminal *term) {
1505+
char *filtered = guac_mem_alloc(term->clipboard->length + 12);
1506+
uint8_t *src_ptr = (uint8_t *)term->clipboard->buffer;
1507+
uint8_t *src_end = (uint8_t *)(term->clipboard->buffer + term->clipboard->length);
1508+
uint8_t *dst_ptr = (uint8_t *)filtered;
1509+
int filtered_len = 0;
1510+
1511+
/* Send the paste start sequence */
1512+
if (term->bracketed_paste_mode) {
1513+
memcpy(dst_ptr, "\x1B[200~", 6);
1514+
dst_ptr += 6;
1515+
filtered_len += 6;
1516+
}
1517+
1518+
while (src_ptr < src_end) {
1519+
1520+
/* Exclude Unicode CO and C1 control characters except tab, line feed
1521+
* and carriage return. */
1522+
bool is_control = (((*src_ptr >= 0x00) && (*src_ptr < 0x20)) ||
1523+
((*src_ptr >= 0x80) && (*src_ptr < 0xa0))) &&
1524+
(*src_ptr != 0x09) && (*src_ptr != 0x0a) && (*src_ptr != 0x0d);
1525+
1526+
/* Allow UTF-8 codepoints */
1527+
if ((*src_ptr & 0xe0) == 0xc0) {
1528+
1529+
/* UTF-8 2-byte codepoint */
1530+
if ((src_ptr + 1 < src_end) && ((src_ptr[1] & 0xc0) == 0x80)) {
1531+
dst_ptr[0] = src_ptr[0];
1532+
dst_ptr[1] = src_ptr[1];
1533+
dst_ptr += 2;
1534+
filtered_len += 2;
1535+
src_ptr++;
1536+
}
1537+
}
1538+
else if ((*src_ptr & 0xf0) == 0xe0) {
1539+
1540+
/* UTF-8 3-byte codepoint */
1541+
if ((src_ptr + 2 < src_end) && ((src_ptr[1] & 0xc0) == 0x80) &&
1542+
((src_ptr[2] & 0xc0) == 0x80)) {
1543+
dst_ptr[0] = src_ptr[0];
1544+
dst_ptr[1] = src_ptr[1];
1545+
dst_ptr[2] = src_ptr[2];
1546+
dst_ptr += 3;
1547+
filtered_len += 3;
1548+
src_ptr += 2;
1549+
}
1550+
}
1551+
else if ((*src_ptr & 0xf8) == 0xf0) {
1552+
1553+
/* UTF-8 4-byte codepoint */
1554+
if ((src_ptr + 3 < src_end) && ((src_ptr[1] & 0xc0) == 0x80) &&
1555+
((src_ptr[2] & 0xc0) == 0x80) && ((src_ptr[3] & 0xc0) == 0x80)) {
1556+
dst_ptr[0] = src_ptr[0];
1557+
dst_ptr[1] = src_ptr[1];
1558+
dst_ptr[2] = src_ptr[2];
1559+
dst_ptr[3] = src_ptr[3];
1560+
dst_ptr += 4;
1561+
filtered_len += 4;
1562+
src_ptr += 3;
1563+
}
1564+
}
1565+
else if (!is_control) {
1566+
*dst_ptr = *src_ptr;
1567+
dst_ptr++;
1568+
filtered_len++;
1569+
}
1570+
src_ptr++;
1571+
}
1572+
1573+
/* Send the paste stop sequence */
1574+
if (term->bracketed_paste_mode) {
1575+
memcpy(dst_ptr, "\x1B[201~", 6);
1576+
dst_ptr += 6;
1577+
filtered_len += 6;
1578+
}
1579+
1580+
int result = guac_terminal_send_data(term, filtered, filtered_len);
1581+
guac_mem_free(filtered);
1582+
return result;
1583+
}
1584+
15031585
static int __guac_terminal_send_key(guac_terminal* term, int keysym, int pressed) {
15041586

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

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

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

17231805
/* Paste contents of clipboard on right or middle mouse button up */
17241806
if ((released_mask & GUAC_CLIENT_MOUSE_RIGHT) || (released_mask & GUAC_CLIENT_MOUSE_MIDDLE))
1725-
return guac_terminal_send_data(term, term->clipboard->buffer, term->clipboard->length);
1807+
return guac_terminal_send_clipboard(term);
17261808

17271809
/* If left mouse button was just released, stop selection */
17281810
if (released_mask & GUAC_CLIENT_MOUSE_LEFT)

src/terminal/terminal/terminal-priv.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,11 @@ struct guac_terminal {
390390
*/
391391
bool automatic_carriage_return;
392392

393+
/**
394+
* Whether the current application supports bracketed paste mode.
395+
*/
396+
bool bracketed_paste_mode;
397+
393398
/**
394399
* Whether insert mode is enabled (DECIM).
395400
*/

src/terminal/terminal/terminal.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,22 @@ int guac_terminal_send_data(guac_terminal* term, const char* data, int length);
542542
*/
543543
int guac_terminal_send_string(guac_terminal* term, const char* data);
544544

545+
/**
546+
* Sends the terminal clipboard contents after sanitisation. If terminal input
547+
* is currently coming from a stream due to a prior call to
548+
* guac_terminal_send_stream(), any input which would normally result from
549+
* invoking this function is dropped.
550+
*
551+
* @param term
552+
* The terminal which should receive the given data on STDIN.
553+
*
554+
* @return
555+
* The number of bytes written to STDIN, or a negative value if an error
556+
* occurs preventing the data from being written. This should always be
557+
* the size of the data given unless data is intentionally dropped.
558+
*/
559+
int guac_terminal_send_clipboard(guac_terminal* term);
560+
545561
/**
546562
* Writes the given buffer to the given terminal's STDOUT. All requested bytes
547563
* will be written unless an error occurs. This function may block until space

0 commit comments

Comments
 (0)