Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

PATCH: zcurses mouse handling



This implements mouse handling, but only for ncurses.  The array of
mouse status could be done in a myriad different ways; the one here is a
fairly simple formatting of the information available, not of all which
is necessarily useful.

The manual page doesn't document the way buttons and events are
combined, so I've just assumed that only the combined results like
BUTTON1_PRESSED are available publically, which complicates the code a
bit.

It appears the REPORT_MOUSE_POSITION mask ("zcurses mouse +motion")
isn't implemented; Google produced hints to that effect, but no
definitive ruling; anyone have a clue?

In principle you can specify mouse events to be reported or not in much
more detail, but there didn't seem a lot of point in that.

I think the big thing missing now is to be able to query characters and
attributes within windows.

Index: Doc/Zsh/mod_curses.yo
===================================================================
RCS file: /cvsroot/zsh/zsh/Doc/Zsh/mod_curses.yo,v
retrieving revision 1.19
diff -u -r1.19 mod_curses.yo
--- Doc/Zsh/mod_curses.yo	6 Nov 2007 14:14:17 -0000	1.19
+++ Doc/Zsh/mod_curses.yo	7 Nov 2007 22:20:46 -0000
@@ -23,8 +23,9 @@
 xitem(tt(zcurses) tt(border) var(targetwin) var(border) )(
 xitem(tt(zcurses) tt(attr) var(targetwin) [ var({+/-}attribute) | var(fg_col)tt(/)var(bg_col) ] [...])
 xitem(tt(zcurses) tt(bg) var(targetwin) [ var({+/-}attribute) | var(fg_col)tt(/)var(bg_col) | tt(@)var(char) ] [...])
-xitem(tt(zcurses) tt(scroll) [ tt(on) | tt(off) | {+/-}var(lines) ])
-xitem(tt(zcurses) tt(input) var(targetwin) [ var(param) [ var(kpparm) ] ])
+xitem(tt(zcurses) tt(scroll) var(targetwin) [ tt(on) | tt(off) | {+/-}var(lines) ])
+xitem(tt(zcurses) tt(input) var(targetwin) [ var(param) [ var(kparam) [ var(mparam) ] ] ])
+xitem(tt(zcurses) tt(mouse) [ tt(delay) var(num) | {+/-}tt(motion) ])
 item(tt(zcurses) tt(timeout) var(targetwin) var(intval))(
 Manipulate curses windows.  All uses of this command should be
 bracketed by `tt(zcurses init)' to initialise use of curses, and
@@ -129,14 +130,59 @@
 tt(input) reads a single character from the window without echoing
 it back.  If var(param) is supplied the character is assigned to the
 parameter var(param), else it is assigned to the parameter var(REPLY).
-If both var(param) and var(kpparam) are supplied, the key is read
-in `keypad' mode.  In this mode special keys such as function keys
-and arrow keys return the name of the key in the parameter var(kpparam).
-The key names are the macros defined in the tt(curses.h) or tt(ncurses.h)
-with the prefix `tt(KEY_)' removed.  Other keys cause a value to be set in
-var(param) as before.  On a succesful return only one of var(param) or
-var(kpparm) contains a non-empty string; the other is set to an empty
-string.
+
+If both var(param) and var(kparam) are supplied, the key is read in
+`keypad' mode.  In this mode special keys such as function keys and
+arrow keys return the name of the key in the parameter var(kparam).  The
+key names are the macros defined in the tt(curses.h) or tt(ncurses.h)
+with the prefix `tt(KEY_)' removed; see also the description of the
+parameter tt(zcurses_keycodes) below.  Other keys cause a value to be
+set in var(param) as before.  On a succesful return only one of
+var(param) or var(kparam) contains a non-empty string; the other is set
+to an empty string.
+
+If var(mparam) is also supplied, tt(input) attempts to handle mouse
+input.  This is only available with the ncurses library; mouse handling
+can be detected by checking for the exit status of `tt(zcurses mouse)' with
+no arguments.  If a mouse
+button is clicked (or double- or triple-clicked, or pressed or released with
+a configurable delay from being clicked) then tt(kparam) is set to the string
+tt(MOUSE), and var(mparam) is set to an array consisting of the
+following elements:
+startitem()
+sitem(-)(An identifier to discriminate different input devices; this
+is only rarely useful.)
+sitem(-)(The x, y and z coordinates of the mouse click relative to
+the full screen, as three elements in that order (i.e. the y coordinate
+is, unusually, after the x coordinate).  The z coordinate is only
+available for a few unusual input devices and is otherwise set to zero.)
+sitem(-)(Any events that occurred as separate items; usually
+there will be just one.  An event consists of tt(PRESSED), tt(RELEASED),
+tt(CLICKED), tt(DOUBLE_CLICKED) or tt(TRIPLE_CLICKED) followed
+immediately (in the same element) by the number of the button.)
+sitem(-)(If the shift key was pressed, the string tt(SHIFT).)
+sitem(-)(If the control key was pressed, the string tt(CTRL).)
+sitem(-)(If the alt key was pressed, the string tt(ALT).)
+endsitem()
+
+Not all mouse events may be passed through to the terminal window;
+most terminal emulators handle some mouse events themselves.  Note
+that the ncurses manual implies that using input both with and
+without mouse handling may cause the mouse cursor to appear and
+disappear.
+
+The subcommand tt(mouse) can be used to configure the use of the mouse.
+There is no window argument; mouse options are global.
+`tt(zcurses mouse)' with no arguments returns status 0 if mouse handling
+is possible, else status 1.  Otherwise, the possible arguments (which
+may be combined on the same command line) are as follows.
+tt(delay) var(num) sets the maximum delay in milliseconds between press and
+release events to be considered as a click; the value 0 disables click
+resolution, and the default is one sixth of a second.  tt(motion) proceeded
+by an optional `tt(PLUS())' (the default) or tt(-) turns on or off
+reporting of mouse motion in addition to clicks, presses and releases,
+which are always reported.  However, it appears reports for mouse
+motion are not currently implemented.
 
 tt(timeout) specifies a timeout value for input from var(targetwin).
 If var(intval) is negative, `tt(zcurses input)' waits indefinitely for
Index: Src/Modules/curses.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/Modules/curses.c,v
retrieving revision 1.33
diff -u -r1.33 curses.c
--- Src/Modules/curses.c	6 Nov 2007 14:14:17 -0000	1.33
+++ Src/Modules/curses.c	7 Nov 2007 22:20:47 -0000
@@ -92,6 +92,7 @@
 static struct ttyinfo curses_tty_state;
 static LinkList zcurses_windows;
 static HashTable zcurses_colorpairs = NULL;
+static int zcurses_flags;
 
 #define ZCURSES_EINVALID 1
 #define ZCURSES_EDEFINED 2
@@ -106,6 +107,11 @@
 static int zc_errno, zc_color_phase=0;
 static short next_cp=0;
 
+enum {
+    ZCF_MOUSE_ACTIVE,
+    ZCF_MOUSE_MASK_CHANGED
+};
+
 static const struct zcurses_namenumberpair zcurses_attributes[] = {
     {"blink", A_BLINK},
     {"bold", A_BOLD},
@@ -131,6 +137,70 @@
     {NULL, 0}
 };
 
+#ifdef NCURSES_MOUSE_VERSION
+enum zcurses_mouse_event_types {
+    ZCME_PRESSED,
+    ZCME_RELEASED,
+    ZCME_CLICKED,
+    ZCME_DOUBLE_CLICKED,
+    ZCME_TRIPLE_CLICKED
+};
+
+static const struct zcurses_namenumberpair zcurses_mouse_event_list[] = {
+    {"PRESSED", ZCME_PRESSED},
+    {"RELEASED", ZCME_RELEASED},
+    {"CLICKED", ZCME_CLICKED},
+    {"DOUBLE_CLICKED", ZCME_DOUBLE_CLICKED},
+    {"TRIPLE_CLICKED", ZCME_TRIPLE_CLICKED},
+    {NULL, 0}
+};
+
+struct zcurses_mouse_event {
+    int button;
+    int what;
+    mmask_t event;
+};
+
+static const struct zcurses_mouse_event zcurses_mouse_map[] = {
+    { 1, ZCME_PRESSED, BUTTON1_PRESSED },
+    { 1, ZCME_RELEASED, BUTTON1_RELEASED },
+    { 1, ZCME_CLICKED, BUTTON1_CLICKED },
+    { 1, ZCME_DOUBLE_CLICKED, BUTTON1_DOUBLE_CLICKED },
+    { 1, ZCME_TRIPLE_CLICKED, BUTTON1_TRIPLE_CLICKED },
+
+    { 2, ZCME_PRESSED, BUTTON2_PRESSED },
+    { 2, ZCME_RELEASED, BUTTON2_RELEASED },
+    { 2, ZCME_CLICKED, BUTTON2_CLICKED },
+    { 2, ZCME_DOUBLE_CLICKED, BUTTON2_DOUBLE_CLICKED },
+    { 2, ZCME_TRIPLE_CLICKED, BUTTON2_TRIPLE_CLICKED },
+
+    { 3, ZCME_PRESSED, BUTTON3_PRESSED },
+    { 3, ZCME_RELEASED, BUTTON3_RELEASED },
+    { 3, ZCME_CLICKED, BUTTON3_CLICKED },
+    { 3, ZCME_DOUBLE_CLICKED, BUTTON3_DOUBLE_CLICKED },
+    { 3, ZCME_TRIPLE_CLICKED, BUTTON3_TRIPLE_CLICKED },
+
+    { 4, ZCME_PRESSED, BUTTON4_PRESSED },
+    { 4, ZCME_RELEASED, BUTTON4_RELEASED },
+    { 4, ZCME_CLICKED, BUTTON4_CLICKED },
+    { 4, ZCME_DOUBLE_CLICKED, BUTTON4_DOUBLE_CLICKED },
+    { 4, ZCME_TRIPLE_CLICKED, BUTTON4_TRIPLE_CLICKED },
+
+#ifdef BUTTON5_PRESSED
+    /* Not defined if only 32 bits available */
+    { 5, ZCME_PRESSED, BUTTON5_PRESSED },
+    { 5, ZCME_RELEASED, BUTTON5_RELEASED },
+    { 5, ZCME_CLICKED, BUTTON5_CLICKED },
+    { 5, ZCME_DOUBLE_CLICKED, BUTTON5_DOUBLE_CLICKED },
+    { 5, ZCME_TRIPLE_CLICKED, BUTTON5_TRIPLE_CLICKED },
+#endif
+    { 0, 0, 0 }
+};
+
+mmask_t zcurses_mouse_mask = ALL_MOUSE_EVENTS;
+
+#endif
+
 /* Autogenerated keypad string/number mapping*/
 #include "curses_keys.h"
 
@@ -919,6 +989,7 @@
     ZCWin w;
     char *var;
     int keypadnum = -1;
+    int nargs = arrlen(args);
 #ifdef HAVE_WGET_WCH
     int ret;
     wint_t wi;
@@ -936,12 +1007,37 @@
 
     w = (ZCWin)getdata(node);
 
-    if (args[1] && args[2]) {
+    if (nargs >= 3) {
 	keypad(w->win, TRUE);
     } else {
 	keypad(w->win, FALSE);
     }
 
+    if (nargs >= 4) {
+#ifdef NCURSES_MOUSE_VERSION
+	if (!(zcurses_flags & ZCF_MOUSE_ACTIVE) ||
+	    (zcurses_flags & ZCF_MOUSE_MASK_CHANGED)) {
+	    if (mousemask(zcurses_mouse_mask, NULL) == ERR) {
+		zwarnnam(nam, "current mouse mode is not supported");
+		return 1;
+	    }
+	    zcurses_flags = (zcurses_flags & ~ZCF_MOUSE_MASK_CHANGED) |
+		ZCF_MOUSE_ACTIVE;
+	}
+#else
+	zwarnnam(nam, "mouse events are not supported");
+	return 1;
+#endif
+    }
+#ifdef NCURSES_MOUSE_VERSION
+    else {
+	if (zcurses_flags & ZCF_MOUSE_ACTIVE) {
+	    mousemask((mmask_t)0, NULL);
+	    zcurses_flags &= ~ZCF_MOUSE_ACTIVE;
+	}
+    }
+#endif
+
 #ifdef HAVE_WGET_WCH
     switch (wget_wch(w->win, &wi)) {
     case OK:
@@ -988,32 +1084,97 @@
 	var = "REPLY";
     if (!setsparam(var, ztrdup(instr)))
 	return 1;
-    if (args[1] && args[2]) {
+    if (nargs >= 3) {
 	if (keypadnum > 0) {
-	    const struct zcurses_namenumberpair *nnptr;
-	    char fbuf[DIGBUFSIZE+1];
-
-	    for (nnptr = keypad_names; nnptr->name; nnptr++) {
-		if (keypadnum == nnptr->number) {
-		    if (!setsparam(args[2], ztrdup(nnptr->name)))
-			return 1;
+#ifdef NCURSES_MOUSE_VERSION
+	    if (nargs >= 4 && keypadnum == KEY_MOUSE) {
+		MEVENT mevent;
+		char digits[DIGBUFSIZE];
+		LinkList margs;
+		const struct zcurses_mouse_event *zcmmp = zcurses_mouse_map;
+
+		if (!setsparam(args[2], ztrdup("MOUSE")))
+		    return 1;
+		if (getmouse(&mevent) == ERR) {
+		    /*
+		     * This may happen if the mouse wasn't in
+		     * the window, so set the array to empty
+		     * but return success.
+		     */
+		    setaparam(args[3], mkarray(NULL));
 		    return 0;
 		}
-	    }
-	    if (keypadnum > KEY_F0) {
-		/* assume it's a function key */
-		sprintf(fbuf, "F%d", keypadnum - KEY_F0);
+		margs = newlinklist();
+		sprintf(digits, "%d", (int)mevent.id);
+		addlinknode(margs, dupstring(digits));
+		sprintf(digits, "%d", mevent.x);
+		addlinknode(margs, dupstring(digits));
+		sprintf(digits, "%d", mevent.y);
+		addlinknode(margs, dupstring(digits));
+		sprintf(digits, "%d", mevent.z);
+		addlinknode(margs, dupstring(digits));
+
+		/*
+		 * We only expect one event, but it doesn't hurt
+		 * to keep testing.
+		 */
+		for (; zcmmp->button; zcmmp++) {
+		    if (mevent.bstate & zcmmp->event) {
+			const struct zcurses_namenumberpair *zcmelp =
+			    zcurses_mouse_event_list;
+			for (; zcmelp->name; zcmelp++) {
+			    if (zcmelp->number == zcmmp->what) {
+				char *evstr = zhalloc(strlen(zcmelp->name)+2);
+				sprintf(evstr, "%s%d", zcmelp->name,
+					zcmmp->button);
+				addlinknode(margs, evstr);
+
+				break;
+			    }
+			}
+		    }
+		}
+		if (mevent.bstate & BUTTON_SHIFT)
+		    addlinknode(margs, "SHIFT");
+		if (mevent.bstate & BUTTON_CTRL)
+		    addlinknode(margs, "CTRL");
+		if (mevent.bstate & BUTTON_SHIFT)
+		    addlinknode(margs, "ALT");
+		if (!setaparam(args[3], zlinklist2array(margs)));
+		    return 1;
 	    } else {
-		/* print raw number */
-		sprintf(fbuf, "%d", keypadnum);
+#endif
+		const struct zcurses_namenumberpair *nnptr;
+		char fbuf[DIGBUFSIZE+1];
+
+		for (nnptr = keypad_names; nnptr->name; nnptr++) {
+		    if (keypadnum == nnptr->number) {
+			if (!setsparam(args[2], ztrdup(nnptr->name)))
+			    return 1;
+			return 0;
+		    }
+		}
+		if (keypadnum > KEY_F0) {
+		    /* assume it's a function key */
+		    sprintf(fbuf, "F%d", keypadnum - KEY_F0);
+		} else {
+		    /* print raw number */
+		    sprintf(fbuf, "%d", keypadnum);
+		}
+		if (!setsparam(args[2], ztrdup(fbuf)))
+		    return 1;
+#ifdef NCURSES_MOUSE_VERSION
 	    }
-	    if (!setsparam(args[2], ztrdup(fbuf)))
-		return 1;
+#endif
 	} else {
 	    if (!setsparam(args[2], ztrdup("")))
 		return 1;
 	}
     }
+#ifdef NCURSES_MOUSE_VERSION
+    if (keypadnum != KEY_MOUSE && nargs >= 4)
+	setaparam(args[3], mkarray(NULL));
+#endif
     return 0;
 }
 
@@ -1058,6 +1219,55 @@
 
 
 static int
+zccmd_mouse(const char *nam, char **args)
+{
+#ifdef NCURSES_MOUSE_VERSION
+    int ret = 0;
+
+    for (; *args; args++) {
+	if (!strcmp(*args, "delay")) {
+	    char *eptr;
+	    zlong delay;
+
+	    if (!*++args ||
+		((delay = zstrtol(*args, &eptr, 10)), eptr != NULL)) {
+		zwarnnam(nam, "mouse delay requires an integer argument");
+		return 1;
+	    }
+	    if (mouseinterval((int)delay) != OK)
+		ret = 1;
+	} else {
+	    char *arg = *args;
+	    int onoff = 1;
+	    if (*arg == '+')
+		arg++;
+	    else if (*arg == '-') {
+		arg++;
+		onoff = 0;
+	    }
+	    if (!strcmp(arg, "motion")) {
+		mmask_t old_mask = zcurses_mouse_mask;
+		if (onoff)
+		    zcurses_mouse_mask |= REPORT_MOUSE_POSITION;
+		else
+		    zcurses_mouse_mask &= ~REPORT_MOUSE_POSITION;
+		if (old_mask != zcurses_mouse_mask)
+		    zcurses_flags |= ZCF_MOUSE_MASK_CHANGED;
+	    } else {
+		zwarnnam(nam, "unrecognised mouse command: %s", *arg);
+		return 1;
+	    }
+	}
+    }
+
+    return ret;
+#else
+    return 1;
+#endif
+}
+
+
+static int
 zccmd_position(const char *nam, char **args)
 {
     LinkNode node;
@@ -1141,8 +1351,9 @@
 	{"attr", zccmd_attr, 2, -1},
 	{"bg", zccmd_bg, 2, -1},
 	{"scroll", zccmd_scroll, 2, 2},
-	{"input", zccmd_input, 1, 3},
+	{"input", zccmd_input, 1, 4},
 	{"timeout", zccmd_timeout, 2, 2},
+	{"mouse", zccmd_mouse, 1, -1},
 	{"touch", zccmd_touch, 1, -1},
 	{NULL, (zccmd_t)0, 0, 0}
     };
@@ -1165,7 +1376,7 @@
 	zwarnnam(nam, "too few arguments for subcommand: %s", args[0]);
 	return 1;
     } else if (zcsc->maxargs >= 0 && num_args > zcsc->maxargs) {
-	zwarnnam(nam, "too may arguments for subcommand: %s", args[0]);
+	zwarnnam(nam, "too many arguments for subcommand: %s", args[0]);
 	return 1;
     }
 

-- 
Peter Stephenson <p.w.stephenson@xxxxxxxxxxxx>
Web page now at http://homepage.ntlworld.com/p.w.stephenson/



Messages sorted by: Reverse Date, Date, Thread, Author