]> mj.ucw.cz Git - ursary.git/blob - ursaryd.c
ca3f0617de0ccc693188f8970ccc98ad0d00ab49
[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 #if 0   // Not used at the moment
292
293 static int find_touched_client(void)
294 {
295   int touched = -1;
296
297   for (uns i=0; i < NUM_GROUPS; i++)
298     if (group_config[i].enabled && noct_rotary_touched[i])
299       {
300         if (touched >= 0)
301           return -1;
302         touched = i;
303       }
304   return touched;
305 }
306
307 #endif
308
309 /*** Default sink controls ***/
310
311 #if 0   // Not mapped to any button at the moment
312
313 static const char *get_client_sink(int i)
314 {
315   const char *sink = NULL;
316
317   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
318     if (s->noct_group_idx == i)
319       {
320         struct pulse_sink *sk = (s->sink_idx >= 0) ? pulse_sink_by_idx(s->sink_idx) : NULL;
321         const char *ss = sk ? sk->name : NULL;
322         if (!sink)
323           sink = ss;
324         else if (strcmp(sink, ss))
325           sink = "?";
326       }
327   return sink ? : "?";
328 }
329
330 static void update_default_sink(void)
331 {
332   int i = find_touched_client();
333   const char *sink;
334   if (i >= 0)
335     sink = get_client_sink(i);
336   else
337     sink = pulse_default_sink_name ? : "?";
338
339   if (!strcmp(sink, "ursarium"))
340     {
341       noct_set_button(8, 1);
342       noct_set_button(9, 0);
343       noct_set_button(10, 0);
344     }
345   else if (!strcmp(sink, "catarium"))
346     {
347       noct_set_button(8, 0);
348       noct_set_button(9, 1);
349       noct_set_button(10, 0);
350     }
351   else if (!strcmp(sink, "compress"))
352     {
353       noct_set_button(8, 0);
354       noct_set_button(9, 0);
355       noct_set_button(10, 1);
356     }
357   else
358     {
359       noct_set_button(8, 0);
360       noct_set_button(9, 0);
361       noct_set_button(10, 0);
362     }
363 }
364
365 static void update_default_sink_from_button(int button, int on)
366 {
367   if (!on)
368     return;
369
370   int i = find_touched_client();
371   const char *sink;
372   if (i >= 0)
373     sink = get_client_sink(i);
374   else
375     sink = pulse_default_sink_name ? : "?";
376
377   const char *switch_to = NULL;
378   if (button == 8)
379     {
380       if (!strcmp(sink, "ursarium"))
381         switch_to = "burrow";
382       else
383         switch_to = "ursarium";
384     }
385   else if (button == 9)
386     {
387       if (!strcmp(sink, "catarium"))
388         switch_to = "burrow";
389       else
390         switch_to = "catarium";
391     }
392   else if (button == 10)
393     {
394       if (!strcmp(sink, "compress"))
395         switch_to = "burrow";
396       else
397         switch_to = "compress";
398     }
399
400   if (!switch_to)
401     return;
402
403   if (i >= 0)
404     {
405       struct pulse_sink *sk = pulse_sink_by_name(switch_to);
406       if (!sk)
407         return;
408
409       CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
410         if (s->noct_group_idx == i)
411           {
412             DBG("Moving input #%d to sink #%d", s->idx, sk->idx);
413             pulse_sink_input_move(s->idx, sk->idx);
414           }
415     }
416   else
417     {
418       DBG("Switching default sink to %s", switch_to);
419       pulse_server_set_default_sink(switch_to);
420     }
421 }
422
423 #endif
424
425 /*** MPD controls ***/
426
427 static bool mpd_flash_state;
428
429 static void mpd_flash_timeout(struct main_timer *t)
430 {
431   mpd_flash_state ^= 1;
432   noct_set_button(12, mpd_flash_state);
433   timer_add_rel(t, 500);
434 }
435
436 static struct main_timer mpd_flash_timer = {
437   .handler = mpd_flash_timeout,
438 };
439
440 static void update_mpd(void)
441 {
442   const char *state = mpd_get_player_state();
443   if (!strcmp(state, "play"))
444     {
445       noct_set_button(12, 1);
446       timer_del(&mpd_flash_timer);
447     }
448   else if (!strcmp(state, "pause"))
449     {
450       if (!timer_is_active(&mpd_flash_timer))
451         {
452           mpd_flash_state = 1;
453           mpd_flash_timeout(&mpd_flash_timer);
454         }
455     }
456   else
457     {
458       noct_set_button(12, 0);
459       timer_del(&mpd_flash_timer);
460     }
461 }
462
463 static void mpd_button_timeout(struct main_timer *t)
464 {
465   DBG("MPD stop");
466   timer_del(t);
467   mpd_stop();
468 }
469
470 static void update_mpd_from_button(int button UNUSED, int on)
471 {
472   static struct main_timer mpd_button_timer = {
473     .handler = mpd_button_timeout,
474   };
475
476   const char *state = mpd_get_player_state();
477
478   if (!on)
479     {
480       if (timer_is_active(&mpd_button_timer))
481         {
482           timer_del(&mpd_button_timer);
483           if (!strcmp(state, "play"))
484             {
485               DBG("MPD pause");
486               mpd_pause(1);
487             }
488           else if (!strcmp(state, "pause"))
489             {
490               DBG("MPD resume");
491               mpd_pause(0);
492             }
493         }
494       return;
495     }
496
497   if (!strcmp(state, "stop"))
498     {
499       DBG("MPD play");
500       mpd_play();
501     }
502   else
503     {
504       DBG("MPD starting button timer");
505       timer_add_rel(&mpd_button_timer, 1000);
506     }
507 }
508
509 /*** Main update routines ***/
510
511 static struct main_timer update_timer;
512 static timestamp_t last_touch_time;
513
514 enum update_state {
515   US_OFFLINE,
516   US_ONLINE,
517   US_SLEEPING,
518   US_PULSE_DEAD,
519 };
520
521 static enum update_state update_state;
522
523 static bool want_sleep_p(void)
524 {
525   CLIST_FOR_EACH(struct pulse_sink *, s, pulse_sink_list)
526     if (!s->suspended)
527       return 0;
528   return 1;
529 }
530
531 static void do_update(struct main_timer *t)
532 {
533   DBG("## UPDATE in state %u", update_state);
534   timer_del(t);
535
536   // Nocturn dead?
537   if (!noct_is_ready())
538     {
539       DBG("## UPDATE: Nocturn is not ready");
540       update_state = US_OFFLINE;
541       return;
542     }
543   else if (update_state == US_OFFLINE)
544     {
545       DBG("## UPDATE: Going online");
546       update_state = US_ONLINE;
547     }
548
549   // Pulse dead?
550   if (!pulse_is_ready())
551     {
552       DBG("## UPDATE: Pulse is not online");
553       if (update_state != US_PULSE_DEAD)
554         {
555           update_state = US_PULSE_DEAD;
556           noct_clear();
557           for (int i=0; i<8; i++)
558             noct_set_button(i, 1);
559         }
560       return;
561     }
562   else if (update_state == US_PULSE_DEAD)
563     {
564       DBG("## UPDATE: Waking up from the dead");
565       update_state = US_ONLINE;
566       noct_clear();
567     }
568
569 #ifdef LOCAL_DEBUG
570   pulse_dump();
571 #endif
572
573   // Sleeping?
574   bool want_sleep = want_sleep_p();
575   if (!want_sleep)
576     last_touch_time = main_get_now();
577   timestamp_t since_touch = last_touch_time ? main_get_now() - last_touch_time : 0;
578   timestamp_t sleep_in = 5000;
579   if (since_touch >= sleep_in)
580     {
581       DBG("UPDATE: Sleeping");
582       if (update_state == US_ONLINE)
583         {
584           update_state = US_SLEEPING;
585           noct_clear();
586           noct_set_ring(8, RING_MODE_LEFT, 127);
587         }
588       return;
589     }
590   else
591     {
592       if (update_state == US_SLEEPING)
593         {
594           DBG("UPDATE: Waking up");
595           update_state = US_ONLINE;
596           noct_clear();
597         }
598       if (want_sleep)
599         {
600           timestamp_t t = sleep_in - since_touch + 10;
601           DBG("UPDATE: Scheduling sleep in %d ms", (int) t);
602           timer_add_rel(&update_timer, t);
603         }
604     }
605
606   // Everything normal
607   update_ring_from_sink(0, "alsa_output.brum.analog-stereo");
608   update_button_from_port(8, "alsa_output.brum.analog-stereo", "analog-output-headphones");
609   update_groups();
610 #if 0
611   update_default_sink();
612 #endif
613   update_mpd();
614 }
615
616 void schedule_update(void)
617 {
618   timer_add_rel(&update_timer, 10);
619 }
620
621 static bool prepare_notify(void)
622 {
623   if (!pulse_is_ready())
624     {
625       DBG("## NOTIFY: Pulse is not online");
626       return 0;
627     }
628
629   last_touch_time = main_get_now();
630   if (update_state == US_SLEEPING)
631     {
632       DBG("## NOTIFY: Scheduling wakeup");
633       schedule_update();
634     }
635
636   return 1;
637 }
638
639 void notify_rotary(int rotary, int delta)
640 {
641   if (!prepare_notify())
642     return;
643
644   switch (rotary)
645     {
646     case 0:
647       update_sink_from_rotary(delta, "alsa_output.brum.analog-stereo");
648       break;
649     default:
650       update_group_from_rotary(rotary, delta);
651     }
652 }
653
654 void notify_button(int button, int on)
655 {
656   if (!prepare_notify())
657     return;
658
659   switch (button)
660     {
661     case 0:
662       update_sink_mute_from_button(on, "alsa_output.brum.analog-stereo");
663       break;
664     case 8:
665       update_port_from_button(on, "alsa_output.brum.analog-stereo", "analog-output-lineout", "analog-output-headphones");
666       break;
667 #if 0
668     case 9:
669     case 10:
670       update_default_sink_from_button(button, on);
671       break;
672 #endif
673     case 12:
674       update_mpd_from_button(button, on);
675       break;
676     case 13:
677       if (on)
678         mpd_stop();
679       break;
680     case 14:
681       if (on)
682         mpd_prev();
683       break;
684     case 15:
685       if (on)
686         mpd_next();
687       break;
688     default:
689       update_group_from_button(button, on);
690     }
691 }
692
693 void notify_touch(int rotary, int on UNUSED)
694 {
695   if (!prepare_notify())
696     return;
697
698   // Rotary touches switch meaning of LEDs, this is handled inside display updates
699   if (rotary >= 4 && rotary < 8)
700     schedule_update();
701 }
702
703 /*** Main entry point ***/
704
705 static int debug;
706 static int no_fork;
707
708 static struct opt_section options = {
709   OPT_ITEMS {
710     OPT_HELP("Control console for the Ursary"),
711     OPT_HELP(""),
712     OPT_HELP("Options:"),
713     OPT_HELP_OPTION,
714     OPT_BOOL('d', "debug", debug, 0, "\tEnable debugging mode (no fork etc.)"),
715     OPT_BOOL(0, "no-fork", no_fork, 0, "\tDo not fork\n"),
716     OPT_END
717   }
718 };
719
720 static void sigterm_handler(struct main_signal *ms UNUSED)
721 {
722   main_shut_down();
723 }
724
725 static void daemon_body(struct daemon_params *dp)
726 {
727   main_init();
728   update_timer.handler = do_update;
729
730   noct_init();
731   pulse_init();
732   mpd_init();
733
734   static struct main_signal term_sig = {
735     .signum = SIGTERM,
736     .handler = sigterm_handler,
737   };
738   signal_add(&term_sig);
739
740   msg(L_INFO, "Ursary daemon starting");
741   main_loop();
742   msg(L_INFO, "Ursary daemon shut down");
743   daemon_exit(dp);
744 }
745
746 int main(int argc UNUSED, char **argv)
747 {
748   opt_parse(&options, argv+1);
749   unsetenv("DISPLAY");
750   unsetenv("HOME");
751
752   struct daemon_params dp = {
753     .flags = ((debug || no_fork) ? DAEMON_FLAG_SIMULATE : 0),
754     .pid_file = "/run/ursaryd.pid",
755     .run_as_user = "ursary",
756   };
757   daemon_init(&dp);
758
759   log_init(argv[0]);
760   if (!debug)
761     {
762       struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
763       ls->levels = ~(1U << L_DEBUG);
764       log_set_default_stream(ls);
765     }
766
767   daemon_run(&dp, daemon_body);
768   return 0;
769 }