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