From f1f9a00cda728eabd833e81e14cc7606bb34986a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Wed, 22 Oct 2014 22:23:01 +0200 Subject: [PATCH] Add support for setting the mouse protocol --- driver-ti.c | 136 ++++++++++++++++++++++++++++++++++------------- termo-internal.h | 16 ++++++ termo.c | 54 +++++++++++++++++++ termo.h | 27 ++++++++++ 4 files changed, 196 insertions(+), 37 deletions(-) diff --git a/driver-ti.c b/driver-ti.c index 84c86ff..e31de47 100644 --- a/driver-ti.c +++ b/driver-ti.c @@ -1,4 +1,4 @@ -// we want strdup() +// We want strdup() #define _XOPEN_SOURCE 600 #include "termo.h" @@ -261,6 +261,21 @@ load_terminfo (termo_ti_t *ti, const char *term) } } + if (!ti->have_mouse) + ti->tk->guessed_mouse_proto = TERMO_MOUSE_PROTO_NONE; + else if (strstr (term, "rxvt") == term) + // urxvt generally doesn't understand the SGR protocol. + ti->tk->guessed_mouse_proto = TERMO_MOUSE_PROTO_RXVT; + else + // SGR (1006) is the superior protocol. If it's not supported by the + // terminal, nothing much happens and we continue getting events via + // the original protocol (1000). We can't afford to enable the UTF-8 + // protocol (1005) because it collides with the original (1000) and we + // have no way of knowing if it's supported by the terminal. Also both + // 1000 and 1005 are broken in that they may produce characters that + // are illegal in the current locale's charset. + ti->tk->guessed_mouse_proto = TERMO_MOUSE_PROTO_SGR; + // Take copies of these terminfo strings, in case we build multiple termo // instances for multiple different termtypes, and it's different by the // time we want to use it @@ -289,32 +304,6 @@ load_terminfo (termo_ti_t *ti, const char *term) return true; } -static void * -new_driver (termo_t *tk, const char *term) -{ - termo_ti_t *ti = calloc (1, sizeof *ti); - if (!ti) - return NULL; - - ti->tk = tk; - ti->root = new_node_arr (0, 0xff); - if (!ti->root) - goto abort_free_ti; - - if (!load_terminfo (ti, term)) - goto abort_free_trie; - - ti->root = compress_trie (ti->root); - return ti; - -abort_free_trie: - free_trie (ti->root); - -abort_free_ti: - free (ti); - return NULL; -} - static bool write_string (termo_t *tk, char *string) { @@ -352,32 +341,105 @@ set_mouse (termo_ti_t *ti, bool enable) return write_string (ti->tk, start_string); } +static bool +mouse_reset (termo_ti_t *ti) +{ + // Disable everything, a de-facto reset for all terminal mouse protocols + return set_mouse (ti, false) + && write_string (ti->tk, "\E[?1002l") + && write_string (ti->tk, "\E[?1003l") + + && write_string (ti->tk, "\E[?1005l") + && write_string (ti->tk, "\E[?1006l") + && write_string (ti->tk, "\E[?1015l"); +} + +static bool +mouse_set_proto (void *data, int proto, bool enable) +{ + termo_ti_t *ti = data; + bool success = true; + if (proto & TERMO_MOUSE_PROTO_VT200) + success &= set_mouse (ti, enable); + if (proto & TERMO_MOUSE_PROTO_UTF8) + success &= write_string (ti->tk, enable ? "\E[?1005h" : "\E[?1005l"); + if (proto & TERMO_MOUSE_PROTO_SGR) + success &= write_string (ti->tk, enable ? "\E[?1006h" : "\E[?1006l"); + if (proto & TERMO_MOUSE_PROTO_RXVT) + success &= write_string (ti->tk, enable ? "\E[?1015h" : "\E[?1015l"); + return success; +} + +static bool +mouse_set_tracking_mode (void *data, + termo_mouse_tracking_t tracking, bool enable) +{ + termo_ti_t *ti = data; + if (tracking == TERMO_MOUSE_TRACKING_DRAG) + return write_string (ti->tk, enable ? "\E[?1002h" : "\E[?1002l"); + if (tracking == TERMO_MOUSE_TRACKING_ANY) + return write_string (ti->tk, enable ? "\E[?1003h" : "\E[?1003l"); + return true; +} + static int start_driver (termo_t *tk, void *info) { termo_ti_t *ti = info; - // TODO: Don't start the mouse automatically, find a nice place to put - // a public function to be called by users. - // TODO: Try to autodetect rxvt and use its protocol instead of mode 1000 - // TODO: Also give the user a choice to use 1005, 1006 or 1015 - if (ti->have_mouse && !set_mouse (ti, true)) - return false; - return write_string (tk, ti->start_string); + return write_string (tk, ti->start_string) + && ((!ti->have_mouse && tk->mouse_proto == TERMO_MOUSE_PROTO_NONE) + || mouse_reset (ti)) // We can never be sure + && mouse_set_proto (ti, tk->mouse_proto, true) + && mouse_set_tracking_mode (ti, tk->mouse_tracking, true); } static int stop_driver (termo_t *tk, void *info) { termo_ti_t *ti = info; - if (ti->have_mouse && !set_mouse (ti, false)) - return false; - return write_string (tk, ti->stop_string); + return write_string (tk, ti->stop_string) + && mouse_set_proto (ti, tk->mouse_proto, false) + && mouse_set_tracking_mode (ti, tk->mouse_tracking, false); +} + +static void * +new_driver (termo_t *tk, const char *term) +{ + termo_ti_t *ti = calloc (1, sizeof *ti); + if (!ti) + return NULL; + + ti->tk = tk; + ti->root = new_node_arr (0, 0xff); + if (!ti->root) + goto abort_free_ti; + + if (!load_terminfo (ti, term)) + goto abort_free_trie; + + ti->root = compress_trie (ti->root); + + tk->ti_data = ti; + tk->ti_method.set_mouse_proto = mouse_set_proto; + tk->ti_method.set_mouse_tracking_mode = mouse_set_tracking_mode; + return ti; + +abort_free_trie: + free_trie (ti->root); + +abort_free_ti: + free (ti); + return NULL; } static void free_driver (void *info) { termo_ti_t *ti = info; + ti->tk->ti_data = NULL; + ti->tk->ti_method.set_mouse_proto = NULL; + ti->tk->ti_method.set_mouse_tracking_mode = NULL; + free_trie (ti->root); free (ti->set_mouse_string); free (ti->start_string); diff --git a/termo-internal.h b/termo-internal.h index b9a27c2..b2b1c3a 100644 --- a/termo-internal.h +++ b/termo-internal.h @@ -80,6 +80,22 @@ struct termo termo_key_t *key, size_t *nbytes); } method; + + int guessed_mouse_proto; // What we think should be the mouse protocol + int mouse_proto; // The active mouse protocol + termo_mouse_tracking_t mouse_tracking; // Mouse tracking mode + + // The mouse unfortunately directly depends on the terminfo driver to let + // it handle changes in the mouse protocol. + + void *ti_data; // termo_ti_t pointer + + struct + { + bool (*set_mouse_proto) (void *, int, bool); + bool (*set_mouse_tracking_mode) (void *, termo_mouse_tracking_t, bool); + } + ti_method; }; static inline void diff --git a/termo.c b/termo.c index 6970a82..a9addc4 100644 --- a/termo.c +++ b/termo.c @@ -313,6 +313,14 @@ termo_alloc (void) tk->method.emit_codepoint = &emit_codepoint; tk->method.peekkey_simple = &peekkey_simple; tk->method.peekkey_mouse = &peekkey_mouse; + + tk->mouse_proto = TERMO_MOUSE_PROTO_NONE; + tk->mouse_tracking = TERMO_MOUSE_TRACKING_NORMAL; + tk->guessed_mouse_proto = TERMO_MOUSE_PROTO_NONE; + + tk->ti_data = NULL; + tk->ti_method.set_mouse_proto = NULL; + tk->ti_method.set_mouse_tracking_mode = NULL; return tk; } @@ -625,6 +633,52 @@ termo_get_buffer_remaining (termo_t *tk) return tk->buffsize - tk->buffcount; } +int +termo_get_mouse_proto (termo_t *tk) +{ + return tk->mouse_proto; +} + +int +termo_guess_mouse_proto (termo_t *tk) +{ + return tk->guessed_mouse_proto; +} + +int +termo_set_mouse_proto (termo_t *tk, int proto) +{ + int old_proto = tk->mouse_proto; + tk->mouse_proto = proto; + + // Call the TI driver to apply the change if needed + if (!tk->is_started + || !tk->ti_method.set_mouse_proto) + return true; + return tk->ti_method.set_mouse_proto (tk->ti_data, old_proto, false) + && tk->ti_method.set_mouse_proto (tk->ti_data, proto, true); +} + +termo_mouse_tracking_t +termo_get_mouse_tracking_mode (termo_t *tk) +{ + return tk->mouse_tracking; +} + +int +termo_set_mouse_tracking_mode (termo_t *tk, termo_mouse_tracking_t mode) +{ + termo_mouse_tracking_t old_mode = tk->mouse_tracking; + tk->mouse_tracking = mode; + + // Call the TI driver to apply the change if needed + if (!tk->is_started + || !tk->ti_method.set_mouse_tracking_mode) + return true; + return tk->ti_method.set_mouse_tracking_mode (tk->ti_data, old_mode, false) + && tk->ti_method.set_mouse_tracking_mode (tk->ti_data, mode, true); +} + static void eat_bytes (termo_t *tk, size_t count) { diff --git a/termo.h b/termo.h index d24dccd..ee24d87 100644 --- a/termo.h +++ b/termo.h @@ -121,6 +121,26 @@ enum termo_mouse_event TERMO_MOUSE_RELEASE }; +// You will want to handle GPM (incompatible license) and FreeBSD's sysmouse +// yourself. http://www.monkey.org/openbsd/archive/tech/0009/msg00018.html + +enum +{ + TERMO_MOUSE_PROTO_NONE = 0, + TERMO_MOUSE_PROTO_VT200 = 1 << 0, + TERMO_MOUSE_PROTO_UTF8 = 1 << 1 | TERMO_MOUSE_PROTO_VT200, + TERMO_MOUSE_PROTO_SGR = 1 << 2 | TERMO_MOUSE_PROTO_VT200, + TERMO_MOUSE_PROTO_RXVT = 1 << 3 | TERMO_MOUSE_PROTO_VT200 +}; + +typedef enum termo_mouse_tracking termo_mouse_tracking_t; +enum termo_mouse_tracking +{ + TERMO_MOUSE_TRACKING_NORMAL, + TERMO_MOUSE_TRACKING_DRAG, + TERMO_MOUSE_TRACKING_ANY +}; + enum { TERMO_KEYMOD_SHIFT = 1 << 0, @@ -209,6 +229,13 @@ size_t termo_get_buffer_remaining (termo_t *tk); void termo_canonicalise (termo_t *tk, termo_key_t *key); +int termo_get_mouse_proto (termo_t *tk); +int termo_set_mouse_proto (termo_t *tk, int proto); +int termo_guess_mouse_proto (termo_t *tk); + +termo_mouse_tracking_t termo_get_mouse_tracking_mode (termo_t *tk); +int termo_set_mouse_tracking_mode (termo_t *tk, termo_mouse_tracking_t mode); + termo_result_t termo_getkey (termo_t *tk, termo_key_t *key); termo_result_t termo_getkey_force (termo_t *tk, termo_key_t *key); termo_result_t termo_waitkey (termo_t *tk, termo_key_t *key);