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