2 #include "termkey-internal.h"
13 #define strcaseeq(a,b) (strcasecmp(a,b) == 0)
15 void termkey_check_version(int major
, int minor
)
17 if(major
!= TERMKEY_VERSION_MAJOR
) {
18 fprintf(stderr
, "libtermkey major version mismatch; %d (wants) != %d (library)\n",
19 major
, TERMKEY_VERSION_MAJOR
);
23 if(minor
> TERMKEY_VERSION_MINOR
) {
24 fprintf(stderr
, "libtermkey minor version mismatch; %d (wants) > %d (library)\n",
25 minor
, TERMKEY_VERSION_MINOR
);
32 static struct TermKeyDriver
*drivers
[] = {
38 // Forwards for the "protected" methods
39 // static void eat_bytes(TermKey *tk, size_t count);
40 static void emit_codepoint(TermKey
*tk
, long codepoint
, TermKeyKey
*key
);
41 static TermKeyResult
peekkey_simple(TermKey
*tk
, TermKeyKey
*key
, int force
, size_t *nbytes
);
42 static TermKeyResult
peekkey_mouse(TermKey
*tk
, TermKeyKey
*key
, size_t *nbytes
);
44 static TermKeySym
register_c0(TermKey
*tk
, TermKeySym sym
, unsigned char ctrl
, const char *name
);
45 static TermKeySym
register_c0_full(TermKey
*tk
, TermKeySym sym
, int modifier_set
, int modifier_mask
, unsigned char ctrl
, const char *name
);
51 { TERMKEY_SYM_NONE
, "NONE" },
52 { TERMKEY_SYM_BACKSPACE
, "Backspace" },
53 { TERMKEY_SYM_TAB
, "Tab" },
54 { TERMKEY_SYM_ENTER
, "Enter" },
55 { TERMKEY_SYM_ESCAPE
, "Escape" },
56 { TERMKEY_SYM_SPACE
, "Space" },
57 { TERMKEY_SYM_DEL
, "DEL" },
58 { TERMKEY_SYM_UP
, "Up" },
59 { TERMKEY_SYM_DOWN
, "Down" },
60 { TERMKEY_SYM_LEFT
, "Left" },
61 { TERMKEY_SYM_RIGHT
, "Right" },
62 { TERMKEY_SYM_BEGIN
, "Begin" },
63 { TERMKEY_SYM_FIND
, "Find" },
64 { TERMKEY_SYM_INSERT
, "Insert" },
65 { TERMKEY_SYM_DELETE
, "Delete" },
66 { TERMKEY_SYM_SELECT
, "Select" },
67 { TERMKEY_SYM_PAGEUP
, "PageUp" },
68 { TERMKEY_SYM_PAGEDOWN
, "PageDown" },
69 { TERMKEY_SYM_HOME
, "Home" },
70 { TERMKEY_SYM_END
, "End" },
71 { TERMKEY_SYM_CANCEL
, "Cancel" },
72 { TERMKEY_SYM_CLEAR
, "Clear" },
73 { TERMKEY_SYM_CLOSE
, "Close" },
74 { TERMKEY_SYM_COMMAND
, "Command" },
75 { TERMKEY_SYM_COPY
, "Copy" },
76 { TERMKEY_SYM_EXIT
, "Exit" },
77 { TERMKEY_SYM_HELP
, "Help" },
78 { TERMKEY_SYM_MARK
, "Mark" },
79 { TERMKEY_SYM_MESSAGE
, "Message" },
80 { TERMKEY_SYM_MOVE
, "Move" },
81 { TERMKEY_SYM_OPEN
, "Open" },
82 { TERMKEY_SYM_OPTIONS
, "Options" },
83 { TERMKEY_SYM_PRINT
, "Print" },
84 { TERMKEY_SYM_REDO
, "Redo" },
85 { TERMKEY_SYM_REFERENCE
, "Reference" },
86 { TERMKEY_SYM_REFRESH
, "Refresh" },
87 { TERMKEY_SYM_REPLACE
, "Replace" },
88 { TERMKEY_SYM_RESTART
, "Restart" },
89 { TERMKEY_SYM_RESUME
, "Resume" },
90 { TERMKEY_SYM_SAVE
, "Save" },
91 { TERMKEY_SYM_SUSPEND
, "Suspend" },
92 { TERMKEY_SYM_UNDO
, "Undo" },
93 { TERMKEY_SYM_KP0
, "KP0" },
94 { TERMKEY_SYM_KP1
, "KP1" },
95 { TERMKEY_SYM_KP2
, "KP2" },
96 { TERMKEY_SYM_KP3
, "KP3" },
97 { TERMKEY_SYM_KP4
, "KP4" },
98 { TERMKEY_SYM_KP5
, "KP5" },
99 { TERMKEY_SYM_KP6
, "KP6" },
100 { TERMKEY_SYM_KP7
, "KP7" },
101 { TERMKEY_SYM_KP8
, "KP8" },
102 { TERMKEY_SYM_KP9
, "KP9" },
103 { TERMKEY_SYM_KPENTER
, "KPEnter" },
104 { TERMKEY_SYM_KPPLUS
, "KPPlus" },
105 { TERMKEY_SYM_KPMINUS
, "KPMinus" },
106 { TERMKEY_SYM_KPMULT
, "KPMult" },
107 { TERMKEY_SYM_KPDIV
, "KPDiv" },
108 { TERMKEY_SYM_KPCOMMA
, "KPComma" },
109 { TERMKEY_SYM_KPPERIOD
, "KPPeriod" },
110 { TERMKEY_SYM_KPEQUALS
, "KPEquals" },
114 #define CHARAT(i) (tk->buffer[tk->buffstart + (i)])
117 /* Some internal deubgging functions */
119 static void print_buffer(TermKey
*tk
)
122 for(i
= 0; i
< tk
->buffcount
&& i
< 20; i
++)
123 fprintf(stderr
, "%02x ", CHARAT(i
));
124 if(tk
->buffcount
> 20)
125 fprintf(stderr
, "...");
128 static void print_key(TermKey
*tk
, TermKeyKey
*key
)
131 case TERMKEY_TYPE_UNICODE
:
132 fprintf(stderr
, "Unicode codepoint=U+%04lx utf8='%s'", key
->code
.codepoint
, key
->utf8
);
134 case TERMKEY_TYPE_FUNCTION
:
135 fprintf(stderr
, "Function F%d", key
->code
.number
);
137 case TERMKEY_TYPE_KEYSYM
:
138 fprintf(stderr
, "Keysym sym=%d(%s)", key
->code
.sym
, termkey_get_keyname(tk
, key
->code
.sym
));
140 case TERMKEY_TYPE_MOUSE
:
142 TermKeyMouseEvent ev
;
143 int button
, line
, col
;
144 termkey_interpret_mouse(tk
, key
, &ev
, &button
, &line
, &col
);
145 fprintf(stderr
, "Mouse ev=%d button=%d pos=(%d,%d)\n", ev
, button
, line
, col
);
148 case TERMKEY_TYPE_POSITION
:
151 termkey_interpret_position(tk
, key
, &line
, &col
);
152 fprintf(stderr
, "Position report pos=(%d,%d)\n", line
, col
);
155 case TERMKEY_TYPE_MODEREPORT
:
157 int initial
, mode
, value
;
158 termkey_interpret_modereport(tk
, key
, &initial
, &mode
, &value
);
159 fprintf(stderr
, "Mode report mode=%s %d val=%d\n", initial
== '?' ? "DEC" : "ANSI", mode
, value
);
162 case TERMKEY_TYPE_UNKNOWN_CSI
:
163 fprintf(stderr
, "unknown CSI\n");
167 int m
= key
->modifiers
;
168 fprintf(stderr
, " mod=%s%s%s+%02x",
169 (m
& TERMKEY_KEYMOD_CTRL
? "C" : ""),
170 (m
& TERMKEY_KEYMOD_ALT
? "A" : ""),
171 (m
& TERMKEY_KEYMOD_SHIFT
? "S" : ""),
172 m
& ~(TERMKEY_KEYMOD_CTRL
|TERMKEY_KEYMOD_ALT
|TERMKEY_KEYMOD_SHIFT
));
175 static const char *res2str(TermKeyResult res
)
177 static char errorbuffer
[256];
180 case TERMKEY_RES_KEY
:
181 return "TERMKEY_RES_KEY";
182 case TERMKEY_RES_EOF
:
183 return "TERMKEY_RES_EOF";
184 case TERMKEY_RES_AGAIN
:
185 return "TERMKEY_RES_AGAIN";
186 case TERMKEY_RES_NONE
:
187 return "TERMKEY_RES_NONE";
188 case TERMKEY_RES_ERROR
:
189 snprintf(errorbuffer
, sizeof errorbuffer
, "TERMKEY_RES_ERROR(errno=%d)\n", errno
);
190 return (const char*)errorbuffer
;
197 /* Similar to snprintf(str, size, "%s", src) except it turns CamelCase into
198 * space separated values
200 static int snprint_cameltospaces(char *str
, size_t size
, const char *src
)
204 while(*src
&& l
< size
- 1) {
205 if(isupper(*src
) && prev_lower
) {
211 prev_lower
= islower(*src
);
212 str
[l
++] = tolower(*src
++);
215 /* For consistency with snprintf, return the number of bytes that would have
216 * been written, excluding '\0' */
218 if(isupper(*src
) && prev_lower
) {
221 prev_lower
= islower(*src
);
227 /* Similar to strcmp(str, strcamel, n) except that:
228 * it compares CamelCase in strcamel with space separated values in str;
229 * it takes char**s and updates them
230 * n counts bytes of strcamel, not str
232 static int strpncmp_camel(const char **strp
, const char **strcamelp
, size_t n
)
234 const char *str
= *strp
, *strcamel
= *strcamelp
;
237 for( ; (*str
|| *strcamel
) && n
; n
--) {
238 char b
= tolower(*strcamel
);
239 if(isupper(*strcamel
) && prev_lower
) {
250 prev_lower
= islower(*strcamel
);
257 *strcamelp
= strcamel
;
258 return *str
- *strcamel
;
261 static TermKey
*termkey_alloc(void)
263 TermKey
*tk
= malloc(sizeof(TermKey
));
267 /* Default all the object fields but don't allocate anything */
276 tk
->buffsize
= 256; /* bytes */
279 tk
->restore_termios_valid
= 0;
281 tk
->waittime
= 50; /* msec */
289 for(int i
= 0; i
< 32; i
++)
290 tk
->c0
[i
].sym
= TERMKEY_SYM_NONE
;
294 tk
->method
.emit_codepoint
= &emit_codepoint
;
295 tk
->method
.peekkey_simple
= &peekkey_simple
;
296 tk
->method
.peekkey_mouse
= &peekkey_mouse
;
301 static int termkey_init(TermKey
*tk
, const char *term
)
303 tk
->buffer
= malloc(tk
->buffsize
);
307 tk
->keynames
= malloc(sizeof(tk
->keynames
[0]) * tk
->nkeynames
);
309 goto abort_free_buffer
;
312 for(i
= 0; i
< tk
->nkeynames
; i
++)
313 tk
->keynames
[i
] = NULL
;
315 for(i
= 0; keynames
[i
].name
; i
++)
316 if(termkey_register_keyname(tk
, keynames
[i
].sym
, keynames
[i
].name
) == -1)
317 goto abort_free_keynames
;
319 register_c0(tk
, TERMKEY_SYM_TAB
, 0x09, NULL
);
320 register_c0(tk
, TERMKEY_SYM_ENTER
, 0x0d, NULL
);
321 register_c0(tk
, TERMKEY_SYM_ESCAPE
, 0x1b, NULL
);
323 struct TermKeyDriverNode
*tail
= NULL
;
325 for(i
= 0; drivers
[i
]; i
++) {
326 void *info
= (*drivers
[i
]->new_driver
)(tk
, term
);
331 fprintf(stderr
, "Loading the %s driver...\n", drivers
[i
]->name
);
334 struct TermKeyDriverNode
*thisdrv
= malloc(sizeof(*thisdrv
));
336 goto abort_free_drivers
;
338 thisdrv
->driver
= drivers
[i
];
339 thisdrv
->info
= info
;
340 thisdrv
->next
= NULL
;
343 tk
->drivers
= thisdrv
;
345 tail
->next
= thisdrv
;
350 fprintf(stderr
, "Loaded %s driver\n", drivers
[i
]->name
);
356 goto abort_free_keynames
;
362 for(struct TermKeyDriverNode
*p
= tk
->drivers
; p
; ) {
363 (*p
->driver
->free_driver
)(p
->info
);
364 struct TermKeyDriverNode
*next
= p
->next
;
378 TermKey
*termkey_new(int fd
, int flags
)
380 TermKey
*tk
= termkey_alloc();
386 if(!(flags
& (TERMKEY_FLAG_RAW
|TERMKEY_FLAG_UTF8
))) {
389 /* Most OSes will set .UTF-8. Some will set .utf8. Try to be fairly
390 * generous in parsing these
392 if(((e
= getenv("LANG")) || (e
= getenv("LC_MESSAGES")) || (e
= getenv("LC_ALL"))) &&
393 (e
= strchr(e
, '.')) && e
++ &&
394 (strcaseeq(e
, "UTF-8") || strcaseeq(e
, "UTF8")))
395 flags
|= TERMKEY_FLAG_UTF8
;
397 flags
|= TERMKEY_FLAG_RAW
;
400 termkey_set_flags(tk
, flags
);
402 const char *term
= getenv("TERM");
404 if(!termkey_init(tk
, term
))
407 if(!termkey_start(tk
))
417 TermKey
*termkey_new_abstract(const char *term
, int flags
)
419 TermKey
*tk
= termkey_alloc();
425 termkey_set_flags(tk
, flags
);
427 if(!termkey_init(tk
, term
)) {
437 void termkey_free(TermKey
*tk
)
439 free(tk
->buffer
); tk
->buffer
= NULL
;
440 free(tk
->keynames
); tk
->keynames
= NULL
;
442 struct TermKeyDriverNode
*p
;
443 for(p
= tk
->drivers
; p
; ) {
444 (*p
->driver
->free_driver
)(p
->info
);
445 struct TermKeyDriverNode
*next
= p
->next
;
453 void termkey_destroy(TermKey
*tk
)
461 int termkey_start(TermKey
*tk
)
466 if(tk
->fd
!= -1 && !(tk
->flags
& TERMKEY_FLAG_NOTERMIOS
)) {
467 struct termios termios
;
468 if(tcgetattr(tk
->fd
, &termios
) == 0) {
469 tk
->restore_termios
= termios
;
470 tk
->restore_termios_valid
= 1;
472 termios
.c_iflag
&= ~(IXON
|INLCR
|ICRNL
);
473 termios
.c_lflag
&= ~(ICANON
|ECHO
478 termios
.c_cc
[VMIN
] = 1;
479 termios
.c_cc
[VTIME
] = 0;
481 if(tk
->flags
& TERMKEY_FLAG_CTRLC
)
482 /* want no signal keys at all, so just disable ISIG */
483 termios
.c_lflag
&= ~ISIG
;
485 /* Disable Ctrl-\==VQUIT and Ctrl-D==VSUSP but leave Ctrl-C as SIGINT */
486 termios
.c_cc
[VQUIT
] = _POSIX_VDISABLE
;
487 termios
.c_cc
[VSUSP
] = _POSIX_VDISABLE
;
488 /* Some OSes have Ctrl-Y==VDSUSP */
490 termios
.c_cc
[VDSUSP
] = _POSIX_VDISABLE
;
495 fprintf(stderr
, "Setting termios(3) flags\n");
497 tcsetattr(tk
->fd
, TCSANOW
, &termios
);
501 struct TermKeyDriverNode
*p
;
502 for(p
= tk
->drivers
; p
; p
= p
->next
)
503 if(p
->driver
->start_driver
)
504 if(!(*p
->driver
->start_driver
)(tk
, p
->info
))
508 fprintf(stderr
, "Drivers started; termkey instance %p is ready\n", tk
);
515 int termkey_stop(TermKey
*tk
)
520 struct TermKeyDriverNode
*p
;
521 for(p
= tk
->drivers
; p
; p
= p
->next
)
522 if(p
->driver
->stop_driver
)
523 (*p
->driver
->stop_driver
)(tk
, p
->info
);
525 if(tk
->restore_termios_valid
)
526 tcsetattr(tk
->fd
, TCSANOW
, &tk
->restore_termios
);
533 int termkey_is_started(TermKey
*tk
)
535 return tk
->is_started
;
538 int termkey_get_fd(TermKey
*tk
)
543 int termkey_get_flags(TermKey
*tk
)
548 void termkey_set_flags(TermKey
*tk
, int newflags
)
550 tk
->flags
= newflags
;
552 if(tk
->flags
& TERMKEY_FLAG_SPACESYMBOL
)
553 tk
->canonflags
|= TERMKEY_CANON_SPACESYMBOL
;
555 tk
->canonflags
&= ~TERMKEY_CANON_SPACESYMBOL
;
558 void termkey_set_waittime(TermKey
*tk
, int msec
)
563 int termkey_get_waittime(TermKey
*tk
)
568 int termkey_get_canonflags(TermKey
*tk
)
570 return tk
->canonflags
;
573 void termkey_set_canonflags(TermKey
*tk
, int flags
)
575 tk
->canonflags
= flags
;
577 if(tk
->canonflags
& TERMKEY_CANON_SPACESYMBOL
)
578 tk
->flags
|= TERMKEY_FLAG_SPACESYMBOL
;
580 tk
->flags
&= ~TERMKEY_FLAG_SPACESYMBOL
;
583 size_t termkey_get_buffer_size(TermKey
*tk
)
588 int termkey_set_buffer_size(TermKey
*tk
, size_t size
)
590 unsigned char *buffer
= realloc(tk
->buffer
, size
);
600 size_t termkey_get_buffer_remaining(TermKey
*tk
)
602 /* Return the total number of free bytes in the buffer, because that's what
603 * is available to the user. */
604 return tk
->buffsize
- tk
->buffcount
;
607 static void eat_bytes(TermKey
*tk
, size_t count
)
609 if(count
>= tk
->buffcount
) {
615 tk
->buffstart
+= count
;
616 tk
->buffcount
-= count
;
619 static inline unsigned int utf8_seqlen(long codepoint
)
621 if(codepoint
< 0x0000080) return 1;
622 if(codepoint
< 0x0000800) return 2;
623 if(codepoint
< 0x0010000) return 3;
624 if(codepoint
< 0x0200000) return 4;
625 if(codepoint
< 0x4000000) return 5;
629 static void fill_utf8(TermKeyKey
*key
)
631 long codepoint
= key
->code
.codepoint
;
632 int nbytes
= utf8_seqlen(codepoint
);
634 key
->utf8
[nbytes
] = 0;
636 // This is easier done backwards
640 key
->utf8
[b
] = 0x80 | (codepoint
& 0x3f);
645 case 1: key
->utf8
[0] = (codepoint
& 0x7f); break;
646 case 2: key
->utf8
[0] = 0xc0 | (codepoint
& 0x1f); break;
647 case 3: key
->utf8
[0] = 0xe0 | (codepoint
& 0x0f); break;
648 case 4: key
->utf8
[0] = 0xf0 | (codepoint
& 0x07); break;
649 case 5: key
->utf8
[0] = 0xf8 | (codepoint
& 0x03); break;
650 case 6: key
->utf8
[0] = 0xfc | (codepoint
& 0x01); break;
654 #define UTF8_INVALID 0xFFFD
655 static TermKeyResult
parse_utf8(const unsigned char *bytes
, size_t len
, long *cp
, size_t *nbytep
)
659 unsigned char b0
= bytes
[0];
665 return TERMKEY_RES_KEY
;
668 // Starts with a continuation byte - that's not right
671 return TERMKEY_RES_KEY
;
696 return TERMKEY_RES_KEY
;
699 for(unsigned int b
= 1; b
< nbytes
; b
++) {
703 return TERMKEY_RES_AGAIN
;
706 if(cb
< 0x80 || cb
>= 0xc0) {
709 return TERMKEY_RES_KEY
;
716 // Check for overlong sequences
717 if(nbytes
> utf8_seqlen(*cp
))
720 // Check for UTF-16 surrogates or invalid *cps
721 if((*cp
>= 0xD800 && *cp
<= 0xDFFF) ||
727 return TERMKEY_RES_KEY
;
730 static void emit_codepoint(TermKey
*tk
, long codepoint
, TermKeyKey
*key
)
732 if(codepoint
< 0x20) {
734 key
->code
.codepoint
= 0;
737 if(!(tk
->flags
& TERMKEY_FLAG_NOINTERPRET
) && tk
->c0
[codepoint
].sym
!= TERMKEY_SYM_UNKNOWN
) {
738 key
->code
.sym
= tk
->c0
[codepoint
].sym
;
739 key
->modifiers
|= tk
->c0
[codepoint
].modifier_set
;
743 key
->type
= TERMKEY_TYPE_UNICODE
;
744 /* Generically modified Unicode ought not report the SHIFT state, or else
745 * we get into complicationg trying to report Shift-; vs : and so on...
746 * In order to be able to represent Ctrl-Shift-A as CTRL modified
747 * unicode A, we need to call Ctrl-A simply 'a', lowercase
749 if(codepoint
+0x40 >= 'A' && codepoint
+0x40 <= 'Z')
750 // it's a letter - use lowecase instead
751 key
->code
.codepoint
= codepoint
+ 0x60;
753 key
->code
.codepoint
= codepoint
+ 0x40;
754 key
->modifiers
= TERMKEY_KEYMOD_CTRL
;
757 key
->type
= TERMKEY_TYPE_KEYSYM
;
760 else if(codepoint
== 0x7f && !(tk
->flags
& TERMKEY_FLAG_NOINTERPRET
)) {
762 key
->type
= TERMKEY_TYPE_KEYSYM
;
763 key
->code
.sym
= TERMKEY_SYM_DEL
;
766 else if(codepoint
>= 0x20 && codepoint
< 0x80) {
767 // ASCII lowbyte range
768 key
->type
= TERMKEY_TYPE_UNICODE
;
769 key
->code
.codepoint
= codepoint
;
772 else if(codepoint
>= 0x80 && codepoint
< 0xa0) {
773 // UTF-8 never starts with a C1 byte. So we can be sure of these
774 key
->type
= TERMKEY_TYPE_UNICODE
;
775 key
->code
.codepoint
= codepoint
- 0x40;
776 key
->modifiers
= TERMKEY_KEYMOD_CTRL
|TERMKEY_KEYMOD_ALT
;
780 key
->type
= TERMKEY_TYPE_UNICODE
;
781 key
->code
.codepoint
= codepoint
;
785 termkey_canonicalise(tk
, key
);
787 if(key
->type
== TERMKEY_TYPE_UNICODE
)
791 void termkey_canonicalise(TermKey
*tk
, TermKeyKey
*key
)
793 int flags
= tk
->canonflags
;
795 if(flags
& TERMKEY_CANON_SPACESYMBOL
) {
796 if(key
->type
== TERMKEY_TYPE_UNICODE
&& key
->code
.number
== 0x20) {
797 key
->type
= TERMKEY_TYPE_KEYSYM
;
798 key
->code
.sym
= TERMKEY_SYM_SPACE
;
802 if(key
->type
== TERMKEY_TYPE_KEYSYM
&& key
->code
.sym
== TERMKEY_SYM_SPACE
) {
803 key
->type
= TERMKEY_TYPE_UNICODE
;
804 key
->code
.number
= 0x20;
809 if(flags
& TERMKEY_CANON_DELBS
) {
810 if(key
->type
== TERMKEY_TYPE_KEYSYM
&& key
->code
.sym
== TERMKEY_SYM_DEL
) {
811 key
->code
.sym
= TERMKEY_SYM_BACKSPACE
;
816 static TermKeyResult
peekkey(TermKey
*tk
, TermKeyKey
*key
, int force
, size_t *nbytep
)
820 if(!tk
->is_started
) {
822 return TERMKEY_RES_ERROR
;
826 fprintf(stderr
, "getkey(force=%d): buffer ", force
);
828 fprintf(stderr
, "\n");
832 tk
->buffstart
+= tk
->hightide
;
833 tk
->buffcount
-= tk
->hightide
;
838 struct TermKeyDriverNode
*p
;
839 for(p
= tk
->drivers
; p
; p
= p
->next
) {
840 ret
= (p
->driver
->peekkey
)(tk
, p
->info
, key
, force
, nbytep
);
843 fprintf(stderr
, "Driver %s yields %s\n", p
->driver
->name
, res2str(ret
));
847 case TERMKEY_RES_KEY
:
849 print_key(tk
, key
); fprintf(stderr
, "\n");
851 // Slide the data down to stop it running away
853 size_t halfsize
= tk
->buffsize
/ 2;
855 if(tk
->buffstart
> halfsize
) {
856 memcpy(tk
->buffer
, tk
->buffer
+ halfsize
, halfsize
);
857 tk
->buffstart
-= halfsize
;
862 case TERMKEY_RES_EOF
:
863 case TERMKEY_RES_ERROR
:
866 case TERMKEY_RES_AGAIN
:
871 case TERMKEY_RES_NONE
:
877 return TERMKEY_RES_AGAIN
;
879 ret
= peekkey_simple(tk
, key
, force
, nbytep
);
882 fprintf(stderr
, "getkey_simple(force=%d) yields %s\n", force
, res2str(ret
));
883 if(ret
== TERMKEY_RES_KEY
) {
884 print_key(tk
, key
); fprintf(stderr
, "\n");
891 static TermKeyResult
peekkey_simple(TermKey
*tk
, TermKeyKey
*key
, int force
, size_t *nbytep
)
893 if(tk
->buffcount
== 0)
894 return tk
->is_closed
? TERMKEY_RES_EOF
: TERMKEY_RES_NONE
;
896 unsigned char b0
= CHARAT(0);
899 // Escape-prefixed value? Might therefore be Alt+key
900 if(tk
->buffcount
== 1) {
901 // This might be an <Esc> press, or it may want to be part of a longer
904 return TERMKEY_RES_AGAIN
;
906 (*tk
->method
.emit_codepoint
)(tk
, b0
, key
);
908 return TERMKEY_RES_KEY
;
911 // Try another key there
915 // Run the full driver
916 TermKeyResult metakey_result
= peekkey(tk
, key
, force
, nbytep
);
921 switch(metakey_result
) {
922 case TERMKEY_RES_KEY
:
923 key
->modifiers
|= TERMKEY_KEYMOD_ALT
;
927 case TERMKEY_RES_NONE
:
928 case TERMKEY_RES_EOF
:
929 case TERMKEY_RES_AGAIN
:
930 case TERMKEY_RES_ERROR
:
934 return metakey_result
;
937 // Single byte C0, G0 or C1 - C1 is never UTF-8 initial byte
938 (*tk
->method
.emit_codepoint
)(tk
, b0
, key
);
940 return TERMKEY_RES_KEY
;
942 else if(tk
->flags
& TERMKEY_FLAG_UTF8
) {
945 TermKeyResult res
= parse_utf8(tk
->buffer
+ tk
->buffstart
, tk
->buffcount
, &codepoint
, nbytep
);
947 if(res
== TERMKEY_RES_AGAIN
&& force
) {
948 /* There weren't enough bytes for a complete UTF-8 sequence but caller
949 * demands an answer. About the best thing we can do here is eat as many
950 * bytes as we have, and emit a UTF8_INVALID. If the remaining bytes
951 * arrive later, they'll be invalid too.
953 codepoint
= UTF8_INVALID
;
954 *nbytep
= tk
->buffcount
;
955 res
= TERMKEY_RES_KEY
;
958 key
->type
= TERMKEY_TYPE_UNICODE
;
960 (*tk
->method
.emit_codepoint
)(tk
, codepoint
, key
);
964 // Non UTF-8 case - just report the raw byte
965 key
->type
= TERMKEY_TYPE_UNICODE
;
966 key
->code
.codepoint
= b0
;
969 key
->utf8
[0] = key
->code
.codepoint
;
974 return TERMKEY_RES_KEY
;
978 static TermKeyResult
peekkey_mouse(TermKey
*tk
, TermKeyKey
*key
, size_t *nbytep
)
980 if(tk
->buffcount
< 3)
981 return TERMKEY_RES_AGAIN
;
983 key
->type
= TERMKEY_TYPE_MOUSE
;
984 key
->code
.mouse
[0] = CHARAT(0) - 0x20;
985 key
->code
.mouse
[1] = CHARAT(1) - 0x20;
986 key
->code
.mouse
[2] = CHARAT(2) - 0x20;
987 key
->code
.mouse
[3] = 0;
989 key
->modifiers
= (key
->code
.mouse
[0] & 0x1c) >> 2;
990 key
->code
.mouse
[0] &= ~0x1c;
993 return TERMKEY_RES_KEY
;
996 TermKeyResult
termkey_getkey(TermKey
*tk
, TermKeyKey
*key
)
999 TermKeyResult ret
= peekkey(tk
, key
, 0, &nbytes
);
1001 if(ret
== TERMKEY_RES_KEY
)
1002 eat_bytes(tk
, nbytes
);
1004 if(ret
== TERMKEY_RES_AGAIN
)
1005 /* Call peekkey() again in force mode to obtain whatever it can */
1006 (void)peekkey(tk
, key
, 1, &nbytes
);
1007 /* Don't eat it yet though */
1012 TermKeyResult
termkey_getkey_force(TermKey
*tk
, TermKeyKey
*key
)
1015 TermKeyResult ret
= peekkey(tk
, key
, 1, &nbytes
);
1017 if(ret
== TERMKEY_RES_KEY
)
1018 eat_bytes(tk
, nbytes
);
1023 TermKeyResult
termkey_waitkey(TermKey
*tk
, TermKeyKey
*key
)
1027 return TERMKEY_RES_ERROR
;
1031 TermKeyResult ret
= termkey_getkey(tk
, key
);
1034 case TERMKEY_RES_KEY
:
1035 case TERMKEY_RES_EOF
:
1036 case TERMKEY_RES_ERROR
:
1039 case TERMKEY_RES_NONE
:
1040 ret
= termkey_advisereadable(tk
);
1041 if(ret
== TERMKEY_RES_ERROR
)
1045 case TERMKEY_RES_AGAIN
:
1048 // We're closed now. Never going to get more bytes so just go with
1050 return termkey_getkey_force(tk
, key
);
1058 int pollret
= poll(&fd
, 1, tk
->waittime
);
1060 if(errno
== EINTR
&& !(tk
->flags
& TERMKEY_FLAG_EINTR
))
1063 return TERMKEY_RES_ERROR
;
1066 if(fd
.revents
& (POLLIN
|POLLHUP
|POLLERR
))
1067 ret
= termkey_advisereadable(tk
);
1069 ret
= TERMKEY_RES_NONE
;
1071 if(ret
== TERMKEY_RES_ERROR
)
1073 if(ret
== TERMKEY_RES_NONE
)
1074 return termkey_getkey_force(tk
, key
);
1083 TermKeyResult
termkey_advisereadable(TermKey
*tk
)
1089 return TERMKEY_RES_ERROR
;
1093 memmove(tk
->buffer
, tk
->buffer
+ tk
->buffstart
, tk
->buffcount
);
1097 /* Not expecting it ever to be greater but doesn't hurt to handle that */
1098 if(tk
->buffcount
>= tk
->buffsize
) {
1100 return TERMKEY_RES_ERROR
;
1104 len
= read(tk
->fd
, tk
->buffer
+ tk
->buffcount
, tk
->buffsize
- tk
->buffcount
);
1108 return TERMKEY_RES_NONE
;
1109 else if(errno
== EINTR
&& !(tk
->flags
& TERMKEY_FLAG_EINTR
))
1112 return TERMKEY_RES_ERROR
;
1116 return TERMKEY_RES_NONE
;
1119 tk
->buffcount
+= len
;
1120 return TERMKEY_RES_AGAIN
;
1124 size_t termkey_push_bytes(TermKey
*tk
, const char *bytes
, size_t len
)
1127 memmove(tk
->buffer
, tk
->buffer
+ tk
->buffstart
, tk
->buffcount
);
1131 /* Not expecting it ever to be greater but doesn't hurt to handle that */
1132 if(tk
->buffcount
>= tk
->buffsize
) {
1137 if(len
> tk
->buffsize
- tk
->buffcount
)
1138 len
= tk
->buffsize
- tk
->buffcount
;
1140 // memcpy(), not strncpy() in case of null bytes in input
1141 memcpy(tk
->buffer
+ tk
->buffcount
, bytes
, len
);
1142 tk
->buffcount
+= len
;
1147 TermKeySym
termkey_register_keyname(TermKey
*tk
, TermKeySym sym
, const char *name
)
1150 sym
= tk
->nkeynames
;
1152 if(sym
>= tk
->nkeynames
) {
1153 const char **new_keynames
= realloc(tk
->keynames
, sizeof(new_keynames
[0]) * (sym
+ 1));
1157 tk
->keynames
= new_keynames
;
1160 for(int i
= tk
->nkeynames
; i
< sym
; i
++)
1161 tk
->keynames
[i
] = NULL
;
1163 tk
->nkeynames
= sym
+ 1;
1166 tk
->keynames
[sym
] = name
;
1171 const char *termkey_get_keyname(TermKey
*tk
, TermKeySym sym
)
1173 if(sym
== TERMKEY_SYM_UNKNOWN
)
1176 if(sym
< tk
->nkeynames
)
1177 return tk
->keynames
[sym
];
1182 static const char *termkey_lookup_keyname_format(TermKey
*tk
, const char *str
, TermKeySym
*sym
, TermKeyFormat format
)
1184 /* We store an array, so we can't do better than a linear search. Doesn't
1185 * matter because user won't be calling this too often */
1187 for(*sym
= 0; *sym
< tk
->nkeynames
; (*sym
)++) {
1188 const char *thiskey
= tk
->keynames
[*sym
];
1191 size_t len
= strlen(thiskey
);
1192 if(format
& TERMKEY_FORMAT_LOWERSPACE
) {
1193 const char *thisstr
= str
;
1194 if(strpncmp_camel(&thisstr
, &thiskey
, len
) == 0)
1198 if(strncmp(str
, thiskey
, len
) == 0)
1199 return (char *)str
+ len
;
1206 const char *termkey_lookup_keyname(TermKey
*tk
, const char *str
, TermKeySym
*sym
)
1208 return termkey_lookup_keyname_format(tk
, str
, sym
, 0);
1211 TermKeySym
termkey_keyname2sym(TermKey
*tk
, const char *keyname
)
1214 const char *endp
= termkey_lookup_keyname(tk
, keyname
, &sym
);
1215 if(!endp
|| endp
[0])
1216 return TERMKEY_SYM_UNKNOWN
;
1220 static TermKeySym
register_c0(TermKey
*tk
, TermKeySym sym
, unsigned char ctrl
, const char *name
)
1222 return register_c0_full(tk
, sym
, 0, 0, ctrl
, name
);
1225 static TermKeySym
register_c0_full(TermKey
*tk
, TermKeySym sym
, int modifier_set
, int modifier_mask
, unsigned char ctrl
, const char *name
)
1233 sym
= termkey_register_keyname(tk
, sym
, name
);
1235 tk
->c0
[ctrl
].sym
= sym
;
1236 tk
->c0
[ctrl
].modifier_set
= modifier_set
;
1237 tk
->c0
[ctrl
].modifier_mask
= modifier_mask
;
1242 /* Previous name for this function
1243 * No longer declared in termkey.h but it remains in the compiled library for
1244 * backward-compatibility reasons.
1246 size_t termkey_snprint_key(TermKey
*tk
, char *buffer
, size_t len
, TermKeyKey
*key
, TermKeyFormat format
)
1248 return termkey_strfkey(tk
, buffer
, len
, key
, format
);
1251 static struct modnames
{
1252 const char *shift
, *alt
, *ctrl
;
1255 { "S", "A", "C" }, // 0
1256 { "Shift", "Alt", "Ctrl" }, // LONGMOD
1257 { "S", "M", "C" }, // ALTISMETA
1258 { "Shift", "Meta", "Ctrl" }, // ALTISMETA+LONGMOD
1259 { "s", "a", "c" }, // LOWERMOD
1260 { "shift", "alt", "ctrl" }, // LOWERMOD+LONGMOD
1261 { "s", "m", "c" }, // LOWERMOD+ALTISMETA
1262 { "shift", "meta", "ctrl" }, // LOWERMOD+ALTISMETA+LONGMOD
1265 size_t termkey_strfkey(TermKey
*tk
, char *buffer
, size_t len
, TermKeyKey
*key
, TermKeyFormat format
)
1270 struct modnames
*mods
= &modnames
[!!(format
& TERMKEY_FORMAT_LONGMOD
) +
1271 !!(format
& TERMKEY_FORMAT_ALTISMETA
) * 2 +
1272 !!(format
& TERMKEY_FORMAT_LOWERMOD
) * 4];
1274 int wrapbracket
= (format
& TERMKEY_FORMAT_WRAPBRACKET
) &&
1275 (key
->type
!= TERMKEY_TYPE_UNICODE
|| key
->modifiers
!= 0);
1277 char sep
= (format
& TERMKEY_FORMAT_SPACEMOD
) ? ' ' : '-';
1279 if(format
& TERMKEY_FORMAT_CARETCTRL
&&
1280 key
->type
== TERMKEY_TYPE_UNICODE
&&
1281 key
->modifiers
== TERMKEY_KEYMOD_CTRL
) {
1282 long codepoint
= key
->code
.codepoint
;
1284 // Handle some of the special casesfirst
1285 if(codepoint
>= 'a' && codepoint
<= 'z') {
1286 l
= snprintf(buffer
+ pos
, len
- pos
, wrapbracket
? "<^%c>" : "^%c", (char)codepoint
- 0x20);
1287 if(l
<= 0) return pos
;
1291 else if((codepoint
>= '@' && codepoint
< 'A') ||
1292 (codepoint
> 'Z' && codepoint
<= '_')) {
1293 l
= snprintf(buffer
+ pos
, len
- pos
, wrapbracket
? "<^%c>" : "^%c", (char)codepoint
);
1294 if(l
<= 0) return pos
;
1301 l
= snprintf(buffer
+ pos
, len
- pos
, "<");
1302 if(l
<= 0) return pos
;
1306 if(key
->modifiers
& TERMKEY_KEYMOD_ALT
) {
1307 l
= snprintf(buffer
+ pos
, len
- pos
, "%s%c", mods
->alt
, sep
);
1308 if(l
<= 0) return pos
;
1312 if(key
->modifiers
& TERMKEY_KEYMOD_CTRL
) {
1313 l
= snprintf(buffer
+ pos
, len
- pos
, "%s%c", mods
->ctrl
, sep
);
1314 if(l
<= 0) return pos
;
1318 if(key
->modifiers
& TERMKEY_KEYMOD_SHIFT
) {
1319 l
= snprintf(buffer
+ pos
, len
- pos
, "%s%c", mods
->shift
, sep
);
1320 if(l
<= 0) return pos
;
1325 case TERMKEY_TYPE_UNICODE
:
1326 if(!key
->utf8
[0]) // In case of user-supplied key structures
1328 l
= snprintf(buffer
+ pos
, len
- pos
, "%s", key
->utf8
);
1330 case TERMKEY_TYPE_KEYSYM
:
1332 const char *name
= termkey_get_keyname(tk
, key
->code
.sym
);
1333 if(format
& TERMKEY_FORMAT_LOWERSPACE
)
1334 l
= snprint_cameltospaces(buffer
+ pos
, len
- pos
, name
);
1336 l
= snprintf(buffer
+ pos
, len
- pos
, "%s", name
);
1339 case TERMKEY_TYPE_FUNCTION
:
1340 l
= snprintf(buffer
+ pos
, len
- pos
, "%c%d",
1341 (format
& TERMKEY_FORMAT_LOWERSPACE
? 'f' : 'F'), key
->code
.number
);
1343 case TERMKEY_TYPE_MOUSE
:
1345 TermKeyMouseEvent ev
;
1348 termkey_interpret_mouse(tk
, key
, &ev
, &button
, &line
, &col
);
1350 static const char *evnames
[] = { "Unknown", "Press", "Drag", "Release" };
1352 l
= snprintf(buffer
+ pos
, len
- pos
, "Mouse%s(%d)",
1353 evnames
[ev
], button
);
1355 if(format
& TERMKEY_FORMAT_MOUSE_POS
) {
1356 if(l
<= 0) return pos
;
1359 l
= snprintf(buffer
+ pos
, len
- pos
, " @ (%u,%u)", col
, line
);
1363 case TERMKEY_TYPE_POSITION
:
1364 l
= snprintf(buffer
+ pos
, len
- pos
, "Position");
1366 case TERMKEY_TYPE_MODEREPORT
:
1368 int initial
, mode
, value
;
1369 termkey_interpret_modereport(tk
, key
, &initial
, &mode
, &value
);
1371 l
= snprintf(buffer
+ pos
, len
- pos
, "Mode(%c%d=%d)", initial
, mode
, value
);
1373 l
= snprintf(buffer
+ pos
, len
- pos
, "Mode(%d=%d)", mode
, value
);
1375 case TERMKEY_TYPE_UNKNOWN_CSI
:
1376 l
= snprintf(buffer
+ pos
, len
- pos
, "CSI %c", key
->code
.number
& 0xff);
1380 if(l
<= 0) return pos
;
1384 l
= snprintf(buffer
+ pos
, len
- pos
, ">");
1385 if(l
<= 0) return pos
;
1392 const char *termkey_strpkey(TermKey
*tk
, const char *str
, TermKeyKey
*key
, TermKeyFormat format
)
1394 struct modnames
*mods
= &modnames
[!!(format
& TERMKEY_FORMAT_LONGMOD
) +
1395 !!(format
& TERMKEY_FORMAT_ALTISMETA
) * 2 +
1396 !!(format
& TERMKEY_FORMAT_LOWERMOD
) * 4];
1400 if((format
& TERMKEY_FORMAT_CARETCTRL
) && str
[0] == '^' && str
[1]) {
1401 str
= termkey_strpkey(tk
, str
+1, key
, format
& ~TERMKEY_FORMAT_CARETCTRL
);
1404 key
->type
!= TERMKEY_TYPE_UNICODE
||
1405 key
->code
.codepoint
< '@' || key
->code
.codepoint
> '_' ||
1406 key
->modifiers
!= 0)
1409 if(key
->code
.codepoint
>= 'A' && key
->code
.codepoint
<= 'Z')
1410 key
->code
.codepoint
+= 0x20;
1411 key
->modifiers
= TERMKEY_KEYMOD_CTRL
;
1418 while((sep_at
= strchr(str
, (format
& TERMKEY_FORMAT_SPACEMOD
) ? ' ' : '-'))) {
1419 size_t n
= sep_at
- str
;
1421 if(n
== strlen(mods
->alt
) && strncmp(mods
->alt
, str
, n
) == 0)
1422 key
->modifiers
|= TERMKEY_KEYMOD_ALT
;
1423 else if(n
== strlen(mods
->ctrl
) && strncmp(mods
->ctrl
, str
, n
) == 0)
1424 key
->modifiers
|= TERMKEY_KEYMOD_CTRL
;
1425 else if(n
== strlen(mods
->shift
) && strncmp(mods
->shift
, str
, n
) == 0)
1426 key
->modifiers
|= TERMKEY_KEYMOD_SHIFT
;
1438 if((endstr
= termkey_lookup_keyname_format(tk
, str
, &key
->code
.sym
, format
))) {
1439 key
->type
= TERMKEY_TYPE_KEYSYM
;
1442 else if(sscanf(str
, "F%d%zn", &key
->code
.number
, &snbytes
) == 1) {
1443 key
->type
= TERMKEY_TYPE_FUNCTION
;
1446 // Unicode must be last
1447 else if(parse_utf8((unsigned const char *)str
, strlen(str
), &key
->code
.codepoint
, &nbytes
) == TERMKEY_RES_KEY
) {
1448 key
->type
= TERMKEY_TYPE_UNICODE
;
1452 // TODO: Consider mouse events?
1456 termkey_canonicalise(tk
, key
);
1461 int termkey_keycmp(TermKey
*tk
, const TermKeyKey
*key1p
, const TermKeyKey
*key2p
)
1463 /* Copy the key structs since we'll be modifying them */
1464 TermKeyKey key1
= *key1p
, key2
= *key2p
;
1466 termkey_canonicalise(tk
, &key1
);
1467 termkey_canonicalise(tk
, &key2
);
1469 if(key1
.type
!= key2
.type
)
1470 return key1
.type
- key2
.type
;
1473 case TERMKEY_TYPE_UNICODE
:
1474 if(key1
.code
.codepoint
!= key2
.code
.codepoint
)
1475 return key1
.code
.codepoint
- key2
.code
.codepoint
;
1477 case TERMKEY_TYPE_KEYSYM
:
1478 if(key1
.code
.sym
!= key2
.code
.sym
)
1479 return key1
.code
.sym
- key2
.code
.sym
;
1481 case TERMKEY_TYPE_FUNCTION
:
1482 case TERMKEY_TYPE_UNKNOWN_CSI
:
1483 if(key1
.code
.number
!= key2
.code
.number
)
1484 return key1
.code
.number
- key2
.code
.number
;
1486 case TERMKEY_TYPE_MOUSE
:
1488 int cmp
= strncmp(key1
.code
.mouse
, key2
.code
.mouse
, 4);
1493 case TERMKEY_TYPE_POSITION
:
1495 int line1
, col1
, line2
, col2
;
1496 termkey_interpret_position(tk
, &key1
, &line1
, &col1
);
1497 termkey_interpret_position(tk
, &key2
, &line2
, &col2
);
1499 return line1
- line2
;
1503 case TERMKEY_TYPE_MODEREPORT
:
1505 int initial1
, initial2
, mode1
, mode2
, value1
, value2
;
1506 termkey_interpret_modereport(tk
, &key1
, &initial1
, &mode1
, &value1
);
1507 termkey_interpret_modereport(tk
, &key2
, &initial2
, &mode2
, &value2
);
1508 if(initial1
!= initial2
)
1509 return initial1
- initial2
;
1511 return mode1
- mode2
;
1512 return value1
- value2
;
1516 return key1
.modifiers
- key2
.modifiers
;