1 /* $OpenBSD: lib_getch.c,v 1.11 2010/01/12 23:22:05 nicm Exp $ */ 2 3 /**************************************************************************** 4 * Copyright (c) 1998-2007,2008 Free Software Foundation, Inc. * 5 * * 6 * Permission is hereby granted, free of charge, to any person obtaining a * 7 * copy of this software and associated documentation files (the * 8 * "Software"), to deal in the Software without restriction, including * 9 * without limitation the rights to use, copy, modify, merge, publish, * 10 * distribute, distribute with modifications, sublicense, and/or sell * 11 * copies of the Software, and to permit persons to whom the Software is * 12 * furnished to do so, subject to the following conditions: * 13 * * 14 * The above copyright notice and this permission notice shall be included * 15 * in all copies or substantial portions of the Software. * 16 * * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 18 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 20 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 21 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 22 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 23 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 24 * * 25 * Except as contained in this notice, the name(s) of the above copyright * 26 * holders shall not be used in advertising or otherwise to promote the * 27 * sale, use or other dealings in this Software without prior written * 28 * authorization. * 29 ****************************************************************************/ 30 31 /**************************************************************************** 32 * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 * 33 * and: Eric S. Raymond <esr@snark.thyrsus.com> * 34 * and: Thomas E. Dickey 1996-on * 35 ****************************************************************************/ 36 37 /* 38 ** lib_getch.c 39 ** 40 ** The routine getch(). 41 ** 42 */ 43 44 #include <curses.priv.h> 45 46 MODULE_ID("$Id: lib_getch.c,v 1.11 2010/01/12 23:22:05 nicm Exp $") 47 48 #include <fifo_defs.h> 49 50 #if USE_REENTRANT 51 #define GetEscdelay(sp) (sp)->_ESCDELAY 52 NCURSES_EXPORT(int) 53 NCURSES_PUBLIC_VAR(ESCDELAY) (void) 54 { 55 return SP ? GetEscdelay(SP) : 1000; 56 } 57 #else 58 #define GetEscdelay(sp) ESCDELAY 59 NCURSES_EXPORT_VAR(int) 60 ESCDELAY = 1000; /* max interval betw. chars in funkeys, in millisecs */ 61 #endif 62 63 #if NCURSES_EXT_FUNCS 64 NCURSES_EXPORT(int) 65 set_escdelay(int value) 66 { 67 int code = OK; 68 #if USE_REENTRANT 69 if (SP) { 70 SP->_ESCDELAY = value; 71 } else { 72 code = ERR; 73 } 74 #else 75 ESCDELAY = value; 76 #endif 77 return code; 78 } 79 #endif 80 81 static int 82 _nc_use_meta(WINDOW *win) 83 { 84 SCREEN *sp = _nc_screen_of(win); 85 return (sp ? sp->_use_meta : 0); 86 } 87 88 #ifdef NCURSES_WGETCH_EVENTS 89 #define TWAIT_MASK 7 90 #else 91 #define TWAIT_MASK 3 92 #endif 93 94 /* 95 * Check for mouse activity, returning nonzero if we find any. 96 */ 97 static int 98 check_mouse_activity(SCREEN *sp, int delay EVENTLIST_2nd(_nc_eventlist * evl)) 99 { 100 int rc; 101 102 #if USE_SYSMOUSE 103 if ((sp->_mouse_type == M_SYSMOUSE) 104 && (sp->_sysmouse_head < sp->_sysmouse_tail)) { 105 return 2; 106 } 107 #endif 108 rc = _nc_timed_wait(sp, TWAIT_MASK, delay, (int *) 0 EVENTLIST_2nd(evl)); 109 #if USE_SYSMOUSE 110 if ((sp->_mouse_type == M_SYSMOUSE) 111 && (sp->_sysmouse_head < sp->_sysmouse_tail) 112 && (rc == 0) 113 && (errno == EINTR)) { 114 rc |= 2; 115 } 116 #endif 117 return rc; 118 } 119 120 static NCURSES_INLINE int 121 fifo_peek(SCREEN *sp) 122 { 123 int ch = sp->_fifo[peek]; 124 TR(TRACE_IEVENT, ("peeking at %d", peek)); 125 126 p_inc(); 127 return ch; 128 } 129 130 static NCURSES_INLINE int 131 fifo_pull(SCREEN *sp) 132 { 133 int ch; 134 ch = sp->_fifo[head]; 135 TR(TRACE_IEVENT, ("pulling %s from %d", _nc_tracechar(sp, ch), head)); 136 137 if (peek == head) { 138 h_inc(); 139 peek = head; 140 } else 141 h_inc(); 142 143 #ifdef TRACE 144 if (USE_TRACEF(TRACE_IEVENT)) { 145 _nc_fifo_dump(sp); 146 _nc_unlock_global(tracef); 147 } 148 #endif 149 return ch; 150 } 151 152 static NCURSES_INLINE int 153 fifo_push(SCREEN *sp EVENTLIST_2nd(_nc_eventlist * evl)) 154 { 155 int n; 156 int ch = 0; 157 int mask = 0; 158 159 (void) mask; 160 if (tail == -1) 161 return ERR; 162 163 #ifdef HIDE_EINTR 164 again: 165 errno = 0; 166 #endif 167 168 #ifdef NCURSES_WGETCH_EVENTS 169 if (evl 170 #if USE_GPM_SUPPORT || USE_EMX_MOUSE || USE_SYSMOUSE 171 || (sp->_mouse_fd >= 0) 172 #endif 173 ) { 174 mask = check_mouse_activity(sp, -1 EVENTLIST_2nd(evl)); 175 } else 176 mask = 0; 177 178 if (mask & 4) { 179 T(("fifo_push: ungetch KEY_EVENT")); 180 _nc_ungetch(sp, KEY_EVENT); 181 return KEY_EVENT; 182 } 183 #elif USE_GPM_SUPPORT || USE_EMX_MOUSE || USE_SYSMOUSE 184 if (sp->_mouse_fd >= 0) { 185 mask = check_mouse_activity(sp, -1 EVENTLIST_2nd(evl)); 186 } 187 #endif 188 189 #if USE_GPM_SUPPORT || USE_EMX_MOUSE 190 if ((sp->_mouse_fd >= 0) && (mask & 2)) { 191 sp->_mouse_event(sp); 192 ch = KEY_MOUSE; 193 n = 1; 194 } else 195 #endif 196 #if USE_SYSMOUSE 197 if ((sp->_mouse_type == M_SYSMOUSE) 198 && (sp->_sysmouse_head < sp->_sysmouse_tail)) { 199 sp->_mouse_event(sp); 200 ch = KEY_MOUSE; 201 n = 1; 202 } else if ((sp->_mouse_type == M_SYSMOUSE) 203 && (mask <= 0) && errno == EINTR) { 204 sp->_mouse_event(sp); 205 ch = KEY_MOUSE; 206 n = 1; 207 } else 208 #endif 209 { /* Can block... */ 210 unsigned char c2 = 0; 211 n = read(sp->_ifd, &c2, 1); 212 ch = c2; 213 } 214 215 #ifdef HIDE_EINTR 216 /* 217 * Under System V curses with non-restarting signals, getch() returns 218 * with value ERR when a handled signal keeps it from completing. 219 * If signals restart system calls, OTOH, the signal is invisible 220 * except to its handler. 221 * 222 * We don't want this difference to show. This piece of code 223 * tries to make it look like we always have restarting signals. 224 */ 225 if (n <= 0 && errno == EINTR) 226 goto again; 227 #endif 228 229 if ((n == -1) || (n == 0)) { 230 TR(TRACE_IEVENT, ("read(%d,&ch,1)=%d, errno=%d", sp->_ifd, n, errno)); 231 ch = ERR; 232 } 233 TR(TRACE_IEVENT, ("read %d characters", n)); 234 235 sp->_fifo[tail] = ch; 236 sp->_fifohold = 0; 237 if (head == -1) 238 head = peek = tail; 239 t_inc(); 240 TR(TRACE_IEVENT, ("pushed %s at %d", _nc_tracechar(sp, ch), tail)); 241 #ifdef TRACE 242 if (USE_TRACEF(TRACE_IEVENT)) { 243 _nc_fifo_dump(sp); 244 _nc_unlock_global(tracef); 245 } 246 #endif 247 return ch; 248 } 249 250 static NCURSES_INLINE void 251 fifo_clear(SCREEN *sp) 252 { 253 memset(sp->_fifo, 0, sizeof(sp->_fifo)); 254 head = -1; 255 tail = peek = 0; 256 } 257 258 static int kgetch(SCREEN *EVENTLIST_2nd(_nc_eventlist * evl)); 259 260 static void 261 recur_wrefresh(WINDOW *win) 262 { 263 #ifdef USE_PTHREADS 264 SCREEN *sp = _nc_screen_of(win); 265 if (_nc_use_pthreads && sp != SP) { 266 SCREEN *save_SP; 267 268 /* temporarily switch to the window's screen to check/refresh */ 269 _nc_lock_global(curses); 270 save_SP = SP; 271 _nc_set_screen(sp); 272 recur_wrefresh(win); 273 _nc_set_screen(save_SP); 274 _nc_unlock_global(curses); 275 } else 276 #endif 277 if ((is_wintouched(win) || (win->_flags & _HASMOVED)) 278 && !(win->_flags & _ISPAD)) { 279 wrefresh(win); 280 } 281 } 282 283 static int 284 recur_wgetnstr(WINDOW *win, char *buf) 285 { 286 SCREEN *sp = _nc_screen_of(win); 287 int rc; 288 289 if (sp != 0) { 290 #ifdef USE_PTHREADS 291 if (_nc_use_pthreads && sp != SP) { 292 SCREEN *save_SP; 293 294 /* temporarily switch to the window's screen to get cooked input */ 295 _nc_lock_global(curses); 296 save_SP = SP; 297 _nc_set_screen(sp); 298 rc = recur_wgetnstr(win, buf); 299 _nc_set_screen(save_SP); 300 _nc_unlock_global(curses); 301 } else 302 #endif 303 { 304 sp->_called_wgetch = TRUE; 305 rc = wgetnstr(win, buf, MAXCOLUMNS); 306 sp->_called_wgetch = FALSE; 307 } 308 } else { 309 rc = ERR; 310 } 311 return rc; 312 } 313 314 NCURSES_EXPORT(int) 315 _nc_wgetch(WINDOW *win, 316 unsigned long *result, 317 int use_meta 318 EVENTLIST_2nd(_nc_eventlist * evl)) 319 { 320 SCREEN *sp; 321 int ch; 322 #ifdef NCURSES_WGETCH_EVENTS 323 long event_delay = -1; 324 #endif 325 326 T((T_CALLED("_nc_wgetch(%p)"), win)); 327 328 *result = 0; 329 330 sp = _nc_screen_of(win); 331 if (win == 0 || sp == 0) { 332 returnCode(ERR); 333 } 334 335 if (cooked_key_in_fifo()) { 336 recur_wrefresh(win); 337 *result = fifo_pull(sp); 338 returnCode(*result >= KEY_MIN ? KEY_CODE_YES : OK); 339 } 340 #ifdef NCURSES_WGETCH_EVENTS 341 if (evl && (evl->count == 0)) 342 evl = NULL; 343 event_delay = _nc_eventlist_timeout(evl); 344 #endif 345 346 /* 347 * Handle cooked mode. Grab a string from the screen, 348 * stuff its contents in the FIFO queue, and pop off 349 * the first character to return it. 350 */ 351 if (head == -1 && 352 !sp->_notty && 353 !sp->_raw && 354 !sp->_cbreak && 355 !sp->_called_wgetch) { 356 char buf[MAXCOLUMNS], *bufp; 357 int rc; 358 359 TR(TRACE_IEVENT, ("filling queue in cooked mode")); 360 361 rc = recur_wgetnstr(win, buf); 362 363 /* ungetch in reverse order */ 364 #ifdef NCURSES_WGETCH_EVENTS 365 if (rc != KEY_EVENT) 366 #endif 367 _nc_ungetch(sp, '\n'); 368 for (bufp = buf + strlen(buf); bufp > buf; bufp--) 369 _nc_ungetch(sp, bufp[-1]); 370 371 #ifdef NCURSES_WGETCH_EVENTS 372 /* Return it first */ 373 if (rc == KEY_EVENT) { 374 *result = rc; 375 } else 376 #endif 377 *result = fifo_pull(sp); 378 returnCode(*result >= KEY_MIN ? KEY_CODE_YES : OK); 379 } 380 381 if (win->_use_keypad != sp->_keypad_on) 382 _nc_keypad(sp, win->_use_keypad); 383 384 recur_wrefresh(win); 385 386 if (win->_notimeout || (win->_delay >= 0) || (sp->_cbreak > 1)) { 387 if (head == -1) { /* fifo is empty */ 388 int delay; 389 int rc; 390 391 TR(TRACE_IEVENT, ("timed delay in wgetch()")); 392 if (sp->_cbreak > 1) 393 delay = (sp->_cbreak - 1) * 100; 394 else 395 delay = win->_delay; 396 397 #ifdef NCURSES_WGETCH_EVENTS 398 if (event_delay >= 0 && delay > event_delay) 399 delay = event_delay; 400 #endif 401 402 TR(TRACE_IEVENT, ("delay is %d milliseconds", delay)); 403 404 rc = check_mouse_activity(sp, delay EVENTLIST_2nd(evl)); 405 406 #ifdef NCURSES_WGETCH_EVENTS 407 if (rc & 4) { 408 *result = KEY_EVENT; 409 returnCode(KEY_CODE_YES); 410 } 411 #endif 412 if (!rc) { 413 returnCode(ERR); 414 } 415 } 416 /* else go on to read data available */ 417 } 418 419 if (win->_use_keypad) { 420 /* 421 * This is tricky. We only want to get special-key 422 * events one at a time. But we want to accumulate 423 * mouse events until either (a) the mouse logic tells 424 * us it's picked up a complete gesture, or (b) 425 * there's a detectable time lapse after one. 426 * 427 * Note: if the mouse code starts failing to compose 428 * press/release events into clicks, you should probably 429 * increase the wait with mouseinterval(). 430 */ 431 int runcount = 0; 432 int rc; 433 434 do { 435 ch = kgetch(sp EVENTLIST_2nd(evl)); 436 if (ch == KEY_MOUSE) { 437 ++runcount; 438 if (sp->_mouse_inline(sp)) 439 break; 440 } 441 if (sp->_maxclick < 0) 442 break; 443 } while 444 (ch == KEY_MOUSE 445 && (((rc = check_mouse_activity(sp, sp->_maxclick 446 EVENTLIST_2nd(evl))) != 0 447 && !(rc & 4)) 448 || !sp->_mouse_parse(sp, runcount))); 449 #ifdef NCURSES_WGETCH_EVENTS 450 if ((rc & 4) && !ch == KEY_EVENT) { 451 _nc_ungetch(sp, ch); 452 ch = KEY_EVENT; 453 } 454 #endif 455 if (runcount > 0 && ch != KEY_MOUSE) { 456 #ifdef NCURSES_WGETCH_EVENTS 457 /* mouse event sequence ended by an event, report event */ 458 if (ch == KEY_EVENT) { 459 _nc_ungetch(sp, KEY_MOUSE); /* FIXME This interrupts a gesture... */ 460 } else 461 #endif 462 { 463 /* mouse event sequence ended by keystroke, store keystroke */ 464 _nc_ungetch(sp, ch); 465 ch = KEY_MOUSE; 466 } 467 } 468 } else { 469 if (head == -1) 470 fifo_push(sp EVENTLIST_2nd(evl)); 471 ch = fifo_pull(sp); 472 } 473 474 if (ch == ERR) { 475 #if USE_SIZECHANGE 476 if (_nc_handle_sigwinch(sp)) { 477 _nc_update_screensize(sp); 478 /* resizeterm can push KEY_RESIZE */ 479 if (cooked_key_in_fifo()) { 480 *result = fifo_pull(sp); 481 returnCode(*result >= KEY_MIN ? KEY_CODE_YES : OK); 482 } 483 } 484 #endif 485 returnCode(ERR); 486 } 487 488 /* 489 * If echo() is in effect, display the printable version of the 490 * key on the screen. Carriage return and backspace are treated 491 * specially by Solaris curses: 492 * 493 * If carriage return is defined as a function key in the 494 * terminfo, e.g., kent, then Solaris may return either ^J (or ^M 495 * if nonl() is set) or KEY_ENTER depending on the echo() mode. 496 * We echo before translating carriage return based on nonl(), 497 * since the visual result simply moves the cursor to column 0. 498 * 499 * Backspace is a different matter. Solaris curses does not 500 * translate it to KEY_BACKSPACE if kbs=^H. This does not depend 501 * on the stty modes, but appears to be a hardcoded special case. 502 * This is a difference from ncurses, which uses the terminfo entry. 503 * However, we provide the same visual result as Solaris, moving the 504 * cursor to the left. 505 */ 506 if (sp->_echo && !(win->_flags & _ISPAD)) { 507 chtype backup = (ch == KEY_BACKSPACE) ? '\b' : ch; 508 if (backup < KEY_MIN) 509 wechochar(win, backup); 510 } 511 512 /* 513 * Simulate ICRNL mode 514 */ 515 if ((ch == '\r') && sp->_nl) 516 ch = '\n'; 517 518 /* Strip 8th-bit if so desired. We do this only for characters that 519 * are in the range 128-255, to provide compatibility with terminals 520 * that display only 7-bit characters. Note that 'ch' may be a 521 * function key at this point, so we mustn't strip _those_. 522 */ 523 if (!use_meta) 524 if ((ch < KEY_MIN) && (ch & 0x80)) 525 ch &= 0x7f; 526 527 T(("wgetch returning : %s", _nc_tracechar(sp, ch))); 528 529 *result = ch; 530 returnCode(ch >= KEY_MIN ? KEY_CODE_YES : OK); 531 } 532 533 #ifdef NCURSES_WGETCH_EVENTS 534 NCURSES_EXPORT(int) 535 wgetch_events(WINDOW *win, _nc_eventlist * evl) 536 { 537 int code; 538 unsigned long value; 539 540 T((T_CALLED("wgetch_events(%p,%p)"), win, evl)); 541 code = _nc_wgetch(win, 542 &value, 543 _nc_use_meta(win) 544 EVENTLIST_2nd(evl)); 545 if (code != ERR) 546 code = value; 547 returnCode(code); 548 } 549 #endif 550 551 NCURSES_EXPORT(int) 552 wgetch(WINDOW *win) 553 { 554 int code; 555 unsigned long value; 556 557 T((T_CALLED("wgetch(%p)"), win)); 558 code = _nc_wgetch(win, 559 &value, 560 _nc_use_meta(win) 561 EVENTLIST_2nd((_nc_eventlist *) 0)); 562 if (code != ERR) 563 code = value; 564 returnCode(code); 565 } 566 567 /* 568 ** int 569 ** kgetch() 570 ** 571 ** Get an input character, but take care of keypad sequences, returning 572 ** an appropriate code when one matches the input. After each character 573 ** is received, set an alarm call based on ESCDELAY. If no more of the 574 ** sequence is received by the time the alarm goes off, pass through 575 ** the sequence gotten so far. 576 ** 577 ** This function must be called when there are no cooked keys in queue. 578 ** (that is head==-1 || peek==head) 579 ** 580 */ 581 582 static int 583 kgetch(SCREEN *sp EVENTLIST_2nd(_nc_eventlist * evl)) 584 { 585 TRIES *ptr; 586 int ch = 0; 587 int timeleft = GetEscdelay(sp); 588 589 TR(TRACE_IEVENT, ("kgetch() called")); 590 591 ptr = sp->_keytry; 592 593 for (;;) { 594 if (cooked_key_in_fifo() && sp->_fifo[head] >= KEY_MIN) { 595 break; 596 } else if (!raw_key_in_fifo()) { 597 ch = fifo_push(sp EVENTLIST_2nd(evl)); 598 if (ch == ERR) { 599 peek = head; /* the keys stay uninterpreted */ 600 return ERR; 601 } 602 #ifdef NCURSES_WGETCH_EVENTS 603 else if (ch == KEY_EVENT) { 604 peek = head; /* the keys stay uninterpreted */ 605 return fifo_pull(sp); /* Remove KEY_EVENT from the queue */ 606 } 607 #endif 608 } 609 610 ch = fifo_peek(sp); 611 if (ch >= KEY_MIN) { 612 /* If not first in queue, somebody put this key there on purpose in 613 * emergency. Consider it higher priority than the unfinished 614 * keysequence we are parsing. 615 */ 616 peek = head; 617 /* assume the key is the last in fifo */ 618 t_dec(); /* remove the key */ 619 return ch; 620 } 621 622 TR(TRACE_IEVENT, ("ch: %s", _nc_tracechar(sp, (unsigned char) ch))); 623 while ((ptr != NULL) && (ptr->ch != (unsigned char) ch)) 624 ptr = ptr->sibling; 625 626 if (ptr == NULL) { 627 TR(TRACE_IEVENT, ("ptr is null")); 628 break; 629 } 630 TR(TRACE_IEVENT, ("ptr=%p, ch=%d, value=%d", 631 ptr, ptr->ch, ptr->value)); 632 633 if (ptr->value != 0) { /* sequence terminated */ 634 TR(TRACE_IEVENT, ("end of sequence")); 635 if (peek == tail) 636 fifo_clear(sp); 637 else 638 head = peek; 639 return (ptr->value); 640 } 641 642 ptr = ptr->child; 643 644 if (!raw_key_in_fifo()) { 645 int rc; 646 647 TR(TRACE_IEVENT, ("waiting for rest of sequence")); 648 rc = check_mouse_activity(sp, timeleft EVENTLIST_2nd(evl)); 649 #ifdef NCURSES_WGETCH_EVENTS 650 if (rc & 4) { 651 TR(TRACE_IEVENT, ("interrupted by a user event")); 652 /* FIXME Should have preserved remainder timeleft for reuse... */ 653 peek = head; /* Restart interpreting later */ 654 return KEY_EVENT; 655 } 656 #endif 657 if (!rc) { 658 TR(TRACE_IEVENT, ("ran out of time")); 659 break; 660 } 661 } 662 } 663 ch = fifo_pull(sp); 664 peek = head; 665 return ch; 666 } 667