getch.c revision 1.11 1 /* $NetBSD: getch.c,v 1.11 1999/06/06 20:43:00 pk 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.11 1999/06/06 20:43:00 pk 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 char inbuf[INBUF_SZ];
95 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 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 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 const int num_tcs = (sizeof(tc) / sizeof(struct tcdata));
155
156 /* The root keymap */
157
158 keymap_t *base_keymap;
159
160 /* prototypes for private functions */
161 keymap_t *
162 new_keymap(void); /* create a new keymap */
163
164 key_entry_t *
165 new_key(void); /* create a new key entry */
166
167 unsigned
168 inkey(int, int);
169
170 /*
171 * Init_getch - initialise all the pointers & structures needed to make
172 * getch work in keypad mode.
173 *
174 */
175 void
176 __init_getch(sp)
177 char *sp;
178 {
179 int i, j, length;
180 keymap_t *current;
181 static char termcap[1024];
182 char entry[1024], termname[1024], *p;
183 key_entry_t *the_key;
184
185 /* init the inkey state variable */
186 state = INKEY_NORM;
187
188 /* init the base keymap */
189 base_keymap = new_keymap();
190
191 /* key input buffer pointers */
192 start = end = working = 0;
193
194 /* now do the termcap snarfing ... */
195 strncpy(termname, sp, 1022);
196 termname[1023] = 0;
197
198 if (tgetent(termcap, termname) > 0) {
199 for (i = 0; i < num_tcs; i++) {
200 p = entry;
201 if (tgetstr(tc[i].name, &p) != NULL) {
202 current = base_keymap; /* always start with
203 * base keymap. */
204 length = strlen(entry);
205
206 for (j = 0; j < length - 1; j++) {
207 if (current->mapping[(unsigned) entry[j]] < 0) {
208 /* first time for this char */
209 current->mapping[(unsigned) entry[j]] = current->count; /* map new entry */
210 the_key = new_key();
211 /* multikey coz we are here */
212 the_key->type = KEYMAP_MULTI;
213
214 /* need for next key */
215 the_key->value.next
216 = new_keymap();
217
218 /* put into key array */
219 if ((current->key = realloc(current->key, (current->count + 1) * sizeof(key_entry_t *))) == NULL) {
220 fprintf(stderr,
221 "Could not malloc for key entry\n");
222 exit(1);
223 }
224
225 current->key[current->count++]
226 = the_key;
227
228 }
229 /* next key uses this map... */
230 current = current->key[current->mapping[(unsigned) entry[j]]]->value.next;
231 }
232
233 /* this is the last key in the sequence (it
234 * may have been the only one but that does
235 * not matter) this means it is a leaf key and
236 * should have a symbol associated with it */
237 if (current->count > 0) {
238 /* if there were other keys then
239 we need to extend the mapping
240 array */
241 if ((current->key =
242 realloc(current->key,
243 (current->count + 1) *
244 sizeof(key_entry_t *)))
245 == NULL) {
246 fprintf(stderr,
247 "Could not malloc for key entry\n");
248 exit(1);
249 }
250 }
251 current->mapping[(unsigned) entry[length - 1]]
252 = current->count;
253 the_key = new_key();
254 the_key->type = KEYMAP_LEAF; /* leaf key */
255
256 /* the associated symbol */
257 the_key->value.symbol = tc[i].symbol;
258 current->key[current->count++] = the_key;
259 }
260 }
261 }
262 }
263
264
265 /*
266 * new_keymap - allocates & initialises a new keymap structure. This
267 * function returns a pointer to the new keymap.
268 *
269 */
270 keymap_t *
271 new_keymap(void)
272 {
273 int i;
274 keymap_t *new_map;
275
276 if ((new_map = malloc(sizeof(keymap_t))) == NULL) {
277 perror("Inkey: Cannot allocate new keymap");
278 exit(2);
279 }
280 /* initialise the new map */
281 new_map->count = 0;
282 for (i = 0; i < MAX_CHAR; i++) {
283 new_map->mapping[i] = -1; /* no mapping for char */
284 }
285
286 /* one does assume there will be at least one key mapped.... */
287 if ((new_map->key = malloc(sizeof(key_entry_t *))) == NULL) {
288 perror("Could not malloc first key ent");
289 exit(1);
290 }
291
292 return new_map;
293 }
294
295 /*
296 * new_key - allocates & initialises a new key entry. This function returns
297 * a pointer to the newly allocated key entry.
298 *
299 */
300 key_entry_t *
301 new_key(void)
302 {
303 key_entry_t *new_one;
304
305 if ((new_one = malloc(sizeof(key_entry_t))) == NULL) {
306 perror("inkey: Cannot allocate new key entry");
307 exit(2);
308 }
309 new_one->type = 0;
310 new_one->value.next = NULL;
311
312 return new_one;
313 }
314
315 /*
316 * inkey - do the work to process keyboard input, check for multi-key
317 * sequences and return the appropriate symbol if we get a match.
318 *
319 */
320
321 unsigned
322 inkey(to, delay)
323 int to, delay;
324 {
325 int k, nchar;
326 char c;
327 keymap_t *current = base_keymap;
328
329 for (;;) { /* loop until we get a complete key sequence */
330 reread:
331 if (state == INKEY_NORM) {
332 if (delay && __timeout(delay) == ERR)
333 return ERR;
334 if ((nchar = read(STDIN_FILENO, &c, sizeof(char))) < 0)
335 return ERR;
336 if (delay && (__notimeout() == ERR))
337 return ERR;
338 if (nchar == 0)
339 return ERR; /* just in case we are nodelay
340 * mode */
341 k = (unsigned int) c;
342 #ifdef DEBUG
343 __CTRACE("inkey (state normal) got '%s'\n", unctrl(k));
344 #endif
345
346 working = start;
347 inbuf[working] = k;
348 INC_POINTER(working);
349 end = working;
350 state = INKEY_ASSEMBLING; /* go to the assembling
351 * state now */
352 } else
353 if (state == INKEY_BACKOUT) {
354 k = inbuf[working];
355 INC_POINTER(working);
356 if (working == end) { /* see if we have run
357 * out of keys in the
358 * backlog */
359
360 /* if we have then switch to
361 assembling */
362 state = INKEY_ASSEMBLING;
363 }
364 } else if (state == INKEY_ASSEMBLING) {
365 /* assembling a key sequence */
366 if (delay)
367 {
368 if (__timeout(to ? DEFAULT_DELAY : delay) == ERR)
369 return ERR;
370 } else {
371 if (to && (__timeout(DEFAULT_DELAY) == ERR))
372 return ERR;
373 }
374 if ((nchar = read(STDIN_FILENO, &c,
375 sizeof(char))) < 0)
376 return ERR;
377 if ((to || delay) && (__notimeout() == ERR))
378 return ERR;
379
380 k = (unsigned int) c;
381 #ifdef DEBUG
382 __CTRACE("inkey (state assembling) got '%s'\n", unctrl(k));
383 #endif
384 if (nchar == 0) { /* inter-char timeout,
385 * start backing out */
386 if (start == end)
387 goto reread; /* no chars in the
388 * buffer, restart */
389 k = inbuf[start];
390 state = INKEY_TIMEOUT;
391 } else {
392 inbuf[working] = k;
393 INC_POINTER(working);
394 end = working;
395 }
396 } else {
397 fprintf(stderr,
398 "Inkey state screwed - exiting!!!");
399 exit(2);
400 }
401
402 /* Check key has no special meaning and we have not timed out */
403 if ((current->mapping[k] < 0) || (state == INKEY_TIMEOUT)) {
404 k = inbuf[start]; /* return the first key we
405 * know about */
406
407 INC_POINTER(start);
408 working = start;
409
410 if (start == end) { /* only one char processed */
411 state = INKEY_NORM;
412 } else {/* otherwise we must have more than one char
413 * to backout */
414 state = INKEY_BACKOUT;
415 }
416 return k;
417 } else { /* must be part of a multikey sequence */
418 /* check for completed key sequence */
419 if (current->key[current->mapping[k]]->type == KEYMAP_LEAF) {
420 start = working; /* eat the key sequence
421 * in inbuf */
422
423 if (start == end) { /* check if inbuf empty
424 * now */
425 state = INKEY_NORM; /* if it is go
426 back to normal */
427 } else { /* otherwise go to backout
428 * state */
429 state = INKEY_BACKOUT;
430 }
431
432 /* return the symbol */
433 return current->key[current->mapping[k]]->value.symbol;
434
435 } else {/* step on to next part of the multi-key
436 * sequence */
437 current = current->key[current->mapping[k]]->value.next;
438 }
439 }
440 }
441 }
442
443 /*
444 * wgetch --
445 * Read in a character from the window.
446 */
447 int
448 wgetch(win)
449 WINDOW *win;
450 {
451 int inp, weset;
452 int nchar;
453 char c;
454
455 if (!(win->flags & __SCROLLOK) && (win->flags & __FULLWIN)
456 && win->curx == win->maxx - 1 && win->cury == win->maxy - 1
457 && __echoit)
458 return (ERR);
459 #ifdef DEBUG
460 __CTRACE("wgetch: __echoit = %d, __rawmode = %d\n",
461 __echoit, __rawmode);
462 #endif
463 if (__echoit && !__rawmode) {
464 cbreak();
465 weset = 1;
466 } else
467 weset = 0;
468
469 __save_termios();
470
471 if (win->flags & __KEYPAD) {
472 switch (win->delay)
473 {
474 case -1:
475 inp = inkey (win->flags & __NOTIMEOUT ? 0 : 1, 0);
476 break;
477 case 0:
478 if (__nodelay() == ERR) return ERR;
479 inp = inkey(0, 0);
480 break;
481 default:
482 inp = inkey(win->flags & __NOTIMEOUT ? 0 : 1, win->delay);
483 break;
484 }
485 } else {
486 switch (win->delay)
487 {
488 case -1:
489 break;
490 case 0:
491 if (__nodelay() == ERR) {
492 __restore_termios();
493 return ERR;
494 }
495 break;
496 default:
497 if (__timeout(win->delay) == ERR) {
498 __restore_termios();
499 return ERR;
500 }
501 break;
502 }
503 if ((nchar = read(STDIN_FILENO, &c, sizeof(char))) < 0)
504 inp = ERR;
505 else {
506 if (nchar == 0) {
507 __restore_termios();
508 return ERR; /* we have timed out */
509 }
510 inp = (unsigned int) c;
511 }
512 }
513 #ifdef DEBUG
514 __CTRACE("wgetch got '%s'\n", unctrl(inp));
515 #endif
516 if (win->delay > -1)
517 if (__delay() == ERR) {
518 __restore_termios();
519 return ERR;
520 }
521 __restore_termios();
522 if (__echoit) {
523 mvwaddch(curscr,
524 (int) (win->cury + win->begy), (int) (win->curx + win->begx), inp);
525 waddch(win, inp);
526 }
527 if (weset)
528 nocbreak();
529 return ((inp < 0) || (inp == ERR) ? ERR : inp);
530 }
531