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