summaryrefslogtreecommitdiff
path: root/obt/mainloop.c
diff options
context:
space:
mode:
Diffstat (limited to 'obt/mainloop.c')
-rw-r--r--obt/mainloop.c669
1 files changed, 669 insertions, 0 deletions
diff --git a/obt/mainloop.c b/obt/mainloop.c
new file mode 100644
index 00000000..9797770d
--- /dev/null
+++ b/obt/mainloop.c
@@ -0,0 +1,669 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
+
+ obt/mainloop.c for the Openbox window manager
+ Copyright (c) 2006 Mikael Magnusson
+ Copyright (c) 2003-2007 Dana Jansens
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ See the COPYING file for a copy of the GNU General Public License.
+*/
+
+#include "obt/mainloop.h"
+#include "obt/util.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/select.h>
+#include <signal.h>
+
+typedef struct _ObtMainLoopTimer ObtMainLoopTimer;
+typedef struct _ObtMainLoopSignal ObtMainLoopSignal;
+typedef struct _ObtMainLoopSignalHandlerType ObtMainLoopSignalHandlerType;
+typedef struct _ObtMainLoopXHandlerType ObtMainLoopXHandlerType;
+typedef struct _ObtMainLoopFdHandlerType ObtMainLoopFdHandlerType;
+
+/* this should be more than the number of possible signals on any
+ architecture... */
+#define NUM_SIGNALS 99
+
+/* all created ObtMainLoops. Used by the signal handler to pass along
+ signals */
+static GSList *all_loops;
+
+/* signals are global to all loops */
+static struct {
+ guint installed; /* a ref count */
+ struct sigaction oldact;
+} all_signals[NUM_SIGNALS];
+
+/* a set of all possible signals */
+static sigset_t all_signals_set;
+
+/* signals which cause a core dump, these can't be used for callbacks */
+static gint core_signals[] =
+{
+ SIGABRT,
+ SIGSEGV,
+ SIGFPE,
+ SIGILL,
+ SIGQUIT,
+ SIGTRAP,
+ SIGSYS,
+ SIGBUS,
+ SIGXCPU,
+ SIGXFSZ
+};
+#define NUM_CORE_SIGNALS (sizeof(core_signals) / sizeof(core_signals[0]))
+
+static void sighandler(gint sig);
+static void timer_dispatch(ObtMainLoop *loop, GTimeVal **wait);
+static void fd_handler_destroy(gpointer data);
+
+struct _ObtMainLoop
+{
+ gint ref;
+ Display *display;
+
+ gboolean run; /* do keep running */
+ gboolean running; /* is still running */
+
+ GSList *x_handlers;
+
+ gint fd_x; /* The X fd is a special case! */
+ gint fd_max;
+ GHashTable *fd_handlers;
+ fd_set fd_set;
+
+ GSList *timers;
+ GTimeVal now;
+ GTimeVal ret_wait;
+
+ gboolean signal_fired;
+ guint signals_fired[NUM_SIGNALS];
+ GSList *signal_handlers[NUM_SIGNALS];
+};
+
+struct _ObtMainLoopTimer
+{
+ gulong delay;
+ GSourceFunc func;
+ gpointer data;
+ GEqualFunc equal;
+ GDestroyNotify destroy;
+
+ /* The timer needs to be freed */
+ gboolean del_me;
+ /* The time the last fire should've been at */
+ GTimeVal last;
+ /* When this timer will next trigger */
+ GTimeVal timeout;
+
+ /* Only allow a timer's function to fire once per run through the list,
+ so that it doesn't get locked in there forever */
+ gboolean fired;
+};
+
+struct _ObtMainLoopSignalHandlerType
+{
+ ObtMainLoop *loop;
+ gint signal;
+ gpointer data;
+ ObtMainLoopSignalHandler func;
+ GDestroyNotify destroy;
+};
+
+struct _ObtMainLoopXHandlerType
+{
+ ObtMainLoop *loop;
+ gpointer data;
+ ObtMainLoopXHandler func;
+ GDestroyNotify destroy;
+};
+
+struct _ObtMainLoopFdHandlerType
+{
+ ObtMainLoop *loop;
+ gint fd;
+ gpointer data;
+ ObtMainLoopFdHandler func;
+ GDestroyNotify destroy;
+};
+
+ObtMainLoop *obt_main_loop_new(Display *display)
+{
+ ObtMainLoop *loop;
+
+ loop = g_new0(ObtMainLoop, 1);
+ loop->ref = 1;
+ loop->display = display;
+ loop->fd_x = ConnectionNumber(display);
+ FD_ZERO(&loop->fd_set);
+ FD_SET(loop->fd_x, &loop->fd_set);
+ loop->fd_max = loop->fd_x;
+
+ loop->fd_handlers = g_hash_table_new_full(g_int_hash, g_int_equal,
+ NULL, fd_handler_destroy);
+
+ g_get_current_time(&loop->now);
+
+ /* only do this if we're the first loop created */
+ if (!all_loops) {
+ guint i;
+ struct sigaction action;
+ sigset_t sigset;
+
+ /* initialize the all_signals_set */
+ sigfillset(&all_signals_set);
+
+ sigemptyset(&sigset);
+ action.sa_handler = sighandler;
+ action.sa_mask = sigset;
+ action.sa_flags = SA_NOCLDSTOP;
+
+ /* grab all the signals that cause core dumps */
+ for (i = 0; i < NUM_CORE_SIGNALS; ++i) {
+ /* SIGABRT is curiously not grabbed here!! that's because when we
+ get one of the core_signals, we use abort() to dump the core.
+ And having the abort() only go back to our signal handler again
+ is less than optimal */
+ if (core_signals[i] != SIGABRT) {
+ sigaction(core_signals[i], &action,
+ &all_signals[core_signals[i]].oldact);
+ all_signals[core_signals[i]].installed++;
+ }
+ }
+ }
+
+ all_loops = g_slist_prepend(all_loops, loop);
+
+ return loop;
+}
+
+void obt_main_loop_ref(ObtMainLoop *loop)
+{
+ ++loop->ref;
+}
+
+void obt_main_loop_unref(ObtMainLoop *loop)
+{
+ guint i;
+ GSList *it, *next;
+
+ if (loop && --loop->ref == 0) {
+ g_assert(loop->running == FALSE);
+
+ for (it = loop->x_handlers; it; it = next) {
+ ObtMainLoopXHandlerType *h = it->data;
+ next = g_slist_next(it);
+ obt_main_loop_x_remove(loop, h->func);
+ }
+
+ g_hash_table_destroy(loop->fd_handlers);
+
+ for (it = loop->timers; it; it = g_slist_next(it)) {
+ ObtMainLoopTimer *t = it->data;
+ if (t->destroy) t->destroy(t->data);
+ g_free(t);
+ }
+ g_slist_free(loop->timers);
+ loop->timers = NULL;
+
+ for (i = 0; i < NUM_SIGNALS; ++i)
+ for (it = loop->signal_handlers[i]; it; it = next) {
+ ObtMainLoopSignalHandlerType *h = it->data;
+ next = g_slist_next(it);
+ obt_main_loop_signal_remove(loop, h->func);
+ }
+
+ all_loops = g_slist_remove(all_loops, loop);
+
+ /* only do this if we're the last loop destroyed */
+ if (!all_loops) {
+ /* grab all the signals that cause core dumps */
+ for (i = 0; i < NUM_CORE_SIGNALS; ++i) {
+ if (all_signals[core_signals[i]].installed) {
+ sigaction(core_signals[i],
+ &all_signals[core_signals[i]].oldact, NULL);
+ all_signals[core_signals[i]].installed--;
+ }
+ }
+ }
+
+ obt_free0(loop, ObtMainLoop, 1);
+ }
+}
+
+static void fd_handle_foreach(gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ ObtMainLoopFdHandlerType *h = value;
+ fd_set *set = data;
+
+ if (FD_ISSET(h->fd, set))
+ h->func(h->fd, h->data);
+}
+
+void obt_main_loop_run(ObtMainLoop *loop)
+{
+ XEvent e;
+ struct timeval *wait;
+ fd_set selset;
+ GSList *it;
+
+ loop->run = TRUE;
+ loop->running = TRUE;
+
+ while (loop->run) {
+ if (loop->signal_fired) {
+ guint i;
+ sigset_t oldset;
+
+ /* block signals so that we can do this without the data changing
+ on us */
+ sigprocmask(SIG_SETMASK, &all_signals_set, &oldset);
+
+ for (i = 0; i < NUM_SIGNALS; ++i) {
+ while (loop->signals_fired[i]) {
+ for (it = loop->signal_handlers[i];
+ it; it = g_slist_next(it)) {
+ ObtMainLoopSignalHandlerType *h = it->data;
+ h->func(i, h->data);
+ }
+ loop->signals_fired[i]--;
+ }
+ }
+ loop->signal_fired = FALSE;
+
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+ } else if (loop->display && XPending(loop->display)) {
+ do {
+ XNextEvent(loop->display, &e);
+
+ for (it = loop->x_handlers; it; it = g_slist_next(it)) {
+ ObtMainLoopXHandlerType *h = it->data;
+ h->func(&e, h->data);
+ }
+ } while (XPending(loop->display) && loop->run);
+ } else {
+ /* this only runs if there were no x events received */
+
+ timer_dispatch(loop, (GTimeVal**)&wait);
+
+ selset = loop->fd_set;
+ /* there is a small race condition here. if a signal occurs
+ between this if() and the select() then we will not process
+ the signal until 'wait' expires. possible solutions include
+ using GStaticMutex, and having the signal handler set 'wait'
+ to 0 */
+ if (!loop->signal_fired)
+ select(loop->fd_max + 1, &selset, NULL, NULL, wait);
+
+ /* handle the X events with highest prioirity */
+ if (FD_ISSET(loop->fd_x, &selset))
+ continue;
+
+ g_hash_table_foreach(loop->fd_handlers,
+ fd_handle_foreach, &selset);
+ }
+ }
+
+ loop->running = FALSE;
+}
+
+void obt_main_loop_exit(ObtMainLoop *loop)
+{
+ loop->run = FALSE;
+}
+
+/*** XEVENT WATCHERS ***/
+
+void obt_main_loop_x_add(ObtMainLoop *loop,
+ ObtMainLoopXHandler handler,
+ gpointer data,
+ GDestroyNotify notify)
+{
+ ObtMainLoopXHandlerType *h;
+
+ h = g_new(ObtMainLoopXHandlerType, 1);
+ h->loop = loop;
+ h->func = handler;
+ h->data = data;
+ h->destroy = notify;
+ loop->x_handlers = g_slist_prepend(loop->x_handlers, h);
+}
+
+void obt_main_loop_x_remove(ObtMainLoop *loop,
+ ObtMainLoopXHandler handler)
+{
+ GSList *it, *next;
+
+ for (it = loop->x_handlers; it; it = next) {
+ ObtMainLoopXHandlerType *h = it->data;
+ next = g_slist_next(it);
+ if (h->func == handler) {
+ loop->x_handlers = g_slist_delete_link(loop->x_handlers, it);
+ if (h->destroy) h->destroy(h->data);
+ g_free(h);
+ }
+ }
+}
+
+/*** SIGNAL WATCHERS ***/
+
+static void sighandler(gint sig)
+{
+ GSList *it;
+ guint i;
+
+ g_return_if_fail(sig < NUM_SIGNALS);
+
+ for (i = 0; i < NUM_CORE_SIGNALS; ++i)
+ if (sig == core_signals[i]) {
+ /* XXX special case for signals that default to core dump.
+ but throw some helpful output here... */
+
+ fprintf(stderr, "How are you gentlemen? All your base are"
+ " belong to us. (Openbox received signal %d)\n", sig);
+
+ /* die with a core dump */
+ abort();
+ }
+
+ for (it = all_loops; it; it = g_slist_next(it)) {
+ ObtMainLoop *loop = it->data;
+ loop->signal_fired = TRUE;
+ loop->signals_fired[sig]++;
+ }
+}
+
+void obt_main_loop_signal_add(ObtMainLoop *loop,
+ gint signal,
+ ObtMainLoopSignalHandler handler,
+ gpointer data,
+ GDestroyNotify notify)
+{
+ ObtMainLoopSignalHandlerType *h;
+
+ g_return_if_fail(signal < NUM_SIGNALS);
+
+ h = g_new(ObtMainLoopSignalHandlerType, 1);
+ h->loop = loop;
+ h->signal = signal;
+ h->func = handler;
+ h->data = data;
+ h->destroy = notify;
+ loop->signal_handlers[h->signal] =
+ g_slist_prepend(loop->signal_handlers[h->signal], h);
+
+ if (!all_signals[signal].installed) {
+ struct sigaction action;
+ sigset_t sigset;
+
+ sigemptyset(&sigset);
+ action.sa_handler = sighandler;
+ action.sa_mask = sigset;
+ action.sa_flags = SA_NOCLDSTOP;
+
+ sigaction(signal, &action, &all_signals[signal].oldact);
+ }
+
+ all_signals[signal].installed++;
+}
+
+void obt_main_loop_signal_remove(ObtMainLoop *loop,
+ ObtMainLoopSignalHandler handler)
+{
+ guint i;
+ GSList *it, *next;
+
+ for (i = 0; i < NUM_SIGNALS; ++i) {
+ for (it = loop->signal_handlers[i]; it; it = next) {
+ ObtMainLoopSignalHandlerType *h = it->data;
+
+ next = g_slist_next(it);
+
+ if (h->func == handler) {
+ g_assert(all_signals[h->signal].installed > 0);
+
+ all_signals[h->signal].installed--;
+ if (!all_signals[h->signal].installed) {
+ sigaction(h->signal, &all_signals[h->signal].oldact, NULL);
+ }
+
+ loop->signal_handlers[i] =
+ g_slist_delete_link(loop->signal_handlers[i], it);
+ if (h->destroy) h->destroy(h->data);
+
+ g_free(h);
+ }
+ }
+ }
+
+}
+
+/*** FILE DESCRIPTOR WATCHERS ***/
+
+static void max_fd_func(gpointer key, gpointer value, gpointer data)
+{
+ ObtMainLoop *loop = data;
+
+ /* key is the fd */
+ loop->fd_max = MAX(loop->fd_max, *(gint*)key);
+}
+
+static void calc_max_fd(ObtMainLoop *loop)
+{
+ loop->fd_max = loop->fd_x;
+
+ g_hash_table_foreach(loop->fd_handlers, max_fd_func, loop);
+}
+
+void obt_main_loop_fd_add(ObtMainLoop *loop,
+ gint fd,
+ ObtMainLoopFdHandler handler,
+ gpointer data,
+ GDestroyNotify notify)
+{
+ ObtMainLoopFdHandlerType *h;
+
+ h = g_new(ObtMainLoopFdHandlerType, 1);
+ h->loop = loop;
+ h->fd = fd;
+ h->func = handler;
+ h->data = data;
+ h->destroy = notify;
+
+ g_hash_table_replace(loop->fd_handlers, &h->fd, h);
+ FD_SET(h->fd, &loop->fd_set);
+ calc_max_fd(loop);
+}
+
+static void fd_handler_destroy(gpointer data)
+{
+ ObtMainLoopFdHandlerType *h = data;
+
+ FD_CLR(h->fd, &h->loop->fd_set);
+
+ if (h->destroy)
+ h->destroy(h->data);
+}
+
+void obt_main_loop_fd_remove(ObtMainLoop *loop,
+ gint fd)
+{
+ g_hash_table_remove(loop->fd_handlers, &fd);
+}
+
+/*** TIMEOUTS ***/
+
+#define NEAREST_TIMEOUT(loop) \
+ (((ObtMainLoopTimer*)(loop)->timers->data)->timeout)
+
+static glong timecompare(GTimeVal *a, GTimeVal *b)
+{
+ glong r;
+ if ((r = a->tv_sec - b->tv_sec)) return r;
+ return a->tv_usec - b->tv_usec;
+}
+
+static void insert_timer(ObtMainLoop *loop, ObtMainLoopTimer *ins)
+{
+ GSList *it;
+ for (it = loop->timers; it; it = g_slist_next(it)) {
+ ObMainLoopTimer *t = it->data;
+ if (timecompare(&ins->timeout, &t->timeout) <= 0) {
+ loop->timers = g_slist_insert_before(loop->timers, it, ins);
+ break;
+ }
+ }
+ if (it == NULL) /* didnt fit anywhere in the list */
+ loop->timers = g_slist_append(loop->timers, ins);
+}
+
+void obt_main_loop_timeout_add(ObtMainLoop *loop,
+ gulong microseconds,
+ GSourceFunc handler,
+ gpointer data,
+ GEqualFunc cmp,
+ GDestroyNotify notify)
+{
+ ObMainLoopTimer *t = g_new(ObMainLoopTimer, 1);
+
+ g_assert(microseconds > 0); /* if it's 0 it'll cause an infinite loop */
+
+ t->delay = microseconds;
+ t->func = handler;
+ t->data = data;
+ t->equal = cmp;
+ t->destroy = notify;
+ t->del_me = FALSE;
+ g_get_current_time(&loop->now);
+ t->last = t->timeout = loop->now;
+ g_time_val_add(&t->timeout, t->delay);
+
+ insert_timer(loop, t);
+}
+
+void obt_main_loop_timeout_remove(ObtMainLoop *loop,
+ GSourceFunc handler)
+{
+ GSList *it;
+
+ for (it = loop->timers; it; it = g_slist_next(it)) {
+ ObtMainLoopTimer *t = it->data;
+ if (t->func == handler)
+ t->del_me = TRUE;
+ }
+}
+
+void obt_main_loop_timeout_remove_data(ObtMainLoop *loop, GSourceFunc handler,
+ gpointer data, gboolean cancel_dest)
+{
+ GSList *it;
+
+ for (it = loop->timers; it; it = g_slist_next(it)) {
+ ObtMainLoopTimer *t = it->data;
+ if (t->func == handler && t->equal(t->data, data)) {
+ t->del_me = TRUE;
+ if (cancel_dest)
+ t->destroy = NULL;
+ }
+ }
+}
+
+/* find the time to wait for the nearest timeout */
+static gboolean nearest_timeout_wait(ObtMainLoop *loop, GTimeVal *tm)
+{
+ if (loop->timers == NULL)
+ return FALSE;
+
+ tm->tv_sec = NEAREST_TIMEOUT(loop).tv_sec - loop->now.tv_sec;
+ tm->tv_usec = NEAREST_TIMEOUT(loop).tv_usec - loop->now.tv_usec;
+
+ while (tm->tv_usec < 0) {
+ tm->tv_usec += G_USEC_PER_SEC;
+ tm->tv_sec--;
+ }
+ tm->tv_sec += tm->tv_usec / G_USEC_PER_SEC;
+ tm->tv_usec %= G_USEC_PER_SEC;
+ if (tm->tv_sec < 0)
+ tm->tv_sec = 0;
+
+ return TRUE;
+}
+
+static void timer_dispatch(ObtMainLoop *loop, GTimeVal **wait)
+{
+ GSList *it, *next;
+
+ gboolean fired = FALSE;
+
+ g_get_current_time(&loop->now);
+
+ for (it = loop->timers; it; it = next) {
+ ObtMainLoopTimer *curr;
+
+ next = g_slist_next(it);
+
+ curr = it->data;
+
+ /* since timer_stop doesn't actually free the timer, we have to do our
+ real freeing in here.
+ */
+ if (curr->del_me) {
+ /* delete the top */
+ loop->timers = g_slist_delete_link(loop->timers, it);
+ if (curr->destroy)
+ curr->destroy(curr->data);
+ g_free(curr);
+ continue;
+ }
+
+ /* the queue is sorted, so if this timer shouldn't fire, none are
+ ready */
+ if (timecompare(&NEAREST_TIMEOUT(loop), &loop->now) > 0)
+ break;
+
+ /* we set the last fired time to delay msec after the previous firing,
+ then re-insert. timers maintain their order and may trigger more
+ than once if they've waited more than one delay's worth of time.
+ */
+ loop->timers = g_slist_delete_link(loop->timers, it);
+ g_time_val_add(&curr->last, curr->delay);
+ if (curr->func(curr->data)) {
+ g_time_val_add(&curr->timeout, curr->delay);
+ insert_timer(loop, curr);
+ } else {
+ if (curr->destroy)
+ curr->destroy(curr->data);
+ g_free(curr);
+ }
+
+ /* the timer queue has been shuffled, start from the beginning
+ (which is the next one to fire) */
+ next = loop->timers;
+
+ fired = TRUE;
+ }
+
+ if (fired) {
+ /* if at least one timer fires, then don't wait on X events, as there
+ may already be some in the queue from the timer callbacks.
+ */
+ loop->ret_wait.tv_sec = loop->ret_wait.tv_usec = 0;
+ *wait = &loop->ret_wait;
+ } else if (nearest_timeout_wait(loop, &loop->ret_wait))
+ *wait = &loop->ret_wait;
+ else
+ *wait = NULL;
+}