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