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