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