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 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 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 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 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 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 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 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 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