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