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