diff --git a/TODO b/TODO index 57a45e2b..aadc5af2 100644 --- a/TODO +++ b/TODO @@ -171,7 +171,6 @@ TERMINAL ISSUES - support for bce - use screen-256color when started on 256 colour terminal?? - if-shell/run-shell should block further command execution in the same command -- possibly support rxvt-unicode extended mouse input (1015) - wrap/no wrap esc seq DEC CSI ? 7 h/l * We need a tmux terminfo entry to document the extensions we are using in upstream terminfo. Must NOT change (only add or remove) anything from diff --git a/input-keys.c b/input-keys.c index 0953ce7e..d57926ad 100644 --- a/input-keys.c +++ b/input-keys.c @@ -201,12 +201,25 @@ input_key(struct window_pane *wp, int key) void input_mouse(struct window_pane *wp, struct session *s, struct mouse_event *m) { - char buf[10]; + char buf[40]; size_t len; struct paste_buffer *pb; if (wp->screen->mode & ALL_MOUSE_MODES) { - if (wp->screen->mode & MODE_MOUSE_UTF8) { + /* + * Use the SGR (1006) extension only if the application + * requested it and the underlying terminal also sent the event + * in this format (this is because an old style mouse release + * event cannot be converted into the new SGR format, since the + * released button is unknown). Otherwise pretend that tmux + * doesn't speak this extension, and fall back to the UTF-8 + * (1005) extension if the application requested, or to the + * legacy format. + */ + if (m->sgr && (wp->screen->mode & MODE_MOUSE_SGR)) { + len = xsnprintf(buf, sizeof buf, "\033[<%d;%d;%d%c", + m->sgr_xb, m->x + 1, m->y + 1, m->sgr_rel ? 'm' : 'M'); + } else if (wp->screen->mode & MODE_MOUSE_UTF8) { len = xsnprintf(buf, sizeof buf, "\033[M"); len += utf8_split2(m->xb + 32, &buf[len]); len += utf8_split2(m->x + 33, &buf[len]); diff --git a/input.c b/input.c index 8846c1c7..c4e19c06 100644 --- a/input.c +++ b/input.c @@ -1260,6 +1260,9 @@ input_csi_dispatch(struct input_ctx *ictx) case 1005: screen_write_mode_clear(&ictx->ctx, MODE_MOUSE_UTF8); break; + case 1006: + screen_write_mode_clear(&ictx->ctx, MODE_MOUSE_SGR); + break; case 47: case 1047: window_pane_alternate_off(wp, &ictx->cell, 0); @@ -1320,6 +1323,9 @@ input_csi_dispatch(struct input_ctx *ictx) case 1005: screen_write_mode_set(&ictx->ctx, MODE_MOUSE_UTF8); break; + case 1006: + screen_write_mode_set(&ictx->ctx, MODE_MOUSE_SGR); + break; case 47: case 1047: window_pane_alternate_on(wp, &ictx->cell, 0); diff --git a/screen-write.c b/screen-write.c index 2cc98501..59d91fd0 100644 --- a/screen-write.c +++ b/screen-write.c @@ -55,7 +55,9 @@ screen_write_reset(struct screen_write_ctx *ctx) screen_reset_tabs(s); screen_write_scrollregion(ctx, 0, screen_size_y(s) - 1); - s->mode &= ~(MODE_INSERT|MODE_KCURSOR|MODE_KKEYPAD|ALL_MOUSE_MODES); + + s->mode &= ~(MODE_INSERT|MODE_KCURSOR|MODE_KKEYPAD); + s->mode &= ~(ALL_MOUSE_MODES|MODE_MOUSE_UTF8|MODE_MOUSE_SGR); screen_write_clearscreen(ctx); screen_write_cursormove(ctx, 0, 0); diff --git a/tmux.h b/tmux.h index e10fb629..1efad53e 100644 --- a/tmux.h +++ b/tmux.h @@ -661,7 +661,8 @@ struct mode_key_table { #define MODE_MOUSE_BUTTON 0x40 #define MODE_MOUSE_ANY 0x80 #define MODE_MOUSE_UTF8 0x100 -#define MODE_BRACKETPASTE 0x200 +#define MODE_MOUSE_SGR 0x200 +#define MODE_BRACKETPASTE 0x400 #define ALL_MOUSE_MODES (MODE_MOUSE_STANDARD|MODE_MOUSE_BUTTON|MODE_MOUSE_ANY) @@ -1149,6 +1150,9 @@ LIST_HEAD(tty_terms, tty_term); * - bits 3, 4 and 5 are for keys * - bit 6 is set for dragging * - bit 7 for buttons 4 and 5 + * + * With the SGR 1006 extension the released button becomes known. Store these + * in separate fields and store the value converted to the old format in xb. */ struct mouse_event { u_int xb; @@ -1161,6 +1165,10 @@ struct mouse_event { u_int ly; u_int sy; + u_int sgr; /* whether the input arrived in SGR format */ + u_int sgr_xb; /* only for SGR: the unmangled button */ + u_int sgr_rel; /* only for SGR: whether it is a release event */ + u_int button; u_int clicks; diff --git a/tty-keys.c b/tty-keys.c index 5fda2fb3..00327bb2 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -587,20 +587,26 @@ tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size) { struct mouse_event *m = &tty->mouse; struct utf8_data utf8data; - u_int i, value, x, y, b; + u_int i, value, x, y, b, sgr, sgr_b, sgr_rel; + unsigned char c; /* * Standard mouse sequences are \033[M followed by three characters - * indicating buttons, X and Y, all based at 32 with 1,1 top-left. + * indicating button, X and Y, all based at 32 with 1,1 top-left. * * UTF-8 mouse sequences are similar but the three are expressed as * UTF-8 characters. + * + * SGR extended mouse sequences are \033[< followed by three numbers in + * decimal and separated by semicolons indicating button, X and Y. A + * trailing 'M' is click or scroll and trailing 'm' release. All are + * based at 0 with 1,1 top-left. */ *size = 0; - x = y = b = 0; + x = y = b = sgr = sgr_b = sgr_rel = 0; - /* First three bytes are always \033[M. */ + /* First two bytes are always \033[. */ if (buf[0] != '\033') return (-1); if (len == 1) @@ -609,50 +615,99 @@ tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size) return (-1); if (len == 2) return (1); - if (buf[2] != 'M') - return (-1); - if (len == 3) - return (1); - /* Read the three inputs. */ - *size = 3; - for (i = 0; i < 3; i++) { - if (len < *size) - return (1); + /* + * Third byte is M in old standard and UTF-8 extension, < in SGR + * extension. + */ + if (buf[2] == 'M') { + /* Read the three inputs. */ + *size = 3; + for (i = 0; i < 3; i++) { + if (len <= *size) + return (1); - if (tty->mode & MODE_MOUSE_UTF8) { - if (utf8_open(&utf8data, buf[*size])) { - if (utf8data.size != 2) - return (-1); + if (tty->mode & MODE_MOUSE_UTF8) { + if (utf8_open(&utf8data, buf[*size])) { + if (utf8data.size != 2) + return (-1); + (*size)++; + if (len <= *size) + return (1); + utf8_append(&utf8data, buf[*size]); + value = utf8_combine(&utf8data); + } else + value = (u_char) buf[*size]; (*size)++; - if (len < *size) - return (1); - utf8_append(&utf8data, buf[*size]); - value = utf8_combine(&utf8data); - } else - value = (unsigned char)buf[*size]; - (*size)++; - } else { - value = (unsigned char)buf[*size]; - (*size)++; + } else { + value = (u_char) buf[*size]; + (*size)++; + } + + if (i == 0) + b = value; + else if (i == 1) + x = value; + else + y = value; } + log_debug("mouse input: %.*s", (int) *size, buf); - if (i == 0) - b = value; - else if (i == 1) - x = value; - else - y = value; - } - log_debug("mouse input: %.*s", (int) *size, buf); + /* Check and return the mouse input. */ + if (b < 32 || x < 33 || y < 33) + return (-1); + b -= 32; + x -= 33; + y -= 33; + } else if (buf[2] == '<') { + /* Read the three inputs. */ + *size = 3; + while (1) { + if (len <= *size) + return (1); + c = (u_char)buf[(*size)++]; + if (c == ';') + break; + if (c < '0' || c > '9') + return (-1); + sgr_b = 10 * sgr_b + (c - '0'); + } + while (1) { + if (len <= *size) + return (1); + c = (u_char)buf[(*size)++]; + if (c == ';') + break; + if (c < '0' || c > '9') + return (-1); + x = 10 * x + (c - '0'); + } + while (1) { + if (len <= *size) + return (1); + c = (u_char) buf[(*size)++]; + if (c == 'M' || c == 'm') + break; + if (c < '0' || c > '9') + return (-1); + y = 10 * y + (c - '0'); + } + log_debug("mouse input (sgr): %.*s", (int) *size, buf); - /* Check and return the mouse input. */ - if (b < 32 || x < 33 || y < 33) + /* Check and return the mouse input. */ + if (x < 1 || y < 1) + return (-1); + x--; + y--; + sgr = 1; + sgr_rel = (c == 'm'); + + /* Figure out what b would be in old format. */ + b = sgr_b; + if (sgr_rel) + b |= 3; + } else return (-1); - b -= 32; - x -= 33; - y -= 33; - log_debug("mouse position: x=%u y=%u b=%u", x, y, b); /* Fill in mouse structure. */ if (~m->event & MOUSE_EVENT_WHEEL) { @@ -660,6 +715,9 @@ tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size) m->ly = m->y; } m->xb = b; + m->sgr = sgr; + m->sgr_xb = sgr_b; + m->sgr_rel = sgr_rel; if (b & 64) { /* wheel button */ b &= 3; if (b == 0) diff --git a/tty.c b/tty.c index bae97ac8..6da63bf1 100644 --- a/tty.c +++ b/tty.c @@ -218,7 +218,7 @@ tty_start_tty(struct tty *tty) tty_putcode(tty, TTYC_CNORM); if (tty_term_has(tty->term, TTYC_KMOUS)) - tty_puts(tty, "\033[?1000l"); + tty_puts(tty, "\033[?1000l\033[?1006l\033[?1005l"); if (tty_term_has(tty->term, TTYC_XT)) tty_puts(tty, "\033[c\033[>4;1m"); @@ -281,7 +281,7 @@ tty_stop_tty(struct tty *tty) tty_raw(tty, tty_term_string(tty->term, TTYC_CNORM)); if (tty_term_has(tty->term, TTYC_KMOUS)) - tty_raw(tty, "\033[?1000l"); + tty_raw(tty, "\033[?1000l\033[?1006l\033[?1005l"); if (tty_term_has(tty->term, TTYC_XT)) tty_puts(tty, "\033[>4m"); @@ -491,8 +491,17 @@ tty_update_mode(struct tty *tty, int mode, struct screen *s) } if (changed & ALL_MOUSE_MODES) { if (mode & ALL_MOUSE_MODES) { + /* + * Enable the UTF-8 (1005) extension if configured to. + * Enable the SGR (1006) extension unconditionally, as + * this is safe from misinterpretation. Do it in this + * order, because in some terminals it's the last one + * that takes effect and SGR is the preferred one. + */ if (mode & MODE_MOUSE_UTF8) tty_puts(tty, "\033[?1005h"); + tty_puts(tty, "\033[?1006h"); + if (mode & MODE_MOUSE_ANY) tty_puts(tty, "\033[?1003h"); else if (mode & MODE_MOUSE_BUTTON) @@ -506,6 +515,8 @@ tty_update_mode(struct tty *tty, int mode, struct screen *s) tty_puts(tty, "\033[?1002l"); else if (tty->mode & MODE_MOUSE_STANDARD) tty_puts(tty, "\033[?1000l"); + + tty_puts(tty, "\033[?1006l"); if (tty->mode & MODE_MOUSE_UTF8) tty_puts(tty, "\033[?1005l"); }