2 #define _XOPEN_SOURCE 600
5 #include "termkey-internal.h"
8 # include <unibilium.h>
13 /* curses.h has just poluted our namespace. We want this back */
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.
38 struct trie_node_key
{
43 struct trie_node_arr
{
45 unsigned char min
, max
; /* INCLUSIVE endpoints of the extent range */
46 struct trie_node
*arr
[]; /* dynamic size at allocation time */
52 struct trie_node
*root
;
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
));
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]));
84 n
->min
= min
; n
->max
= max
;
87 for(i
= min
; i
<= max
; i
++)
90 return (struct trie_node
*)n
;
93 static struct trie_node
*lookup_next(struct trie_node
*n
, unsigned char b
)
98 fprintf(stderr
, "ABORT: lookup_next within a TYPE_KEY node\n");
102 struct trie_node_arr
*nar
= (struct trie_node_arr
*)n
;
103 if(b
< nar
->min
|| b
> nar
->max
)
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
)
120 struct trie_node_arr
*nar
= (struct trie_node_arr
*)n
;
122 for(i
= nar
->min
; i
<= nar
->max
; i
++)
123 if(nar
->arr
[i
- nar
->min
])
124 free_trie(nar
->arr
[i
- nar
->min
]);
132 static struct trie_node
*compress_trie(struct trie_node
*n
)
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
);
153 for(i
= min
; i
<= max
; i
++)
154 new->arr
[i
- min
] = compress_trie(nar
->arr
[i
]);
157 return (struct trie_node
*)new;
164 static int load_terminfo(TermKeyTI
*ti
, const char *term
)
168 #ifdef HAVE_UNIBILIUM
169 unibi_term
*unibi
= unibi_from_term(term
);
175 /* Have to cast away the const. But it's OK - we know terminfo won't really
177 if(setupterm((char*)term
, 1, &err
) != OK
)
181 #ifdef HAVE_UNIBILIUM
182 for(i
= unibi_string_begin_
+1; i
< unibi_string_end_
; i
++)
184 for(i
= 0; strfnames
[i
]; i
++)
187 // Only care about the key_* constants
188 #ifdef HAVE_UNIBILIUM
189 const char *name
= unibi_name_str(i
);
191 const char *name
= strfnames
[i
];
193 if(strncmp(name
, "key_", 4) != 0)
196 #ifdef HAVE_UNIBILIUM
197 const char *value
= unibi_get_str(unibi
, i
);
199 const char *value
= tigetstr(strnames
[i
]);
201 if(!value
|| value
== (char*)-1)
204 struct trie_node
*node
= NULL
;
206 if(strcmp(name
+ 4, "mouse") == 0) {
207 node
= malloc(sizeof(*node
));
211 node
->type
= TYPE_MOUSE
;
219 if(!funcname2keysym(name
+ 4, &type
, &sym
, &mask
, &set
))
222 if(sym
== TERMKEY_SYM_NONE
)
225 node
= new_node_key(type
, sym
, mask
, set
);
229 if(!insert_seq(ti
, value
, node
)) {
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
);
244 ti
->start_string
= strdup(keypad_xmit
);
246 ti
->start_string
= NULL
;
248 #ifdef HAVE_UNIBILIUM
249 const char *keypad_local
= unibi_get_str(unibi
, unibi_pkey_local
);
253 ti
->stop_string
= strdup(keypad_local
);
255 ti
->stop_string
= NULL
;
257 #ifdef HAVE_UNIBILIUM
258 unibi_destroy(unibi
);
264 static void *new_driver(TermKey
*tk
, const char *term
)
266 TermKeyTI
*ti
= malloc(sizeof *ti
);
272 ti
->root
= new_node_arr(0, 0xff);
276 if(!load_terminfo(ti
, term
))
277 goto abort_free_trie
;
279 ti
->root
= compress_trie(ti
->root
);
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
;
322 free(ti
->start_string
);
325 free(ti
->stop_string
);
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
));
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
;
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
)
373 // If p is not NULL then we hadn't walked off the end yet, so we have a
376 return TERMKEY_RES_AGAIN
;
378 return TERMKEY_RES_NONE
;
382 const char *funcname
;
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 },
434 static int funcname2keysym(const char *funcname
, TermKeyType
*typep
, TermKeySym
*symp
, int *modmaskp
, int *modsetp
)
439 int end
= sizeof(funcs
)/sizeof(funcs
[0]); // is "one past" the end of the range
442 int i
= (start
+end
) / 2;
443 int cmp
= strcmp(funcname
, funcs
[i
].funcname
);
446 *typep
= funcs
[i
].type
;
447 *symp
= funcs
[i
].sym
;
448 *modmaskp
= funcs
[i
].mods
;
449 *modsetp
= funcs
[i
].mods
;
452 else if(end
== start
+ 1)
453 // That was our last choice and it wasn't it - not found
461 if(funcname
[0] == 'f' && isdigit(funcname
[1])) {
462 *typep
= TERMKEY_TYPE_FUNCTION
;
463 *symp
= atoi(funcname
+ 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
;
475 fprintf(stderr
, "TODO: Need to convert funcname %s to a type/sym\n", funcname
);
481 static int insert_seq(TermKeyTI
*ti
, const char *seq
, struct trie_node
*node
)
484 struct trie_node
*p
= ti
->root
;
486 // Unsigned because we'll be using it as an array subscript
489 while((b
= seq
[pos
])) {
490 struct trie_node
*next
= lookup_next(p
, b
);
497 while((b
= seq
[pos
])) {
498 struct trie_node
*next
;
501 next
= new_node_arr(0, 0xff);
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
);
518 nar
->arr
[b
- nar
->min
] = next
;
524 fprintf(stderr
, "ASSERT FAIL: Tried to insert child node in TYPE_KEY\n");
534 struct TermKeyDriver termkey_driver_ti
= {
537 .new_driver
= new_driver
,
538 .free_driver
= free_driver
,
540 .start_driver
= start_driver
,
541 .stop_driver
= stop_driver
,