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