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