]> mj.ucw.cz Git - misc.git/blob - ursaryd/ut.c
Ursary: Partial interface to PulseAudio
[misc.git] / ursaryd / ut.c
1 #define LOCAL_DEBUG
2
3 #include <ucw/lib.h>
4 #include <ucw/bitops.h>
5 #include <ucw/clists.h>
6 #include <ucw/gary.h>
7 #include <ucw/mainloop.h>
8 #include <ucw/string.h>
9
10 #include <stdio.h>
11 #include <string.h>
12 #include <stdlib.h>
13 #include <sys/poll.h>
14 #include <sys/time.h>
15
16 #include <libusb.h>
17 #include <pulse/pulseaudio.h>
18
19 /*
20  *  Interface to Novation Nocturn
21  *
22  *  Protocol reverse-engineered by De Wet van Niekerk <dewert@gmail.com>,
23  *  see https://github.com/dewert/nocturn-linux-midi for inspiration.
24  */
25
26 static libusb_context *usb_ctx;
27 static libusb_device_handle *usb_dev;
28
29 static struct main_file **usb_fds;
30
31 static int usb_fd_ready(struct main_file *f UNUSED)
32 {
33   DBG("USB: Handling events (ready on fd %d)", f->fd);
34   struct timeval tv = { 0, 0 };
35   int comp = 0;
36   int err = libusb_handle_events_timeout_completed(usb_ctx, &tv, &comp);
37   if (err < 0)
38     msg(L_ERROR, "libusb_handle_events: error %d", err);
39   return HOOK_IDLE;
40 }
41
42 static void usb_added_fd(int fd, short events, void *user_data UNUSED)
43 {
44   if (fd >= (int) GARY_SIZE(usb_fds))
45     GARY_RESIZE(usb_fds, fd + 1);
46
47   struct main_file *f = usb_fds[fd];
48   if (!f)
49     {
50       f = xmalloc_zero(sizeof(*f));
51       usb_fds[fd] = f;
52     }
53   else if (file_is_active(f))
54     {
55       DBG("USB: Releasing fd %d", fd);
56       file_del(f);
57     }
58
59   DBG("USB: Adding fd %d with event mask %u", fd, events);
60   f->fd = fd;
61   f->read_handler = (events & POLLIN) ? usb_fd_ready : NULL;
62   f->write_handler = (events & POLLOUT) ? usb_fd_ready : NULL;
63   file_add(f);
64 }
65
66 static void usb_removed_fd(int fd, void *user_data UNUSED)
67 {
68   DBG("USB: Releasing fd %d", fd);
69   ASSERT(fd < (int) GARY_SIZE(usb_fds));
70   struct main_file *f = usb_fds[fd];
71   ASSERT(f);
72   ASSERT(file_is_active(f));
73   file_del(f);
74 }
75
76 static const char noct_init[4][9] = {
77   { 3, 0xb0, 0x00, 0x00 },
78   { 8, 0x28, 0x00, 0x2b, 0x4a, 0x2c, 0x00, 0x2e, 0x35 },
79   { 6, 0x2a, 0x02, 0x2c, 0x72, 0x2e, 0x30 },
80   { 2, 0x7f, 0x00 },
81 };
82
83 static void noct_read_done(struct libusb_transfer *xfer)
84 {
85   byte *pkt = xfer->buffer;
86   int len = xfer->actual_length;
87   DBG("USB: Read done: status %d, length %d", xfer->status, len);
88
89   if (xfer->status != LIBUSB_TRANSFER_COMPLETED)
90     {
91       msg(L_ERROR, "USB read failed with status %d, not submitting again", xfer->status);
92       return;
93     }
94
95 #ifdef LOCAL_DEBUG
96   char buf[256];
97   mem_to_hex(buf, pkt, len, ' ');
98   DBG("USB: Read <%s>", buf);
99 #endif
100
101   int i = 0;
102   while (i < len)
103     {
104       if (i + 3 > len)
105         {
106           msg(L_ERROR, "Unknown USB packet: length %d not divisible by 3", len);
107           break;
108         }
109       if (pkt[i] != 0xb0)
110         {
111           msg(L_ERROR, "Unknown USB packet: expected 0xb0 at position %d", i);
112           break;
113         }
114       int cmd = pkt[i+1];
115       int arg = pkt[i+2];
116       i += 3;
117       switch (cmd)
118         {
119         case 0x30:
120           // Unknown packet sent during init
121           continue;
122         case 0x40 ... 0x47:
123           if (arg < 0x80)
124             {
125               int r = cmd - 0x40;
126               int delta = (arg < 0x40 ? arg : arg - 0x80);
127               DBG("Noct: Rotary %d = %d", r, delta);
128               continue;
129             }
130           break;
131         case 0x48:
132           if (arg < 0x80)
133             {
134               DBG("Noct: Slider value = %d", arg);
135               continue;
136             }
137           break;
138         case 0x49:
139           // Unknown packet, maybe least significant bit of slider
140           continue;
141         case 0x4a:
142           if (arg < 0x80)
143             {
144               int delta = (arg < 0x40 ? arg : arg - 0x80);
145               DBG("Noct: Center = %d", delta);
146               continue;
147             }
148           break;
149         case 0x52:
150           if (arg == 0x00 || arg == 0x7f)
151             {
152               int state = !!arg;
153               DBG("Noct: Center touch = %d", state);
154               continue;
155             }
156           break;
157         case 0x53:
158           if (arg == 0x00 || arg == 0x7f)
159             {
160               int state = !!arg;
161               DBG("Noct: Slider touch = %d", state);
162               continue;
163             }
164           break;
165         case 0x60 ... 0x67:
166           if (arg == 0x00 || arg == 0x7f)
167             {
168               int r = cmd - 0x60;
169               int state = !!arg;
170               DBG("Noct: Rotary %d touch = %d", r, state);
171               continue;
172             }
173           break;
174         case 0x70 ... 0x7f:
175           if (arg == 0x00 || arg == 0x7f)
176             {
177               int b = cmd - 0x70;
178               int state = !!arg;
179               DBG("Noct: Button %d = %d", b, state);
180               continue;
181             }
182           break;
183         }
184       msg(L_ERROR, "Unknown USB packet: unrecognized cmd=%02x arg=%02x", cmd, arg);
185     }
186
187   int err;
188   if ((err = libusb_submit_transfer(xfer)) < 0)
189     die("Cannot submit transfer: error %d", err);
190 }
191
192 static void noct_read_init(void)
193 {
194   DBG("Noct: Read init");
195
196   struct libusb_transfer *xfer = libusb_alloc_transfer(0);
197   libusb_fill_interrupt_transfer(xfer, usb_dev, 0x81, xmalloc(8), 8, noct_read_done, NULL, 0);
198
199   int err;
200   if ((err = libusb_submit_transfer(xfer)) < 0)
201     die("Cannot submit transfer: error %d", err);
202 }
203
204 static byte noct_button_state[16];
205 static byte noct_ring_mode[8];          // 0=from-min, 1=from-max, 2=from-mid-right, 3=from-mid-both, 4=single-on, 5=single-off
206 static byte noct_ring_val[9];
207
208 static uns noct_dirty_button;
209 static uns noct_dirty_ring_mode;
210 static uns noct_dirty_ring_val;
211
212 static struct libusb_transfer *noct_write_xfer;
213 static uns noct_write_pending;
214 static void noct_sched_write(void);
215
216 static void noct_write_done(struct libusb_transfer *xfer)
217 {
218   int len = xfer->actual_length;
219   DBG("USB: Write done: status %d, length %d", xfer->status, len);
220
221   if (xfer->status != LIBUSB_TRANSFER_COMPLETED)
222     {
223       msg(L_ERROR, "USB write failed with status %d", xfer->status);
224       return;
225     }
226
227   noct_write_pending = 0;
228   noct_sched_write();
229 }
230
231 static void noct_do_write(uns cmd, uns arg)
232 {
233   DBG("USB: Submitting write %02x %02x", cmd, arg);
234   ASSERT(!noct_write_pending);
235   noct_write_pending = 1;
236
237   struct libusb_transfer *xfer = noct_write_xfer;
238   byte *pkt = xfer->buffer;
239   pkt[0] = cmd;
240   pkt[1] = arg;
241   xfer->length = 2;
242
243   int err;
244   if ((err = libusb_submit_transfer(xfer)) < 0)
245     die("Cannot submit transfer: error %d", err);
246 }
247
248 static void noct_sched_write(void)
249 {
250   if (noct_write_pending)
251     return;
252
253   if (noct_dirty_button)
254     {
255       int i = bit_ffs(noct_dirty_button);
256       noct_dirty_button ^= 1U << i;
257       noct_do_write(0x70 + i, noct_button_state[i]);
258     }
259   else if (noct_dirty_ring_mode)
260     {
261       int i = bit_ffs(noct_dirty_ring_mode);
262       noct_dirty_ring_mode ^= 1U << i;
263       noct_do_write(0x48 + i, noct_ring_mode[i] << 4);
264     }
265   else if (noct_dirty_ring_val)
266     {
267       int i = bit_ffs(noct_dirty_ring_val);
268       noct_dirty_ring_val ^= 1U << i;
269       if (i == 8)
270         noct_do_write(0x50 + i, noct_ring_val[i]);
271       else
272         noct_do_write(0x40 + i, noct_ring_val[i]);
273     }
274 }
275
276 static void noct_write_init(void)
277 {
278   DBG("Noct: Write init");
279
280   noct_write_xfer = libusb_alloc_transfer(0);
281   libusb_fill_interrupt_transfer(noct_write_xfer, usb_dev, 0x02, xmalloc(8), 0, noct_write_done, NULL, 1000);
282
283 #if 0 // FIXME
284   noct_button_state[2] = 1;
285   noct_ring_mode[0] = 4;
286   noct_ring_val[0] = 0x40;
287 #endif
288
289   noct_dirty_button = 0xffff;
290   noct_dirty_ring_mode = 0xff;
291   noct_dirty_ring_val = 0x1ff;
292   noct_sched_write();
293 }
294
295 static void usb_init(void)
296 {
297   int err;
298
299   if ((err = libusb_init(&usb_ctx)) < 0)
300     die("libusb_init failed: error %d", err);
301   libusb_set_debug(usb_ctx, 3);
302
303   libusb_device **dev_list;
304   libusb_device *found_dev = NULL;
305   ssize_t len = libusb_get_device_list(usb_ctx, &dev_list);
306   for (ssize_t i=0; i < len; i++)
307     {
308       libusb_device *dev = dev_list[i];
309       struct libusb_device_descriptor desc;
310       if (libusb_get_device_descriptor(dev, &desc) >= 0 &&
311           desc.idVendor == 0x1235 &&
312           desc.idProduct == 0x000a)
313         {
314           msg(L_DEBUG, "Found device: bus %d, addr %d", libusb_get_bus_number(dev), libusb_get_device_address(dev));
315           if (found_dev)
316             die("Multiple Nocturn devices found. Please fix me to handle it.");
317           found_dev = libusb_ref_device(dev);
318         }
319     }
320   libusb_free_device_list(dev_list, 1);
321
322   if (!found_dev)
323     die("No Nocturn device found");
324
325   msg(L_DEBUG, "Initializing device");
326
327   if ((err = libusb_open(found_dev, &usb_dev)) < 0)
328     die("libusb_open failed: error %d", err);
329
330   // There exist configurations 1 (high brightness) and 2 (power-save)
331   if ((err = libusb_set_configuration(usb_dev, 1)) < 0)
332     die("libusb_set_configuration: error %d", err);
333
334   if ((err = libusb_claim_interface(usb_dev, 0)) < 0)
335     die("libusb_claim_interface: error %d", err);
336
337   for (int i=0; i<4; i++)
338     {
339       int done;
340       if ((err = libusb_interrupt_transfer(usb_dev, 0x02, (byte *) noct_init[i] + 1, noct_init[i][0], &done, 5000)) < 0)
341         die("Cannot send init packets: error %d", err);
342       if (done != noct_init[i][0])
343         die("Partial send of init packet: %d < %d", done, noct_init[i][0]);
344     }
345
346 #if 0
347   byte xxx[] = { 0x7f, 0x01 };
348   int done;
349   libusb_interrupt_transfer(usb_dev, 0x02, xxx, 2, &done, 5000);
350 #endif
351
352   DBG("USB: Connecting libusb to mainloop");
353
354   if (!libusb_pollfds_handle_timeouts(usb_ctx))
355     die("Unsupported version of libusb, please fix me");
356
357   GARY_INIT_ZERO(usb_fds, 0);
358   libusb_set_pollfd_notifiers(usb_ctx, usb_added_fd, usb_removed_fd, NULL);
359
360   const struct libusb_pollfd **fds = libusb_get_pollfds(usb_ctx);
361   ASSERT(fds);
362   for (int i=0; fds[i]; i++)
363     usb_added_fd(fds[i]->fd, fds[i]->events, NULL);
364   free(fds);
365
366   noct_read_init();
367   noct_write_init();
368 }
369
370 /*
371  *  Interface to PulseAudio
372  *
373  *  FIXME
374  */
375
376 struct pmain_io {
377   cnode n;
378   struct main_file f;
379   clist io_events;
380 };
381
382 static clist pmain_io_list;
383
384 struct pa_io_event {
385   cnode n;
386   cnode gc_n;
387   struct pmain_io *io;
388   pa_io_event_flags_t events;
389   pa_io_event_cb_t callback;
390   pa_io_event_destroy_cb_t destroy_callback;
391   void *userdata;
392 };
393
394 static clist pmain_io_gc_list;
395
396 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);
397 static void pmain_io_enable(pa_io_event *e, pa_io_event_flags_t events);
398 static void pmain_io_free(pa_io_event *e);
399 static void pmain_io_set_destroy(pa_io_event *e, pa_io_event_destroy_cb_t cb);
400
401 struct pa_time_event {
402   cnode n;
403   struct main_timer t;
404   pa_time_event_cb_t callback;
405   pa_time_event_destroy_cb_t destroy_callback;
406   void *userdata;
407   struct timeval tv;
408 };
409
410 static clist pmain_time_gc_list;
411
412 static pa_time_event *pmain_time_new(pa_mainloop_api *api, const struct timeval *tv, pa_time_event_cb_t cb, void *userdata);
413 static void pmain_time_restart(pa_time_event *e, const struct timeval *tv);
414 static void pmain_time_free(pa_time_event *e);
415 static void pmain_time_set_destroy(pa_time_event *e, pa_time_event_destroy_cb_t cb);
416
417 struct pa_defer_event {
418   cnode n;
419   struct main_hook h;
420   pa_defer_event_cb_t callback;
421   pa_defer_event_destroy_cb_t destroy_callback;
422   void *userdata;
423 };
424
425 static clist pmain_defer_gc_list;
426
427 static pa_defer_event *pmain_defer_new(pa_mainloop_api *api, pa_defer_event_cb_t cb, void *userdata);
428 static void pmain_defer_enable(pa_defer_event *e, int b);
429 static void pmain_defer_free(pa_defer_event *e);
430 static void pmain_defer_set_destroy(pa_defer_event *e, pa_defer_event_destroy_cb_t cb);
431
432 static void pmain_quit(pa_mainloop_api *a, int retval);
433
434 static struct main_hook pmain_gc_hook;
435
436 static void pmain_trigger_gc(void);
437
438 static struct pa_mainloop_api pmainloop_api = {
439   .io_new = pmain_io_new,
440   .io_enable = pmain_io_enable,
441   .io_free = pmain_io_free,
442   .io_set_destroy = pmain_io_set_destroy,
443
444   .time_new = pmain_time_new,
445   .time_restart = pmain_time_restart,
446   .time_free = pmain_time_free,
447   .time_set_destroy = pmain_time_set_destroy,
448
449   .defer_new = pmain_defer_new,
450   .defer_enable = pmain_defer_enable,
451   .defer_free = pmain_defer_free,
452   .defer_set_destroy = pmain_defer_set_destroy,
453
454   .quit = pmain_quit,
455 };
456
457 static struct pmain_io *pmain_get_io(int fd)
458 {
459   CLIST_FOR_EACH(struct pmain_io *, io, pmain_io_list)
460     if (io->f.fd == fd)
461       {
462         DBG("Pulse: Recycling IO master");
463         return io;
464       }
465
466   struct pmain_io *io = xmalloc(sizeof(*io));
467   io->f.fd = fd;
468   io->f.data = io;
469   clist_add_tail(&pmain_io_list, &io->n);
470   clist_init(&io->io_events);
471   return io;
472 }
473
474 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)
475 {
476   struct pa_io_event *e = xmalloc_zero(sizeof(*e));
477   DBG("Pulse: Creating new IO %p for fd %u", e, fd);
478
479   e->io = pmain_get_io(fd);
480   e->callback = cb;
481   e->userdata = userdata;
482   clist_add_head(&e->io->io_events, &e->n);     // Do not call the new IO if created from another IO on the same fd
483   pmain_io_enable(e, events);
484   return e;
485 }
486
487 static int pmain_io_read(struct main_file *f)
488 {
489   struct pmain_io *io = f->data;
490   DBG("Pulse: fd %d ready for read", io->f.fd);
491
492   CLIST_FOR_EACH(struct pa_io_event *, e, io->io_events)
493     if (e->events & PA_IO_EVENT_INPUT)
494       {
495         DBG("Pulse: Callback on IO %p", e);
496         e->callback(&pmainloop_api, e, io->f.fd, PA_IO_EVENT_INPUT, e->userdata);
497       }
498
499   DBG("Pulse: fd %d read done", io->f.fd);
500   return HOOK_IDLE;
501 }
502
503 static int pmain_io_write(struct main_file *f)
504 {
505   struct pmain_io *io = f->data;
506   DBG("Pulse: fd %d ready for write", io->f.fd);
507
508   CLIST_FOR_EACH(struct pa_io_event *, e, io->io_events)
509     if (e->events & PA_IO_EVENT_OUTPUT)
510       {
511         DBG("Pulse: Callback on IO %p", e);
512         e->callback(&pmainloop_api, e, io->f.fd, PA_IO_EVENT_OUTPUT, e->userdata);
513       }
514
515   DBG("Pulse: fd %d write done", io->f.fd);
516   return HOOK_IDLE;
517 }
518
519 static void pmain_io_enable(pa_io_event *e, pa_io_event_flags_t events)
520 {
521   struct pmain_io *io = e->io;
522   DBG("Pulse: Changing IO event mask for IO %p on fd %d to %02x", e, io->f.fd, events);
523   e->events = events;
524
525   pa_io_event_flags_t mask = 0;
526   CLIST_FOR_EACH(struct pa_io_event *, f, io->io_events)
527     mask |= f->events;
528   DBG("Pulse: Recalculated IO mask for fd %d to %02x", io->f.fd, mask);
529
530   if (mask)
531     {
532       io->f.read_handler = (mask & PA_IO_EVENT_INPUT) ? pmain_io_read : NULL;
533       io->f.write_handler = (mask & PA_IO_EVENT_OUTPUT) ? pmain_io_write : NULL;
534       if (file_is_active(&io->f))
535         file_chg(&io->f);
536       else
537         file_add(&io->f);
538     }
539   else
540     file_del(&io->f);
541 }
542
543 static void pmain_io_free(pa_io_event *e)
544 {
545   DBG("Pulse: Deleting IO %p for fd %d", e, e->io->f.fd);
546   pmain_io_enable(e, 0);
547   clist_add_tail(&pmain_io_gc_list, &e->gc_n);
548   pmain_trigger_gc();
549 }
550
551 static void pmain_io_set_destroy(pa_io_event *e, pa_io_event_destroy_cb_t cb)
552 {
553   e->destroy_callback = cb;
554 }
555
556 static void pmain_time_handler(struct main_timer *t)
557 {
558   struct pa_time_event *e = t->data;
559   DBG("Pulse: Timer %p triggered", e);
560   timer_del(t);
561   e->callback(&pmainloop_api, e, &e->tv, e->userdata);
562   DBG("Pulse: Timer %p done", e);
563 }
564
565 static pa_time_event *pmain_time_new(pa_mainloop_api *api UNUSED, const struct timeval *tv, pa_time_event_cb_t cb, void *userdata)
566 {
567   struct pa_time_event *e = xmalloc_zero(sizeof(*e));
568   DBG("Pulse: Creating timer %p", e);
569   e->callback = cb;
570   e->userdata = userdata;
571   e->t.handler = pmain_time_handler;
572   e->t.data = e;
573   pmain_time_restart(e, tv);
574   return e;
575 }
576
577 static timestamp_t timeval_to_timestamp(const struct timeval *tv)
578 {
579   return 1000 * (timestamp_t) tv->tv_sec + tv->tv_usec / 1000;
580 }
581
582 static void pmain_time_restart(pa_time_event *e, const struct timeval *tv)
583 {
584   struct timeval now;
585   gettimeofday(&now, NULL);
586   timestamp_t ts_now = timeval_to_timestamp(&now);
587   timestamp_t ts_fire = timeval_to_timestamp(tv);
588   timestamp_t ts_delta = ts_fire - ts_now;
589   DBG("Pulse: Setting timer %p to %+d", e, (int) ts_delta);
590   timer_del(&e->t);
591   e->tv = *tv;
592   timer_add_rel(&e->t, ts_delta);
593 }
594
595 static void pmain_time_free(pa_time_event *e)
596 {
597   DBG("Pulse: Timer %p deleted", e);
598   timer_del(&e->t);
599   clist_add_tail(&pmain_time_gc_list, &e->n);
600   pmain_trigger_gc();
601 }
602
603 static void pmain_time_set_destroy(pa_time_event *e, pa_time_event_destroy_cb_t cb)
604 {
605   e->destroy_callback = cb;
606 }
607
608 static int pmain_defer_handler(struct main_hook *h)
609 {
610   struct pa_defer_event *e = h->data;
611   DBG("Pulse: Deferred event %p triggered", e);
612   e->callback(&pmainloop_api, e, e->userdata);
613   DBG("Pulse: Deferred event done");
614   return hook_is_active(&e->h) ? HOOK_RETRY : HOOK_IDLE;
615 }
616
617 static pa_defer_event *pmain_defer_new(pa_mainloop_api *api UNUSED, pa_defer_event_cb_t cb, void *userdata)
618 {
619   struct pa_defer_event *e = xmalloc_zero(sizeof(*e));
620   DBG("Pulse: Creating defer %p", e);
621   e->callback = cb;
622   e->userdata = userdata;
623   e->h.handler = pmain_defer_handler;
624   e->h.data = e;
625   pmain_defer_enable(e, 1);
626   return e;
627 }
628
629 static void pmain_defer_enable(pa_defer_event *e, int b)
630 {
631   DBG("Pulse: %sabling defer %p", (b ? "En" : "Dis"), e);
632   if (b)
633     hook_add(&e->h);
634   else
635     hook_del(&e->h);
636 }
637
638 static void pmain_defer_free(pa_defer_event *e)
639 {
640   DBG("Pulse: Deferred event %p deleted", e);
641   hook_del(&e->h);
642   clist_add_tail(&pmain_defer_gc_list, &e->n);
643   pmain_trigger_gc();
644 }
645
646 static void pmain_defer_set_destroy(pa_defer_event *e, pa_defer_event_destroy_cb_t cb)
647 {
648   e->destroy_callback = cb;
649 }
650
651 static void pmain_quit(pa_mainloop_api *a UNUSED, int retval UNUSED)
652 {
653   DBG("Pulse: Main loop quit not implemented");
654 }
655
656 static int pmain_gc_handler(struct main_hook *h)
657 {
658   DBG("Pulse: Garbage collector");
659   hook_del(h);
660
661   cnode *n;
662   while (n = clist_remove_head(&pmain_io_gc_list))
663     {
664       struct pa_io_event *ei = SKIP_BACK(struct pa_io_event, gc_n, n);
665       struct pmain_io *io = ei->io;
666       DBG("Pulse: GC of IO event %p on fd %d", ei, io->f.fd);
667       if (ei->destroy_callback)
668         ei->destroy_callback(&pmainloop_api, ei, ei->userdata);
669       clist_remove(&ei->n);
670       if (clist_empty(&io->io_events))
671         {
672           ASSERT(!file_is_active(&io->f));
673           DBG("Pulse: GC of IO master for fd %d", io->f.fd);
674           clist_remove(&io->n);
675           xfree(io);
676         }
677       xfree(ei);
678     }
679
680   struct pa_time_event *et;
681   while (et = (struct pa_time_event *) clist_remove_head(&pmain_time_gc_list))
682     {
683       DBG("Pulse: GC for timer %p", et);
684       if (et->destroy_callback)
685         et->destroy_callback(&pmainloop_api, et, et->userdata);
686       xfree(et);
687     }
688
689   struct pa_defer_event *ed;
690   while (ed = (struct pa_defer_event *) clist_remove_head(&pmain_defer_gc_list))
691     {
692       DBG("Pulse: GC for defer %p", ed);
693       if (ed->destroy_callback)
694         ed->destroy_callback(&pmainloop_api, ed, ed->userdata);
695       xfree(ed);
696     }
697
698   DBG("Pulse: Garbage collector done");
699   return HOOK_RETRY;
700 }
701
702 static void pmain_trigger_gc(void)
703 {
704   hook_add(&pmain_gc_hook);
705 }
706
707 static pa_context *pulse_ctx;
708 static bool pulse_ready;
709
710 static void pulse_client_info_cb(pa_context *ctx, const pa_client_info *i, int eol, void *userdata)
711 {
712   if (eol)
713     {
714       DBG("Pulse: CLIENT DONE");
715       return;
716     }
717
718   DBG("Pulse: CLIENT #%u: %s mod=%u drv=%s", i->index, i->name, i->owner_module, i->driver);
719 }
720
721 static void pulse_state_cb(pa_context *ctx, void *userdata UNUSED)
722 {
723   int state = pa_context_get_state(ctx);
724   DBG("Pulse: State callback, new state = %d", state);
725   if (state == PA_CONTEXT_READY)
726     {
727       if (!pulse_ready)
728         {
729           pulse_ready = 1;
730           DBG("Pulse: ONLINE");
731           pa_context_get_client_info_list(ctx, pulse_client_info_cb, NULL);
732           // FIXME: Discard the operation when server goes offline
733         }
734     }
735   else
736     {
737       if (pulse_ready)
738         {
739           pulse_ready = 0;
740           DBG("Pulse: OFFLINE");
741         }
742     }
743 }
744
745 static void pulse_init(void)
746 {
747   clist_init(&pmain_io_list);
748   clist_init(&pmain_io_gc_list);
749   clist_init(&pmain_time_gc_list);
750   clist_init(&pmain_defer_gc_list);
751   pmain_gc_hook.handler = pmain_gc_handler;
752
753   pulse_ctx = pa_context_new(&pmainloop_api, "ursaryd");
754   pa_context_set_state_callback(pulse_ctx, pulse_state_cb, NULL);
755   pa_context_connect(pulse_ctx, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
756 }
757
758 int main(int argc UNUSED, char **argv)
759 {
760   log_init(argv[0]);
761   main_init();
762
763   // msg(L_INFO, "Initializing USB");
764   // usb_init();
765
766   msg(L_INFO, "Initializing PulseAudio");
767   pulse_init();
768
769   msg(L_INFO, "Entering main loop");
770   main_loop();
771
772   return 0;
773 }