1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 obt/mainloop.c for the Openbox window manager
4 Copyright (c) 2006 Mikael Magnusson
5 Copyright (c) 2003-2007 Dana Jansens
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 See the COPYING file for a copy of the GNU General Public License.
20 #include "obt/mainloop.h"
21 #include "obt/display.h"
22 #include "obt/xqueue.h"
31 #ifdef HAVE_SYS_SELECT_H
32 #include <sys/select.h>
34 #ifdef HAVE_SYS_SOCKET_H
35 #include <sys/socket.h>
41 typedef struct _ObtMainLoopTimer ObtMainLoopTimer;
42 typedef struct _ObtMainLoopSignal ObtMainLoopSignal;
43 typedef struct _ObtMainLoopSignalHandlerType ObtMainLoopSignalHandlerType;
44 typedef struct _ObtMainLoopXHandlerType ObtMainLoopXHandlerType;
45 typedef struct _ObtMainLoopFdHandlerType ObtMainLoopFdHandlerType;
47 /* this should be more than the number of possible signals on any
49 #define NUM_SIGNALS 99
51 /* all created ObtMainLoops. Used by the signal handler to pass along
53 static GSList *all_loops;
55 /* signals are global to all loops */
57 guint installed; /* a ref count */
58 struct sigaction oldact;
59 } all_signals[NUM_SIGNALS];
61 /* a set of all possible signals */
62 static sigset_t all_signals_set;
64 /* signals which cause a core dump, these can't be used for callbacks */
65 static gint core_signals[] =
78 #define NUM_CORE_SIGNALS (sizeof(core_signals) / sizeof(core_signals[0]))
80 static void sighandler(gint sig);
81 static void timer_dispatch(ObtMainLoop *loop, GTimeVal **wait);
82 static void fd_handler_destroy(gpointer data);
83 static void calc_max_fd(ObtMainLoop *loop);
90 gboolean run; /* do keep running */
91 gboolean running; /* is still running */
95 gint fd_x; /* The X fd is a special case! */
97 GHashTable *fd_handlers;
104 gboolean signal_fired;
105 guint signals_fired[NUM_SIGNALS];
106 GSList *signal_handlers[NUM_SIGNALS];
109 struct _ObtMainLoopTimer
115 GDestroyNotify destroy;
117 /* The timer needs to be freed */
119 /* The time the last fire should've been at */
121 /* When this timer will next trigger */
124 /* Only allow a timer's function to fire once per run through the list,
125 so that it doesn't get locked in there forever */
129 struct _ObtMainLoopSignalHandlerType
134 ObtMainLoopSignalHandler func;
135 GDestroyNotify destroy;
138 struct _ObtMainLoopXHandlerType
142 ObtMainLoopXHandler func;
143 GDestroyNotify destroy;
146 struct _ObtMainLoopFdHandlerType
151 ObtMainLoopFdHandler func;
152 GDestroyNotify destroy;
155 ObtMainLoop *obt_main_loop_new(void)
159 loop = g_slice_new0(ObtMainLoop);
161 FD_ZERO(&loop->fd_set);
165 loop->fd_handlers = g_hash_table_new_full(g_int_hash, g_int_equal,
166 NULL, fd_handler_destroy);
168 g_get_current_time(&loop->now);
170 /* only do this if we're the first loop created */
173 struct sigaction action;
176 /* initialize the all_signals_set */
177 sigfillset(&all_signals_set);
179 sigemptyset(&sigset);
180 action.sa_handler = sighandler;
181 action.sa_mask = sigset;
182 action.sa_flags = SA_NOCLDSTOP;
184 /* grab all the signals that cause core dumps */
185 for (i = 0; i < NUM_CORE_SIGNALS; ++i) {
186 /* SIGABRT is curiously not grabbed here!! that's because when we
187 get one of the core_signals, we use abort() to dump the core.
188 And having the abort() only go back to our signal handler again
189 is less than optimal */
190 if (core_signals[i] != SIGABRT) {
191 sigaction(core_signals[i], &action,
192 &all_signals[core_signals[i]].oldact);
193 all_signals[core_signals[i]].installed++;
198 all_loops = g_slist_prepend(all_loops, loop);
203 void obt_main_loop_ref(ObtMainLoop *loop)
208 void obt_main_loop_unref(ObtMainLoop *loop)
213 if (loop && --loop->ref == 0) {
214 g_assert(loop->running == FALSE);
216 for (it = loop->x_handlers; it; it = next) {
217 ObtMainLoopXHandlerType *h = it->data;
218 next = g_slist_next(it);
219 obt_main_loop_x_remove(loop, h->func);
222 g_hash_table_destroy(loop->fd_handlers);
224 for (it = loop->timers; it; it = g_slist_next(it)) {
225 ObtMainLoopTimer *t = it->data;
226 if (t->destroy) t->destroy(t->data);
227 g_slice_free(ObtMainLoopTimer, t);
229 g_slist_free(loop->timers);
232 for (i = 0; i < NUM_SIGNALS; ++i)
233 for (it = loop->signal_handlers[i]; it; it = next) {
234 ObtMainLoopSignalHandlerType *h = it->data;
235 next = g_slist_next(it);
236 obt_main_loop_signal_remove(loop, h->func);
239 all_loops = g_slist_remove(all_loops, loop);
241 /* only do this if we're the last loop destroyed */
243 /* grab all the signals that cause core dumps */
244 for (i = 0; i < NUM_CORE_SIGNALS; ++i) {
245 if (all_signals[core_signals[i]].installed) {
246 sigaction(core_signals[i],
247 &all_signals[core_signals[i]].oldact, NULL);
248 all_signals[core_signals[i]].installed--;
253 g_slice_free(ObtMainLoop, loop);
257 static void fd_handle_foreach(gpointer key,
261 ObtMainLoopFdHandlerType *h = value;
264 if (FD_ISSET(h->fd, set))
265 h->func(h->fd, h->data);
268 void obt_main_loop_run(ObtMainLoop *loop)
271 struct timeval *wait;
276 loop->running = TRUE;
279 if (loop->signal_fired) {
283 /* block signals so that we can do this without the data changing
285 sigprocmask(SIG_SETMASK, &all_signals_set, &oldset);
287 for (i = 0; i < NUM_SIGNALS; ++i) {
288 while (loop->signals_fired[i]) {
289 for (it = loop->signal_handlers[i];
290 it; it = g_slist_next(it)) {
291 ObtMainLoopSignalHandlerType *h = it->data;
294 loop->signals_fired[i]--;
297 loop->signal_fired = FALSE;
299 sigprocmask(SIG_SETMASK, &oldset, NULL);
300 } else if (loop->display && xqueue_pending_local()) {
301 while (xqueue_next_local(&e) && loop->run) {
302 if (e.type == MappingNotify)
303 XRefreshKeyboardMapping(&e.xmapping);
305 for (it = loop->x_handlers; it; it = g_slist_next(it)) {
306 ObtMainLoopXHandlerType *h = it->data;
307 h->func(&e, h->data);
311 /* this only runs if there were no x events received */
312 timer_dispatch(loop, (GTimeVal**)&wait);
314 selset = loop->fd_set;
315 /* there is a small race condition here. if a signal occurs
316 between this if() and the select() then we will not process
317 the signal until 'wait' expires. possible solutions include
318 using GStaticMutex, and having the signal handler set 'wait'
320 if (!loop->signal_fired)
321 select(loop->fd_max + 1, &selset, NULL, NULL, wait);
323 /* handle the X events with highest prioirity */
324 if (FD_ISSET(loop->fd_x, &selset))
327 g_hash_table_foreach(loop->fd_handlers,
328 fd_handle_foreach, &selset);
332 loop->running = FALSE;
335 void obt_main_loop_exit(ObtMainLoop *loop)
340 /*** XEVENT WATCHERS ***/
342 void obt_main_loop_x_add(ObtMainLoop *loop,
343 ObtMainLoopXHandler handler,
345 GDestroyNotify notify)
347 ObtMainLoopXHandlerType *h;
349 h = g_slice_new(ObtMainLoopXHandlerType);
355 if (!loop->x_handlers) {
356 g_assert(obt_display); /* is the display open? */
358 loop->display = obt_display;
359 loop->fd_x = ConnectionNumber(loop->display);
360 FD_SET(loop->fd_x, &loop->fd_set);
364 loop->x_handlers = g_slist_prepend(loop->x_handlers, h);
367 void obt_main_loop_x_remove(ObtMainLoop *loop,
368 ObtMainLoopXHandler handler)
372 for (it = loop->x_handlers; it; it = next) {
373 ObtMainLoopXHandlerType *h = it->data;
374 next = g_slist_next(it);
375 if (h->func == handler) {
376 loop->x_handlers = g_slist_delete_link(loop->x_handlers, it);
377 if (h->destroy) h->destroy(h->data);
378 g_slice_free(ObtMainLoopXHandlerType, h);
382 if (!loop->x_handlers) {
383 FD_CLR(loop->fd_x, &loop->fd_set);
388 /*** SIGNAL WATCHERS ***/
390 static void sighandler(gint sig)
395 g_return_if_fail(sig < NUM_SIGNALS);
397 for (i = 0; i < NUM_CORE_SIGNALS; ++i)
398 if (sig == core_signals[i]) {
399 /* XXX special case for signals that default to core dump.
400 but throw some helpful output here... */
402 fprintf(stderr, "How are you gentlemen? All your base are"
403 " belong to us. (Openbox received signal %d)\n", sig);
405 /* die with a core dump */
409 for (it = all_loops; it; it = g_slist_next(it)) {
410 ObtMainLoop *loop = it->data;
411 loop->signal_fired = TRUE;
412 loop->signals_fired[sig]++;
416 void obt_main_loop_signal_add(ObtMainLoop *loop,
418 ObtMainLoopSignalHandler handler,
420 GDestroyNotify notify)
422 ObtMainLoopSignalHandlerType *h;
424 g_return_if_fail(signal < NUM_SIGNALS);
426 h = g_slice_new(ObtMainLoopSignalHandlerType);
432 loop->signal_handlers[h->signal] =
433 g_slist_prepend(loop->signal_handlers[h->signal], h);
435 if (!all_signals[signal].installed) {
436 struct sigaction action;
439 sigemptyset(&sigset);
440 action.sa_handler = sighandler;
441 action.sa_mask = sigset;
442 action.sa_flags = SA_NOCLDSTOP;
444 sigaction(signal, &action, &all_signals[signal].oldact);
447 all_signals[signal].installed++;
450 void obt_main_loop_signal_remove(ObtMainLoop *loop,
451 ObtMainLoopSignalHandler handler)
456 for (i = 0; i < NUM_SIGNALS; ++i) {
457 for (it = loop->signal_handlers[i]; it; it = next) {
458 ObtMainLoopSignalHandlerType *h = it->data;
460 next = g_slist_next(it);
462 if (h->func == handler) {
463 g_assert(all_signals[h->signal].installed > 0);
465 all_signals[h->signal].installed--;
466 if (!all_signals[h->signal].installed) {
467 sigaction(h->signal, &all_signals[h->signal].oldact, NULL);
470 loop->signal_handlers[i] =
471 g_slist_delete_link(loop->signal_handlers[i], it);
472 if (h->destroy) h->destroy(h->data);
474 g_slice_free(ObtMainLoopSignalHandlerType, h);
481 /*** FILE DESCRIPTOR WATCHERS ***/
483 static void max_fd_func(gpointer key, gpointer value, gpointer data)
485 ObtMainLoop *loop = data;
488 loop->fd_max = MAX(loop->fd_max, *(gint*)key);
491 static void calc_max_fd(ObtMainLoop *loop)
493 loop->fd_max = loop->fd_x;
495 g_hash_table_foreach(loop->fd_handlers, max_fd_func, loop);
498 void obt_main_loop_fd_add(ObtMainLoop *loop,
500 ObtMainLoopFdHandler handler,
502 GDestroyNotify notify)
504 ObtMainLoopFdHandlerType *h;
506 h = g_slice_new(ObtMainLoopFdHandlerType);
513 g_hash_table_replace(loop->fd_handlers, &h->fd, h);
514 FD_SET(h->fd, &loop->fd_set);
518 static void fd_handler_destroy(gpointer data)
520 ObtMainLoopFdHandlerType *h = data;
522 FD_CLR(h->fd, &h->loop->fd_set);
526 g_slice_free(ObtMainLoopFdHandlerType, h);
529 void obt_main_loop_fd_remove(ObtMainLoop *loop,
532 g_hash_table_remove(loop->fd_handlers, &fd);
538 #define NEAREST_TIMEOUT(loop) \
539 (((ObtMainLoopTimer*)(loop)->timers->data)->timeout)
541 static glong timecompare(GTimeVal *a, GTimeVal *b)
544 if ((r = a->tv_sec - b->tv_sec)) return r;
545 return a->tv_usec - b->tv_usec;
548 static void insert_timer(ObtMainLoop *loop, ObtMainLoopTimer *ins)
551 for (it = loop->timers; it; it = g_slist_next(it)) {
552 ObtMainLoopTimer *t = it->data;
553 if (timecompare(&ins->timeout, &t->timeout) <= 0) {
554 loop->timers = g_slist_insert_before(loop->timers, it, ins);
558 if (it == NULL) /* didnt fit anywhere in the list */
559 loop->timers = g_slist_append(loop->timers, ins);
562 void obt_main_loop_timeout_add(ObtMainLoop *loop,
567 GDestroyNotify notify)
569 ObtMainLoopTimer *t = g_slice_new(ObtMainLoopTimer);
571 g_assert(microseconds > 0); /* if it's 0 it'll cause an infinite loop */
573 t->delay = microseconds;
579 g_get_current_time(&loop->now);
580 t->last = t->timeout = loop->now;
581 g_time_val_add(&t->timeout, t->delay);
583 insert_timer(loop, t);
586 void obt_main_loop_timeout_remove(ObtMainLoop *loop,
591 for (it = loop->timers; it; it = g_slist_next(it)) {
592 ObtMainLoopTimer *t = it->data;
593 if (t->func == handler)
598 void obt_main_loop_timeout_remove_data(ObtMainLoop *loop, GSourceFunc handler,
599 gpointer data, gboolean cancel_dest)
603 for (it = loop->timers; it; it = g_slist_next(it)) {
604 ObtMainLoopTimer *t = it->data;
605 if (t->func == handler && t->equal(t->data, data)) {
613 /* find the time to wait for the nearest timeout */
614 static gboolean nearest_timeout_wait(ObtMainLoop *loop, GTimeVal *tm)
616 if (loop->timers == NULL)
619 tm->tv_sec = NEAREST_TIMEOUT(loop).tv_sec - loop->now.tv_sec;
620 tm->tv_usec = NEAREST_TIMEOUT(loop).tv_usec - loop->now.tv_usec;
622 while (tm->tv_usec < 0) {
623 tm->tv_usec += G_USEC_PER_SEC;
626 tm->tv_sec += tm->tv_usec / G_USEC_PER_SEC;
627 tm->tv_usec %= G_USEC_PER_SEC;
634 static void timer_dispatch(ObtMainLoop *loop, GTimeVal **wait)
638 gboolean fired = FALSE;
640 g_get_current_time(&loop->now);
642 for (it = loop->timers; it; it = next) {
643 ObtMainLoopTimer *curr;
645 next = g_slist_next(it);
649 /* since timer_stop doesn't actually free the timer, we have to do our
650 real freeing in here.
654 loop->timers = g_slist_delete_link(loop->timers, it);
656 curr->destroy(curr->data);
657 g_slice_free(ObtMainLoopTimer, curr);
661 /* the queue is sorted, so if this timer shouldn't fire, none are
663 if (timecompare(&NEAREST_TIMEOUT(loop), &loop->now) > 0)
666 /* we set the last fired time to delay msec after the previous firing,
667 then re-insert. timers maintain their order and may trigger more
668 than once if they've waited more than one delay's worth of time.
670 loop->timers = g_slist_delete_link(loop->timers, it);
671 g_time_val_add(&curr->last, curr->delay);
672 if (curr->func(curr->data)) {
673 g_time_val_add(&curr->timeout, curr->delay);
674 insert_timer(loop, curr);
677 curr->destroy(curr->data);
678 g_slice_free(ObtMainLoopTimer, curr);
681 /* the timer queue has been shuffled, start from the beginning
682 (which is the next one to fire) */
689 /* if at least one timer fires, then don't wait on X events, as there
690 may already be some in the queue from the timer callbacks.
692 loop->ret_wait.tv_sec = loop->ret_wait.tv_usec = 0;
693 *wait = &loop->ret_wait;
694 } else if (nearest_timeout_wait(loop, &loop->ret_wait))
695 *wait = &loop->ret_wait;