get_wch.c revision 1.10
1285101Semaste/*   $NetBSD: get_wch.c,v 1.10 2012/06/29 10:40:29 blymn Exp $ */
2285101Semaste
3285101Semaste/*
4285101Semaste * Copyright (c) 2005 The NetBSD Foundation Inc.
5285101Semaste * All rights reserved.
6285101Semaste *
7285101Semaste * This code is derived from code donated to the NetBSD Foundation
8285101Semaste * by Ruibiao Qiu <ruibiao@arl.wustl.edu,ruibiao@gmail.com>.
9285101Semaste *
10285101Semaste *
11285101Semaste * Redistribution and use in source and binary forms, with or without
12285101Semaste * modification, are permitted provided that the following conditions
13285101Semaste * are met:
14285101Semaste * 1. Redistributions of source code must retain the above copyright
15285101Semaste *	notice, this list of conditions and the following disclaimer.
16285101Semaste * 2. Redistributions in binary form must reproduce the above copyright
17285101Semaste *	notice, this list of conditions and the following disclaimer in the
18285101Semaste *	documentation and/or other materials provided with the distribution.
19285101Semaste * 3. Neither the name of the NetBSD Foundation nor the names of its
20285101Semaste *	contributors may be used to endorse or promote products derived
21285101Semaste *	from this software without specific prior written permission.
22285101Semaste *
23285101Semaste * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
24285101Semaste * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
25285101Semaste * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
26285101Semaste * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27285101Semaste * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28285101Semaste * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29285101Semaste * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30285101Semaste * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31285101Semaste * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32285101Semaste * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33285101Semaste * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34285101Semaste * SUCH DAMAGE.
35285101Semaste */
36285101Semaste
37285101Semaste#include <sys/cdefs.h>
38285101Semaste#ifndef lint
39285101Semaste__RCSID("$NetBSD: get_wch.c,v 1.10 2012/06/29 10:40:29 blymn Exp $");
40285101Semaste#endif						  /* not lint */
41285101Semaste
42285101Semaste#include <string.h>
43285101Semaste#include <stdlib.h>
44285101Semaste#include <unistd.h>
45285101Semaste#include <stdio.h>
46285101Semaste#include "curses.h"
47285101Semaste#include "curses_private.h"
48285101Semaste#include "keymap.h"
49285101Semaste
50285101Semaste#ifdef HAVE_WCHAR
51285101Semastestatic short   wstate;		  /* state of the wcinkey function */
52285101Semaste#endif /* HAVE_WCHAR */
53285101Semasteextern short state;		/* storage declared in getch.c */
54285101Semaste
55285101Semaste/* prototypes for private functions */
56285101Semaste#ifdef HAVE_WCHAR
57285101Semastestatic int inkey(wchar_t *wc, int to, int delay);
58285101Semaste#endif /* HAVE_WCHAR */
59285101Semaste
60285101Semaste#ifdef HAVE_WCHAR
61285101Semaste/*
62285101Semaste * __init_get_wch - initialise all the pointers & structures needed to make
63285101Semaste * get_wch work in keypad mode.
64285101Semaste *
65285101Semaste */
66285101Semastevoid
67285101Semaste__init_get_wch(SCREEN *screen)
68285101Semaste{
69285101Semaste	wstate = INKEY_NORM;
70285101Semaste	memset( &screen->cbuf, 0, sizeof(screen->cbuf));
71285101Semaste	screen->cbuf_head = screen->cbuf_tail = screen->cbuf_cur = 0;
72285101Semaste}
73285101Semaste#endif /* HAVE_WCHAR */
74285101Semaste
75285101Semaste
76285101Semaste#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
223					= ( *start + 1 ) % MAX_CBUF_SIZE;
224				if (*start == *end) {
225					state = wstate = INKEY_NORM;
226#ifdef DEBUG
227					__CTRACE(__CTRACE_INPUT,
228					    "inkey: WCASSEMBLING=>NORM, "
229					    "start(%d), current(%d), end(%d)",
230					    *start, *working, *end);
231#endif /* DEBUG */
232				} else {
233					state = wstate = INKEY_BACKOUT;
234#ifdef DEBUG
235					__CTRACE(__CTRACE_INPUT,
236					    "inkey: WCASSEMBLING=>BACKOUT, "
237					    "start(%d), current(%d), end(%d)",
238					    *start, *working, *end);
239#endif /* DEBUG */
240				}
241				return OK;
242			} else {
243				/* assembling wide characters */
244				inbuf[ *end ] = k;
245				*working = *end;
246				*end = ( *end + 1 ) % MAX_CBUF_SIZE;
247#ifdef DEBUG
248				__CTRACE(__CTRACE_INPUT,
249				    "inkey: WCASSEMBLING[head(%d), "
250				    "urrent(%d), tail(%d)]\n",
251				    *start, *working, *end);
252#endif /* DEBUG */
253				ret = (int) mbrtowc( wc, inbuf + (*working), 1,
254					&_cursesi_screen->sp );
255#ifdef DEBUG
256				__CTRACE(__CTRACE_INPUT,
257				    "inkey: mbrtowc returns %d, wc(%x)\n",
258				    ret, *wc );
259#endif /* DEBUG */
260				if ( ret == -2 ) {
261					*working = (*working + 1)
262						% 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) {	/* only one char processed */
292					state = wstate = INKEY_NORM;
293#ifdef DEBUG
294					__CTRACE(__CTRACE_INPUT,
295					    "inkey: WCASSEMBLING=>NORM, "
296					    "start(%d), current(%d), end(%d)",
297					    *start, *working, *end);
298#endif /* DEBUG */
299				} else {
300					/* otherwise we must have more than 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			/* wide-character specific code */
326#ifdef DEBUG
327			__CTRACE(__CTRACE_INPUT,
328			    "inkey: Checking for wide char\n");
329#endif /* DEBUG */
330			mbrtowc( NULL, NULL, 1, &_cursesi_screen->sp );
331			*working = *start;
332			mlen = *end > *working ?
333				*end - *working : MAX_CBUF_SIZE - *working;
334			if ( !mlen )
335				return ERR;
336#ifdef DEBUG
337			__CTRACE(__CTRACE_INPUT,
338			    "inkey: Check wide char[head(%d), "
339			    "current(%d), tail(%d), mlen(%ld)]\n",
340			    *start, *working, *end, (long) mlen);
341#endif /* DEBUG */
342			ret = (int) mbrtowc( wc, inbuf + (*working), mlen,
343				&_cursesi_screen->sp );
344#ifdef DEBUG
345			__CTRACE(__CTRACE_INPUT,
346			    "inkey: mbrtowc returns %d, wc(%x)\n", ret, *wc);
347#endif /* DEBUG */
348			if ( ret == -2 && *end < *working ) {
349				/* second half of a wide character */
350				*working = 0;
351				mlen = *end;
352				if ( mlen )
353					ret = (int) mbrtowc( wc, inbuf, mlen,
354						&_cursesi_screen->sp );
355			}
356			if ( ret == -2 && wstate != INKEY_TIMEOUT ) {
357				*working = (*working + (int) mlen)
358					% MAX_CBUF_SIZE;
359				wstate = INKEY_WCASSEMBLING;
360				continue;
361			}
362			if ( ret == 0 )
363				ret = 1;
364			if ( ret == -1 ) {
365				/* return the first key we know about */
366				*wc = inbuf[ *start ];
367				*working = *start
368					= ( *start + 1 ) % MAX_CBUF_SIZE;
369#ifdef DEBUG
370				__CTRACE(__CTRACE_INPUT,
371				    "inkey: Invalid wide char(%x)[head(%d), "
372				    "current(%d), tail(%d)]\n",
373				    *wc, *start, *working, *end);
374#endif /* DEBUG */
375			} else { /* > 0 */
376				/* return the wide character */
377				*start = *working
378					= ( *working + ret ) % MAX_CBUF_SIZE;
379#ifdef DEBUG
380				__CTRACE(__CTRACE_INPUT,
381				    "inkey: Wide char found(%x)[head(%d), "
382				    "current(%d), tail(%d)]\n",
383				    *wc, *start, *working, *end);
384#endif /* DEBUG */
385			}
386
387			if (*start == *end) {	/* only one char processed */
388				state = wstate = INKEY_NORM;
389#ifdef DEBUG
390				__CTRACE(__CTRACE_INPUT,
391				    "inkey: Empty cbuf=>NORM, "
392				    "start(%d), current(%d), end(%d)\n",
393				    *start, *working, *end);
394#endif /* DEBUG */
395			} else {
396				/* otherwise we must have more than one char to backout */
397				state = wstate = INKEY_BACKOUT;
398#ifdef DEBUG
399				__CTRACE(__CTRACE_INPUT,
400				    "inkey: Non-empty cbuf=>BACKOUT, "
401				    "start(%d), current(%d), end(%d)\n",
402				    *start, *working, *end);
403#endif /* DEBUG */
404			}
405			return OK;
406		} else {	/* must be part of a multikey sequence */
407					/* check for completed key sequence */
408			if (current->key[current->mapping[k]]->type
409					== KEYMAP_LEAF) {
410				/* eat the key sequence in cbuf */
411				*start = *working = ( *working + 1 ) % MAX_CBUF_SIZE;
412
413				/* check if inbuf empty now */
414#ifdef DEBUG
415				__CTRACE(__CTRACE_INPUT,
416				    "inkey: Key found(%s)\n",
417				    key_name(current->key[mapping]->value.symbol));
418#endif /* DEBUG */
419				if (*start == *end) {
420					/* if it is go back to normal */
421					state = wstate = INKEY_NORM;
422#ifdef DEBUG
423					__CTRACE(__CTRACE_INPUT,
424					    "[inkey]=>NORM, start(%d), "
425					    "current(%d), end(%d)",
426					    *start, *working, *end);
427#endif /* DEBUG */
428				} else {
429					/* otherwise go to backout state */
430					state = wstate = INKEY_BACKOUT;
431#ifdef DEBUG
432					__CTRACE(__CTRACE_INPUT,
433					    "[inkey]=>BACKOUT, start(%d), "
434					    "current(%d), end(%d)",
435					    *start, *working, *end );
436#endif /* DEBUG */
437				}
438
439				/* return the symbol */
440				*wc = current->key[mapping]->value.symbol;
441				return KEY_CODE_YES;
442			} else {
443				/* Step to next part of multi-key sequence */
444				current = current->key[current->mapping[k]]->value.next;
445			}
446		}
447	}
448}
449#endif /* HAVE_WCHAR */
450
451/*
452 * get_wch --
453 *	Read in a wide character from stdscr.
454 */
455int
456get_wch(wint_t *ch)
457{
458#ifndef HAVE_WCHAR
459	return ERR;
460#else
461	return wget_wch(stdscr, ch);
462#endif /* HAVE_WCHAR */
463}
464
465/*
466 * mvget_wch --
467 *	  Read in a character from stdscr at the given location.
468 */
469int
470mvget_wch(int y, int x, wint_t *ch)
471{
472#ifndef HAVE_WCHAR
473	return ERR;
474#else
475	return mvwget_wch(stdscr, y, x, ch);
476#endif /* HAVE_WCHAR */
477}
478
479/*
480 * mvwget_wch --
481 *	  Read in a character from stdscr at the given location in the
482 *	  given window.
483 */
484int
485mvwget_wch(WINDOW *win, int y, int x, wint_t *ch)
486{
487#ifndef HAVE_WCHAR
488	return ERR;
489#else
490	if (wmove(win, y, x) == ERR)
491		return ERR;
492
493	return wget_wch(win, ch);
494#endif /* HAVE_WCHAR */
495}
496
497/*
498 * wget_wch --
499 *	Read in a wide character from the window.
500 */
501int
502wget_wch(WINDOW *win, wint_t *ch)
503{
504#ifndef HAVE_WCHAR
505	return ERR;
506#else
507	int ret, weset;
508	int c;
509	FILE *infd = _cursesi_screen->infd;
510	cchar_t wc;
511	wchar_t inp, ws[ 2 ];
512
513	if (!(win->flags & __SCROLLOK) && (win->flags & __FULLWIN)
514			&& win->curx == win->maxx - 1
515			&& win->cury == win->maxy - 1
516			&& __echoit)
517		return (ERR);
518
519	if (is_wintouched(win))
520		wrefresh(win);
521#ifdef DEBUG
522	__CTRACE(__CTRACE_INPUT, "wget_wch: __echoit = %d, "
523	    "__rawmode = %d, __nl = %d, flags = %#.4x\n",
524	    __echoit, __rawmode, _cursesi_screen->nl, win->flags);
525#endif
526	if (_cursesi_screen->resized) {
527		_cursesi_screen->resized = 0;
528		*ch = KEY_RESIZE;
529		return KEY_CODE_YES;
530	}
531	if (_cursesi_screen->unget_pos) {
532#ifdef DEBUG
533		__CTRACE(__CTRACE_INPUT, "wget_wch returning char at %d\n",
534		    _cursesi_screen->unget_pos);
535#endif
536		_cursesi_screen->unget_pos--;
537		*ch = _cursesi_screen->unget_list[_cursesi_screen->unget_pos];
538		if (__echoit) {
539			ws[0] = *ch, ws[1] = L'\0';
540			setcchar(&wc, ws, win->wattr, 0, NULL);
541			wadd_wch(win, &wc);
542		}
543		return KEY_CODE_YES;
544	}
545	if (__echoit && !__rawmode) {
546		cbreak();
547		weset = 1;
548	} else
549		weset = 0;
550
551	__save_termios();
552
553	if (win->flags & __KEYPAD) {
554		switch (win->delay) {
555			case -1:
556				ret = inkey(&inp,
557					win->flags & __NOTIMEOUT ? 0 : 1, 0);
558				break;
559			case 0:
560				if (__nodelay() == ERR)
561					return ERR;
562				ret = inkey(&inp, 0, 0);
563				break;
564			default:
565				ret = inkey(&inp,
566					win->flags & __NOTIMEOUT ? 0 : 1,
567					win->delay);
568				break;
569		}
570		if ( ret == ERR )
571			return ERR;
572	} else {
573		switch (win->delay) {
574			case -1:
575				break;
576			case 0:
577				if (__nodelay() == ERR)
578					return ERR;
579				break;
580			default:
581				if (__timeout(win->delay) == ERR)
582					return ERR;
583				break;
584		}
585
586		c = getwchar();
587		if (feof(infd)) {
588			clearerr(infd);
589			__restore_termios();
590			return ERR;	/* we have timed out */
591		}
592
593		if (ferror(infd)) {
594			clearerr(infd);
595			return ERR;
596		} else {
597			ret = c;
598			inp = c;
599		}
600	}
601#ifdef DEBUG
602	if (inp > 255)
603		/* we have a key symbol - treat it differently */
604		/* XXXX perhaps __unctrl should be expanded to include
605		 * XXXX the keysyms in the table....
606		 */
607		__CTRACE(__CTRACE_INPUT, "wget_wch assembled keysym 0x%x\n",
608		    inp);
609	else
610		__CTRACE(__CTRACE_INPUT, "wget_wch got '%s'\n", unctrl(inp));
611#endif
612	if (win->delay > -1) {
613		if (__delay() == ERR)
614			return ERR;
615	}
616
617	__restore_termios();
618
619	if (__echoit) {
620		if ( ret == KEY_CODE_YES ) {
621			/* handle [DEL], [BS], and [LEFT] */
622			if ( win->curx &&
623					( inp == KEY_DC ||
624					  inp == KEY_BACKSPACE ||
625					  inp == KEY_LEFT )) {
626				wmove( win, win->cury, win->curx - 1 );
627				wdelch( win );
628			}
629		} else {
630			ws[ 0 ] = inp, ws[ 1 ] = L'\0';
631			setcchar( &wc, ws, win->wattr, 0, NULL );
632			wadd_wch( win, &wc );
633		}
634	}
635
636	if (weset)
637		nocbreak();
638
639	if (_cursesi_screen->nl && inp == 13)
640		inp = 10;
641
642	*ch = inp;
643
644	if ( ret == KEY_CODE_YES )
645		return KEY_CODE_YES;
646	return ( inp < 0 ? ERR : OK );
647#endif /* HAVE_WCHAR */
648}
649
650/*
651 * unget_wch --
652 *	 Put the wide character back into the input queue.
653 */
654int
655unget_wch(const wchar_t c)
656{
657	return __unget((wint_t) c);
658}
659