]> mj.ucw.cz Git - ursary.git/blob - ursaryd.c
Added udev rules
[ursary.git] / ursaryd.c
1 /*
2  *      The Ursary Audio Controls
3  *
4  *      (c) 2014 Martin Mares <mj@ucw.cz>
5  */
6
7 #define LOCAL_DEBUG
8
9 #include <ucw/lib.h>
10 #include <ucw/clists.h>
11 #include <ucw/mainloop.h>
12 #include <ucw/stkstring.h>
13
14 #include <stdio.h>
15 #include <string.h>
16 #include <stdlib.h>
17
18 #include "ursaryd.h"
19
20 /*
21  *      Map of all controls
22  *
23  *              rotary          red button      green button
24  *      0       sink Ursarium   mute            select as default (or assign to client selected by touch)
25  *      1       sink Catarium   mute            dtto
26  *      2       sink Compress   -               dtto
27  *      3       -               -               -
28  *      4       MPD             mute            play/pause/stop
29  *      5       Albireo         mute            -
30  *      6       Ogion           mute            -
31  *      7       Ursula          mute            -
32  *
33  *      center  all sinks
34  *      slider  -
35  */
36
37 /*** Sink controls ***/
38
39 static double volume_from_pa(pa_volume_t vol)
40 {
41   return (double) vol / PA_VOLUME_NORM;
42 }
43
44 static pa_volume_t volume_to_pa(double vol)
45 {
46   return vol * PA_VOLUME_NORM + 0.0001;
47 }
48
49 static void update_ring_from_sink(int ring, const char *sink_name)
50 {
51   struct pulse_sink *s = pulse_sink_by_name(sink_name);
52   if (!s)
53     {
54       noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
55       noct_set_button(ring, 0);
56       return;
57     }
58
59   if (s->mute)
60     {
61       noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
62       noct_set_button(ring, 1);
63       return;
64     }
65
66   double vol = CLAMP(volume_from_pa(s->volume), 0, 1);
67   noct_set_ring(ring, RING_MODE_LEFT, CLAMP((int)(0x7f * vol), 12, 0x7f));
68   noct_set_button(ring, 0);
69 }
70
71 static void update_sink_from_rotary(int delta, const char *sink_name)
72 {
73   struct pulse_sink *s = pulse_sink_by_name(sink_name);
74   if (!s)
75     return;
76
77   double vol = volume_from_pa(s->volume) + delta * 0.02;
78   pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, 1));
79   if (pavol == s->volume)
80     return;
81   pa_cvolume cvol;
82   pa_cvolume_set(&cvol, s->channels, pavol);
83
84   DBG("## Setting volume of sink %s to %d", s->name, cvol.values[0]);
85   pulse_sink_set_volume(s->idx, &cvol);
86 }
87
88 static void update_sink_mute_from_button(int on, const char *sink_name)
89 {
90   if (!on)
91     return;
92
93   struct pulse_sink *s = pulse_sink_by_name(sink_name);
94   if (!s)
95     return;
96
97   DBG("## Setting mute of sink %s to %d", s->name, !s->mute);
98   pulse_sink_set_mute(s->idx, !s->mute);
99 }
100
101 /*** Client controls ***/
102
103 struct client_map {
104   int rotary;
105   const char *client;
106   const char *host;
107   double range;
108 };
109
110 static struct client_map client_map[] = {
111   { 4, "Music Player Daemon",   "albireo",      1.5 },
112   { 5, NULL,                    "albireo",      1.5 },
113   { 6, NULL,                    "ogion",        1.5 },
114   { 7, NULL,                    "ursula",       1.5 },
115 };
116
117 #define NUM_CLIENTS ARRAY_SIZE(client_map)
118
119 struct client_state {
120   double volume;
121   bool have_muted[2];
122 };
123
124 static struct client_state client_state[NUM_CLIENTS];
125
126 static int find_client_by_rotary(int rotary)
127 {
128   uns i;
129   for (i=0; i < NUM_CLIENTS; i++)
130     if (client_map[i].rotary == rotary)
131       return i;
132   return -1;
133 }
134
135 static void calc_clients(void)
136 {
137   bzero(client_state, sizeof(client_state));
138
139   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
140     {
141       s->noct_client_idx = -1;
142
143       if (s->client_idx < 0 || s->sink_idx < 0)
144         continue;
145
146       struct pulse_client *c = pulse_client_by_idx(s->client_idx);
147       if (!c)
148         continue;
149
150       for (uns i=0; i < NUM_CLIENTS; i++)
151         {
152           struct client_map *cm = &client_map[i];
153           struct client_state *cs = &client_state[i];
154           if ((!cm->client || !strcmp(cm->client, c->name)) &&
155               (!cm->host || !strcmp(cm->host, c->host)))
156             {
157               // DBG("@@ Client #%d, sink input #%d -> rotary %d", s->client_idx, s->idx, cm->rotary);
158               s->noct_client_idx = i;
159               cs->volume = MAX(cs->volume, s->volume);
160               cs->have_muted[!!s->mute] = 1;
161               break;
162             }
163         }
164     }
165 }
166
167 static void update_clients(void)
168 {
169   calc_clients();
170
171   for (uns i=0; i < NUM_CLIENTS; i++)
172     {
173       struct client_map *cm = &client_map[i];
174       struct client_state *cs = &client_state[i];
175       if (!cs->have_muted[0] && !cs->have_muted[1])
176         {
177           noct_set_ring(cm->rotary, RING_MODE_LEFT, 0);
178           noct_set_button(cm->rotary, 0);
179         }
180       else if (!cs->have_muted[0])
181         {
182           noct_set_ring(cm->rotary, RING_MODE_SINGLE_ON, 0x7f);
183           noct_set_button(cm->rotary, 1);
184         }
185       else
186         {
187           double vol = CLAMP(volume_from_pa(cs->volume), 0, cm->range);
188           int val = 0x7f * vol / cm->range;
189           val = CLAMP(val, 12, 0x7f);
190           noct_set_ring(cm->rotary, RING_MODE_LEFT, val);
191           noct_set_button(cm->rotary, 0);
192         }
193     }
194 }
195
196 static void update_client_from_rotary(int rotary, int delta)
197 {
198   int i = find_client_by_rotary(rotary);
199   if (i < 0)
200     return;
201   struct client_map *cm = &client_map[i];
202   struct client_state *cs = &client_state[i];
203
204   calc_clients();
205   double vol = volume_from_pa(cs->volume) + delta*0.02;
206   pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, cm->range));
207
208   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
209     {
210       if (s->noct_client_idx == i && s->volume != pavol)
211         {
212           DBG("@@ Client #%d, sink input #%d: setting volume=%u", s->client_idx, s->idx, pavol);
213           pa_cvolume cvol;
214           pa_cvolume_set(&cvol, s->channels, pavol);
215           pulse_sink_input_set_volume(s->idx, &cvol);
216         }
217     }
218 }
219
220 static void update_client_from_button(int button, int on)
221 {
222   if (button >= 8 || !on)
223     return;
224
225   int i = find_client_by_rotary(button);
226   if (i < 0)
227     return;
228   struct client_state *cs = &client_state[i];
229
230   calc_clients();
231   if (!cs->have_muted[0] && !cs->have_muted[1])
232     return;
233   uns mute = !cs->have_muted[1];
234
235   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
236     {
237       if (s->noct_client_idx == i)
238         {
239           DBG("@@ Client #%d, sink input #%d: setting mute=%u", s->client_idx, s->idx, mute);
240           pulse_sink_input_set_mute(s->idx, mute);
241         }
242     }
243 }
244
245 static int find_touched_client(void)
246 {
247   int touched = -1;
248
249   for (uns i=0; i < NUM_CLIENTS; i++)
250     if (noct_rotary_touched[client_map[i].rotary])
251       {
252         if (touched >= 0)
253           return -1;
254         touched = i;
255       }
256   return touched;
257 }
258
259 /*** Default sink controls ***/
260
261 static const char *get_client_sink(int i)
262 {
263   const char *sink = NULL;
264
265   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
266     if (s->noct_client_idx == i)
267       {
268         struct pulse_sink *sk = (s->sink_idx >= 0) ? pulse_sink_by_idx(s->sink_idx) : NULL;
269         const char *ss = sk ? sk->name : NULL;
270         if (!sink)
271           sink = ss;
272         else if (strcmp(sink, ss))
273           sink = "?";
274       }
275   return sink ? : "?";
276 }
277
278 static void update_default_sink(void)
279 {
280   int i = find_touched_client();
281   const char *sink;
282   if (i >= 0)
283     sink = get_client_sink(i);
284   else
285     sink = pulse_default_sink_name ? : "?";
286
287   if (!strcmp(sink, "ursarium"))
288     {
289       noct_set_button(8, 1);
290       noct_set_button(9, 0);
291       noct_set_button(10, 0);
292     }
293   else if (!strcmp(sink, "catarium"))
294     {
295       noct_set_button(8, 0);
296       noct_set_button(9, 1);
297       noct_set_button(10, 0);
298     }
299   else if (!strcmp(sink, "compress"))
300     {
301       noct_set_button(8, 0);
302       noct_set_button(9, 0);
303       noct_set_button(10, 1);
304     }
305   else
306     {
307       noct_set_button(8, 0);
308       noct_set_button(9, 0);
309       noct_set_button(10, 0);
310     }
311 }
312
313 static void update_default_sink_from_button(int button, int on)
314 {
315   if (!on)
316     return;
317
318   int i = find_touched_client();
319   const char *sink;
320   if (i >= 0)
321     sink = get_client_sink(i);
322   else
323     sink = pulse_default_sink_name ? : "?";
324
325   const char *switch_to = NULL;
326   if (button == 8)
327     {
328       if (!strcmp(sink, "ursarium"))
329         switch_to = "burrow";
330       else
331         switch_to = "ursarium";
332     }
333   else if (button == 9)
334     {
335       if (!strcmp(sink, "catarium"))
336         switch_to = "burrow";
337       else
338         switch_to = "catarium";
339     }
340   else if (button == 10)
341     {
342       if (!strcmp(sink, "compress"))
343         switch_to = "burrow";
344       else
345         switch_to = "compress";
346     }
347
348   if (!switch_to)
349     return;
350
351   if (i >= 0)
352     {
353       struct pulse_sink *sk = pulse_sink_by_name(switch_to);
354       if (!sk)
355         return;
356
357       CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
358         if (s->noct_client_idx == i)
359           {
360             DBG("Moving input #%d to sink #%d", s->idx, sk->idx);
361             pulse_sink_input_move(s->idx, sk->idx);
362           }
363     }
364   else
365     {
366       DBG("Switching default sink to %s", switch_to);
367       pulse_server_set_default_sink(switch_to);
368     }
369 }
370
371 /*** MPD controls ***/
372
373 static bool mpd_flash_state;
374
375 static void mpd_flash_timeout(struct main_timer *t)
376 {
377   mpd_flash_state ^= 1;
378   noct_set_button(12, mpd_flash_state);
379   timer_add_rel(t, 500);
380 }
381
382 static struct main_timer mpd_flash_timer = {
383   .handler = mpd_flash_timeout,
384 };
385
386 static void update_mpd(void)
387 {
388   const char *state = mpd_get_player_state();
389   if (!strcmp(state, "play"))
390     {
391       noct_set_button(12, 1);
392       timer_del(&mpd_flash_timer);
393     }
394   else if (!strcmp(state, "pause"))
395     {
396       if (!timer_is_active(&mpd_flash_timer))
397         {
398           mpd_flash_state = 1;
399           mpd_flash_timeout(&mpd_flash_timer);
400         }
401     }
402   else
403     {
404       noct_set_button(12, 0);
405       timer_del(&mpd_flash_timer);
406     }
407 }
408
409 static void mpd_button_timeout(struct main_timer *t)
410 {
411   DBG("MPD stop");
412   timer_del(t);
413   mpd_stop();
414 }
415
416 static void update_mpd_from_button(int button UNUSED, int on)
417 {
418   static struct main_timer mpd_button_timer = {
419     .handler = mpd_button_timeout,
420   };
421
422   const char *state = mpd_get_player_state();
423
424   if (!on)
425     {
426       if (timer_is_active(&mpd_button_timer))
427         {
428           timer_del(&mpd_button_timer);
429           if (!strcmp(state, "play"))
430             {
431               DBG("MPD pause");
432               mpd_pause(1);
433             }
434           else if (!strcmp(state, "pause"))
435             {
436               DBG("MPD resume");
437               mpd_pause(0);
438             }
439         }
440       return;
441     }
442
443   if (!strcmp(state, "stop"))
444     {
445       DBG("MPD play");
446       mpd_play();
447     }
448   else
449     {
450       DBG("MPD starting button timer");
451       timer_add_rel(&mpd_button_timer, 1000);
452     }
453 }
454
455 /*** Main update routines ***/
456
457 static struct main_timer update_timer;
458 static timestamp_t last_touch_time;
459
460 enum update_state {
461   US_OFFLINE,
462   US_ONLINE,
463   US_SLEEPING,
464   US_PULSE_DEAD,
465 };
466
467 static enum update_state update_state;
468
469 static bool want_sleep_p(void)
470 {
471   CLIST_FOR_EACH(struct pulse_sink *, s, pulse_sink_list)
472     if (!s->suspended)
473       return 0;
474   return 1;
475 }
476
477 static void do_update(struct main_timer *t)
478 {
479   DBG("## UPDATE in state %u", update_state);
480   timer_del(t);
481
482   // Nocturn dead?
483   if (!noct_is_ready())
484     {
485       DBG("## UPDATE: Nocturn is not ready");
486       update_state = US_OFFLINE;
487       return;
488     }
489   else if (update_state == US_OFFLINE)
490     {
491       DBG("## UPDATE: Going online");
492       update_state = US_ONLINE;
493     }
494
495   // Pulse dead?
496   if (!pulse_is_ready())
497     {
498       DBG("## UPDATE: Pulse is not online");
499       if (update_state != US_PULSE_DEAD)
500         {
501           update_state = US_PULSE_DEAD;
502           noct_clear();
503           for (int i=0; i<8; i++)
504             noct_set_button(i, 1);
505         }
506       return;
507     }
508   else if (update_state == US_PULSE_DEAD)
509     {
510       DBG("## UPDATE: Waking up from the dead");
511       update_state = US_ONLINE;
512       noct_clear();
513     }
514
515 #ifdef LOCAL_DEBUG
516   pulse_dump();
517 #endif
518
519   // Sleeping?
520   bool want_sleep = want_sleep_p();
521   if (!want_sleep)
522     last_touch_time = main_get_now();
523   timestamp_t since_touch = last_touch_time ? main_get_now() - last_touch_time : 0;
524   timestamp_t sleep_in = 5000;
525   if (since_touch >= sleep_in)
526     {
527       DBG("UPDATE: Sleeping");
528       if (update_state == US_ONLINE)
529         {
530           update_state = US_SLEEPING;
531           noct_clear();
532           noct_set_ring(8, RING_MODE_LEFT, 127);
533         }
534       return;
535     }
536   else
537     {
538       if (update_state == US_SLEEPING)
539         {
540           DBG("UPDATE: Waking up");
541           update_state = US_ONLINE;
542           noct_clear();
543         }
544       if (want_sleep)
545         {
546           timestamp_t t = sleep_in - since_touch + 10;
547           DBG("UPDATE: Scheduling sleep in %d ms", (int) t);
548           timer_add_rel(&update_timer, t);
549         }
550     }
551
552   // Everything normal
553   update_ring_from_sink(0, "ursarium");
554   update_ring_from_sink(1, "catarium");
555   update_clients();
556   update_default_sink();
557   update_mpd();
558 }
559
560 void schedule_update(void)
561 {
562   timer_add_rel(&update_timer, 10);
563 }
564
565 static bool prepare_notify(void)
566 {
567   if (!pulse_is_ready())
568     {
569       DBG("## NOTIFY: Pulse is not online");
570       return 0;
571     }
572
573   last_touch_time = main_get_now();
574   if (update_state == US_SLEEPING)
575     {
576       DBG("## NOTIFY: Scheduling wakeup");
577       schedule_update();
578     }
579
580   return 1;
581 }
582
583 void notify_rotary(int rotary, int delta)
584 {
585   if (!prepare_notify())
586     return;
587
588   switch (rotary)
589     {
590     case 0:
591       update_sink_from_rotary(delta, "ursarium");
592       break;
593     case 1:
594       update_sink_from_rotary(delta, "catarium");
595       break;
596     case 8:
597       update_sink_from_rotary(delta, "ursarium");
598       update_sink_from_rotary(delta, "catarium");
599       break;
600     default:
601       update_client_from_rotary(rotary, delta);
602     }
603 }
604
605 void notify_button(int button, int on)
606 {
607   if (!prepare_notify())
608     return;
609
610   switch (button)
611     {
612     case 0:
613       update_sink_mute_from_button(on, "ursarium");
614       break;
615     case 1:
616       update_sink_mute_from_button(on, "catarium");
617       break;
618     case 8:
619     case 9:
620     case 10:
621       update_default_sink_from_button(button, on);
622       break;
623     case 12:
624       update_mpd_from_button(button, on);
625       break;
626     default:
627       update_client_from_button(button, on);
628     }
629 }
630
631 void notify_touch(int rotary, int on UNUSED)
632 {
633   if (!prepare_notify())
634     return;
635
636   // Rotary touches switch meaning of LEDs, this is handled inside display updates
637   if (rotary >= 4 && rotary < 8)
638     schedule_update();
639 }
640
641 /*** Main entry point ***/
642
643 int main(int argc UNUSED, char **argv)
644 {
645   log_init(argv[0]);
646   main_init();
647   update_timer.handler = do_update;
648
649   noct_init();
650   pulse_init();
651   mpd_init();
652
653   msg(L_DEBUG, "Entering main loop");
654   main_loop();
655
656   return 0;
657 }