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