From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 13734 invoked from network); 13 May 2002 15:37:00 -0000 Received: from sunsite.dk (130.225.247.90) by ns1.primenet.com.au with SMTP; 13 May 2002 15:37:00 -0000 Received: (qmail 10503 invoked by alias); 13 May 2002 15:34:37 -0000 Mailing-List: contact zsh-workers-help@sunsite.dk; run by ezmlm Precedence: bulk X-No-Archive: yes X-Seq: 17141 Received: (qmail 10207 invoked from network); 13 May 2002 15:33:31 -0000 To: zsh-workers@sunsite.dk (Zsh hackers list) Subject: PATCH: more selection Date: Mon, 13 May 2002 16:33:01 +0100 Message-ID: <21560.1021303981@csr.com> From: Peter Stephenson This patch is more advanced warning of future pain than anything else, in case anyone wants to scream at the very idea. The following patch adds an fd-watching facility to zle. You tell zle (via zle -F) to select on an fd and give it a function handler. When data is available for reading on that handle (only reading, at the moment), the handler will be called, and zle will sail on without returning. This allows you to handle input from other sources while the shell is inactive waiting for terminal input. It's all handled synchronously within zle, so it should be free of the hazards of asynchronous input. Unfortunately, this has to be part of the core zle, since it needs to be tied into getkey(), so it's not an optional feature. I've been getting good results with this so far (I don't even seem to have screwed up key timeouts, yet!) but I want to do more work, even if nobody else wants this, or would prefer it to be a compile-time option. In particular, it might be better to make the list of fds and functions a list rather than a pair of arrays. Also, once again it's perfectly possible to use poll() instead of select(), but I haven't bothered adding it. The could easily be more nasties lurking within the fact that the call to the handler function is rather deep within zle. Index: Doc/Zsh/zle.yo =================================================================== RCS file: /cvsroot/zsh/zsh/Doc/Zsh/zle.yo,v retrieving revision 1.17 diff -u -r1.17 zle.yo --- Doc/Zsh/zle.yo 5 Mar 2002 16:33:21 -0000 1.17 +++ Doc/Zsh/zle.yo 13 May 2002 15:18:49 -0000 @@ -314,6 +314,7 @@ xitem(tt(zle) tt(-M) var(string)) xitem(tt(zle) tt(-U) var(string)) xitem(tt(zle) tt(-K) var(keymap)) +xitem(tt(zle) tt(-F) [ tt(-L) ] [ var(fd) [ var(handler) ] ]) xitem(tt(zle) tt(-I)) xitem(tt(zle) var(widget) tt([ -n) var(num) tt(]) tt([ -N ]) var(args) ...) item(tt(zle))( @@ -410,6 +411,65 @@ This keymap selection affects the interpretation of following keystrokes within this invocation of ZLE. Any following invocation (e.g., the next command line) will start as usual with the `tt(main)' keymap selected. +) +item(tt(-F) [ tt(-L) ] [ var(fd) [ var(handler) ] ])( +Only available if your system support the `select' system call; most +modern systems do. + +Installs var(handler) (the name of a shell function) to handle input from +file descriptor var(fd). When zle is attempting to read data, it will +examine both the terminal and the list of handled var(fd)'s. If data +becomes available on a handled var(fd), zle will call var(handler) with +the fd which is ready for reading as the only argument. If the handler +produces output to the terminal, it should call `tt(zle -I)' before doing +so (see below). The handler should not attempt to read from the terminal. +Note that zle makes no attempt to check whether this fd is actually +readable when installing the handler. The user must make their own +arrangments for handling the file descriptor when zle is not active. + +Any number of handlers for any number of readable file descriptors may be +installed. Installing a handler for an var(fd) which is already handled +causes the existing handler to be replaced. + +If no var(handler) is given, but an var(fd) is present, any handler for +that var(fd) is removed. If there is none, an error message is printed +and status 1 is returned. + +If no arguments are given, or the tt(-L) option is supplied, a list of +handlers is printed in a form which can be stored for later execution. + +An var(fd) (but not a var(handler)) may optionally be given with the tt(-L) +option; in this case, the function will list the handler if any, else +silently return status 1. + +Note that this feature should be used with care. Activity on one of the +var(fd)'s which is not properly handled can cause the terminal to become +unusable. + +Here is a simple example of using this feature. A connection to a remote +TCP port is created using the ztcp command; see +ifzman(the description of the tt(zsh/net/tcp) module in zmanref(zshmodules))\ +ifnzman(noderef(The zsh/net/tcp Module)). Then a handler is installed +which simply prints out any data which arrives on this connection. Note +that `select' will indicate that the file descriptor needs handling +if the remote side has closed the connection; we handle that by testing +for a failed read. +example(if ztcp pwspc 2811; then + tcpfd=$REPLY + handler() { + zle -I + local line + if ! read line <&$1; then + # select marks this fd if we reach EOF, + # so handle this specially. + print "[Read on fd $1 failed, removing.]" >&2 + zle -F $1 + return 1 + fi + print -r - $line + } + zle -F $tcpfd handler +fi) ) item(tt(-I))( Unusually, this option is only useful em(outside) ordinary widget functions. Index: Src/Zle/zle_main.c =================================================================== RCS file: /cvsroot/zsh/zsh/Src/Zle/zle_main.c,v retrieving revision 1.22 diff -u -r1.22 zle_main.c --- Src/Zle/zle_main.c 1 Mar 2002 10:42:02 -0000 1.22 +++ Src/Zle/zle_main.c 13 May 2002 15:18:49 -0000 @@ -141,6 +141,17 @@ static int delayzsetterm; #endif +/* + * File descriptors we are watching as well as the terminal fd. + * These are all for reading; we don't watch for writes or exceptions. + */ +/**/ +int nwatch; /* Number of fd's we are watching */ +/**/ +int *watch_fds; /* The list of fds, not terminated! */ +/**/ +char **watch_funcs; /* The corresponding functions to call, normal array */ + /* set up terminal */ /**/ @@ -324,86 +335,169 @@ # define read breakread #endif -/**/ -mod_export int -getkey(int keytmout) +static int +raw_getkey(int keytmout, char *cptr) { - char cc; - unsigned int ret; long exp100ths; - int die = 0, r, icnt = 0; - int old_errno = errno, obreaks = breaks; - + int ret; #ifdef HAVE_SELECT fd_set foofd; - #else # ifdef HAS_TIO struct ttyinfo ti; - # endif #endif - if (kungetct) - ret = STOUC(kungetbuf[--kungetct]); - else { -#ifdef FIONREAD - if (delayzsetterm) { - int val; - ioctl(SHTTY, FIONREAD, (char *)&val); - if (!val) - zsetterm(); - } -#endif - if (keytmout + /* + * Handle timeouts and watched fd's. We only do one at once; + * key timeouts take precedence. This saves tricky timing + * problems with the key timeout. + */ + if ((nwatch || keytmout) #ifdef FIONREAD - && ! delayzsetterm + && ! delayzsetterm #endif - ) { - if (keytimeout > 500) - exp100ths = 500; - else if (keytimeout > 0) - exp100ths = keytimeout; - else - exp100ths = 0; + ) { + if (!keytmout || keytimeout <= 0) + exp100ths = 0; + else if (keytimeout > 500) + exp100ths = 500; + else + exp100ths = keytimeout; #ifdef HAVE_SELECT + if (!keytmout || exp100ths) { + struct timeval *tvptr = NULL; + struct timeval expire_tv; + int i, fdmax = SHTTY, errtry = 0; if (exp100ths) { - struct timeval expire_tv; - expire_tv.tv_sec = exp100ths / 100; expire_tv.tv_usec = (exp100ths % 100) * 10000L; + tvptr = &expire_tv; + } + do { + int selret; FD_ZERO(&foofd); FD_SET(SHTTY, &foofd); - if (select(SHTTY+1, (SELECT_ARG_2_T) & foofd, - NULL, NULL, &expire_tv) <= 0) - return EOF; - } + if (!keytmout && !errtry) { + for (i = 0; i < nwatch; i++) { + int fd = watch_fds[i]; + FD_SET(fd, &foofd); + if (fd > fdmax) + fdmax = fd; + } + } + selret = select(fdmax+1, (SELECT_ARG_2_T) & foofd, + NULL, NULL, tvptr); + /* + * Try to avoid errors on our special fd's from + * messing up reads from the terminal. Try first + * with all fds, then try unsetting the special ones. + */ + if (selret < 0 && !keytmout && !errtry) { + errtry = 1; + continue; + } + if (selret == 0) { + /* Special value -2 signals nothing ready */ + return -2; + } else if (selret < 0) + return selret; + if (!keytmout && nwatch) { + /* + * Copy the details of the watch fds in case the + * user decides to delete one from inside the + * handler function. + */ + int lnwatch = nwatch; + int *lwatch_fds = zalloc(lnwatch*sizeof(int)); + char **lwatch_funcs = zarrdup(watch_funcs); + memcpy(lwatch_fds, watch_fds, lnwatch*sizeof(int)); + for (i = 0; i < lnwatch; i++) { + if (FD_ISSET(lwatch_fds[i], &foofd)) { + /* Handle the fd. */ + LinkList funcargs = znewlinklist(); + zaddlinknode(funcargs, ztrdup(lwatch_funcs[i])); + { + char buf[BDIGBUFSIZE]; + convbase(buf, lwatch_fds[i], 10); + zaddlinknode(funcargs, ztrdup(buf)); + } + + callhookfunc(lwatch_funcs[i], funcargs); + if (errflag) { + /* No sensible way of handling errors here */ + errflag = 0; + /* + * Paranoia: don't run the hooks again this + * time. + */ + errtry = 1; + } + freelinklist(funcargs, freestr); + } + } + /* Function may have invalidated the display. */ + if (resetneeded) + zrefresh(); + zfree(lwatch_fds, lnwatch*sizeof(int)); + freearray(lwatch_funcs); + } + } while (!FD_ISSET(SHTTY, &foofd)); + } #else # ifdef HAS_TIO - ti = shttyinfo; - ti.tio.c_lflag &= ~ICANON; - ti.tio.c_cc[VMIN] = 0; - ti.tio.c_cc[VTIME] = exp100ths / 10; + ti = shttyinfo; + ti.tio.c_lflag &= ~ICANON; + ti.tio.c_cc[VMIN] = 0; + ti.tio.c_cc[VTIME] = exp100ths / 10; # ifdef HAVE_TERMIOS_H - tcsetattr(SHTTY, TCSANOW, &ti.tio); + tcsetattr(SHTTY, TCSANOW, &ti.tio); # else - ioctl(SHTTY, TCSETA, &ti.tio); + ioctl(SHTTY, TCSETA, &ti.tio); # endif - r = read(SHTTY, &cc, 1); + ret = read(SHTTY, cptr, 1); # ifdef HAVE_TERMIOS_H - tcsetattr(SHTTY, TCSANOW, &shttyinfo.tio); + tcsetattr(SHTTY, TCSANOW, &shttyinfo.tio); # else - ioctl(SHTTY, TCSETA, &shttyinfo.tio); + ioctl(SHTTY, TCSETA, &shttyinfo.tio); # endif - return (r <= 0) ? EOF : cc; + return (ret <= 0) ? ret : *cptr; # endif #endif + } + + ret = read(SHTTY, cptr, 1); + + return ret; +} + +/**/ +mod_export int +getkey(int keytmout) +{ + char cc; + unsigned int ret; + int die = 0, r, icnt = 0; + int old_errno = errno, obreaks = breaks; + + if (kungetct) + ret = STOUC(kungetbuf[--kungetct]); + else { +#ifdef FIONREAD + if (delayzsetterm) { + int val; + ioctl(SHTTY, FIONREAD, (char *)&val); + if (!val) + zsetterm(); } +#endif for (;;) { int q = queue_signal_level(); dont_queue_signals(); - r = read(SHTTY, &cc, 1); + r = raw_getkey(keytmout, &cc); restore_queue_signals(q); + if (r == -2) /* timeout */ + return EOF; if (r == 1) break; if (r == 0) { @@ -1101,7 +1195,7 @@ static struct builtin bintab[] = { BUILTIN("bindkey", 0, bin_bindkey, 0, -1, 0, "evaMldDANmrsLRp", NULL), BUILTIN("vared", 0, bin_vared, 1, 7, 0, NULL, NULL), - BUILTIN("zle", 0, bin_zle, 0, -1, 0, "lDANCLmMgGcRaUKI", NULL), + BUILTIN("zle", 0, bin_zle, 0, -1, 0, "aAcCDFgGIKlLmMNRU", NULL), }; /* The order of the entries in this table has to match the *HOOK Index: Src/Zle/zle_thingy.c =================================================================== RCS file: /cvsroot/zsh/zsh/Src/Zle/zle_thingy.c,v retrieving revision 1.4 diff -u -r1.4 zle_thingy.c --- Src/Zle/zle_thingy.c 3 Sep 2001 01:39:20 -0000 1.4 +++ Src/Zle/zle_thingy.c 13 May 2002 15:18:49 -0000 @@ -341,6 +341,7 @@ { 'U', bin_zle_unget, 1, 1 }, { 'K', bin_zle_keymap, 1, 1 }, { 'I', bin_zle_invalidate, 0, 0 }, + { 'F', bin_zle_fd, 0, 2 }, { 0, bin_zle_call, 0, -1 }, }; struct opn const *op, *opp; @@ -676,6 +677,108 @@ return 0; } else return 1; +} + +/**/ +static int +bin_zle_fd(char *name, char **args, char *ops, char func) +{ + int fd = 0, i, found = 0; + char *endptr; + + if (*args) { + fd = (int)zstrtol(*args, &endptr, 10); + + if (*endptr || fd < 0) { + zwarnnam(name, "Bad file descriptor number for -F: %s", *args, 0); + return 1; + } + } + + if (ops['L'] || !*args) { + /* Listing handlers. */ + if (args[1]) { + zwarnnam(name, "too many arguments for -FL", NULL, 0); + return 1; + } + for (i = 0; i < nwatch; i++) { + if (*args && watch_fds[i] != fd) + continue; + found = 1; + printf("%s -F %d %s\n", name, watch_fds[i], watch_funcs[i]); + } + /* only return status 1 if fd given and not found */ + return *args && !found; + } + + if (args[1]) { + /* Adding or replacing a handler */ + char *funcnam = ztrdup(args[1]); + if (nwatch) { + for (i = 0; i < nwatch; i++) { + if (watch_fds[i] == fd) { + zsfree(watch_funcs[i]); + watch_funcs[i] = funcnam; + found = 1; + break; + } + } + } + if (!found) { + /* zrealloc handles NULL pointers, so OK for first time through */ + int newnwatch = nwatch+1; + watch_fds = (int *)zrealloc(watch_fds, + newnwatch * sizeof(int)); + watch_funcs = (char **)zrealloc(watch_funcs, + (newnwatch+1) * sizeof(char *)); + watch_fds[nwatch] = fd; + watch_funcs[nwatch] = funcnam; + watch_funcs[newnwatch] = NULL; + nwatch = newnwatch; + } + } else { + /* Deleting a handler */ + for (i = 0; i < nwatch; i++) { + if (watch_fds[i] == fd) { + int newnwatch = nwatch-1; + int *new_fds; + char **new_funcs; + + zsfree(watch_funcs[i]); + if (newnwatch) { + new_fds = zalloc(newnwatch*sizeof(int)); + new_funcs = zalloc((newnwatch+1)*sizeof(char*)); + if (i) { + memcpy(new_fds, watch_fds, i*sizeof(int)); + memcpy(new_funcs, new_fds, i*sizeof(char *)); + } + if (i < newnwatch) { + memcpy(new_fds+i, watch_fds+i+1, + (newnwatch-i)*sizeof(int)); + memcpy(new_funcs+i, watch_funcs+i+1, + (newnwatch-i)*sizeof(char *)); + } + new_funcs[newnwatch] = NULL; + } else { + new_fds = NULL; + new_funcs = NULL; + } + zfree(watch_fds, nwatch*sizeof(int)); + zfree(watch_funcs, (nwatch+1)*sizeof(char *)); + watch_fds = new_fds; + watch_funcs = new_funcs; + nwatch = newnwatch; + found = 1; + break; + } + } + if (!found) { + zwarnnam(name, "No handler installed for fd %d", NULL, fd); + return 1; + } + } + + return 0; } /*******************/ -- Peter Stephenson Software Engineer CSR Ltd., Science Park, Milton Road, Cambridge, CB4 0WH, UK Tel: +44 (0)1223 392070 ********************************************************************** The information transmitted is intended only for the person or entity to which it is addressed and may contain confidential and/or privileged material. Any review, retransmission, dissemination or other use of, or taking of any action in reliance upon, this information by persons or entities other than the intended recipient is prohibited. If you received this in error, please contact the sender and delete the material from any computer. **********************************************************************