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