getch.c revision 1.24
1/* $NetBSD: getch.c,v 1.24 2000/04/23 14:14:49 blymn Exp $ */ 2 3/* 4 * Copyright (c) 1981, 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by the University of 18 * California, Berkeley and its contributors. 19 * 4. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36#include <sys/cdefs.h> 37#ifndef lint 38#if 0 39static char sccsid[] = "@(#)getch.c 8.2 (Berkeley) 5/4/94"; 40#else 41__RCSID("$NetBSD: getch.c,v 1.24 2000/04/23 14:14:49 blymn Exp $"); 42#endif 43#endif /* not lint */ 44 45#include <string.h> 46#include <stdlib.h> 47#include <unistd.h> 48#include <stdio.h> 49#include "curses.h" 50#include "curses_private.h" 51 52#define DEFAULT_DELAY 2 /* default delay for timeout() */ 53 54/* 55 * Keyboard input handler. Do this by snarfing 56 * all the info we can out of the termcap entry for TERM and putting it 57 * into a set of keymaps. A keymap is an array the size of all the possible 58 * single characters we can get, the contents of the array is a structure 59 * that contains the type of entry this character is (i.e. part/end of a 60 * multi-char sequence or a plain char) and either a pointer which will point 61 * to another keymap (in the case of a multi-char sequence) OR the data value 62 * that this key should return. 63 * 64 */ 65 66/* private data structures for holding the key definitions */ 67typedef struct keymap keymap_t; 68typedef struct key_entry key_entry_t; 69 70struct key_entry { 71 short type; /* type of key this is */ 72 union { 73 keymap_t *next; /* next keymap is key is multi-key sequence */ 74 wchar_t symbol; /* key symbol if key is a leaf entry */ 75 } value; 76}; 77/* Types of key structures we can have */ 78#define KEYMAP_MULTI 1 /* part of a multi char sequence */ 79#define KEYMAP_LEAF 2 /* key has a symbol associated with it, either 80 * it is the end of a multi-char sequence or a 81 * single char key that generates a symbol */ 82 83/* allocate this many key_entry structs at once to speed start up must 84 * be a power of 2. 85 */ 86#define KEYMAP_ALLOC_CHUNK 4 87 88/* The max number of different chars we can receive */ 89#define MAX_CHAR 256 90 91struct keymap { 92 int count; /* count of number of key structs allocated */ 93 short mapping[MAX_CHAR]; /* mapping of key to allocated structs */ 94 key_entry_t **key; /* dynamic array of keys */ 95}; 96 97 98/* Key buffer */ 99#define INBUF_SZ 16 /* size of key buffer - must be larger than 100 * longest multi-key sequence */ 101static wchar_t inbuf[INBUF_SZ]; 102static int start, end, working; /* pointers for manipulating inbuf data */ 103 104#define INC_POINTER(ptr) do { \ 105 (ptr)++; \ 106 ptr %= INBUF_SZ; \ 107} while(/*CONSTCOND*/0) 108 109static short state; /* state of the inkey function */ 110 111#define INKEY_NORM 0 /* no key backlog to process */ 112#define INKEY_ASSEMBLING 1 /* assembling a multi-key sequence */ 113#define INKEY_BACKOUT 2 /* recovering from an unrecognised key */ 114#define INKEY_TIMEOUT 3 /* multi-key sequence timeout */ 115 116/* The termcap data we are interested in and the symbols they map to */ 117struct tcdata { 118 const char *name; /* name of termcap entry */ 119 wchar_t symbol; /* the symbol associated with it */ 120}; 121 122static const struct tcdata tc[] = { 123 {"K1", KEY_A1}, 124 {"K2", KEY_B2}, 125 {"K3", KEY_A3}, 126 {"K4", KEY_C1}, 127 {"K5", KEY_C3}, 128 {"k0", KEY_F0}, 129 {"k1", KEY_F(1)}, 130 {"k2", KEY_F(2)}, 131 {"k3", KEY_F(3)}, 132 {"k4", KEY_F(4)}, 133 {"k5", KEY_F(5)}, 134 {"k6", KEY_F(6)}, 135 {"k7", KEY_F(7)}, 136 {"k8", KEY_F(8)}, 137 {"k9", KEY_F(9)}, 138 {"kA", KEY_IL}, 139 {"ka", KEY_CATAB}, 140 {"kb", KEY_BACKSPACE}, 141 {"kC", KEY_CLEAR}, 142 {"kD", KEY_DC}, 143 {"kd", KEY_DOWN}, 144 {"kE", KEY_EOL}, 145 {"kF", KEY_SF}, 146 {"kH", KEY_LL}, 147 {"kh", KEY_HOME}, 148 {"kI", KEY_IC}, 149 {"kL", KEY_DL}, 150 {"kl", KEY_LEFT}, 151 {"kN", KEY_NPAGE}, 152 {"kP", KEY_PPAGE}, 153 {"kR", KEY_SR}, 154 {"kr", KEY_RIGHT}, 155 {"kS", KEY_EOS}, 156 {"kT", KEY_STAB}, 157 {"kt", KEY_CTAB}, 158 {"ku", KEY_UP} 159}; 160/* Number of TC entries .... */ 161static const int num_tcs = (sizeof(tc) / sizeof(struct tcdata)); 162 163/* The root keymap */ 164 165static keymap_t *base_keymap; 166 167/* prototypes for private functions */ 168static key_entry_t *add_new_key(keymap_t *current, char chr, int key_type, 169 int symbol); 170static keymap_t *new_keymap(void); /* create a new keymap */ 171static key_entry_t *new_key(void); /* create a new key entry */ 172static wchar_t inkey(int to, int delay); 173 174/* 175 * Add a new key entry to the keymap pointed to by current. Entry 176 * contains the character to add to the keymap, type is the type of 177 * entry to add (either multikey or leaf) and symbol is the symbolic 178 * value for a leaf type entry. The function returns a pointer to the 179 * new keymap entry. 180 */ 181static key_entry_t * 182add_new_key(keymap_t *current, char chr, int key_type, int symbol) 183{ 184 key_entry_t *the_key; 185 int i; 186 187#ifdef DEBUG 188 __CTRACE("Adding character %s of type %d, symbol 0x%x\n", unctrl(chr), 189 key_type, symbol); 190#endif 191 if (current->mapping[(unsigned) chr] < 0) { 192 /* first time for this char */ 193 current->mapping[(unsigned) chr] = current->count; /* map new entry */ 194 /* make sure we have room in the key array first */ 195 if ((current->count & (KEYMAP_ALLOC_CHUNK - 1)) == 0) 196 { 197 if ((current->key = 198 realloc(current->key, 199 (current->count) * sizeof(key_entry_t *) 200 + KEYMAP_ALLOC_CHUNK * sizeof(key_entry_t *))) == NULL) { 201 fprintf(stderr, 202 "Could not malloc for key entry\n"); 203 exit(1); 204 } 205 206 the_key = new_key(); 207 for (i = 0; i < KEYMAP_ALLOC_CHUNK; i++) { 208 current->key[current->count + i] 209 = &the_key[i]; 210 } 211 } 212 213 /* point at the current key array element to use */ 214 the_key = current->key[current->count]; 215 216 the_key->type = key_type; 217 218 switch (key_type) { 219 case KEYMAP_MULTI: 220 /* need for next key */ 221#ifdef DEBUG 222 __CTRACE("Creating new keymap\n"); 223#endif 224 the_key->value.next = new_keymap(); 225 break; 226 227 case KEYMAP_LEAF: 228 /* the associated symbol for the key */ 229#ifdef DEBUG 230 __CTRACE("Adding leaf key\n"); 231#endif 232 the_key->value.symbol = symbol; 233 break; 234 235 default: 236 fprintf(stderr, "add_new_key: bad type passed\n"); 237 exit(1); 238 } 239 240 current->count++; 241 } else { 242 /* the key is already known - just return the address. */ 243#ifdef DEBUG 244 __CTRACE("Keymap already known\n"); 245#endif 246 the_key = current->key[current->mapping[(unsigned) chr]]; 247 } 248 249 return the_key; 250} 251 252/* 253 * Init_getch - initialise all the pointers & structures needed to make 254 * getch work in keypad mode. 255 * 256 */ 257void 258__init_getch(char *sp) 259{ 260 static struct tinfo *termcap; 261 char entry[1024], termname[1024], *p; 262 int i, j, length, key_ent; 263 size_t limit; 264 key_entry_t *tmp_key; 265 keymap_t *current; 266#ifdef DEBUG 267 int k; 268#endif 269 270 /* init the inkey state variable */ 271 state = INKEY_NORM; 272 273 /* init the base keymap */ 274 base_keymap = new_keymap(); 275 276 /* key input buffer pointers */ 277 start = end = working = 0; 278 279 /* now do the termcap snarfing ... */ 280 (void) strncpy(termname, sp, (size_t) 1022); 281 termname[1023] = 0; 282 283 if (t_getent(&termcap, termname) > 0) { 284 for (i = 0; i < num_tcs; i++) { 285 p = entry; 286 limit = 1023; 287 if (t_getstr(termcap, tc[i].name, &p, &limit) != NULL) { 288 current = base_keymap; /* always start with 289 * base keymap. */ 290 length = (int) strlen(entry); 291#ifdef DEBUG 292 __CTRACE("Processing termcap entry %s, sequence ", 293 tc[i].name); 294 for (k = 0; k <= length -1; k++) 295 __CTRACE("%s", unctrl(entry[k])); 296 __CTRACE("\n"); 297#endif 298 for (j = 0; j < length - 1; j++) { 299 /* add the entry to the struct */ 300 tmp_key = add_new_key(current, 301 entry[j], 302 KEYMAP_MULTI, 0); 303 304 /* index into the key array - it's 305 clearer if we stash this */ 306 key_ent = current->mapping[ 307 (unsigned) entry[j]]; 308 309 current->key[key_ent] = tmp_key; 310 311 /* next key uses this map... */ 312 current = current->key[key_ent]->value.next; 313 } 314 315 /* this is the last key in the sequence (it 316 * may have been the only one but that does 317 * not matter) this means it is a leaf key and 318 * should have a symbol associated with it. 319 */ 320 tmp_key = add_new_key(current, 321 entry[length - 1], 322 KEYMAP_LEAF, 323 tc[i].symbol); 324 current->key[ 325 current->mapping[(int)entry[length - 1]]] = 326 tmp_key; 327 } 328 } 329 } 330} 331 332 333/* 334 * new_keymap - allocates & initialises a new keymap structure. This 335 * function returns a pointer to the new keymap. 336 * 337 */ 338static keymap_t * 339new_keymap(void) 340{ 341 int i; 342 keymap_t *new_map; 343 344 if ((new_map = malloc(sizeof(keymap_t))) == NULL) { 345 perror("Inkey: Cannot allocate new keymap"); 346 exit(2); 347 } 348 349 /* Initialise the new map */ 350 new_map->count = 0; 351 for (i = 0; i < MAX_CHAR; i++) { 352 new_map->mapping[i] = -1; /* no mapping for char */ 353 } 354 355 /* key array will be allocated when first key is added */ 356 new_map->key = NULL; 357 358 return new_map; 359} 360 361/* 362 * new_key - allocates & initialises a new key entry. This function returns 363 * a pointer to the newly allocated key entry. 364 * 365 */ 366static key_entry_t * 367new_key(void) 368{ 369 key_entry_t *new_one; 370 int i; 371 372 if ((new_one = malloc(KEYMAP_ALLOC_CHUNK * sizeof(key_entry_t))) 373 == NULL) { 374 perror("inkey: Cannot allocate new key entry chunk"); 375 exit(2); 376 } 377 378 for (i = 0; i < KEYMAP_ALLOC_CHUNK; i++) { 379 new_one[i].type = 0; 380 new_one[i].value.next = NULL; 381 } 382 383 return new_one; 384} 385 386/* 387 * inkey - do the work to process keyboard input, check for multi-key 388 * sequences and return the appropriate symbol if we get a match. 389 * 390 */ 391 392wchar_t 393inkey(int to, int delay) 394{ 395 wchar_t k; 396 int c; 397 keymap_t *current = base_keymap; 398 399 for (;;) { /* loop until we get a complete key sequence */ 400reread: 401 if (state == INKEY_NORM) { 402 if (delay && __timeout(delay) == ERR) 403 return ERR; 404 if ((c = getchar()) == EOF) { 405 clearerr(stdin); 406 return ERR; 407 } 408 409 if (delay && (__notimeout() == ERR)) 410 return ERR; 411 412 k = (wchar_t) c; 413#ifdef DEBUG 414 __CTRACE("inkey (state normal) got '%s'\n", unctrl(k)); 415#endif 416 417 working = start; 418 inbuf[working] = k; 419 INC_POINTER(working); 420 end = working; 421 state = INKEY_ASSEMBLING; /* go to the assembling 422 * state now */ 423 } else if (state == INKEY_BACKOUT) { 424 k = inbuf[working]; 425 INC_POINTER(working); 426 if (working == end) { /* see if we have run 427 * out of keys in the 428 * backlog */ 429 430 /* if we have then switch to 431 assembling */ 432 state = INKEY_ASSEMBLING; 433 } 434 } else if (state == INKEY_ASSEMBLING) { 435 /* assembling a key sequence */ 436 if (delay) { 437 if (__timeout(to ? DEFAULT_DELAY : delay) == ERR) 438 return ERR; 439 } else { 440 if (to && (__timeout(DEFAULT_DELAY) == ERR)) 441 return ERR; 442 } 443 444 c = getchar(); 445 if (ferror(stdin)) { 446 clearerr(stdin); 447 return ERR; 448 } 449 450 if ((to || delay) && (__notimeout() == ERR)) 451 return ERR; 452 453 k = (wchar_t) c; 454#ifdef DEBUG 455 __CTRACE("inkey (state assembling) got '%s'\n", unctrl(k)); 456#endif 457 if (feof(stdin)) { /* inter-char timeout, 458 * start backing out */ 459 clearerr(stdin); 460 if (start == end) 461 /* no chars in the buffer, restart */ 462 goto reread; 463 464 k = inbuf[start]; 465 state = INKEY_TIMEOUT; 466 } else { 467 inbuf[working] = k; 468 INC_POINTER(working); 469 end = working; 470 } 471 } else { 472 fprintf(stderr, "Inkey state screwed - exiting!!!"); 473 exit(2); 474 } 475 476 /* Check key has no special meaning and we have not timed out */ 477 if ((state == INKEY_TIMEOUT) || (current->mapping[k] < 0)) { 478 /* return the first key we know about */ 479 k = inbuf[start]; 480 481 INC_POINTER(start); 482 working = start; 483 484 if (start == end) { /* only one char processed */ 485 state = INKEY_NORM; 486 } else {/* otherwise we must have more than one char 487 * to backout */ 488 state = INKEY_BACKOUT; 489 } 490 return k; 491 } else { /* must be part of a multikey sequence */ 492 /* check for completed key sequence */ 493 if (current->key[current->mapping[k]]->type == KEYMAP_LEAF) { 494 start = working; /* eat the key sequence 495 * in inbuf */ 496 497 /* check if inbuf empty now */ 498 if (start == end) { 499 /* if it is go back to normal */ 500 state = INKEY_NORM; 501 } else { 502 /* otherwise go to backout state */ 503 state = INKEY_BACKOUT; 504 } 505 506 /* return the symbol */ 507 return current->key[current->mapping[k]]->value.symbol; 508 509 } else { 510 /* 511 * Step on to next part of the multi-key 512 * sequence. 513 */ 514 current = current->key[current->mapping[k]]->value.next; 515 } 516 } 517 } 518} 519 520#ifndef _CURSES_USE_MACROS 521/* 522 * getch -- 523 * Read in a character from stdscr. 524 */ 525int 526getch(void) 527{ 528 return wgetch(stdscr); 529} 530 531/* 532 * mvgetch -- 533 * Read in a character from stdscr at the given location. 534 */ 535int 536mvgetch(int y, int x) 537{ 538 return mvwgetch(stdscr, y, x); 539} 540 541/* 542 * mvwgetch -- 543 * Read in a character from stdscr at the given location in the 544 * given window. 545 */ 546int 547mvwgetch(WINDOW *win, int y, int x) 548{ 549 if (wmove(win, y, x) == ERR) 550 return ERR; 551 552 return wgetch(win); 553} 554 555#endif 556 557/* 558 * wgetch -- 559 * Read in a character from the window. 560 */ 561int 562wgetch(WINDOW *win) 563{ 564 int inp, weset; 565 char c; 566 567 if (!(win->flags & __SCROLLOK) && (win->flags & __FULLWIN) 568 && win->curx == win->maxx - 1 && win->cury == win->maxy - 1 569 && __echoit) 570 return (ERR); 571 572 wrefresh(win); 573#ifdef DEBUG 574 __CTRACE("wgetch: __echoit = %d, __rawmode = %d, flags = %0.2o\n", 575 __echoit, __rawmode, win->flags); 576#endif 577 if (__echoit && !__rawmode) { 578 cbreak(); 579 weset = 1; 580 } else 581 weset = 0; 582 583 __save_termios(); 584 585 if (win->flags & __KEYPAD) { 586 switch (win->delay) 587 { 588 case -1: 589 inp = inkey (win->flags & __NOTIMEOUT ? 0 : 1, 0); 590 break; 591 case 0: 592 if (__nodelay() == ERR) { 593 __restore_termios(); 594 return ERR; 595 } 596 inp = inkey(0, 0); 597 break; 598 default: 599 inp = inkey(win->flags & __NOTIMEOUT ? 0 : 1, win->delay); 600 break; 601 } 602 } else { 603 switch (win->delay) 604 { 605 case -1: 606 break; 607 case 0: 608 if (__nodelay() == ERR) { 609 __restore_termios(); 610 return ERR; 611 } 612 break; 613 default: 614 if (__timeout(win->delay) == ERR) { 615 __restore_termios(); 616 return ERR; 617 } 618 break; 619 } 620 621 c = getchar(); 622 if (feof(stdin)) { 623 clearerr(stdin); 624 __restore_termios(); 625 return ERR; /* we have timed out */ 626 } 627 628 if (ferror(stdin)) { 629 clearerr(stdin); 630 inp = ERR; 631 } else { 632 inp = (unsigned int) c; 633 } 634 } 635#ifdef DEBUG 636 if (inp > 255) 637 /* we have a key symbol - treat it differently */ 638 /* XXXX perhaps __unctrl should be expanded to include 639 * XXXX the keysyms in the table.... 640 */ 641 __CTRACE("wgetch assembled keysym 0x%x\n", inp); 642 else 643 __CTRACE("wgetch got '%s'\n", unctrl(inp)); 644#endif 645 if (win->delay > -1) { 646 if (__delay() == ERR) { 647 __restore_termios(); 648 return ERR; 649 } 650 } 651 652 __restore_termios(); 653 if (__echoit) { 654 waddch(win, (chtype) inp); 655 } 656 if (weset) 657 nocbreak(); 658 659 return ((inp < 0) || (inp == ERR) ? ERR : inp); 660} 661 662/* 663 * ungetch -- 664 * Put the character back into the input queue. 665 */ 666int 667ungetch(int c) 668{ 669 return ((ungetc(c, stdin) == EOF) ? ERR : OK); 670} 671