get_wch.c revision 1.6
1/*   $NetBSD: get_wch.c,v 1.6 2008/04/14 20:33:59 jdc 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.6 2008/04/14 20:33:59 jdc 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, MAX_CBUF_SIZE * sizeof( int ));
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 = getchar();
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 = getchar();
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 = getchar();
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					__restore_termios();
562					return ERR;
563				}
564				ret = inkey(&inp, 0, 0);
565				break;
566			default:
567				ret = inkey(&inp,
568					win->flags & __NOTIMEOUT ? 0 : 1,
569					win->delay);
570				break;
571		}
572		if ( ret == ERR )
573			return ERR;
574	} else {
575		switch (win->delay) {
576			case -1:
577				break;
578			case 0:
579				if (__nodelay() == ERR) {
580					__restore_termios();
581					return ERR;
582				}
583				break;
584			default:
585				if (__timeout(win->delay) == ERR) {
586					__restore_termios();
587					return ERR;
588				}
589				break;
590		}
591
592		c = getwchar();
593		if (feof(infd)) {
594			clearerr(infd);
595			__restore_termios();
596			return ERR;	/* we have timed out */
597		}
598
599		if (ferror(infd)) {
600			clearerr(infd);
601			return ERR;
602		} else {
603			ret = c;
604			inp = c;
605		}
606	}
607#ifdef DEBUG
608	if (inp > 255)
609		/* we have a key symbol - treat it differently */
610		/* XXXX perhaps __unctrl should be expanded to include
611		 * XXXX the keysyms in the table....
612		 */
613		__CTRACE(__CTRACE_INPUT, "wget_wch assembled keysym 0x%x\n",
614		    inp);
615	else
616		__CTRACE(__CTRACE_INPUT, "wget_wch got '%s'\n", unctrl(inp));
617#endif
618	if (win->delay > -1) {
619		if (__delay() == ERR) {
620			__restore_termios();
621			return ERR;
622		}
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