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