/*** This file is part of PulseAudio. Copyright 2004-2006 Lennart Poettering PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio 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. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. ***/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include "glib-mainloop.h" struct pa_io_event { pa_glib_mainloop *mainloop; int dead; GPollFD poll_fd; int poll_fd_added; pa_io_event_cb_t callback; void *userdata; pa_io_event_destroy_cb_t destroy_callback; PA_LLIST_FIELDS(pa_io_event); }; struct pa_time_event { pa_glib_mainloop *mainloop; int dead; int enabled; struct timeval timeval; pa_time_event_cb_t callback; void *userdata; pa_time_event_destroy_cb_t destroy_callback; PA_LLIST_FIELDS(pa_time_event); }; struct pa_defer_event { pa_glib_mainloop *mainloop; int dead; int enabled; pa_defer_event_cb_t callback; void *userdata; pa_defer_event_destroy_cb_t destroy_callback; PA_LLIST_FIELDS(pa_defer_event); }; struct pa_glib_mainloop { GSource source; pa_mainloop_api api; GMainContext *context; PA_LLIST_HEAD(pa_io_event, io_events); PA_LLIST_HEAD(pa_time_event, time_events); PA_LLIST_HEAD(pa_defer_event, defer_events); int n_enabled_defer_events, n_enabled_time_events; int io_events_please_scan, time_events_please_scan, defer_events_please_scan; pa_time_event *cached_next_time_event; }; static void cleanup_io_events(pa_glib_mainloop *g, int force) { pa_io_event *e; e = g->io_events; while (e) { pa_io_event *n = e->next; if (!force && g->io_events_please_scan <= 0) break; if (force || e->dead) { PA_LLIST_REMOVE(pa_io_event, g->io_events, e); if (e->dead) { g_assert(g->io_events_please_scan > 0); g->io_events_please_scan--; } if (e->poll_fd_added) g_source_remove_poll(&g->source, &e->poll_fd); if (e->destroy_callback) e->destroy_callback(&g->api, e, e->userdata); pa_xfree(e); } e = n; } g_assert(g->io_events_please_scan == 0); } static void cleanup_time_events(pa_glib_mainloop *g, int force) { pa_time_event *e; e = g->time_events; while (e) { pa_time_event *n = e->next; if (!force && g->time_events_please_scan <= 0) break; if (force || e->dead) { PA_LLIST_REMOVE(pa_time_event, g->time_events, e); if (e->dead) { g_assert(g->time_events_please_scan > 0); g->time_events_please_scan--; } if (!e->dead && e->enabled) { g_assert(g->n_enabled_time_events > 0); g->n_enabled_time_events--; } if (e->destroy_callback) e->destroy_callback(&g->api, e, e->userdata); pa_xfree(e); } e = n; } g_assert(g->time_events_please_scan == 0); } static void cleanup_defer_events(pa_glib_mainloop *g, int force) { pa_defer_event *e; e = g->defer_events; while (e) { pa_defer_event *n = e->next; if (!force && g->defer_events_please_scan <= 0) break; if (force || e->dead) { PA_LLIST_REMOVE(pa_defer_event, g->defer_events, e); if (e->dead) { g_assert(g->defer_events_please_scan > 0); g->defer_events_please_scan--; } if (!e->dead && e->enabled) { g_assert(g->n_enabled_defer_events > 0); g->n_enabled_defer_events--; } if (e->destroy_callback) e->destroy_callback(&g->api, e, e->userdata); pa_xfree(e); } e = n; } g_assert(g->defer_events_please_scan == 0); } static gushort map_flags_to_glib(pa_io_event_flags_t flags) { return (gushort) ((flags & PA_IO_EVENT_INPUT ? G_IO_IN : 0) | (flags & PA_IO_EVENT_OUTPUT ? G_IO_OUT : 0) | (flags & PA_IO_EVENT_ERROR ? G_IO_ERR : 0) | (flags & PA_IO_EVENT_HANGUP ? G_IO_HUP : 0)); } static pa_io_event_flags_t map_flags_from_glib(gushort flags) { return (flags & G_IO_IN ? PA_IO_EVENT_INPUT : 0) | (flags & G_IO_OUT ? PA_IO_EVENT_OUTPUT : 0) | (flags & G_IO_ERR ? PA_IO_EVENT_ERROR : 0) | (flags & G_IO_HUP ? PA_IO_EVENT_HANGUP : 0); } static pa_io_event* glib_io_new( pa_mainloop_api*m, int fd, pa_io_event_flags_t f, pa_io_event_cb_t cb, void *userdata) { pa_io_event *e; pa_glib_mainloop *g; g_assert(m); g_assert(m->userdata); g_assert(fd >= 0); g_assert(cb); g = m->userdata; e = pa_xnew(pa_io_event, 1); e->mainloop = g; e->dead = 0; e->poll_fd.fd = fd; e->poll_fd.events = map_flags_to_glib(f); e->poll_fd.revents = 0; e->callback = cb; e->userdata = userdata; e->destroy_callback = NULL; PA_LLIST_PREPEND(pa_io_event, g->io_events, e); g_source_add_poll(&g->source, &e->poll_fd); e->poll_fd_added = 1; return e; } static void glib_io_enable(pa_io_event*e, pa_io_event_flags_t f) { g_assert(e); g_assert(!e->dead); e->poll_fd.events = map_flags_to_glib(f); } static void glib_io_free(pa_io_event*e) { g_assert(e); g_assert(!e->dead); e->dead = 1; e->mainloop->io_events_please_scan++; if (e->poll_fd_added) { g_source_remove_poll(&e->mainloop->source, &e->poll_fd); e->poll_fd_added = 0; } } static void glib_io_set_destroy(pa_io_event*e, pa_io_event_destroy_cb_t cb) { g_assert(e); g_assert(!e->dead); e->destroy_callback = cb; } /* Time sources */ static pa_time_event* glib_time_new( pa_mainloop_api*m, const struct timeval *tv, pa_time_event_cb_t cb, void *userdata) { pa_glib_mainloop *g; pa_time_event *e; g_assert(m); g_assert(m->userdata); g_assert(cb); g = m->userdata; e = pa_xnew(pa_time_event, 1); e->mainloop = g; e->dead = 0; if ((e->enabled = !!tv)) { e->timeval = *tv; g->n_enabled_time_events++; if (g->cached_next_time_event) { g_assert(g->cached_next_time_event->enabled); if (pa_timeval_cmp(tv, &g->cached_next_time_event->timeval) < 0) g->cached_next_time_event = e; } } e->callback = cb; e->userdata = userdata; e->destroy_callback = NULL; PA_LLIST_PREPEND(pa_time_event, g->time_events, e); return e; } static void glib_time_restart(pa_time_event*e, const struct timeval *tv) { g_assert(e); g_assert(!e->dead); if (e->enabled && !tv) { g_assert(e->mainloop->n_enabled_time_events > 0); e->mainloop->n_enabled_time_events--; } else if (!e->enabled && tv) e->mainloop->n_enabled_time_events++; if ((e->enabled = !!tv)) e->timeval = *tv; if (e->mainloop->cached_next_time_event && e->enabled) { g_assert(e->mainloop->cached_next_time_event->enabled); if (pa_timeval_cmp(tv, &e->mainloop->cached_next_time_event->timeval) < 0) e->mainloop->cached_next_time_event = e; } else if (e->mainloop->cached_next_time_event == e) e->mainloop->cached_next_time_event = NULL; } static void glib_time_free(pa_time_event *e) { g_assert(e); g_assert(!e->dead); e->dead = 1; e->mainloop->time_events_please_scan++; if (e->enabled) e->mainloop->n_enabled_time_events--; if (e->mainloop->cached_next_time_event == e) e->mainloop->cached_next_time_event = NULL; } static void glib_time_set_destroy(pa_time_event *e, pa_time_event_destroy_cb_t cb) { g_assert(e); g_assert(!e->dead); e->destroy_callback = cb; } /* Deferred sources */ static pa_defer_event* glib_defer_new( pa_mainloop_api*m, pa_defer_event_cb_t cb, void *userdata) { pa_defer_event *e; pa_glib_mainloop *g; g_assert(m); g_assert(m->userdata); g_assert(cb); g = m->userdata; e = pa_xnew(pa_defer_event, 1); e->mainloop = g; e->dead = 0; e->enabled = 1; g->n_enabled_defer_events++; e->callback = cb; e->userdata = userdata; e->destroy_callback = NULL; PA_LLIST_PREPEND(pa_defer_event, g->defer_events, e); return e; } static void glib_defer_enable(pa_defer_event *e, int b) { g_assert(e); g_assert(!e->dead); if (e->enabled && !b) { g_assert(e->mainloop->n_enabled_defer_events > 0); e->mainloop->n_enabled_defer_events--; } else if (!e->enabled && b) e->mainloop->n_enabled_defer_events++; e->enabled = b; } static void glib_defer_free(pa_defer_event *e) { g_assert(e); g_assert(!e->dead); e->dead = 1; e->mainloop->defer_events_please_scan++; if (e->enabled) { g_assert(e->mainloop->n_enabled_defer_events > 0); e->mainloop->n_enabled_defer_events--; } } static void glib_defer_set_destroy(pa_defer_event *e, pa_defer_event_destroy_cb_t cb) { g_assert(e); g_assert(!e->dead); e->destroy_callback = cb; } /* quit() */ static void glib_quit(pa_mainloop_api*a, int retval) { g_warning("quit() ignored"); /* NOOP */ } static pa_time_event* find_next_time_event(pa_glib_mainloop *g) { pa_time_event *t, *n = NULL; g_assert(g); if (g->cached_next_time_event) return g->cached_next_time_event; for (t = g->time_events; t; t = t->next) { if (t->dead || !t->enabled) continue; if (!n || pa_timeval_cmp(&t->timeval, &n->timeval) < 0) { n = t; /* Shortcut for tv = { 0, 0 } */ if (n->timeval.tv_sec <= 0) break; } } g->cached_next_time_event = n; return n; } static void scan_dead(pa_glib_mainloop *g) { g_assert(g); if (g->io_events_please_scan) cleanup_io_events(g, 0); if (g->time_events_please_scan) cleanup_time_events(g, 0); if (g->defer_events_please_scan) cleanup_defer_events(g, 0); } static gboolean prepare_func(GSource *source, gint *timeout) { pa_glib_mainloop *g = (pa_glib_mainloop*) source; g_assert(g); g_assert(timeout); scan_dead(g); if (g->n_enabled_defer_events) { *timeout = 0; return TRUE; } else if (g->n_enabled_time_events) { pa_time_event *t; GTimeVal now; struct timeval tvnow; pa_usec_t usec; t = find_next_time_event(g); g_assert(t); g_source_get_current_time(source, &now); tvnow.tv_sec = now.tv_sec; tvnow.tv_usec = now.tv_usec; if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0) { *timeout = 0; return TRUE; } usec = pa_timeval_diff(&t->timeval, &tvnow); *timeout = (gint) (usec / 1000); } else *timeout = -1; return FALSE; } static gboolean check_func(GSource *source) { pa_glib_mainloop *g = (pa_glib_mainloop*) source; pa_io_event *e; g_assert(g); if (g->n_enabled_defer_events) return TRUE; else if (g->n_enabled_time_events) { pa_time_event *t; GTimeVal now; struct timeval tvnow; t = find_next_time_event(g); g_assert(t); g_source_get_current_time(source, &now); tvnow.tv_sec = now.tv_sec; tvnow.tv_usec = now.tv_usec; if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0) return TRUE; } for (e = g->io_events; e; e = e->next) if (!e->dead && e->poll_fd.revents != 0) return TRUE; return FALSE; } static gboolean dispatch_func(GSource *source, GSourceFunc callback, gpointer userdata) { pa_glib_mainloop *g = (pa_glib_mainloop*) source; pa_io_event *e; g_assert(g); if (g->n_enabled_defer_events) { pa_defer_event *d; for (d = g->defer_events; d; d = d->next) { if (d->dead || !d->enabled) continue; break; } g_assert(d); d->callback(&g->api, d, d->userdata); return TRUE; } if (g->n_enabled_time_events) { GTimeVal now; struct timeval tvnow; pa_time_event *t; t = find_next_time_event(g); g_assert(t); g_source_get_current_time(source, &now); tvnow.tv_sec = now.tv_sec; tvnow.tv_usec = now.tv_usec; if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0) { /* Disable time event */ glib_time_restart(t, NULL); t->callback(&g->api, t, &t->timeval, t->userdata); return TRUE; } } for (e = g->io_events; e; e = e->next) if (!e->dead && e->poll_fd.revents != 0) { e->callback(&g->api, e, e->poll_fd.fd, map_flags_from_glib(e->poll_fd.revents), e->userdata); e->poll_fd.revents = 0; return TRUE; } return FALSE; } static const pa_mainloop_api vtable = { .userdata = NULL, .io_new = glib_io_new, .io_enable = glib_io_enable, .io_free = glib_io_free, .io_set_destroy= glib_io_set_destroy, .time_new = glib_time_new, .time_restart = glib_time_restart, .time_free = glib_time_free, .time_set_destroy = glib_time_set_destroy, .defer_new = glib_defer_new, .defer_enable = glib_defer_enable, .defer_free = glib_defer_free, .defer_set_destroy = glib_defer_set_destroy, .quit = glib_quit, }; pa_glib_mainloop *pa_glib_mainloop_new(GMainContext *c) { pa_glib_mainloop *g; static GSourceFuncs source_funcs = { prepare_func, check_func, dispatch_func, NULL, NULL, NULL }; g = (pa_glib_mainloop*) g_source_new(&source_funcs, sizeof(pa_glib_mainloop)); g_main_context_ref(g->context = c ? c : g_main_context_default()); g->api = vtable; g->api.userdata = g; PA_LLIST_HEAD_INIT(pa_io_event, g->io_events); PA_LLIST_HEAD_INIT(pa_time_event, g->time_events); PA_LLIST_HEAD_INIT(pa_defer_event, g->defer_events); g->n_enabled_defer_events = g->n_enabled_time_events = 0; g->io_events_please_scan = g->time_events_please_scan = g->defer_events_please_scan = 0; g->cached_next_time_event = NULL; g_source_attach(&g->source, g->context); g_source_set_can_recurse(&g->source, FALSE); return g; } void pa_glib_mainloop_free(pa_glib_mainloop* g) { g_assert(g); cleanup_io_events(g, 1); cleanup_defer_events(g, 1); cleanup_time_events(g, 1); g_main_context_unref(g->context); g_source_destroy(&g->source); g_source_unref(&g->source); } pa_mainloop_api* pa_glib_mainloop_get_api(pa_glib_mainloop *g) { g_assert(g); return &g->api; }