<<< Date Index >>>     <<< Thread Index >>>

[PATCH, RFC] mouse tracking support



Hi,

I've updated Anatoly's patch from 2005 that adds mouse tracking using
slang/ncurses to mutt. With "set mouse" (unset by default) mutt will
react to button1/2/3/wheel with configurable bindings. I like it :)

I've tested it with Debian's ncurses which unfortunately doesn't
support the mouse wheel (yet), but I think it should work. slang is
untested. Testers welcome.

It would be nice if the patch could be considered for mutt 2.0.

(More comments below.)


# HG changeset patch
# User Christoph Berg <cb@xxxxxxxx>
# Date 1179420372 -7200
# Node ID c3665ea5ba2ac090527ce98fd672252e65c012b7
# Parent  33af2883d52b99ece0a935818a91293b502603aa
Add mouse ncurses/slang mouse tracking capability, set mouse=yes to enable.
Based on a patch by Anatoly Vorobey.

diff -r 33af2883d52b -r c3665ea5ba2a OPS
--- a/OPS       Tue May 15 21:05:53 2007 +0200
+++ b/OPS       Thu May 17 18:46:12 2007 +0200
@@ -129,6 +129,7 @@ OP_MAIN_UNDELETE_PATTERN "undelete messa
 OP_MAIN_UNDELETE_PATTERN "undelete messages matching a pattern"
 OP_MAIN_UNTAG_PATTERN "untag messages matching a pattern"
 OP_MIDDLE_PAGE "move to the middle of the page"
+OP_MOUSE_SELECT "select entry with a mouse button"
 OP_NEXT_ENTRY "move to the next entry"
 OP_NEXT_LINE "scroll down one line"
 OP_NEXT_PAGE "move to the next page"
diff -r 33af2883d52b -r c3665ea5ba2a curs_lib.c
--- a/curs_lib.c        Tue May 15 21:05:53 2007 +0200
+++ b/curs_lib.c        Thu May 17 18:46:12 2007 +0200
@@ -80,6 +80,9 @@ event_t mutt_getch (void)
 {
   int ch;
   event_t err = {-1, OP_NULL }, ret;
+#ifndef USE_SLANG_CURSES
+  MEVENT m_event;
+#endif
 
   if (!option(OPTUNBUFFEREDINPUT) && UngetCount)
     return (KeyEvent[--UngetCount]);
@@ -100,7 +103,74 @@ event_t mutt_getch (void)
 
   if(ch == ERR)
     return err;
-  
+
+#ifdef USE_SLANG_CURSES /* mouse tracking */
+  if (ch == KEY_SLANG_MOUSE) {
+    /* get button number and coordinates */
+    /* coordinates are further -1'd synchronize them with the values 
+       ncurses returns: starting from 0,0 */
+    ch = getch();
+    if (ch == ERR)
+      return err;
+    LastButton = ch - 32;
+    ch = getch();
+    if (ch == ERR)
+      return err;
+    LastX = ch - 32 - 1;
+    ch = getch();
+    if (ch == ERR)
+      return err;
+    LastY = ch - 32 - 1;
+
+    dprint (5, (debugfile, "SLang mouse message: Button: %d X: %d Y: %d\n", 
LastButton, LastX, LastY));
+
+    /* which event is it? */
+    ch = -1;
+
+    /* ignore shift-alt-control modifiers for now */
+
+    if (LastButton & 64) {     /* wheel mouse? */
+      if ((LastButton & 3) == 0)
+        ch = KEY_MUTT_BUTTON4;
+      else if ((LastButton & 3) == 1)
+        ch = KEY_MUTT_BUTTON5;
+    } else {
+      if ((LastButton & 3) == 0)
+        ch = KEY_MUTT_BUTTON1;
+      else if ((LastButton & 3) == 1)
+        ch = KEY_MUTT_BUTTON2;
+      else if ((LastButton & 3) == 2)
+        ch = KEY_MUTT_BUTTON3;
+    }
+
+    dprint(5, (debugfile, "SLang mouse: emitting key 0x%x\n", ch));
+
+  }
+#else
+  if (ch == KEY_MOUSE) {
+    if (getmouse(&m_event) == OK) {
+      LastX = m_event.x;
+      LastY = m_event.y;
+      LastButton = m_event.bstate;
+      if (m_event.bstate & BUTTON1_CLICKED)
+        ch = KEY_MUTT_BUTTON1;
+      else if (m_event.bstate & BUTTON2_CLICKED)
+        ch = KEY_MUTT_BUTTON2;
+      else if (m_event.bstate & BUTTON3_CLICKED)
+        ch = KEY_MUTT_BUTTON3;
+      else if (m_event.bstate & BUTTON4_CLICKED)
+        ch = KEY_MUTT_BUTTON4;
+# if NCURSES_MOUSE_VERSION > 1
+      else if (m_event.bstate & BUTTON5_CLICKED)
+        ch = KEY_MUTT_BUTTON5;
+# endif
+      else ch = -1;
+      dprint (5, (debugfile, "NCurses mouse message: bstate: 0x%x X: %d Y: 
%d\n", LastButton, LastX, LastY));
+      dprint(5, (debugfile, "NCurses mouse: emitting key 0x%x\n", ch));
+    }
+  }
+#endif /* mouse tracking */
+
   if ((ch & 0x80) && option (OPTMETAKEY))
   {
     /* send ALT-x as ESC-x */
@@ -417,6 +487,10 @@ void mutt_endwin (const char *msg)
     
     attrset (A_NORMAL);
     mutt_refresh ();
+#ifdef USE_SLANG_CURSES
+    SLtt_set_mouse_mode(0, 0);
+    /* ncurses: endwin() resets xterm mouse tracking for us */
+#endif
     endwin ();
   }
   
diff -r 33af2883d52b -r c3665ea5ba2a curs_main.c
--- a/curs_main.c       Tue May 15 21:05:53 2007 +0200
+++ b/curs_main.c       Thu May 17 18:46:12 2007 +0200
@@ -744,6 +744,12 @@ int mutt_index_menu (void)
        menu_current_bottom (menu);
        break;
 
+      case OP_MOUSE_SELECT:
+       /* jump to a line if we're not on it already; otherwise
+          display that message */
+       menu_mouse_click (menu, OP_DISPLAY_MESSAGE);
+       break;
+
       case OP_JUMP:
 
        CHECK_MSGCOUNT;
diff -r 33af2883d52b -r c3665ea5ba2a functions.h
--- a/functions.h       Tue May 15 21:05:53 2007 +0200
+++ b/functions.h       Thu May 17 18:46:12 2007 +0200
@@ -79,6 +79,7 @@ struct binding_t OpGeneric[] = { /* map:
   { "current-middle",   OP_CURRENT_MIDDLE,     NULL },
   { "current-bottom",   OP_CURRENT_BOTTOM,     NULL },
   { "what-key",                OP_WHAT_KEY,            NULL },
+  { "mouse-select",    OP_MOUSE_SELECT,        "<Button1>" },
   { NULL,              0,                      NULL }
 };
 
@@ -158,7 +159,7 @@ struct binding_t OpMain[] = { /* map: in
   { "next-unread",             OP_MAIN_NEXT_UNREAD,            NULL },
   { "previous-unread",         OP_MAIN_PREV_UNREAD,            NULL },
   { "parent-message",          OP_MAIN_PARENT_MESSAGE,         "P" },
-
+  { "mouse-select",            OP_MOUSE_SELECT,                "<Button1>" },
 
   { "extract-keys",            OP_EXTRACT_KEYS,                "\013" },
   { "forget-passphrase",       OP_FORGET_PASSPHRASE,           "\006" },
diff -r 33af2883d52b -r c3665ea5ba2a init.h
--- a/init.h    Tue May 15 21:05:53 2007 +0200
+++ b/init.h    Thu May 17 18:46:12 2007 +0200
@@ -1279,7 +1279,14 @@ struct option_t MuttVars[] = {
   */
 #endif
 
-
+  { "mouse",           DT_BOOL, R_NONE, OPTMOUSE, 0 },
+  /*
+  ** .pp
+  ** Controls whether or not Mutt will react to mouse clicks in your terminal.
+  ** .pp
+  ** \fBNote:\fP you need to set this option in .muttrc; it won't have any
+  ** effect when used interactively.
+  */
   { "move",            DT_QUAD, R_NONE, OPT_MOVE, M_ASKNO },
   /*
   ** .pp
diff -r 33af2883d52b -r c3665ea5ba2a keymap.c
--- a/keymap.c  Tue May 15 21:05:53 2007 +0200
+++ b/keymap.c  Thu May 17 18:46:12 2007 +0200
@@ -86,11 +86,24 @@ static struct mapping_t KeyNames[] = {
 #ifdef KEY_NEXT
   { "<Next>",    KEY_NEXT },
 #endif  
+
+  { "<Button1>", KEY_MUTT_BUTTON1 },
+  { "<Button2>", KEY_MUTT_BUTTON2 },
+  { "<Button3>", KEY_MUTT_BUTTON3 },
+  { "<WheelUp>", KEY_MUTT_BUTTON4 },
+  { "<WheelDown>", KEY_MUTT_BUTTON5 },
+  { "<Button4>", KEY_MUTT_BUTTON4 },
+  { "<Button5>", KEY_MUTT_BUTTON5 },
+
   { NULL,      0 }
 };
 
 /* contains the last key the user pressed */
 int LastKey;
+
+/* coordinates of last mouse event, and button number */
+int LastX, LastY;
+int LastButton;
 
 struct keymap_t *Keymaps[MENU_MAX];
 
@@ -661,6 +674,17 @@ void km_init (void)
   /* edit-to (default "t") hides generic tag-entry in Compose menu
      This will bind tag-entry to  "T" in the Compose menu */
   km_bindkey ("T", MENU_COMPOSE, OP_TAG);
+
+  /* mouse tracking bindings */
+  km_bindkey ("<Button3>", MENU_GENERIC, OP_EXIT);
+  km_bindkey ("<Button3>", MENU_PAGER, OP_EXIT);
+  km_bindkey ("<Button3>", MENU_MAIN, OP_QUIT);
+#if defined(USE_SLANG_CURSES) || NCURSES_MOUSE_VERSION > 1
+  km_bindkey ("<WheelUp>", MENU_PAGER, OP_PREV_LINE);
+  km_bindkey ("<WheelDown>", MENU_PAGER, OP_NEXT_LINE);
+  km_bindkey ("<WheelUp>", MENU_GENERIC, OP_PREV_ENTRY);
+  km_bindkey ("<WheelDown>", MENU_GENERIC, OP_NEXT_ENTRY);
+#endif
 }
 
 void km_error_key (int menu)
diff -r 33af2883d52b -r c3665ea5ba2a keymap.h
--- a/keymap.h  Tue May 15 21:05:53 2007 +0200
+++ b/keymap.h  Thu May 17 18:46:12 2007 +0200
@@ -87,6 +87,10 @@ extern struct keymap_t *Keymaps[];
 /* dokey() records the last real key pressed  */
 extern int LastKey;
 
+/* coordinates of last mouse event, and button number */
+extern int LastX, LastY;
+extern int LastButton;
+
 extern struct mapping_t Menus[];
 
 struct binding_t
diff -r 33af2883d52b -r c3665ea5ba2a main.c
--- a/main.c    Tue May 15 21:05:53 2007 +0200
+++ b/main.c    Thu May 17 18:46:12 2007 +0200
@@ -714,6 +714,35 @@ int main (int argc, char **argv)
   mutt_init (flags & M_NOSYSRC, commands);
   mutt_free_list (&commands);
 
+  if (!option (OPTNOCURSES) && option (OPTMOUSE))
+  {
+#ifdef USE_SLANG_CURSES
+    /* TODO: also add a runtime option for this. maybe also for the
+       second argument, to force */
+    SLtt_set_mouse_mode (1, 0);
+
+    /* HACK: add ESC[M to SLang's keypad keymap, so that it "finds"
+       it and doesn't clear the input buffer. If we don't do this,
+       SLkeypad.c tries to match ESC[M and when it cannot, returns it
+       but before that flushes the remaining input, so we lose
+       parameters that follow ESC[M.
+
+       Sigh. SLang should support its own mouse tracking together with
+       its own keypad better. */
+
+    SLkp_define_keysym ("\033[M", KEY_SLANG_MOUSE);
+#else
+    mousemask (BUTTON1_CLICKED | BUTTON2_CLICKED | BUTTON3_CLICKED | 
BUTTON4_CLICKED
+# if NCURSES_MOUSE_VERSION > 1
+      | BUTTON5_CLICKED
+# endif
+      , 0);
+    /* make ncurses give us a click for every press and ignore doubleclicks
+       and such. -1 here seems like it should be working but didn't. */
+    mouseinterval (0);
+#endif
+  }
+
   /* Initialize crypto backends.  */
   crypt_init ();
 
diff -r 33af2883d52b -r c3665ea5ba2a menu.c
--- a/menu.c    Tue May 15 21:05:53 2007 +0200
+++ b/menu.c    Thu May 17 18:46:12 2007 +0200
@@ -662,6 +662,21 @@ void menu_prev_entry (MUTTMENU *menu)
   }
   else
     mutt_error _("You are on the first entry.");
+}
+
+void menu_mouse_click (MUTTMENU *menu, int op)
+{
+  int click = LastY + menu->top - menu->offset;
+  if (click < 0)
+    click = 0;
+  if (click >= menu->max)
+    click = menu->max -1;
+  if (click == menu->current) {
+    mutt_ungetch(0, op);
+  } else {
+    menu->current = click;
+    menu->redraw = REDRAW_MOTION;
+  }
 }
 
 static int default_color (int i)
@@ -1005,6 +1020,12 @@ int mutt_menuLoop (MUTTMENU *menu)
          mutt_error _("Jumping is not implemented for dialogs.");
        else
          menu_jump (menu);
+       break;
+
+      case OP_MOUSE_SELECT:
+       /* jump to a line if we're not on it already; otherwise
+          select entry */
+       menu_mouse_click (menu, OP_GENERIC_SELECT_ENTRY);
        break;
 
       case OP_ENTER_COMMAND:
diff -r 33af2883d52b -r c3665ea5ba2a mutt.h
--- a/mutt.h    Tue May 15 21:05:53 2007 +0200
+++ b/mutt.h    Thu May 17 18:46:12 2007 +0200
@@ -407,6 +407,7 @@ enum
   OPTMETOO,
   OPTMHPURGE,
   OPTMIMEFORWDECODE,
+  OPTMOUSE,            /* curses/slang mouse tracking */
   OPTNARROWTREE,
   OPTPAGERSTOP,
   OPTPIPEDECODE,
diff -r 33af2883d52b -r c3665ea5ba2a mutt_curses.h
--- a/mutt_curses.h     Tue May 15 21:05:53 2007 +0200
+++ b/mutt_curses.h     Thu May 17 18:46:12 2007 +0200
@@ -185,6 +185,21 @@ void ci_start_color (void);
 #define MAYBE_REDRAW(x) if (option (OPTNEEDREDRAW)) { unset_option 
(OPTNEEDREDRAW); x = REDRAW_FULL; }
 
 /* ----------------------------------------------------------------------------
+ * Support for mouse tracking
+ */
+
+/* Neither SLang nor ncurses use keysyms above 0x1000, use these
+   for our private keys */
+#ifdef USE_SLANG_CURSES
+# define KEY_SLANG_MOUSE  0x1001  /* a pseudo-key for SLang */
+#endif
+#define KEY_MUTT_BUTTON1 0x1011
+#define KEY_MUTT_BUTTON2 0x1012
+#define KEY_MUTT_BUTTON3 0x1013
+#define KEY_MUTT_BUTTON4 0x1014
+#define KEY_MUTT_BUTTON5 0x1015
+
+/* ----------------------------------------------------------------------------
  * These are here to avoid compiler warnings with -Wall under SunOS 4.1.x
  */
 
diff -r 33af2883d52b -r c3665ea5ba2a mutt_menu.h
--- a/mutt_menu.h       Tue May 15 21:05:53 2007 +0200
+++ b/mutt_menu.h       Thu May 17 18:46:12 2007 +0200
@@ -102,6 +102,7 @@ void menu_current_middle (MUTTMENU *);
 void menu_current_middle (MUTTMENU *);
 void menu_current_bottom (MUTTMENU *);
 void menu_check_recenter (MUTTMENU *);
+void menu_mouse_click (MUTTMENU *, int);
 void menu_status_line (char *, size_t, MUTTMENU *, const char *);
 
 MUTTMENU *mutt_new_menu (void);


Comments on the original message:


Re: Anatoly Vorobey 2005-04-03 <20050402224923.GA21137@xxxxxxxxx>
> Attached is a patch to enable [some degree of] mouse tracking support,
> for mutt compiled against either ncurses or SLang.
> 
> The patch is against CVS HEAD; criticism, praise, bug reports, requests for 
> patches against other versions, feature requests, questions, and so on are 
> welcome. 
> 
> I'd especially like to hear if this has any chance of making it into mutt. 
> (I'm aware of past discussions of this issue, and the fact that many 
> users/developers see no reason to add such a feature. Still, I had an itch
> to scratch, so I wrote it).
> 
> Notes:
> 
> - To use, apply the patch, configure mutt as usual, with either ncurses or 
> SLang, and then add either "#define HAVE_NCURSES_MOUSE 1" or 
> "#define HAVE_SLANG_MOUSE 1" to your config.h, and then make, or re-make, 
> mutt.
> 
>   Or, if you don't want to change config.h, use a hack such as 
>   $ export AM_CFLAGS="-DHAVE_SLANG_MOUSE=1" 
>   $ make
>   That'll make mutt's Makefile pick up the needed flag.
> 
>   I haven't yet added the recognition of the right option to configure, 
>   because, in order to do that, I have to learn autoconf first. Been avoiding
>   that for some years.

Mouse support is now a config option. I didn't add a configure flag as
I don't think having it built-in but disabled by default hurts.

> - With SLang, you must have a version recent enough to include 
> SLtt_set_mouse_mode() . Based on slang's changes.txt, this appears to be
> 0.99.21, but I've only tested with 1.4.9 so far. I haven't tested this stuff
> on platforms other than Linux (so far).

Untested.

> - Currently the events that are recognised are: button clicks and wheel 
> movements for wheel mice. Wheel movements are only recognized with SLang 
> because ncurses doesn't support wheel movement recognition. 

Wheel mouse should work with a ncurses compiled with --ext-mouse
(requires ABI/SONAME bump for libncurses).

> - The following bindings are hardcoded into keymap.c (but can of course be 
> changed in .muttrc): In message index menu, wheel movement scrolls the menu; 
> clicking a line moves the selection to that message, if it's not selected, or
> displays the message if it's alrady selected (as if you pressed Enter on it).
> This means also that double-clicking a line selects and displays the 
> message. In pager mode, wheel movement scrolls the view. Ideas for other 
> bindinds/new operations to add which would rely on them will be appreciated.

Left button will jump to/select a line in all menus. Right button is
bound to "exit" in menus/pager and "quit" in the index.

> - Your terminal program may or may not support the needed functionality for
> mouse tracking support to work. Currently, the SLang version only works with 
> terminals that support "xterm mouse tracking" in the "old X10 compatibility 
> mode", because this is what SLang's SLtt_set_mouse_mode() does. The ncurses
> version works with whatever ncurses was compiled to recognize, which will
> at least be "xterm mouse tracking" in the "normal tracking mode" (better than 
> the one SLang uses), and also possibly GPM (Linux) and sysmouse (BSD). I only
> tested ncurses with xterm-style tracking so far. The terminal programs I 
> tested this on are: 
>   * gnome-terminal, a recent version - ncurses and SLang work, wheel movement 
>     works with SLang.
>   * xterm, some recent version - ncurses and SLang work, wheel movement 
>     doesn't work.
>   * PuTTY - only works with ncurses, because PuTTY doesn't support the 
>     "old X10 compatibility mode".

Tested here with xterm.

> Plans:
> 
> - introduce a runtime option to use/not use mouse tracking, therefore 
> enabling mutt not to use it even if it's compiled in (suggestions for
> option name/behavior?)
> - integrate recognition of HAVE_SLANG_MOUSE/HAVE_NCURSES_MOUSE into 
> configure;

set mouse.

> - configurable speed of wheel scrolling in pager/menu?

Should be done by macros (bind index WheelUp next-page or macro ...
<down><down><down> etc.)

> - look into bypassing SLang's SLtt_set_mouse_mode() and using normal
> mouse tracking with SLang; then it'd be possible to track and bind
> button releases and buttons+modifiers (shift, control, meta -- whenever the 
> terminal program doesn't use them for its own actions).
> 
>   This might be a good thing to do, especially because it's possible that 
>   the only combination I have right now in which wheel movement works (SLang 
> + 
>   gnome-terminal) does so because gnome-terminal sends too much information
>   in violation, strictly speaking, of the X10 compatibility mode; the spec is 
>   a bit unclear on this. I'd like to see reports of other terminal programs.

xterm uses Shift+Mouse for selection handling as with disabled mouse
tracking; probably not possible/desirable to work around.

> - look into bypassing ncurses' handling of mouse tracking and parsing xterm 
>   mouse tracking ourselves; this would be bad because ncurses won't use 
>   GPM/sysmouse if they're available, but this would be good because mutt will 
>   be able to recognize wheel movement with ncurses.

While gpm support could be a nice feature, I don't think it is needed.

> - suggestions?
> 
> I'm going to do some/all of the above based on whether I need these 
> features, whether  there's enough interest in the patch and 
> requests/suggestions from other users, and whether it has any chance of 
> making it into mutt. 
> 
> Best regards,
> Anatoly


Christoph