get_wch.c revision 1.14
1/* $NetBSD: get_wch.c,v 1.14 2017/01/31 09:17:53 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.14 2017/01/31 09:17:53 roy 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 51static short wstate; /* state of the wcinkey function */ 52#endif /* HAVE_WCHAR */ 53extern short state; /* storage declared in getch.c */ 54 55/* prototypes for private functions */ 56#ifdef HAVE_WCHAR 57static 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 */ 66void 67__init_get_wch(SCREEN *screen) 68{ 69 wstate = INKEY_NORM; 70 memset(&screen->cbuf, 0, sizeof(screen->cbuf)); 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 */ 82static int 83inkey(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 = (*start +1) % MAX_CBUF_SIZE; 223 if (*start == *end) { 224 state = wstate = INKEY_NORM; 225#ifdef DEBUG 226 __CTRACE(__CTRACE_INPUT, 227 "inkey: WCASSEMBLING=>NORM, " 228 "start(%d), current(%d), end(%d)", 229 *start, *working, *end); 230#endif /* DEBUG */ 231 } else { 232 state = wstate = INKEY_BACKOUT; 233#ifdef DEBUG 234 __CTRACE(__CTRACE_INPUT, 235 "inkey: WCASSEMBLING=>BACKOUT, " 236 "start(%d), current(%d), end(%d)", 237 *start, *working, *end); 238#endif /* DEBUG */ 239 } 240 return OK; 241 } else { 242 /* assembling wide characters */ 243 inbuf[*end] = k; 244 *working = *end; 245 *end = (*end + 1) % MAX_CBUF_SIZE; 246#ifdef DEBUG 247 __CTRACE(__CTRACE_INPUT, 248 "inkey: WCASSEMBLING[head(%d), " 249 "urrent(%d), tail(%d)]\n", 250 *start, *working, *end); 251#endif /* DEBUG */ 252 ret = (int)mbrtowc(wc, inbuf + (*working), 1, 253 &_cursesi_screen->sp); 254#ifdef DEBUG 255 __CTRACE(__CTRACE_INPUT, 256 "inkey: mbrtowc returns %d, wc(%x)\n", 257 ret, *wc); 258#endif /* DEBUG */ 259 if (ret == -2) { 260 *working = (*working+1) % MAX_CBUF_SIZE; 261 continue; 262 } 263 if ( ret == 0 ) 264 ret = 1; 265 if ( ret == -1 ) { 266 /* return the 1st character we know */ 267 *wc = inbuf[*start]; 268 *working = *start = (*start + 1) % MAX_CBUF_SIZE; 269#ifdef DEBUG 270 __CTRACE(__CTRACE_INPUT, 271 "inkey: Invalid wide char(%x) " 272 "[head(%d), current(%d), " 273 "tail(%d)]\n", 274 *wc, *start, *working, *end); 275#endif /* DEBUG */ 276 } else { /* > 0 */ 277 /* return the wide character */ 278 *start = *working 279 = (*working + ret)%MAX_CBUF_SIZE; 280#ifdef DEBUG 281 __CTRACE(__CTRACE_INPUT, 282 "inkey: Wide char found(%x) " 283 "[head(%d), current(%d), " 284 "tail(%d)]\n", 285 *wc, *start, *working, *end); 286#endif /* DEBUG */ 287 } 288 289 if (*start == *end) { 290 /* only one char processed */ 291 state = wstate = INKEY_NORM; 292#ifdef DEBUG 293 __CTRACE(__CTRACE_INPUT, 294 "inkey: WCASSEMBLING=>NORM, " 295 "start(%d), current(%d), end(%d)", 296 *start, *working, *end); 297#endif /* DEBUG */ 298 } else { 299 /* otherwise we must have more than 300 * 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 { 326 /* wide-character specific code */ 327#ifdef DEBUG 328 __CTRACE(__CTRACE_INPUT, 329 "inkey: Checking for wide char\n"); 330#endif /* DEBUG */ 331 mbrtowc( NULL, NULL, 1, &_cursesi_screen->sp ); 332 *working = *start; 333 mlen = *end > *working ? 334 *end - *working : MAX_CBUF_SIZE - *working; 335 if (!mlen) 336 return ERR; 337#ifdef DEBUG 338 __CTRACE(__CTRACE_INPUT, 339 "inkey: Check wide char[head(%d), " 340 "current(%d), tail(%d), mlen(%ld)]\n", 341 *start, *working, *end, (long) mlen); 342#endif /* DEBUG */ 343 ret = (int)mbrtowc(wc, inbuf + (*working), mlen, 344 &_cursesi_screen->sp); 345#ifdef DEBUG 346 __CTRACE(__CTRACE_INPUT, 347 "inkey: mbrtowc returns %d, wc(%x)\n", ret, *wc); 348#endif /* DEBUG */ 349 if (ret == -2 && *end < *working) { 350 /* second half of a wide character */ 351 *working = 0; 352 mlen = *end; 353 if (mlen) 354 ret = (int)mbrtowc(wc, inbuf, mlen, 355 &_cursesi_screen->sp); 356 } 357 if (ret == -2 && wstate != INKEY_TIMEOUT) { 358 *working = (*working + (int) mlen) 359 % MAX_CBUF_SIZE; 360 wstate = INKEY_WCASSEMBLING; 361 continue; 362 } 363 if (ret == 0) 364 ret = 1; 365 if (ret == -1) { 366 /* return the first key we know about */ 367 *wc = inbuf[*start]; 368 *working = *start 369 = (*start + 1) % MAX_CBUF_SIZE; 370#ifdef DEBUG 371 __CTRACE(__CTRACE_INPUT, 372 "inkey: Invalid wide char(%x)[head(%d), " 373 "current(%d), tail(%d)]\n", 374 *wc, *start, *working, *end); 375#endif /* DEBUG */ 376 } else { /* > 0 */ 377 /* return the wide character */ 378 *start = *working 379 = (*working + ret) % MAX_CBUF_SIZE; 380#ifdef DEBUG 381 __CTRACE(__CTRACE_INPUT, 382 "inkey: Wide char found(%x)[head(%d), " 383 "current(%d), tail(%d)]\n", 384 *wc, *start, *working, *end); 385#endif /* DEBUG */ 386 } 387 388 if (*start == *end) { /* only one char processed */ 389 state = wstate = INKEY_NORM; 390#ifdef DEBUG 391 __CTRACE(__CTRACE_INPUT, 392 "inkey: Empty cbuf=>NORM, " 393 "start(%d), current(%d), end(%d)\n", 394 *start, *working, *end); 395#endif /* DEBUG */ 396 } else { 397 /* otherwise we must have more than one 398 * char to backout */ 399 state = wstate = INKEY_BACKOUT; 400#ifdef DEBUG 401 __CTRACE(__CTRACE_INPUT, 402 "inkey: Non-empty cbuf=>BACKOUT, " 403 "start(%d), current(%d), end(%d)\n", 404 *start, *working, *end); 405#endif /* DEBUG */ 406 } 407 return OK; 408 } else { /* must be part of a multikey sequence */ 409 /* check for completed key sequence */ 410 if (current->key[current->mapping[k]]->type 411 == KEYMAP_LEAF) { 412 /* eat the key sequence in cbuf */ 413 *start = *working = ( *working + 1 ) 414 % MAX_CBUF_SIZE; 415 416 /* check if inbuf empty now */ 417#ifdef DEBUG 418 __CTRACE(__CTRACE_INPUT, 419 "inkey: Key found(%s)\n", 420 key_name(current->key[mapping]->value.symbol)); 421#endif /* DEBUG */ 422 if (*start == *end) { 423 /* if it is go back to normal */ 424 state = wstate = INKEY_NORM; 425#ifdef DEBUG 426 __CTRACE(__CTRACE_INPUT, 427 "[inkey]=>NORM, start(%d), " 428 "current(%d), end(%d)", 429 *start, *working, *end); 430#endif /* DEBUG */ 431 } else { 432 /* otherwise go to backout state */ 433 state = wstate = INKEY_BACKOUT; 434#ifdef DEBUG 435 __CTRACE(__CTRACE_INPUT, 436 "[inkey]=>BACKOUT, start(%d), " 437 "current(%d), end(%d)", 438 *start, *working, *end ); 439#endif /* DEBUG */ 440 } 441 442 /* return the symbol */ 443 *wc = current->key[mapping]->value.symbol; 444 return KEY_CODE_YES; 445 } else { 446 /* Step to next part of multi-key sequence */ 447 current = current->key[current->mapping[k]]->value.next; 448 } 449 } 450 } 451} 452#endif /* HAVE_WCHAR */ 453 454/* 455 * get_wch -- 456 * Read in a wide character from stdscr. 457 */ 458int 459get_wch(wint_t *ch) 460{ 461#ifndef HAVE_WCHAR 462 return ERR; 463#else 464 return wget_wch(stdscr, ch); 465#endif /* HAVE_WCHAR */ 466} 467 468/* 469 * mvget_wch -- 470 * Read in a character from stdscr at the given location. 471 */ 472int 473mvget_wch(int y, int x, wint_t *ch) 474{ 475#ifndef HAVE_WCHAR 476 return ERR; 477#else 478 return mvwget_wch(stdscr, y, x, ch); 479#endif /* HAVE_WCHAR */ 480} 481 482/* 483 * mvwget_wch -- 484 * Read in a character from stdscr at the given location in the 485 * given window. 486 */ 487int 488mvwget_wch(WINDOW *win, int y, int x, wint_t *ch) 489{ 490#ifndef HAVE_WCHAR 491 return ERR; 492#else 493 if (wmove(win, y, x) == ERR) 494 return ERR; 495 496 return wget_wch(win, ch); 497#endif /* HAVE_WCHAR */ 498} 499 500/* 501 * wget_wch -- 502 * Read in a wide character from the window. 503 */ 504int 505wget_wch(WINDOW *win, wint_t *ch) 506{ 507#ifndef HAVE_WCHAR 508 return ERR; 509#else 510 int ret, weset; 511 int c; 512 FILE *infd = _cursesi_screen->infd; 513 cchar_t wc; 514 wchar_t inp, ws[2]; 515 516 if (!(win->flags & __SCROLLOK) 517 && (win->flags & __FULLWIN) 518 && win->curx == win->maxx - 1 519 && win->cury == win->maxy - 1 520 && __echoit) 521 return ERR; 522 523 if (is_wintouched(win)) 524 wrefresh(win); 525#ifdef DEBUG 526 __CTRACE(__CTRACE_INPUT, "wget_wch: __echoit = %d, " 527 "__rawmode = %d, __nl = %d, flags = %#.4x\n", 528 __echoit, __rawmode, _cursesi_screen->nl, win->flags); 529#endif 530 if (_cursesi_screen->resized) { 531 _cursesi_screen->resized = 0; 532 *ch = KEY_RESIZE; 533 return KEY_CODE_YES; 534 } 535 if (_cursesi_screen->unget_pos) { 536#ifdef DEBUG 537 __CTRACE(__CTRACE_INPUT, "wget_wch returning char at %d\n", 538 _cursesi_screen->unget_pos); 539#endif 540 _cursesi_screen->unget_pos--; 541 *ch = _cursesi_screen->unget_list[_cursesi_screen->unget_pos]; 542 if (__echoit) { 543 ws[0] = *ch, ws[1] = L'\0'; 544 setcchar(&wc, ws, win->wattr, 0, NULL); 545 wadd_wch(win, &wc); 546 } 547 return KEY_CODE_YES; 548 } 549 if (__echoit && !__rawmode) { 550 cbreak(); 551 weset = 1; 552 } else 553 weset = 0; 554 555 __save_termios(); 556 557 if (win->flags & __KEYPAD) { 558 switch (win->delay) { 559 case -1: 560 ret = inkey(&inp, 561 win->flags & __NOTIMEOUT ? 0 : 1, 0); 562 break; 563 case 0: 564 if (__nodelay() == ERR) 565 return ERR; 566 ret = inkey(&inp, 0, 0); 567 break; 568 default: 569 ret = inkey(&inp, 570 win->flags & __NOTIMEOUT ? 0 : 1, 571 win->delay); 572 break; 573 } 574 if ( ret == ERR ) 575 return ERR; 576 } else { 577 switch (win->delay) { 578 case -1: 579 break; 580 case 0: 581 if (__nodelay() == ERR) 582 return ERR; 583 break; 584 default: 585 if (__timeout(win->delay) == ERR) 586 return ERR; 587 break; 588 } 589 590 c = getwchar(); 591 if (feof(infd)) { 592 clearerr(infd); 593 __restore_termios(); 594 return ERR; /* we have timed out */ 595 } 596 597 if (ferror(infd)) { 598 clearerr(infd); 599 return ERR; 600 } else { 601 ret = c; 602 inp = c; 603 } 604 } 605#ifdef DEBUG 606 if (inp > 255) 607 /* we have a key symbol - treat it differently */ 608 /* XXXX perhaps __unctrl should be expanded to include 609 * XXXX the keysyms in the table.... 610 */ 611 __CTRACE(__CTRACE_INPUT, "wget_wch assembled keysym 0x%x\n", 612 inp); 613 else 614 __CTRACE(__CTRACE_INPUT, "wget_wch got '%s'\n", unctrl(inp)); 615#endif 616 if (win->delay > -1) { 617 if (__delay() == ERR) 618 return ERR; 619 } 620 621 __restore_termios(); 622 623 if (__echoit) { 624 if ( ret == KEY_CODE_YES ) { 625 /* handle [DEL], [BS], and [LEFT] */ 626 if ( win->curx && 627 ( inp == KEY_DC || 628 inp == KEY_BACKSPACE || 629 inp == KEY_LEFT )) { 630 wmove( win, win->cury, win->curx - 1 ); 631 wdelch( win ); 632 } 633 } else { 634 ws[ 0 ] = inp, ws[ 1 ] = L'\0'; 635 setcchar( &wc, ws, win->wattr, 0, NULL ); 636 wadd_wch( win, &wc ); 637 } 638 } 639 640 if (weset) 641 nocbreak(); 642 643 if (_cursesi_screen->nl && inp == 13) 644 inp = 10; 645 646 *ch = inp; 647 648 if ( ret == KEY_CODE_YES ) 649 return KEY_CODE_YES; 650 return inp < 0 ? ERR : OK; 651#endif /* HAVE_WCHAR */ 652} 653 654/* 655 * unget_wch -- 656 * Put the wide character back into the input queue. 657 */ 658int 659unget_wch(const wchar_t c) 660{ 661 return __unget((wint_t)c); 662} 663