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