]> mj.ucw.cz Git - misc.git/blob - ursaryd/ut.c
5e3fab44ceeadfef11568ae60fd3abf5b0cbb273
[misc.git] / ursaryd / ut.c
1 #define LOCAL_DEBUG
2
3 #include <ucw/lib.h>
4 #include <ucw/clists.h>
5 #include <ucw/mainloop.h>
6 #include <ucw/stkstring.h>
7
8 #include <stdio.h>
9 #include <string.h>
10 #include <stdlib.h>
11
12 #include <pulse/pulseaudio.h>
13
14 #include "ursaryd.h"
15
16 /*
17  *  Interface to PulseAudio
18  */
19
20 static pa_context *pulse_ctx;
21
22 static void pulse_dump(void);
23 static void pulse_schedule_update(void);
24
25 enum pulse_state {
26   PS_OFFLINE,
27   PS_SUBSCRIBE,
28   PS_GET_CLIENTS,
29   PS_GET_SINKS,
30   PS_GET_SINK_INPUTS,
31   PS_ONLINE,
32 };
33
34 static enum pulse_state pulse_state;
35 #define PULSE_STATE(s) do { pulse_state = s; DBG("Pulse: " #s); } while (0)
36
37 // Tracking of currently running asynchronous operations
38 struct pulse_op {
39   cnode n;
40   pa_operation *o;
41   bool is_init;
42 };
43
44 static clist pulse_op_list;
45
46 static struct pulse_op *pulse_op_new(void)
47 {
48   struct pulse_op *op = xmalloc_zero(sizeof(*op));
49   clist_add_tail(&pulse_op_list, &op->n);
50   return op;
51 }
52
53 static void pulse_op_done(struct pulse_op *op)
54 {
55   if (op->o)
56     pa_operation_unref(op->o);
57   clist_remove(&op->n);
58   xfree(op);
59 }
60
61 static void pulse_op_cancel_all(void)
62 {
63   struct pulse_op *op;
64   while (op = (struct pulse_op *) clist_head(&pulse_op_list))
65     {
66       DBG("Pulse: Cancelling pending operation");
67       pa_operation_cancel(op->o);
68       pulse_op_done(op);
69     }
70 }
71
72 #define PULSE_ASYNC_RUN(name, ...) do { struct pulse_op *_op = pulse_op_new(); _op->o = name(__VA_ARGS__, _op); } while (0)
73 #define PULSE_ASYNC_INIT_RUN(name, ...) do { struct pulse_op *_op = pulse_op_new(); _op->is_init = 1; _op->o = name(__VA_ARGS__, _op); } while (0)
74
75 static void pulse_dump_proplist(pa_proplist *pl UNUSED)
76 {
77 #ifdef LOCAL_DEBUG
78   void *iterator = NULL;
79   const char *key;
80
81   while (key = pa_proplist_iterate(pl, &iterator))
82     {
83       const char *val = pa_proplist_gets(pl, key);
84       DBG("   %s = %s", key, val);
85     }
86 #endif
87 }
88
89 struct pulse_sink_input {
90   int idx;
91   char *name;
92   int client_idx;
93   int sink_idx;
94   uns volume;
95   uns mute;
96 };
97
98 #define HASH_NODE struct pulse_sink_input
99 #define HASH_PREFIX(x) pulse_sink_input_##x
100 #define HASH_KEY_ATOMIC idx
101 // #define HASH_WANT_CLEANUP
102 #define HASH_WANT_LOOKUP
103 #define HASH_WANT_REMOVE
104 #define HASH_ZERO_FILL
105 #include <ucw/hashtable.h>
106
107 #define SET_STRING(_field, _val) do { if (!_field || strcmp(_field, _val)) { xfree(_field); _field = xstrdup(_val); } } while (0)
108
109 static void pulse_sink_input_cb(pa_context *ctx UNUSED, const pa_sink_input_info *i, int eol, void *userdata)
110 {
111   struct pulse_op *op = userdata;
112
113   if (eol)
114     {
115       if (op->is_init)
116         {
117           PULSE_STATE(PS_ONLINE);
118           pulse_schedule_update();
119         }
120       pulse_op_done(op);
121       return;
122     }
123
124   DBG("Pulse: SINK INPUT #%u: %s client=%d sink=%d has_vol=%d vol_rw=%d volume=%u mute=%d",
125     i->index, i->name, i->client, i->sink, i->has_volume, i->volume_writable, i->volume.values[0], i->mute);
126   pulse_dump_proplist(i->proplist);
127
128   struct pulse_sink_input *s = pulse_sink_input_lookup(i->index);
129   SET_STRING(s->name, i->name);
130   s->client_idx = i->client;
131   s->sink_idx = i->sink;
132   s->volume = pa_cvolume_avg(&i->volume);
133   s->mute = i->mute;
134   pulse_schedule_update();
135 }
136
137 static void pulse_sink_input_gone(int idx)
138 {
139   DBG("Pulse: REMOVE SINK INPUT #%d", idx);
140   struct pulse_sink_input *s = pulse_sink_input_lookup(idx);
141   pulse_sink_input_remove(s);
142   pulse_schedule_update();
143 }
144
145 struct pulse_sink {
146   int idx;
147   char *name;
148   uns volume;
149   uns base_volume;
150   int mute;
151 };
152
153 #define HASH_NODE struct pulse_sink
154 #define HASH_PREFIX(x) pulse_sink_##x
155 #define HASH_KEY_ATOMIC idx
156 // #define HASH_WANT_CLEANUP
157 #define HASH_WANT_LOOKUP
158 #define HASH_WANT_REMOVE
159 #define HASH_ZERO_FILL
160 #include <ucw/hashtable.h>
161
162 static void pulse_sink_cb(pa_context *ctx, const pa_sink_info *i, int eol, void *userdata)
163 {
164   struct pulse_op *op = userdata;
165
166   if (eol)
167     {
168       if (op->is_init)
169         {
170           PULSE_STATE(PS_GET_SINK_INPUTS);
171           PULSE_ASYNC_INIT_RUN(pa_context_get_sink_input_info_list, ctx, pulse_sink_input_cb);
172         }
173       pulse_op_done(op);
174       return;
175     }
176
177   DBG("Pulse: SINK #%u: %s (%s) flags=%08x volume=%u mute=%d base_vol=%u state=%u",
178     i->index, i->name, i->description, i->flags, i->volume.values[0], i->mute, i->base_volume, i->state);
179   pulse_dump_proplist(i->proplist);
180
181   struct pulse_sink *s = pulse_sink_lookup(i->index);
182   SET_STRING(s->name, i->name);
183   s->volume = pa_cvolume_avg(&i->volume);
184   s->base_volume = i->base_volume;
185   s->mute = i->mute;
186   pulse_schedule_update();
187 }
188
189 static void pulse_sink_gone(int idx)
190 {
191   DBG("Pulse: REMOVE SINK #%d", idx);
192   struct pulse_sink *s = pulse_sink_lookup(idx);
193   pulse_sink_remove(s);
194   pulse_schedule_update();
195 }
196
197 struct pulse_client {
198   int idx;
199   char *name;
200   char *host;
201 };
202
203 #define HASH_NODE struct pulse_client
204 #define HASH_PREFIX(x) pulse_client_##x
205 #define HASH_KEY_ATOMIC idx
206 // #define HASH_WANT_CLEANUP
207 #define HASH_WANT_LOOKUP
208 #define HASH_WANT_REMOVE
209 #define HASH_ZERO_FILL
210 #include <ucw/hashtable.h>
211
212 static void pulse_client_cb(pa_context *ctx, const pa_client_info *i, int eol, void *userdata)
213 {
214   struct pulse_op *op = userdata;
215
216   if (eol)
217     {
218       if (op->is_init)
219         {
220           PULSE_STATE(PS_GET_SINKS);
221           PULSE_ASYNC_INIT_RUN(pa_context_get_sink_info_list, ctx, pulse_sink_cb);
222         }
223       pulse_op_done(op);
224       return;
225     }
226
227   char *host = stk_strdup(pa_proplist_gets(i->proplist, "application.process.host") ? : "?");
228   DBG("Pulse: CLIENT #%u: %s mod=%u drv=%s host=%s",
229     i->index, i->name, i->owner_module, i->driver, host);
230   pulse_dump_proplist(i->proplist);
231
232   struct pulse_client *c = pulse_client_lookup(i->index);
233   SET_STRING(c->name, i->name);
234   SET_STRING(c->host, host);
235   pulse_schedule_update();
236 }
237
238 static void pulse_client_gone(int idx)
239 {
240   DBG("Pulse: REMOVE CLIENT #%d", idx);
241   struct pulse_client *c = pulse_client_lookup(idx);
242   pulse_client_remove(c);
243   pulse_schedule_update();
244 }
245
246 static void pulse_subscribe_done_cb(pa_context *ctx, int success, void *userdata)
247 {
248   pulse_op_done(userdata);
249
250   if (!success)
251     msg(L_ERROR, "pa_context_subscribe failed: success=%d", success);
252
253   PULSE_STATE(PS_GET_CLIENTS);
254   PULSE_ASYNC_INIT_RUN(pa_context_get_client_info_list, ctx, pulse_client_cb);
255 }
256
257 static void pulse_event_cb(pa_context *ctx, pa_subscription_event_type_t type, uint32_t idx, void *userdata UNUSED)
258 {
259   DBG("Pulse: SUBSCRIBE EVENT type=%08x idx=%u", type, idx);
260
261   uns object = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
262   uns action = type & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
263   switch (object)
264     {
265     case PA_SUBSCRIPTION_EVENT_CLIENT:
266       if (action == PA_SUBSCRIPTION_EVENT_NEW || action == PA_SUBSCRIPTION_EVENT_CHANGE)
267         PULSE_ASYNC_RUN(pa_context_get_client_info, ctx, idx, pulse_client_cb);
268       else if (action == PA_SUBSCRIPTION_EVENT_REMOVE)
269         pulse_client_gone(idx);
270       break;
271     case PA_SUBSCRIPTION_EVENT_SINK:
272       if (action == PA_SUBSCRIPTION_EVENT_NEW || action == PA_SUBSCRIPTION_EVENT_CHANGE)
273         PULSE_ASYNC_RUN(pa_context_get_sink_info_by_index, ctx, idx, pulse_sink_cb);
274       else if (action == PA_SUBSCRIPTION_EVENT_REMOVE)
275         pulse_sink_gone(idx);
276       break;
277     case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
278       if (action == PA_SUBSCRIPTION_EVENT_NEW || action == PA_SUBSCRIPTION_EVENT_CHANGE)
279         PULSE_ASYNC_RUN(pa_context_get_sink_input_info, ctx, idx, pulse_sink_input_cb);
280       else if (action == PA_SUBSCRIPTION_EVENT_REMOVE)
281         pulse_sink_input_gone(idx);
282       break;
283     }
284 }
285
286 static void pulse_state_cb(pa_context *ctx, void *userdata UNUSED)
287 {
288   int state = pa_context_get_state(ctx);
289   DBG("Pulse: State callback, new state = %d", state);
290   if (state == PA_CONTEXT_READY)
291     {
292       if (pulse_state == PS_OFFLINE)
293         {
294           PULSE_STATE(PS_SUBSCRIBE);
295           pa_context_set_subscribe_callback(ctx, pulse_event_cb, NULL);
296           PULSE_ASYNC_INIT_RUN(pa_context_subscribe, ctx, PA_SUBSCRIPTION_MASK_ALL, pulse_subscribe_done_cb);
297         }
298     }
299   else
300     {
301       if (pulse_state != PS_OFFLINE)
302         {
303           PULSE_STATE(PS_OFFLINE);
304           pulse_op_cancel_all();
305         }
306     }
307 }
308
309 static void pulse_dump(void)
310 {
311   HASH_FOR_ALL(pulse_client, c)
312     {
313       DBG("## Client #%d: %s host=%s", c->idx, c->name, c->host);
314     }
315   HASH_END_FOR;
316
317   HASH_FOR_ALL(pulse_sink, s)
318     {
319       DBG("## Sink #%d: %s volume=%u base_vol=%u mute=%u",
320         s->idx, s->name, s->volume, s->base_volume, s->mute);
321     }
322   HASH_END_FOR;
323
324   HASH_FOR_ALL(pulse_sink_input, s)
325     {
326       DBG("## Sink input #%d: %s client=%d sink=%d volume=%u mute=%u",
327         s->idx, s->name, s->client_idx, s->sink_idx, s->volume, s->mute);
328     }
329   HASH_END_FOR;
330 }
331
332 static struct main_timer pulse_update_timer;
333
334 static void pulse_update(struct main_timer *t)
335 {
336   timer_del(t);
337   DBG("## UPDATE");
338   pulse_dump();
339 }
340
341 static void pulse_schedule_update(void)
342 {
343   timer_add_rel(&pulse_update_timer, 500);
344 }
345
346 static void pulse_init(void)
347 {
348   pmain_init();
349   clist_init(&pulse_op_list);
350   pulse_client_init();
351   pulse_sink_init();
352   pulse_sink_input_init();
353   pulse_update_timer.handler = pulse_update;
354
355   pulse_ctx = pa_context_new(&pmain_api, "ursaryd");
356   pa_context_set_state_callback(pulse_ctx, pulse_state_cb, NULL);
357   pa_context_connect(pulse_ctx, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
358 }
359
360 int main(int argc UNUSED, char **argv)
361 {
362   log_init(argv[0]);
363   main_init();
364
365   // msg(L_INFO, "Initializing Nocturn");
366   // noct_init();
367
368   msg(L_INFO, "Initializing PulseAudio");
369   pulse_init();
370
371   msg(L_INFO, "Entering main loop");
372   main_loop();
373
374   return 0;
375 }