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