1 /* We need C99 vsnprintf() semantics */
2 #ifdef __GLIBC__
3 /* We need C99 vsnprintf() semantics */
4 # define _ISOC99_SOURCE
5 /* We need sigaction() and struct sigaction */
6 # define _POSIX_C_SOURCE 199309L
7 /* We need strdup and va_copy */
8 # define _GNU_SOURCE
9 #endif
10
11 #include "tickit.h"
12 #include "bindings.h"
13 #include "termdriver.h"
14
15 #include "xterm-palette.inc"
16
17 #include <errno.h>
18 #include <signal.h>
19 #include <stdarg.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <termios.h>
24 #include <unistd.h>
25
26 #include <sys/ioctl.h>
27 #include <sys/select.h>
28 #include <sys/time.h>
29
30 #define streq(a,b) (!strcmp(a,b))
31 #define strneq(a,b,n) (strncmp(a,b,n)==0)
32
33 /* unit multipliers for working in microseconds */
34 #define MSEC 1000
35 #define SECOND 1000000
36
37 #include <termkey.h>
38
39 static TickitTermDriverProbe *driver_probes[] = {
40 &tickit_termdrv_probe_xterm,
41 &tickit_termdrv_probe_ti,
42 NULL,
43 };
44
45 struct TickitTerm {
46 int outfd;
47 TickitTermOutputFunc *outfunc;
48 void *outfunc_user;
49
50 int infd;
51 TermKey *termkey;
52 struct timeval input_timeout_at; /* absolute time */
53
54 struct TickitTerminfoHook ti_hook;
55
56 char *termtype;
57 TickitMaybeBool is_utf8;
58
59 char *outbuffer;
60 size_t outbuffer_len; /* size of outbuffer */
61 size_t outbuffer_cur; /* current fill level */
62
63 char *tmpbuffer;
64 size_t tmpbuffer_len;
65
66 TickitTermDriver *driver;
67
68 int lines;
69 int cols;
70
71 bool observe_winch;
72 TickitTerm *next_sigwinch_observer;
73
74 bool window_changed;
75
76 enum { UNSTARTED, STARTING, STARTED } state;
77
78 int colors;
79 TickitPen *pen;
80
81 int refcount;
82 struct TickitBindings bindings;
83
84 int mouse_buttons_held;
85 };
86
87 DEFINE_BINDINGS_FUNCS(term,TickitTerm,TickitTermEventFn)
88
89 static void *get_tmpbuffer(TickitTerm *tt, size_t len);
90
getstr_hook(const char * name,const char * value,void * _tt)91 static const char *getstr_hook(const char *name, const char *value, void *_tt)
92 {
93 TickitTerm *tt = _tt;
94
95 if(streq(name, "key_backspace")) {
96 /* Many terminfos lie about backspace. Rather than trust it even a little
97 * tiny smidge, we'll interrogate what termios thinks of the VERASE char
98 * and claim that is the backspace key. It's what neovim does
99 *
100 * https://github.com/neovim/neovim/blob/1083c626b9a3fc858c552d38250c3c555cda4074/src/nvim/tui/tui.c#L1982
101 */
102 struct termios termios;
103
104 if(tt->infd != -1 &&
105 tcgetattr(tt->infd, &termios) == 0) {
106 char *ret = get_tmpbuffer(tt, 2);
107 ret[0] = termios.c_cc[VERASE];
108 ret[1] = 0;
109
110 value = ret;
111 }
112 }
113
114 if(tt->ti_hook.getstr)
115 value = (*tt->ti_hook.getstr)(name, value, tt->ti_hook.data);
116
117 return value;
118 }
119
get_termkey(TickitTerm * tt)120 static TermKey *get_termkey(TickitTerm *tt)
121 {
122 if(!tt->termkey) {
123 int flags = 0;
124 if(tt->is_utf8 == TICKIT_YES)
125 flags |= TERMKEY_FLAG_UTF8;
126 else if(tt->is_utf8 == TICKIT_NO)
127 flags |= TERMKEY_FLAG_RAW;
128
129 /* A horrible hack: termkey_new doesn't take a termtype;
130 * termkey_new_abstract does but doesn't take an fd. When we migrate
131 * libtermkey source into here this will be much neater
132 */
133 {
134 const char *was_term = getenv("TERM");
135 setenv("TERM", tt->termtype, true);
136
137 tt->termkey = termkey_new(tt->infd, TERMKEY_FLAG_EINTR|TERMKEY_FLAG_NOSTART | flags);
138
139 if(was_term)
140 setenv("TERM", was_term, true);
141 else
142 unsetenv("TERM");
143 }
144
145 termkey_hook_terminfo_getstr(tt->termkey, getstr_hook, tt);
146 termkey_start(tt->termkey);
147
148 tt->is_utf8 = !!(termkey_get_flags(tt->termkey) & TERMKEY_FLAG_UTF8);
149 }
150
151 termkey_set_canonflags(tt->termkey,
152 termkey_get_canonflags(tt->termkey) | TERMKEY_CANON_DELBS);
153
154 return tt->termkey;
155 }
156
tickit_term_build_driver(struct TickitTermBuilder * builder)157 static TickitTermDriver *tickit_term_build_driver(struct TickitTermBuilder *builder)
158 {
159 if(builder->driver)
160 return builder->driver;
161
162 TickitTermProbeArgs args = {
163 .termtype = builder->termtype,
164 .ti_hook = builder->ti_hook,
165 };
166
167 for(int i = 0; driver_probes[i]; i++) {
168 TickitTermDriver *driver = (*driver_probes[i]->new)(&args);
169 if(driver)
170 return driver;
171 }
172
173 errno = ENOENT;
174 return NULL;
175 }
176
tickit_term_build(const struct TickitTermBuilder * _builder)177 TickitTerm *tickit_term_build(const struct TickitTermBuilder *_builder)
178 {
179 struct TickitTermBuilder builder = { 0 };
180 if(_builder)
181 builder = *_builder;
182
183 if(!builder.termtype)
184 builder.termtype = getenv("TERM");
185 if(!builder.termtype)
186 builder.termtype = "xterm";
187
188 TickitTerm *tt = malloc(sizeof(TickitTerm));
189 if(!tt)
190 return NULL;
191
192 if(builder.ti_hook)
193 tt->ti_hook = *builder.ti_hook;
194 else
195 tt->ti_hook = (struct TickitTerminfoHook){ 0 };
196
197 TickitTermDriver *driver = tickit_term_build_driver(&builder);
198 if(!driver)
199 return NULL;
200
201 tt->outfd = -1;
202 tt->outfunc = NULL;
203
204 tt->infd = -1;
205 tt->termkey = NULL;
206 tt->input_timeout_at.tv_sec = -1;
207
208 tt->outbuffer = NULL;
209 tt->outbuffer_len = 0;
210 tt->outbuffer_cur = 0;
211
212 tt->tmpbuffer = NULL;
213 tt->tmpbuffer_len = 0;
214
215 tt->is_utf8 = TICKIT_MAYBE;
216
217 /* Initially; the driver may provide a more accurate value */
218 tt->lines = 25;
219 tt->cols = 80;
220
221 tt->observe_winch = false;
222 tt->next_sigwinch_observer = NULL;
223 tt->window_changed = false;
224
225 tt->refcount = 1;
226 tt->bindings = (struct TickitBindings){ NULL };
227
228 tt->mouse_buttons_held = 0;
229
230 /* Initially empty because we don't necessarily know the initial state
231 * of the terminal
232 */
233 tt->pen = tickit_pen_new();
234
235 if(builder.termtype)
236 tt->termtype = strdup(builder.termtype);
237 else
238 tt->termtype = NULL;
239
240 tt->driver = driver;
241 tt->driver->tt = tt;
242
243 if(tt->driver->vtable->attach)
244 (*tt->driver->vtable->attach)(tt->driver, tt);
245
246 tickit_term_getctl_int(tt, TICKIT_TERMCTL_COLORS, &tt->colors);
247
248 // Can't 'start' yet until we have an output method
249 tt->state = UNSTARTED;
250
251 int fd_in = -1, fd_out = -1;
252
253 switch(builder.open) {
254 case TICKIT_NO_OPEN:
255 break;
256
257 case TICKIT_OPEN_FDS:
258 fd_in = builder.input_fd;
259 fd_out = builder.output_fd;
260 break;
261
262 case TICKIT_OPEN_STDIO:
263 fd_in = STDIN_FILENO;
264 fd_out = STDOUT_FILENO;
265 break;
266
267 case TICKIT_OPEN_STDTTY:
268 for(fd_in = 0; fd_in <= 2; fd_in++) {
269 if(isatty(fd_in))
270 break;
271 }
272 if(fd_in > 2) {
273 fprintf(stderr, "Cannot find a TTY filehandle\n");
274 abort();
275 }
276 fd_out = fd_in;
277 break;
278 }
279
280 if(fd_in != -1)
281 tickit_term_set_input_fd(tt, fd_in);
282 if(fd_out != -1)
283 tickit_term_set_output_fd(tt, fd_out);
284 if(builder.output_func)
285 tickit_term_set_output_func(tt, builder.output_func, builder.output_func_user);
286
287 if(builder.output_buffersize)
288 tickit_term_set_output_buffer(tt, builder.output_buffersize);
289
290 return tt;
291 }
292
tickit_term_new(void)293 TickitTerm *tickit_term_new(void)
294 {
295 return tickit_term_build(NULL);
296 }
297
tickit_term_new_for_termtype(const char * termtype)298 TickitTerm *tickit_term_new_for_termtype(const char *termtype)
299 {
300 return tickit_term_build(&(struct TickitTermBuilder){
301 .termtype = termtype,
302 });
303 }
304
tickit_term_open_stdio(void)305 TickitTerm *tickit_term_open_stdio(void)
306 {
307 TickitTerm *tt = tickit_term_build(&(const struct TickitTermBuilder){
308 .open = TICKIT_OPEN_STDIO,
309 });
310 if(!tt)
311 return NULL;
312
313 tickit_term_observe_sigwinch(tt, true);
314
315 return tt;
316 }
317
tickit_term_teardown(TickitTerm * tt)318 void tickit_term_teardown(TickitTerm *tt)
319 {
320 if(tt->driver && tt->state != UNSTARTED) {
321 if(tt->driver->vtable->stop)
322 (*tt->driver->vtable->stop)(tt->driver);
323
324 tt->state = UNSTARTED;
325 }
326
327 if(tt->termkey)
328 termkey_stop(tt->termkey);
329
330 tickit_term_flush(tt);
331 }
332
tickit_term_destroy(TickitTerm * tt)333 void tickit_term_destroy(TickitTerm *tt)
334 {
335 if(tt->observe_winch)
336 tickit_term_observe_sigwinch(tt, false);
337
338 if(tt->driver) {
339 tickit_term_teardown(tt);
340
341 (*tt->driver->vtable->destroy)(tt->driver);
342 }
343
344 tickit_term_flush(tt);
345
346 if(tt->outfunc)
347 (*tt->outfunc)(tt, NULL, 0, tt->outfunc_user);
348
349 tickit_bindings_unbind_and_destroy(&tt->bindings, tt);
350 tickit_pen_unref(tt->pen);
351
352 if(tt->termkey)
353 termkey_destroy(tt->termkey);
354
355 if(tt->outbuffer)
356 free(tt->outbuffer);
357
358 if(tt->tmpbuffer)
359 free(tt->tmpbuffer);
360
361 if(tt->termtype)
362 free(tt->termtype);
363
364 free(tt);
365 }
366
tickit_term_ref(TickitTerm * tt)367 TickitTerm *tickit_term_ref(TickitTerm *tt)
368 {
369 tt->refcount++;
370 return tt;
371 }
372
tickit_term_unref(TickitTerm * tt)373 void tickit_term_unref(TickitTerm *tt)
374 {
375 if(tt->refcount < 1) {
376 fprintf(stderr, "tickit_term_unref: invalid refcount %d\n", tt->refcount);
377 abort();
378 }
379 tt->refcount--;
380 if(!tt->refcount)
381 tickit_term_destroy(tt);
382 }
383
tickit_term_get_termtype(TickitTerm * tt)384 const char *tickit_term_get_termtype(TickitTerm *tt)
385 {
386 return tt->termtype;
387 }
388
tickit_term_get_driver(TickitTerm * tt)389 TickitTermDriver *tickit_term_get_driver(TickitTerm *tt)
390 {
391 return tt->driver;
392 }
393
get_tmpbuffer(TickitTerm * tt,size_t len)394 static void *get_tmpbuffer(TickitTerm *tt, size_t len)
395 {
396 if(tt->tmpbuffer_len < len) {
397 if(tt->tmpbuffer)
398 free(tt->tmpbuffer);
399 tt->tmpbuffer = malloc(len);
400 tt->tmpbuffer_len = len;
401 }
402
403 return tt->tmpbuffer;
404 }
405
406 /* Driver API */
tickit_termdrv_get_tmpbuffer(TickitTermDriver * ttd,size_t len)407 void *tickit_termdrv_get_tmpbuffer(TickitTermDriver *ttd, size_t len)
408 {
409 return get_tmpbuffer(ttd->tt, len);
410 }
411
tickit_term_get_size(const TickitTerm * tt,int * lines,int * cols)412 void tickit_term_get_size(const TickitTerm *tt, int *lines, int *cols)
413 {
414 if(lines)
415 *lines = tt->lines;
416 if(cols)
417 *cols = tt->cols;
418 }
419
tickit_term_set_size(TickitTerm * tt,int lines,int cols)420 void tickit_term_set_size(TickitTerm *tt, int lines, int cols)
421 {
422 if(tt->lines != lines || tt->cols != cols) {
423 tt->lines = lines;
424 tt->cols = cols;
425
426 TickitResizeEventInfo info = { .lines = lines, .cols = cols };
427 run_events(tt, TICKIT_TERM_ON_RESIZE, &info);
428 }
429 }
430
tickit_term_refresh_size(TickitTerm * tt)431 void tickit_term_refresh_size(TickitTerm *tt)
432 {
433 if(tt->outfd == -1)
434 return;
435
436 struct winsize ws = { 0, 0, 0, 0 };
437 if(ioctl(tt->outfd, TIOCGWINSZ, &ws) == -1)
438 return;
439
440 tickit_term_set_size(tt, ws.ws_row, ws.ws_col);
441 }
442
443 static TickitTerm *first_sigwinch_observer;
444
sigwinch(int signum)445 static void sigwinch(int signum)
446 {
447 for(TickitTerm *tt = first_sigwinch_observer; tt; tt = tt->next_sigwinch_observer)
448 tt->window_changed = 1;
449 }
450
tickit_term_observe_sigwinch(TickitTerm * tt,bool observe)451 void tickit_term_observe_sigwinch(TickitTerm *tt, bool observe)
452 {
453 sigset_t newset;
454 sigset_t oldset;
455
456 sigemptyset(&newset);
457 sigaddset(&newset, SIGWINCH);
458
459 sigprocmask(SIG_BLOCK, &newset, &oldset);
460
461 if(observe && !tt->observe_winch) {
462 tt->observe_winch = true;
463
464 if(!first_sigwinch_observer)
465 sigaction(SIGWINCH, &(struct sigaction){ .sa_handler = sigwinch }, NULL);
466
467 TickitTerm **tailp = &first_sigwinch_observer;
468 while(*tailp)
469 tailp = &(*tailp)->next_sigwinch_observer;
470 *tailp = tt;
471 }
472 else if(!observe && tt->observe_winch) {
473 TickitTerm **tailp = &first_sigwinch_observer;
474 while(tailp && *tailp != tt)
475 tailp = &(*tailp)->next_sigwinch_observer;
476 if(tailp)
477 *tailp = (*tailp)->next_sigwinch_observer;
478
479 if(!first_sigwinch_observer)
480 sigaction(SIGWINCH, &(struct sigaction){ .sa_handler = SIG_DFL }, NULL);
481
482 tt->observe_winch = false;
483 }
484
485 sigprocmask(SIG_SETMASK, &oldset, NULL);
486 }
487
check_resize(TickitTerm * tt)488 static void check_resize(TickitTerm *tt)
489 {
490 if(!tt->window_changed)
491 return;
492
493 tt->window_changed = 0;
494 tickit_term_refresh_size(tt);
495 }
496
tickit_term_set_output_fd(TickitTerm * tt,int fd)497 void tickit_term_set_output_fd(TickitTerm *tt, int fd)
498 {
499 tt->outfd = fd;
500
501 tickit_term_refresh_size(tt);
502
503 if(tt->state == UNSTARTED) {
504 if(tt->driver->vtable->start)
505 (*tt->driver->vtable->start)(tt->driver);
506 tt->state = STARTING;
507 }
508 }
509
tickit_term_get_output_fd(const TickitTerm * tt)510 int tickit_term_get_output_fd(const TickitTerm *tt)
511 {
512 return tt->outfd;
513 }
514
tickit_term_set_output_func(TickitTerm * tt,TickitTermOutputFunc * fn,void * user)515 void tickit_term_set_output_func(TickitTerm *tt, TickitTermOutputFunc *fn, void *user)
516 {
517 if(tt->outfunc)
518 (*tt->outfunc)(tt, NULL, 0, tt->outfunc_user);
519
520 tt->outfunc = fn;
521 tt->outfunc_user = user;
522
523 if(tt->state == UNSTARTED) {
524 if(tt->driver->vtable->start)
525 (*tt->driver->vtable->start)(tt->driver);
526 tt->state = STARTING;
527 }
528 }
529
tickit_term_set_output_buffer(TickitTerm * tt,size_t len)530 void tickit_term_set_output_buffer(TickitTerm *tt, size_t len)
531 {
532 void *buffer = len ? malloc(len) : NULL;
533
534 if(tt->outbuffer)
535 free(tt->outbuffer);
536
537 tt->outbuffer = buffer;
538 tt->outbuffer_len = len;
539 tt->outbuffer_cur = 0;
540 }
541
tickit_term_set_input_fd(TickitTerm * tt,int fd)542 void tickit_term_set_input_fd(TickitTerm *tt, int fd)
543 {
544 if(tt->termkey)
545 termkey_destroy(tt->termkey);
546
547 tt->infd = fd;
548 (void)get_termkey(tt);
549 }
550
tickit_term_get_input_fd(const TickitTerm * tt)551 int tickit_term_get_input_fd(const TickitTerm *tt)
552 {
553 return tt->infd;
554 }
555
tickit_term_get_utf8(const TickitTerm * tt)556 TickitMaybeBool tickit_term_get_utf8(const TickitTerm *tt)
557 {
558 return tt->is_utf8;
559 }
560
tickit_term_set_utf8(TickitTerm * tt,bool utf8)561 void tickit_term_set_utf8(TickitTerm *tt, bool utf8)
562 {
563 tt->is_utf8 = !!utf8;
564
565 /* TODO: See what we can think of for the output side */
566
567 if(tt->termkey) {
568 int flags = termkey_get_flags(tt->termkey) & ~(TERMKEY_FLAG_UTF8|TERMKEY_FLAG_RAW);
569
570 if(utf8)
571 flags |= TERMKEY_FLAG_UTF8;
572 else
573 flags |= TERMKEY_FLAG_RAW;
574
575 termkey_set_flags(tt->termkey, flags);
576 }
577 }
578
tickit_term_await_started_msec(TickitTerm * tt,long msec)579 void tickit_term_await_started_msec(TickitTerm *tt, long msec)
580 {
581 if(msec > -1)
582 tickit_term_await_started_tv(tt, &(struct timeval){
583 .tv_sec = msec / 1000,
584 .tv_usec = (msec % 1000) * 1000,
585 });
586 else
587 tickit_term_await_started_tv(tt, NULL);
588 }
589
tickit_term_await_started_tv(TickitTerm * tt,const struct timeval * timeout)590 void tickit_term_await_started_tv(TickitTerm *tt, const struct timeval *timeout)
591 {
592 if(tt->state == STARTED)
593 return;
594
595 struct timeval until;
596 gettimeofday(&until, NULL);
597
598 // until += timeout
599 if(until.tv_usec + timeout->tv_usec >= 1E6) {
600 until.tv_sec += timeout->tv_sec + 1;
601 until.tv_usec += timeout->tv_usec - 1E6;
602 }
603 else {
604 until.tv_sec += timeout->tv_sec;
605 until.tv_usec += timeout->tv_usec;
606 }
607
608 while(tt->state != STARTED) {
609 if(!tt->driver->vtable->started ||
610 (*tt->driver->vtable->started)(tt->driver))
611 break;
612
613 struct timeval timeout;
614 gettimeofday(&timeout, NULL);
615
616 // timeout = until - timeout
617 if(until.tv_usec < timeout.tv_usec) {
618 timeout.tv_sec = until.tv_sec - timeout.tv_sec - 1;
619 timeout.tv_usec = until.tv_usec - timeout.tv_usec + 1E6;
620 }
621 else {
622 timeout.tv_sec = until.tv_sec - timeout.tv_sec;
623 timeout.tv_usec = until.tv_usec - timeout.tv_usec;
624 }
625
626 if(timeout.tv_sec < 0)
627 break;
628
629 tickit_term_input_wait_tv(tt, &timeout);
630 }
631
632 tt->state = STARTED;
633 }
634
got_key(TickitTerm * tt,TermKey * tk,TermKeyKey * key)635 static void got_key(TickitTerm *tt, TermKey *tk, TermKeyKey *key)
636 {
637 if(key->type == TERMKEY_TYPE_MOUSE) {
638 TermKeyMouseEvent ev;
639 TickitMouseEventInfo info;
640 termkey_interpret_mouse(tk, key, &ev, &info.button, &info.line, &info.col);
641 /* TermKey is 1-based, Tickit is 0-based for position */
642 info.line--; info.col--;
643 switch(ev) {
644 case TERMKEY_MOUSE_PRESS: info.type = TICKIT_MOUSEEV_PRESS; break;
645 case TERMKEY_MOUSE_DRAG: info.type = TICKIT_MOUSEEV_DRAG; break;
646 case TERMKEY_MOUSE_RELEASE: info.type = TICKIT_MOUSEEV_RELEASE; break;
647 default: info.type = -1; break;
648 }
649
650 /* Translate PRESS of buttons >= 4 into wheel events */
651 if(ev == TERMKEY_MOUSE_PRESS && info.button >= 4) {
652 info.type = TICKIT_MOUSEEV_WHEEL;
653 info.button -= (4 - TICKIT_MOUSEWHEEL_UP);
654 }
655
656 info.mod = key->modifiers;
657
658 if(info.type == TICKIT_MOUSEEV_PRESS || info.type == TICKIT_MOUSEEV_DRAG) {
659 tt->mouse_buttons_held |= (1 << info.button);
660 }
661 else if(info.type == TICKIT_MOUSEEV_RELEASE && info.button) {
662 tt->mouse_buttons_held &= ~(1 << info.button);
663 }
664 else if(info.type == TICKIT_MOUSEEV_RELEASE) {
665 /* X10 cannot report which button was released. Just report that they
666 * all were */
667 for(info.button = 1; tt->mouse_buttons_held; info.button++)
668 if(tt->mouse_buttons_held & (1 << info.button)) {
669 run_events_whilefalse(tt, TICKIT_TERM_ON_MOUSE, &info);
670 tt->mouse_buttons_held &= ~(1 << info.button);
671 }
672 return; // Buttons have been handled
673 }
674
675 run_events_whilefalse(tt, TICKIT_TERM_ON_MOUSE, &info);
676 }
677 else if(key->type == TERMKEY_TYPE_UNICODE && !key->modifiers) {
678 /* Unmodified unicode */
679 TickitKeyEventInfo info = {
680 .type = TICKIT_KEYEV_TEXT,
681 .str = key->utf8,
682 .mod = key->modifiers,
683 };
684
685 run_events_whilefalse(tt, TICKIT_TERM_ON_KEY, &info);
686 }
687 else if(key->type == TERMKEY_TYPE_UNICODE ||
688 key->type == TERMKEY_TYPE_FUNCTION ||
689 key->type == TERMKEY_TYPE_KEYSYM) {
690 char buffer[64]; // TODO: should be long enough
691 termkey_strfkey(tk, buffer, sizeof buffer, key, TERMKEY_FORMAT_ALTISMETA);
692
693 TickitKeyEventInfo info = {
694 .type = TICKIT_KEYEV_KEY,
695 .str = buffer,
696 .mod = key->modifiers,
697 };
698
699 run_events_whilefalse(tt, TICKIT_TERM_ON_KEY, &info);
700 }
701 else if(key->type == TERMKEY_TYPE_MODEREPORT) {
702 if(tt->driver->vtable->on_modereport) {
703 int initial, mode, value;
704 termkey_interpret_modereport(tk, key, &initial, &mode, &value);
705
706 (tt->driver->vtable->on_modereport)(tt->driver, initial, mode, value);
707 }
708 }
709 else if(key->type == TERMKEY_TYPE_DCS) {
710 const char *dcs;
711 if(termkey_interpret_string(tk, key, &dcs) != TERMKEY_RES_KEY)
712 return;
713
714 if(strneq(dcs, "1$r", 3)) { // Successful DECRQSS
715 if(tt->driver->vtable->on_decrqss)
716 (tt->driver->vtable->on_decrqss)(tt->driver, dcs + 3, strlen(dcs + 3));
717 }
718 }
719 }
720
tickit_term_emit_key(TickitTerm * tt,TickitKeyEventInfo * info)721 void tickit_term_emit_key(TickitTerm *tt, TickitKeyEventInfo *info)
722 {
723 run_events_whilefalse(tt, TICKIT_TERM_ON_KEY, info);
724 }
725
tickit_term_emit_mouse(TickitTerm * tt,TickitMouseEventInfo * info)726 void tickit_term_emit_mouse(TickitTerm *tt, TickitMouseEventInfo *info)
727 {
728 run_events_whilefalse(tt, TICKIT_TERM_ON_MOUSE, info);
729 }
730
get_keys(TickitTerm * tt,TermKey * tk)731 static void get_keys(TickitTerm *tt, TermKey *tk)
732 {
733 TermKeyResult res;
734 TermKeyKey key;
735 while((res = termkey_getkey(tk, &key)) == TERMKEY_RES_KEY) {
736 got_key(tt, tk, &key);
737 }
738
739 if(res == TERMKEY_RES_AGAIN) {
740 struct timeval tv;
741 gettimeofday(&tv, NULL);
742
743 /* tv += waittime in MSEC */
744 int new_usec = tv.tv_usec + (termkey_get_waittime(tk) * MSEC);
745 if(new_usec >= SECOND) {
746 tv.tv_sec++;
747 new_usec -= SECOND;
748 }
749 tv.tv_usec = new_usec;
750
751 tt->input_timeout_at = tv;
752 }
753 else {
754 tt->input_timeout_at.tv_sec = -1;
755 }
756 }
757
tickit_term_input_push_bytes(TickitTerm * tt,const char * bytes,size_t len)758 void tickit_term_input_push_bytes(TickitTerm *tt, const char *bytes, size_t len)
759 {
760 check_resize(tt);
761
762 TermKey *tk = get_termkey(tt);
763 termkey_push_bytes(tk, bytes, len);
764
765 get_keys(tt, tk);
766 }
767
tickit_term_input_readable(TickitTerm * tt)768 void tickit_term_input_readable(TickitTerm *tt)
769 {
770 check_resize(tt);
771
772 TermKey *tk = get_termkey(tt);
773 termkey_advisereadable(tk);
774
775 get_keys(tt, tk);
776 }
777
get_timeout(TickitTerm * tt)778 static int get_timeout(TickitTerm *tt)
779 {
780 if(tt->input_timeout_at.tv_sec == -1)
781 return -1;
782
783 struct timeval tv;
784 gettimeofday(&tv, NULL);
785
786 /* tv = tt->input_timeout_at - tv */
787 int new_usec = tt->input_timeout_at.tv_usec - tv.tv_usec;
788 tv.tv_sec = tt->input_timeout_at.tv_sec - tv.tv_sec;
789 if(new_usec < 0) {
790 tv.tv_sec--;
791 tv.tv_usec = new_usec + SECOND;
792 }
793 else {
794 tv.tv_usec = new_usec;
795 }
796
797 if(tv.tv_sec > 0 || (tv.tv_sec == 0 && tv.tv_usec > 0))
798 return tv.tv_sec * 1000 + (tv.tv_usec+MSEC-1)/MSEC;
799
800 return 0;
801 }
802
timedout(TickitTerm * tt)803 static void timedout(TickitTerm *tt)
804 {
805 TermKey *tk = get_termkey(tt);
806
807 TermKeyKey key;
808 if(termkey_getkey_force(tk, &key) == TERMKEY_RES_KEY) {
809 got_key(tt, tk, &key);
810 }
811
812 tt->input_timeout_at.tv_sec = -1;
813 }
814
tickit_term_input_check_timeout_msec(TickitTerm * tt)815 int tickit_term_input_check_timeout_msec(TickitTerm *tt)
816 {
817 check_resize(tt);
818
819 int msec = get_timeout(tt);
820
821 if(msec != 0)
822 return msec;
823
824 timedout(tt);
825 return -1;
826 }
827
tickit_term_input_wait_msec(TickitTerm * tt,long msec)828 void tickit_term_input_wait_msec(TickitTerm *tt, long msec)
829 {
830 TermKey *tk = get_termkey(tt);
831
832 int maxwait = get_timeout(tt);
833 if(maxwait > -1) {
834 if(msec == -1 || maxwait < msec)
835 msec = maxwait;
836 }
837
838 struct timeval timeout;
839 if(msec > -1) {
840 timeout.tv_sec = msec / 1000;
841 timeout.tv_usec = (msec % 1000) * 1000;
842 }
843
844 fd_set readfds;
845 FD_ZERO(&readfds);
846
847 int fd = termkey_get_fd(tk);
848 if (fd < 0 || fd >= FD_SETSIZE)
849 return;
850
851 FD_SET(fd, &readfds);
852 int ret = select(fd + 1, &readfds, NULL, NULL, msec > -1 ? &timeout : NULL);
853
854 if(ret == 0)
855 timedout(tt);
856 else if(ret > 0)
857 termkey_advisereadable(tk);
858
859 check_resize(tt);
860
861 get_keys(tt, tk);
862 }
863
tickit_term_input_wait_tv(TickitTerm * tt,const struct timeval * timeout)864 void tickit_term_input_wait_tv(TickitTerm *tt, const struct timeval *timeout)
865 {
866 if(timeout)
867 tickit_term_input_wait_msec(tt, (long)(timeout->tv_sec) + (timeout->tv_usec / 1000));
868 else
869 tickit_term_input_wait_msec(tt, -1);
870 }
871
tickit_term_flush(TickitTerm * tt)872 void tickit_term_flush(TickitTerm *tt)
873 {
874 if(tt->outbuffer_cur == 0)
875 return;
876
877 if(tt->outfunc)
878 (*tt->outfunc)(tt, tt->outbuffer, tt->outbuffer_cur, tt->outfunc_user);
879 else if(tt->outfd != -1) {
880 write(tt->outfd, tt->outbuffer, tt->outbuffer_cur);
881 }
882
883 tt->outbuffer_cur = 0;
884 }
885
write_str(TickitTerm * tt,const char * str,size_t len)886 static void write_str(TickitTerm *tt, const char *str, size_t len)
887 {
888 if(len == 0)
889 len = strlen(str);
890
891 if(tt->outbuffer) {
892 while(len > 0) {
893 size_t space = tt->outbuffer_len - tt->outbuffer_cur;
894 if(len < space)
895 space = len;
896 memcpy(tt->outbuffer + tt->outbuffer_cur, str, space);
897 tt->outbuffer_cur += space;
898 str += space;
899 len -= space;
900 if(tt->outbuffer_cur >= tt->outbuffer_len)
901 tickit_term_flush(tt);
902 }
903 }
904 else if(tt->outfunc) {
905 (*tt->outfunc)(tt, str, len, tt->outfunc_user);
906 }
907 else if(tt->outfd != -1) {
908 write(tt->outfd, str, len);
909 }
910 }
911 /* Driver API */
tickit_termdrv_write_str(TickitTermDriver * ttd,const char * str,size_t len)912 void tickit_termdrv_write_str(TickitTermDriver *ttd, const char *str, size_t len)
913 {
914 write_str(ttd->tt, str, len);
915 }
916
write_vstrf(TickitTerm * tt,const char * fmt,va_list args)917 static void write_vstrf(TickitTerm *tt, const char *fmt, va_list args)
918 {
919 /* It's likely the output will fit in, say, 64 bytes */
920 char buffer[64];
921 size_t len;
922 {
923 va_list args_for_size;
924 va_copy(args_for_size, args);
925
926 len = vsnprintf(buffer, sizeof buffer, fmt, args_for_size);
927
928 va_end(args_for_size);
929 }
930
931 if(len < sizeof buffer) {
932 write_str(tt, buffer, len);
933 return;
934 }
935
936 char *morebuffer = get_tmpbuffer(tt, len + 1);
937 vsnprintf(morebuffer, len + 1, fmt, args);
938
939 write_str(tt, morebuffer, len);
940 }
941
942 /* Driver API */
tickit_termdrv_write_strf(TickitTermDriver * ttd,const char * fmt,...)943 void tickit_termdrv_write_strf(TickitTermDriver *ttd, const char *fmt, ...)
944 {
945 va_list args;
946 va_start(args, fmt);
947 write_vstrf(ttd->tt, fmt, args);
948 va_end(args);
949 }
950
tickit_term_print(TickitTerm * tt,const char * str)951 void tickit_term_print(TickitTerm *tt, const char *str)
952 {
953 (*tt->driver->vtable->print)(tt->driver, str, strlen(str));
954 }
955
tickit_term_printn(TickitTerm * tt,const char * str,size_t len)956 void tickit_term_printn(TickitTerm *tt, const char *str, size_t len)
957 {
958 (*tt->driver->vtable->print)(tt->driver, str, len);
959 }
960
tickit_term_printf(TickitTerm * tt,const char * fmt,...)961 void tickit_term_printf(TickitTerm *tt, const char *fmt, ...)
962 {
963 va_list args;
964 va_start(args, fmt);
965 tickit_term_vprintf(tt, fmt, args);
966 va_end(args);
967 }
968
tickit_term_vprintf(TickitTerm * tt,const char * fmt,va_list args)969 void tickit_term_vprintf(TickitTerm *tt, const char *fmt, va_list args)
970 {
971 va_list args2;
972 va_copy(args2, args);
973
974 size_t len = vsnprintf(NULL, 0, fmt, args);
975 char *buf = get_tmpbuffer(tt, len + 1);
976 vsnprintf(buf, len + 1, fmt, args2);
977 (*tt->driver->vtable->print)(tt->driver, buf, len);
978
979 va_end(args2);
980 }
981
tickit_term_goto(TickitTerm * tt,int line,int col)982 bool tickit_term_goto(TickitTerm *tt, int line, int col)
983 {
984 return (*tt->driver->vtable->goto_abs)(tt->driver, line, col);
985 }
986
tickit_term_move(TickitTerm * tt,int downward,int rightward)987 void tickit_term_move(TickitTerm *tt, int downward, int rightward)
988 {
989 (*tt->driver->vtable->move_rel)(tt->driver, downward, rightward);
990 }
991
tickit_term_scrollrect(TickitTerm * tt,TickitRect rect,int downward,int rightward)992 bool tickit_term_scrollrect(TickitTerm *tt, TickitRect rect, int downward, int rightward)
993 {
994 return (*tt->driver->vtable->scrollrect)(tt->driver, &rect, downward, rightward);
995 }
996
convert_colour(int index,int colours)997 static int convert_colour(int index, int colours)
998 {
999 if(colours >= 16)
1000 return xterm256[index].as16;
1001 else
1002 return xterm256[index].as8;
1003 }
1004
tickit_term_chpen(TickitTerm * tt,const TickitPen * pen)1005 void tickit_term_chpen(TickitTerm *tt, const TickitPen *pen)
1006 {
1007 TickitPen *delta = tickit_pen_new();
1008
1009 for(TickitPenAttr attr = 1; attr < TICKIT_N_PEN_ATTRS; attr++) {
1010 if(!tickit_pen_has_attr(pen, attr))
1011 continue;
1012
1013 if(tickit_pen_has_attr(tt->pen, attr) && tickit_pen_equiv_attr(tt->pen, pen, attr))
1014 continue;
1015
1016 int index;
1017 if((attr == TICKIT_PEN_FG || attr == TICKIT_PEN_BG) &&
1018 (index = tickit_pen_get_colour_attr(pen, attr)) >= tt->colors) {
1019 index = convert_colour(index, tt->colors);
1020 tickit_pen_set_colour_attr(tt->pen, attr, index);
1021 tickit_pen_set_colour_attr(delta, attr, index);
1022 }
1023 else {
1024 tickit_pen_copy_attr(tt->pen, pen, attr);
1025 tickit_pen_copy_attr(delta, pen, attr);
1026 }
1027 }
1028
1029 (*tt->driver->vtable->chpen)(tt->driver, delta, tt->pen);
1030
1031 tickit_pen_unref(delta);
1032 }
1033
tickit_term_setpen(TickitTerm * tt,const TickitPen * pen)1034 void tickit_term_setpen(TickitTerm *tt, const TickitPen *pen)
1035 {
1036 TickitPen *delta = tickit_pen_new();
1037
1038 for(TickitPenAttr attr = 1; attr < TICKIT_N_PEN_ATTRS; attr++) {
1039 if(tickit_pen_has_attr(tt->pen, attr) && tickit_pen_equiv_attr(tt->pen, pen, attr))
1040 continue;
1041
1042 int index;
1043 if((attr == TICKIT_PEN_FG || attr == TICKIT_PEN_BG) &&
1044 (index = tickit_pen_get_colour_attr(pen, attr)) >= tt->colors) {
1045 index = convert_colour(index, tt->colors);
1046 tickit_pen_set_colour_attr(tt->pen, attr, index);
1047 tickit_pen_set_colour_attr(delta, attr, index);
1048 }
1049 else {
1050 tickit_pen_copy_attr(tt->pen, pen, attr);
1051 tickit_pen_copy_attr(delta, pen, attr);
1052 }
1053 }
1054
1055 (*tt->driver->vtable->chpen)(tt->driver, delta, tt->pen);
1056
1057 tickit_pen_unref(delta);
1058 }
1059
1060 /* Driver API */
tickit_termdrv_current_pen(TickitTermDriver * ttd)1061 TickitPen *tickit_termdrv_current_pen(TickitTermDriver *ttd)
1062 {
1063 return ttd->tt->pen;
1064 }
1065
tickit_term_clear(TickitTerm * tt)1066 void tickit_term_clear(TickitTerm *tt)
1067 {
1068 (*tt->driver->vtable->clear)(tt->driver);
1069 }
1070
tickit_term_erasech(TickitTerm * tt,int count,TickitMaybeBool moveend)1071 void tickit_term_erasech(TickitTerm *tt, int count, TickitMaybeBool moveend)
1072 {
1073 (*tt->driver->vtable->erasech)(tt->driver, count, moveend);
1074 }
1075
tickit_term_getctl_int(TickitTerm * tt,TickitTermCtl ctl,int * value)1076 bool tickit_term_getctl_int(TickitTerm *tt, TickitTermCtl ctl, int *value)
1077 {
1078 return (*tt->driver->vtable->getctl_int)(tt->driver, ctl, value);
1079 }
1080
tickit_term_setctl_int(TickitTerm * tt,TickitTermCtl ctl,int value)1081 bool tickit_term_setctl_int(TickitTerm *tt, TickitTermCtl ctl, int value)
1082 {
1083 return (*tt->driver->vtable->setctl_int)(tt->driver, ctl, value);
1084 }
1085
tickit_term_setctl_str(TickitTerm * tt,TickitTermCtl ctl,const char * value)1086 bool tickit_term_setctl_str(TickitTerm *tt, TickitTermCtl ctl, const char *value)
1087 {
1088 return (*tt->driver->vtable->setctl_str)(tt->driver, ctl, value);
1089 }
1090
tickit_term_pause(TickitTerm * tt)1091 void tickit_term_pause(TickitTerm *tt)
1092 {
1093 if(tt->driver->vtable->pause)
1094 (*tt->driver->vtable->pause)(tt->driver);
1095
1096 if(tt->termkey)
1097 termkey_stop(tt->termkey);
1098 }
1099
tickit_term_resume(TickitTerm * tt)1100 void tickit_term_resume(TickitTerm *tt)
1101 {
1102 if(tt->termkey)
1103 termkey_start(tt->termkey);
1104
1105 if(tt->driver->vtable->resume)
1106 (*tt->driver->vtable->resume)(tt->driver);
1107 }
1108
tickit_term_ctlname(TickitTermCtl ctl)1109 const char *tickit_term_ctlname(TickitTermCtl ctl)
1110 {
1111 switch(ctl) {
1112 case TICKIT_TERMCTL_ALTSCREEN: return "altscreen";
1113 case TICKIT_TERMCTL_CURSORVIS: return "cursorvis";
1114 case TICKIT_TERMCTL_MOUSE: return "mouse";
1115 case TICKIT_TERMCTL_CURSORBLINK: return "cursorblink";
1116 case TICKIT_TERMCTL_CURSORSHAPE: return "cursorshape";
1117 case TICKIT_TERMCTL_ICON_TEXT: return "icon_text";
1118 case TICKIT_TERMCTL_TITLE_TEXT: return "title_text";
1119 case TICKIT_TERMCTL_ICONTITLE_TEXT: return "icontitle_text";
1120 case TICKIT_TERMCTL_KEYPAD_APP: return "keypad_app";
1121 case TICKIT_TERMCTL_COLORS: return "colors";
1122
1123 case TICKIT_N_TERMCTLS: ;
1124 }
1125 return NULL;
1126 }
1127
tickit_term_lookup_ctl(const char * name)1128 TickitTermCtl tickit_term_lookup_ctl(const char *name)
1129 {
1130 const char *s;
1131
1132 for(TickitTermCtl ctl = 1; ctl < TICKIT_N_TERMCTLS; ctl++)
1133 if((s = tickit_term_ctlname(ctl)) && streq(name, s))
1134 return ctl;
1135
1136 return -1;
1137 }
1138
tickit_term_ctltype(TickitTermCtl ctl)1139 TickitType tickit_term_ctltype(TickitTermCtl ctl)
1140 {
1141 switch(ctl) {
1142 case TICKIT_TERMCTL_ALTSCREEN:
1143 case TICKIT_TERMCTL_CURSORVIS:
1144 case TICKIT_TERMCTL_CURSORBLINK:
1145 case TICKIT_TERMCTL_KEYPAD_APP:
1146 return TICKIT_TYPE_BOOL;
1147
1148 case TICKIT_TERMCTL_COLORS:
1149 case TICKIT_TERMCTL_CURSORSHAPE:
1150 case TICKIT_TERMCTL_MOUSE:
1151 return TICKIT_TYPE_INT;
1152
1153 case TICKIT_TERMCTL_ICON_TEXT:
1154 case TICKIT_TERMCTL_ICONTITLE_TEXT:
1155 case TICKIT_TERMCTL_TITLE_TEXT:
1156 return TICKIT_TYPE_STR;
1157
1158 case TICKIT_N_TERMCTLS:
1159 ;
1160 }
1161 return TICKIT_TYPE_NONE;
1162 }
1163