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