+/*
+ * Glue between PulseAudio and LibUCW Mainloop
+ *
+ * (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#undef LOCAL_DEBUG
+
+#include <ucw/lib.h>
+#include <ucw/clists.h>
+#include <ucw/mainloop.h>
+
+#include <sys/poll.h>
+#include <sys/time.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "ursaryd.h"
+
+struct pmain_io {
+ cnode n;
+ struct main_file f;
+ clist io_events;
+};
+
+static clist pmain_io_list;
+
+struct pa_io_event {
+ cnode n;
+ cnode gc_n;
+ struct pmain_io *io;
+ pa_io_event_flags_t events;
+ pa_io_event_cb_t callback;
+ pa_io_event_destroy_cb_t destroy_callback;
+ void *userdata;
+};
+
+static clist pmain_io_gc_list;
+
+static pa_io_event *pmain_io_new(pa_mainloop_api *api, int fd, pa_io_event_flags_t events, pa_io_event_cb_t cb, void *userdata);
+static void pmain_io_enable(pa_io_event *e, pa_io_event_flags_t events);
+static void pmain_io_free(pa_io_event *e);
+static void pmain_io_set_destroy(pa_io_event *e, pa_io_event_destroy_cb_t cb);
+
+struct pa_time_event {
+ cnode n;
+ struct main_timer t;
+ pa_time_event_cb_t callback;
+ pa_time_event_destroy_cb_t destroy_callback;
+ void *userdata;
+ struct timeval tv;
+};
+
+static clist pmain_time_gc_list;
+
+static pa_time_event *pmain_time_new(pa_mainloop_api *api, const struct timeval *tv, pa_time_event_cb_t cb, void *userdata);
+static void pmain_time_restart(pa_time_event *e, const struct timeval *tv);
+static void pmain_time_free(pa_time_event *e);
+static void pmain_time_set_destroy(pa_time_event *e, pa_time_event_destroy_cb_t cb);
+
+struct pa_defer_event {
+ cnode n;
+ struct main_hook h;
+ pa_defer_event_cb_t callback;
+ pa_defer_event_destroy_cb_t destroy_callback;
+ void *userdata;
+};
+
+static clist pmain_defer_gc_list;
+
+static pa_defer_event *pmain_defer_new(pa_mainloop_api *api, pa_defer_event_cb_t cb, void *userdata);
+static void pmain_defer_enable(pa_defer_event *e, int b);
+static void pmain_defer_free(pa_defer_event *e);
+static void pmain_defer_set_destroy(pa_defer_event *e, pa_defer_event_destroy_cb_t cb);
+
+static void pmain_quit(pa_mainloop_api *a, int retval);
+
+static struct main_hook pmain_gc_hook;
+
+static void pmain_trigger_gc(void);
+
+struct pa_mainloop_api pmain_api = {
+ .io_new = pmain_io_new,
+ .io_enable = pmain_io_enable,
+ .io_free = pmain_io_free,
+ .io_set_destroy = pmain_io_set_destroy,
+
+ .time_new = pmain_time_new,
+ .time_restart = pmain_time_restart,
+ .time_free = pmain_time_free,
+ .time_set_destroy = pmain_time_set_destroy,
+
+ .defer_new = pmain_defer_new,
+ .defer_enable = pmain_defer_enable,
+ .defer_free = pmain_defer_free,
+ .defer_set_destroy = pmain_defer_set_destroy,
+
+ .quit = pmain_quit,
+};
+
+static struct pmain_io *pmain_get_io(int fd)
+{
+ CLIST_FOR_EACH(struct pmain_io *, io, pmain_io_list)
+ if (io->f.fd == fd)
+ {
+ DBG("Pulse: Recycling IO master");
+ return io;
+ }
+
+ struct pmain_io *io = xmalloc(sizeof(*io));
+ io->f.fd = fd;
+ io->f.data = io;
+ clist_add_tail(&pmain_io_list, &io->n);
+ clist_init(&io->io_events);
+ return io;
+}
+
+static pa_io_event *pmain_io_new(pa_mainloop_api *api UNUSED, int fd, pa_io_event_flags_t events, pa_io_event_cb_t cb, void *userdata)
+{
+ struct pa_io_event *e = xmalloc_zero(sizeof(*e));
+ DBG("Pulse: Creating new IO %p for fd %u", e, fd);
+
+ e->io = pmain_get_io(fd);
+ e->callback = cb;
+ e->userdata = userdata;
+ clist_add_head(&e->io->io_events, &e->n); // Do not call the new IO if created from another IO on the same fd
+ pmain_io_enable(e, events);
+ return e;
+}
+
+static int pmain_io_read(struct main_file *f)
+{
+ struct pmain_io *io = f->data;
+ DBG("Pulse: fd %d ready for read", io->f.fd);
+
+ CLIST_FOR_EACH(struct pa_io_event *, e, io->io_events)
+ if (e->events & PA_IO_EVENT_INPUT)
+ {
+ DBG("Pulse: Callback on IO %p", e);
+ e->callback(&pmain_api, e, io->f.fd, PA_IO_EVENT_INPUT, e->userdata);
+ }
+
+ DBG("Pulse: fd %d read done", io->f.fd);
+ return HOOK_IDLE;
+}
+
+static int pmain_io_write(struct main_file *f)
+{
+ struct pmain_io *io = f->data;
+ DBG("Pulse: fd %d ready for write", io->f.fd);
+
+ CLIST_FOR_EACH(struct pa_io_event *, e, io->io_events)
+ if (e->events & PA_IO_EVENT_OUTPUT)
+ {
+ DBG("Pulse: Callback on IO %p", e);
+ e->callback(&pmain_api, e, io->f.fd, PA_IO_EVENT_OUTPUT, e->userdata);
+ }
+
+ DBG("Pulse: fd %d write done", io->f.fd);
+ return HOOK_IDLE;
+}
+
+static void pmain_io_enable(pa_io_event *e, pa_io_event_flags_t events)
+{
+ struct pmain_io *io = e->io;
+ DBG("Pulse: Changing IO event mask for IO %p on fd %d to %02x", e, io->f.fd, events);
+ e->events = events;
+
+ pa_io_event_flags_t mask = 0;
+ CLIST_FOR_EACH(struct pa_io_event *, f, io->io_events)
+ mask |= f->events;
+ DBG("Pulse: Recalculated IO mask for fd %d to %02x", io->f.fd, mask);
+
+ if (mask)
+ {
+ io->f.read_handler = (mask & PA_IO_EVENT_INPUT) ? pmain_io_read : NULL;
+ io->f.write_handler = (mask & PA_IO_EVENT_OUTPUT) ? pmain_io_write : NULL;
+ if (file_is_active(&io->f))
+ file_chg(&io->f);
+ else
+ file_add(&io->f);
+ }
+ else
+ file_del(&io->f);
+}
+
+static void pmain_io_free(pa_io_event *e)
+{
+ DBG("Pulse: Deleting IO %p for fd %d", e, e->io->f.fd);
+ pmain_io_enable(e, 0);
+ clist_add_tail(&pmain_io_gc_list, &e->gc_n);
+ pmain_trigger_gc();
+}
+
+static void pmain_io_set_destroy(pa_io_event *e, pa_io_event_destroy_cb_t cb)
+{
+ e->destroy_callback = cb;
+}
+
+static void pmain_time_handler(struct main_timer *t)
+{
+ struct pa_time_event *e = t->data;
+ DBG("Pulse: Timer %p triggered", e);
+ timer_del(t);
+ e->callback(&pmain_api, e, &e->tv, e->userdata);
+ DBG("Pulse: Timer %p done", e);
+}
+
+static pa_time_event *pmain_time_new(pa_mainloop_api *api UNUSED, const struct timeval *tv, pa_time_event_cb_t cb, void *userdata)
+{
+ struct pa_time_event *e = xmalloc_zero(sizeof(*e));
+ DBG("Pulse: Creating timer %p", e);
+ e->callback = cb;
+ e->userdata = userdata;
+ e->t.handler = pmain_time_handler;
+ e->t.data = e;
+ pmain_time_restart(e, tv);
+ return e;
+}
+
+static timestamp_t timeval_to_timestamp(const struct timeval *tv)
+{
+ return 1000 * (timestamp_t) tv->tv_sec + tv->tv_usec / 1000;
+}
+
+static void pmain_time_restart(pa_time_event *e, const struct timeval *tv)
+{
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ timestamp_t ts_now = timeval_to_timestamp(&now);
+ timestamp_t ts_fire = timeval_to_timestamp(tv);
+ timestamp_t ts_delta = ts_fire - ts_now;
+ DBG("Pulse: Setting timer %p to %+d", e, (int) ts_delta);
+ timer_del(&e->t);
+ e->tv = *tv;
+ timer_add_rel(&e->t, ts_delta);
+}
+
+static void pmain_time_free(pa_time_event *e)
+{
+ DBG("Pulse: Timer %p deleted", e);
+ timer_del(&e->t);
+ clist_add_tail(&pmain_time_gc_list, &e->n);
+ pmain_trigger_gc();
+}
+
+static void pmain_time_set_destroy(pa_time_event *e, pa_time_event_destroy_cb_t cb)
+{
+ e->destroy_callback = cb;
+}
+
+static int pmain_defer_handler(struct main_hook *h)
+{
+ struct pa_defer_event *e = h->data;
+ DBG("Pulse: Deferred event %p triggered", e);
+ e->callback(&pmain_api, e, e->userdata);
+ DBG("Pulse: Deferred event done");
+ return hook_is_active(&e->h) ? HOOK_RETRY : HOOK_IDLE;
+}
+
+static pa_defer_event *pmain_defer_new(pa_mainloop_api *api UNUSED, pa_defer_event_cb_t cb, void *userdata)
+{
+ struct pa_defer_event *e = xmalloc_zero(sizeof(*e));
+ DBG("Pulse: Creating defer %p", e);
+ e->callback = cb;
+ e->userdata = userdata;
+ e->h.handler = pmain_defer_handler;
+ e->h.data = e;
+ pmain_defer_enable(e, 1);
+ return e;
+}
+
+static void pmain_defer_enable(pa_defer_event *e, int b)
+{
+ DBG("Pulse: %sabling defer %p", (b ? "En" : "Dis"), e);
+ if (b)
+ hook_add(&e->h);
+ else
+ hook_del(&e->h);
+}
+
+static void pmain_defer_free(pa_defer_event *e)
+{
+ DBG("Pulse: Deferred event %p deleted", e);
+ hook_del(&e->h);
+ clist_add_tail(&pmain_defer_gc_list, &e->n);
+ pmain_trigger_gc();
+}
+
+static void pmain_defer_set_destroy(pa_defer_event *e, pa_defer_event_destroy_cb_t cb)
+{
+ e->destroy_callback = cb;
+}
+
+static void pmain_quit(pa_mainloop_api *a UNUSED, int retval UNUSED)
+{
+ DBG("Pulse: Main loop quit not implemented");
+}
+
+static int pmain_gc_handler(struct main_hook *h)
+{
+ DBG("Pulse: Garbage collector");
+ hook_del(h);
+
+ cnode *n;
+ while (n = clist_remove_head(&pmain_io_gc_list))
+ {
+ struct pa_io_event *ei = SKIP_BACK(struct pa_io_event, gc_n, n);
+ struct pmain_io *io = ei->io;
+ DBG("Pulse: GC of IO event %p on fd %d", ei, io->f.fd);
+ if (ei->destroy_callback)
+ ei->destroy_callback(&pmain_api, ei, ei->userdata);
+ clist_remove(&ei->n);
+ if (clist_empty(&io->io_events))
+ {
+ ASSERT(!file_is_active(&io->f));
+ DBG("Pulse: GC of IO master for fd %d", io->f.fd);
+ clist_remove(&io->n);
+ xfree(io);
+ }
+ xfree(ei);
+ }
+
+ struct pa_time_event *et;
+ while (et = (struct pa_time_event *) clist_remove_head(&pmain_time_gc_list))
+ {
+ DBG("Pulse: GC for timer %p", et);
+ if (et->destroy_callback)
+ et->destroy_callback(&pmain_api, et, et->userdata);
+ xfree(et);
+ }
+
+ struct pa_defer_event *ed;
+ while (ed = (struct pa_defer_event *) clist_remove_head(&pmain_defer_gc_list))
+ {
+ DBG("Pulse: GC for defer %p", ed);
+ if (ed->destroy_callback)
+ ed->destroy_callback(&pmain_api, ed, ed->userdata);
+ xfree(ed);
+ }
+
+ DBG("Pulse: Garbage collector done");
+ return HOOK_RETRY;
+}
+
+static void pmain_trigger_gc(void)
+{
+ hook_add(&pmain_gc_hook);
+}
+
+void pmain_init(void)
+{
+ clist_init(&pmain_io_list);
+ clist_init(&pmain_io_gc_list);
+ clist_init(&pmain_time_gc_list);
+ clist_init(&pmain_defer_gc_list);
+ pmain_gc_hook.handler = pmain_gc_handler;
+}