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