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