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