getch.c revision 1.15 1 /* $NetBSD: getch.c,v 1.15 1999/12/07 03:53:11 simonb 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
39 static char sccsid[] = "@(#)getch.c 8.2 (Berkeley) 5/4/94";
40 #else
41 __RCSID("$NetBSD: getch.c,v 1.15 1999/12/07 03:53:11 simonb 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
51 #define DEFAULT_DELAY 2 /* default delay for timeout() */
52
53 /*
54 * Keyboard input handler. Do this by snarfing
55 * all the info we can out of the termcap entry for TERM and putting it
56 * into a set of keymaps. A keymap is an array the size of all the possible
57 * single characters we can get, the contents of the array is a structure
58 * that contains the type of entry this character is (i.e. part/end of a
59 * multi-char sequence or a plain char) and either a pointer which will point
60 * to another keymap (in the case of a multi-char sequence) OR the data value
61 * that this key should return.
62 *
63 */
64
65 /* private data structures for holding the key definitions */
66 typedef struct keymap keymap_t;
67 typedef struct key_entry key_entry_t;
68
69 struct key_entry {
70 short type; /* type of key this is */
71 union {
72 keymap_t *next; /* next keymap is key is multi-key sequence */
73 int symbol; /* key symbol if key is a leaf entry */
74 } value;
75 };
76 /* Types of key structures we can have */
77 #define KEYMAP_MULTI 1 /* part of a multi char sequence */
78 #define KEYMAP_LEAF 2 /* key has a symbol associated with it, either
79 * it is the end of a multi-char sequence or a
80 * single char key that generates a symbol */
81
82 /* The max number of different chars we can receive */
83 #define MAX_CHAR 256
84
85 struct keymap {
86 int count; /* count of number of key structs allocated */
87 short mapping[MAX_CHAR]; /* mapping of key to allocated structs */
88 key_entry_t **key; /* dynamic array of keys */};
89
90
91 /* Key buffer */
92 #define INBUF_SZ 16 /* size of key buffer - must be larger than
93 * longest multi-key sequence */
94 static char inbuf[INBUF_SZ];
95 static int start, end, working; /* pointers for manipulating inbuf data */
96
97 #define INC_POINTER(ptr) do { \
98 (ptr)++; \
99 ptr %= INBUF_SZ; \
100 } while(/*CONSTCOND*/0)
101
102 static short state; /* state of the inkey function */
103
104 #define INKEY_NORM 0 /* no key backlog to process */
105 #define INKEY_ASSEMBLING 1 /* assembling a multi-key sequence */
106 #define INKEY_BACKOUT 2 /* recovering from an unrecognised key */
107 #define INKEY_TIMEOUT 3 /* multi-key sequence timeout */
108
109 /* The termcap data we are interested in and the symbols they map to */
110 struct tcdata {
111 char *name; /* name of termcap entry */
112 int symbol; /* the symbol associated with it */
113 };
114
115 static const struct tcdata tc[] = {
116 {"K1", KEY_A1},
117 {"K2", KEY_B2},
118 {"K3", KEY_A3},
119 {"K4", KEY_C1},
120 {"K5", KEY_C3},
121 {"k0", KEY_F0},
122 {"k1", KEY_F(1)},
123 {"k2", KEY_F(2)},
124 {"k3", KEY_F(3)},
125 {"k4", KEY_F(4)},
126 {"k5", KEY_F(5)},
127 {"k6", KEY_F(6)},
128 {"k7", KEY_F(7)},
129 {"k8", KEY_F(8)},
130 {"k9", KEY_F(9)},
131 {"kA", KEY_IL},
132 {"ka", KEY_CATAB},
133 {"kb", KEY_BACKSPACE},
134 {"kC", KEY_CLEAR},
135 {"kD", KEY_DC},
136 {"kd", KEY_DOWN},
137 {"kE", KEY_EOL},
138 {"kF", KEY_SF},
139 {"kH", KEY_LL},
140 {"kh", KEY_HOME},
141 {"kI", KEY_IC},
142 {"kL", KEY_DL},
143 {"kl", KEY_LEFT},
144 {"kN", KEY_NPAGE},
145 {"kP", KEY_PPAGE},
146 {"kR", KEY_SR},
147 {"kr", KEY_RIGHT},
148 {"kS", KEY_EOS},
149 {"kT", KEY_STAB},
150 {"kt", KEY_CTAB},
151 {"ku", KEY_UP}
152 };
153 /* Number of TC entries .... */
154 static const int num_tcs = (sizeof(tc) / sizeof(struct tcdata));
155
156 /* The root keymap */
157
158 static keymap_t *base_keymap;
159
160 /* prototypes for private functions */
161 static keymap_t *new_keymap(void); /* create a new keymap */
162 static key_entry_t *new_key(void); /* create a new key entry */
163 static unsigned inkey(int, int);
164
165 /*
166 * Init_getch - initialise all the pointers & structures needed to make
167 * getch work in keypad mode.
168 *
169 */
170 void
171 __init_getch(sp)
172 char *sp;
173 {
174 static char termcap[1024];
175 char entry[1024], termname[1024], *p;
176 int i, j, length;
177 keymap_t *current;
178 key_entry_t *the_key;
179
180 /* init the inkey state variable */
181 state = INKEY_NORM;
182
183 /* init the base keymap */
184 base_keymap = new_keymap();
185
186 /* key input buffer pointers */
187 start = end = working = 0;
188
189 /* now do the termcap snarfing ... */
190 strncpy(termname, sp, 1022);
191 termname[1023] = 0;
192
193 if (tgetent(termcap, termname) <= 0)
194 return;
195
196 for (i = 0; i < num_tcs; i++) {
197
198 p = entry;
199 if (tgetstr(tc[i].name, &p) == NULL)
200 continue;
201
202 current = base_keymap; /* always start with base keymap. */
203 length = strlen(entry);
204
205 for (j = 0; j < length - 1; j++) {
206 if (current->mapping[(unsigned) entry[j]] < 0) {
207 /* first time for this char */
208 current->mapping[(unsigned) entry[j]] = current->count; /* map new entry */
209 the_key = new_key();
210 /* multikey coz we are here */
211 the_key->type = KEYMAP_MULTI;
212
213 /* need for next key */
214 the_key->value.next = new_keymap();
215
216 /* put into key array */
217 if ((current->key = realloc(current->key, (current->count + 1) * sizeof(key_entry_t *))) == NULL) {
218 fprintf(stderr,
219 "Could not malloc for key entry\n");
220 exit(1);
221 }
222
223 current->key[current->count++] = the_key;
224
225 }
226 /* next key uses this map... */
227 current = current->key[current->mapping[(unsigned) entry[j]]]->value.next;
228 }
229
230 /*
231 * This is the last key in the sequence (it may have been
232 * the only one but that does not matter) this means it is
233 * a leaf key and should have a symbol associated with it.
234 */
235 if (current->count > 0) {
236 /*
237 * If there were other keys then we need to
238 * extend the mapping array.
239 */
240 if ((current->key =
241 realloc(current->key,
242 (current->count + 1) *
243 sizeof(key_entry_t *))) == NULL) {
244
245 fprintf(stderr,
246 "Could not malloc for key entry\n");
247 exit(1);
248 }
249 }
250 current->mapping[(unsigned) entry[length - 1]] = current->count;
251 the_key = new_key();
252 the_key->type = KEYMAP_LEAF; /* leaf key */
253
254 /* the associated symbol */
255 the_key->value.symbol = tc[i].symbol;
256 current->key[current->count++] = the_key;
257 }
258 }
259
260
261 /*
262 * new_keymap - allocates & initialises a new keymap structure. This
263 * function returns a pointer to the new keymap.
264 *
265 */
266 static keymap_t *
267 new_keymap(void)
268 {
269 int i;
270 keymap_t *new_map;
271
272 if ((new_map = malloc(sizeof(keymap_t))) == NULL) {
273 perror("Inkey: Cannot allocate new keymap");
274 exit(2);
275 }
276
277 /* Initialise the new map */
278 new_map->count = 0;
279 for (i = 0; i < MAX_CHAR; i++) {
280 new_map->mapping[i] = -1; /* no mapping for char */
281 }
282
283 /* one does assume there will be at least one key mapped.... */
284 if ((new_map->key = malloc(sizeof(key_entry_t *))) == NULL) {
285 perror("Could not malloc first key ent");
286 exit(1);
287 }
288
289 return (new_map);
290 }
291
292 /*
293 * new_key - allocates & initialises a new key entry. This function returns
294 * a pointer to the newly allocated key entry.
295 *
296 */
297 static key_entry_t *
298 new_key(void)
299 {
300 key_entry_t *new_one;
301
302 if ((new_one = malloc(sizeof(key_entry_t))) == NULL) {
303 perror("inkey: Cannot allocate new key entry");
304 exit(2);
305 }
306 new_one->type = 0;
307 new_one->value.next = NULL;
308
309 return (new_one);
310 }
311
312 /*
313 * inkey - do the work to process keyboard input, check for multi-key
314 * sequences and return the appropriate symbol if we get a match.
315 *
316 */
317
318 unsigned
319 inkey(to, delay)
320 int to, delay;
321 {
322 int k, nchar;
323 char c;
324 keymap_t *current = base_keymap;
325
326 for (;;) { /* loop until we get a complete key sequence */
327 reread:
328 if (state == INKEY_NORM) {
329 if (delay && __timeout(delay) == ERR)
330 return ERR;
331 if ((nchar = read(STDIN_FILENO, &c, sizeof(char))) < 0)
332 return ERR;
333 if (delay && (__notimeout() == ERR))
334 return ERR;
335 if (nchar == 0)
336 return ERR; /* just in case we are nodelay
337 * mode */
338 k = (unsigned int) c;
339 #ifdef DEBUG
340 __CTRACE("inkey (state normal) got '%s'\n", unctrl(k));
341 #endif
342
343 working = start;
344 inbuf[working] = k;
345 INC_POINTER(working);
346 end = working;
347 state = INKEY_ASSEMBLING; /* go to the assembling
348 * state now */
349 } else if (state == INKEY_BACKOUT) {
350 k = inbuf[working];
351 INC_POINTER(working);
352 if (working == end) { /* see if we have run
353 * out of keys in the
354 * backlog */
355
356 /* if we have then switch to
357 assembling */
358 state = INKEY_ASSEMBLING;
359 }
360 } else if (state == INKEY_ASSEMBLING) {
361 /* assembling a key sequence */
362 if (delay) {
363 if (__timeout(to ? DEFAULT_DELAY : delay) == ERR)
364 return ERR;
365 } else {
366 if (to && (__timeout(DEFAULT_DELAY) == ERR))
367 return ERR;
368 }
369 if ((nchar = read(STDIN_FILENO, &c,
370 sizeof(char))) < 0)
371 return ERR;
372 if ((to || delay) && (__notimeout() == ERR))
373 return ERR;
374
375 k = (unsigned int) c;
376 #ifdef DEBUG
377 __CTRACE("inkey (state assembling) got '%s'\n", unctrl(k));
378 #endif
379 if (nchar == 0) { /* inter-char timeout,
380 * start backing out */
381 if (start == end)
382 /* no chars in the buffer, restart */
383 goto reread;
384
385 k = inbuf[start];
386 state = INKEY_TIMEOUT;
387 } else {
388 inbuf[working] = k;
389 INC_POINTER(working);
390 end = working;
391 }
392 } else {
393 fprintf(stderr, "Inkey state screwed - exiting!!!");
394 exit(2);
395 }
396
397 /* Check key has no special meaning and we have not timed out */
398 if ((current->mapping[k] < 0) || (state == INKEY_TIMEOUT)) {
399 /* return the first key we know about */
400 k = inbuf[start];
401
402 INC_POINTER(start);
403 working = start;
404
405 if (start == end) { /* only one char processed */
406 state = INKEY_NORM;
407 } else {/* otherwise we must have more than one char
408 * to backout */
409 state = INKEY_BACKOUT;
410 }
411 return k;
412 } else { /* must be part of a multikey sequence */
413 /* check for completed key sequence */
414 if (current->key[current->mapping[k]]->type == KEYMAP_LEAF) {
415 start = working; /* eat the key sequence
416 * in inbuf */
417
418 /* check if inbuf empty now */
419 if (start == end) {
420 /* if it is go back to normal */
421 state = INKEY_NORM;
422 } else {
423 /* otherwise go to backout state */
424 state = INKEY_BACKOUT;
425 }
426
427 /* return the symbol */
428 return current->key[current->mapping[k]]->value.symbol;
429
430 } else {
431 /*
432 * Step on to next part of the multi-key
433 * sequence.
434 */
435 current = current->key[current->mapping[k]]->value.next;
436 }
437 }
438 }
439 }
440
441 /*
442 * wgetch --
443 * Read in a character from the window.
444 */
445 int
446 wgetch(win)
447 WINDOW *win;
448 {
449 int inp, weset;
450 int nchar;
451 char c;
452
453 if (!(win->flags & __SCROLLOK) && (win->flags & __FULLWIN)
454 && win->curx == win->maxx - 1 && win->cury == win->maxy - 1
455 && __echoit)
456 return (ERR);
457 #ifdef DEBUG
458 __CTRACE("wgetch: __echoit = %d, __rawmode = %d\n",
459 __echoit, __rawmode);
460 #endif
461 if (__echoit && !__rawmode) {
462 cbreak();
463 weset = 1;
464 } else
465 weset = 0;
466
467 __save_termios();
468
469 if (win->flags & __KEYPAD) {
470 switch (win->delay)
471 {
472 case -1:
473 inp = inkey (win->flags & __NOTIMEOUT ? 0 : 1, 0);
474 break;
475 case 0:
476 if (__nodelay() == ERR) return ERR;
477 inp = inkey(0, 0);
478 break;
479 default:
480 inp = inkey(win->flags & __NOTIMEOUT ? 0 : 1, win->delay);
481 break;
482 }
483 } else {
484 switch (win->delay)
485 {
486 case -1:
487 break;
488 case 0:
489 if (__nodelay() == ERR) {
490 __restore_termios();
491 return ERR;
492 }
493 break;
494 default:
495 if (__timeout(win->delay) == ERR) {
496 __restore_termios();
497 return ERR;
498 }
499 break;
500 }
501
502 if ((nchar = read(STDIN_FILENO, &c, sizeof(char))) < 0) {
503 inp = ERR;
504 } else {
505 if (nchar == 0) {
506 __restore_termios();
507 return ERR; /* we have timed out */
508 }
509 inp = (unsigned int) c;
510 }
511 }
512 #ifdef DEBUG
513 if (inp > 255)
514 /* we have a key symbol - treat it differently */
515 /* XXXX perhaps __unctrl should be expanded to include
516 * XXXX the keysyms in the table....
517 */
518 __CTRACE("wgetch assembled keysym 0x%x\n", inp);
519 else
520 __CTRACE("wgetch got '%s'\n", unctrl(inp));
521 #endif
522 if (win->delay > -1) {
523 if (__delay() == ERR) {
524 __restore_termios();
525 return ERR;
526 }
527 }
528
529 __restore_termios();
530 if (__echoit) {
531 mvwaddch(curscr,
532 (int) (win->cury + win->begy), (int) (win->curx + win->begx), inp);
533 waddch(win, inp);
534 }
535 if (weset)
536 nocbreak();
537
538 return ((inp < 0) || (inp == ERR) ? ERR : inp);
539 }
540