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

[PATCH] mouse tracking support



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.

- 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).

- 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. 

- 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.

- 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".

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;
- configurable speed of wheel scrolling in pager/menu?
- 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.

- 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.

- 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

-- 
avva
"There's nothing simply good, nor ill alone" -- John Donne

Index: OPS
===================================================================
RCS file: /home/roessler/cvs/mutt/OPS,v
retrieving revision 3.6
diff -u -r3.6 OPS
--- OPS 17 Feb 2005 03:33:00 -0000      3.6
+++ OPS 2 Apr 2005 21:48:23 -0000
@@ -175,3 +175,4 @@
 OP_MAIN_SHOW_LIMIT "show currently active limit pattern"
 OP_MAIN_COLLAPSE_THREAD "collapse/uncollapse current thread"
 OP_MAIN_COLLAPSE_ALL "collapse/uncollapse all threads"
+OP_MSELECT "select a message with a mouse button"
\ No newline at end of file
Index: curs_lib.c
===================================================================
RCS file: /home/roessler/cvs/mutt/curs_lib.c,v
retrieving revision 3.14
diff -u -r3.14 curs_lib.c
--- curs_lib.c  3 Feb 2005 17:01:43 -0000       3.14
+++ curs_lib.c  2 Apr 2005 21:48:25 -0000
@@ -80,6 +80,9 @@
 {
   int ch;
   event_t err = {-1, OP_NULL }, ret;
+#ifdef HAVE_NCURSES_MOUSE
+  MEVENT m_event;
+#endif
 
   if (!option(OPTUNBUFFEREDINPUT) && UngetCount)
     return (KeyEvent[--UngetCount]);
@@ -100,7 +103,72 @@
 
   if(ch == ERR)
     return err;
-  
+
+#ifdef HAVE_SLANG_MOUSE
+  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));
+
+  }
+#endif
+
+#ifdef HAVE_NCURSES_MOUSE
+  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;
+      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
+
   if ((ch & 0x80) && option (OPTMETAKEY))
   {
     /* send ALT-x as ESC-x */
@@ -361,6 +429,12 @@
     
     attrset (A_NORMAL);
     mutt_refresh ();
+#if HAVE_SLANG_MOUSE
+    SLtt_set_mouse_mode(0, 0);
+#endif
+#if HAVE_NCURSES_MOUSE
+    /* endwin() resets xterm mouse tracking for us */
+#endif 
     endwin ();
   }
   
Index: curs_main.c
===================================================================
RCS file: /home/roessler/cvs/mutt/curs_main.c,v
retrieving revision 3.21
diff -u -r3.21 curs_main.c
--- curs_main.c 28 Feb 2005 18:33:45 -0000      3.21
+++ curs_main.c 2 Apr 2005 21:48:28 -0000
@@ -734,6 +734,19 @@
        menu_current_bottom (menu);
        break;
 
+#if defined(HAVE_SLANG_MOUSE) || defined(HAVE_NCURSES_MOUSE)
+      case OP_MSELECT:
+        /* jump to a line if we're not on it already; otherwise
+           display that message */
+        if (LastY + menu->top - menu->offset == menu->current) {
+          mutt_ungetch(0, OP_DISPLAY_MESSAGE);
+        } else {
+          menu->current = LastY + menu->top - menu->offset;
+          menu->redraw = REDRAW_MOTION;
+        }
+        break;
+#endif
+
       case OP_JUMP:
 
        CHECK_MSGCOUNT;
@@ -1119,7 +1132,7 @@
 
       case OP_DISPLAY_MESSAGE:
       case OP_DISPLAY_HEADERS: /* don't weed the headers */
-
+        
        CHECK_MSGCOUNT;
         CHECK_VISIBLE;
        /*
Index: keymap.c
===================================================================
RCS file: /home/roessler/cvs/mutt/keymap.c,v
retrieving revision 3.13
diff -u -r3.13 keymap.c
--- keymap.c    3 Feb 2005 17:01:43 -0000       3.13
+++ keymap.c    2 Apr 2005 21:48:30 -0000
@@ -83,12 +83,27 @@
 #ifdef KEY_BTAB
   { "<BackTab>", KEY_BTAB },
 #endif
+#if defined(HAVE_SLANG_MOUSE) || defined(HAVE_NCURSES_MOUSE)
+  { "<Button1>", KEY_MUTT_BUTTON1 },
+  { "<Button2>", KEY_MUTT_BUTTON2 },
+  { "<Button3>", KEY_MUTT_BUTTON3 },
+  { "<Button4>", KEY_MUTT_BUTTON4 },
+  { "<Button5>", KEY_MUTT_BUTTON5 },
+  { "<WheelUp>", KEY_MUTT_BUTTON4 },
+  { "<WheelDown>", KEY_MUTT_BUTTON5 },
+#endif
   { NULL,      0 }
 };
 
 /* contains the last key the user pressed */
 int LastKey;
 
+#if defined(HAVE_SLANG_MOUSE) || defined(HAVE_NCURSES_MOUSE)
+/* coordinates of last mouse event, and button number */
+int LastX, LastY;
+int LastButton;
+#endif
+
 struct keymap_t *Keymaps[MENU_MAX];
 
 static struct keymap_t *allocKeys (int len, keycode_t *keys)
@@ -658,6 +673,16 @@
   /* 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 */
+#if defined(HAVE_SLANG_MOUSE) || defined(HAVE_NCURSES_MOUSE)
+  km_bindkey("<Button1>", MENU_MAIN, OP_MSELECT);
+  km_bindkey("<WheelUp>", MENU_PAGER, OP_PREV_LINE);
+  km_bindkey("<WheelDown>", MENU_PAGER, OP_NEXT_LINE);
+  km_bindkey ("<WheelUp>", MENU_MAIN, OP_PREV_ENTRY);
+  km_bindkey ("<WheelDown>", MENU_MAIN, OP_NEXT_ENTRY);
+
+#endif
 }
 
 void km_error_key (int menu)
Index: keymap.h
===================================================================
RCS file: /home/roessler/cvs/mutt/keymap.h,v
retrieving revision 3.8
diff -u -r3.8 keymap.h
--- keymap.h    17 Jun 2004 20:36:13 -0000      3.8
+++ keymap.h    2 Apr 2005 21:48:30 -0000
@@ -87,6 +87,12 @@
 /* dokey() records the last real key pressed  */
 extern int LastKey;
 
+#if defined(HAVE_SLANG_MOUSE) || defined(HAVE_NCURSES_MOUSE)
+/* coordinates of last mouse event, and button number */
+extern int LastX, LastY;
+extern int LastButton;
+#endif
+
 extern struct mapping_t Menus[];
 
 struct binding_t
@@ -119,4 +125,15 @@
 
 #include "keymap_defs.h"
 
+#if defined(HAVE_SLANG_MOUSE) || defined(HAVE_NCURSES_MOUSE)
+/* Neither SLang nor ncurses use keysyms above 0x1000, use these
+   for our private keys */
+#define KEY_SLANG_MOUSE   0x1001  /* a pseudo-key for SLang */
+#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
+#endif
+
 #endif /* KEYMAP_H */
Index: main.c
===================================================================
RCS file: /home/roessler/cvs/mutt/main.c,v
retrieving revision 3.22
diff -u -r3.22 main.c
--- main.c      12 Feb 2005 19:38:14 -0000      3.22
+++ main.c      2 Apr 2005 21:48:32 -0000
@@ -492,6 +492,31 @@
 #if HAVE_META
   meta (stdscr, TRUE);
 #endif
+#ifdef HAVE_SLANG_MOUSE
+  /* 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);
+#endif
+
+#ifdef HAVE_NCURSES_MOUSE
+  mousemask(BUTTON1_CLICKED | BUTTON2_CLICKED | BUTTON3_CLICKED |
+            BUTTON4_CLICKED, 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. */
+  (void)mouseinterval(0);
+#endif
+
 }
 
 #define M_IGNORE  (1<<0)       /* -z */