get_wch.c revision 1.19
1/*   $NetBSD: get_wch.c,v 1.19 2018/09/28 15:03:48 roy Exp $ */
2
3/*
4 * Copyright (c) 2005 The NetBSD Foundation Inc.
5 * All rights reserved.
6 *
7 * This code is derived from code donated to the NetBSD Foundation
8 * by Ruibiao Qiu <ruibiao@arl.wustl.edu,ruibiao@gmail.com>.
9 *
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 *	notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *	notice, this list of conditions and the following disclaimer in the
18 *	documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the NetBSD Foundation nor the names of its
20 *	contributors may be used to endorse or promote products derived
21 *	from this software without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
24 * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
25 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
26 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37#include <sys/cdefs.h>
38#ifndef lint
39__RCSID("$NetBSD: get_wch.c,v 1.19 2018/09/28 15:03:48 roy Exp $");
40#endif						  /* not lint */
41
42#include <errno.h>
43#include <string.h>
44#include <stdlib.h>
45#include <unistd.h>
46#include <stdio.h>
47#include "curses.h"
48#include "curses_private.h"
49#include "keymap.h"
50
51#ifdef HAVE_WCHAR
52static short   wstate;		  /* state of the wcinkey function */
53#endif /* HAVE_WCHAR */
54extern short state;		/* storage declared in getch.c */
55
56/* prototypes for private functions */
57#ifdef HAVE_WCHAR
58static int inkey(wchar_t *wc, int to, int delay);
59static wint_t __fgetwc_resize(FILE *infd, bool *resized);
60#endif /* HAVE_WCHAR */
61
62#ifdef HAVE_WCHAR
63/*
64 * __init_get_wch - initialise all the pointers & structures needed to make
65 * get_wch work in keypad mode.
66 *
67 */
68void
69__init_get_wch(SCREEN *screen)
70{
71	wstate = INKEY_NORM;
72	memset(&screen->cbuf, 0, sizeof(screen->cbuf));
73	screen->cbuf_head = screen->cbuf_tail = screen->cbuf_cur = 0;
74}
75#endif /* HAVE_WCHAR */
76
77
78#ifdef HAVE_WCHAR
79/*
80 * inkey - do the work to process keyboard input, check for multi-key
81 * sequences and return the appropriate symbol if we get a match.
82 *
83 */
84static int
85inkey(wchar_t *wc, int to, int delay)
86{
87	wchar_t		 k = 0;
88	int		 c, mapping, ret = 0;
89	size_t	  mlen = 0;
90	keymap_t	*current = _cursesi_screen->base_keymap;
91	FILE		*infd = _cursesi_screen->infd;
92	int		 *start = &_cursesi_screen->cbuf_head,
93				*working = &_cursesi_screen->cbuf_cur,
94				*end = &_cursesi_screen->cbuf_tail;
95	char		*inbuf = &_cursesi_screen->cbuf[ 0 ];
96
97#ifdef DEBUG
98	__CTRACE(__CTRACE_INPUT, "inkey (%p, %d, %d)\n", wc, to, delay);
99#endif
100	for (;;) { /* loop until we get a complete key sequence */
101		if (wstate == INKEY_NORM) {
102			if (delay && __timeout(delay) == ERR)
103				return ERR;
104			c = __fgetc_resize(infd);
105			if (c == ERR || c == KEY_RESIZE) {
106				clearerr(infd);
107				return c;
108			}
109
110			if (delay && (__notimeout() == ERR))
111				return ERR;
112
113			k = (wchar_t)c;
114#ifdef DEBUG
115			__CTRACE(__CTRACE_INPUT,
116			    "inkey (wstate normal) got '%s'\n", unctrl(k));
117#endif
118
119			inbuf[*end] = k;
120			*end = (*end + 1) % MAX_CBUF_SIZE;
121			*working = *start;
122			wstate = INKEY_ASSEMBLING; /* go to assembling state */
123#ifdef DEBUG
124			__CTRACE(__CTRACE_INPUT,
125			    "inkey: NORM=>ASSEMBLING: start(%d), "
126			    "current(%d), end(%d)\n", *start, *working, *end);
127#endif /* DEBUG */
128		} else if (wstate == INKEY_BACKOUT) {
129			k = inbuf[*working];
130			*working = (*working + 1) % MAX_CBUF_SIZE;
131			if (*working == *end) {	/* see if run out of keys */
132				/* if so, switch to assembling */
133				wstate = INKEY_ASSEMBLING;
134#ifdef DEBUG
135				__CTRACE(__CTRACE_INPUT,
136				    "inkey: BACKOUT=>ASSEMBLING, start(%d), "
137				    "current(%d), end(%d)\n",
138				    *start, *working, *end);
139#endif /* DEBUG */
140			}
141		} else if (wstate == INKEY_ASSEMBLING) {
142			/* assembling a key sequence */
143			if (delay) {
144				if (__timeout(to ? (ESCDELAY / 100) : delay)
145						== ERR)
146					return ERR;
147			} else {
148				if (to && (__timeout(ESCDELAY / 100) == ERR))
149					return ERR;
150			}
151
152			c = __fgetc_resize(infd);
153			if (ferror(infd)) {
154				clearerr(infd);
155				return c;
156			}
157
158			if ((to || delay) && (__notimeout() == ERR))
159				return ERR;
160
161			k = (wchar_t)c;
162#ifdef DEBUG
163			__CTRACE(__CTRACE_INPUT,
164			    "inkey (wstate assembling) got '%s'\n", unctrl(k));
165#endif /* DEBUG */
166			if (feof(infd)) { /* inter-char T/O, start backout */
167				clearerr(infd);
168				if (*start == *end)
169					/* no chars in the buffer, restart */
170					continue;
171
172				k = inbuf[*start];
173				wstate = INKEY_TIMEOUT;
174#ifdef DEBUG
175				__CTRACE(__CTRACE_INPUT,
176				    "inkey: ASSEMBLING=>TIMEOUT, start(%d), "
177				    "current(%d), end(%d)\n",
178				    *start, *working, *end);
179#endif /* DEBUG */
180			} else {
181				inbuf[*end] = k;
182				*working = *end;
183				*end = (*end + 1) % MAX_CBUF_SIZE;
184#ifdef DEBUG
185				__CTRACE(__CTRACE_INPUT,
186				    "inkey: ASSEMBLING: start(%d), "
187				    "current(%d), end(%d)",
188				    *start, *working, *end);
189#endif /* DEBUG */
190			}
191		} else if (wstate == INKEY_WCASSEMBLING) {
192			/* assembling a wide-char sequence */
193			if (delay) {
194				if (__timeout(to ? (ESCDELAY / 100) : delay)
195						== ERR)
196					return ERR;
197			} else {
198				if (to && (__timeout(ESCDELAY / 100) == ERR))
199					return ERR;
200			}
201
202			c = __fgetc_resize(infd);
203			if (ferror(infd)) {
204				clearerr(infd);
205				return c;
206			}
207
208			if ((to || delay) && (__notimeout() == ERR))
209				return ERR;
210
211			k = (wchar_t)c;
212#ifdef DEBUG
213			__CTRACE(__CTRACE_INPUT,
214			    "inkey (wstate wcassembling) got '%s'\n",
215				unctrl(k));
216#endif
217			if (feof(infd)) { /* inter-char T/O, start backout */
218				clearerr(infd);
219				if (*start == *end)
220					/* no chars in the buffer, restart */
221					continue;
222
223				*wc = inbuf[*start];
224				*working = *start = (*start +1) % MAX_CBUF_SIZE;
225				if (*start == *end) {
226					state = wstate = INKEY_NORM;
227#ifdef DEBUG
228					__CTRACE(__CTRACE_INPUT,
229					    "inkey: WCASSEMBLING=>NORM, "
230					    "start(%d), current(%d), end(%d)",
231					    *start, *working, *end);
232#endif /* DEBUG */
233				} else {
234					state = wstate = INKEY_BACKOUT;
235#ifdef DEBUG
236					__CTRACE(__CTRACE_INPUT,
237					    "inkey: WCASSEMBLING=>BACKOUT, "
238					    "start(%d), current(%d), end(%d)",
239					    *start, *working, *end);
240#endif /* DEBUG */
241				}
242				return OK;
243			} else {
244				/* assembling wide characters */
245				inbuf[*end] = k;
246				*working = *end;
247				*end = (*end + 1) % MAX_CBUF_SIZE;
248#ifdef DEBUG
249				__CTRACE(__CTRACE_INPUT,
250				    "inkey: WCASSEMBLING[head(%d), "
251				    "urrent(%d), tail(%d)]\n",
252				    *start, *working, *end);
253#endif /* DEBUG */
254				ret = (int)mbrtowc(wc, inbuf + (*working), 1,
255						   &_cursesi_screen->sp);
256#ifdef DEBUG
257				__CTRACE(__CTRACE_INPUT,
258				    "inkey: mbrtowc returns %d, wc(%x)\n",
259				    ret, *wc);
260#endif /* DEBUG */
261				if (ret == -2) {
262					*working = (*working+1) % MAX_CBUF_SIZE;
263					continue;
264				}
265				if ( ret == 0 )
266					ret = 1;
267				if ( ret == -1 ) {
268					/* return the 1st character we know */
269					*wc = inbuf[*start];
270					*working = *start = (*start + 1) % MAX_CBUF_SIZE;
271#ifdef DEBUG
272					__CTRACE(__CTRACE_INPUT,
273					    "inkey: Invalid wide char(%x) "
274					    "[head(%d), current(%d), "
275					    "tail(%d)]\n",
276					    *wc, *start, *working, *end);
277#endif /* DEBUG */
278				} else { /* > 0 */
279					/* return the wide character */
280					*start = *working
281					       = (*working + ret)%MAX_CBUF_SIZE;
282#ifdef DEBUG
283					__CTRACE(__CTRACE_INPUT,
284					    "inkey: Wide char found(%x) "
285					    "[head(%d), current(%d), "
286					    "tail(%d)]\n",
287					    *wc, *start, *working, *end);
288#endif /* DEBUG */
289				}
290
291				if (*start == *end) {
292					/* only one char processed */
293					state = wstate = INKEY_NORM;
294#ifdef DEBUG
295					__CTRACE(__CTRACE_INPUT,
296					    "inkey: WCASSEMBLING=>NORM, "
297					    "start(%d), current(%d), end(%d)",
298					    *start, *working, *end);
299#endif /* DEBUG */
300				} else {
301					/* otherwise we must have more than
302					 * one char to backout */
303					state = wstate = INKEY_BACKOUT;
304#ifdef DEBUG
305					__CTRACE(__CTRACE_INPUT,
306					    "inkey: WCASSEMBLING=>BACKOUT, "
307					    "start(%d), current(%d), end(%d)",
308					    *start, *working, *end);
309#endif /* DEBUG */
310				}
311				return OK;
312			}
313		} else {
314			fprintf(stderr, "Inkey wstate screwed - exiting!!!");
315			exit(2);
316		}
317
318		/*
319		 * Check key has no special meaning and we have not
320		 * timed out and the key has not been disabled
321		 */
322		mapping = current->mapping[k];
323		if (((wstate == INKEY_TIMEOUT) || (mapping < 0))
324				|| ((current->key[mapping]->type
325					== KEYMAP_LEAF)
326				&& (current->key[mapping]->enable == FALSE)))
327		{
328			/* wide-character specific code */
329#ifdef DEBUG
330			__CTRACE(__CTRACE_INPUT,
331			    "inkey: Checking for wide char\n");
332#endif /* DEBUG */
333			mbrtowc( NULL, NULL, 1, &_cursesi_screen->sp );
334			*working = *start;
335			mlen = *end > *working ?
336				*end - *working : MAX_CBUF_SIZE - *working;
337			if (!mlen)
338				return ERR;
339#ifdef DEBUG
340			__CTRACE(__CTRACE_INPUT,
341			    "inkey: Check wide char[head(%d), "
342			    "current(%d), tail(%d), mlen(%ld)]\n",
343			    *start, *working, *end, (long) mlen);
344#endif /* DEBUG */
345			ret = (int)mbrtowc(wc, inbuf + (*working), mlen,
346			                   &_cursesi_screen->sp);
347#ifdef DEBUG
348			__CTRACE(__CTRACE_INPUT,
349			    "inkey: mbrtowc returns %d, wc(%x)\n", ret, *wc);
350#endif /* DEBUG */
351			if (ret == -2 && *end < *working) {
352				/* second half of a wide character */
353				*working = 0;
354				mlen = *end;
355				if (mlen)
356					ret = (int)mbrtowc(wc, inbuf, mlen,
357							  &_cursesi_screen->sp);
358			}
359			if (ret == -2 && wstate != INKEY_TIMEOUT) {
360				*working = (*working + (int) mlen)
361					% MAX_CBUF_SIZE;
362				wstate = INKEY_WCASSEMBLING;
363				continue;
364			}
365			if (ret == 0)
366				ret = 1;
367			if (ret == -1) {
368				/* return the first key we know about */
369				*wc = inbuf[*start];
370				*working = *start
371					= (*start + 1) % MAX_CBUF_SIZE;
372#ifdef DEBUG
373				__CTRACE(__CTRACE_INPUT,
374				    "inkey: Invalid wide char(%x)[head(%d), "
375				    "current(%d), tail(%d)]\n",
376				    *wc, *start, *working, *end);
377#endif /* DEBUG */
378			} else { /* > 0 */
379				/* return the wide character */
380				*start = *working
381					= (*working + ret) % MAX_CBUF_SIZE;
382#ifdef DEBUG
383				__CTRACE(__CTRACE_INPUT,
384				    "inkey: Wide char found(%x)[head(%d), "
385				    "current(%d), tail(%d)]\n",
386				    *wc, *start, *working, *end);
387#endif /* DEBUG */
388			}
389
390			if (*start == *end) {	/* only one char processed */
391				state = wstate = INKEY_NORM;
392#ifdef DEBUG
393				__CTRACE(__CTRACE_INPUT,
394				    "inkey: Empty cbuf=>NORM, "
395				    "start(%d), current(%d), end(%d)\n",
396				    *start, *working, *end);
397#endif /* DEBUG */
398			} else {
399				/* otherwise we must have more than one
400				 * char to backout */
401				state = wstate = INKEY_BACKOUT;
402#ifdef DEBUG
403				__CTRACE(__CTRACE_INPUT,
404				    "inkey: Non-empty cbuf=>BACKOUT, "
405				    "start(%d), current(%d), end(%d)\n",
406				    *start, *working, *end);
407#endif /* DEBUG */
408			}
409			return OK;
410		} else {	/* must be part of a multikey sequence */
411					/* check for completed key sequence */
412			if (current->key[current->mapping[k]]->type
413					== KEYMAP_LEAF) {
414				/* eat the key sequence in cbuf */
415				*start = *working = ( *working + 1 )
416				    % MAX_CBUF_SIZE;
417
418				/* check if inbuf empty now */
419#ifdef DEBUG
420				__CTRACE(__CTRACE_INPUT,
421				    "inkey: Key found(%s)\n",
422				    key_name(current->key[mapping]->value.symbol));
423#endif /* DEBUG */
424				if (*start == *end) {
425					/* if it is go back to normal */
426					state = wstate = INKEY_NORM;
427#ifdef DEBUG
428					__CTRACE(__CTRACE_INPUT,
429					    "[inkey]=>NORM, start(%d), "
430					    "current(%d), end(%d)",
431					    *start, *working, *end);
432#endif /* DEBUG */
433				} else {
434					/* otherwise go to backout state */
435					state = wstate = INKEY_BACKOUT;
436#ifdef DEBUG
437					__CTRACE(__CTRACE_INPUT,
438					    "[inkey]=>BACKOUT, start(%d), "
439					    "current(%d), end(%d)",
440					    *start, *working, *end );
441#endif /* DEBUG */
442				}
443
444				/* return the symbol */
445				*wc = current->key[mapping]->value.symbol;
446				return KEY_CODE_YES;
447			} else {
448				/* Step to next part of multi-key sequence */
449				current = current->key[current->mapping[k]]->value.next;
450			}
451		}
452	}
453}
454#endif /* HAVE_WCHAR */
455
456/*
457 * get_wch --
458 *	Read in a wide character from stdscr.
459 */
460int
461get_wch(wint_t *ch)
462{
463#ifndef HAVE_WCHAR
464	return ERR;
465#else
466	return wget_wch(stdscr, ch);
467#endif /* HAVE_WCHAR */
468}
469
470/*
471 * mvget_wch --
472 *	  Read in a character from stdscr at the given location.
473 */
474int
475mvget_wch(int y, int x, wint_t *ch)
476{
477#ifndef HAVE_WCHAR
478	return ERR;
479#else
480	return mvwget_wch(stdscr, y, x, ch);
481#endif /* HAVE_WCHAR */
482}
483
484/*
485 * mvwget_wch --
486 *	  Read in a character from stdscr at the given location in the
487 *	  given window.
488 */
489int
490mvwget_wch(WINDOW *win, int y, int x, wint_t *ch)
491{
492#ifndef HAVE_WCHAR
493	return ERR;
494#else
495	if (wmove(win, y, x) == ERR)
496		return ERR;
497
498	return wget_wch(win, ch);
499#endif /* HAVE_WCHAR */
500}
501
502/*
503 * wget_wch --
504 *	Read in a wide character from the window.
505 */
506int
507wget_wch(WINDOW *win, wint_t *ch)
508{
509#ifndef HAVE_WCHAR
510	return ERR;
511#else
512	int ret, weset;
513	int c;
514	FILE *infd = _cursesi_screen->infd;
515	cchar_t wc;
516	wchar_t inp, ws[2];
517
518	if (!(win->flags & __SCROLLOK)
519	    && (win->flags & __FULLWIN)
520	    && win->curx == win->maxx - 1
521	    && win->cury == win->maxy - 1
522	    && __echoit)
523		return ERR;
524
525	if (is_wintouched(win))
526		wrefresh(win);
527#ifdef DEBUG
528	__CTRACE(__CTRACE_INPUT, "wget_wch: __echoit = %d, "
529	    "__rawmode = %d, __nl = %d, flags = %#.4x\n",
530	    __echoit, __rawmode, _cursesi_screen->nl, win->flags);
531#endif
532	if (_cursesi_screen->resized) {
533		resizeterm(LINES, COLS);
534		_cursesi_screen->resized = 0;
535		*ch = KEY_RESIZE;
536		return KEY_CODE_YES;
537	}
538	if (_cursesi_screen->unget_pos) {
539#ifdef DEBUG
540		__CTRACE(__CTRACE_INPUT, "wget_wch returning char at %d\n",
541		    _cursesi_screen->unget_pos);
542#endif
543		_cursesi_screen->unget_pos--;
544		*ch = _cursesi_screen->unget_list[_cursesi_screen->unget_pos];
545		if (__echoit) {
546			ws[0] = *ch, ws[1] = L'\0';
547			setcchar(&wc, ws, win->wattr, 0, NULL);
548			wadd_wch(win, &wc);
549		}
550		return KEY_CODE_YES;
551	}
552	if (__echoit && !__rawmode) {
553		cbreak();
554		weset = 1;
555	} else
556		weset = 0;
557
558	__save_termios();
559
560	if (win->flags & __KEYPAD) {
561		switch (win->delay) {
562			case -1:
563				ret = inkey(&inp,
564					win->flags & __NOTIMEOUT ? 0 : 1, 0);
565				break;
566			case 0:
567				if (__nodelay() == ERR)
568					return ERR;
569				ret = inkey(&inp, 0, 0);
570				break;
571			default:
572				ret = inkey(&inp,
573					win->flags & __NOTIMEOUT ? 0 : 1,
574					win->delay);
575				break;
576		}
577		if ( ret == ERR )
578			return ERR;
579	} else {
580		bool resized;
581
582		switch (win->delay) {
583			case -1:
584				break;
585			case 0:
586				if (__nodelay() == ERR)
587					return ERR;
588				break;
589			default:
590				if (__timeout(win->delay) == ERR)
591					return ERR;
592				break;
593		}
594
595		c = __fgetwc_resize(infd, &resized);
596		if (c == WEOF) {
597			clearerr(infd);
598			__restore_termios();
599			if (resized) {
600				*ch = KEY_RESIZE;
601				return KEY_CODE_YES;
602			} else
603				return ERR;
604		} else {
605			ret = c;
606			inp = c;
607		}
608	}
609#ifdef DEBUG
610	if (inp > 255)
611		/* we have a key symbol - treat it differently */
612		/* XXXX perhaps __unctrl should be expanded to include
613		 * XXXX the keysyms in the table....
614		 */
615		__CTRACE(__CTRACE_INPUT, "wget_wch assembled keysym 0x%x\n",
616		    inp);
617	else
618		__CTRACE(__CTRACE_INPUT, "wget_wch got '%s'\n", unctrl(inp));
619#endif
620	if (win->delay > -1) {
621		if (__delay() == ERR)
622			return ERR;
623	}
624
625	__restore_termios();
626
627	if (__echoit) {
628		if ( ret == KEY_CODE_YES ) {
629			/* handle [DEL], [BS], and [LEFT] */
630			if ( win->curx &&
631					( inp == KEY_DC ||
632					  inp == KEY_BACKSPACE ||
633					  inp == KEY_LEFT )) {
634				wmove( win, win->cury, win->curx - 1 );
635				wdelch( win );
636			}
637		} else {
638			ws[ 0 ] = inp, ws[ 1 ] = L'\0';
639			setcchar( &wc, ws, win->wattr, 0, NULL );
640			wadd_wch( win, &wc );
641		}
642	}
643
644	if (weset)
645		nocbreak();
646
647	if (_cursesi_screen->nl && inp == 13)
648		inp = 10;
649
650	*ch = inp;
651
652	if ( ret == KEY_CODE_YES )
653		return KEY_CODE_YES;
654	return inp < 0 ? ERR : OK;
655#endif /* HAVE_WCHAR */
656}
657
658/*
659 * unget_wch --
660 *	 Put the wide character back into the input queue.
661 */
662int
663unget_wch(const wchar_t c)
664{
665	return __unget((wint_t)c);
666}
667
668#ifdef HAVE_WCHAR
669/*
670 * __fgetwc_resize --
671 *    Any call to fgetwc(3) should use this function instead.
672 */
673static wint_t
674__fgetwc_resize(FILE *infd, bool *resized)
675{
676	wint_t c;
677
678	c = fgetwc(infd);
679	if (c != WEOF)
680		return c;
681
682	if (!ferror(infd) || errno != EINTR || !_cursesi_screen->resized)
683		return ERR;
684#ifdef DEBUG
685	__CTRACE(__CTRACE_INPUT, "__fgetwc_resize returning KEY_RESIZE\n");
686#endif
687	resizeterm(LINES, COLS);
688	_cursesi_screen->resized = 0;
689	*resized = true;
690	return c;
691}
692#endif
693