summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog6
-rw-r--r--Doc/Zsh/mod_curses.yo66
-rw-r--r--Src/Modules/curses.c249
3 files changed, 292 insertions, 29 deletions
diff --git a/ChangeLog b/ChangeLog
index b1761d64f..db32292bd 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2007-11-07  Peter Stephenson  <p.w.stephenson@ntlworld.com>
+
+	* 24073 (plus tweak to allow "zcurses mouse" with no additional
+	arguments): Doc/Zsh/mod_curses.yo, Src/Modules/curses.c:
+	add zcurses mouse handling.
+
 2007-11-06  Peter Stephenson  <p.w.stephenson@ntlworld.com>
 
 	* 24070: Src/utils.c, Test/A03quoting.ztst,
diff --git a/Doc/Zsh/mod_curses.yo b/Doc/Zsh/mod_curses.yo
index 993584ea1..060ec8f29 100644
--- a/Doc/Zsh/mod_curses.yo
+++ b/Doc/Zsh/mod_curses.yo
@@ -23,8 +23,9 @@ xitem(tt(zcurses) tt(string) var(targetwin) var(string) )
 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 @@ to allow the window to be scrolled.
 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
diff --git a/Src/Modules/curses.c b/Src/Modules/curses.c
index 08fe0099b..64ba356a1 100644
--- a/Src/Modules/curses.c
+++ b/Src/Modules/curses.c
@@ -92,6 +92,7 @@ static struct ttyinfo saved_tty_state;
 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 HashTable zcurses_colorpairs = NULL;
 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 @@ static const struct zcurses_namenumberpair zcurses_colors[] = {
     {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 @@ zccmd_input(const char *nam, char **args)
     ZCWin w;
     char *var;
     int keypadnum = -1;
+    int nargs = arrlen(args);
 #ifdef HAVE_WGET_WCH
     int ret;
     wint_t wi;
@@ -936,12 +1007,37 @@ zccmd_input(const char *nam, char **args)
 
     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 @@ zccmd_input(const char *nam, char **args)
 	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 @@ zccmd_timeout(const char *nam, char **args)
 
 
 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 @@ bin_zcurses(char *nam, char **args, Options ops, UNUSED(int func))
 	{"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, 0, -1},
 	{"touch", zccmd_touch, 1, -1},
 	{NULL, (zccmd_t)0, 0, 0}
     };
@@ -1165,7 +1376,7 @@ bin_zcurses(char *nam, char **args, Options ops, UNUSED(int func))
 	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;
     }