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