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