1 #include "tickit.h"
2 #include "tickit-evloop.h"
3 
4 #include <errno.h>
5 #include <signal.h>
6 #include <string.h>
7 #include <sys/time.h>
8 #include <sys/wait.h>
9 #include <unistd.h>
10 
11 /* POSIX doesn't actually define an NSIG macro, but if it did, almost every
12  * known platform would set it to 32
13  */
14 #ifndef NSIG
15 #  define NSIG 32
16 #endif
17 
18 #define streq(a,b) (!strcmp(a,b))
19 
20 /* INTERNAL */
21 TickitWindow* tickit_window_new_root2(Tickit *t, TickitTerm *term);
22 
23 struct TickitWatch {
24   TickitWatch *next;
25 
26   Tickit *t; // uncounted
27 
28   enum {
29     WATCH_NONE,
30     WATCH_IO,
31     WATCH_TIMER,
32     WATCH_LATER,
33     WATCH_SIGNAL,
34     WATCH_PROCESS,
35   } type;
36 
37   TickitBindFlags flags;
38   TickitCallbackFn *fn;
39   void *user;
40 
41   union {
42     void *ptr;
43     int   i;
44   } evdata;
45 
46   union {
47     struct {
48       int fd;
49       TickitIOCondition cond;
50     } io;
51 
52     struct {
53       struct timeval at;
54     } timer;
55 
56     struct {
57       int signum;
58     } signal;
59 
60     struct {
61       pid_t pid;
62       int wstatus; /* in case of pre-exited process */
63     } process;
64   };
65 };
66 
67 extern TickitEventHooks tickit_evloop_default;
68 
69 struct Tickit {
70   int refcount;
71 
72   TickitTerm   *term;
73   TickitWindow *rootwin;
74 
75   TickitWatch *iowatches, *timers, *laters, *signals, *processes;
76 
77   const TickitEventHooks *evhooks;
78   void                   *evdata;
79 
80   struct {
81     int pipefds[2];
82     void *pipewatch;
83     sigset_t watched;
84     sigset_t pending;
85   } signal;
86 
87   void *sigchldwatch;
88 
89   unsigned int done_setup    : 1,
90                use_altscreen : 1;
91 };
92 
93 static int on_term_timeout(Tickit *t, TickitEventFlags flags, void *info, void *user);
on_term_timeout(Tickit * t,TickitEventFlags flags,void * info,void * user)94 static int on_term_timeout(Tickit *t, TickitEventFlags flags, void *info, void *user)
95 {
96   int timeout = tickit_term_input_check_timeout_msec(t->term);
97   if(timeout > -1)
98     tickit_watch_timer_after_msec(t, timeout, 0, on_term_timeout, NULL);
99 
100   return 0;
101 }
102 
on_term_readable(Tickit * t,TickitEventFlags flags,void * info,void * user)103 static int on_term_readable(Tickit *t, TickitEventFlags flags, void *info, void *user)
104 {
105   tickit_term_input_readable(t->term);
106 
107   on_term_timeout(t, TICKIT_EV_FIRE, NULL, NULL);
108   return 0;
109 }
110 
111 static Tickit *signal_observer;
112 
sighandler(int signum)113 static void sighandler(int signum)
114 {
115   if(signal_observer) {
116     sigaddset(&signal_observer->signal.pending, signum);
117     write(signal_observer->signal.pipefds[1], "\0", 1);
118   }
119 }
120 
on_sigpipe_readable(Tickit * t,TickitEventFlags flags,void * info,void * user)121 static int on_sigpipe_readable(Tickit *t, TickitEventFlags flags, void *info, void *user)
122 {
123   char buf[1];
124   read(t->signal.pipefds[0], &buf, 1);
125 
126   sigset_t pending;
127   {
128     sigset_t orig;
129     sigprocmask(SIG_BLOCK, &t->signal.watched, &orig);
130 
131     pending = t->signal.pending;
132     sigemptyset(&t->signal.pending);
133 
134     sigprocmask(SIG_SETMASK, &orig, NULL);
135   }
136 
137   TickitWatch *this;
138   for(this = t->signals; this; this = this->next) {
139     if(sigismember(&pending, this->signal.signum))
140       (*this->fn)(this->t, TICKIT_EV_FIRE, NULL, this->user);
141   }
142 
143   return 0;
144 }
145 
on_sigwinch(Tickit * t,TickitEventFlags flags,void * info,void * user)146 static int on_sigwinch(Tickit *t, TickitEventFlags flags, void *info, void *user)
147 {
148   if(!t->term)
149     return 0;
150 
151   tickit_term_refresh_size(t->term);
152   return 0;
153 }
154 
setupterm(Tickit * t)155 static void setupterm(Tickit *t)
156 {
157   TickitTerm *tt = t->term;
158 
159   tickit_term_await_started_msec(tt, 50);
160 
161   if(t->use_altscreen)
162     tickit_term_setctl_int(tt, TICKIT_TERMCTL_ALTSCREEN, 1);
163   tickit_term_setctl_int(tt, TICKIT_TERMCTL_CURSORVIS, 0);
164   tickit_term_setctl_int(tt, TICKIT_TERMCTL_MOUSE, TICKIT_TERM_MOUSEMODE_DRAG);
165   tickit_term_setctl_int(tt, TICKIT_TERMCTL_KEYPAD_APP, 1);
166 
167   tickit_term_clear(tt);
168   tickit_term_flush(tt);
169 
170   t->done_setup = true;
171 }
172 
teardownterm(Tickit * t)173 static void teardownterm(Tickit *t)
174 {
175   t->done_setup = false;
176 }
177 
tickit_build(const struct TickitBuilder * builder)178 Tickit *tickit_build(const struct TickitBuilder *builder)
179 {
180   Tickit *t = malloc(sizeof(Tickit));
181   if(!t)
182     return NULL;
183 
184   t->refcount = 1;
185 
186   t->term = NULL;
187   t->rootwin = NULL;
188 
189   t->evhooks = builder->evhooks;
190   if(!t->evhooks)
191     t->evhooks = &tickit_evloop_default;
192   t->evdata  = (*t->evhooks->init)(t, builder->evinitdata);
193   if(!t->evdata)
194     goto abort;
195 
196   t->iowatches = NULL;
197   t->timers    = NULL;
198   t->laters    = NULL;
199   t->signals   = NULL;
200   t->processes = NULL;
201 
202   t->signal.pipefds[0] = -1;
203   t->signal.pipewatch = NULL;
204   sigemptyset(&t->signal.watched);
205   sigemptyset(&t->signal.pending);
206 
207   t->sigchldwatch = NULL;
208 
209   t->done_setup = false;
210 
211   t->use_altscreen = true;
212 
213   TickitTerm *tt = builder->tt;
214   if(!tt) {
215     struct TickitTermBuilder term_builder = builder->term_builder;
216     if(!term_builder.output_buffersize)
217       term_builder.output_buffersize = 4096;
218 
219     tt = tickit_term_build(&term_builder);
220     if(!tt)
221       goto abort;
222   }
223   t->term = tt; /* take ownership */
224 
225   tickit_watch_io(t, tickit_term_get_input_fd(tt), TICKIT_IO_IN,
226       0, on_term_readable, NULL);
227 
228   tickit_watch_signal(t, SIGWINCH, 0, on_sigwinch, NULL);
229 
230   return t;
231 
232 abort:
233   free(t);
234   return NULL;
235 }
236 
tickit_new_with_evloop(TickitTerm * tt,TickitEventHooks * evhooks,void * initdata)237 Tickit *tickit_new_with_evloop(TickitTerm *tt, TickitEventHooks *evhooks, void *initdata)
238 {
239   return tickit_build(&(struct TickitBuilder){
240     .tt = tt,
241     /* In case tt==NULL */
242     .term_builder.open = TICKIT_OPEN_STDTTY,
243 
244     .evhooks = evhooks,
245     .evinitdata = initdata,
246   });
247 }
248 
tickit_new_for_term(TickitTerm * tt)249 Tickit *tickit_new_for_term(TickitTerm *tt)
250 {
251   return tickit_build(&(struct TickitBuilder){
252     .tt = tt,
253   });
254 }
255 
tickit_new_stdio(void)256 Tickit *tickit_new_stdio(void)
257 {
258   return tickit_build(&(const struct TickitBuilder){
259     .term_builder.open = TICKIT_OPEN_STDIO,
260   });
261 }
262 
tickit_new_stdtty(void)263 Tickit *tickit_new_stdtty(void)
264 {
265   return tickit_build(&(const struct TickitBuilder){
266     .term_builder.open = TICKIT_OPEN_STDTTY,
267   });
268 }
269 
insert_watch(TickitWatch ** watchesptr,TickitBindFlags flags,TickitWatch * new)270 static void insert_watch(TickitWatch **watchesptr, TickitBindFlags flags, TickitWatch *new)
271 {
272   if(!(flags & TICKIT_BIND_FIRST)) {
273     while(*watchesptr)
274       watchesptr = &(*watchesptr)->next;
275   }
276 
277   new->next = *watchesptr;
278   *watchesptr = new;
279 }
280 
destroy_watchlist(Tickit * t,TickitWatch * watches,void (* cancelfunc)(void * data,TickitWatch * watch))281 static void destroy_watchlist(Tickit *t, TickitWatch *watches, void (*cancelfunc)(void *data, TickitWatch *watch))
282 {
283   TickitWatch *this, *next;
284   for(this = watches; this; this = next) {
285     next = this->next;
286 
287     if(this->flags & (TICKIT_BIND_UNBIND|TICKIT_BIND_DESTROY))
288       (*this->fn)(this->t, TICKIT_EV_UNBIND|TICKIT_EV_DESTROY, NULL, this->user);
289 
290     if(cancelfunc)
291       (*cancelfunc)(t->evdata, this);
292 
293     free(this);
294   }
295 }
296 
invoke_watch(TickitWatch * watch,TickitEventFlags flags,void * info)297 static void invoke_watch(TickitWatch *watch, TickitEventFlags flags, void *info)
298 {
299   (*watch->fn)(watch->t, flags, info, watch->user);
300 
301   /* Remove oneshot watches from the list */
302   TickitWatch **prevp;
303   switch(watch->type) {
304     case WATCH_NONE:
305     case WATCH_IO:
306     case WATCH_SIGNAL:
307       return;
308 
309     case WATCH_TIMER:
310       prevp = &watch->t->timers;
311       break;
312 
313     case WATCH_LATER:
314       prevp = &watch->t->laters;
315       break;
316 
317     case WATCH_PROCESS:
318       prevp = &watch->t->processes;
319       break;
320   }
321 
322   while(*prevp) {
323     if(*prevp == watch) {
324       *prevp = watch->next;
325       watch->next = NULL;
326       watch->type = WATCH_NONE;
327       free(watch);
328       return;
329     }
330 
331     prevp = &(*prevp)->next;
332   }
333 }
334 
tickit_destroy(Tickit * t)335 static void tickit_destroy(Tickit *t)
336 {
337   if(t->done_setup)
338     teardownterm(t);
339 
340   if(t->rootwin)
341     tickit_window_unref(t->rootwin);
342   if(t->term) {
343     tickit_term_teardown(t->term);
344     tickit_term_unref(t->term);
345   }
346 
347   if(t->sigchldwatch)
348     tickit_watch_cancel(t, t->sigchldwatch);
349 
350   if(t->signal.pipewatch)
351     tickit_watch_cancel(t, t->signal.pipewatch);
352   for(int signum = 1; signum < NSIG; signum++) {
353     if(sigismember(&t->signal.watched, signum))
354       sigaction(signum, &(struct sigaction){ .sa_handler = SIG_DFL }, NULL);
355   }
356   if(signal_observer == t)
357     signal_observer = NULL;
358 
359   if(t->iowatches)
360     destroy_watchlist(t, t->iowatches, t->evhooks->cancel_io);
361   if(t->timers)
362     destroy_watchlist(t, t->timers, t->evhooks->cancel_timer);
363   if(t->laters)
364     destroy_watchlist(t, t->laters, t->evhooks->cancel_later);
365   if(t->signals)
366     destroy_watchlist(t, t->signals, t->evhooks->cancel_signal);
367   if(t->processes)
368     destroy_watchlist(t, t->processes, t->evhooks->cancel_process);
369 
370   (*t->evhooks->destroy)(t->evdata);
371 
372   free(t);
373 }
374 
tickit_ref(Tickit * t)375 Tickit *tickit_ref(Tickit *t)
376 {
377   t->refcount++;
378   return t;
379 }
380 
tickit_unref(Tickit * t)381 void tickit_unref(Tickit *t)
382 {
383   t->refcount--;
384   if(!t->refcount)
385     tickit_destroy(t);
386 }
387 
tickit_get_term(Tickit * t)388 TickitTerm *tickit_get_term(Tickit *t)
389 {
390   return t->term;
391 }
392 
tickit_get_rootwin(Tickit * t)393 TickitWindow *tickit_get_rootwin(Tickit *t)
394 {
395   if(!t->rootwin) {
396     t->rootwin = tickit_window_new_root2(t, t->term);
397   }
398 
399   return t->rootwin;
400 }
401 
tickit_getctl_int(Tickit * t,TickitCtl ctl,int * value)402 bool tickit_getctl_int(Tickit *t, TickitCtl ctl, int *value)
403 {
404   switch(ctl) {
405     case TICKIT_CTL_USE_ALTSCREEN:
406       *value = t->use_altscreen;
407       return true;
408 
409     case TICKIT_N_CTLS:
410       ;
411   }
412   return false;
413 }
414 
tickit_setctl_int(Tickit * t,TickitCtl ctl,int value)415 bool tickit_setctl_int(Tickit *t, TickitCtl ctl, int value)
416 {
417   switch(ctl) {
418     case TICKIT_CTL_USE_ALTSCREEN:
419       t->use_altscreen = value;
420       return true;
421 
422     case TICKIT_N_CTLS:
423       ;
424   }
425   return false;
426 }
427 
on_sigint(Tickit * t,TickitEventFlags flags,void * info,void * data)428 static int on_sigint(Tickit *t, TickitEventFlags flags, void *info, void *data)
429 {
430   tickit_stop(t);
431   return 0;
432 }
433 
tickit_tick(Tickit * t,TickitRunFlags flags)434 void tickit_tick(Tickit *t, TickitRunFlags flags)
435 {
436   if(!t->done_setup && !(flags & TICKIT_RUN_NOSETUP))
437     setupterm(t);
438 
439   (*t->evhooks->run)(t->evdata, TICKIT_RUN_ONCE | flags);
440 }
441 
tickit_run(Tickit * t)442 void tickit_run(Tickit *t)
443 {
444   void *sigint_watch = tickit_watch_signal(t, SIGINT, 0, &on_sigint, NULL);
445 
446   if(!t->done_setup)
447     setupterm(t);
448 
449   (*t->evhooks->run)(t->evdata, TICKIT_RUN_DEFAULT);
450 
451   tickit_watch_cancel(t, sigint_watch);
452 }
453 
tickit_stop(Tickit * t)454 void tickit_stop(Tickit *t)
455 {
456   return (*t->evhooks->stop)(t->evdata);
457 }
458 
tickit_watch_io(Tickit * t,int fd,TickitIOCondition cond,TickitBindFlags flags,TickitCallbackFn * fn,void * user)459 void *tickit_watch_io(Tickit *t, int fd, TickitIOCondition cond, TickitBindFlags flags, TickitCallbackFn *fn, void *user)
460 {
461   TickitWatch *watch = malloc(sizeof(TickitWatch));
462   if(!watch)
463     return NULL;
464 
465   watch->next = NULL;
466   watch->t    = t;
467   watch->type = WATCH_IO;
468 
469   watch->flags = flags & (TICKIT_BIND_UNBIND|TICKIT_BIND_UNBIND);
470   watch->fn = fn;
471   watch->user = user;
472 
473   watch->io.fd   = fd;
474   watch->io.cond = cond;
475 
476   if(!(*t->evhooks->io)(t->evdata, fd, cond, flags, watch))
477     goto fail;
478 
479   insert_watch(&t->iowatches, flags, watch);
480 
481   return watch;
482 
483 fail:
484   free(watch);
485   return NULL;
486 }
487 
tickit_watch_io_read(Tickit * t,int fd,TickitBindFlags flags,TickitCallbackFn * fn,void * user)488 void *tickit_watch_io_read(Tickit *t, int fd, TickitBindFlags flags, TickitCallbackFn *fn, void *user)
489 {
490   return tickit_watch_io(t, fd, TICKIT_IO_IN, flags, fn, user);
491 }
492 
tickit_watch_timer_at_tv(Tickit * t,const struct timeval * at,TickitBindFlags flags,TickitCallbackFn * fn,void * user)493 void *tickit_watch_timer_at_tv(Tickit *t, const struct timeval *at, TickitBindFlags flags, TickitCallbackFn *fn, void *user)
494 {
495   TickitWatch *watch = malloc(sizeof(TickitWatch));
496   if(!watch)
497     return NULL;
498 
499   watch->next = NULL;
500   watch->t    = t;
501   watch->type = WATCH_TIMER;
502 
503   watch->flags = flags & (TICKIT_BIND_UNBIND|TICKIT_BIND_DESTROY);
504   watch->fn = fn;
505   watch->user = user;
506 
507   watch->timer.at = *at;
508 
509   if(t->evhooks->timer)
510     if(!(*t->evhooks->timer)(t->evdata, at, flags, watch))
511       goto fail;
512 
513   TickitWatch **prevp = &t->timers;
514   /* Try to insert in-order at matching timestamp; don't use insert_watch() */
515   while(*prevp && !timercmp(&(*prevp)->timer.at, at, >))
516     prevp = &(*prevp)->next;
517 
518   watch->next = *prevp;
519   *prevp = watch;
520 
521   return watch;
522 
523 fail:
524   free(watch);
525   return NULL;
526 }
527 
tickit_watch_timer_at_epoch(Tickit * t,time_t at,TickitBindFlags flags,TickitCallbackFn * func,void * user)528 void *tickit_watch_timer_at_epoch(Tickit *t, time_t at, TickitBindFlags flags, TickitCallbackFn *func, void *user)
529 {
530   return tickit_watch_timer_at_tv(t, &(struct timeval){
531       .tv_sec = at,
532       .tv_usec = 0,
533     }, flags, func, user);
534 }
535 
tickit_watch_timer_after_tv(Tickit * t,const struct timeval * after,TickitBindFlags flags,TickitCallbackFn * fn,void * user)536 void *tickit_watch_timer_after_tv(Tickit *t, const struct timeval *after, TickitBindFlags flags, TickitCallbackFn *fn, void *user)
537 {
538   struct timeval at;
539   gettimeofday(&at, NULL);
540 
541   /* at + after ==> at */
542   timeradd(&at, after, &at);
543 
544   return tickit_watch_timer_at_tv(t, &at, flags, fn, user);
545 }
546 
tickit_watch_timer_after_msec(Tickit * t,int msec,TickitBindFlags flags,TickitCallbackFn * fn,void * user)547 void *tickit_watch_timer_after_msec(Tickit *t, int msec, TickitBindFlags flags, TickitCallbackFn *fn, void *user)
548 {
549   return tickit_watch_timer_after_tv(t, &(struct timeval){
550       .tv_sec = msec / 1000,
551       .tv_usec = (msec % 1000) * 1000,
552     }, flags, fn, user);
553 }
554 
tickit_watch_later(Tickit * t,TickitBindFlags flags,TickitCallbackFn * fn,void * user)555 void *tickit_watch_later(Tickit *t, TickitBindFlags flags, TickitCallbackFn *fn, void *user)
556 {
557   TickitWatch *watch = malloc(sizeof(TickitWatch));
558   if(!watch)
559     return NULL;
560 
561   watch->next = NULL;
562   watch->t    = t;
563   watch->type = WATCH_LATER;
564 
565   watch->flags = flags & (TICKIT_BIND_UNBIND|TICKIT_BIND_DESTROY);
566   watch->fn = fn;
567   watch->user = user;
568 
569   if(t->evhooks->later)
570     if(!(*t->evhooks->later)(t->evdata, flags, watch))
571       goto fail;
572 
573   insert_watch(&t->laters, flags, watch);
574 
575   return watch;
576 
577 fail:
578   free(watch);
579   return NULL;
580 }
581 
watch_signal(Tickit * t,int signum,TickitWatch * watch)582 static void watch_signal(Tickit *t, int signum, TickitWatch *watch)
583 {
584   if(t->signal.pipefds[0] == -1) {
585     pipe(t->signal.pipefds);
586     t->signal.pipewatch = tickit_watch_io(t, t->signal.pipefds[0], TICKIT_IO_IN, 0, &on_sigpipe_readable, t);
587   }
588 
589   if(sigismember(&t->signal.watched, signum))
590     return;
591 
592   sigaction(signum, &(struct sigaction){ .sa_handler = sighandler }, NULL);
593   sigaddset(&t->signal.watched, signum);
594 
595   if(!signal_observer)
596     signal_observer = t;
597 }
598 
unwatch_signal(Tickit * t,TickitWatch * watch)599 static void unwatch_signal(Tickit *t, TickitWatch *watch)
600 {
601   int signum = watch->signal.signum;
602 
603   /* watch has already been removed from t->signals */
604   TickitWatch *this;
605   for(this = t->signals; this; this = this->next) {
606     if(this->signal.signum == signum)
607       return;
608   }
609 
610   sigdelset(&t->signal.watched, signum);
611   sigaction(signum, &(struct sigaction){ .sa_handler = SIG_DFL }, NULL);
612 }
613 
tickit_watch_signal(Tickit * t,int signum,TickitBindFlags flags,TickitCallbackFn * fn,void * user)614 void *tickit_watch_signal(Tickit *t, int signum, TickitBindFlags flags, TickitCallbackFn *fn, void *user)
615 {
616   TickitWatch *watch = malloc(sizeof(TickitWatch));
617   if(!watch)
618     return NULL;
619 
620   watch->next = NULL;
621   watch->t    = t;
622   watch->type = WATCH_SIGNAL;
623 
624   watch->flags = flags & (TICKIT_BIND_UNBIND|TICKIT_BIND_DESTROY);
625   watch->fn = fn;
626   watch->user = user;
627 
628   watch->signal.signum = signum;
629 
630   if(!t->evhooks->signal ||
631       !(*t->evhooks->signal)(t->evdata, signum, flags, watch))
632     watch_signal(t, signum, watch);
633 
634   insert_watch(&t->signals, flags, watch);
635 
636   return watch;
637 }
638 
on_sigchld(Tickit * t,TickitEventFlags flags,void * info,void * data)639 static int on_sigchld(Tickit *t, TickitEventFlags flags, void *info, void *data)
640 {
641   TickitWatch *this, *next;
642   for(this = t->processes; this; this = next) {
643     next = this->next;
644 
645     TickitProcessWatchInfo info;
646     if(waitpid(this->process.pid, &info.wstatus, WNOHANG) <= 0)
647       continue;
648 
649     info.pid = this->process.pid;
650     invoke_watch(this, TICKIT_EV_FIRE, &info);
651   }
652   return 0;
653 }
654 
process_notify(Tickit * t,TickitEventFlags flags,void * _info,void * data)655 static int process_notify(Tickit *t, TickitEventFlags flags, void *_info, void *data)
656 {
657   TickitWatch *watch = data;
658 
659   tickit_evloop_invoke_processwatch(watch, TICKIT_EV_FIRE, watch->process.wstatus);
660 
661   return 0;
662 }
663 
tickit_watch_process(Tickit * t,pid_t pid,TickitBindFlags flags,TickitCallbackFn * fn,void * user)664 void *tickit_watch_process(Tickit *t, pid_t pid, TickitBindFlags flags, TickitCallbackFn *fn, void *user)
665 {
666   TickitWatch *watch = malloc(sizeof(TickitWatch));
667   if(!watch)
668     return NULL;
669 
670   watch->next = NULL;
671   watch->t    = t;
672   watch->type = WATCH_PROCESS;
673 
674   watch->flags = flags & (TICKIT_BIND_UNBIND|TICKIT_BIND_DESTROY);
675   watch->fn = fn;
676   watch->user = user;
677 
678   watch->process.pid = pid;
679 
680   if(!t->evhooks->process ||
681       !(*t->evhooks->process)(t->evdata, pid, flags, watch)) {
682     if(!t->sigchldwatch)
683       t->sigchldwatch = tickit_watch_signal(t, SIGCHLD, 0, &on_sigchld, NULL);
684 
685     if(waitpid(pid, &watch->process.wstatus, WNOHANG) > 0) {
686       /* Process already exited, so SIGCHLD won't see it. We can't invoke
687        * callback immediately as user will be expecting it to only be called via
688        * tickit_run(). We'll install a later handler for it
689        */
690       tickit_watch_later(t, 0, process_notify, watch);
691 
692       return watch;
693     }
694   }
695 
696   insert_watch(&t->processes, flags, watch);
697 
698   return watch;
699 }
700 
tickit_watch_cancel(Tickit * t,void * _watch)701 void tickit_watch_cancel(Tickit *t, void *_watch)
702 {
703   TickitWatch *watch = _watch;
704 
705   TickitWatch **thisp;
706   switch(watch->type) {
707     case WATCH_IO:
708       thisp = &t->iowatches;
709       break;
710     case WATCH_TIMER:
711       thisp = &t->timers;
712       break;
713     case WATCH_LATER:
714       thisp = &t->laters;
715       break;
716     case WATCH_SIGNAL:
717       thisp = &t->signals;
718       break;
719     case WATCH_PROCESS:
720       thisp = &t->processes;
721       break;
722 
723     case WATCH_NONE:
724       return;
725   }
726 
727   while(*thisp) {
728     TickitWatch *this = *thisp;
729     if(this == watch) {
730       *thisp = this->next;
731 
732       if(this->flags & TICKIT_BIND_UNBIND)
733         (*this->fn)(t, TICKIT_EV_UNBIND, NULL, this->user);
734 
735       switch(this->type) {
736         case WATCH_IO:
737           (*t->evhooks->cancel_io)(t->evdata, this);
738           break;
739         case WATCH_TIMER:
740           if(t->evhooks->cancel_timer)
741             (*t->evhooks->cancel_timer)(t->evdata, this);
742           break;
743         case WATCH_LATER:
744           if(t->evhooks->cancel_later)
745             (*t->evhooks->cancel_later)(t->evdata, this);
746           break;
747         case WATCH_SIGNAL:
748           if(t->evhooks->cancel_signal)
749             (*t->evhooks->cancel_signal)(t->evdata, this);
750           else
751             unwatch_signal(t, this);
752           break;
753         case WATCH_PROCESS:
754           if(t->evhooks->cancel_process)
755             (*t->evhooks->cancel_process)(t->evdata, this);
756           break;
757 
758         case WATCH_NONE:
759           ;
760       }
761 
762       free(this);
763     }
764 
765     if(!thisp || !*thisp)
766       break;
767 
768     thisp = &(*thisp)->next;
769   }
770 }
771 
tickit_evloop_next_timer_msec(Tickit * t)772 int tickit_evloop_next_timer_msec(Tickit *t)
773 {
774   if(t->laters)
775     return 0;
776 
777   if(!t->timers)
778     return -1;
779 
780   struct timeval now, delay;
781   gettimeofday(&now, NULL);
782 
783   /* timers->timer.at - now ==> delay */
784   timersub(&t->timers->timer.at, &now, &delay);
785 
786   int msec = (delay.tv_sec * 1000) + (delay.tv_usec / 1000);
787   if(msec < 0)
788     msec = 0;
789 
790   return msec;
791 }
792 
tickit_evloop_invoke_timers(Tickit * t)793 void tickit_evloop_invoke_timers(Tickit *t)
794 {
795   /* detach the later queue before running any events */
796   TickitWatch *later = t->laters;
797   t->laters = NULL;
798 
799   if(t->timers) {
800     struct timeval now;
801     gettimeofday(&now, NULL);
802 
803     /* timer queue is stored ordered, so we can just eat a prefix
804      * of it
805      */
806 
807     TickitWatch *this = t->timers;
808     while(this) {
809       if(timercmp(&this->timer.at, &now, >))
810         break;
811 
812       /* TODO: consider what info might point at */
813       (*this->fn)(this->t, TICKIT_EV_FIRE|TICKIT_EV_UNBIND, NULL, this->user);
814 
815       TickitWatch *next = this->next;
816       free(this);
817       this = next;
818     }
819 
820     t->timers = this;
821   }
822 
823   while(later) {
824     (*later->fn)(later->t, TICKIT_EV_FIRE|TICKIT_EV_UNBIND, NULL, later->user);
825 
826     TickitWatch *next = later->next;
827     free(later);
828     later = next;
829   }
830 }
831 
tickit_evloop_get_watch_data(TickitWatch * watch)832 void *tickit_evloop_get_watch_data(TickitWatch *watch)
833 {
834   return watch->evdata.ptr;
835 }
836 
tickit_evloop_set_watch_data(TickitWatch * watch,void * data)837 void tickit_evloop_set_watch_data(TickitWatch *watch, void *data)
838 {
839   watch->evdata.ptr = data;
840 }
841 
tickit_evloop_get_watch_data_int(TickitWatch * watch)842 int  tickit_evloop_get_watch_data_int(TickitWatch *watch)
843 {
844   return watch->evdata.i;
845 }
846 
tickit_evloop_set_watch_data_int(TickitWatch * watch,int data)847 void tickit_evloop_set_watch_data_int(TickitWatch *watch, int data)
848 {
849   watch->evdata.i = data;
850 }
851 
tickit_evloop_invoke_watch(TickitWatch * watch,TickitEventFlags flags)852 void tickit_evloop_invoke_watch(TickitWatch *watch, TickitEventFlags flags)
853 {
854   invoke_watch(watch, flags, NULL);
855 }
856 
tickit_evloop_invoke_iowatch(TickitWatch * watch,TickitEventFlags flags,TickitIOCondition cond)857 void tickit_evloop_invoke_iowatch(TickitWatch *watch, TickitEventFlags flags, TickitIOCondition cond)
858 {
859   invoke_watch(watch, flags, &(TickitIOWatchInfo){
860     .fd   = watch->io.fd,
861     .cond = cond,
862   });
863 }
864 
tickit_evloop_invoke_processwatch(TickitWatch * watch,TickitEventFlags flags,int wstatus)865 void tickit_evloop_invoke_processwatch(TickitWatch *watch, TickitEventFlags flags, int wstatus)
866 {
867   invoke_watch(watch, flags, &(TickitProcessWatchInfo){
868     .pid     = watch->process.pid,
869     .wstatus = wstatus,
870   });
871 }
872 
tickit_evloop_invoke_sigwatches(Tickit * t,int signum)873 void tickit_evloop_invoke_sigwatches(Tickit *t, int signum)
874 {
875   TickitWatch *this;
876   for(this = t->signals; this; this = this->next) {
877     if(this->signal.signum == signum)
878       (*this->fn)(this->t, TICKIT_EV_FIRE, NULL, this->user);
879   }
880 }
881 
tickit_evloop_sigwinch(Tickit * t)882 void tickit_evloop_sigwinch(Tickit *t)
883 {
884   /* No longer need to do anything, as on_sigwinch() has already handled it
885    * But this function needs to remain for ABI reasons
886    */
887 }
888 
tickit_ctlname(TickitCtl ctl)889 const char *tickit_ctlname(TickitCtl ctl)
890 {
891   switch(ctl) {
892     case TICKIT_CTL_USE_ALTSCREEN: return "use-altscreen";
893 
894     case TICKIT_N_CTLS: ;
895   }
896   return NULL;
897 }
898 
tickit_lookup_ctl(const char * name)899 TickitCtl tickit_lookup_ctl(const char *name)
900 {
901   const char *s;
902 
903   for(TickitCtl ctl = 1; ctl < TICKIT_N_CTLS; ctl++)
904     if((s = tickit_ctlname(ctl)) && streq(name, s))
905       return ctl;
906 
907   return -1;
908 }
909 
tickit_ctltype(TickitCtl ctl)910 TickitType tickit_ctltype(TickitCtl ctl)
911 {
912   switch(ctl) {
913     case TICKIT_CTL_USE_ALTSCREEN:
914       return TICKIT_TYPE_BOOL;
915 
916     case TICKIT_N_CTLS:
917       ;
918   }
919   return TICKIT_TYPE_NONE;
920 }
921 
tickit_version_major(void)922 int tickit_version_major(void) { return TICKIT_VERSION_MAJOR; }
tickit_version_minor(void)923 int tickit_version_minor(void) { return TICKIT_VERSION_MINOR; }
tickit_version_patch(void)924 int tickit_version_patch(void) { return TICKIT_VERSION_PATCH; }
925