Fix missing includes
[centerim5.git] / cppconsui / libtermkey / driver-ti.c
blob124a530c806e03866e46596f8be7949fb79c1619
1 // we want strdup()
2 #define _XOPEN_SOURCE 600
4 #include "termkey.h"
5 #include "termkey-internal.h"
7 #ifdef HAVE_UNIBILIUM
8 # include <unibilium.h>
9 #else
10 # include <curses.h>
11 # include <term.h>
13 /* curses.h has just poluted our namespace. We want this back */
14 # undef buttons
15 #endif
17 #include <ctype.h>
18 #include <stdio.h>
19 #include <string.h>
20 #include <unistd.h>
22 /* To be efficient at lookups, we store the byte sequence => keyinfo mapping
23 * in a trie. This avoids a slow linear search through a flat list of
24 * sequences. Because it is likely most nodes will be very sparse, we optimise
25 * vector to store an extent map after the database is loaded.
28 typedef enum {
29 TYPE_KEY,
30 TYPE_ARR,
31 TYPE_MOUSE,
32 } trie_nodetype;
34 struct trie_node {
35 trie_nodetype type;
38 struct trie_node_key {
39 trie_nodetype type;
40 struct keyinfo key;
43 struct trie_node_arr {
44 trie_nodetype type;
45 unsigned char min, max; /* INCLUSIVE endpoints of the extent range */
46 struct trie_node *arr[]; /* dynamic size at allocation time */
49 typedef struct {
50 TermKey *tk;
52 struct trie_node *root;
54 char *start_string;
55 char *stop_string;
56 } TermKeyTI;
58 static int funcname2keysym(const char *funcname, TermKeyType *typep, TermKeySym *symp, int *modmask, int *modsetp);
59 static int insert_seq(TermKeyTI *ti, const char *seq, struct trie_node *node);
61 static struct trie_node *new_node_key(TermKeyType type, TermKeySym sym, int modmask, int modset)
63 struct trie_node_key *n = malloc(sizeof(*n));
64 if(!n)
65 return NULL;
67 n->type = TYPE_KEY;
69 n->key.type = type;
70 n->key.sym = sym;
71 n->key.modifier_mask = modmask;
72 n->key.modifier_set = modset;
74 return (struct trie_node*)n;
77 static struct trie_node *new_node_arr(unsigned char min, unsigned char max)
79 struct trie_node_arr *n = malloc(sizeof(*n) + ((int)max-min+1) * sizeof(n->arr[0]));
80 if(!n)
81 return NULL;
83 n->type = TYPE_ARR;
84 n->min = min; n->max = max;
86 int i;
87 for(i = min; i <= max; i++)
88 n->arr[i-min] = NULL;
90 return (struct trie_node*)n;
93 static struct trie_node *lookup_next(struct trie_node *n, unsigned char b)
95 switch(n->type) {
96 case TYPE_KEY:
97 case TYPE_MOUSE:
98 fprintf(stderr, "ABORT: lookup_next within a TYPE_KEY node\n");
99 abort();
100 case TYPE_ARR:
102 struct trie_node_arr *nar = (struct trie_node_arr*)n;
103 if(b < nar->min || b > nar->max)
104 return NULL;
105 return nar->arr[b - nar->min];
109 return NULL; // Never reached but keeps compiler happy
112 static void free_trie(struct trie_node *n)
114 switch(n->type) {
115 case TYPE_KEY:
116 case TYPE_MOUSE:
117 break;
118 case TYPE_ARR:
120 struct trie_node_arr *nar = (struct trie_node_arr*)n;
121 int i;
122 for(i = nar->min; i <= nar->max; i++)
123 if(nar->arr[i - nar->min])
124 free_trie(nar->arr[i - nar->min]);
125 break;
129 free(n);
132 static struct trie_node *compress_trie(struct trie_node *n)
134 if(!n)
135 return NULL;
137 switch(n->type) {
138 case TYPE_KEY:
139 case TYPE_MOUSE:
140 return n;
141 case TYPE_ARR:
143 struct trie_node_arr *nar = (struct trie_node_arr*)n;
144 unsigned char min, max;
145 // Find the real bounds
146 for(min = 0; !nar->arr[min]; min++)
148 for(max = 0xff; !nar->arr[max]; max--)
151 struct trie_node_arr *new = (struct trie_node_arr*)new_node_arr(min, max);
152 int i;
153 for(i = min; i <= max; i++)
154 new->arr[i - min] = compress_trie(nar->arr[i]);
156 free(nar);
157 return (struct trie_node*)new;
161 return n;
164 static int load_terminfo(TermKeyTI *ti, const char *term)
166 int i;
168 #ifdef HAVE_UNIBILIUM
169 unibi_term *unibi = unibi_from_term(term);
170 if(!unibi)
171 return 0;
172 #else
173 int err;
175 /* Have to cast away the const. But it's OK - we know terminfo won't really
176 * modify term */
177 if(setupterm((char*)term, 1, &err) != OK)
178 return 0;
179 #endif
181 #ifdef HAVE_UNIBILIUM
182 for(i = unibi_string_begin_+1; i < unibi_string_end_; i++)
183 #else
184 for(i = 0; strfnames[i]; i++)
185 #endif
187 // Only care about the key_* constants
188 #ifdef HAVE_UNIBILIUM
189 const char *name = unibi_name_str(i);
190 #else
191 const char *name = strfnames[i];
192 #endif
193 if(strncmp(name, "key_", 4) != 0)
194 continue;
196 #ifdef HAVE_UNIBILIUM
197 const char *value = unibi_get_str(unibi, i);
198 #else
199 const char *value = tigetstr(strnames[i]);
200 #endif
201 if(!value || value == (char*)-1)
202 continue;
204 struct trie_node *node = NULL;
206 if(strcmp(name + 4, "mouse") == 0) {
207 node = malloc(sizeof(*node));
208 if(!node)
209 return 0;
211 node->type = TYPE_MOUSE;
213 else {
214 TermKeyType type;
215 TermKeySym sym;
216 int mask = 0;
217 int set = 0;
219 if(!funcname2keysym(name + 4, &type, &sym, &mask, &set))
220 continue;
222 if(sym == TERMKEY_SYM_NONE)
223 continue;
225 node = new_node_key(type, sym, mask, set);
228 if(node)
229 if(!insert_seq(ti, value, node)) {
230 free(node);
231 return 0;
235 /* Take copies of these terminfo strings, in case we build multiple termkey
236 * instances for multiple different termtypes, and it's different by the
237 * time we want to use it
239 #ifdef HAVE_UNIBILIUM
240 const char *keypad_xmit = unibi_get_str(unibi, unibi_pkey_xmit);
241 #endif
243 if(keypad_xmit)
244 ti->start_string = strdup(keypad_xmit);
245 else
246 ti->start_string = NULL;
248 #ifdef HAVE_UNIBILIUM
249 const char *keypad_local = unibi_get_str(unibi, unibi_pkey_local);
250 #endif
252 if(keypad_local)
253 ti->stop_string = strdup(keypad_local);
254 else
255 ti->stop_string = NULL;
257 #ifdef HAVE_UNIBILIUM
258 unibi_destroy(unibi);
259 #endif
261 return 1;
264 static void *new_driver(TermKey *tk, const char *term)
266 TermKeyTI *ti = malloc(sizeof *ti);
267 if(!ti)
268 return NULL;
270 ti->tk = tk;
272 ti->root = new_node_arr(0, 0xff);
273 if(!ti->root)
274 goto abort_free_ti;
276 if(!load_terminfo(ti, term))
277 goto abort_free_trie;
279 ti->root = compress_trie(ti->root);
281 return ti;
283 abort_free_trie:
284 free_trie(ti->root);
286 abort_free_ti:
287 free(ti);
289 return NULL;
292 static void start_driver(TermKey *tk, void *info)
294 TermKeyTI *ti = info;
296 /* The terminfo database will contain keys in application cursor key mode.
297 * We may need to enable that mode
299 if(ti->start_string) {
300 // Can't call putp or tputs because they suck and don't give us fd control
301 write(tk->fd, ti->start_string, strlen(ti->start_string));
305 static void stop_driver(TermKey *tk, void *info)
307 TermKeyTI *ti = info;
309 if(ti->stop_string) {
310 // Can't call putp or tputs because they suck and don't give us fd control
311 write(tk->fd, ti->stop_string, strlen(ti->stop_string));
315 static void free_driver(void *info)
317 TermKeyTI *ti = info;
319 free_trie(ti->root);
321 if(ti->start_string)
322 free(ti->start_string);
324 if(ti->stop_string)
325 free(ti->stop_string);
327 free(ti);
330 #define CHARAT(i) (tk->buffer[tk->buffstart + (i)])
332 static TermKeyResult peekkey(TermKey *tk, void *info, TermKeyKey *key, int force, size_t *nbytep)
334 TermKeyTI *ti = info;
336 if(tk->buffcount == 0)
337 return tk->is_closed ? TERMKEY_RES_EOF : TERMKEY_RES_NONE;
339 struct trie_node *p = ti->root;
341 unsigned int pos = 0;
342 while(pos < tk->buffcount) {
343 p = lookup_next(p, CHARAT(pos));
344 if(!p)
345 break;
347 pos++;
349 if(p->type == TYPE_KEY) {
350 struct trie_node_key *nk = (struct trie_node_key*)p;
351 key->type = nk->key.type;
352 key->code.sym = nk->key.sym;
353 key->modifiers = nk->key.modifier_set;
354 *nbytep = pos;
355 return TERMKEY_RES_KEY;
357 else if(p->type == TYPE_MOUSE) {
358 tk->buffstart += pos;
359 tk->buffcount -= pos;
361 TermKeyResult mouse_result = (*tk->method.peekkey_mouse)(tk, key, nbytep);
363 tk->buffstart -= pos;
364 tk->buffcount += pos;
366 if(mouse_result == TERMKEY_RES_KEY)
367 *nbytep += pos;
369 return mouse_result;
373 // If p is not NULL then we hadn't walked off the end yet, so we have a
374 // partial match
375 if(p && !force)
376 return TERMKEY_RES_AGAIN;
378 return TERMKEY_RES_NONE;
381 static struct {
382 const char *funcname;
383 TermKeyType type;
384 TermKeySym sym;
385 int mods;
386 } funcs[] =
388 /* THIS LIST MUST REMAIN SORTED! */
389 { "backspace", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_BACKSPACE, 0 },
390 { "begin", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_BEGIN, 0 },
391 { "beg", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_BEGIN, 0 },
392 { "btab", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_TAB, TERMKEY_KEYMOD_SHIFT },
393 { "cancel", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_CANCEL, 0 },
394 { "clear", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_CLEAR, 0 },
395 { "close", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_CLOSE, 0 },
396 { "command", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_COMMAND, 0 },
397 { "copy", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_COPY, 0 },
398 { "dc", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_DELETE, 0 },
399 { "down", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_DOWN, 0 },
400 { "end", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_END, 0 },
401 { "enter", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_ENTER, 0 },
402 { "exit", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_EXIT, 0 },
403 { "find", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_FIND, 0 },
404 { "help", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_HELP, 0 },
405 { "home", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_HOME, 0 },
406 { "ic", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_INSERT, 0 },
407 { "left", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_LEFT, 0 },
408 { "mark", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_MARK, 0 },
409 { "message", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_MESSAGE, 0 },
410 { "mouse", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_NONE, 0 },
411 { "move", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_MOVE, 0 },
412 { "next", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEDOWN, 0 }, // Not quite, but it's the best we can do
413 { "npage", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEDOWN, 0 },
414 { "open", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_OPEN, 0 },
415 { "options", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_OPTIONS, 0 },
416 { "ppage", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEUP, 0 },
417 { "previous", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEUP, 0 }, // Not quite, but it's the best we can do
418 { "print", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PRINT, 0 },
419 { "redo", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_REDO, 0 },
420 { "reference", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_REFERENCE, 0 },
421 { "refresh", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_REFRESH, 0 },
422 { "replace", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_REPLACE, 0 },
423 { "restart", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_RESTART, 0 },
424 { "resume", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_RESUME, 0 },
425 { "right", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_RIGHT, 0 },
426 { "save", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_SAVE, 0 },
427 { "select", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_SELECT, 0 },
428 { "suspend", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_SUSPEND, 0 },
429 { "undo", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_UNDO, 0 },
430 { "up", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_UP, 0 },
431 { NULL, 0, 0, 0 },
434 static int funcname2keysym(const char *funcname, TermKeyType *typep, TermKeySym *symp, int *modmaskp, int *modsetp)
436 // Binary search
438 int start = 0;
439 int end = sizeof(funcs)/sizeof(funcs[0]); // is "one past" the end of the range
441 while(1) {
442 int i = (start+end) / 2;
443 int cmp = strcmp(funcname, funcs[i].funcname);
445 if(cmp == 0) {
446 *typep = funcs[i].type;
447 *symp = funcs[i].sym;
448 *modmaskp = funcs[i].mods;
449 *modsetp = funcs[i].mods;
450 return 1;
452 else if(end == start + 1)
453 // That was our last choice and it wasn't it - not found
454 break;
455 else if(cmp > 0)
456 start = i;
457 else
458 end = i;
461 if(funcname[0] == 'f' && isdigit(funcname[1])) {
462 *typep = TERMKEY_TYPE_FUNCTION;
463 *symp = atoi(funcname + 1);
464 return 1;
467 // Last-ditch attempt; maybe it's a shift key?
468 if(funcname[0] == 's' && funcname2keysym(funcname + 1, typep, symp, modmaskp, modsetp)) {
469 *modmaskp |= TERMKEY_KEYMOD_SHIFT;
470 *modsetp |= TERMKEY_KEYMOD_SHIFT;
471 return 1;
474 #ifdef DEBUG
475 fprintf(stderr, "TODO: Need to convert funcname %s to a type/sym\n", funcname);
476 #endif
478 return 0;
481 static int insert_seq(TermKeyTI *ti, const char *seq, struct trie_node *node)
483 int pos = 0;
484 struct trie_node *p = ti->root;
486 // Unsigned because we'll be using it as an array subscript
487 unsigned char b;
489 while((b = seq[pos])) {
490 struct trie_node *next = lookup_next(p, b);
491 if(!next)
492 break;
493 p = next;
494 pos++;
497 while((b = seq[pos])) {
498 struct trie_node *next;
499 if(seq[pos+1])
500 // Intermediate node
501 next = new_node_arr(0, 0xff);
502 else
503 // Final key node
504 next = node;
506 if(!next)
507 return 0;
509 switch(p->type) {
510 case TYPE_ARR:
512 struct trie_node_arr *nar = (struct trie_node_arr*)p;
513 if(b < nar->min || b > nar->max) {
514 fprintf(stderr, "ASSERT FAIL: Trie insert at 0x%02x is outside of extent bounds (0x%02x..0x%02x)\n",
515 b, nar->min, nar->max);
516 abort();
518 nar->arr[b - nar->min] = next;
519 p = next;
520 break;
522 case TYPE_KEY:
523 case TYPE_MOUSE:
524 fprintf(stderr, "ASSERT FAIL: Tried to insert child node in TYPE_KEY\n");
525 abort();
528 pos++;
531 return 1;
534 struct TermKeyDriver termkey_driver_ti = {
535 .name = "terminfo",
537 .new_driver = new_driver,
538 .free_driver = free_driver,
540 .start_driver = start_driver,
541 .stop_driver = stop_driver,
543 .peekkey = peekkey,