From a563cd895832c198a06818ebd975a19ff3532a85 Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Wed, 7 Nov 2007 22:35:13 +0000 Subject: 24073 (plus tweak): zcurses mouse handling --- ChangeLog | 6 ++ Doc/Zsh/mod_curses.yo | 66 +++++++++++-- Src/Modules/curses.c | 249 ++++++++++++++++++++++++++++++++++++++++++++++---- 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 + + * 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 * 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; } @@ -1057,6 +1218,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) { @@ -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; } -- cgit 1.4.1