xref: /illumos-gate/usr/src/lib/libcurses/screen/tgetch.c (revision 20a7641f9918de8574b8b3b47dbe35c4bfc78df1)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*	Copyright (c) 1988 AT&T	*/
27 /*	  All Rights Reserved	*/
28 
29 /*
30  * University Copyright- Copyright (c) 1982, 1986, 1988
31  * The Regents of the University of California
32  * All Rights Reserved
33  *
34  * University Acknowledgment- Portions of this document are derived from
35  * software developed by the University of California, Berkeley, and its
36  * contributors.
37  */
38 
39 /*LINTLIBRARY*/
40 
41 #include	"curses_inc.h"
42 #include	<signal.h>
43 #include	<unistd.h>
44 #ifndef	FIONREAD
45 #include	<fcntl.h>
46 #endif /* FIONREAD */
47 #ifdef	DEBUG
48 #include	<ctype.h>
49 #endif	/* DEBUG */
50 
51 /*
52  * Read a key typed from the terminal
53  *
54  * interpret:	= 0 for single-char key only
55  * 		= 1 for matching function key and macro patterns.
56  * 		= 2 same as 1 but no time-out for funckey matching.
57  */
58 
59 static	int _getkey(int, chtype *);
60 static	int _fpk(void);
61 static	int _pk(void);
62 
63 chtype
64 tgetch(int interpret)
65 {
66 	int		i = 0, j, collapse = 1;
67 #define	WAIT3		333
68 	chtype		inp;
69 	chtype		*inputQ = cur_term->_input_queue;
70 	char		*chars_onQ = &(cur_term->_chars_on_queue);
71 
72 #ifdef	SYSV
73 	/*
74 	 * Register the fact that getch is being used so
75 	 * that typeahead checking can be done.
76 	 * This code should GO AWAY when a poll() or FIONREAD can
77 	 * be done on the file descriptor as then the check
78 	 * will be non-destructive.
79 	 */
80 	cur_term->fl_typeahdok = TRUE;
81 #endif	/* SYSV */
82 
83 	/* ask for input */
84 	if (cur_term->_ungotten > 0) {
85 		cur_term->_ungotten--;
86 		/* decode an ungetch()'d character */
87 		inp = -inputQ[0];
88 	} else {
89 		/* Only read a character if there is no typeahead/peekahead. */
90 		if (*chars_onQ == 0) {
91 			/* (*chars_onQ)++;  MR */
92 #ifdef	FIONREAD
93 			inp = _readchar();
94 #else	/* FIONREAD */
95 			inp = (chtype) _pk();
96 			if ((int)inp == ERR) {
97 		/*
98 		 * interpret is set to 0 so that down below we don't
99 		 * drop into getkey since we already know there can't be
100 		 * a key that starts with -1.  Also, we don't want to
101 		 * access funckeystarter[-1].
102 		 */
103 				interpret = FALSE;
104 			}
105 #endif	/* FIONREAD */
106 			(*chars_onQ)++;
107 		} else
108 			inp = inputQ[0];
109 
110 #ifdef	DEBUG
111 		if (outf)
112 			fprintf(outf, "TGETCH read '%s'\n", unctrl(inp));
113 #endif	/* DEBUG */
114 
115 		/* Check for arrow and function keys */
116 		if (interpret && cur_term->funckeystarter[inp])
117 			collapse = _getkey(interpret - 1, &inp);
118 	}
119 
120 	/* Collapse the input queue to remove the escape */
121 	/* sequence from the stack. */
122 
123 	j = *chars_onQ;
124 	(*chars_onQ) -= collapse;
125 	while (collapse < j)
126 		inputQ[i++] = inputQ[collapse++];
127 	return (inp);
128 }
129 
130 #ifdef	FIONREAD
131 static	int
132 _readchar()
133 {
134 	int		i;
135 	unsigned	char	c;
136 
137 	if (cur_term->_delay == 0) {
138 		int	arg;
139 
140 		(void) ioctl(cur_term->_inputfd, FIONREAD, &arg);
141 #ifdef	DEBUG
142 		if (outf)
143 			fprintf(outf, "FIONREAD returns %d\n", arg);
144 #endif	/* DEBUG */
145 		if (arg < 1)
146 			return (-1);
147 	} else
148 		if (cur_term->_delay > 0) {
149 			char	c;
150 			int	infd;
151 
152 			infd = 1 << cur_term->_inputfd;
153 			t.tv_sec = cur_term->_delay / 1000;
154 			t.tv_usec = (cur_term->_delay % 1000) * 1000;
155 			i = select(20, &infd, (int *)NULL, (int *)NULL, &t);
156 			if (i < 0)
157 				return (ERR);
158 			i = read(cur_term->_inputfd, &c, 1);
159 		} else
160 			i = read(cur_term->_inputfd, &c, 1);
161 
162 #ifdef	DEBUG
163 	if (outf)
164 		fprintf(outf, "read from %d returns %d chars, first %o\n",
165 		    cur_term->_inputfd, i, c);
166 #endif	/* DEBUG */
167 
168 	if (i > 0)
169 		return (c);
170 	else
171 		return (ERR);
172 }
173 #endif	/* !FIONREAD */
174 
175 #ifdef	DEBUG
176 extern	char	*_asciify();
177 #endif	/* DEBUG */
178 
179 static int get_xterm_mouse(int, int *);
180 static void _map_button(chtype *);
181 
182 /*
183  * This algorithm is a "learning" algorithm. The premise is
184  * that keys used once are like to be used again and again.
185  * Since the time for a linear search of the table is so
186  * expensive, we move keys that are found up to the top of
187  * the list, making the access to a repeated key very fast and
188  * keys that have been used before close to the top.
189  */
190 
191 static	int
192 _getkey(int blockpeek, chtype *inp)
193 {
194 	_KEY_MAP	**kp = cur_term->_keys;
195 	int		key, num_keys = cur_term->_ksz;
196 	int		i;
197 	chtype		*inputQ = cur_term->_input_queue;
198 	char		*chars_onQ = &(cur_term->_chars_on_queue);
199 	char		flag = cur_term->funckeystarter[*inp];
200 	int		first, collapse = 1;
201 
202 
203 #ifdef	DEBUG
204 	if (outf)
205 		fprintf(outf, "getkey(): looking in linear table, "
206 		    "inp=%d\n", *inp);
207 #endif	/* DEBUG */
208 
209 	if (flag & _KEY)
210 		key = 0;
211 	else {
212 		key = cur_term->_first_macro;
213 		blockpeek = TRUE;
214 	}
215 	first = key;
216 
217 	for (; key < num_keys; key++) {
218 		if (kp[key]->_sends[0] == *inp) {
219 			for (i = 1; i < INP_QSIZE; i++) {
220 				/* found it? */
221 				if (kp[key]->_sends[i] == '\0')
222 					break;
223 				/* partial match? peek ahead. */
224 				if (*chars_onQ == i) {
225 					(*chars_onQ)++;
226 					inputQ[i] = (blockpeek) ?
227 					    _pk() : _fpk();
228 					switch ((int)inputQ[i]) {
229 					case -2:
230 			/*
231 			 * Since -2 signifies a timeout we don't really
232 			 * want to put it on the queue so we decrement
233 			 * our counter.
234 			 */
235 						(*chars_onQ)--;
236 #ifdef	DEBUG
237 					if (outf)
238 						fprintf(outf, "Timed out\n");
239 #endif	/* DEBUG */
240 					if (flag & _MACRO) {
241 #ifdef	DEBUG
242 						if (outf)
243 							fprintf(outf,
244 							    "Found macro\n");
245 #endif	/* DEBUG */
246 				/*
247 				 * We have to decrement one because key will be
248 				 * incremented at the bottom of the out loop.
249 				 */
250 						key = (first = blockpeek =
251 						    cur_term->_first_macro) -
252 						    1;
253 						goto outerloop;
254 					}
255 
256 					/*FALLTHROUGH*/
257 
258 					case -1:
259 						goto ret;
260 					}
261 				}
262 
263 				/* not this one? */
264 				if (kp[key]->_sends[i] != inputQ[i])
265 					goto outerloop;
266 			}
267 
268 			/* SS-mouse */
269 			if (kp[key]->_keyval == KEY_MOUSE) {
270 				MOUSE_STATUS old_mouse;
271 				int rc;
272 
273 				old_mouse = Mouse_status;
274 
275 				/* read the mouse status information	*/
276 
277 				if (mouse_info)
278 					rc = -3;	/* NOT IMPLEMENTED */
279 				else
280 					rc = get_xterm_mouse(blockpeek, &i);
281 
282 				if (rc == -1)		/* read error */
283 					goto ret;
284 				else if (rc == -2 || rc == -3) /* timeout */
285 							/* or not mouse */
286 					goto outerloop;
287 				else if (rc == 0) /* report mouse pos */
288 					Mouse_status.changes |= 020;
289 				else if (rc >= 1 && rc <= 3)
290 					/* mouse button event */
291 					Mouse_status.changes =
292 					    (((MOUSE_X_POS != old_mouse.x ||
293 					    MOUSE_Y_POS != old_mouse.y) << 3) |
294 					    ((Mouse_status.button[2] !=
295 					    old_mouse.button[2]) << 2) |
296 					    ((Mouse_status.button[1] !=
297 					    old_mouse.button[1]) << 1) |
298 					    (Mouse_status.button[0] !=
299 					    old_mouse.button[0]));
300 			}
301 
302 			/* We found it! Read in any chars left in _sends */
303 
304 			if ((collapse = i) == INP_QSIZE)
305 				for (; kp[key]->_sends[i]; i++)
306 					(void) _fpk();
307 
308 			/* move key to top of ordered list */
309 			if (key != first) {
310 				_KEY_MAP	*savekey = kp[key];
311 				short		*lorder;
312 				int		j;
313 
314 				if (key > cur_term->_first_macro)
315 				lorder = &(cur_term->_lastmacro_ordered);
316 				else
317 					lorder = &(cur_term->_lastkey_ordered);
318 		/*
319 		 * If we're below the last ordered key, swap next unordered
320 		 * key with this one and ripple from there.
321 		 */
322 				if (key > *lorder)
323 					kp[key] = kp[(i = ++(*lorder))];
324 				else
325 					i = key;
326 				/* ripple the ordered keys down */
327 				for (j = i--; j > first; )
328 					kp[j--] = kp[i--];
329 				kp[first] = savekey;
330 			}
331 			*inp = kp[first]->_keyval;
332 
333 			/*
334 			 * SS-mouse support: if mouse button event
335 			 * occured on top of the soft label, we may
336 			 * have to return the function key corresponding
337 			 * to that soft label
338 			 */
339 
340 			if (*inp == KEY_MOUSE && A_BUTTON_CHANGED &&
341 			    (MOUSE_Y_POS == LINES) &&
342 			    (SP->slk != (SLK_MAP *) NULL) &&
343 			    (SP->_map_mbe_to_key  != 0)) {
344 				_map_button(inp);
345 			}
346 
347 			goto ret;
348 		}
349 outerloop:
350 		;
351 	}
352 
353 ret:
354 	/* key not found */
355 #ifdef	DEBUG
356 	if (outf)
357 		if (key == num_keys)
358 			fprintf(outf, "Did not match anything.\n");
359 #endif	/* DEBUG */
360 	return (collapse);
361 }
362 
363 
364 /* SS-mouse */
365 /* this function tries to read in information that follows KEY_MOUSE: */
366 /* the first character identifies what button is involved (1,2,or 3)  */
367 /* if the first character is 0, we are dealing with report_mouse_pos  */
368 /*
369  *	The routine returns the following:
370  *		-3:	not a mouse button event
371  *		-2:	read timed out
372  *		-1:	the read failed
373  *		[0, 1, 2, 3] - the first character in the mouse event
374  */
375 static int
376 get_xterm_mouse(int blockpeek, int *i)
377 {
378 	chtype	*inputQ = cur_term->_input_queue;		/* ??? */
379 	/* LINTED */
380 	chtype	*chars_onQ = (chtype *) &(cur_term->_chars_on_queue);
381 	int	j, mx, my;
382 	int	char1, char2, c1, c2;
383 
384 	/* the first character should be 0, 1, 2, or 4	*/
385 
386 	char1 = (inputQ[(*i)++] = (blockpeek) ? _pk() : _fpk());
387 
388 	/* read error or timeout	*/
389 
390 	if (char1 < 0)
391 		return (char1);
392 	(*chars_onQ)++;
393 
394 	if (char1 < '0' || char1 > '3')
395 		return (-3);
396 
397 	/* if the character is 1, 2, or 3 it must be followed by 	*/
398 	/* P, R, C, D, or T						*/
399 
400 	if (char1 != '0') {
401 		char2 = (inputQ[(*i)++] = (blockpeek) ? _pk() : _fpk());
402 
403 		if (char2 < 0)
404 			return (char2);
405 
406 		(*chars_onQ)++;
407 		if (char2 != 'P' && char2 != 'R' && char2 != 'C' &&
408 		    char2 != 'D' && char2 != 'T')
409 			return (-3);
410 	}
411 
412 	/* read X  and Y coordinates of the mouse	*/
413 
414 	for (j = 0; j < 2; j++) {
415 		c1 = (inputQ[(*i)++] = (blockpeek) ? _pk() : _fpk());
416 		if (c1 < 0)
417 			return (c1);
418 		(*chars_onQ)++;
419 		if (c1 >= ' ' && c1 <= '~') {	/* ascii char */
420 			if (j == 0)
421 				mx = c1 - ' ';
422 			else
423 				my = c1 - ' ';
424 		} else if (char1 == 01 || char1 == 02) {   /* ^A || ^B */
425 			c2 = (inputQ[(*i)++] = (blockpeek) ? _pk() : _fpk());
426 			if (c2 < 0)
427 				return (c2);
428 			(*chars_onQ)++;
429 			if (c2 >= ' ' && c2 <= '~') {
430 				if (j == 0)
431 					mx = c1 * (c2 - ' ');
432 				else
433 					my = c1 * (c2 - ' ');
434 			} else
435 				return (-3);
436 		} else
437 			return (-3);
438 	}
439 
440 	/* read complete mouse event: update the Mouse_status structure */
441 
442 	MOUSE_X_POS = mx;
443 	MOUSE_Y_POS = my;
444 	j = char1 - '0';
445 	if (j != 0) {
446 		switch (char2) {
447 			case 'P':
448 				BUTTON_STATUS(j) = BUTTON_PRESSED;
449 				break;
450 			case 'R':
451 				BUTTON_STATUS(j) = BUTTON_RELEASED;
452 				break;
453 			case 'C':
454 				BUTTON_STATUS(j) = BUTTON_CLICKED;
455 				break;
456 			case 'D':
457 				BUTTON_STATUS(j) = BUTTON_DOUBLE_CLICKED;
458 				break;
459 			case 'T':
460 				BUTTON_STATUS(j) = BUTTON_TRIPLE_CLICKED;
461 				break;
462 		}
463 	}
464 	return (j);
465 }
466 /* SS-mouse-end */
467 
468 
469 /*
470  * Fast peek key.  Like getchar but if the right flags are set, times out
471  * quickly if there is nothing waiting, returning -1.
472  * f is an output stdio descriptor, we read from the fileno.
473  * We wait for long enough for a terminal to send another character
474  * (at 15cps repeat rate, this is 67 ms, I'm using 100ms to allow
475  * a bit of a fudge factor) and time out more quickly.
476  * -2 is returned if we time out, -1 is returned if interrupted, and the
477  * character is returned otherwise.
478  */
479 
480 #ifndef	FIONREAD
481 
482 /*
483  * Traditional implementation.  The best resolution we have is 1 second,
484  * so we set a 1 second alarm and try to read.  If we fail for 1 second,
485  * we assume there is no key waiting.  Problem here is that 1 second is
486  * too long; people can type faster than this.
487  *
488  * Another possible implementation of changing VMIN/VTIME before and
489  * after each read does not work because the tty driver's timeout
490  * mechanism is too unreliable when the timeouts are changed too quickly.
491  */
492 
493 static	char	sig_caught;
494 
495 static
496 #ifdef	SIGPOLL	/* Vr3 and beyond */
497 void
498 #endif  /* SIGPOLL */
499 /* The following line causes a lint warning for "dummy" which is not used. */
500 _catch_alarm(int dummy)
501 {
502 	sig_caught = 1;
503 }
504 
505 static int
506 _fpk(void)
507 {
508 	unsigned	char	c;
509 	int		infd = cur_term->_inputfd;
510 	ssize_t		rc;
511 #ifdef	SIGPOLL	/* Vr3 and beyond */
512 	void	(*oldsig)(int);
513 #else	/* SIGPOLL */
514 	int		(*oldsig)(int);
515 #endif	/* SIGPOLL */
516 	unsigned	int	oldalarm, alarm(unsigned);
517 
518 	/* turn off any user alarms and set our own */
519 	oldalarm = alarm(0);
520 	sig_caught = 0;
521 	oldsig = signal(SIGALRM, _catch_alarm);
522 	(void) alarm(1);
523 	rc = read(cur_term->_inputfd, (char *)&c, 1);
524 	(void) alarm(0);
525 
526 	/*
527 	 * This code is to take care of the possibility of
528 	 * the process getting swapped out in the middle of
529 	 * read() call above. The interrupt will cause the
530 	 * read() call to retur, even if a character is really
531 	 * on the clist. So we do a non-blocking read() to make
532 	 * sure that there really isn't a character there.
533 	 */
534 
535 	if (sig_caught && rc != 1)
536 		if (cur_term->_check_fd != -1)
537 			rc = read(cur_term->_check_fd, (char *)&c, 1);
538 		else {
539 			int	fcflags = fcntl(infd, F_GETFL, 0);
540 
541 			(void) fcntl(infd, F_SETFL, fcflags | O_NDELAY);
542 			rc = read(infd, (char *)&c, 1);
543 			(void) fcntl(infd, F_SETFL, fcflags);
544 		}
545 
546 	/* restore the user alarms */
547 	(void) signal(SIGALRM, oldsig);
548 	if (sig_caught && oldalarm > 1)
549 		oldalarm--;
550 	(void) alarm(oldalarm);
551 	if (rc == 1)			/* got a character */
552 		return (c);
553 	else
554 		if (sig_caught)		/* timed out */
555 			return (-2);
556 		else			/* EOF or got interrupted */
557 			return (-1);
558 }
559 #else	/* FIONREAD */
560 /*
561  * If we have the select system call, we can do much better than the
562  * traditional method. Even if we don't have the real 4.2BSD select, we
563  * can emulate it with napms and FIONREAD.  napms might be done with only
564  * 1 second resolution, but this is no worse than what we have in the
565  * traditional implementation.
566  */
567 static
568 _fpk()
569 {
570 	int		infd, rc;
571 	int		*outfd, *exfd;
572 	unsigned	char	c;
573 	struct timeval	t;
574 
575 	infd = 1 << cur_term->_inputfd;
576 	outfd = exfd = (int *)NULL;
577 	t.tv_sec = 0;
578 	t.tv_usec = 100000;		/* 100 milliseconds */
579 	rc = select(20, &infd, outfd, exfd, &t);
580 	if (rc < 0)
581 		return (-2);
582 	rc = read(fileno(f), &c, 1);
583 	return (rc == 1 ? c : -1);
584 }
585 #endif	/* FIONREAD */
586 
587 /*
588  * Plain peekchar function.  Nothing fancy.  This is just like _fpk
589  * but will wait forever rather than time out.
590  */
591 
592 static int
593 _pk(void)
594 {
595 	unsigned	char	c;
596 
597 	return ((read(cur_term->_inputfd, (char *)&c, 1) == 1) ? c : ERR);
598 }
599 
600 
601 /*
602  * SS-mouse: check if this mouse button event should map into
603  * function key
604  */
605 
606 
607 static void
608 _map_button(chtype *inp)
609 {
610 	SLK_MAP *slk = SP->slk;
611 	int num = slk->_num;
612 	int len = slk->_len;
613 	int i;
614 
615 	/* first determine if this mouse button event should be */
616 	/* mapped into function key				*/
617 
618 	if (!(SP->_map_mbe_to_key &
619 	    ((BUTTON_CHANGED(3) << (10 + BUTTON_STATUS(3))) |
620 	    (BUTTON_CHANGED(2) << (5 + BUTTON_STATUS(2)))  |
621 	    (BUTTON_CHANGED(1) << BUTTON_STATUS(1)))))
622 		return;
623 
624 	for (i = 0; i < num; i++) {
625 		if (MOUSE_X_POS < slk->_labx[i])
626 			break;
627 		if (MOUSE_X_POS > slk->_labx[i] + len)
628 			continue;
629 		*inp = KEY_F(1) + i;
630 		break;
631 	}
632 }
633