1 #include "e.h"
2 #include "emix.h"
3
4 #include <pulse/pulseaudio.h>
5
6 #undef ERR
7 #undef DBG
8 #undef WRN
9 #define ERR(...) EINA_LOG_ERR(__VA_ARGS__)
10 #define DBG(...) EINA_LOG_DBG(__VA_ARGS__)
11 #define WRN(...) EINA_LOG_WARN(__VA_ARGS__)
12
13 #define PA_VOLUME_TO_INT(_vol) \
14 (((_vol * EMIX_VOLUME_BARRIER) / \
15 (double)PA_VOLUME_NORM) + 0.5)
16 #define INT_TO_PA_VOLUME(_vol) \
17 (((PA_VOLUME_NORM * _vol) / \
18 (double)EMIX_VOLUME_BARRIER) + 0.5)
19
20 typedef struct _Context
21 {
22 pa_mainloop_api api;
23 pa_context *context;
24 pa_context_state_t state;
25 Emix_Event_Cb cb;
26 const void *userdata;
27 Ecore_Timer *connect;
28 int default_sink;
29
30 Eina_List *sinks, *sources, *inputs, *cards;
31 Eina_Bool connected;
32 } Context;
33
34 typedef struct _Sink
35 {
36 Emix_Sink base;
37 int idx;
38 } Sink;
39
40 typedef struct _Sink_Input
41 {
42 Emix_Sink_Input base;
43 int idx;
44 } Sink_Input;
45
46 typedef struct _Source
47 {
48 Emix_Source base;
49 int idx;
50 } Source;
51
52 typedef struct _Profile
53 {
54 Emix_Profile base;
55 uint32_t priority;
56 } Profile;
57
58 typedef struct _Card
59 {
60 Emix_Card base;
61 int idx;
62 } Card;
63
64 static Context *ctx = NULL;
65 extern pa_mainloop_api functable;
66
67 static pa_cvolume
_emix_volume_convert(const Emix_Volume * volume)68 _emix_volume_convert(const Emix_Volume *volume)
69 {
70 pa_cvolume vol;
71 unsigned int i;
72
73 vol.channels = volume->channel_count;
74 for (i = 0; i < volume->channel_count; i++)
75 vol.values[i] = INT_TO_PA_VOLUME(volume->volumes[i]);
76 return vol;
77 }
78
79 static void
_pa_cvolume_convert(const pa_cvolume * volume,Emix_Volume * vol)80 _pa_cvolume_convert(const pa_cvolume *volume, Emix_Volume *vol)
81 {
82 int i;
83
84 if (vol->volumes) free(vol->volumes);
85 vol->volumes = calloc(volume->channels, sizeof(int));
86 if (!vol->volumes)
87 {
88 WRN("Could not allocate memory for volume");
89 vol->channel_count = 0;
90 return;
91 }
92
93 vol->channel_count = volume->channels;
94 for (i = 0; i < volume->channels; i++)
95 vol->volumes[i] = PA_VOLUME_TO_INT(volume->values[i]);
96 }
97
98 static void
_sink_del(Sink * sink)99 _sink_del(Sink *sink)
100 {
101 unsigned int i;
102 Emix_Port *port;
103
104 EINA_SAFETY_ON_NULL_RETURN(sink);
105 EINA_LIST_FREE(sink->base.ports, port)
106 {
107 eina_stringshare_del(port->name);
108 eina_stringshare_del(port->description);
109 free(port);
110 }
111
112 free(sink->base.volume.volumes);
113 for(i = 0; i < sink->base.volume.channel_count; ++i)
114 eina_stringshare_del(sink->base.volume.channel_names[i]);
115 free(sink->base.volume.channel_names);
116 eina_stringshare_del(sink->base.name);
117 free(sink);
118 }
119
120 static void
_sink_input_del(Sink_Input * input)121 _sink_input_del(Sink_Input *input)
122 {
123 unsigned int i;
124 EINA_SAFETY_ON_NULL_RETURN(input);
125
126 free(input->base.volume.volumes);
127 for(i = 0; i < input->base.volume.channel_count; ++i)
128 eina_stringshare_del(input->base.volume.channel_names[i]);
129 free(input->base.volume.channel_names);
130 eina_stringshare_del(input->base.name);
131 eina_stringshare_del(input->base.icon);
132 free(input);
133 }
134
135 static void
_source_del(Source * source)136 _source_del(Source *source)
137 {
138 unsigned int i;
139 EINA_SAFETY_ON_NULL_RETURN(source);
140
141 free(source->base.volume.volumes);
142 for(i = 0; i < source->base.volume.channel_count; ++i)
143 eina_stringshare_del(source->base.volume.channel_names[i]);
144 free(source->base.volume.channel_names);
145 eina_stringshare_del(source->base.name);
146 free(source);
147 }
148
149 static void
_card_del(Card * card)150 _card_del(Card *card)
151 {
152 Profile *profile;
153 EINA_SAFETY_ON_NULL_RETURN(card);
154
155 EINA_LIST_FREE(card->base.profiles, profile)
156 {
157 eina_stringshare_del(profile->base.name);
158 eina_stringshare_del(profile->base.description);
159 free(profile);
160 }
161 eina_stringshare_del(card->base.name);
162 free(card);
163 }
164
165 static void
_sink_cb(pa_context * c EINA_UNUSED,const pa_sink_info * info,int eol,void * userdata EINA_UNUSED)166 _sink_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol,
167 void *userdata EINA_UNUSED)
168 {
169 Sink *sink;
170 Emix_Port *port;
171 uint32_t i;
172
173 if (eol < 0)
174 {
175 if (pa_context_errno(c) == PA_ERR_NOENTITY)
176 return;
177
178 ERR("Sink callback failure");
179 return;
180 }
181
182 if (eol > 0)
183 return;
184
185 DBG("sink index: %d\nsink name: %s", info->index,
186 info->name);
187
188 sink = calloc(1, sizeof(Sink));
189 sink->idx = info->index;
190 sink->base.name = eina_stringshare_add(info->description);
191 _pa_cvolume_convert(&info->volume, &sink->base.volume);
192 sink->base.volume.channel_names = calloc(sink->base.volume.channel_count, sizeof(Emix_Channel));
193 for (i = 0; i < sink->base.volume.channel_count; ++i)
194 sink->base.volume.channel_names[i] = eina_stringshare_add(pa_channel_position_to_pretty_string(info->channel_map.map[i]));
195 sink->base.mute = !!info->mute;
196
197 for (i = 0; i < info->n_ports; i++)
198 {
199 port = calloc(1, sizeof(Emix_Port));
200 if (!port)
201 {
202 WRN("Could not allocate memory for Sink's port");
203 continue;
204 }
205
206 port->available = !!info->ports[i]->available;
207 port->name = eina_stringshare_add(info->ports[i]->name);
208 port->description = eina_stringshare_add(info->ports[i]->description);
209 sink->base.ports = eina_list_append(sink->base.ports, port);
210 if (info->ports[i]->name == info->active_port->name)
211 port->active = EINA_TRUE;
212 }
213
214 ctx->sinks = eina_list_append(ctx->sinks, sink);
215 if (ctx->cb)
216 ctx->cb((void *)ctx->userdata, EMIX_SINK_ADDED_EVENT,
217 (Emix_Sink *)sink);
218 }
219
220 static void
_sink_changed_cb(pa_context * c EINA_UNUSED,const pa_sink_info * info,int eol,void * userdata EINA_UNUSED)221 _sink_changed_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol,
222 void *userdata EINA_UNUSED)
223 {
224 Sink *sink = NULL, *s;
225 Emix_Port *port;
226 uint32_t i;
227 Eina_List *l;
228
229 if (eol < 0)
230 {
231 if (pa_context_errno(c) == PA_ERR_NOENTITY)
232 return;
233
234 ERR("Sink callback failure");
235 return;
236 }
237
238 if (eol > 0)
239 return;
240
241 DBG("sink index: %d\nsink name: %s", info->index,
242 info->name);
243
244 EINA_LIST_FOREACH(ctx->sinks, l, s)
245 {
246 if (s->idx == (int)info->index)
247 {
248 sink = s;
249 break;
250 }
251 }
252
253 EINA_SAFETY_ON_NULL_RETURN(sink);
254
255 eina_stringshare_replace(&sink->base.name, info->description);
256
257 if (sink->base.volume.channel_count != info->volume.channels)
258 {
259 for (i = 0; i < sink->base.volume.channel_count; ++i)
260 eina_stringshare_del(sink->base.volume.channel_names[i]);
261 free(sink->base.volume.channel_names);
262 sink->base.volume.channel_names = calloc(info->volume.channels, sizeof(Emix_Channel));
263 }
264 _pa_cvolume_convert(&info->volume, &sink->base.volume);
265 for (i = 0; i < sink->base.volume.channel_count; ++i)
266 eina_stringshare_replace(&sink->base.volume.channel_names[i],
267 pa_channel_position_to_pretty_string(info->channel_map.map[i]));
268 sink->base.mute = !!info->mute;
269
270 if (sink->base.ports)
271 {
272 EINA_LIST_FREE(sink->base.ports, port)
273 {
274 eina_stringshare_del(port->name);
275 eina_stringshare_del(port->description);
276 free(port);
277 }
278 }
279 for (i = 0; i < info->n_ports; i++)
280 {
281 port = calloc(1, sizeof(Emix_Port));
282 if (!port)
283 {
284 WRN("Could not allocate memory for Sink's port");
285 continue;
286 }
287
288 port->available = !!info->ports[i]->available;
289 port->name = eina_stringshare_add(info->ports[i]->name);
290 port->description = eina_stringshare_add(info->ports[i]->description);
291 sink->base.ports = eina_list_append(sink->base.ports, port);
292 if (info->ports[i]->name == info->active_port->name)
293 port->active = EINA_TRUE;
294 }
295
296 if (ctx->cb)
297 ctx->cb((void *)ctx->userdata, EMIX_SINK_CHANGED_EVENT,
298 (Emix_Sink *)sink);
299 }
300
301 static void
_sink_remove_cb(int index,void * data EINA_UNUSED)302 _sink_remove_cb(int index, void *data EINA_UNUSED)
303 {
304 Sink *sink;
305 Eina_List *l;
306 DBG("Removing sink: %d", index);
307
308 EINA_LIST_FOREACH(ctx->sinks, l, sink)
309 {
310 if (sink->idx == index)
311 {
312 ctx->sinks = eina_list_remove_list(ctx->sinks, l);
313 if (ctx->cb)
314 ctx->cb((void *)ctx->userdata, EMIX_SINK_REMOVED_EVENT,
315 (Emix_Sink *)sink);
316 _sink_del(sink);
317 break;
318 }
319 }
320 }
321
322 static const char *
_icon_from_properties(pa_proplist * l)323 _icon_from_properties(pa_proplist *l)
324 {
325 const char *t;
326
327 if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ICON_NAME)))
328 return t;
329
330 if ((t = pa_proplist_gets(l, PA_PROP_WINDOW_ICON_NAME)))
331 return t;
332
333 if ((t = pa_proplist_gets(l, PA_PROP_APPLICATION_ICON_NAME)))
334 return t;
335
336 if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ROLE)))
337 {
338
339 if (strcmp(t, "video") == 0 ||
340 strcmp(t, "phone") == 0)
341 return t;
342
343 if (strcmp(t, "music") == 0)
344 return "audio";
345
346 if (strcmp(t, "game") == 0)
347 return "applications-games";
348
349 if (strcmp(t, "event") == 0)
350 return "dialog-information";
351 }
352
353 return "audio-card";
354 }
355
356 static void
_sink_input_cb(pa_context * c EINA_UNUSED,const pa_sink_input_info * info,int eol,void * userdata EINA_UNUSED)357 _sink_input_cb(pa_context *c EINA_UNUSED, const pa_sink_input_info *info,
358 int eol, void *userdata EINA_UNUSED)
359 {
360 Sink_Input *input;
361 Eina_List *l;
362 Sink *s;
363 const char *t;
364 unsigned int i;
365 EINA_SAFETY_ON_NULL_RETURN(ctx);
366
367 if (eol < 0)
368 {
369 if (pa_context_errno(c) == PA_ERR_NOENTITY)
370 return;
371
372 ERR("Sink input callback failure");
373 return;
374 }
375
376 if (eol > 0)
377 return;
378
379 input = calloc(1, sizeof(Sink_Input));
380 EINA_SAFETY_ON_NULL_RETURN(input);
381
382 DBG("sink input index: %d\nsink input name: %s", info->index,
383 info->name);
384
385 input->idx = info->index;
386
387 Eina_Strbuf *input_name;
388
389 input_name = eina_strbuf_new();
390 const char *application = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_NAME);
391 if (application)
392 {
393 eina_strbuf_append(input_name, application);
394 eina_strbuf_append(input_name, ":");
395 eina_strbuf_append(input_name, info->name);
396 }
397 else if (info->name)
398 {
399 eina_strbuf_append(input_name, info->name);
400 }
401 input->base.name = eina_stringshare_add(eina_strbuf_string_get(input_name));
402 eina_strbuf_free(input_name);
403 _pa_cvolume_convert(&info->volume, &input->base.volume);
404 input->base.volume.channel_names = calloc(input->base.volume.channel_count, sizeof(Emix_Channel));
405 for (i = 0; i < input->base.volume.channel_count; ++i)
406 input->base.volume.channel_names[i] = eina_stringshare_add(pa_channel_position_to_pretty_string(info->channel_map.map[i]));
407 input->base.mute = !!info->mute;
408 EINA_LIST_FOREACH(ctx->sinks, l, s)
409 {
410 if (s->idx == (int)info->sink)
411 input->base.sink = (Emix_Sink *)s;
412 }
413 input->base.icon = eina_stringshare_add(_icon_from_properties(info->proplist));
414 ctx->inputs = eina_list_append(ctx->inputs, input);
415
416 if ((t = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_PROCESS_ID)))
417 {
418 input->base.pid = atoi(t);
419 }
420
421 if (ctx->cb)
422 ctx->cb((void *)ctx->userdata, EMIX_SINK_INPUT_ADDED_EVENT,
423 (Emix_Sink_Input *)input);
424 }
425
426 static void
_sink_input_changed_cb(pa_context * c EINA_UNUSED,const pa_sink_input_info * info,int eol,void * userdata EINA_UNUSED)427 _sink_input_changed_cb(pa_context *c EINA_UNUSED,
428 const pa_sink_input_info *info, int eol,
429 void *userdata EINA_UNUSED)
430 {
431 Sink_Input *input = NULL, *si;
432 Sink *s = NULL;
433 Eina_List *l;
434 const char *t;
435 unsigned int i;
436
437 EINA_SAFETY_ON_NULL_RETURN(ctx);
438 if (eol < 0)
439 {
440 if (pa_context_errno(c) == PA_ERR_NOENTITY)
441 return;
442
443 ERR("Sink input changed callback failure");
444 return;
445 }
446
447 if (eol > 0)
448 return;
449
450 EINA_LIST_FOREACH(ctx->inputs, l, si)
451 {
452 if (si->idx == (int)info->index)
453 {
454 input = si;
455 break;
456 }
457 }
458
459 DBG("sink input changed index: %d\n", info->index);
460
461 if (!input)
462 {
463 input = calloc(1, sizeof(Sink_Input));
464 EINA_SAFETY_ON_NULL_RETURN(input);
465 ctx->inputs = eina_list_append(ctx->inputs, input);
466 }
467 input->idx = info->index;
468 if (input->base.volume.channel_count != info->volume.channels)
469 {
470 for (i = 0; i < input->base.volume.channel_count; ++i)
471 eina_stringshare_del(input->base.volume.channel_names[i]);
472 free(input->base.volume.channel_names);
473 input->base.volume.channel_names = calloc(info->volume.channels, sizeof(Emix_Channel));
474 }
475 _pa_cvolume_convert(&info->volume, &input->base.volume);
476 for (i = 0; i < input->base.volume.channel_count; ++i)
477 eina_stringshare_replace(&input->base.volume.channel_names[i],
478 pa_channel_position_to_pretty_string(info->channel_map.map[i]));
479
480 input->base.mute = !!info->mute;
481
482 EINA_LIST_FOREACH(ctx->sinks, l, s)
483 {
484 if (s->idx == (int)info->sink)
485 input->base.sink = (Emix_Sink *)s;
486 }
487 if ((t = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_PROCESS_ID)))
488 {
489 input->base.pid = atoi(t);
490 }
491
492 if (ctx->cb)
493 ctx->cb((void *)ctx->userdata, EMIX_SINK_INPUT_CHANGED_EVENT,
494 (Emix_Sink_Input *)input);
495 }
496
497 static void
_sink_input_remove_cb(int index,void * data EINA_UNUSED)498 _sink_input_remove_cb(int index, void *data EINA_UNUSED)
499 {
500 Sink_Input *input;
501 Eina_List *l;
502 EINA_SAFETY_ON_NULL_RETURN(ctx);
503
504 DBG("Removing sink input: %d", index);
505
506 EINA_LIST_FOREACH(ctx->inputs, l, input)
507 {
508 if (input->idx == index)
509 {
510 ctx->inputs = eina_list_remove_list(ctx->inputs, l);
511 if (ctx->cb)
512 ctx->cb((void *)ctx->userdata,
513 EMIX_SINK_INPUT_REMOVED_EVENT,
514 (Emix_Sink_Input *)input);
515 _sink_input_del(input);
516
517 break;
518 }
519 }
520 }
521
522 static void
_source_cb(pa_context * c EINA_UNUSED,const pa_source_info * info,int eol,void * userdata EINA_UNUSED)523 _source_cb(pa_context *c EINA_UNUSED, const pa_source_info *info,
524 int eol, void *userdata EINA_UNUSED)
525 {
526 Source *source;
527 unsigned int i;
528 EINA_SAFETY_ON_NULL_RETURN(ctx);
529
530 if (eol < 0)
531 {
532 if (pa_context_errno(c) == PA_ERR_NOENTITY)
533 return;
534
535 ERR("Source callback failure");
536 return;
537 }
538
539 if (eol > 0)
540 return;
541
542 source = calloc(1, sizeof(Source));
543 EINA_SAFETY_ON_NULL_RETURN(source);
544
545 source->idx = info->index;
546 source->base.name = eina_stringshare_add(info->name);
547 _pa_cvolume_convert(&info->volume, &source->base.volume);
548 source->base.volume.channel_names = calloc(source->base.volume.channel_count, sizeof(Emix_Channel));
549 for (i = 0; i < source->base.volume.channel_count; ++i)
550 source->base.volume.channel_names[i] = eina_stringshare_add(pa_channel_position_to_pretty_string(info->channel_map.map[i]));
551 source->base.mute = !!info->mute;
552
553 ctx->sources = eina_list_append(ctx->sources, source);
554 if (ctx->cb)
555 ctx->cb((void *)ctx->userdata, EMIX_SOURCE_ADDED_EVENT,
556 (Emix_Source *)source);
557 }
558
559 static void
_source_changed_cb(pa_context * c EINA_UNUSED,const pa_source_info * info,int eol,void * userdata EINA_UNUSED)560 _source_changed_cb(pa_context *c EINA_UNUSED,
561 const pa_source_info *info, int eol,
562 void *userdata EINA_UNUSED)
563 {
564 Source *source = NULL, *s;
565 Eina_List *l;
566 unsigned int i;
567 EINA_SAFETY_ON_NULL_RETURN(ctx);
568
569 if (eol < 0)
570 {
571 if (pa_context_errno(c) == PA_ERR_NOENTITY)
572 return;
573
574 ERR("Source changed callback failure");
575 return;
576 }
577
578 if (eol > 0)
579 return;
580
581 EINA_LIST_FOREACH(ctx->sources, l, s)
582 {
583 if (s->idx == (int)info->index)
584 {
585 source = s;
586 break;
587 }
588 }
589
590 DBG("source changed index: %d\n", info->index);
591
592 if (!source)
593 {
594 source = calloc(1, sizeof(Source));
595 EINA_SAFETY_ON_NULL_RETURN(source);
596 ctx->sources = eina_list_append(ctx->sources, source);
597 }
598 source->idx= info->index;
599 if (source->base.volume.channel_count != info->volume.channels)
600 {
601 for (i = 0; i < source->base.volume.channel_count; ++i)
602 eina_stringshare_del(source->base.volume.channel_names[i]);
603 free(source->base.volume.channel_names);
604 source->base.volume.channel_names = calloc(info->volume.channels, sizeof(Emix_Channel));
605 }
606 _pa_cvolume_convert(&info->volume, &source->base.volume);
607 for (i = 0; i < source->base.volume.channel_count; ++i)
608 eina_stringshare_replace(&source->base.volume.channel_names[i],
609 pa_channel_position_to_pretty_string(info->channel_map.map[i]));
610 source->base.mute = !!info->mute;
611
612 if (ctx->cb)
613 ctx->cb((void *)ctx->userdata, EMIX_SOURCE_CHANGED_EVENT,
614 (Emix_Source *)source);
615 }
616
617 static void
_source_remove_cb(int index,void * data EINA_UNUSED)618 _source_remove_cb(int index, void *data EINA_UNUSED)
619 {
620 Source *source;
621 Eina_List *l;
622 EINA_SAFETY_ON_NULL_RETURN(ctx);
623
624 DBG("Removing source: %d", index);
625
626 EINA_LIST_FOREACH(ctx->sources, l, source)
627 {
628 if (source->idx == index)
629 {
630 ctx->sources = eina_list_remove_list(ctx->sources, l);
631 if (ctx->cb)
632 ctx->cb((void *)ctx->userdata, EMIX_SOURCE_REMOVED_EVENT,
633 (Emix_Source *)source);
634
635 _source_del(source);
636 break;
637 }
638 }
639 }
640
641 static void
_sink_default_cb(pa_context * c EINA_UNUSED,const pa_sink_info * info,int eol,void * userdata EINA_UNUSED)642 _sink_default_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol,
643 void *userdata EINA_UNUSED)
644 {
645 if (eol < 0)
646 {
647 if (pa_context_errno(c) == PA_ERR_NOENTITY)
648 return;
649
650 ERR("Sink callback failure");
651 return;
652 }
653
654 if (eol > 0)
655 return;
656
657 DBG("sink index: %d\nsink name: %s", info->index,
658 info->name);
659
660 ctx->default_sink = info->index;
661 if (ctx->cb)
662 ctx->cb((void *)ctx->userdata, EMIX_READY_EVENT, NULL);
663 }
664
665 static void
_server_info_cb(pa_context * c,const pa_server_info * info,void * userdata)666 _server_info_cb(pa_context *c, const pa_server_info *info,
667 void *userdata)
668 {
669 pa_operation *o;
670
671 if (pa_context_errno(c) != PA_OK)
672 {
673 WRN("Could not get pa server info, error: %d", pa_context_errno(c));
674 return;
675 }
676
677 EINA_SAFETY_ON_NULL_RETURN(info);
678
679 if (!(o = pa_context_get_sink_info_by_name(c, info->default_sink_name,
680 _sink_default_cb, userdata)))
681 {
682 ERR("pa_context_get_sink_info_by_name() failed");
683 return;
684 }
685 pa_operation_unref(o);
686 }
687
688 static int
_profile_sort_cb(const Profile * p1,const Profile * p2)689 _profile_sort_cb(const Profile *p1, const Profile *p2)
690 {
691 if (p1->priority < p2->priority) return 1;
692 if (p1->priority > p2->priority) return -1;
693 return 0;
694 }
695
696 static void
_card_cb(pa_context * c,const pa_card_info * info,int eol,void * userdata EINA_UNUSED)697 _card_cb(pa_context *c, const pa_card_info *info, int eol, void *userdata EINA_UNUSED)
698 {
699 Card *card;
700 const char *description;
701 uint32_t i, j;
702 Profile *profile;
703
704 if (eol < 0) {
705 if (pa_context_errno(c) == PA_ERR_NOENTITY)
706 return;
707
708 ERR("Card callback failure: %d", pa_context_errno(c));
709 return;
710 }
711 if (eol > 0)
712 return;
713
714 card = calloc(1, sizeof(Card));
715 EINA_SAFETY_ON_NULL_RETURN(card);
716
717 card->idx = info->index;
718
719 description = pa_proplist_gets(info->proplist, PA_PROP_DEVICE_DESCRIPTION);
720 card->base.name = description
721 ? eina_stringshare_add(description)
722 : eina_stringshare_add(info->name);
723
724 for (i = 0; i < info->n_ports; ++i)
725 {
726 for (j = 0; j < info->ports[i]->n_profiles; ++j)
727 {
728 profile = calloc(1, sizeof(Profile));
729 profile->base.name = eina_stringshare_add(info->ports[i]->profiles[j]->name);
730 profile->base.description = eina_stringshare_add(info->ports[i]->profiles[j]->description);
731 profile->priority = info->ports[i]->profiles[j]->priority;
732 if ((info->ports[i]->available == PA_PORT_AVAILABLE_NO)
733 && ((strcmp("analog-output-speaker", profile->base.name))
734 || (strcmp("analog-input-microphone-internal", profile->base.name))))
735 profile->base.plugged = EINA_FALSE;
736 else
737 profile->base.plugged = EINA_TRUE;
738
739 if (info->active_profile)
740 {
741 if (info->ports[i]->profiles[j]->name == info->active_profile->name)
742 profile->base.active = EINA_TRUE;
743 }
744 card->base.profiles =
745 eina_list_sorted_insert(card->base.profiles,
746 (Eina_Compare_Cb)_profile_sort_cb,
747 profile);
748 }
749 }
750 ctx->cards = eina_list_append(ctx->cards, card);
751 if (ctx->cb)
752 ctx->cb((void *)ctx->userdata, EMIX_CARD_ADDED_EVENT,
753 (Emix_Card *)card);
754 }
755
756 static void
_card_changed_cb(pa_context * c EINA_UNUSED,const pa_card_info * info,int eol,void * userdata EINA_UNUSED)757 _card_changed_cb(pa_context *c EINA_UNUSED, const pa_card_info *info, int eol,
758 void *userdata EINA_UNUSED)
759 {
760 Card *card = NULL, *ca;
761 Eina_List *l;
762 const char *description;
763 uint32_t i, j;
764 Profile *profile;
765
766 if (eol < 0)
767 {
768 if (pa_context_errno(c) == PA_ERR_NOENTITY)
769 return;
770
771 ERR("Card callback failure");
772 return;
773 }
774
775 if (eol > 0)
776 return;
777
778 DBG("card index: %d\n", info->index);
779
780 EINA_LIST_FOREACH(ctx->cards, l, ca)
781 {
782 if (ca->idx == (int)info->index)
783 {
784 card = ca;
785 break;
786 }
787 }
788
789 EINA_SAFETY_ON_NULL_RETURN(card);
790
791 description = pa_proplist_gets(info->proplist, PA_PROP_DEVICE_DESCRIPTION);
792 eina_stringshare_replace(&card->base.name,
793 (description
794 ? eina_stringshare_add(description)
795 : eina_stringshare_add(info->name)));
796
797 EINA_LIST_FREE(card->base.profiles, profile)
798 {
799 eina_stringshare_del(profile->base.name);
800 eina_stringshare_del(profile->base.description);
801 free(profile);
802 }
803 for (i = 0; i < info->n_ports; ++i)
804 {
805 for (j = 0; j < info->ports[i]->n_profiles; ++j)
806 {
807 profile = calloc(1, sizeof(Profile));
808 profile->base.name = eina_stringshare_add(info->ports[i]->profiles[j]->name);
809 profile->base.description = eina_stringshare_add(info->ports[i]->profiles[j]->description);
810 profile->priority = info->ports[i]->profiles[j]->priority;
811 if ((info->ports[i]->available == PA_PORT_AVAILABLE_NO)
812 && ((strcmp("analog-output-speaker", profile->base.name))
813 || (strcmp("analog-input-microphone-internal", profile->base.name))))
814 profile->base.plugged = EINA_FALSE;
815 else
816 profile->base.plugged = EINA_TRUE;
817
818 if (info->active_profile)
819 {
820 if (info->ports[i]->profiles[j]->name == info->active_profile->name)
821 profile->base.active = EINA_TRUE;
822 }
823 card->base.profiles =
824 eina_list_sorted_insert(card->base.profiles,
825 (Eina_Compare_Cb)_profile_sort_cb,
826 profile);
827 }
828 }
829
830 if (ctx->cb)
831 ctx->cb((void *)ctx->userdata, EMIX_CARD_CHANGED_EVENT,
832 (Emix_Card *)card);
833 }
834
835 static void
_card_remove_cb(int index,void * data EINA_UNUSED)836 _card_remove_cb(int index, void *data EINA_UNUSED)
837 {
838 Card *card;
839 Eina_List *l;
840 EINA_SAFETY_ON_NULL_RETURN(ctx);
841
842 DBG("Removing card: %d", index);
843
844 EINA_LIST_FOREACH(ctx->cards, l, card)
845 {
846 if (card->idx == index)
847 {
848 ctx->cards = eina_list_remove_list(ctx->cards, l);
849 if (ctx->cb)
850 ctx->cb((void *)ctx->userdata,
851 EMIX_CARD_REMOVED_EVENT,
852 (Emix_Card *)card);
853 _card_del(card);
854
855 break;
856 }
857 }
858 }
859
860
861 static void
_subscribe_cb(pa_context * c,pa_subscription_event_type_t t,uint32_t index,void * data)862 _subscribe_cb(pa_context *c, pa_subscription_event_type_t t,
863 uint32_t index, void *data)
864 {
865 pa_operation *o;
866
867 switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
868 case PA_SUBSCRIPTION_EVENT_SINK:
869 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
870 PA_SUBSCRIPTION_EVENT_REMOVE)
871 _sink_remove_cb(index, data);
872 else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
873 PA_SUBSCRIPTION_EVENT_NEW)
874 {
875 if (!(o = pa_context_get_sink_info_by_index(c, index,
876 _sink_cb, data)))
877 {
878 ERR("pa_context_get_sink_info_by_index() failed");
879 return;
880 }
881 pa_operation_unref(o);
882 }
883 else
884 {
885 if (!(o = pa_context_get_sink_info_by_index(c, index,
886 _sink_changed_cb,
887 data)))
888 {
889 ERR("pa_context_get_sink_info_by_index() failed");
890 return;
891 }
892 pa_operation_unref(o);
893 }
894 break;
895
896 case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
897 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
898 PA_SUBSCRIPTION_EVENT_REMOVE)
899 _sink_input_remove_cb(index, data);
900 else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
901 PA_SUBSCRIPTION_EVENT_NEW)
902 {
903 if (!(o = pa_context_get_sink_input_info(c, index,
904 _sink_input_cb, data)))
905 {
906 ERR("pa_context_get_sink_input_info() failed");
907 return;
908 }
909 pa_operation_unref(o);
910 }
911 else
912 {
913 if (!(o = pa_context_get_sink_input_info(c, index,
914 _sink_input_changed_cb,
915 data)))
916 {
917 ERR("pa_context_get_sink_input_info() failed");
918 return;
919 }
920 pa_operation_unref(o);
921 }
922 break;
923
924 case PA_SUBSCRIPTION_EVENT_SOURCE:
925 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
926 PA_SUBSCRIPTION_EVENT_REMOVE)
927 _source_remove_cb(index, data);
928 else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
929 PA_SUBSCRIPTION_EVENT_NEW)
930 {
931 if (!(o = pa_context_get_source_info_by_index(c, index,
932 _source_cb, data)))
933 {
934 ERR("pa_context_get_source_info() failed");
935 return;
936 }
937 pa_operation_unref(o);
938 }
939 else
940 {
941 if (!(o = pa_context_get_source_info_by_index(c, index,
942 _source_changed_cb,
943 data)))
944 {
945 ERR("pa_context_get_source_info() failed");
946 return;
947 }
948 pa_operation_unref(o);
949 }
950 break;
951 case PA_SUBSCRIPTION_EVENT_SERVER:
952 if (!(o = pa_context_get_server_info(c, _server_info_cb,
953 data)))
954 {
955 ERR("pa_context_get_server_info() failed");
956 return;
957 }
958 pa_operation_unref(o);
959 break;
960
961 case PA_SUBSCRIPTION_EVENT_CARD:
962 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
963 PA_SUBSCRIPTION_EVENT_REMOVE)
964 _card_remove_cb(index, data);
965 else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
966 PA_SUBSCRIPTION_EVENT_NEW)
967 {
968 if (!(o = pa_context_get_card_info_by_index(c, index, _card_cb,
969 data)))
970 {
971 ERR("pa_context_get_card_info() failed");
972 return;
973 }
974 pa_operation_unref(o);
975 }
976 else
977 {
978 if (!(o = pa_context_get_card_info_by_index(c, index, _card_changed_cb,
979 data)))
980 {
981 ERR("pa_context_get_card_info() failed");
982 return;
983 }
984 pa_operation_unref(o);
985 }
986 break;
987
988 default:
989 WRN("Event not handled");
990 break;
991 }
992 }
993
994 static Eina_Bool _pulse_connect(void *data);
995 static void _disconnect_cb(void);
996
997 static void
_pulse_pa_state_cb(pa_context * context,void * data)998 _pulse_pa_state_cb(pa_context *context, void *data)
999 {
1000 pa_operation *o;
1001
1002 switch (pa_context_get_state(context))
1003 {
1004 case PA_CONTEXT_UNCONNECTED:
1005 case PA_CONTEXT_CONNECTING:
1006 case PA_CONTEXT_AUTHORIZING:
1007 case PA_CONTEXT_SETTING_NAME:
1008 break;
1009
1010 case PA_CONTEXT_READY:
1011 ctx->connect = NULL;
1012 ctx->connected = EINA_TRUE;
1013 pa_context_set_subscribe_callback(context, _subscribe_cb, ctx);
1014 if (!(o = pa_context_subscribe(context, (pa_subscription_mask_t)
1015 (PA_SUBSCRIPTION_MASK_SINK|
1016 PA_SUBSCRIPTION_MASK_SOURCE|
1017 PA_SUBSCRIPTION_MASK_SINK_INPUT|
1018 PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT|
1019 PA_SUBSCRIPTION_MASK_CLIENT|
1020 PA_SUBSCRIPTION_MASK_SERVER|
1021 PA_SUBSCRIPTION_MASK_CARD),
1022 NULL, NULL)))
1023 {
1024 ERR("pa_context_subscribe() failed");
1025 return;
1026 }
1027 pa_operation_unref(o);
1028
1029 if (!(o = pa_context_get_sink_info_list(context, _sink_cb, ctx)))
1030 {
1031 ERR("pa_context_get_sink_info_list() failed");
1032 return;
1033 }
1034 pa_operation_unref(o);
1035
1036 if (!(o = pa_context_get_sink_input_info_list(context,
1037 _sink_input_cb,
1038 ctx)))
1039 {
1040 ERR("pa_context_get_sink_input_info_list() failed");
1041 return;
1042 }
1043 pa_operation_unref(o);
1044
1045 if (!(o = pa_context_get_source_info_list(context, _source_cb,
1046 ctx)))
1047 {
1048 ERR("pa_context_get_source_info_list() failed");
1049 return;
1050 }
1051 pa_operation_unref(o);
1052
1053 if (!(o = pa_context_get_server_info(context, _server_info_cb,
1054 ctx)))
1055 {
1056 ERR("pa_context_get_server_info() failed");
1057 return;
1058 }
1059 pa_operation_unref(o);
1060
1061 if (!(o = pa_context_get_card_info_list(context, _card_cb,
1062 ctx)))
1063 {
1064 ERR("pa_context_get_server_info() failed");
1065 return;
1066 }
1067 pa_operation_unref(o);
1068
1069
1070
1071 break;
1072
1073 case PA_CONTEXT_FAILED:
1074 WRN("PA_CONTEXT_FAILED");
1075 if (!ctx->connect)
1076 ctx->connect = ecore_timer_loop_add(1, _pulse_connect, data);
1077 goto err;
1078
1079 case PA_CONTEXT_TERMINATED:
1080 ERR("PA_CONTEXT_TERMINATE:");
1081 EINA_FALLTHROUGH;
1082 /* no break */
1083
1084 default:
1085 if (ctx->connect)
1086 {
1087 ecore_timer_del(ctx->connect);
1088 ctx->connect = NULL;
1089 }
1090 goto err;
1091 }
1092 return;
1093
1094 err:
1095 if (ctx->connected)
1096 {
1097 _disconnect_cb();
1098 ctx->connected = EINA_FALSE;
1099 }
1100 pa_context_unref(ctx->context);
1101 ctx->context = NULL;
1102 }
1103
1104 static Eina_Bool
_pulse_connect(void * data)1105 _pulse_connect(void *data)
1106 {
1107 pa_proplist *proplist;
1108 Context *c = data;
1109
1110 proplist = pa_proplist_new();
1111 pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "Efl Volume Control");
1112 pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID,
1113 "org.enlightenment.volumecontrol");
1114 pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "audio-card");
1115 #if !defined(EMIXER_BUILD) && defined(HAVE_WAYLAND) && !defined(HAVE_WAYLAND_ONLY)
1116 char *display = NULL;
1117
1118 if (e_comp->comp_type != E_PIXMAP_TYPE_X)
1119 {
1120 display = getenv("DISPLAY");
1121 if (display) display = strdup(display);
1122 e_env_unset("DISPLAY");
1123 }
1124 #endif
1125 c->context = pa_context_new_with_proplist(&(c->api), NULL, proplist);
1126 if (c->context)
1127 {
1128 pa_context_set_state_callback(c->context, _pulse_pa_state_cb, c);
1129 if (pa_context_connect(c->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0)
1130 ERR("Could not connect to pulse");
1131 }
1132 #if !defined(EMIXER_BUILD) && defined(HAVE_WAYLAND) && !defined(HAVE_WAYLAND_ONLY)
1133 if (e_comp->comp_type != E_PIXMAP_TYPE_X)
1134 {
1135 if (display)
1136 {
1137 e_env_set("DISPLAY", display);
1138 free(display);
1139 }
1140 }
1141 #endif
1142
1143 pa_proplist_free(proplist);
1144 return ECORE_CALLBACK_DONE;
1145 }
1146
1147 static Eina_Bool pulse_started;
1148
1149 static void
_shutdown(void)1150 _shutdown(void)
1151 {
1152 if (!ctx)
1153 return;
1154 if (pulse_started)
1155 {
1156 ecore_exe_run("pulseaudio -k", NULL);
1157 pulse_started = EINA_FALSE;
1158 }
1159
1160 if (ctx->connect)
1161 {
1162 ecore_timer_del(ctx->connect);
1163 ctx->connect = NULL;
1164 }
1165 if (ctx->context)
1166 pa_context_unref(ctx->context);
1167 if (ctx->connected)
1168 _disconnect_cb();
1169 free(ctx);
1170 ctx = NULL;
1171 }
1172
1173 static Eina_Bool
_init(Emix_Event_Cb cb,const void * data)1174 _init(Emix_Event_Cb cb, const void *data)
1175 {
1176 if (ctx)
1177 return EINA_TRUE;
1178
1179 ctx = calloc(1, sizeof(Context));
1180 if (!ctx)
1181 {
1182 ERR("Could not create Epulse Context");
1183 return EINA_FALSE;
1184 }
1185
1186 ctx->api = functable;
1187 ctx->api.userdata = ctx;
1188
1189 if (_pulse_connect(ctx) == EINA_TRUE) // true == failed and try again
1190 {
1191 if (!pulse_started)
1192 ecore_exe_run("pulseaudio --start", NULL);
1193 pulse_started = 1;
1194 }
1195
1196 ctx->cb = cb;
1197 ctx->userdata = data;
1198
1199 return EINA_TRUE;
1200 }
1201
1202 static void
_disconnect_cb()1203 _disconnect_cb()
1204 {
1205 Source *source;
1206 Sink *sink;
1207 Sink_Input *input;
1208 Card *card;
1209
1210 if (ctx->cb)
1211 ctx->cb((void *)ctx->userdata, EMIX_DISCONNECTED_EVENT, NULL);
1212
1213 EINA_LIST_FREE(ctx->sources, source)
1214 _source_del(source);
1215 EINA_LIST_FREE(ctx->sinks, sink)
1216 _sink_del(sink);
1217 EINA_LIST_FREE(ctx->inputs, input)
1218 _sink_input_del(input);
1219 EINA_LIST_FREE(ctx->cards, card)
1220 _card_del(card);
1221 }
1222
1223 static void
_source_volume_set(Emix_Source * source,Emix_Volume * volume)1224 _source_volume_set(Emix_Source *source, Emix_Volume *volume)
1225 {
1226 pa_operation* o;
1227 pa_cvolume vol = _emix_volume_convert(volume);
1228 Source *s = (Source *)source;
1229 EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && source != NULL);
1230
1231 if (!(o = pa_context_set_source_volume_by_index(ctx->context,
1232 s->idx, &vol,
1233 NULL, NULL)))
1234 ERR("pa_context_set_source_volume_by_index() failed");
1235 }
1236
1237 static void
_source_mute_set(Emix_Source * source,Eina_Bool mute)1238 _source_mute_set(Emix_Source *source, Eina_Bool mute)
1239 {
1240 pa_operation* o;
1241 Source *s = (Source *)source;
1242 EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && source != NULL);
1243
1244 if (!(o = pa_context_set_source_mute_by_index(ctx->context,
1245 s->idx, mute, NULL, NULL)))
1246 ERR("pa_context_set_source_mute() failed");
1247 }
1248
1249 static const Eina_List *
_sinks_get(void)1250 _sinks_get(void)
1251 {
1252 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
1253 return ctx->sinks;
1254 }
1255
1256 static const Eina_List *
_sources_get(void)1257 _sources_get(void)
1258 {
1259 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
1260 return ctx->sources;
1261 }
1262
1263 static const Eina_List *
_sink_inputs_get(void)1264 _sink_inputs_get(void)
1265 {
1266 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
1267 return ctx->inputs;
1268 }
1269
1270 static void
_sink_volume_set(Emix_Sink * sink,Emix_Volume * volume)1271 _sink_volume_set(Emix_Sink *sink, Emix_Volume *volume)
1272 {
1273 pa_operation* o;
1274 Sink *s = (Sink *)sink;
1275 pa_cvolume vol = _emix_volume_convert(volume);
1276 EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->context && sink != NULL));
1277
1278 if (!(o = pa_context_set_sink_volume_by_index(ctx->context,
1279 s->idx, &vol, NULL, NULL)))
1280 ERR("pa_context_set_sink_volume_by_index() failed");
1281 }
1282
1283 static void
_sink_mute_set(Emix_Sink * sink,Eina_Bool mute)1284 _sink_mute_set(Emix_Sink *sink, Eina_Bool mute)
1285 {
1286 pa_operation* o;
1287 Sink *s = (Sink *)sink;
1288 EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->context && sink != NULL));
1289
1290 if (!(o = pa_context_set_sink_mute_by_index(ctx->context,
1291 s->idx, mute, NULL, NULL)))
1292 ERR("pa_context_set_sink_mute() failed");
1293 }
1294
1295 static void
_sink_input_volume_set(Emix_Sink_Input * input,Emix_Volume * volume)1296 _sink_input_volume_set(Emix_Sink_Input *input, Emix_Volume *volume)
1297 {
1298 pa_operation* o;
1299 Sink_Input *sink_input = (Sink_Input *)input;
1300 pa_cvolume vol = _emix_volume_convert(volume);
1301 EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && input != NULL);
1302
1303
1304 if (!(o = pa_context_set_sink_input_volume(ctx->context,
1305 sink_input->idx, &vol,
1306 NULL, NULL)))
1307 ERR("pa_context_set_sink_input_volume_by_index() failed");
1308 }
1309
1310 static void
_sink_input_mute_set(Emix_Sink_Input * input,Eina_Bool mute)1311 _sink_input_mute_set(Emix_Sink_Input *input, Eina_Bool mute)
1312 {
1313 pa_operation* o;
1314 Sink_Input *sink_input = (Sink_Input *)input;
1315 EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && input != NULL);
1316
1317 if (!(o = pa_context_set_sink_input_mute(ctx->context,
1318 sink_input->idx, mute,
1319 NULL, NULL)))
1320 ERR("pa_context_set_sink_input_mute() failed");
1321 }
1322
1323 static void
_sink_input_move(Emix_Sink_Input * input,Emix_Sink * sink)1324 _sink_input_move(Emix_Sink_Input *input, Emix_Sink *sink)
1325 {
1326 pa_operation* o;
1327 Sink *s = (Sink *)sink;
1328 Sink_Input *i = (Sink_Input *)input;
1329 EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && input != NULL
1330 && sink != NULL);
1331
1332 if (!(o = pa_context_move_sink_input_by_index(ctx->context,
1333 i->idx, s->idx, NULL,
1334 NULL)))
1335 ERR("pa_context_move_sink_input_by_index() failed");
1336 }
1337
1338 static Eina_Bool
_sink_port_set(Emix_Sink * sink,const Emix_Port * port)1339 _sink_port_set(Emix_Sink *sink, const Emix_Port *port)
1340 {
1341 pa_operation* o;
1342 Sink *s = (Sink *)sink;
1343 EINA_SAFETY_ON_FALSE_RETURN_VAL(ctx && ctx->context &&
1344 sink != NULL && port != NULL, EINA_FALSE);
1345
1346 if (!(o = pa_context_set_sink_port_by_index(ctx->context,
1347 s->idx, port->name, NULL,
1348 NULL)))
1349 {
1350 ERR("pa_context_set_source_port_by_index() failed");
1351 return EINA_FALSE;
1352 }
1353 pa_operation_unref(o);
1354
1355 return EINA_TRUE;
1356 }
1357
1358 static Eina_Bool
_sink_default_support(void)1359 _sink_default_support(void)
1360 {
1361 return EINA_TRUE;
1362 }
1363
1364 static const Emix_Sink *
_sink_default_get(void)1365 _sink_default_get(void)
1366 {
1367 Sink *s;
1368 Eina_List *l;
1369
1370 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
1371 EINA_LIST_FOREACH(ctx->sinks, l, s)
1372 if (s->idx == ctx->default_sink)
1373 return (Emix_Sink *)s;
1374
1375 return NULL;
1376 }
1377
1378 static Eina_Bool
_sink_change_support(void)1379 _sink_change_support(void)
1380 {
1381 return EINA_TRUE;
1382 }
1383
1384 static int
_max_volume(void)1385 _max_volume(void)
1386 {
1387 return 150;
1388 }
1389
1390 static const Eina_List *
_cards_get(void)1391 _cards_get(void)
1392 {
1393 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
1394 return ctx->cards;
1395 }
1396
1397 static Eina_Bool
_card_profile_set(Emix_Card * card,const Emix_Profile * profile)1398 _card_profile_set(Emix_Card *card, const Emix_Profile *profile)
1399 {
1400 pa_operation* o;
1401 Card *c = (Card *)card;
1402 Profile *p = (Profile *)profile;
1403 EINA_SAFETY_ON_FALSE_RETURN_VAL(ctx && ctx->context &&
1404 (c != NULL) && (p != NULL), EINA_FALSE);
1405
1406 if (!(o = pa_context_set_card_profile_by_index(ctx->context,
1407 c->idx, p->base.name, NULL,
1408 NULL)))
1409 {
1410 ERR("pa_context_set_card_profile_by_index() failed");
1411 return EINA_FALSE;
1412 }
1413 pa_operation_unref(o);
1414
1415 return EINA_TRUE;
1416 }
1417
1418 static Emix_Backend
1419 _pulseaudio_backend =
1420 {
1421 _init,
1422 _shutdown,
1423 _max_volume,
1424 _sinks_get,
1425 _sink_default_support,
1426 _sink_default_get,
1427 NULL,
1428 _sink_mute_set,
1429 _sink_volume_set,
1430 _sink_port_set,
1431 _sink_change_support,
1432 _sink_inputs_get,
1433 _sink_input_mute_set,
1434 _sink_input_volume_set,
1435 _sink_input_move,
1436 _sources_get,
1437 _source_mute_set,
1438 _source_volume_set,
1439 NULL,
1440 _cards_get,
1441 _card_profile_set
1442 };
1443
1444 E_API Emix_Backend *
emix_backend_pulse_get(void)1445 emix_backend_pulse_get(void)
1446 {
1447 return &_pulseaudio_backend;
1448 }
1449
1450 E_API const char *emix_backend_pulse_name = "PULSEAUDIO";
1451