xref: /openbsd-src/lib/libcurses/base/lib_getch.c (revision 92dd1ec0a89df25171bc5d61a3d95ea1a68cef0b)
1 /*	$OpenBSD: lib_getch.c,v 1.1 1999/01/18 19:09:46 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.41 1998/09/26 23:34:53 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 	int delay;
236 
237 		T(("timed delay in wgetch()"));
238 		if (SP->_cbreak > 1)
239 		    delay = (SP->_cbreak - 1) * 100;
240 		else
241 		    delay = win->_delay;
242 
243 		T(("delay is %d milliseconds", delay));
244 
245 		if (head == -1)	/* fifo is empty */
246 			if (!_nc_timed_wait(3, delay, (int *)0))
247 				returnCode(ERR);
248 		/* else go on to read data available */
249 	}
250 
251 	if (win->_use_keypad) {
252 		/*
253 		 * This is tricky.  We only want to get special-key
254 		 * events one at a time.  But we want to accumulate
255 		 * mouse events until either (a) the mouse logic tells
256 		 * us it's picked up a complete gesture, or (b)
257 		 * there's a detectable time lapse after one.
258 		 *
259 		 * Note: if the mouse code starts failing to compose
260 		 * press/release events into clicks, you should probably
261 		 * increase the wait with mouseinterval().
262 		 */
263 		int runcount = 0;
264 
265 		do {
266 			ch = kgetch(win);
267 			if (ch == KEY_MOUSE)
268 			{
269 				++runcount;
270 				if (SP->_mouse_inline(SP))
271 				    break;
272 			}
273 		} while
274 		    (ch == KEY_MOUSE
275 		     && (_nc_timed_wait(3, SP->_maxclick, (int *)0)
276 			 || !SP->_mouse_parse(runcount)));
277 		if (runcount > 0 && ch != KEY_MOUSE)
278 		{
279 		    /* mouse event sequence ended by keystroke, push it */
280 		    ungetch(ch);
281 		    ch = KEY_MOUSE;
282 		}
283 	} else {
284 		if (head == -1)
285 			fifo_push();
286 		ch = fifo_pull();
287 	}
288 
289 	if (ch == ERR)
290 	{
291 #if USE_SIZECHANGE
292 	    if(SP->_sig_winch)
293 	    {
294 		_nc_update_screensize();
295 		/* resizeterm can push KEY_RESIZE */
296 		if(cooked_key_in_fifo())
297 		{
298 		    ch = fifo_pull();
299 		    T(("wgetch returning (pre-cooked): %#x = %s", ch, _trace_key(ch));)
300 		    returnCode(ch);
301 		}
302 	    }
303 #endif
304 	    T(("wgetch returning ERR"));
305 	    returnCode(ERR);
306 	}
307 
308 	/*
309 	 * Simulate ICRNL mode
310 	 */
311 	if ((ch == '\r') && SP->_nl)
312 		ch = '\n';
313 
314 	/* Strip 8th-bit if so desired.  We do this only for characters that
315 	 * are in the range 128-255, to provide compatibility with terminals
316 	 * that display only 7-bit characters.  Note that 'ch' may be a
317 	 * function key at this point, so we mustn't strip _those_.
318 	 */
319 	if ((ch < KEY_MIN) && (ch & 0x80))
320 		if (!SP->_use_meta)
321 			ch &= 0x7f;
322 
323 	if (SP->_echo && ch < KEY_MIN && !(win->_flags & _ISPAD))
324 		wechochar(win, (chtype)ch);
325 
326 	T(("wgetch returning : %#x = %s", ch, _trace_key(ch)));
327 
328 	returnCode(ch);
329 }
330 
331 
332 /*
333 **      int
334 **      kgetch()
335 **
336 **      Get an input character, but take care of keypad sequences, returning
337 **      an appropriate code when one matches the input.  After each character
338 **      is received, set an alarm call based on ESCDELAY.  If no more of the
339 **      sequence is received by the time the alarm goes off, pass through
340 **      the sequence gotten so far.
341 **
342 **	This function must be called when there is no cooked keys in queue.
343 **	(that is head==-1 || peek==head)
344 **
345 */
346 
347 static int
348 kgetch(WINDOW *win GCC_UNUSED)
349 {
350 struct tries  *ptr;
351 int ch = 0;
352 int timeleft = ESCDELAY;
353 
354 	TR(TRACE_IEVENT, ("kgetch(%p) called", win));
355 
356 	ptr = SP->_keytry;
357 
358 	for(;;)
359 	{
360 		if (!raw_key_in_fifo())
361 		{
362 		    if(fifo_push() == ERR)
363 		    {
364 			peek = head;	/* the keys stay uninterpreted */
365 			return ERR;
366 		    }
367 		}
368 		ch = fifo_peek();
369 		if (ch >= KEY_MIN)
370 		{
371 		    peek = head;
372 		    /* assume the key is the last in fifo */
373 		    t_dec(); /* remove the key */
374 		    return ch;
375 		}
376 
377 		TR(TRACE_IEVENT, ("ch: %s", _trace_key((unsigned char)ch)));
378 		while ((ptr != NULL) && (ptr->ch != (unsigned char)ch))
379 			ptr = ptr->sibling;
380 #ifdef TRACE
381 		if (ptr == NULL)
382 			{TR(TRACE_IEVENT, ("ptr is null"));}
383 		else
384 			TR(TRACE_IEVENT, ("ptr=%p, ch=%d, value=%d",
385 					ptr, ptr->ch, ptr->value));
386 #endif /* TRACE */
387 
388 		if (ptr == NULL)
389 			break;
390 
391 		if (ptr->value != 0) {	/* sequence terminated */
392 			TR(TRACE_IEVENT, ("end of sequence"));
393 			if (peek == tail)
394 			    fifo_clear();
395 			else
396 			    head = peek;
397 			return(ptr->value);
398 		}
399 
400 		ptr = ptr->child;
401 
402 		if (!raw_key_in_fifo())
403 		{
404 			TR(TRACE_IEVENT, ("waiting for rest of sequence"));
405 			if (!_nc_timed_wait(3, timeleft, &timeleft)) {
406 				TR(TRACE_IEVENT, ("ran out of time"));
407 				break;
408 			}
409 		}
410 	}
411 	ch = fifo_pull();
412 	peek = head;
413 	return ch;
414 }
415