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