1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* Copyright (c) 1988 AT&T */ 27 /* All Rights Reserved */ 28 29 /* 30 * University Copyright- Copyright (c) 1982, 1986, 1988 31 * The Regents of the University of California 32 * All Rights Reserved 33 * 34 * University Acknowledgment- Portions of this document are derived from 35 * software developed by the University of California, Berkeley, and its 36 * contributors. 37 */ 38 39 /*LINTLIBRARY*/ 40 41 #include "curses_inc.h" 42 #include <signal.h> 43 #include <unistd.h> 44 #ifdef DEBUG 45 #include <ctype.h> 46 #endif /* DEBUG */ 47 48 /* 49 * Read a key typed from the terminal 50 * 51 * interpret: = 0 for single-char key only 52 * = 1 for matching function key and macro patterns. 53 * = 2 same as 1 but no time-out for funckey matching. 54 */ 55 56 static int _getkey(int, chtype *); 57 static int _fpk(void); 58 static int _pk(void); 59 60 chtype 61 tgetch(int interpret) 62 { 63 int i = 0, j, collapse = 1; 64 #define WAIT3 333 65 chtype inp; 66 chtype *inputQ = cur_term->_input_queue; 67 char *chars_onQ = &(cur_term->_chars_on_queue); 68 69 #ifdef SYSV 70 /* 71 * Register the fact that getch is being used so 72 * that typeahead checking can be done. 73 * This code should GO AWAY when a poll() or FIONREAD can 74 * be done on the file descriptor as then the check 75 * will be non-destructive. 76 */ 77 cur_term->fl_typeahdok = TRUE; 78 #endif /* SYSV */ 79 80 /* ask for input */ 81 if (cur_term->_ungotten > 0) { 82 cur_term->_ungotten--; 83 /* decode an ungetch()'d character */ 84 inp = -inputQ[0]; 85 } else { 86 /* Only read a character if there is no typeahead/peekahead. */ 87 if (*chars_onQ == 0) { 88 /* (*chars_onQ)++; MR */ 89 #ifdef FIONREAD 90 inp = _readchar(); 91 #else /* FIONREAD */ 92 inp = (chtype) _pk(); 93 if ((int)inp == ERR) { 94 /* 95 * interpret is set to 0 so that down below we don't 96 * drop into getkey since we already know there can't be 97 * a key that starts with -1. Also, we don't want to 98 * access funckeystarter[-1]. 99 */ 100 interpret = FALSE; 101 } 102 #endif /* FIONREAD */ 103 (*chars_onQ)++; 104 } else 105 inp = inputQ[0]; 106 107 #ifdef DEBUG 108 if (outf) 109 fprintf(outf, "TGETCH read '%s'\n", unctrl(inp)); 110 #endif /* DEBUG */ 111 112 /* Check for arrow and function keys */ 113 if (interpret && cur_term->funckeystarter[inp]) 114 collapse = _getkey(interpret - 1, &inp); 115 } 116 117 /* Collapse the input queue to remove the escape */ 118 /* sequence from the stack. */ 119 120 j = *chars_onQ; 121 (*chars_onQ) -= collapse; 122 while (collapse < j) 123 inputQ[i++] = inputQ[collapse++]; 124 return (inp); 125 } 126 127 #ifdef FIONREAD 128 static int 129 _readchar() 130 { 131 int i; 132 unsigned char c; 133 134 if (cur_term->_delay == 0) { 135 int arg; 136 137 (void) ioctl(cur_term->_inputfd, FIONREAD, &arg); 138 #ifdef DEBUG 139 if (outf) 140 fprintf(outf, "FIONREAD returns %d\n", arg); 141 #endif /* DEBUG */ 142 if (arg < 1) 143 return (-1); 144 } else 145 if (cur_term->_delay > 0) { 146 char c; 147 int infd; 148 149 infd = 1 << cur_term->_inputfd; 150 t.tv_sec = cur_term->_delay / 1000; 151 t.tv_usec = (cur_term->_delay % 1000) * 1000; 152 i = select(20, &infd, (int *)NULL, (int *)NULL, &t); 153 if (i < 0) 154 return (ERR); 155 i = read(cur_term->_inputfd, &c, 1); 156 } else 157 i = read(cur_term->_inputfd, &c, 1); 158 159 #ifdef DEBUG 160 if (outf) 161 fprintf(outf, "read from %d returns %d chars, first %o\n", 162 cur_term->_inputfd, i, c); 163 #endif /* DEBUG */ 164 165 if (i > 0) 166 return (c); 167 else 168 return (ERR); 169 } 170 #endif /* !FIONREAD */ 171 172 #ifdef DEBUG 173 extern char *_asciify(); 174 #endif /* DEBUG */ 175 176 static int get_xterm_mouse(int, int *); 177 static void _map_button(chtype *); 178 179 /* 180 * This algorithm is a "learning" algorithm. The premise is 181 * that keys used once are like to be used again and again. 182 * Since the time for a linear search of the table is so 183 * expensive, we move keys that are found up to the top of 184 * the list, making the access to a repeated key very fast and 185 * keys that have been used before close to the top. 186 */ 187 188 static int 189 _getkey(int blockpeek, chtype *inp) 190 { 191 _KEY_MAP **kp = cur_term->_keys; 192 int key, num_keys = cur_term->_ksz; 193 int i; 194 chtype *inputQ = cur_term->_input_queue; 195 char *chars_onQ = &(cur_term->_chars_on_queue); 196 char flag = cur_term->funckeystarter[*inp]; 197 int first, collapse = 1; 198 199 200 #ifdef DEBUG 201 if (outf) 202 fprintf(outf, "getkey(): looking in linear table, " 203 "inp=%d\n", *inp); 204 #endif /* DEBUG */ 205 206 if (flag & _KEY) 207 key = 0; 208 else { 209 key = cur_term->_first_macro; 210 blockpeek = TRUE; 211 } 212 first = key; 213 214 for (; key < num_keys; key++) { 215 if (kp[key]->_sends[0] == *inp) { 216 for (i = 1; i < INP_QSIZE; i++) { 217 /* found it? */ 218 if (kp[key]->_sends[i] == '\0') 219 break; 220 /* partial match? peek ahead. */ 221 if (*chars_onQ == i) { 222 (*chars_onQ)++; 223 inputQ[i] = (blockpeek) ? 224 _pk() : _fpk(); 225 switch ((int)inputQ[i]) { 226 case -2: 227 /* 228 * Since -2 signifies a timeout we don't really 229 * want to put it on the queue so we decrement 230 * our counter. 231 */ 232 (*chars_onQ)--; 233 #ifdef DEBUG 234 if (outf) 235 fprintf(outf, "Timed out\n"); 236 #endif /* DEBUG */ 237 if (flag & _MACRO) { 238 #ifdef DEBUG 239 if (outf) 240 fprintf(outf, 241 "Found macro\n"); 242 #endif /* DEBUG */ 243 /* 244 * We have to decrement one because key will be 245 * incremented at the bottom of the out loop. 246 */ 247 key = (first = blockpeek = 248 cur_term->_first_macro) - 249 1; 250 goto outerloop; 251 } 252 253 /*FALLTHROUGH*/ 254 255 case -1: 256 goto ret; 257 } 258 } 259 260 /* not this one? */ 261 if (kp[key]->_sends[i] != inputQ[i]) 262 goto outerloop; 263 } 264 265 /* SS-mouse */ 266 if (kp[key]->_keyval == KEY_MOUSE) { 267 MOUSE_STATUS old_mouse; 268 int rc; 269 270 old_mouse = Mouse_status; 271 272 /* read the mouse status information */ 273 274 if (mouse_info) 275 rc = -3; /* NOT IMPLEMENTED */ 276 else 277 rc = get_xterm_mouse(blockpeek, &i); 278 279 if (rc == -1) /* read error */ 280 goto ret; 281 else if (rc == -2 || rc == -3) /* timeout */ 282 /* or not mouse */ 283 goto outerloop; 284 else if (rc == 0) /* report mouse pos */ 285 Mouse_status.changes |= 020; 286 else if (rc >= 1 && rc <= 3) 287 /* mouse button event */ 288 Mouse_status.changes = 289 (((MOUSE_X_POS != old_mouse.x || 290 MOUSE_Y_POS != old_mouse.y) << 3) | 291 ((Mouse_status.button[2] != 292 old_mouse.button[2]) << 2) | 293 ((Mouse_status.button[1] != 294 old_mouse.button[1]) << 1) | 295 (Mouse_status.button[0] != 296 old_mouse.button[0])); 297 } 298 299 /* We found it! Read in any chars left in _sends */ 300 301 if ((collapse = i) == INP_QSIZE) 302 for (; kp[key]->_sends[i]; i++) 303 (void) _fpk(); 304 305 /* move key to top of ordered list */ 306 if (key != first) { 307 _KEY_MAP *savekey = kp[key]; 308 short *lorder; 309 int j; 310 311 if (key > cur_term->_first_macro) 312 lorder = &(cur_term->_lastmacro_ordered); 313 else 314 lorder = &(cur_term->_lastkey_ordered); 315 /* 316 * If we're below the last ordered key, swap next unordered 317 * key with this one and ripple from there. 318 */ 319 if (key > *lorder) 320 kp[key] = kp[(i = ++(*lorder))]; 321 else 322 i = key; 323 /* ripple the ordered keys down */ 324 for (j = i--; j > first; ) 325 kp[j--] = kp[i--]; 326 kp[first] = savekey; 327 } 328 *inp = kp[first]->_keyval; 329 330 /* 331 * SS-mouse support: if mouse button event 332 * occured on top of the soft label, we may 333 * have to return the function key corresponding 334 * to that soft label 335 */ 336 337 if (*inp == KEY_MOUSE && A_BUTTON_CHANGED && 338 (MOUSE_Y_POS == LINES) && 339 (SP->slk != (SLK_MAP *) NULL) && 340 (SP->_map_mbe_to_key != 0)) { 341 _map_button(inp); 342 } 343 344 goto ret; 345 } 346 outerloop: 347 ; 348 } 349 350 ret: 351 /* key not found */ 352 #ifdef DEBUG 353 if (outf) 354 if (key == num_keys) 355 fprintf(outf, "Did not match anything.\n"); 356 #endif /* DEBUG */ 357 return (collapse); 358 } 359 360 361 /* SS-mouse */ 362 /* this function tries to read in information that follows KEY_MOUSE: */ 363 /* the first character identifies what button is involved (1,2,or 3) */ 364 /* if the first character is 0, we are dealing with report_mouse_pos */ 365 /* 366 * The routine returns the following: 367 * -3: not a mouse button event 368 * -2: read timed out 369 * -1: the read failed 370 * [0, 1, 2, 3] - the first character in the mouse event 371 */ 372 static int 373 get_xterm_mouse(int blockpeek, int *i) 374 { 375 chtype *inputQ = cur_term->_input_queue; /* ??? */ 376 /* LINTED */ 377 chtype *chars_onQ = (chtype *) &(cur_term->_chars_on_queue); 378 int j, mx, my; 379 int char1, char2, c1, c2; 380 381 /* the first character should be 0, 1, 2, or 4 */ 382 383 char1 = (inputQ[(*i)++] = (blockpeek) ? _pk() : _fpk()); 384 385 /* read error or timeout */ 386 387 if (char1 < 0) 388 return (char1); 389 (*chars_onQ)++; 390 391 if (char1 < '0' || char1 > '3') 392 return (-3); 393 394 /* if the character is 1, 2, or 3 it must be followed by */ 395 /* P, R, C, D, or T */ 396 397 if (char1 != '0') { 398 char2 = (inputQ[(*i)++] = (blockpeek) ? _pk() : _fpk()); 399 400 if (char2 < 0) 401 return (char2); 402 403 (*chars_onQ)++; 404 if (char2 != 'P' && char2 != 'R' && char2 != 'C' && 405 char2 != 'D' && char2 != 'T') 406 return (-3); 407 } 408 409 /* read X and Y coordinates of the mouse */ 410 411 for (j = 0; j < 2; j++) { 412 c1 = (inputQ[(*i)++] = (blockpeek) ? _pk() : _fpk()); 413 if (c1 < 0) 414 return (c1); 415 (*chars_onQ)++; 416 if (c1 >= ' ' && c1 <= '~') { /* ascii char */ 417 if (j == 0) 418 mx = c1 - ' '; 419 else 420 my = c1 - ' '; 421 } else if (char1 == 01 || char1 == 02) { /* ^A || ^B */ 422 c2 = (inputQ[(*i)++] = (blockpeek) ? _pk() : _fpk()); 423 if (c2 < 0) 424 return (c2); 425 (*chars_onQ)++; 426 if (c2 >= ' ' && c2 <= '~') { 427 if (j == 0) 428 mx = c1 * (c2 - ' '); 429 else 430 my = c1 * (c2 - ' '); 431 } else 432 return (-3); 433 } else 434 return (-3); 435 } 436 437 /* read complete mouse event: update the Mouse_status structure */ 438 439 MOUSE_X_POS = mx; 440 MOUSE_Y_POS = my; 441 j = char1 - '0'; 442 if (j != 0) { 443 switch (char2) { 444 case 'P': 445 BUTTON_STATUS(j) = BUTTON_PRESSED; 446 break; 447 case 'R': 448 BUTTON_STATUS(j) = BUTTON_RELEASED; 449 break; 450 case 'C': 451 BUTTON_STATUS(j) = BUTTON_CLICKED; 452 break; 453 case 'D': 454 BUTTON_STATUS(j) = BUTTON_DOUBLE_CLICKED; 455 break; 456 case 'T': 457 BUTTON_STATUS(j) = BUTTON_TRIPLE_CLICKED; 458 break; 459 } 460 } 461 return (j); 462 } 463 /* SS-mouse-end */ 464 465 466 /* 467 * Fast peek key. Like getchar but if the right flags are set, times out 468 * quickly if there is nothing waiting, returning -1. 469 * f is an output stdio descriptor, we read from the fileno. 470 * We wait for long enough for a terminal to send another character 471 * (at 15cps repeat rate, this is 67 ms, I'm using 100ms to allow 472 * a bit of a fudge factor) and time out more quickly. 473 * -2 is returned if we time out, -1 is returned if interrupted, and the 474 * character is returned otherwise. 475 */ 476 477 #ifndef FIONREAD 478 479 /* 480 * Traditional implementation. The best resolution we have is 1 second, 481 * so we set a 1 second alarm and try to read. If we fail for 1 second, 482 * we assume there is no key waiting. Problem here is that 1 second is 483 * too long; people can type faster than this. 484 * 485 * Another possible implementation of changing VMIN/VTIME before and 486 * after each read does not work because the tty driver's timeout 487 * mechanism is too unreliable when the timeouts are changed too quickly. 488 */ 489 490 static char sig_caught; 491 492 static 493 #ifdef SIGPOLL /* Vr3 and beyond */ 494 void 495 #endif /* SIGPOLL */ 496 /* The following line causes a lint warning for "dummy" which is not used. */ 497 _catch_alarm(int dummy) 498 { 499 sig_caught = 1; 500 } 501 502 static int 503 _fpk(void) 504 { 505 unsigned char c; 506 int infd = cur_term->_inputfd; 507 ssize_t rc; 508 #ifdef SIGPOLL /* Vr3 and beyond */ 509 void (*oldsig)(int); 510 #else /* SIGPOLL */ 511 int (*oldsig)(int); 512 #endif /* SIGPOLL */ 513 unsigned int oldalarm, alarm(unsigned); 514 515 /* turn off any user alarms and set our own */ 516 oldalarm = alarm(0); 517 sig_caught = 0; 518 oldsig = signal(SIGALRM, _catch_alarm); 519 (void) alarm(1); 520 rc = read(cur_term->_inputfd, (char *)&c, 1); 521 (void) alarm(0); 522 523 /* 524 * This code is to take care of the possibility of 525 * the process getting swapped out in the middle of 526 * read() call above. The interrupt will cause the 527 * read() call to retur, even if a character is really 528 * on the clist. So we do a non-blocking read() to make 529 * sure that there really isn't a character there. 530 */ 531 532 if (sig_caught && rc != 1) 533 if (cur_term->_check_fd != -1) 534 rc = read(cur_term->_check_fd, (char *)&c, 1); 535 else { 536 #include <fcntl.h> 537 int fcflags = fcntl(infd, F_GETFL, 0); 538 539 (void) fcntl(infd, F_SETFL, fcflags | O_NDELAY); 540 rc = read(infd, (char *)&c, 1); 541 (void) fcntl(infd, F_SETFL, fcflags); 542 } 543 544 /* restore the user alarms */ 545 (void) signal(SIGALRM, oldsig); 546 if (sig_caught && oldalarm > 1) 547 oldalarm--; 548 (void) alarm(oldalarm); 549 if (rc == 1) /* got a character */ 550 return (c); 551 else 552 if (sig_caught) /* timed out */ 553 return (-2); 554 else /* EOF or got interrupted */ 555 return (-1); 556 } 557 #else /* FIONREAD */ 558 /* 559 * If we have the select system call, we can do much better than the 560 * traditional method. Even if we don't have the real 4.2BSD select, we 561 * can emulate it with napms and FIONREAD. napms might be done with only 562 * 1 second resolution, but this is no worse than what we have in the 563 * traditional implementation. 564 */ 565 static 566 _fpk() 567 { 568 int infd, rc; 569 int *outfd, *exfd; 570 unsigned char c; 571 struct timeval t; 572 573 infd = 1 << cur_term->_inputfd; 574 outfd = exfd = (int *)NULL; 575 t.tv_sec = 0; 576 t.tv_usec = 100000; /* 100 milliseconds */ 577 rc = select(20, &infd, outfd, exfd, &t); 578 if (rc < 0) 579 return (-2); 580 rc = read(fileno(f), &c, 1); 581 return (rc == 1 ? c : -1); 582 } 583 #endif /* FIONREAD */ 584 585 /* 586 * Plain peekchar function. Nothing fancy. This is just like _fpk 587 * but will wait forever rather than time out. 588 */ 589 590 static int 591 _pk(void) 592 { 593 unsigned char c; 594 595 return ((read(cur_term->_inputfd, (char *)&c, 1) == 1) ? c : ERR); 596 } 597 598 599 /* 600 * SS-mouse: check if this mouse button event should map into 601 * function key 602 */ 603 604 605 static void 606 _map_button(chtype *inp) 607 { 608 SLK_MAP *slk = SP->slk; 609 int num = slk->_num; 610 int len = slk->_len; 611 int i; 612 613 /* first determine if this mouse button event should be */ 614 /* mapped into function key */ 615 616 if (!(SP->_map_mbe_to_key & 617 ((BUTTON_CHANGED(3) << (10 + BUTTON_STATUS(3))) | 618 (BUTTON_CHANGED(2) << (5 + BUTTON_STATUS(2))) | 619 (BUTTON_CHANGED(1) << BUTTON_STATUS(1))))) 620 return; 621 622 for (i = 0; i < num; i++) { 623 if (MOUSE_X_POS < slk->_labx[i]) 624 break; 625 if (MOUSE_X_POS > slk->_labx[i] + len) 626 continue; 627 *inp = KEY_F(1) + i; 628 break; 629 } 630 } 631