1 /*   $NetBSD: get_wch.c,v 1.28 2024/12/23 02:58:03 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.28 2024/12/23 02:58:03 blymn 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 
51 static short wstate;                    /* state of the wcinkey function */
52 extern short _cursesi_state;  /* storage declared in getch.c */
53 
54 /* prototypes for private functions */
55 static int inkey(wchar_t *wc, int to, int delay);
56 static 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  */
63 void
__init_get_wch(SCREEN * screen)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  */
77 static int
inkey(wchar_t * wc,int to,int delay)78 inkey(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 (c == ERR || 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 (c == ERR || 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  */
401 int
get_wch(wint_t * ch)402 get_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  */
411 int
mvget_wch(int y,int x,wint_t * ch)412 mvget_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  */
422 int
mvwget_wch(WINDOW * win,int y,int x,wint_t * ch)423 mvwget_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  */
435 int
wget_wch(WINDOW * win,wint_t * ch)436 wget_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 (__predict_false(win == NULL))
445                     return ERR;
446 
447           if (!(win->flags & __SCROLLOK)
448               && (win->flags & __FULLWIN)
449               && win->curx == win->maxx - 1
450               && win->cury == win->maxy - 1
451               && __echoit)
452                     return ERR;
453 
454           if (!(win->flags & __ISPAD) && is_wintouched(win))
455                     wrefresh(win);
456           __CTRACE(__CTRACE_INPUT, "wget_wch: __echoit = %d, "
457               "__rawmode = %d, __nl = %d, flags = %#.4x\n",
458               __echoit, __rawmode, _cursesi_screen->nl, win->flags);
459           if (_cursesi_screen->resized) {
460                     resizeterm(LINES, COLS);
461                     _cursesi_screen->resized = 0;
462                     *ch = KEY_RESIZE;
463                     return KEY_CODE_YES;
464           }
465           if (_cursesi_screen->unget_pos) {
466                     __CTRACE(__CTRACE_INPUT, "wget_wch returning char at %d\n",
467                         _cursesi_screen->unget_pos);
468                     _cursesi_screen->unget_pos--;
469                     *ch = _cursesi_screen->unget_list[_cursesi_screen->unget_pos];
470                     if (__echoit) {
471                               ws[0] = *ch, ws[1] = L'\0';
472                               setcchar(&wc, ws, win->wattr, 0, NULL);
473                               wadd_wch(win, &wc);
474                     }
475                     return KEY_CODE_YES;
476           }
477           if (__echoit && !__rawmode) {
478                     cbreak();
479                     weset = 1;
480           } else
481                     weset = 0;
482 
483           __save_termios();
484 
485           if (win->flags & __KEYPAD) {
486                     switch (win->delay) {
487                               case -1:
488                                         ret = inkey(&inp,
489                                                   win->flags & __NOTIMEOUT ? 0 : 1, 0);
490                                         break;
491                               case 0:
492                                         if (__nodelay() == ERR)
493                                                   return ERR;
494                                         ret = inkey(&inp, 0, 0);
495                                         break;
496                               default:
497                                         ret = inkey(&inp,
498                                                   win->flags & __NOTIMEOUT ? 0 : 1,
499                                                   win->delay);
500                                         break;
501                     }
502                     if ( ret == ERR )
503                               return ERR;
504           } else {
505                     bool resized;
506 
507                     switch (win->delay) {
508                               case -1:
509                                         break;
510                               case 0:
511                                         if (__nodelay() == ERR)
512                                                   return ERR;
513                                         break;
514                               default:
515                                         if (__timeout(win->delay) == ERR)
516                                                   return ERR;
517                                         break;
518                     }
519 
520                     c = __fgetwc_resize(infd, &resized);
521                     if (c == WEOF) {
522                               clearerr(infd);
523                               __restore_termios();
524                               if (resized) {
525                                         *ch = KEY_RESIZE;
526                                         return KEY_CODE_YES;
527                               } else
528                                         return ERR;
529                     } else {
530                               ret = c;
531                               inp = c;
532                     }
533           }
534 #ifdef DEBUG
535           if (inp > 255)
536                     /* we have a key symbol - treat it differently */
537                     /* XXXX perhaps __unctrl should be expanded to include
538                      * XXXX the keysyms in the table....
539                      */
540                     __CTRACE(__CTRACE_INPUT, "wget_wch assembled keysym 0x%x\n",
541                         inp);
542           else
543                     __CTRACE(__CTRACE_INPUT, "wget_wch got '%s'\n", unctrl(inp));
544 #endif
545           if (win->delay > -1) {
546                     if (__delay() == ERR)
547                               return ERR;
548           }
549 
550           __restore_termios();
551 
552           if (__echoit) {
553                     if ( ret == KEY_CODE_YES ) {
554                               /* handle [DEL], [BS], and [LEFT] */
555                               if ( win->curx &&
556                                                   ( inp == KEY_DC ||
557                                                     inp == KEY_BACKSPACE ||
558                                                     inp == KEY_LEFT )) {
559                                         wmove( win, win->cury, win->curx - 1);
560                                         wdelch( win );
561                               }
562                     } else {
563                               ws[ 0 ] = inp, ws[ 1 ] = L'\0';
564                               setcchar( &wc, ws, win->wattr, 0, NULL );
565                               wadd_wch( win, &wc );
566                     }
567           }
568 
569           if (weset)
570                     nocbreak();
571 
572           if (_cursesi_screen->nl && inp == 13)
573                     inp = 10;
574 
575           *ch = inp;
576 
577           if ( ret == KEY_CODE_YES )
578                     return KEY_CODE_YES;
579           return inp < 0 ? ERR : OK;
580 }
581 
582 /*
583  * unget_wch --
584  *         Put the wide character back into the input queue.
585  */
586 int
unget_wch(const wchar_t c)587 unget_wch(const wchar_t c)
588 {
589           return __unget((wint_t)c);
590 }
591 
592 /*
593  * __fgetwc_resize --
594  *    Any call to fgetwc(3) should use this function instead.
595  */
596 static wint_t
__fgetwc_resize(FILE * infd,bool * resized)597 __fgetwc_resize(FILE *infd, bool *resized)
598 {
599           wint_t c;
600 
601           c = fgetwc(infd);
602           if (c != WEOF)
603                     return c;
604 
605           if (!ferror(infd) || errno != EINTR || !_cursesi_screen->resized)
606                     return ERR;
607           __CTRACE(__CTRACE_INPUT, "__fgetwc_resize returning KEY_RESIZE\n");
608           resizeterm(LINES, COLS);
609           _cursesi_screen->resized = 0;
610           *resized = true;
611           return c;
612 }
613