From 1515c9f1525ef70fc70255ec692e22e38cc194ad Mon Sep 17 00:00:00 2001 From: Egmont Koblinger Date: Wed, 13 Feb 2013 12:37:50 +0300 Subject: [PATCH 1/2] Ticket #2956: Newer protocol for extended mouse clicks. A followup extension, "SGR 1006" was invented by xterm, to overcome some of the shortcomings of the previous one. It is becoming as widespread as the previous one, and is likely to soon overtake it in popularity. Note that most of the patch is just the removal of the huge complexity introduced by the previous one. The previous extension didn't have a unique prefix which made the whole parsing logic extremely complicated. The new extension does have a unique prefix, so parsing becomes a piece of cake. The code becomes much cleaner and much easier to maintain. Signed-off-by: Slava Zanko --- lib/tty/key.c | 235 +++++++++++++----------------------------------- lib/tty/mouse.c | 18 ++-- lib/tty/mouse.h | 5 +- lib/tty/tty.c | 4 + 4 files changed, 80 insertions(+), 182 deletions(-) diff --git a/lib/tty/key.c b/lib/tty/key.c index 63d181056..6c37a999d 100644 --- a/lib/tty/key.c +++ b/lib/tty/key.c @@ -521,8 +521,6 @@ static int *seq_append = NULL; static int *pending_keys = NULL; -static int mouse_btn, mouse_x, mouse_y; - #ifdef __QNXNTO__ ph_dv_f ph_attach; ph_ov_f ph_input_group; @@ -711,16 +709,63 @@ getch_with_delay (void) /* --------------------------------------------------------------------------------------------- */ static void -xmouse_get_event (Gpm_Event * ev) +xmouse_get_event (Gpm_Event * ev, gboolean extended) { static struct timeval tv1 = { 0, 0 }; /* Force first click as single */ static struct timeval tv2; static int clicks = 0; static int last_btn = 0; - int btn = mouse_btn; + int btn; /* Decode Xterm mouse information to a GPM style event */ + if (!extended) + { + /* Variable btn has following meaning: */ + /* 0 = btn1 dn, 1 = btn2 dn, 2 = btn3 dn, 3 = btn up */ + btn = tty_lowlevel_getch () - 32; + /* Coordinates are 33-based */ + /* Transform them to 1-based */ + ev->x = tty_lowlevel_getch () - 32; + ev->y = tty_lowlevel_getch () - 32; + } + else + { + /* SGR 1006 extension (e.g. "\e[<0;12;300M"): + - Numbers are encoded in decimal to make it ASCII-safe + and to overcome the limit of 223 columns/rows. + - Mouse release is encoded by trailing 'm' rather than 'M' + so that the released button can be reported. + - Numbers are no longer offset by 32. */ + char c; + btn = ev->x = ev->y = 0; + ev->type = 0; /* In case we return on an invalid sequence */ + while ((c = tty_lowlevel_getch ()) != ';') + { + if (c < '0' || c > '9') + return; + btn = 10 * btn + (c - '0'); + } + while ((c = tty_lowlevel_getch ()) != ';') + { + if (c < '0' || c > '9') + return; + ev->x = 10 * ev->x + (c - '0'); + } + while ((c = tty_lowlevel_getch ()) != 'M' && c != 'm') + { + if (c < '0' || c > '9') + return; + ev->y = 10 * ev->y + (c - '0'); + } + /* Legacy mouse protocol doesn't tell which button was released, + conveniently all of mc's widgets are written not to rely on this + information. With the SGR extension the released button becomes + known, but for the sake of simplicity we just ignore it. */ + if (c == 'm') + btn = 3; + } + /* There seems to be no way of knowing which button was released */ /* So we assume all the buttons were released */ @@ -798,8 +843,6 @@ xmouse_get_event (Gpm_Event * ev) } last_btn = ev->buttons; } - ev->x = mouse_x; - ev->y = mouse_y; } /* --------------------------------------------------------------------------------------------- */ @@ -933,136 +976,6 @@ push_char (int c) return ret; } -/* --------------------------------------------------------------------------------------------- */ -/* Parse extended mouse coordinates. - Returns -1 if pending_keys (up to seq_append) cannot be a prefix of extended mouse coordinates. - Returns 0 if pending_keys (up to seq_append) is a valid (but still incomplete) prefix for - extended mouse coordinates, e.g. "^[[32;4". - Returns 1 and fills the mouse_btn, mouse_x, mouse_y values if pending_keys (up to seq_append) is - a complete extended mouse sequence, e.g. "^[[32;42;5M" - */ - -/* Technical info (Egmont Koblinger ): - - The ancient way of reporting mouse coordinates only supports coordinates up to 223, - so if your terminal is wider (or taller, but that's unlikely), you cannot use your mouse - in the rightmost columns. - - * The old way of reporting mouse coordinates is the following: - + Output DECSET 1000 to enable mouse - + Expect escape sequences in the format \e[M whereas , - and are single bytes. (Action is 0 for left click, 1 for middle click, - 2 for right click, 3 for release, or something like this.) - + Disadvantages of this format: - + x and y can only go up to 223. - + Coordinates above 95 are not ascii-compatible, so any character set converting - layer (e.g. luit) messes them up. - + The stream is not valid UTF-8, even if everything else is. - - * The first new extension, introduced by xterm-262, is the following: - + Output DECSET 1000 to enable mouse, followed by DECSET 1005 to activate extended mode. - + Expect escape sequences in the format \e[M<><> whereas <> - and <> each can be up to two bytes long: coordinate+32 is encoded in UTF-8. - + Disadvantates of this format: - + There's still a limit of 2015 rows/columns (okay, it's not a real life problem). - + Doesn't solve the luit issue. - + It is "horribly broken" (quoting urxvt's changelog) in terms of compatibility - with the previous standard. There is no way for an application to tell whether - the underlying terminal supports this new mode (whether DECSET 1005 did actually change - the behavior or not), but depending on this a completely different user action might - generate the same input. Example: - + If the terminal doesn't support this extension, then clicking at (162, 129) - generates \e[M<32><194><161>. - + If the terminal supports this extension, then clicking at (129, 1) [bit of math: - 129+32 = 161, U+0161 in UTF-8 is 194 161] generates \e[M<32><194><161><33>. - + so there's no way to tell whether the terminal ignored the 1005 escape sequence, - the user clicked on (162, 129) and then typed an exclamation mark; or whether - the terminal recognized the escape, and the user clicked on (129, 1). - + Due to this horrible brokenness, there's no way to implement support it without - explicitly asking the user (via a setting) if the terminal can speak this extension. - - * The second new extension, introduced by rxvt-unicode-9.10, is the following: - + Output DECSET 1000 to enable mouse, followed by DECSET 1015 to activate this extended mode. - + Expect escape sequences in the format \e[{action+32};{x};{y}M where this time I used - the braces to denote spelling out the numbers in decimal, rather than using raw bytes. - + The only thing I don't understand is why they kept the offset of 32 at action, but other - than that, this format is totally okay, and solves all the weaknesses of the previous ones. - - Currently, at least the following terminal emulators have support for these: - * xterm supports the xterm extension - * rxvt-unicode >= 9.10 supports both extensions - * iterm2 supports both extensions - * vte >= 0.31 supports the urxvt extension - */ - -static int -parse_extended_mouse_coordinates (void) -{ - int c, btn = 0, x = 0, y = 0; - const int *p = pending_keys; - const int *endp = seq_append; - - if (p == endp) - return 0; - c = *p++; - if (c != ESC_CHAR) - return -1; - - if (p == endp) - return 0; - c = *p++; - if (c != '[') - return -1; - - while (TRUE) - { - if (p == endp) - return 0; - c = *p++; - if (c == ';') - break; - if (c < '0' || c > '9') - return -1; - btn = 10 * btn + c - '0'; - } - if (btn < 32) - return -1; - btn -= 32; - - while (TRUE) - { - if (p == endp) - return 0; - c = *p++; - if (c == ';') - break; - if (c < '0' || c > '9') - return -1; - x = 10 * x + c - '0'; - } - if (x < 1) - return -1; - - while (TRUE) - { - if (p == endp) - return 0; - c = *p++; - if (c == 'M') - break; - if (c < '0' || c > '9') - return -1; - y = 10 * y + c - '0'; - } - if (y < 1) - return -1; - - mouse_btn = btn; - mouse_x = x; - mouse_y = y; - return 1; -} - /* --------------------------------------------------------------------------------------------- */ /* Apply corrections for the keycode generated in get_key_code() */ @@ -1831,35 +1744,22 @@ get_key_code (int no_delay) pend_send: if (pending_keys != NULL) { - int m; - - m = parse_extended_mouse_coordinates (); - if (m == 1) + int d = *pending_keys++; + check_pend: + if (*pending_keys == 0) { - pending_keys = seq_append = NULL; - this = NULL; - return MCKEY_EXTENDED_MOUSE; + pending_keys = NULL; + seq_append = NULL; } - if (m == -1) + if ((d == ESC_CHAR) && (pending_keys != NULL)) { - int d = *pending_keys++; - check_pend: - if (*pending_keys == 0) - { - pending_keys = NULL; - seq_append = NULL; - } - if ((d == ESC_CHAR) && (pending_keys != NULL)) - { - d = ALT (*pending_keys++); - goto check_pend; - } - if ((d > 127 && d < 256) && use_8th_bit_as_meta) - d = ALT (d & 0x7f); - this = NULL; - return correct_key_code (d); + d = ALT (*pending_keys++); + goto check_pend; } - /* else if (m == 0), just let it continue */ + if ((d > 127 && d < 256) && use_8th_bit_as_meta) + d = ALT (d & 0x7f); + this = NULL; + return correct_key_code (d); } nodelay_try_again: @@ -2169,18 +2069,7 @@ tty_get_event (struct Gpm_Event *event, gboolean redo_event, gboolean block) || c == MCKEY_EXTENDED_MOUSE)) { /* Mouse event */ - /* In case of extended coordinates, mouse_btn, mouse_x and mouse_y are already filled in. */ - if (c != MCKEY_EXTENDED_MOUSE) - { - /* Variable btn has following meaning: */ - /* 0 = btn1 dn, 1 = btn2 dn, 2 = btn3 dn, 3 = btn up */ - mouse_btn = tty_lowlevel_getch () - 32; - /* Coordinates are 33-based */ - /* Transform them to 1-based */ - mouse_x = tty_lowlevel_getch () - 32; - mouse_y = tty_lowlevel_getch () - 32; - } - xmouse_get_event (event); + xmouse_get_event (event, c == MCKEY_EXTENDED_MOUSE); return (event->type != 0) ? EV_MOUSE : EV_NONE; } diff --git a/lib/tty/mouse.c b/lib/tty/mouse.c index 58eaca26b..8b2a5a970 100644 --- a/lib/tty/mouse.c +++ b/lib/tty/mouse.c @@ -48,6 +48,7 @@ Mouse_Type use_mouse_p = MOUSE_NONE; gboolean mouse_enabled = FALSE; const char *xmouse_seq; +const char *xmouse_extended_seq; /*** file scope macro definitions ****************************************************************/ @@ -90,6 +91,7 @@ init_mouse (void) case MOUSE_XTERM_NORMAL_TRACKING: case MOUSE_XTERM_BUTTON_EVENT_TRACKING: define_sequence (MCKEY_MOUSE, xmouse_seq, MCKEY_NOACTION); + define_sequence (MCKEY_EXTENDED_MOUSE, xmouse_extended_seq, MCKEY_NOACTION); break; default: @@ -138,8 +140,8 @@ enable_mouse (void) /* enable mouse tracking */ printf (ESC_STR "[?1000h"); - /* enable urxvt extended mouse coordinate reporting */ - printf (ESC_STR "[?1015h"); + /* enable SGR extended mouse reporting */ + printf (ESC_STR "[?1006h"); fflush (stdout); mouse_enabled = TRUE; @@ -152,8 +154,8 @@ enable_mouse (void) /* enable mouse tracking */ printf (ESC_STR "[?1002h"); - /* enable urxvt extended mouse coordinate reporting */ - printf (ESC_STR "[?1015h"); + /* enable SGR extended mouse reporting */ + printf (ESC_STR "[?1006h"); fflush (stdout); mouse_enabled = TRUE; @@ -182,8 +184,8 @@ disable_mouse (void) break; #endif case MOUSE_XTERM_NORMAL_TRACKING: - /* disable urxvt extended mouse coordinate reporting */ - printf (ESC_STR "[?1015l"); + /* disable SGR extended mouse reporting */ + printf (ESC_STR "[?1006l"); /* disable mouse tracking */ printf (ESC_STR "[?1000l"); @@ -194,8 +196,8 @@ disable_mouse (void) fflush (stdout); break; case MOUSE_XTERM_BUTTON_EVENT_TRACKING: - /* disable urxvt extended mouse coordinate reporting */ - printf (ESC_STR "[?1015l"); + /* disable SGR extended mouse reporting */ + printf (ESC_STR "[?1006l"); /* disable mouse tracking */ printf (ESC_STR "[?1002l"); diff --git a/lib/tty/mouse.h b/lib/tty/mouse.h index 905248073..4dd5f54fd 100644 --- a/lib/tty/mouse.h +++ b/lib/tty/mouse.h @@ -97,9 +97,12 @@ typedef int (*mouse_h) (Gpm_Event *, void *); /* Type of the currently used mouse */ extern Mouse_Type use_mouse_p; -/* String indicating that a mouse event has occured, usually "\E[M" */ +/* String indicating that a mouse event has occurred, usually "\E[M" */ extern const char *xmouse_seq; +/* String indicating that an SGR extended mouse event has occurred, namely "\E[<" */ +extern const char *xmouse_extended_seq; + /*** declarations of public functions ************************************************************/ /* General (i.e. both for xterm and gpm) mouse support definitions */ diff --git a/lib/tty/tty.c b/lib/tty/tty.c index 564837ef7..3d8976411 100644 --- a/lib/tty/tty.c +++ b/lib/tty/tty.c @@ -303,6 +303,10 @@ tty_init_xterm_support (gboolean is_xterm) } } } + + /* No termcap for SGR extended mouse (yet), hardcode it for now */ + if (xmouse_seq != NULL) + xmouse_extended_seq = ESC_STR "[<"; } /* --------------------------------------------------------------------------------------------- */ From 7e115b302491141dea4b9f0f22842f683f487298 Mon Sep 17 00:00:00 2001 From: Slava Zanko Date: Wed, 13 Feb 2013 12:50:18 +0300 Subject: [PATCH 2/2] Code refactoring: removed unneeded 'go to' label. Signed-off-by: Slava Zanko Signed-off-by: Andrew Borodin --- lib/tty/key.c | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/tty/key.c b/lib/tty/key.c index 6c37a999d..41cc573b4 100644 --- a/lib/tty/key.c +++ b/lib/tty/key.c @@ -1744,20 +1744,18 @@ get_key_code (int no_delay) pend_send: if (pending_keys != NULL) { - int d = *pending_keys++; - check_pend: - if (*pending_keys == 0) - { - pending_keys = NULL; - seq_append = NULL; - } - if ((d == ESC_CHAR) && (pending_keys != NULL)) - { - d = ALT (*pending_keys++); - goto check_pend; - } - if ((d > 127 && d < 256) && use_8th_bit_as_meta) + int d; + + d = *pending_keys++; + while (d == ESC_CHAR && *pending_keys != '\0') + d = ALT (*pending_keys++); + + if (*pending_keys == '\0') + pending_keys = seq_append = NULL; + + if (d > 127 && d < 256 && use_8th_bit_as_meta) d = ALT (d & 0x7f); + this = NULL; return correct_key_code (d); }