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