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