xref: /dflybsd-src/contrib/ncurses/ncurses/base/lib_getch.c (revision c6cf4f8f1ebc9e3fe2a8c566f08adfc86122c7bf)
1 /****************************************************************************
2  * Copyright (c) 1998,1999,2000 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.50 2000/10/09 23:53:57 Ilya.Zakharevich Exp $")
44 
45 #include <fifo_defs.h>
46 
47 int ESCDELAY = 1000;		/* max interval betw. chars in funkeys, in millisecs */
48 
49 static inline int
50 fifo_peek(void)
51 {
52     int ch = SP->_fifo[peek];
53     TR(TRACE_IEVENT, ("peeking at %d", peek));
54 
55     p_inc();
56     return ch;
57 }
58 
59 static inline int
60 fifo_pull(void)
61 {
62     int ch;
63     ch = SP->_fifo[head];
64     TR(TRACE_IEVENT, ("pulling %d from %d", ch, head));
65 
66     if (peek == head) {
67 	h_inc();
68 	peek = head;
69     } else
70 	h_inc();
71 
72 #ifdef TRACE
73     if (_nc_tracing & TRACE_IEVENT)
74 	_nc_fifo_dump();
75 #endif
76     return ch;
77 }
78 
79 static inline int
80 fifo_push(void)
81 {
82     int n;
83     unsigned int ch;
84 
85     if (tail == -1)
86 	return ERR;
87 
88 #ifdef HIDE_EINTR
89   again:
90     errno = 0;
91 #endif
92 
93 #if USE_GPM_SUPPORT || defined(USE_EMX_MOUSE)
94     if ((SP->_mouse_fd >= 0)
95 	&& (_nc_timed_wait(3, -1, (int *) 0) & 2)) {
96 	SP->_mouse_event(SP);
97 	ch = KEY_MOUSE;
98 	n = 1;
99     } else
100 #endif
101     {
102 	unsigned char c2 = 0;
103 	n = read(SP->_ifd, &c2, 1);
104 	ch = c2 & 0xff;
105     }
106 
107 #ifdef HIDE_EINTR
108     /*
109      * Under System V curses with non-restarting signals, getch() returns
110      * with value ERR when a handled signal keeps it from completing.
111      * If signals restart system calls, OTOH, the signal is invisible
112      * except to its handler.
113      *
114      * We don't want this difference to show.  This piece of code
115      * tries to make it look like we always have restarting signals.
116      */
117     if (n <= 0 && errno == EINTR)
118 	goto again;
119 #endif
120 
121     if ((n == -1) || (n == 0)) {
122 	TR(TRACE_IEVENT, ("read(%d,&ch,1)=%d, errno=%d", SP->_ifd, n, errno));
123 	ch = ERR;
124     }
125     TR(TRACE_IEVENT, ("read %d characters", n));
126 
127     SP->_fifo[tail] = ch;
128     SP->_fifohold = 0;
129     if (head == -1)
130 	head = peek = tail;
131     t_inc();
132     TR(TRACE_IEVENT, ("pushed %#x at %d", ch, tail));
133 #ifdef TRACE
134     if (_nc_tracing & TRACE_IEVENT)
135 	_nc_fifo_dump();
136 #endif
137     return ch;
138 }
139 
140 static inline void
141 fifo_clear(void)
142 {
143     int i;
144     for (i = 0; i < FIFO_SIZE; i++)
145 	SP->_fifo[i] = 0;
146     head = -1;
147     tail = peek = 0;
148 }
149 
150 static int kgetch(WINDOW *);
151 
152 #define wgetch_should_refresh(win) (\
153 	(is_wintouched(win) || (win->_flags & _HASMOVED)) \
154 	&& !(win->_flags & _ISPAD))
155 
156 int
157 wgetch(WINDOW *win)
158 {
159     int ch;
160 
161     T((T_CALLED("wgetch(%p)"), win));
162 
163     if (!win)
164 	returnCode(ERR);
165 
166     if (cooked_key_in_fifo()) {
167 	if (wgetch_should_refresh(win))
168 	    wrefresh(win);
169 
170 	ch = fifo_pull();
171 	T(("wgetch returning (pre-cooked): %#x = %s", ch, _trace_key(ch)));
172 	returnCode(ch);
173     }
174 
175     /*
176      * Handle cooked mode.  Grab a string from the screen,
177      * stuff its contents in the FIFO queue, and pop off
178      * the first character to return it.
179      */
180     if (head == -1 && !SP->_raw && !SP->_cbreak) {
181 	char buf[MAXCOLUMNS], *sp;
182 
183 	TR(TRACE_IEVENT, ("filling queue in cooked mode"));
184 
185 	wgetnstr(win, buf, MAXCOLUMNS);
186 
187 	/* ungetch in reverse order */
188 	ungetch('\n');
189 	for (sp = buf + strlen(buf); sp > buf; sp--)
190 	    ungetch(sp[-1]);
191 
192 	returnCode(fifo_pull());
193     }
194 
195     if (wgetch_should_refresh(win))
196 	wrefresh(win);
197 
198     if (!win->_notimeout && (win->_delay >= 0 || SP->_cbreak > 1)) {
199 	int delay;
200 
201 	TR(TRACE_IEVENT, ("timed delay in wgetch()"));
202 	if (SP->_cbreak > 1)
203 	    delay = (SP->_cbreak - 1) * 100;
204 	else
205 	    delay = win->_delay;
206 
207 	TR(TRACE_IEVENT, ("delay is %d milliseconds", delay));
208 
209 	if (head == -1)		/* fifo is empty */
210 	    if (!_nc_timed_wait(3, delay, (int *) 0))
211 		returnCode(ERR);
212 	/* else go on to read data available */
213     }
214 
215     if (win->_use_keypad) {
216 	/*
217 	 * This is tricky.  We only want to get special-key
218 	 * events one at a time.  But we want to accumulate
219 	 * mouse events until either (a) the mouse logic tells
220 	 * us it's picked up a complete gesture, or (b)
221 	 * there's a detectable time lapse after one.
222 	 *
223 	 * Note: if the mouse code starts failing to compose
224 	 * press/release events into clicks, you should probably
225 	 * increase the wait with mouseinterval().
226 	 */
227 	int runcount = 0;
228 
229 	do {
230 	    ch = kgetch(win);
231 	    if (ch == KEY_MOUSE) {
232 		++runcount;
233 		if (SP->_mouse_inline(SP))
234 		    break;
235 	    }
236 	} while
237 	    (ch == KEY_MOUSE
238 	    && (_nc_timed_wait(3, SP->_maxclick, (int *) 0)
239 		|| !SP->_mouse_parse(runcount)));
240 	if (runcount > 0 && ch != KEY_MOUSE) {
241 	    /* mouse event sequence ended by keystroke, push it */
242 	    ungetch(ch);
243 	    ch = KEY_MOUSE;
244 	}
245     } else {
246 	if (head == -1)
247 	    fifo_push();
248 	ch = fifo_pull();
249     }
250 
251     if (ch == ERR) {
252 #if USE_SIZECHANGE
253 	if (SP->_sig_winch) {
254 	    _nc_update_screensize();
255 	    /* resizeterm can push KEY_RESIZE */
256 	    if (cooked_key_in_fifo()) {
257 		ch = fifo_pull();
258 		T(("wgetch returning (pre-cooked): %#x = %s", ch, _trace_key(ch)));
259 		returnCode(ch);
260 	    }
261 	}
262 #endif
263 	T(("wgetch returning ERR"));
264 	returnCode(ERR);
265     }
266 
267     /*
268      * If echo() is in effect, display the printable version of the
269      * key on the screen.  Carriage return and backspace are treated
270      * specially by Solaris curses:
271      *
272      * If carriage return is defined as a function key in the
273      * terminfo, e.g., kent, then Solaris may return either ^J (or ^M
274      * if nonl() is set) or KEY_ENTER depending on the echo() mode.
275      * We echo before translating carriage return based on nonl(),
276      * since the visual result simply moves the cursor to column 0.
277      *
278      * Backspace is a different matter.  Solaris curses does not
279      * translate it to KEY_BACKSPACE if kbs=^H.  This does not depend
280      * on the stty modes, but appears to be a hardcoded special case.
281      * This is a difference from ncurses, which uses the terminfo entry.
282      * However, we provide the same visual result as Solaris, moving the
283      * cursor to the left.
284      */
285     if (SP->_echo && !(win->_flags & _ISPAD)) {
286 	chtype backup = (ch == KEY_BACKSPACE) ? '\b' : ch;
287 	if (backup < KEY_MIN)
288 	    wechochar(win, backup);
289     }
290 
291     /*
292      * Simulate ICRNL mode
293      */
294     if ((ch == '\r') && SP->_nl)
295 	ch = '\n';
296 
297     /* Strip 8th-bit if so desired.  We do this only for characters that
298      * are in the range 128-255, to provide compatibility with terminals
299      * that display only 7-bit characters.  Note that 'ch' may be a
300      * function key at this point, so we mustn't strip _those_.
301      */
302     if ((ch < KEY_MIN) && (ch & 0x80))
303 	if (!SP->_use_meta)
304 	    ch &= 0x7f;
305 
306     T(("wgetch returning : %#x = %s", ch, _trace_key(ch)));
307 
308     returnCode(ch);
309 }
310 
311 /*
312 **      int
313 **      kgetch()
314 **
315 **      Get an input character, but take care of keypad sequences, returning
316 **      an appropriate code when one matches the input.  After each character
317 **      is received, set an alarm call based on ESCDELAY.  If no more of the
318 **      sequence is received by the time the alarm goes off, pass through
319 **      the sequence gotten so far.
320 **
321 **	This function must be called when there is no cooked keys in queue.
322 **	(that is head==-1 || peek==head)
323 **
324 */
325 
326 static int
327 kgetch(WINDOW *win GCC_UNUSED)
328 {
329     struct tries *ptr;
330     int ch = 0;
331     int timeleft = ESCDELAY;
332 
333     TR(TRACE_IEVENT, ("kgetch(%p) called", win));
334 
335     ptr = SP->_keytry;
336 
337     for (;;) {
338 	if (!raw_key_in_fifo()) {
339 	    if (fifo_push() == ERR) {
340 		peek = head;	/* the keys stay uninterpreted */
341 		return ERR;
342 	    }
343 	}
344 	ch = fifo_peek();
345 	if (ch >= KEY_MIN) {
346 	    peek = head;
347 	    /* assume the key is the last in fifo */
348 	    t_dec();		/* remove the key */
349 	    return ch;
350 	}
351 
352 	TR(TRACE_IEVENT, ("ch: %s", _trace_key((unsigned char) ch)));
353 	while ((ptr != NULL) && (ptr->ch != (unsigned char) ch))
354 	    ptr = ptr->sibling;
355 #ifdef TRACE
356 	if (ptr == NULL) {
357 	    TR(TRACE_IEVENT, ("ptr is null"));
358 	} else
359 	    TR(TRACE_IEVENT, ("ptr=%p, ch=%d, value=%d",
360 		    ptr, ptr->ch, ptr->value));
361 #endif /* TRACE */
362 
363 	if (ptr == NULL)
364 	    break;
365 
366 	if (ptr->value != 0) {	/* sequence terminated */
367 	    TR(TRACE_IEVENT, ("end of sequence"));
368 	    if (peek == tail)
369 		fifo_clear();
370 	    else
371 		head = peek;
372 	    return (ptr->value);
373 	}
374 
375 	ptr = ptr->child;
376 
377 	if (!raw_key_in_fifo()) {
378 	    TR(TRACE_IEVENT, ("waiting for rest of sequence"));
379 	    if (!_nc_timed_wait(3, timeleft, &timeleft)) {
380 		TR(TRACE_IEVENT, ("ran out of time"));
381 		break;
382 	    }
383 	}
384     }
385     ch = fifo_pull();
386     peek = head;
387     return ch;
388 }
389