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