]> mj.ucw.cz Git - ursary.git/blob - ursaryd.c
e28cebfa4cd2060c8764c053e45c6e53be3d1a62
[ursary.git] / ursaryd.c
1 /*
2  *      The Ursary Audio Controls
3  *
4  *      (c) 2014--2020 Martin Mares <mj@ucw.cz>
5  */
6
7 #define LOCAL_DEBUG
8
9 #include <ucw/lib.h>
10 #include <ucw/clists.h>
11 #include <ucw/daemon.h>
12 #include <ucw/log.h>
13 #include <ucw/mainloop.h>
14 #include <ucw/opt.h>
15 #include <ucw/stkstring.h>
16
17 #include <math.h>
18 #include <signal.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <stdlib.h>
22 #include <syslog.h>
23
24 #include "ursaryd.h"
25 #include "usb.h"
26
27 /*
28  *      Map of all controls
29  *
30  *              rotary          red button      green button
31  *              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
32  *      0       sink PCH        mute            use headphones
33  *      1       -               -               -
34  *      2       -               -               -
35  *      3       desk brightness -               desk lights on
36  *      4       MPD             mute            MPD play/pause
37  *      5       Albireo MPV     mute            MPD stop
38  *      6       Albireo other   mute            MPD prev
39  *      7       other machines  mute            MPD next
40  *
41  *      center  -
42  *      slider  light color temperature
43  */
44
45 #define PCH_SINK "alsa_output.pci-0000_00_1f.3.analog-stereo"
46
47 /*** Sink controls ***/
48
49 static double volume_from_pa(pa_volume_t vol)
50 {
51   return (double) vol / PA_VOLUME_NORM;
52 }
53
54 static pa_volume_t volume_to_pa(double vol)
55 {
56   return vol * PA_VOLUME_NORM + 0.0001;
57 }
58
59 static void update_ring_from_sink(int ring, const char *sink_name)
60 {
61   struct pulse_sink *s = pulse_sink_by_name(sink_name);
62   if (!s)
63     {
64       noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
65       noct_set_button(ring, 0);
66       return;
67     }
68
69   if (s->mute)
70     {
71       noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
72       noct_set_button(ring, 1);
73       return;
74     }
75
76   double vol = CLAMP(volume_from_pa(s->volume), 0, 1);
77   noct_set_ring(ring, RING_MODE_LEFT, CLAMP((int)(0x7f * vol), 12, 0x7f));
78   noct_set_button(ring, 0);
79 }
80
81 static void update_button_from_port(int button, const char *sink_name, const char *port_name)
82 {
83   struct pulse_sink *s = pulse_sink_by_name(sink_name);
84   if (!s)
85     {
86       noct_set_button(button, 0);
87       return;
88     }
89
90   noct_set_button(button, !strcmp(s->active_port, port_name));
91 }
92
93 static void update_sink_from_rotary(int delta, const char *sink_name)
94 {
95   struct pulse_sink *s = pulse_sink_by_name(sink_name);
96   if (!s)
97     return;
98
99   double vol = volume_from_pa(s->volume) + delta * 0.02;
100   pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, 1));
101   if (pavol == s->volume)
102     return;
103   pa_cvolume cvol;
104   pa_cvolume_set(&cvol, s->channels, pavol);
105
106   DBG("## Setting volume of sink %s to %d", s->name, cvol.values[0]);
107   pulse_sink_set_volume(s->idx, &cvol);
108 }
109
110 static void update_sink_mute_from_button(int on, const char *sink_name)
111 {
112   if (!on)
113     return;
114
115   struct pulse_sink *s = pulse_sink_by_name(sink_name);
116   if (!s)
117     return;
118
119   DBG("## Setting mute of sink %s to %d", s->name, !s->mute);
120   pulse_sink_set_mute(s->idx, !s->mute);
121 }
122
123 static void update_port_from_button(int on, const char *sink_name, const char *port1, const char *port2)
124 {
125   if (!on)
126     return;
127
128   struct pulse_sink *s = pulse_sink_by_name(sink_name);
129   if (!s)
130     return;
131
132   const char *port = port1;
133   if (!strcmp(s->active_port, port1))
134     port = port2;
135
136   DBG("## Setting port of sink %s to %s", s->name, port);
137   pulse_sink_set_port(s->idx, port);
138 }
139
140 /*** Client controls ***/
141
142 struct client_map {
143   int group;
144   const char *client;
145   const char *host;
146 };
147
148 static struct client_map client_map[] = {
149   { 4, "Music Player Daemon",   "albireo",      },
150   { 5, "mpv",                   "albireo",      },
151   { 6, NULL,                    "albireo",      },
152   { 7, NULL,                    NULL,           },
153 };
154
155 #define CLIENT_MAP_SIZE ARRAY_SIZE(client_map)
156
157 struct group_config {
158   bool enabled;
159   double range;
160 };
161
162 struct group_state {
163   double volume;
164   bool have_muted[2];
165 };
166
167 #define NUM_GROUPS 9
168
169 static struct group_config group_config[NUM_GROUPS] = {
170   [4] = { .enabled = 1, .range = 1.5 },
171   [5] = { .enabled = 1, .range = 1.5 },
172   [6] = { .enabled = 1, .range = 1.5 },
173   [7] = { .enabled = 1, .range = 1.5 },
174 };
175
176 static struct group_state group_state[NUM_GROUPS];
177
178 static void calc_groups(void)
179 {
180   bzero(group_state, sizeof(group_state));
181
182   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
183     {
184       s->noct_group_idx = -1;
185
186       if (s->client_idx < 0 || s->sink_idx < 0)
187         continue;
188
189       struct pulse_client *c = pulse_client_by_idx(s->client_idx);
190       if (!c)
191         continue;
192
193       for (uns i=0; i < CLIENT_MAP_SIZE; i++)
194         {
195           struct client_map *cm = &client_map[i];
196           if ((!cm->client || !strcmp(cm->client, c->name)) &&
197               (!cm->host || !strcmp(cm->host, c->host)))
198             {
199               int g = cm->group;
200               struct group_state *gs = &group_state[g];
201               DBG("@@ Client #%d, sink input #%d -> group %d", s->client_idx, s->idx, g);
202               s->noct_group_idx = g;
203               gs->volume = MAX(gs->volume, s->volume);
204               gs->have_muted[!!s->mute] = 1;
205               break;
206             }
207         }
208     }
209 }
210
211 static void update_groups(void)
212 {
213   calc_groups();
214
215   for (uns i=0; i < NUM_GROUPS; i++)
216     {
217       struct group_config *gc = &group_config[i];
218       struct group_state *gs = &group_state[i];
219       if (!gc->enabled)
220         continue;
221
222       DBG("@@ Group #%d: mute=%d/%d volume=%.3f/%.3f", i, gs->have_muted[0], gs->have_muted[1], volume_from_pa(gs->volume), gc->range);
223
224       if (!gs->have_muted[0] && !gs->have_muted[1])
225         {
226           noct_set_ring(i, RING_MODE_LEFT, 0);
227           noct_set_button(i, 0);
228         }
229       else if (!gs->have_muted[0])
230         {
231           noct_set_ring(i, RING_MODE_SINGLE_ON, 0x7f);
232           noct_set_button(i, 1);
233         }
234       else
235         {
236           double vol = CLAMP(volume_from_pa(gs->volume), 0, gc->range);
237           int val = 0x7f * vol / gc->range;
238           val = CLAMP(val, 12, 0x7f);
239           noct_set_ring(i, RING_MODE_LEFT, val);
240           noct_set_button(i, 0);
241         }
242     }
243 }
244
245 static void update_group_from_rotary(int i, int delta)
246 {
247   if (i >= NUM_GROUPS)
248     return;
249   struct group_config *gc = &group_config[i];
250   struct group_state *gs = &group_state[i];
251   if (!gc->enabled)
252     return;
253
254   calc_groups();
255   double vol = volume_from_pa(gs->volume) + delta*0.02;
256   pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, gc->range));
257
258   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
259     {
260       if (s->noct_group_idx == i && s->volume != pavol)
261         {
262           DBG("@@ Client #%d, sink input #%d: setting volume=%u", s->client_idx, s->idx, pavol);
263           pa_cvolume cvol;
264           pa_cvolume_set(&cvol, s->channels, pavol);
265           pulse_sink_input_set_volume(s->idx, &cvol);
266         }
267     }
268 }
269
270 static void update_group_from_button(int i, int on)
271 {
272   if (!on)
273     return;
274   if (i >= NUM_GROUPS)
275     return;
276   struct group_config *gc = &group_config[i];
277   struct group_state *gs = &group_state[i];
278   if (!gc->enabled)
279     return;
280
281   calc_groups();
282   if (!gs->have_muted[0] && !gs->have_muted[1])
283     return;
284   uns mute = !gs->have_muted[1];
285
286   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
287     {
288       if (s->noct_group_idx == i)
289         {
290           DBG("@@ Client #%d, sink input #%d: setting mute=%u", s->client_idx, s->idx, mute);
291           pulse_sink_input_set_mute(s->idx, mute);
292         }
293     }
294 }
295
296 #if 0   // Not used at the moment
297
298 static int find_touched_client(void)
299 {
300   int touched = -1;
301
302   for (uns i=0; i < NUM_GROUPS; i++)
303     if (group_config[i].enabled && noct_rotary_touched[i])
304       {
305         if (touched >= 0)
306           return -1;
307         touched = i;
308       }
309   return touched;
310 }
311
312 #endif
313
314 /*** Default sink controls ***/
315
316 #if 0   // Not mapped to any button at the moment
317
318 static const char *get_client_sink(int i)
319 {
320   const char *sink = NULL;
321
322   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
323     if (s->noct_group_idx == i)
324       {
325         struct pulse_sink *sk = (s->sink_idx >= 0) ? pulse_sink_by_idx(s->sink_idx) : NULL;
326         const char *ss = sk ? sk->name : NULL;
327         if (!sink)
328           sink = ss;
329         else if (strcmp(sink, ss))
330           sink = "?";
331       }
332   return sink ? : "?";
333 }
334
335 static void update_default_sink(void)
336 {
337   int i = find_touched_client();
338   const char *sink;
339   if (i >= 0)
340     sink = get_client_sink(i);
341   else
342     sink = pulse_default_sink_name ? : "?";
343
344   if (!strcmp(sink, "ursarium"))
345     {
346       noct_set_button(8, 1);
347       noct_set_button(9, 0);
348       noct_set_button(10, 0);
349     }
350   else if (!strcmp(sink, "catarium"))
351     {
352       noct_set_button(8, 0);
353       noct_set_button(9, 1);
354       noct_set_button(10, 0);
355     }
356   else if (!strcmp(sink, "compress"))
357     {
358       noct_set_button(8, 0);
359       noct_set_button(9, 0);
360       noct_set_button(10, 1);
361     }
362   else
363     {
364       noct_set_button(8, 0);
365       noct_set_button(9, 0);
366       noct_set_button(10, 0);
367     }
368 }
369
370 static void update_default_sink_from_button(int button, int on)
371 {
372   if (!on)
373     return;
374
375   int i = find_touched_client();
376   const char *sink;
377   if (i >= 0)
378     sink = get_client_sink(i);
379   else
380     sink = pulse_default_sink_name ? : "?";
381
382   const char *switch_to = NULL;
383   if (button == 8)
384     {
385       if (!strcmp(sink, "ursarium"))
386         switch_to = "burrow";
387       else
388         switch_to = "ursarium";
389     }
390   else if (button == 9)
391     {
392       if (!strcmp(sink, "catarium"))
393         switch_to = "burrow";
394       else
395         switch_to = "catarium";
396     }
397   else if (button == 10)
398     {
399       if (!strcmp(sink, "compress"))
400         switch_to = "burrow";
401       else
402         switch_to = "compress";
403     }
404
405   if (!switch_to)
406     return;
407
408   if (i >= 0)
409     {
410       struct pulse_sink *sk = pulse_sink_by_name(switch_to);
411       if (!sk)
412         return;
413
414       CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
415         if (s->noct_group_idx == i)
416           {
417             DBG("Moving input #%d to sink #%d", s->idx, sk->idx);
418             pulse_sink_input_move(s->idx, sk->idx);
419           }
420     }
421   else
422     {
423       DBG("Switching default sink to %s", switch_to);
424       pulse_server_set_default_sink(switch_to);
425     }
426 }
427
428 #endif
429
430 /*** MPD controls ***/
431
432 static bool mpd_flash_state;
433
434 static void mpd_flash_timeout(struct main_timer *t)
435 {
436   mpd_flash_state ^= 1;
437   noct_set_button(12, mpd_flash_state);
438   timer_add_rel(t, 500);
439 }
440
441 static struct main_timer mpd_flash_timer = {
442   .handler = mpd_flash_timeout,
443 };
444
445 static void update_mpd(void)
446 {
447   const char *state = mpd_get_player_state();
448   if (!strcmp(state, "play"))
449     {
450       noct_set_button(12, 1);
451       timer_del(&mpd_flash_timer);
452     }
453   else if (!strcmp(state, "pause"))
454     {
455       if (!timer_is_active(&mpd_flash_timer))
456         {
457           mpd_flash_state = 1;
458           mpd_flash_timeout(&mpd_flash_timer);
459         }
460     }
461   else
462     {
463       noct_set_button(12, 0);
464       timer_del(&mpd_flash_timer);
465     }
466 }
467
468 static void mpd_button_timeout(struct main_timer *t)
469 {
470   DBG("MPD stop");
471   timer_del(t);
472   mpd_stop();
473 }
474
475 static void update_mpd_from_button(int button UNUSED, int on)
476 {
477   static struct main_timer mpd_button_timer = {
478     .handler = mpd_button_timeout,
479   };
480
481   const char *state = mpd_get_player_state();
482
483   if (!on)
484     {
485       if (timer_is_active(&mpd_button_timer))
486         {
487           timer_del(&mpd_button_timer);
488           if (!strcmp(state, "play"))
489             {
490               DBG("MPD pause");
491               mpd_pause(1);
492             }
493           else if (!strcmp(state, "pause"))
494             {
495               DBG("MPD resume");
496               mpd_pause(0);
497             }
498         }
499       return;
500     }
501
502   if (!strcmp(state, "stop"))
503     {
504       DBG("MPD play");
505       mpd_play();
506     }
507   else
508     {
509       DBG("MPD starting button timer");
510       timer_add_rel(&mpd_button_timer, 1000);
511     }
512 }
513
514 /*** Lights ***/
515
516 static bool lights_on[2];
517 static double lights_brightness[2];
518 static double lights_temperature[2];
519
520 static void update_lights(void)
521 {
522   if (!dmx_is_ready())
523     {
524       noct_set_ring(3, RING_MODE_SINGLE_ON, 0x7f);
525       noct_set_button(11, 0);
526       return;
527     }
528
529   for (uint i=0; i<1; i++)
530     {
531       uint warm, cold;
532       if (lights_on[i])
533         {
534           noct_set_ring(3, RING_MODE_LEFT, lights_brightness[i] * 127);
535           noct_set_button(11, 1);
536           double r = 2;
537           double x = (exp(r*lights_brightness[i]) - 1) / (exp(r) - 1);
538           double t = lights_temperature[i];
539           double w = 2*x*(1-t);
540           double c = 2*x*t;
541           warm = CLAMP((int)(255*w), 0, 255);
542           cold = CLAMP((int)(255*c), 0, 255);
543         }
544       else
545         {
546           noct_set_ring(3, RING_MODE_LEFT, 0);
547           noct_set_button(11, 0);
548           warm = cold = 0;
549         }
550       DBG("Lights[%d]: on=%d bri=%.3f temp=%.3f -> warm=%d cold=%d", i, lights_on[i], lights_brightness[i], lights_temperature[i], warm, cold);
551       dmx_set_pwm(2*i, warm);
552       dmx_set_pwm(2*i+1, cold);
553     }
554 }
555
556 static void update_lights_from_rotary(int ch, int delta)
557 {
558   if (lights_on[ch])
559     lights_brightness[ch] = CLAMP(lights_brightness[ch] + 0.015*delta*abs(delta), 0., 1.);
560   update_lights();
561 }
562
563 static void update_lights_from_slider(int value)
564 {
565   lights_temperature[0] = value / 127.;
566   update_lights();
567 }
568
569 static void lights_button_timeout(struct main_timer *t)
570 {
571   int ch = (uintptr_t) t->data;
572   DBG("Lights[%d]: Full throttle!", ch);
573   timer_del(t);
574   lights_on[ch] = 1;
575   lights_brightness[ch] = 1;
576   update_lights();
577 }
578
579 static void update_lights_from_button(int ch, int on)
580 {
581   static struct main_timer lights_button_timer[2] = {{
582     .handler = lights_button_timeout,
583     .data = (void *)(uintptr_t) 0,
584   },{
585     .handler = lights_button_timeout,
586     .data = (void *)(uintptr_t) 1,
587   }};
588
589   if (on)
590     {
591       lights_on[ch] = !lights_on[ch];
592       update_lights();
593       timer_add_rel(&lights_button_timer[ch], 1000);
594     }
595   else
596     timer_del(&lights_button_timer[ch]);
597 }
598
599 /*** Main update routines ***/
600
601 static struct main_timer update_timer;
602 static timestamp_t last_touch_time;
603
604 enum update_state {
605   US_OFFLINE,
606   US_ONLINE,
607   US_SLEEPING,
608   US_PULSE_DEAD,
609 };
610
611 static enum update_state update_state;
612
613 static bool want_sleep_p(void)
614 {
615   CLIST_FOR_EACH(struct pulse_sink *, s, pulse_sink_list)
616     if (!s->suspended)
617       return 0;
618   return 1;
619 }
620
621 static void do_update(struct main_timer *t)
622 {
623   DBG("## UPDATE in state %u", update_state);
624   timer_del(t);
625
626   // Nocturn dead?
627   if (!noct_is_ready())
628     {
629       DBG("## UPDATE: Nocturn is not ready");
630       update_state = US_OFFLINE;
631       return;
632     }
633   else if (update_state == US_OFFLINE)
634     {
635       DBG("## UPDATE: Going online");
636       update_state = US_ONLINE;
637     }
638
639   // Pulse dead?
640   if (!pulse_is_ready())
641     {
642       DBG("## UPDATE: Pulse is not online");
643       if (update_state != US_PULSE_DEAD)
644         {
645           update_state = US_PULSE_DEAD;
646           noct_clear();
647           for (int i=0; i<8; i++)
648             noct_set_button(i, 1);
649         }
650       return;
651     }
652   else if (update_state == US_PULSE_DEAD)
653     {
654       DBG("## UPDATE: Waking up from the dead");
655       update_state = US_ONLINE;
656       noct_clear();
657     }
658
659 #ifdef LOCAL_DEBUG
660   pulse_dump();
661 #endif
662
663   // Sleeping?
664   bool want_sleep = want_sleep_p();
665   if (!want_sleep)
666     last_touch_time = main_get_now();
667   timestamp_t since_touch = last_touch_time ? main_get_now() - last_touch_time : 0;
668   timestamp_t sleep_in = 5000;
669   if (since_touch >= sleep_in)
670     {
671       DBG("UPDATE: Sleeping");
672       if (update_state == US_ONLINE)
673         {
674           update_state = US_SLEEPING;
675           noct_clear();
676           noct_set_ring(8, RING_MODE_LEFT, 127);
677         }
678       return;
679     }
680   else
681     {
682       if (update_state == US_SLEEPING)
683         {
684           DBG("UPDATE: Waking up");
685           update_state = US_ONLINE;
686           noct_clear();
687         }
688       if (want_sleep)
689         {
690           timestamp_t t = sleep_in - since_touch + 10;
691           DBG("UPDATE: Scheduling sleep in %d ms", (int) t);
692           timer_add_rel(&update_timer, t);
693         }
694     }
695
696   // Everything normal
697   update_ring_from_sink(0, PCH_SINK);
698   update_button_from_port(8, PCH_SINK, "analog-output-headphones");
699   update_groups();
700 #if 0
701   update_default_sink();
702 #endif
703   update_mpd();
704   update_lights();
705 }
706
707 void schedule_update(void)
708 {
709   timer_add_rel(&update_timer, 10);
710 }
711
712 static bool prepare_notify(void)
713 {
714   if (!pulse_is_ready())
715     {
716       DBG("## NOTIFY: Pulse is not online");
717       return 0;
718     }
719
720   last_touch_time = main_get_now();
721   if (update_state == US_SLEEPING)
722     {
723       DBG("## NOTIFY: Scheduling wakeup");
724       schedule_update();
725     }
726
727   return 1;
728 }
729
730 void notify_rotary(int rotary, int delta)
731 {
732   if (!prepare_notify())
733     return;
734
735   switch (rotary)
736     {
737     case 0:
738       update_sink_from_rotary(delta, PCH_SINK);
739       break;
740     case 3:
741       update_lights_from_rotary(0, delta);
742       break;
743     case 9:
744       update_lights_from_slider(delta);
745       break;
746     default:
747       update_group_from_rotary(rotary, delta);
748     }
749 }
750
751 void notify_button(int button, int on)
752 {
753   if (!prepare_notify())
754     return;
755
756   switch (button)
757     {
758     case 0:
759       update_sink_mute_from_button(on, PCH_SINK);
760       break;
761     case 8:
762       update_port_from_button(on, PCH_SINK, "analog-output-lineout", "analog-output-headphones");
763       break;
764 #if 0
765     case 9:
766     case 10:
767       update_default_sink_from_button(button, on);
768       break;
769 #endif
770     case 11:
771       update_lights_from_button(0, on);
772       break;
773     case 12:
774       update_mpd_from_button(button, on);
775       break;
776     case 13:
777       if (on)
778         mpd_stop();
779       break;
780     case 14:
781       if (on)
782         mpd_prev();
783       break;
784     case 15:
785       if (on)
786         mpd_next();
787       break;
788     default:
789       update_group_from_button(button, on);
790     }
791 }
792
793 void notify_touch(int rotary UNUSED, int on UNUSED)
794 {
795   if (!prepare_notify())
796     return;
797
798 #if 0
799   // Rotary touches switch meaning of LEDs, this is handled inside display updates
800   if (rotary >= 4 && rotary < 8)
801     schedule_update();
802 #endif
803 }
804
805 /*** Main entry point ***/
806
807 static int debug;
808 static int no_fork;
809
810 static struct opt_section options = {
811   OPT_ITEMS {
812     OPT_HELP("Control console for the Ursary"),
813     OPT_HELP(""),
814     OPT_HELP("Options:"),
815     OPT_HELP_OPTION,
816     OPT_BOOL('d', "debug", debug, 0, "\tEnable debugging mode (no fork etc.)"),
817     OPT_BOOL(0, "no-fork", no_fork, 0, "\tDo not fork\n"),
818     OPT_END
819   }
820 };
821
822 static void sigterm_handler(struct main_signal *ms UNUSED)
823 {
824   main_shut_down();
825 }
826
827 static void daemon_body(struct daemon_params *dp)
828 {
829   main_init();
830   update_timer.handler = do_update;
831
832   usb_init();
833   noct_init();
834   dmx_init();
835   pulse_init();
836   mpd_init();
837
838   static struct main_signal term_sig = {
839     .signum = SIGTERM,
840     .handler = sigterm_handler,
841   };
842   signal_add(&term_sig);
843
844   msg(L_INFO, "Ursary daemon starting");
845   main_loop();
846   msg(L_INFO, "Ursary daemon shut down");
847   daemon_exit(dp);
848 }
849
850 int main(int argc UNUSED, char **argv)
851 {
852   opt_parse(&options, argv+1);
853   unsetenv("DISPLAY");
854   unsetenv("HOME");
855
856   struct daemon_params dp = {
857     .flags = ((debug || no_fork) ? DAEMON_FLAG_SIMULATE : 0),
858     .pid_file = "/run/ursaryd.pid",
859     .run_as_user = "ursary",
860   };
861   daemon_init(&dp);
862
863   log_init(argv[0]);
864   if (!debug)
865     {
866       struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
867       ls->levels = ~(1U << L_DEBUG);
868       log_set_default_stream(ls);
869     }
870
871   daemon_run(&dp, daemon_body);
872   return 0;
873 }