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