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