]> mj.ucw.cz Git - ursary.git/blob - ursaryd.c
Daemonization
[ursary.git] / ursaryd.c
1 /*
2  *      The Ursary Audio Controls
3  *
4  *      (c) 2014 Martin Mares <mj@ucw.cz>
5  */
6
7 #undef 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 <stdio.h>
18 #include <string.h>
19 #include <stdlib.h>
20 #include <syslog.h>
21
22 #include "ursaryd.h"
23
24 /*
25  *      Map of all controls
26  *
27  *              rotary          red button      green button
28  *      0       sink Ursarium   mute            select as default (or assign to client selected by touch)
29  *      1       sink Catarium   mute            dtto
30  *      2       sink Compress   -               dtto
31  *      3       -               -               -
32  *      4       MPD             mute            play/pause/stop
33  *      5       Albireo         mute            -
34  *      6       Ogion           mute            -
35  *      7       Ursula          mute            -
36  *
37  *      center  all sinks
38  *      slider  -
39  */
40
41 /*** Sink controls ***/
42
43 static double volume_from_pa(pa_volume_t vol)
44 {
45   return (double) vol / PA_VOLUME_NORM;
46 }
47
48 static pa_volume_t volume_to_pa(double vol)
49 {
50   return vol * PA_VOLUME_NORM + 0.0001;
51 }
52
53 static void update_ring_from_sink(int ring, const char *sink_name)
54 {
55   struct pulse_sink *s = pulse_sink_by_name(sink_name);
56   if (!s)
57     {
58       noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
59       noct_set_button(ring, 0);
60       return;
61     }
62
63   if (s->mute)
64     {
65       noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
66       noct_set_button(ring, 1);
67       return;
68     }
69
70   double vol = CLAMP(volume_from_pa(s->volume), 0, 1);
71   noct_set_ring(ring, RING_MODE_LEFT, CLAMP((int)(0x7f * vol), 12, 0x7f));
72   noct_set_button(ring, 0);
73 }
74
75 static void update_sink_from_rotary(int delta, const char *sink_name)
76 {
77   struct pulse_sink *s = pulse_sink_by_name(sink_name);
78   if (!s)
79     return;
80
81   double vol = volume_from_pa(s->volume) + delta * 0.02;
82   pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, 1));
83   if (pavol == s->volume)
84     return;
85   pa_cvolume cvol;
86   pa_cvolume_set(&cvol, s->channels, pavol);
87
88   DBG("## Setting volume of sink %s to %d", s->name, cvol.values[0]);
89   pulse_sink_set_volume(s->idx, &cvol);
90 }
91
92 static void update_sink_mute_from_button(int on, const char *sink_name)
93 {
94   if (!on)
95     return;
96
97   struct pulse_sink *s = pulse_sink_by_name(sink_name);
98   if (!s)
99     return;
100
101   DBG("## Setting mute of sink %s to %d", s->name, !s->mute);
102   pulse_sink_set_mute(s->idx, !s->mute);
103 }
104
105 /*** Client controls ***/
106
107 struct client_map {
108   int rotary;
109   const char *client;
110   const char *host;
111   double range;
112 };
113
114 static struct client_map client_map[] = {
115   { 4, "Music Player Daemon",   "albireo",      1.5 },
116   { 5, NULL,                    "albireo",      1.5 },
117   { 6, NULL,                    "ogion",        1.5 },
118   { 7, NULL,                    "ursula",       1.5 },
119 };
120
121 #define NUM_CLIENTS ARRAY_SIZE(client_map)
122
123 struct client_state {
124   double volume;
125   bool have_muted[2];
126 };
127
128 static struct client_state client_state[NUM_CLIENTS];
129
130 static int find_client_by_rotary(int rotary)
131 {
132   uns i;
133   for (i=0; i < NUM_CLIENTS; i++)
134     if (client_map[i].rotary == rotary)
135       return i;
136   return -1;
137 }
138
139 static void calc_clients(void)
140 {
141   bzero(client_state, sizeof(client_state));
142
143   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
144     {
145       s->noct_client_idx = -1;
146
147       if (s->client_idx < 0 || s->sink_idx < 0)
148         continue;
149
150       struct pulse_client *c = pulse_client_by_idx(s->client_idx);
151       if (!c)
152         continue;
153
154       for (uns i=0; i < NUM_CLIENTS; i++)
155         {
156           struct client_map *cm = &client_map[i];
157           struct client_state *cs = &client_state[i];
158           if ((!cm->client || !strcmp(cm->client, c->name)) &&
159               (!cm->host || !strcmp(cm->host, c->host)))
160             {
161               // DBG("@@ Client #%d, sink input #%d -> rotary %d", s->client_idx, s->idx, cm->rotary);
162               s->noct_client_idx = i;
163               cs->volume = MAX(cs->volume, s->volume);
164               cs->have_muted[!!s->mute] = 1;
165               break;
166             }
167         }
168     }
169 }
170
171 static void update_clients(void)
172 {
173   calc_clients();
174
175   for (uns i=0; i < NUM_CLIENTS; i++)
176     {
177       struct client_map *cm = &client_map[i];
178       struct client_state *cs = &client_state[i];
179       if (!cs->have_muted[0] && !cs->have_muted[1])
180         {
181           noct_set_ring(cm->rotary, RING_MODE_LEFT, 0);
182           noct_set_button(cm->rotary, 0);
183         }
184       else if (!cs->have_muted[0])
185         {
186           noct_set_ring(cm->rotary, RING_MODE_SINGLE_ON, 0x7f);
187           noct_set_button(cm->rotary, 1);
188         }
189       else
190         {
191           double vol = CLAMP(volume_from_pa(cs->volume), 0, cm->range);
192           int val = 0x7f * vol / cm->range;
193           val = CLAMP(val, 12, 0x7f);
194           noct_set_ring(cm->rotary, RING_MODE_LEFT, val);
195           noct_set_button(cm->rotary, 0);
196         }
197     }
198 }
199
200 static void update_client_from_rotary(int rotary, int delta)
201 {
202   int i = find_client_by_rotary(rotary);
203   if (i < 0)
204     return;
205   struct client_map *cm = &client_map[i];
206   struct client_state *cs = &client_state[i];
207
208   calc_clients();
209   double vol = volume_from_pa(cs->volume) + delta*0.02;
210   pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, cm->range));
211
212   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
213     {
214       if (s->noct_client_idx == i && s->volume != pavol)
215         {
216           DBG("@@ Client #%d, sink input #%d: setting volume=%u", s->client_idx, s->idx, pavol);
217           pa_cvolume cvol;
218           pa_cvolume_set(&cvol, s->channels, pavol);
219           pulse_sink_input_set_volume(s->idx, &cvol);
220         }
221     }
222 }
223
224 static void update_client_from_button(int button, int on)
225 {
226   if (button >= 8 || !on)
227     return;
228
229   int i = find_client_by_rotary(button);
230   if (i < 0)
231     return;
232   struct client_state *cs = &client_state[i];
233
234   calc_clients();
235   if (!cs->have_muted[0] && !cs->have_muted[1])
236     return;
237   uns mute = !cs->have_muted[1];
238
239   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
240     {
241       if (s->noct_client_idx == i)
242         {
243           DBG("@@ Client #%d, sink input #%d: setting mute=%u", s->client_idx, s->idx, mute);
244           pulse_sink_input_set_mute(s->idx, mute);
245         }
246     }
247 }
248
249 static int find_touched_client(void)
250 {
251   int touched = -1;
252
253   for (uns i=0; i < NUM_CLIENTS; i++)
254     if (noct_rotary_touched[client_map[i].rotary])
255       {
256         if (touched >= 0)
257           return -1;
258         touched = i;
259       }
260   return touched;
261 }
262
263 /*** Default sink controls ***/
264
265 static const char *get_client_sink(int i)
266 {
267   const char *sink = NULL;
268
269   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
270     if (s->noct_client_idx == i)
271       {
272         struct pulse_sink *sk = (s->sink_idx >= 0) ? pulse_sink_by_idx(s->sink_idx) : NULL;
273         const char *ss = sk ? sk->name : NULL;
274         if (!sink)
275           sink = ss;
276         else if (strcmp(sink, ss))
277           sink = "?";
278       }
279   return sink ? : "?";
280 }
281
282 static void update_default_sink(void)
283 {
284   int i = find_touched_client();
285   const char *sink;
286   if (i >= 0)
287     sink = get_client_sink(i);
288   else
289     sink = pulse_default_sink_name ? : "?";
290
291   if (!strcmp(sink, "ursarium"))
292     {
293       noct_set_button(8, 1);
294       noct_set_button(9, 0);
295       noct_set_button(10, 0);
296     }
297   else if (!strcmp(sink, "catarium"))
298     {
299       noct_set_button(8, 0);
300       noct_set_button(9, 1);
301       noct_set_button(10, 0);
302     }
303   else if (!strcmp(sink, "compress"))
304     {
305       noct_set_button(8, 0);
306       noct_set_button(9, 0);
307       noct_set_button(10, 1);
308     }
309   else
310     {
311       noct_set_button(8, 0);
312       noct_set_button(9, 0);
313       noct_set_button(10, 0);
314     }
315 }
316
317 static void update_default_sink_from_button(int button, int on)
318 {
319   if (!on)
320     return;
321
322   int i = find_touched_client();
323   const char *sink;
324   if (i >= 0)
325     sink = get_client_sink(i);
326   else
327     sink = pulse_default_sink_name ? : "?";
328
329   const char *switch_to = NULL;
330   if (button == 8)
331     {
332       if (!strcmp(sink, "ursarium"))
333         switch_to = "burrow";
334       else
335         switch_to = "ursarium";
336     }
337   else if (button == 9)
338     {
339       if (!strcmp(sink, "catarium"))
340         switch_to = "burrow";
341       else
342         switch_to = "catarium";
343     }
344   else if (button == 10)
345     {
346       if (!strcmp(sink, "compress"))
347         switch_to = "burrow";
348       else
349         switch_to = "compress";
350     }
351
352   if (!switch_to)
353     return;
354
355   if (i >= 0)
356     {
357       struct pulse_sink *sk = pulse_sink_by_name(switch_to);
358       if (!sk)
359         return;
360
361       CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
362         if (s->noct_client_idx == i)
363           {
364             DBG("Moving input #%d to sink #%d", s->idx, sk->idx);
365             pulse_sink_input_move(s->idx, sk->idx);
366           }
367     }
368   else
369     {
370       DBG("Switching default sink to %s", switch_to);
371       pulse_server_set_default_sink(switch_to);
372     }
373 }
374
375 /*** MPD controls ***/
376
377 static bool mpd_flash_state;
378
379 static void mpd_flash_timeout(struct main_timer *t)
380 {
381   mpd_flash_state ^= 1;
382   noct_set_button(12, mpd_flash_state);
383   timer_add_rel(t, 500);
384 }
385
386 static struct main_timer mpd_flash_timer = {
387   .handler = mpd_flash_timeout,
388 };
389
390 static void update_mpd(void)
391 {
392   const char *state = mpd_get_player_state();
393   if (!strcmp(state, "play"))
394     {
395       noct_set_button(12, 1);
396       timer_del(&mpd_flash_timer);
397     }
398   else if (!strcmp(state, "pause"))
399     {
400       if (!timer_is_active(&mpd_flash_timer))
401         {
402           mpd_flash_state = 1;
403           mpd_flash_timeout(&mpd_flash_timer);
404         }
405     }
406   else
407     {
408       noct_set_button(12, 0);
409       timer_del(&mpd_flash_timer);
410     }
411 }
412
413 static void mpd_button_timeout(struct main_timer *t)
414 {
415   DBG("MPD stop");
416   timer_del(t);
417   mpd_stop();
418 }
419
420 static void update_mpd_from_button(int button UNUSED, int on)
421 {
422   static struct main_timer mpd_button_timer = {
423     .handler = mpd_button_timeout,
424   };
425
426   const char *state = mpd_get_player_state();
427
428   if (!on)
429     {
430       if (timer_is_active(&mpd_button_timer))
431         {
432           timer_del(&mpd_button_timer);
433           if (!strcmp(state, "play"))
434             {
435               DBG("MPD pause");
436               mpd_pause(1);
437             }
438           else if (!strcmp(state, "pause"))
439             {
440               DBG("MPD resume");
441               mpd_pause(0);
442             }
443         }
444       return;
445     }
446
447   if (!strcmp(state, "stop"))
448     {
449       DBG("MPD play");
450       mpd_play();
451     }
452   else
453     {
454       DBG("MPD starting button timer");
455       timer_add_rel(&mpd_button_timer, 1000);
456     }
457 }
458
459 /*** Main update routines ***/
460
461 static struct main_timer update_timer;
462 static timestamp_t last_touch_time;
463
464 enum update_state {
465   US_OFFLINE,
466   US_ONLINE,
467   US_SLEEPING,
468   US_PULSE_DEAD,
469 };
470
471 static enum update_state update_state;
472
473 static bool want_sleep_p(void)
474 {
475   CLIST_FOR_EACH(struct pulse_sink *, s, pulse_sink_list)
476     if (!s->suspended)
477       return 0;
478   return 1;
479 }
480
481 static void do_update(struct main_timer *t)
482 {
483   DBG("## UPDATE in state %u", update_state);
484   timer_del(t);
485
486   // Nocturn dead?
487   if (!noct_is_ready())
488     {
489       DBG("## UPDATE: Nocturn is not ready");
490       update_state = US_OFFLINE;
491       return;
492     }
493   else if (update_state == US_OFFLINE)
494     {
495       DBG("## UPDATE: Going online");
496       update_state = US_ONLINE;
497     }
498
499   // Pulse dead?
500   if (!pulse_is_ready())
501     {
502       DBG("## UPDATE: Pulse is not online");
503       if (update_state != US_PULSE_DEAD)
504         {
505           update_state = US_PULSE_DEAD;
506           noct_clear();
507           for (int i=0; i<8; i++)
508             noct_set_button(i, 1);
509         }
510       return;
511     }
512   else if (update_state == US_PULSE_DEAD)
513     {
514       DBG("## UPDATE: Waking up from the dead");
515       update_state = US_ONLINE;
516       noct_clear();
517     }
518
519 #ifdef LOCAL_DEBUG
520   pulse_dump();
521 #endif
522
523   // Sleeping?
524   bool want_sleep = want_sleep_p();
525   if (!want_sleep)
526     last_touch_time = main_get_now();
527   timestamp_t since_touch = last_touch_time ? main_get_now() - last_touch_time : 0;
528   timestamp_t sleep_in = 5000;
529   if (since_touch >= sleep_in)
530     {
531       DBG("UPDATE: Sleeping");
532       if (update_state == US_ONLINE)
533         {
534           update_state = US_SLEEPING;
535           noct_clear();
536           noct_set_ring(8, RING_MODE_LEFT, 127);
537         }
538       return;
539     }
540   else
541     {
542       if (update_state == US_SLEEPING)
543         {
544           DBG("UPDATE: Waking up");
545           update_state = US_ONLINE;
546           noct_clear();
547         }
548       if (want_sleep)
549         {
550           timestamp_t t = sleep_in - since_touch + 10;
551           DBG("UPDATE: Scheduling sleep in %d ms", (int) t);
552           timer_add_rel(&update_timer, t);
553         }
554     }
555
556   // Everything normal
557   update_ring_from_sink(0, "ursarium");
558   update_ring_from_sink(1, "catarium");
559   update_clients();
560   update_default_sink();
561   update_mpd();
562 }
563
564 void schedule_update(void)
565 {
566   timer_add_rel(&update_timer, 10);
567 }
568
569 static bool prepare_notify(void)
570 {
571   if (!pulse_is_ready())
572     {
573       DBG("## NOTIFY: Pulse is not online");
574       return 0;
575     }
576
577   last_touch_time = main_get_now();
578   if (update_state == US_SLEEPING)
579     {
580       DBG("## NOTIFY: Scheduling wakeup");
581       schedule_update();
582     }
583
584   return 1;
585 }
586
587 void notify_rotary(int rotary, int delta)
588 {
589   if (!prepare_notify())
590     return;
591
592   switch (rotary)
593     {
594     case 0:
595       update_sink_from_rotary(delta, "ursarium");
596       break;
597     case 1:
598       update_sink_from_rotary(delta, "catarium");
599       break;
600     case 8:
601       update_sink_from_rotary(delta, "ursarium");
602       update_sink_from_rotary(delta, "catarium");
603       break;
604     default:
605       update_client_from_rotary(rotary, delta);
606     }
607 }
608
609 void notify_button(int button, int on)
610 {
611   if (!prepare_notify())
612     return;
613
614   switch (button)
615     {
616     case 0:
617       update_sink_mute_from_button(on, "ursarium");
618       break;
619     case 1:
620       update_sink_mute_from_button(on, "catarium");
621       break;
622     case 8:
623     case 9:
624     case 10:
625       update_default_sink_from_button(button, on);
626       break;
627     case 12:
628       update_mpd_from_button(button, on);
629       break;
630     default:
631       update_client_from_button(button, on);
632     }
633 }
634
635 void notify_touch(int rotary, int on UNUSED)
636 {
637   if (!prepare_notify())
638     return;
639
640   // Rotary touches switch meaning of LEDs, this is handled inside display updates
641   if (rotary >= 4 && rotary < 8)
642     schedule_update();
643 }
644
645 /*** Main entry point ***/
646
647 static void daemon_body(struct daemon_params *dp UNUSED)
648 {
649   main_init();
650   update_timer.handler = do_update;
651
652   noct_init();
653   pulse_init();
654   mpd_init();
655
656   msg(L_INFO, "Ursary daemon starting");
657   main_loop();
658 }
659
660 static int debug;
661
662 static struct opt_section options = {
663   OPT_ITEMS {
664     OPT_HELP("Control console for the Ursary"),
665     OPT_HELP(""),
666     OPT_HELP("Options:"),
667     OPT_HELP_OPTION,
668     OPT_BOOL('d', "debug", debug, 0, "\tEnable debugging mode (no fork etc.)"),
669     OPT_END
670   }
671 };
672
673 int main(int argc UNUSED, char **argv)
674 {
675   opt_parse(&options, argv+1);
676   unsetenv("DISPLAY");
677   unsetenv("HOME");
678
679   struct daemon_params dp = {
680     .flags = (debug ? DAEMON_FLAG_SIMULATE : 0),
681     .pid_file = "/run/ursaryd.pid",
682     .run_as_user = "ursary",
683   };
684   daemon_init(&dp);
685
686   if (debug)
687     {
688       log_init(argv[0]);
689     }
690   else
691     {
692       struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
693       log_set_default_stream(ls);
694     }
695
696   daemon_run(&dp, daemon_body);
697   return 0;
698 }