vendor/NCURSES: Remove version tag.
[dragonfly.git] / contrib / ncurses / ncurses / base / lib_getch.c
1 /****************************************************************************
2  * Copyright (c) 1998-2001,2002 Free Software Foundation, Inc.              *
3  *                                                                          *
4  * Permission is hereby granted, free of charge, to any person obtaining a  *
5  * copy of this software and associated documentation files (the            *
6  * "Software"), to deal in the Software without restriction, including      *
7  * without limitation the rights to use, copy, modify, merge, publish,      *
8  * distribute, distribute with modifications, sublicense, and/or sell       *
9  * copies of the Software, and to permit persons to whom the Software is    *
10  * furnished to do so, subject to the following conditions:                 *
11  *                                                                          *
12  * The above copyright notice and this permission notice shall be included  *
13  * in all copies or substantial portions of the Software.                   *
14  *                                                                          *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22  *                                                                          *
23  * Except as contained in this notice, the name(s) of the above copyright   *
24  * holders shall not be used in advertising or otherwise to promote the     *
25  * sale, use or other dealings in this Software without prior written       *
26  * authorization.                                                           *
27  ****************************************************************************/
28
29 /****************************************************************************
30  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32  ****************************************************************************/
33
34 /*
35 **      lib_getch.c
36 **
37 **      The routine getch().
38 **
39 */
40
41 #include <curses.priv.h>
42
43 MODULE_ID("$Id: lib_getch.c,v 1.71 2003/05/17 23:49:28 tom Exp $")
44
45 #include <fifo_defs.h>
46
47 NCURSES_EXPORT_VAR(int)
48 ESCDELAY = 1000;                /* max interval betw. chars in funkeys, in millisecs */
49
50 #ifdef NCURSES_WGETCH_EVENTS
51 #define TWAIT_MASK 7
52 #else
53 #define TWAIT_MASK 3
54 #endif
55
56 /*
57  * Check for mouse activity, returning nonzero if we find any.
58  */
59 static int
60 check_mouse_activity(int delay EVENTLIST_2nd(_nc_eventlist * evl))
61 {
62     int rc;
63
64 #if USE_SYSMOUSE
65     if ((SP->_mouse_type == M_SYSMOUSE)
66         && (SP->_sysmouse_head < SP->_sysmouse_tail)) {
67         return 2;
68     }
69 #endif
70     rc = _nc_timed_wait(TWAIT_MASK, delay, (int *) 0 EVENTLIST_2nd(evl));
71 #if USE_SYSMOUSE
72     if ((SP->_mouse_type == M_SYSMOUSE)
73         && (SP->_sysmouse_head < SP->_sysmouse_tail)
74         && (rc == 0)
75         && (errno == EINTR)) {
76         rc |= 2;
77     }
78 #endif
79     return rc;
80 }
81
82 static inline int
83 fifo_peek(void)
84 {
85     int ch = SP->_fifo[peek];
86     TR(TRACE_IEVENT, ("peeking at %d", peek));
87
88     p_inc();
89     return ch;
90 }
91
92 static inline int
93 fifo_pull(void)
94 {
95     int ch;
96     ch = SP->_fifo[head];
97     TR(TRACE_IEVENT, ("pulling %s from %d", _tracechar(ch), head));
98
99     if (peek == head) {
100         h_inc();
101         peek = head;
102     } else
103         h_inc();
104
105 #ifdef TRACE
106     if (_nc_tracing & TRACE_IEVENT)
107         _nc_fifo_dump();
108 #endif
109     return ch;
110 }
111
112 static inline int
113 fifo_push(EVENTLIST_0th(_nc_eventlist * evl))
114 {
115     int n;
116     int ch = 0;
117     int mask = 0;
118
119     (void) mask;
120     if (tail == -1)
121         return ERR;
122
123 #ifdef HIDE_EINTR
124   again:
125     errno = 0;
126 #endif
127
128 #ifdef NCURSES_WGETCH_EVENTS
129     if (evl
130 #if USE_GPM_SUPPORT || USE_EMX_MOUSE || USE_SYSMOUSE
131         || (SP->_mouse_fd >= 0)
132 #endif
133         ) {
134         mask = check_mouse_activity(-1 EVENTLIST_2nd(evl));
135     } else
136         mask = 0;
137
138     if (mask & 4) {
139         T(("fifo_push: ungetch KEY_EVENT"));
140         ungetch(KEY_EVENT);
141         return KEY_EVENT;
142     }
143 #elif USE_GPM_SUPPORT || USE_EMX_MOUSE || USE_SYSMOUSE
144     if (SP->_mouse_fd >= 0) {
145         mask = check_mouse_activity(-1 EVENTLIST_2nd(evl));
146     }
147 #endif
148
149 #if USE_GPM_SUPPORT || USE_EMX_MOUSE
150     if ((SP->_mouse_fd >= 0) && (mask & 2)) {
151         SP->_mouse_event(SP);
152         ch = KEY_MOUSE;
153         n = 1;
154     } else
155 #endif
156 #if USE_SYSMOUSE
157         if ((SP->_mouse_type == M_SYSMOUSE)
158             && (SP->_sysmouse_head < SP->_sysmouse_tail)) {
159         SP->_mouse_event(SP);
160         ch = KEY_MOUSE;
161         n = 1;
162     } else if ((SP->_mouse_type == M_SYSMOUSE)
163                && (mask <= 0) && errno == EINTR) {
164         SP->_mouse_event(SP);
165         ch = KEY_MOUSE;
166         n = 1;
167     } else
168 #endif
169     {                           /* Can block... */
170         unsigned char c2 = 0;
171         n = read(SP->_ifd, &c2, 1);
172         ch = c2;
173     }
174
175 #ifdef HIDE_EINTR
176     /*
177      * Under System V curses with non-restarting signals, getch() returns
178      * with value ERR when a handled signal keeps it from completing.
179      * If signals restart system calls, OTOH, the signal is invisible
180      * except to its handler.
181      *
182      * We don't want this difference to show.  This piece of code
183      * tries to make it look like we always have restarting signals.
184      */
185     if (n <= 0 && errno == EINTR)
186         goto again;
187 #endif
188
189     if ((n == -1) || (n == 0)) {
190         TR(TRACE_IEVENT, ("read(%d,&ch,1)=%d, errno=%d", SP->_ifd, n, errno));
191         ch = ERR;
192     }
193     TR(TRACE_IEVENT, ("read %d characters", n));
194
195     SP->_fifo[tail] = ch;
196     SP->_fifohold = 0;
197     if (head == -1)
198         head = peek = tail;
199     t_inc();
200     TR(TRACE_IEVENT, ("pushed %s at %d", _tracechar(ch), tail));
201 #ifdef TRACE
202     if (_nc_tracing & TRACE_IEVENT)
203         _nc_fifo_dump();
204 #endif
205     return ch;
206 }
207
208 static inline void
209 fifo_clear(void)
210 {
211     memset(SP->_fifo, 0, sizeof(SP->_fifo));
212     head = -1;
213     tail = peek = 0;
214 }
215
216 static int kgetch(EVENTLIST_0th(_nc_eventlist * evl));
217
218 #define wgetch_should_refresh(win) (\
219         (is_wintouched(win) || (win->_flags & _HASMOVED)) \
220         && !(win->_flags & _ISPAD))
221
222 NCURSES_EXPORT(int)
223 _nc_wgetch(WINDOW *win,
224            unsigned long *result,
225            int use_meta
226            EVENTLIST_2nd(_nc_eventlist * evl))
227 {
228     int ch;
229 #ifdef NCURSES_WGETCH_EVENTS
230     long event_delay = -1;
231 #endif
232
233     T((T_CALLED("_nc_wgetch(%p)"), win));
234
235     *result = 0;
236     if (!win)
237         returnCode(ERR);
238
239     if (cooked_key_in_fifo()) {
240         if (wgetch_should_refresh(win))
241             wrefresh(win);
242
243         *result = fifo_pull();
244         returnCode(OK);
245     }
246 #ifdef NCURSES_WGETCH_EVENTS
247     if (evl && (evl->count == 0))
248         evl = NULL;
249     event_delay = _nc_eventlist_timeout(evl);
250 #endif
251
252     /*
253      * Handle cooked mode.  Grab a string from the screen,
254      * stuff its contents in the FIFO queue, and pop off
255      * the first character to return it.
256      */
257     if (head == -1 &&
258         !SP->_notty &&
259         !SP->_raw &&
260         !SP->_cbreak &&
261         !SP->_called_wgetch) {
262         char buf[MAXCOLUMNS], *sp;
263         int rc;
264
265         TR(TRACE_IEVENT, ("filling queue in cooked mode"));
266
267         SP->_called_wgetch = TRUE;
268         rc = wgetnstr(win, buf, MAXCOLUMNS);
269         SP->_called_wgetch = FALSE;
270
271         /* ungetch in reverse order */
272 #ifdef NCURSES_WGETCH_EVENTS
273         if (rc != KEY_EVENT)
274 #endif
275             ungetch('\n');
276         for (sp = buf + strlen(buf); sp > buf; sp--)
277             ungetch(sp[-1]);
278
279 #ifdef NCURSES_WGETCH_EVENTS
280         /* Return it first */
281         if (rc == KEY_EVENT) {
282             *result = rc;
283             returnCode(OK);
284         }
285 #endif
286
287         *result = fifo_pull();
288         returnCode(OK);
289     }
290
291     if (win->_use_keypad != SP->_keypad_on)
292         _nc_keypad(win->_use_keypad);
293
294     if (wgetch_should_refresh(win))
295         wrefresh(win);
296
297     if (!win->_notimeout && (win->_delay >= 0 || SP->_cbreak > 1)) {
298         int delay;
299
300         TR(TRACE_IEVENT, ("timed delay in wgetch()"));
301         if (SP->_cbreak > 1)
302             delay = (SP->_cbreak - 1) * 100;
303         else
304             delay = win->_delay;
305
306 #ifdef NCURSES_WGETCH_EVENTS
307         if (event_delay >= 0 && delay > event_delay)
308             delay = event_delay;
309 #endif
310
311         TR(TRACE_IEVENT, ("delay is %d milliseconds", delay));
312
313         if (head == -1) {       /* fifo is empty */
314             int rc = check_mouse_activity(delay EVENTLIST_2nd(evl));
315
316 #ifdef NCURSES_WGETCH_EVENTS
317             if (rc & 4) {
318                 *result = KEY_EVENT;
319                 returnCode(OK);
320             }
321 #endif
322             if (!rc)
323                 returnCode(ERR);
324         }
325         /* else go on to read data available */
326     }
327
328     if (win->_use_keypad) {
329         /*
330          * This is tricky.  We only want to get special-key
331          * events one at a time.  But we want to accumulate
332          * mouse events until either (a) the mouse logic tells
333          * us it's picked up a complete gesture, or (b)
334          * there's a detectable time lapse after one.
335          *
336          * Note: if the mouse code starts failing to compose
337          * press/release events into clicks, you should probably
338          * increase the wait with mouseinterval().
339          */
340         int runcount = 0;
341         int rc;
342
343         do {
344             ch = kgetch(EVENTLIST_1st(evl));
345             if (ch == KEY_MOUSE) {
346                 ++runcount;
347                 if (SP->_mouse_inline(SP))
348                     break;
349             }
350             if (SP->_maxclick < 0)
351                 break;
352         } while
353             (ch == KEY_MOUSE
354              && (((rc = check_mouse_activity(SP->_maxclick
355                                              EVENTLIST_2nd(evl))) != 0
356                   && !(rc & 4))
357                  || !SP->_mouse_parse(runcount)));
358 #ifdef NCURSES_WGETCH_EVENTS
359         if ((rc & 4) && !ch == KEY_EVENT) {
360             ungetch(ch);
361             ch = KEY_EVENT;
362         }
363 #endif
364         if (runcount > 0 && ch != KEY_MOUSE) {
365 #ifdef NCURSES_WGETCH_EVENTS
366             /* mouse event sequence ended by an event, report event */
367             if (ch == KEY_EVENT) {
368                 ungetch(KEY_MOUSE);     /* FIXME This interrupts a gesture... */
369             } else
370 #endif
371             {
372                 /* mouse event sequence ended by keystroke, store keystroke */
373                 ungetch(ch);
374                 ch = KEY_MOUSE;
375             }
376         }
377     } else {
378         if (head == -1)
379             fifo_push(EVENTLIST_1st(evl));
380         ch = fifo_pull();
381     }
382
383     if (ch == ERR) {
384 #if USE_SIZECHANGE
385         if (SP->_sig_winch) {
386             _nc_update_screensize();
387             /* resizeterm can push KEY_RESIZE */
388             if (cooked_key_in_fifo()) {
389                 *result = fifo_pull();
390                 returnCode(*result >= KEY_MIN ? KEY_CODE_YES : OK);
391             }
392         }
393 #endif
394         returnCode(ERR);
395     }
396
397     /*
398      * If echo() is in effect, display the printable version of the
399      * key on the screen.  Carriage return and backspace are treated
400      * specially by Solaris curses:
401      *
402      * If carriage return is defined as a function key in the
403      * terminfo, e.g., kent, then Solaris may return either ^J (or ^M
404      * if nonl() is set) or KEY_ENTER depending on the echo() mode. 
405      * We echo before translating carriage return based on nonl(),
406      * since the visual result simply moves the cursor to column 0.
407      *
408      * Backspace is a different matter.  Solaris curses does not
409      * translate it to KEY_BACKSPACE if kbs=^H.  This does not depend
410      * on the stty modes, but appears to be a hardcoded special case.
411      * This is a difference from ncurses, which uses the terminfo entry.
412      * However, we provide the same visual result as Solaris, moving the
413      * cursor to the left.
414      */
415     if (SP->_echo && !(win->_flags & _ISPAD)) {
416         chtype backup = (ch == KEY_BACKSPACE) ? '\b' : ch;
417         if (backup < KEY_MIN)
418             wechochar(win, backup);
419     }
420
421     /*
422      * Simulate ICRNL mode
423      */
424     if ((ch == '\r') && SP->_nl)
425         ch = '\n';
426
427     /* Strip 8th-bit if so desired.  We do this only for characters that
428      * are in the range 128-255, to provide compatibility with terminals
429      * that display only 7-bit characters.  Note that 'ch' may be a
430      * function key at this point, so we mustn't strip _those_.
431      */
432     if (!use_meta)
433         if ((ch < KEY_MIN) && (ch & 0x80))
434             ch &= 0x7f;
435
436     T(("wgetch returning : %s", _tracechar(ch)));
437
438     *result = ch;
439     returnCode(ch >= KEY_MIN ? KEY_CODE_YES : OK);
440 }
441
442 #ifdef NCURSES_WGETCH_EVENTS
443 NCURSES_EXPORT(int)
444 wgetch_events(WINDOW *win, _nc_eventlist * evl)
445 {
446     int code;
447     unsigned long value;
448
449     T((T_CALLED("wgetch_events(%p,%p)"), win, evl));
450     code = _nc_wgetch(win,
451                       &value,
452                       SP->_use_meta
453                       EVENTLIST_2nd(evl));
454     if (code != ERR)
455         code = value;
456     returnCode(code);
457 }
458 #endif
459
460 NCURSES_EXPORT(int)
461 wgetch(WINDOW *win)
462 {
463     int code;
464     unsigned long value;
465
466     T((T_CALLED("wgetch(%p)"), win));
467     code = _nc_wgetch(win,
468                       &value,
469                       SP->_use_meta
470                       EVENTLIST_2nd((_nc_eventlist *) 0));
471     if (code != ERR)
472         code = value;
473     returnCode(code);
474 }
475
476 /*
477 **      int
478 **      kgetch()
479 **
480 **      Get an input character, but take care of keypad sequences, returning
481 **      an appropriate code when one matches the input.  After each character
482 **      is received, set an alarm call based on ESCDELAY.  If no more of the
483 **      sequence is received by the time the alarm goes off, pass through
484 **      the sequence gotten so far.
485 **
486 **      This function must be called when there are no cooked keys in queue.
487 **      (that is head==-1 || peek==head)
488 **
489 */
490
491 static int
492 kgetch(EVENTLIST_0th(_nc_eventlist * evl))
493 {
494     struct tries *ptr;
495     int ch = 0;
496     int timeleft = ESCDELAY;
497
498     TR(TRACE_IEVENT, ("kgetch() called"));
499
500     ptr = SP->_keytry;
501
502     for (;;) {
503         if (cooked_key_in_fifo() && SP->_fifo[head] >= KEY_MIN) {
504             break;
505         } else if (!raw_key_in_fifo()) {
506             ch = fifo_push(EVENTLIST_1st(evl));
507             if (ch == ERR) {
508                 peek = head;    /* the keys stay uninterpreted */
509                 return ERR;
510             }
511 #ifdef NCURSES_WGETCH_EVENTS
512             else if (ch == KEY_EVENT) {
513                 peek = head;    /* the keys stay uninterpreted */
514                 return fifo_pull();     /* Remove KEY_EVENT from the queue */
515             }
516 #endif
517         }
518
519         ch = fifo_peek();
520         if (ch >= KEY_MIN) {
521             /* If not first in queue, somebody put this key there on purpose in
522              * emergency.  Consider it higher priority than the unfinished
523              * keysequence we are parsing.
524              */
525             peek = head;
526             /* assume the key is the last in fifo */
527             t_dec();            /* remove the key */
528             return ch;
529         }
530
531         TR(TRACE_IEVENT, ("ch: %s", _tracechar((unsigned char) ch)));
532         while ((ptr != NULL) && (ptr->ch != (unsigned char) ch))
533             ptr = ptr->sibling;
534
535         if (ptr == NULL) {
536             TR(TRACE_IEVENT, ("ptr is null"));
537             break;
538         }
539         TR(TRACE_IEVENT, ("ptr=%p, ch=%d, value=%d",
540                           ptr, ptr->ch, ptr->value));
541
542         if (ptr->value != 0) {  /* sequence terminated */
543             TR(TRACE_IEVENT, ("end of sequence"));
544             if (peek == tail)
545                 fifo_clear();
546             else
547                 head = peek;
548             return (ptr->value);
549         }
550
551         ptr = ptr->child;
552
553         if (!raw_key_in_fifo()) {
554             int rc;
555
556             TR(TRACE_IEVENT, ("waiting for rest of sequence"));
557             rc = check_mouse_activity(timeleft EVENTLIST_2nd(evl));
558 #ifdef NCURSES_WGETCH_EVENTS
559             if (rc & 4) {
560                 TR(TRACE_IEVENT, ("interrupted by a user event"));
561                 /* FIXME Should have preserved remainder timeleft for reusal... */
562                 peek = head;    /* Restart interpreting later */
563                 return KEY_EVENT;
564             }
565 #endif
566             if (!rc) {
567                 TR(TRACE_IEVENT, ("ran out of time"));
568                 break;
569             }
570         }
571     }
572     ch = fifo_pull();
573     peek = head;
574     return ch;
575 }