diff options
author | Marcel Holtmann <marcel@holtmann.org> | 2007-01-20 06:03:26 +0000 |
---|---|---|
committer | Marcel Holtmann <marcel@holtmann.org> | 2007-01-20 06:03:26 +0000 |
commit | 8a9d1780d1e3548bae13eaaae56afdcf99ed297d (patch) | |
tree | 9a212a661f2cca74c7518e8087523854dc54d023 /eglib/gmain.c | |
parent | 1c62e7ed6ef51305a5a625ee070de8d2428c4e9a (diff) |
Add main loop and single list functions
Diffstat (limited to 'eglib/gmain.c')
-rw-r--r-- | eglib/gmain.c | 815 |
1 files changed, 815 insertions, 0 deletions
diff --git a/eglib/gmain.c b/eglib/gmain.c new file mode 100644 index 00000000..a3bf3760 --- /dev/null +++ b/eglib/gmain.c @@ -0,0 +1,815 @@ +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <malloc.h> +#include <string.h> +#include <limits.h> +#include <sys/time.h> +#include <time.h> + +#include <gmain.h> + +struct timeout { + guint id; + guint interval; + struct timeval expiration; + gpointer data; + GSourceFunc function; +}; + +struct _GIOChannel { + int fd; + gboolean closed; + gboolean close_on_unref; +}; + +struct _GMainContext { + guint next_id; + glong next_timeout; + + GSList *timeouts; + GSList *proc_timeouts; + gboolean timeout_lock; + + GSList *watches; + GSList *proc_watches; + gboolean watch_lock; +}; + +struct _GMainLoop { + gboolean is_running; + GMainContext *context; +}; + +GIOError g_io_channel_read(GIOChannel *channel, gchar *buf, gsize count, gsize *bytes_read) +{ + int fd = channel->fd; + gssize result; + + if (channel->closed) + return G_IO_STATUS_ERROR; + + /* At least according to the Debian manpage for read */ + if (count > SSIZE_MAX) + count = SSIZE_MAX; + +retry: + result = read (fd, buf, count); + + if (result < 0) { + *bytes_read = 0; + + switch (errno) { +#ifdef EINTR + case EINTR: + goto retry; +#endif +#ifdef EAGAIN + case EAGAIN: + return G_IO_STATUS_AGAIN; +#endif + default: + return G_IO_STATUS_ERROR; + } + } + + *bytes_read = result; + + return (result > 0) ? G_IO_STATUS_NORMAL : G_IO_STATUS_EOF; +} + +void g_io_channel_close(GIOChannel *channel) +{ + if (!channel || channel->closed) + return; + + close(channel->fd); + + channel->closed = TRUE; +} + +void g_io_channel_unref(GIOChannel *channel) +{ + if (!channel) + return; + + if (channel->close_on_unref && channel->fd >= 0) + g_io_channel_close(channel); + + free(channel); +} + +GIOChannel *g_io_channel_unix_new(int fd) +{ + GIOChannel *channel; + + channel = malloc(sizeof(GIOChannel)); + if (!channel) + return NULL; + + memset(channel, 0, sizeof(GIOChannel)); + + channel->fd = fd; + + return channel; +} + +void g_io_channel_set_close_on_unref(GIOChannel *channel, gboolean do_close) +{ + channel->close_on_unref = do_close; +} + +gint g_io_channel_unix_get_fd(GIOChannel *channel) +{ + if (channel->closed) + return -1; + + return channel->fd; +} + +struct watch { + guint id; + GIOChannel *channel; + gint priority; + GIOCondition condition; + short *revents; + GIOFunc func; + gpointer user_data; + GDestroyNotify destroy; +}; + +static GMainContext *default_context = NULL; + +static void watch_free(struct watch *watch) +{ + if (watch->destroy) + watch->destroy(watch->user_data); + free(watch); +} + +static GMainContext *g_main_context_default() +{ + if (default_context) + return default_context; + + default_context = malloc(sizeof(GMainContext)); + if (!default_context) + return NULL; + + memset(default_context, 0, sizeof(GMainContext)); + + default_context->next_timeout = -1; + default_context->next_id = 1; + + return default_context; +} + +void g_io_remove_watch(guint id) +{ + GMainContext *context = g_main_context_default(); + GSList *l; + struct watch *w; + + if (!context) + return; + + for (l = context->watches; l != NULL; l = l->next) { + w = l->data; + + if (w->id != id) + continue; + + context->watches = g_slist_remove(context->watches, w); + watch_free(w); + + return; + } + + for (l = context->proc_watches; l != NULL; l = l->next) { + w = l->data; + + if (w->id != id) + continue; + + context->proc_watches = g_slist_remove(context->proc_watches, w); + watch_free(w); + + return; + } +} + +int watch_prio_cmp(struct watch *w1, struct watch *w2) +{ + return w1->priority - w2->priority; +} + +#define watch_list_add(l, w) g_slist_insert_sorted((l), (w), (GCompareFunc) watch_prio_cmp) + +guint g_io_add_watch_full(GIOChannel *channel, gint priority, + GIOCondition condition, GIOFunc func, + gpointer user_data, GDestroyNotify notify) +{ + struct watch *watch; + GMainContext *context = g_main_context_default(); + + if (!context) + return 0; + + watch = malloc(sizeof(struct watch)); + if (!watch) + return 0; + + watch->id = context->next_id++; + watch->channel = channel; + watch->priority = priority; + watch->condition = condition; + watch->func = func; + watch->user_data = user_data; + watch->destroy = notify; + + if (context->watch_lock) + context->proc_watches = watch_list_add(context->proc_watches, watch); + else + context->watches = watch_list_add(context->watches, watch); + + return watch->id; +} + +guint g_io_add_watch(GIOChannel *channel, GIOCondition condition, + GIOFunc func, gpointer user_data) +{ + return g_io_add_watch_full(channel, 0, condition, + func, user_data, NULL); +} + +GMainLoop *g_main_loop_new(GMainContext *context, gboolean is_running) +{ + GMainLoop *ml; + + if (!context) + context = g_main_context_default(); + + if (!context) + return NULL; + + ml = malloc(sizeof(GMainLoop)); + if (!ml) + return NULL; + + memset(ml, 0, sizeof(GMainLoop)); + + ml->context = context; + ml->is_running = is_running; + + return ml; +} + +static void timeout_handlers_prepare(GMainContext *context) +{ + GSList *l; + struct timeval tv; + glong msec, timeout = LONG_MAX; + + gettimeofday(&tv, NULL); + + for (l = context->timeouts; l != NULL; l = l->next) { + struct timeout *t = l->data; + + /* calculate the remainning time */ + msec = (t->expiration.tv_sec - tv.tv_sec) * 1000 + + (t->expiration.tv_usec - tv.tv_usec) / 1000; + if (msec < 0) + msec = 0; + + timeout = MIN_TIMEOUT(timeout, msec); + } + + /* set to min value found or NO timeout */ + context->next_timeout = (timeout != LONG_MAX ? timeout : -1); +} + +static int ptr_cmp(const void *t1, const void *t2) +{ + return t1 - t2; +} + +static void timeout_handlers_check(GMainContext *context) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + context->timeout_lock = TRUE; + + while (context->timeouts) { + struct timeout *t = context->timeouts->data; + glong secs, msecs; + gboolean ret; + + if (timercmp(&tv, &t->expiration, <)) { + context->timeouts = g_slist_remove(context->timeouts, t); + context->proc_timeouts = g_slist_append(context->proc_timeouts, t); + continue; + } + + ret = t->function(t->data); + + /* Check if the handler was removed/freed by the callback + * function */ + if (!g_slist_find_custom(context->timeouts, t, ptr_cmp)) + continue; + + context->timeouts = g_slist_remove(context->timeouts, t); + + if (!ret) { + free(t); + continue; + } + + /* update the next expiration time */ + secs = t->interval / 1000; + msecs = t->interval - secs * 1000; + + t->expiration.tv_sec = tv.tv_sec + secs; + t->expiration.tv_usec = tv.tv_usec + msecs * 1000; + if (t->expiration.tv_usec >= 1000000) { + t->expiration.tv_usec -= 1000000; + t->expiration.tv_sec++; + } + + context->proc_timeouts = g_slist_append(context->proc_timeouts, t); + } + + context->timeouts = context->proc_timeouts; + context->proc_timeouts = NULL; + context->timeout_lock = FALSE; +} + +void g_main_loop_run(GMainLoop *loop) +{ + int open_max = sysconf(_SC_OPEN_MAX); + struct pollfd *ufds; + GMainContext *context = loop->context; + + ufds = malloc(open_max * sizeof(struct pollfd)); + if (!ufds) + return; + + loop->is_running = TRUE; + + while (loop->is_running) { + int nfds; + GSList *l; + struct watch *w; + + for (nfds = 0, l = context->watches; l != NULL; l = l->next, nfds++) { + w = l->data; + ufds[nfds].fd = w->channel->fd; + ufds[nfds].events = w->condition; + ufds[nfds].revents = 0; + w->revents = &ufds[nfds].revents; + } + + /* calculate the next timeout */ + timeout_handlers_prepare(context); + + if (poll(ufds, nfds, context->next_timeout) < 0) + continue; + + context->watch_lock = TRUE; + + while (context->watches) { + gboolean ret; + + w = context->watches->data; + + if (!*w->revents) { + context->watches = g_slist_remove(context->watches, w); + context->proc_watches = watch_list_add(context->proc_watches, w); + continue; + } + + ret = w->func(w->channel, *w->revents, w->user_data); + + /* Check if the watch was removed/freed by the callback + * function */ + if (!g_slist_find_custom(context->watches, w, ptr_cmp)) + continue; + + context->watches = g_slist_remove(context->watches, w); + + if (!ret) { + watch_free(w); + continue; + } + + context->proc_watches = watch_list_add(context->proc_watches, w); + } + + context->watches = context->proc_watches; + context->proc_watches = NULL; + context->watch_lock = FALSE; + + /* check expired timers */ + timeout_handlers_check(loop->context); + } + + free(ufds); +} + +void g_main_loop_quit(GMainLoop *loop) +{ + loop->is_running = FALSE; +} + +void g_main_loop_unref(GMainLoop *loop) +{ + if (!loop->context) + return; + + g_slist_foreach(loop->context->watches, (GFunc)watch_free, NULL); + g_slist_free(loop->context->watches); + + g_slist_foreach(loop->context->timeouts, (GFunc)free, NULL); + g_slist_free(loop->context->timeouts); + + free(loop->context); + loop->context = NULL; +} + +guint g_timeout_add(guint interval, GSourceFunc function, gpointer data) +{ + GMainContext *context = g_main_context_default(); + struct timeval tv; + guint secs; + guint msecs; + struct timeout *t; + + if (!context || !function) + return 0; + + t = malloc(sizeof(*t)); + + if (!t) + return 0; + + memset(t, 0, sizeof(*t)); + t->interval = interval; + t->function = function; + t->data = data; + + gettimeofday(&tv, NULL); + + secs = interval /1000; + msecs = interval - secs * 1000; + + t->expiration.tv_sec = tv.tv_sec + secs; + t->expiration.tv_usec = tv.tv_usec + msecs * 1000; + + if (t->expiration.tv_usec >= 1000000) { + t->expiration.tv_usec -= 1000000; + t->expiration.tv_sec++; + } + + /* attach the timeout the default context */ + t->id = context->next_id++; + + if (context->timeout_lock) + context->proc_timeouts = g_slist_prepend(context->proc_timeouts, t); + else + context->timeouts = g_slist_prepend(context->timeouts, t); + + return t->id; +} + +gint g_timeout_remove(const guint id) +{ + GMainContext *context = g_main_context_default(); + GSList *l; + struct timeout *t; + + if (!context) + return -1; + + l = context->timeouts; + + while (l) { + t = l->data; + l = l->next; + + if (t->id != id) + continue; + + context->timeouts = g_slist_remove(context->timeouts, t); + free(t); + + return 0; + } + + l = context->proc_timeouts; + + while (l) { + t = l->data; + l = l->next; + + if (t->id != id) + continue; + + context->proc_timeouts = g_slist_remove(context->proc_timeouts, t); + free(t); + + return 0; + } + + return -1; +} + +/* UTF-8 Validation: approximate copy/paste from glib2. */ + +#define UNICODE_VALID(c) \ + ((c) < 0x110000 && \ + (((c) & 0xFFFFF800) != 0xD800) && \ + ((c) < 0xFDD0 || (c) > 0xFDEF) && \ + ((c) & 0xFFFE) != 0xFFFE) + +#define CONTINUATION_CHAR(c, val) \ + do { \ + if (((c) & 0xc0) != 0x80) /* 10xxxxxx */ \ + goto failed; \ + (val) <<= 6; \ + (val) |= (c) & 0x3f; \ + } while (0) + +#define INCREMENT_AND_CHECK_MAX(p, i, max_len) \ + do { \ + (i)++; \ + if ((p)[(i)] == '\0' || ((max_len) >= 0 && (i) >= (max_len))) \ + goto failed; \ + } while (0) + + +gboolean g_utf8_validate(const gchar *str, gssize max_len, const gchar **end) +{ + unsigned long val, min, i; + const unsigned char *p, *last; + + min = val = 0; + + for (p = (unsigned char *) str, i = 0; p[i]; i++) { + if (max_len >= 0 && i >= max_len) + break; + + if (p[i] < 128) + continue; + + last = &p[i]; + + if ((p[i] & 0xe0) == 0xc0) { /* 110xxxxx */ + if ((p[i] & 0x1e) == 0) + goto failed; + INCREMENT_AND_CHECK_MAX(p, i, max_len); + if ((p[i] & 0xc0) != 0x80) + goto failed; /* 10xxxxxx */ + } else { + if ((p[i] & 0xf0) == 0xe0) { + /* 1110xxxx */ + min = (1 << 11); + val = p[i] & 0x0f; + goto two_remaining; + } else if ((p[i] & 0xf8) == 0xf0) { + /* 11110xxx */ + min = (1 << 16); + val = p[i] & 0x07; + } else + goto failed; + + INCREMENT_AND_CHECK_MAX(p, i, max_len); + CONTINUATION_CHAR(p[i], val); +two_remaining: + INCREMENT_AND_CHECK_MAX(p, i, max_len); + CONTINUATION_CHAR(p[i], val); + + INCREMENT_AND_CHECK_MAX(p, i, max_len); + CONTINUATION_CHAR(p[i], val); + + if (val < min || !UNICODE_VALID(val)) + goto failed; + } + } + + if (end) + *end = (const gchar *) &p[i]; + + return TRUE; + +failed: + if (end) + *end = (const gchar *) last; + + return FALSE; +} + +/* GSList functions */ + +GSList *g_slist_append(GSList *list, void *data) +{ + GSList *entry, *tail; + + entry = malloc(sizeof(GSList)); + /* FIXME: this currently just silently fails */ + if (!entry) + return list; + + entry->data = data; + entry->next = NULL; + + if (!list) + return entry; + + /* Find the end of the list */ + for (tail = list; tail->next; tail = tail->next); + + tail->next = entry; + + return list; +} + +GSList *g_slist_prepend(GSList *list, void *data) +{ + GSList *entry; + + entry = malloc(sizeof(GSList)); + /* FIXME: this currently just silently fails */ + if (!entry) + return list; + + entry->data = data; + entry->next = list; + + return entry; +} + +GSList *g_slist_insert_sorted(GSList *list, void *data, GCompareFunc cmp_func) +{ + GSList *tmp, *prev, *entry; + int cmp; + + entry = malloc(sizeof(GSList)); + if (!entry) + return list; + + entry->data = data; + entry->next = NULL; + + if (!list) + return entry; + + prev = NULL; + tmp = list; + + cmp = cmp_func(data, tmp->data); + + while (tmp->next && cmp > 0) { + prev = tmp; + tmp = tmp->next; + + cmp = cmp_func(data, tmp->data); + } + + if (!tmp->next && cmp > 0) { + tmp->next = entry; + return list; + } + + if (prev) { + prev->next = entry; + entry->next = tmp; + return list; + } else { + entry->next = list; + return entry; + } +} + +GSList *g_slist_remove(GSList *list, void *data) +{ + GSList *l, *next, *prev = NULL, *match = NULL; + + if (!list) + return NULL; + + for (l = list; l != NULL; l = l->next) { + if (l->data == data) { + match = l; + break; + } + prev = l; + } + + if (!match) + return list; + + next = match->next; + + free(match); + + /* If the head was removed, return the next element */ + if (!prev) + return next; + + prev->next = next; + + return list; +} + +GSList *g_slist_find_custom(GSList *list, const void *data, + GCompareFunc cmp_func) +{ + GSList *l; + + for (l = list; l != NULL; l = l->next) { + if (!cmp_func(l->data, data)) + return l; + } + + return NULL; +} + +static GSList *g_slist_sort_merge(GSList *l1, GSList *l2, + GCompareFunc cmp_func) +{ + GSList list, *l; + int cmp; + + l = &list; + + while (l1 && l2) { + cmp = cmp_func(l1->data, l2->data); + + if (cmp <= 0) { + l = l->next = l1; + l1 = l1->next; + } else { + l = l->next = l2; + l2 = l2->next; + } + } + + l->next = l1 ? l1 : l2; + + return list.next; +} + +GSList *g_slist_sort(GSList *list, GCompareFunc cmp_func) +{ + GSList *l1, *l2; + + if (!list || !list->next) + return list; + + l1 = list; + l2 = list->next; + + while ((l2 = l2->next) != NULL) { + if ((l2 = l2->next) == NULL) + break; + l1 = l1->next; + } + + l2 = l1->next; + l1->next = NULL; + + return g_slist_sort_merge(g_slist_sort(list, cmp_func), + g_slist_sort(l2, cmp_func), cmp_func); +} + +int g_slist_length(GSList *list) +{ + int len; + + for (len = 0; list != NULL; list = list->next) + len++; + + return len; +} + +void g_slist_foreach(GSList *list, GFunc func, void *user_data) +{ + while (list) { + GSList *next = list->next; + func(list->data, user_data); + list = next; + } +} + +void g_slist_free(GSList *list) +{ + GSList *l, *next; + + for (l = list; l != NULL; l = next) { + next = l->next; + free(l); + } +} |