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