summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarsten Haitzler (Rasterman) <raster@rasterman.com>2015-06-08 21:00:23 +0900
committerCarsten Haitzler (Rasterman) <raster@rasterman.com>2015-06-08 21:15:09 +0900
commit4f6df6b7ca186420a88ea5215f84f5a8ecf2b4f0 (patch)
treeb97a682e8e4d383650f13de575d7f7761805d84b
parentd17851f7143536021c73f1c6ae6f51e814c45034 (diff)
e mixer - replace with epulse/emixer
this is emixer (epulse) from http://git.enlightenment.org/devs/ceolin/epulse.git the emixer binar is rewritten though and the emix lib is compiled-in into the module and into the binary as oppopsed to a shared lib with loadable modules. this supports alsa and pulse. a much more solid mixer.
-rw-r--r--configure.ac25
-rw-r--r--po/POTFILES.in4
-rw-r--r--src/modules/Makefile_mixer.mk48
-rw-r--r--src/modules/mixer/.gitignore1
-rw-r--r--src/modules/mixer/e_mod_config.c201
-rw-r--r--src/modules/mixer/e_mod_config.h18
-rw-r--r--src/modules/mixer/e_mod_main.c817
-rw-r--r--src/modules/mixer/e_mod_main.h25
-rw-r--r--src/modules/mixer/emixer.c727
-rw-r--r--src/modules/mixer/lib/backends/alsa/alsa.c523
-rw-r--r--src/modules/mixer/lib/backends/pulseaudio/pulse.c1047
-rw-r--r--src/modules/mixer/lib/backends/pulseaudio/pulse_ml.c319
-rw-r--r--src/modules/mixer/lib/emix.c395
-rw-r--r--src/modules/mixer/lib/emix.h143
14 files changed, 4243 insertions, 50 deletions
diff --git a/configure.ac b/configure.ac
index 6b7afda..3a59fcb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -722,28 +722,13 @@ define([CHECK_MODULE_NOTIFICATION],
722 722
723 723
724AM_CONDITIONAL(HAVE_ALSA, false) 724AM_CONDITIONAL(HAVE_ALSA, false)
725AM_CONDITIONAL(HAVE_PULSE, false)
725define([CHECK_MODULE_MIXER], 726define([CHECK_MODULE_MIXER],
726[ 727[
727 if test "x$enable_alsa" = "x" || test "x$enable_alsa" = "xdefault" || test "x$enable_alsa" = "xyes"; then 728 AC_E_CHECK_PKG(ALSA, [alsa >= 1.0.8],
728 AC_E_CHECK_PKG(ALSA, [alsa >= 1.0.8], 729 [ ], [ ])
729 [ SOUND_CFLAGS="$ALSA_CFLAGS -DHAVE_ALSA $SOUND_CFLAGS" 730 AC_E_CHECK_PKG([PULSE], [libpulse-simple libpulse],
730 SOUND_LIBS="$ALSA_LIBS $SOUND_LDFLAGS" 731 [ ], [ ])
731 ],
732 [ if test "x$enable_alsa" = "xyes"; then
733 AC_MSG_ERROR([alsa library >= 1.0.8 not found])
734 else
735 AC_MSG_WARN([alsa library development files not present. no alsa support.])
736 fi
737 ])
738 else
739 have_alsa=no
740 fi
741
742 if test "$have_alsa" = "yes"; then
743 AC_DEFINE(HAVE_ALSA, 1, [Define if the ALSA output plugin should be built])
744 else
745 have_alsa=no
746 fi
747]) 732])
748 733
749SHM_OPEN_LIBS="" 734SHM_OPEN_LIBS=""
diff --git a/po/POTFILES.in b/po/POTFILES.in
index c59c782..890250e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -238,10 +238,8 @@ src/modules/ibox/e_mod_config.c
238src/modules/ibox/e_mod_main.c 238src/modules/ibox/e_mod_main.c
239src/modules/lokker/e_mod_main.c 239src/modules/lokker/e_mod_main.c
240src/modules/lokker/lokker.c 240src/modules/lokker/lokker.c
241src/modules/mixer/app_mixer.c
242src/modules/mixer/conf_gadget.c
243src/modules/mixer/conf_module.c
244src/modules/mixer/e_mod_main.c 241src/modules/mixer/e_mod_main.c
242src/modules/mixer/e_mod_config.c
245src/modules/music-control/e_mod_main.c 243src/modules/music-control/e_mod_main.c
246src/modules/music-control/ui.c 244src/modules/music-control/ui.c
247src/modules/notification/e_mod_config.c 245src/modules/notification/e_mod_config.c
diff --git a/src/modules/Makefile_mixer.mk b/src/modules/Makefile_mixer.mk
index c5815d9..9540171 100644
--- a/src/modules/Makefile_mixer.mk
+++ b/src/modules/Makefile_mixer.mk
@@ -5,42 +5,36 @@ mixerdir = $(MDIR)/mixer
5mixer_DATA = src/modules/mixer/e-module-mixer.edj \ 5mixer_DATA = src/modules/mixer/e-module-mixer.edj \
6 src/modules/mixer/module.desktop 6 src/modules/mixer/module.desktop
7 7
8
9mixerpkgdir = $(MDIR)/mixer/$(MODULE_ARCH) 8mixerpkgdir = $(MDIR)/mixer/$(MODULE_ARCH)
10mixerpkg_LTLIBRARIES = src/modules/mixer/module.la 9mixerpkg_LTLIBRARIES = src/modules/mixer/module.la
11 10
12src_modules_mixer_module_la_CPPFLAGS = $(MOD_CPPFLAGS) @SOUND_CFLAGS@ 11emixerlib = src/modules/mixer/lib/emix.c src/modules/mixer/lib/emix.h
13
14src_modules_mixer_module_la_LDFLAGS = $(MOD_LDFLAGS)
15src_modules_mixer_module_la_SOURCES = src/modules/mixer/e_mod_main.c \
16 src/modules/mixer/e_mod_main.h \
17 src/modules/mixer/e_mod_mixer.h \
18 src/modules/mixer/e_mod_mixer.c \
19 src/modules/mixer/app_mixer.c \
20 src/modules/mixer/conf_gadget.c \
21 src/modules/mixer/conf_module.c \
22 src/modules/mixer/msg.c \
23 src/modules/mixer/Pulse.h \
24 src/modules/mixer/pa.h \
25 src/modules/mixer/pa.c \
26 src/modules/mixer/serial.c \
27 src/modules/mixer/sink.c \
28 src/modules/mixer/sys_pulse.c \
29 src/modules/mixer/tag.c
30 12
31if HAVE_ALSA 13if HAVE_ALSA
32src_modules_mixer_module_la_SOURCES += src/modules/mixer/sys_alsa.c 14emixerlib += src/modules/mixer/lib/backends/alsa/alsa.c
33else
34src_modules_mixer_module_la_SOURCES += src/modules/mixer/sys_dummy.c
35endif 15endif
36 16
37src_modules_mixer_module_la_LIBADD = $(MOD_LIBS) @SOUND_LIBS@ 17if HAVE_PULSE
38 18emixerlib += src/modules/mixer/lib/backends/pulseaudio/pulse_ml.c
39if HAVE_ENOTIFY 19emixerlib += src/modules/mixer/lib/backends/pulseaudio/pulse.c
40src_modules_mixer_module_la_CPPFLAGS += @ENOTIFY_CFLAGS@
41src_modules_mixer_module_la_LIBADD += @ENOTIFY_LIBS@
42endif 20endif
43 21
22src_modules_mixer_emixerdir = $(mixerpkgdir)
23src_modules_mixer_emixer_PROGRAMS = src/modules/mixer/emixer
24src_modules_mixer_emixer_SOURCES = src/modules/mixer/emixer.c \
25 $(emixerlib)
26src_modules_mixer_emixer_CPPFLAGS = $(MOD_CPPFLAGS) @e_cflags@ -I$(top_srcdir)/src/modules/mixer/lib
27src_modules_mixer_emixer_LDADD = $(MOD_LIBS) @PULSE_LIBS@ @ALSA_LIBS@
28
29src_modules_mixer_module_la_CPPFLAGS = $(MOD_CPPFLAGS) @e_cflags@ @ALSA_CFLAGS@ @PULSE_CFLAGS@ -I$(top_srcdir)/src/modules/mixer/lib
30src_modules_mixer_module_la_LDFLAGS = $(MOD_LDFLAGS)
31src_modules_mixer_module_la_SOURCES = src/modules/mixer/e_mod_main.c \
32 src/modules/mixer/e_mod_main.h \
33 src/modules/mixer/e_mod_config.c \
34 src/modules/mixer/e_mod_config.h \
35 $(emixerlib)
36src_modules_mixer_module_la_LIBADD = $(MOD_LIBS) @PULSE_LIBS@ @ALSA_LIBS@
37
44PHONIES += mixer install-mixer 38PHONIES += mixer install-mixer
45mixer: $(mixerpkg_LTLIBRARIES) $(mixer_DATA) 39mixer: $(mixerpkg_LTLIBRARIES) $(mixer_DATA)
46install-mixer: install-mixerDATA install-mixerpkgLTLIBRARIES 40install-mixer: install-mixerDATA install-mixerpkgLTLIBRARIES
diff --git a/src/modules/mixer/.gitignore b/src/modules/mixer/.gitignore
new file mode 100644
index 0000000..aaded20
--- /dev/null
+++ b/src/modules/mixer/.gitignore
@@ -0,0 +1 @@
emixer
diff --git a/src/modules/mixer/e_mod_config.c b/src/modules/mixer/e_mod_config.c
new file mode 100644
index 0000000..057b59a
--- /dev/null
+++ b/src/modules/mixer/e_mod_config.c
@@ -0,0 +1,201 @@
1#include "e.h"
2#include "e_mod_config.h"
3#include "e_mod_main.h"
4#include "emix.h"
5
6typedef struct _Emix_Config
7{
8 const char *backend;
9 int notify;
10 int mute;
11
12 emix_config_backend_changed cb;
13 const void *userdata;
14} Emix_Config;
15
16struct _E_Config_Dialog_Data
17{
18 Emix_Config config;
19 Evas_Object *list;
20};
21
22static E_Config_DD *cd;
23static Emix_Config *_config;
24
25static E_Config_DD*
26_emix_config_dd_new(void)
27{
28 E_Config_DD *result = E_CONFIG_DD_NEW("Emix_Config", Emix_Config);
29
30 E_CONFIG_VAL(result, Emix_Config, backend, STR);
31 E_CONFIG_VAL(result, Emix_Config, notify, INT);
32 E_CONFIG_VAL(result, Emix_Config, mute, INT);
33
34 return result;
35}
36
37const char *
38emix_config_backend_get(void)
39{
40 return _config->backend;
41}
42
43void
44emix_config_backend_set(const char *backend)
45{
46 eina_stringshare_replace(&_config->backend, backend);
47 e_config_domain_save("module.emix", cd, _config);
48}
49
50Eina_Bool
51emix_config_notify_get(void)
52{
53 return _config->notify;
54}
55
56Eina_Bool
57emix_config_desklock_mute_get(void)
58{
59 return _config->mute;
60}
61
62static void
63_config_set(Emix_Config *config)
64{
65 if ((config->backend) && (_config->backend != config->backend))
66 eina_stringshare_replace(&_config->backend, config->backend);
67
68 _config->notify = config->notify;
69 _config->mute = config->mute;
70
71 DBG("SAVING CONFIG %s %d %d", _config->backend, config->notify,
72 config->mute);
73 e_config_domain_save("module.emix", cd, config);
74}
75
76void
77emix_config_init(emix_config_backend_changed cb, const void *userdata)
78{
79 const Eina_List *l;
80
81 EINA_SAFETY_ON_FALSE_RETURN(emix_init());
82 cd = _emix_config_dd_new();
83 _config = e_config_domain_load("module.emix", cd);
84 if (!_config)
85 {
86 _config = E_NEW(Emix_Config, 1);
87 l = emix_backends_available();
88 if (l)
89 _config->backend = eina_stringshare_add(l->data);
90 }
91
92 _config->cb = cb;
93 _config->userdata = userdata;
94 DBG("Config loaded, backend to use: %s", _config->backend);
95}
96
97void
98emix_config_shutdown(void)
99{
100 E_CONFIG_DD_FREE(cd);
101 if (_config->backend)
102 eina_stringshare_del(_config->backend);
103 free(_config);
104 emix_shutdown();
105}
106
107static void*
108_create_data(E_Config_Dialog *cfg EINA_UNUSED)
109{
110 E_Config_Dialog_Data *d;
111
112 d = E_NEW(E_Config_Dialog_Data, 1);
113 d->config.backend = eina_stringshare_add(_config->backend);
114 d->config.notify = _config->notify;
115 d->config.mute = _config->mute;
116
117 return d;
118}
119
120static void
121_free_data(E_Config_Dialog *c EINA_UNUSED, E_Config_Dialog_Data *cf)
122{
123 eina_stringshare_del(cf->config.backend);
124 free(cf);
125}
126
127static Evas_Object *
128_basic_create_widgets(E_Config_Dialog *cfd EINA_UNUSED, Evas *evas,
129 E_Config_Dialog_Data *cfdata)
130{
131 Evas_Object *o, *l;
132 const Eina_List *node;
133 char *name;
134 int i = 0;
135
136 o = e_widget_list_add(evas, 0, 0);
137
138 l = e_widget_check_add(evas, "Notify on volume change", &cfdata->config.notify);
139 e_widget_list_object_append(o, l, 0, 0, 0);
140
141 l = e_widget_check_add(evas, "Mute on lock", &cfdata->config.mute);
142 e_widget_list_object_append(o, l, 0, 0, 0);
143
144 l = e_widget_label_add(evas, "Backend to use:");
145 e_widget_list_object_append(o, l, 0, 0, 0);
146
147 cfdata->list = l = e_widget_ilist_add(evas, 0, 0, NULL);
148 e_widget_ilist_multi_select_set(l, EINA_FALSE);
149 e_widget_size_min_set(l, 100, 100);
150 EINA_LIST_FOREACH(emix_backends_available(), node, name)
151 {
152 e_widget_ilist_append(l, NULL, name, NULL, NULL, NULL);
153 i ++;
154 if (_config->backend && !strcmp(_config->backend, name))
155 e_widget_ilist_selected_set(l, i);
156 }
157 e_widget_ilist_go(l);
158 e_widget_ilist_thaw(l);
159 e_widget_list_object_append(o, l, 1, 1, 0);
160
161 return o;
162}
163
164static int
165_basic_apply_data(E_Config_Dialog *cfd EINA_UNUSED,
166 E_Config_Dialog_Data *cfdata)
167{
168 char *new_backend = eina_list_nth(
169 emix_backends_available(),
170 e_widget_ilist_selected_get(cfdata->list));
171
172 eina_stringshare_replace(&cfdata->config.backend, new_backend);
173
174 _config_set(&cfdata->config);
175 if (_config->cb)
176 _config->cb(new_backend, (void *)_config->userdata);
177 return 1;
178}
179
180E_Config_Dialog*
181emix_config_popup_new(Evas_Object *comp, const char *p EINA_UNUSED)
182{
183 E_Config_Dialog *cfd;
184 E_Config_Dialog_View *v;
185
186 if (e_config_dialog_find("E", "windows/emix"))
187 return NULL;
188
189 v = E_NEW(E_Config_Dialog_View, 1);
190 v->create_cfdata = _create_data;
191 v->free_cfdata = _free_data;
192 v->basic.apply_cfdata = _basic_apply_data;
193 v->basic.create_widgets = _basic_create_widgets;
194
195 cfd = e_config_dialog_new(comp,
196 "Emix Configuration",
197 "E", "windows/emix",
198 NULL,
199 0, v, NULL);
200 return cfd;
201}
diff --git a/src/modules/mixer/e_mod_config.h b/src/modules/mixer/e_mod_config.h
new file mode 100644
index 0000000..c725753
--- /dev/null
+++ b/src/modules/mixer/e_mod_config.h
@@ -0,0 +1,18 @@
1#ifndef E_MOD_CONFIG_H
2#define E_MOD_CONFIG_H
3
4#include <e.h>
5
6typedef void (*emix_config_backend_changed)(const char *backend, void *data);
7typedef void (*emix_config_meter_changed)(Eina_Bool enable, void *data);
8
9void emix_config_init(emix_config_backend_changed cb, const void *userdata);
10void emix_config_shutdown(void);
11const char *emix_config_backend_get(void);
12void emix_config_backend_set(const char *backend);
13Eina_Bool emix_config_desklock_mute_get(void);
14Eina_Bool emix_config_meter_get(void);
15Eina_Bool emix_config_notify_get(void);
16E_Config_Dialog* emix_config_popup_new(Evas_Object *comp, const char*p);
17
18#endif
diff --git a/src/modules/mixer/e_mod_main.c b/src/modules/mixer/e_mod_main.c
new file mode 100644
index 0000000..0a7c4fb
--- /dev/null
+++ b/src/modules/mixer/e_mod_main.c
@@ -0,0 +1,817 @@
1#include <e.h>
2#include <Eina.h>
3#include "emix.h"
4#include "e_mod_main.h"
5#include "e_mod_config.h"
6
7#define VOLUME_STEP 5
8
9int _e_emix_log_domain;
10
11/* module requirements */
12E_API E_Module_Api e_modapi =
13 {
14 E_MODULE_API_VERSION,
15 "Mixer"
16 };
17
18/* necessary forward delcaration */
19static E_Gadcon_Client *_gc_init(E_Gadcon *gc, const char *name,
20 const char *id, const char *style);
21static void _gc_shutdown(E_Gadcon_Client *gcc);
22static void _gc_orient(E_Gadcon_Client *gcc,
23 E_Gadcon_Orient orient);
24static const char *_gc_label(const E_Gadcon_Client_Class *client_class);
25static Evas_Object *_gc_icon(const E_Gadcon_Client_Class *client_class,
26 Evas *evas);
27static const char *_gc_id_new(const E_Gadcon_Client_Class *client_class);
28
29static const E_Gadcon_Client_Class _gadcon_class =
30 {
31 GADCON_CLIENT_CLASS_VERSION,
32 "emix",
33 {
34 _gc_init, _gc_shutdown,
35 _gc_orient, _gc_label, _gc_icon, _gc_id_new, NULL,
36 e_gadcon_site_is_not_toolbar
37 },
38 E_GADCON_CLIENT_STYLE_PLAIN
39 };
40
41typedef struct _Context Context;
42struct _Context
43{
44 char *theme;
45 Ecore_Exe *emixer;
46 Ecore_Event_Handler *desklock_handler;
47 Ecore_Event_Handler *emix_event_handler;
48 const Emix_Sink *sink_default;
49 E_Module *module;
50 Eina_List *instances;
51 E_Menu *menu;
52 unsigned int notification_id;
53
54 struct {
55 E_Action *incr;
56 E_Action *decr;
57 E_Action *mute;
58 } actions;
59};
60
61typedef struct _Instance Instance;
62struct _Instance
63{
64 E_Gadcon_Client *gcc;
65 E_Gadcon_Orient orient;
66
67 E_Gadcon_Popup *popup;
68 Evas *evas;
69 Evas_Object *gadget;
70 Evas_Object *list;
71 Evas_Object *slider;
72 Evas_Object *check;
73
74 Eina_Bool mute;
75};
76
77static Context *mixer_context = NULL;
78
79static void
80_notify_cb(void *data EINA_UNUSED, unsigned int id)
81{
82 mixer_context->notification_id = id;
83}
84
85static void
86_notify(const int val)
87{
88 E_Notification_Notify n;
89 char *icon, buf[56];
90 int ret;
91
92 if (!emix_config_notify_get())
93 return;
94
95 memset(&n, 0, sizeof(E_Notification_Notify));
96 if (val > EMIX_VOLUME_MAX || val < 0)
97 return;
98
99 ret = snprintf(buf, (sizeof(buf) - 1), "%s: %d%%", _("New volume"), val);
100 if ((ret < 0) || ((unsigned int)ret > sizeof(buf)))
101 return;
102 //Names are taken from FDO icon naming scheme
103 if (val == 0)
104 icon = "audio-volume-muted";
105 else if ((val > 33) && (val < 66))
106 icon = "audio-volume-medium";
107 else if (val < 33)
108 icon = "audio-volume-low";
109 else
110 icon = "audio-volume-high";
111
112 n.app_name = _("Emix");
113 n.replaces_id = mixer_context->notification_id;
114 n.icon.icon = icon;
115 n.summary = _("Volume changed");
116 n.body = buf;
117 n.timeout = 2000;
118 e_notification_client_send(&n, _notify_cb, NULL);
119}
120
121static void
122_mixer_popup_update(Instance *inst, int mute, int vol)
123{
124 elm_check_state_set(inst->check, !!mute);
125 elm_slider_value_set(inst->slider, vol);
126}
127
128static void _popup_del(Instance *inst);
129
130static void
131_mixer_gadget_update(void)
132{
133 Edje_Message_Int_Set *msg;
134 Instance *inst;
135 Eina_List *l;
136
137 EINA_LIST_FOREACH(mixer_context->instances, l, inst)
138 {
139 msg = alloca(sizeof(Edje_Message_Int_Set) + (2 * sizeof(int)));
140 msg->count = 3;
141
142 if (!mixer_context->sink_default)
143 {
144 msg->val[0] = EINA_FALSE;
145 msg->val[1] = 0;
146 msg->val[2] = 0;
147 if (inst->popup)
148 _popup_del(inst);
149 }
150 else
151 {
152 int vol = 0;
153 unsigned int i = 0;
154 for (i = 0; i <
155 mixer_context->sink_default->volume.channel_count; i++)
156 vol += mixer_context->sink_default->volume.volumes[i];
157 if (mixer_context->sink_default->volume.channel_count)
158 vol /= mixer_context->sink_default->volume.channel_count;
159 msg->val[0] = mixer_context->sink_default->mute;
160 msg->val[1] = vol;
161 msg->val[2] = msg->val[1];
162 if (inst->popup)
163 _mixer_popup_update(inst, mixer_context->sink_default->mute,
164 msg->val[1]);
165 }
166 edje_object_message_send(inst->gadget, EDJE_MESSAGE_INT_SET, 0, msg);
167 edje_object_signal_emit(inst->gadget, "e,action,volume,change", "e");
168 }
169}
170
171static void
172_volume_increase_cb(E_Object *obj EINA_UNUSED, const char *params EINA_UNUSED)
173{
174 unsigned int i;
175 EINA_SAFETY_ON_NULL_RETURN(mixer_context->sink_default);
176 Emix_Volume volume;
177
178 Emix_Sink *s = (Emix_Sink *)mixer_context->sink_default;
179 volume.channel_count = s->volume.channel_count;
180 volume.volumes = calloc(s->volume.channel_count, sizeof(int));
181 for (i = 0; i < volume.channel_count; i++)
182 {
183 if (s->volume.volumes[i] < EMIX_VOLUME_MAX - VOLUME_STEP)
184 volume.volumes[i] = s->volume.volumes[i] + VOLUME_STEP;
185 else if (s->volume.volumes[i] < EMIX_VOLUME_MAX)
186 volume.volumes[i] = EMIX_VOLUME_MAX;
187 else
188 volume.volumes[i] = s->volume.volumes[i];
189 }
190
191 emix_sink_volume_set(s, volume);
192 free(volume.volumes);
193}
194
195static void
196_volume_decrease_cb(E_Object *obj EINA_UNUSED, const char *params EINA_UNUSED)
197{
198 unsigned int i;
199 EINA_SAFETY_ON_NULL_RETURN(mixer_context->sink_default);
200 Emix_Volume volume;
201
202 Emix_Sink *s = (Emix_Sink *)mixer_context->sink_default;
203 volume.channel_count = s->volume.channel_count;
204 volume.volumes = calloc(s->volume.channel_count, sizeof(int));
205 for (i = 0; i < volume.channel_count; i++)
206 {
207 if (s->volume.volumes[i] > VOLUME_STEP)
208 volume.volumes[i] = s->volume.volumes[i] - VOLUME_STEP;
209 else if (s->volume.volumes[i] < VOLUME_STEP)
210 volume.volumes[i] = 0;
211 else
212 volume.volumes[i] = s->volume.volumes[i];
213 }
214
215 emix_sink_volume_set(s, volume);
216 free(volume.volumes);
217}
218
219static void
220_volume_mute_cb(E_Object *obj EINA_UNUSED, const char *params EINA_UNUSED)
221{
222 EINA_SAFETY_ON_NULL_RETURN(mixer_context->sink_default);
223
224 Emix_Sink *s = (Emix_Sink *)mixer_context->sink_default;
225 Eina_Bool mute = !s->mute;
226 emix_sink_mute_set(s, mute);
227}
228
229static void
230_actions_register(void)
231{
232 mixer_context->actions.incr = e_action_add("volume_increase");
233 if (mixer_context->actions.incr)
234 {
235 mixer_context->actions.incr->func.go = _volume_increase_cb;
236 e_action_predef_name_set("Mixer", _("Increase Volume"),
237 "volume_increase", NULL, NULL, 0);
238 }
239
240 mixer_context->actions.decr = e_action_add("volume_decrease");
241 if (mixer_context->actions.decr)
242 {
243 mixer_context->actions.decr->func.go = _volume_decrease_cb;
244 e_action_predef_name_set("Mixer", _("Decrease Volume"),
245 "volume_decrease", NULL, NULL, 0);
246 }
247
248 mixer_context->actions.mute = e_action_add("volume_mute");
249 if (mixer_context->actions.mute)
250 {
251 mixer_context->actions.mute->func.go = _volume_mute_cb;
252 e_action_predef_name_set("Mixer", _("Mute volume"), "volume_mute",
253 NULL, NULL, 0);
254 }
255
256 e_comp_canvas_keys_ungrab();
257 e_comp_canvas_keys_grab();
258}
259
260static void
261_actions_unregister(void)
262{
263 if (mixer_context->actions.incr)
264 {
265 e_action_predef_name_del("Mixer", _("Increase Volume"));
266 e_action_del("volume_increase");
267 mixer_context->actions.incr = NULL;
268 }
269
270 if (mixer_context->actions.decr)
271 {
272 e_action_predef_name_del("Mixer", _("Decrease Volume"));
273 e_action_del("volume_decrease");
274 mixer_context->actions.decr = NULL;
275 }
276
277 if (mixer_context->actions.mute)
278 {
279 e_action_predef_name_del("Mixer", _("Mute Volume"));
280 e_action_del("volume_mute");
281 mixer_context->actions.mute = NULL;
282 }
283
284 e_comp_canvas_keys_ungrab();
285 e_comp_canvas_keys_grab();
286}
287
288static void
289_popup_del(Instance *inst)
290{
291 inst->slider = NULL;
292 inst->check = NULL;
293 E_FREE_FUNC(inst->popup, e_object_del);
294}
295
296static void
297_popup_del_cb(void *obj)
298{
299 _popup_del(e_object_data_get(obj));
300}
301
302static void
303_popup_comp_del_cb(void *data, Evas_Object *obj EINA_UNUSED)
304{
305 Instance *inst = data;
306
307 E_FREE_FUNC(inst->popup, e_object_del);
308}
309
310static Eina_Bool
311_emixer_del_cb(void *data EINA_UNUSED, int type EINA_UNUSED,
312 void *info EINA_UNUSED)
313{
314 mixer_context->emixer = NULL;
315 if (mixer_context->emix_event_handler)
316 ecore_event_handler_del(mixer_context->emix_event_handler);
317
318 return EINA_TRUE;
319}
320
321static void
322_emixer_exec_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
323{
324 Instance *inst = data;
325 char buf[PATH_MAX];
326
327 _popup_del(inst);
328 if (mixer_context->emixer)
329 return;
330
331 snprintf(buf, sizeof(buf), "%s/%s/emixer %s",
332 e_module_dir_get(mixer_context->module),
333 MODULE_ARCH, emix_config_backend_get());
334 mixer_context->emixer = ecore_exe_run(buf, NULL);
335 if (mixer_context->emix_event_handler)
336 ecore_event_handler_del(mixer_context->emix_event_handler);
337 mixer_context->emix_event_handler =
338 ecore_event_handler_add(ECORE_EXE_EVENT_DEL, _emixer_del_cb, NULL);
339}
340
341static void
342_check_changed_cb(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED,
343 void *event EINA_UNUSED)
344{
345 Emix_Sink *s = (Emix_Sink *)mixer_context->sink_default;
346 emix_sink_mute_set(s, !s->mute);
347 /*
348 *TODO: is it really necessary ? or it will be update
349 * with the sink changed hanlder
350 */
351 _mixer_gadget_update();
352}
353
354static void
355_slider_changed_cb(void *data EINA_UNUSED, Evas_Object *obj,
356 void *event EINA_UNUSED)
357{
358 int val;
359 Emix_Volume v;
360 unsigned int i;
361 Emix_Sink *s = (Emix_Sink *)mixer_context->sink_default;
362
363 val = (int)elm_slider_value_get(obj);
364 v.volumes = calloc(s->volume.channel_count, sizeof(int));
365 v.channel_count = s->volume.channel_count;
366 for (i = 0; i < s->volume.channel_count; i++)
367 v.volumes[i] = val;
368
369 emix_sink_volume_set(s, v);
370}
371
372static Evas_Object *
373_popup_add_slider(void)
374{
375 unsigned int volume, i;
376 unsigned int channels = mixer_context->sink_default->volume.channel_count;
377
378 Evas_Object *slider = elm_slider_add(e_comp->elm);
379 evas_object_size_hint_align_set(slider, EVAS_HINT_FILL, EVAS_HINT_FILL);
380 evas_object_size_hint_weight_set(slider, EVAS_HINT_EXPAND, 0.0);
381
382 for (volume = 0, i = 0; i < channels; i++)
383 volume += mixer_context->sink_default->volume.volumes[i];
384
385 if (channels)
386 volume = volume / channels;
387
388 evas_object_show(slider);
389 elm_slider_min_max_set(slider, 0.0, (double) EMIX_VOLUME_MAX);
390 evas_object_smart_callback_add(slider, "changed", _slider_changed_cb,
391 NULL);
392
393 elm_slider_value_set(slider, volume);
394 return slider;
395}
396
397static void
398_sink_selected_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
399{
400 Emix_Sink *s = data;
401
402 mixer_context->sink_default = s;
403 _mixer_gadget_update();
404}
405
406static void
407_popup_new(Instance *inst)
408{
409 Evas_Object *button, *list, *icon;
410 Emix_Sink *s;
411 Eina_List *l;
412
413 EINA_SAFETY_ON_NULL_RETURN(mixer_context->sink_default);
414
415 inst->popup = e_gadcon_popup_new(inst->gcc, 0);
416 list = elm_box_add(e_comp->elm);
417
418 inst->list = elm_list_add(e_comp->elm);
419 evas_object_size_hint_align_set(inst->list, EVAS_HINT_FILL, EVAS_HINT_FILL);
420 evas_object_size_hint_weight_set(inst->list, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
421 evas_object_show(inst->list);
422
423 EINA_LIST_FOREACH((Eina_List *)emix_sinks_get(), l, s)
424 {
425 Elm_Object_Item *it;
426
427 it = elm_list_item_append(inst->list, s->name, NULL, NULL, _sink_selected_cb, s);
428 if (mixer_context->sink_default == s)
429 elm_list_item_selected_set(it, EINA_TRUE);
430 }
431 elm_box_pack_end(list, inst->list);
432
433 inst->slider = _popup_add_slider();
434 elm_box_pack_end(list, inst->slider);
435 evas_object_show(inst->slider);
436
437 inst->mute = (int) mixer_context->sink_default->mute;
438
439 inst->check = elm_check_add(e_comp->elm);
440 elm_object_text_set(inst->check, _("Mute"));
441 elm_check_state_pointer_set(inst->check, &(inst->mute));
442 evas_object_smart_callback_add(inst->check, "changed", _check_changed_cb,
443 NULL);
444 elm_box_pack_end(list, inst->check);
445 evas_object_show(inst->check);
446
447 icon = elm_icon_add(e_comp->elm);
448 elm_icon_standard_set(icon, "preferences-system");
449
450 button = elm_button_add(e_comp->elm);
451 evas_object_size_hint_align_set(button, EVAS_HINT_FILL, EVAS_HINT_FILL);
452 evas_object_size_hint_weight_set(button, EVAS_HINT_EXPAND, 0.0);
453 elm_object_part_content_set(button, "icon", icon);
454 evas_object_smart_callback_add(button, "clicked", _emixer_exec_cb, inst);
455 elm_box_pack_end(list, button);
456 evas_object_show(button);
457
458 evas_object_size_hint_min_set(list, 208, 208);
459
460
461 e_gadcon_popup_content_set(inst->popup, list);
462 e_comp_object_util_autoclose(inst->popup->comp_object,
463 _popup_comp_del_cb, NULL, inst);
464 e_gadcon_popup_show(inst->popup);
465 e_object_data_set(E_OBJECT(inst->popup), inst);
466 E_OBJECT_DEL_SET(inst->popup, _popup_del_cb);
467}
468
469static void
470_menu_cb(void *data, E_Menu *menu EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
471{
472 _emixer_exec_cb(data, NULL, NULL);
473}
474
475static void
476_settings_cb(void *data EINA_UNUSED, E_Menu *menu EINA_UNUSED,
477 E_Menu_Item *mi EINA_UNUSED)
478{
479 emix_config_popup_new(NULL, NULL);
480}
481
482static void
483_menu_new(Instance *inst, Evas_Event_Mouse_Down *ev)
484{
485 E_Zone *zone;
486 E_Menu *m;
487 E_Menu_Item *mi;
488 int x, y;
489
490 zone = e_zone_current_get();
491
492 m = e_menu_new();
493
494 mi = e_menu_item_new(m);
495 e_menu_item_label_set(mi, _("Advanced"));
496 e_util_menu_item_theme_icon_set(mi, "configure");
497 e_menu_item_callback_set(mi, _menu_cb, inst);
498
499 mi = e_menu_item_new(m);
500 e_menu_item_label_set(mi, _("Settings"));
501 e_util_menu_item_theme_icon_set(mi, "configure");
502 e_menu_item_callback_set(mi, _settings_cb, inst);
503
504 m = e_gadcon_client_util_menu_items_append(inst->gcc, m, 0);
505
506 e_gadcon_canvas_zone_geometry_get(inst->gcc->gadcon, &x, &y, NULL, NULL);
507 e_menu_activate_mouse(m, zone, x + ev->output.x, y + ev->output.y,
508 1, 1, E_MENU_POP_DIRECTION_AUTO, ev->timestamp);
509 evas_event_feed_mouse_up(inst->gcc->gadcon->evas, ev->button,
510 EVAS_BUTTON_NONE, ev->timestamp, NULL);
511}
512
513static void
514_mouse_down_cb(void *data, Evas *evas EINA_UNUSED,
515 Evas_Object *obj EINA_UNUSED, void *event)
516{
517 Instance *inst = data;
518 Evas_Event_Mouse_Down *ev = event;
519
520 if (ev->button == 1)
521 {
522 if (!inst->popup)
523 _popup_new(inst);
524 }
525 else if (ev->button == 2)
526 {
527 _volume_mute_cb(NULL, NULL);
528 }
529 else if (ev->button == 3)
530 {
531 _menu_new(inst, ev);
532 }
533}
534
535static void
536_mouse_wheel_cb(void *data EINA_UNUSED, Evas *evas EINA_UNUSED,
537 Evas_Object *obj EINA_UNUSED, void *event)
538{
539 Evas_Event_Mouse_Wheel *ev = event;
540
541 if (ev->z > 0)
542 _volume_decrease_cb(NULL, NULL);
543 else if (ev->z < 0)
544 _volume_increase_cb(NULL, NULL);
545}
546
547/*
548 * Gadcon functions
549 */
550static E_Gadcon_Client *
551_gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style)
552{
553 E_Gadcon_Client *gcc;
554 Instance *inst;
555
556 inst = E_NEW(Instance, 1);
557
558 inst->gadget = edje_object_add(gc->evas);
559 inst->evas = gc->evas;
560 e_theme_edje_object_set(inst->gadget,
561 "base/theme/modules/mixer",
562 "e/modules/mixer/main");
563
564 gcc = e_gadcon_client_new(gc, name, id, style, inst->gadget);
565 gcc->data = inst;
566 inst->gcc = gcc;
567
568 evas_object_event_callback_add(inst->gadget, EVAS_CALLBACK_MOUSE_DOWN,
569 _mouse_down_cb, inst);
570 evas_object_event_callback_add(inst->gadget, EVAS_CALLBACK_MOUSE_WHEEL,
571 _mouse_wheel_cb, inst);
572 mixer_context->instances = eina_list_append(mixer_context->instances, inst);
573
574 if (mixer_context->sink_default)
575 _mixer_gadget_update();
576
577 return gcc;
578}
579
580static void
581_gc_shutdown(E_Gadcon_Client *gcc)
582{
583 Instance *inst;
584
585 inst = gcc->data;
586 evas_object_del(inst->gadget);
587 mixer_context->instances = eina_list_remove(mixer_context->instances, inst);
588 free(inst);
589}
590
591static void
592_gc_orient(E_Gadcon_Client *gcc, E_Gadcon_Orient orient EINA_UNUSED)
593{
594 e_gadcon_client_aspect_set(gcc, 16, 16);
595 e_gadcon_client_min_size_set(gcc, 16, 16);
596}
597
598static const char *
599_gc_label(const E_Gadcon_Client_Class *client_class EINA_UNUSED)
600{
601 return "Mixer";
602}
603
604static Evas_Object *
605_gc_icon(const E_Gadcon_Client_Class *client_class EINA_UNUSED, Evas *evas)
606{
607 Evas_Object *o;
608 char buf[4096] = { 0 };
609
610 o = edje_object_add(evas);
611 snprintf(buf, sizeof(buf), "%s/e-module-mixer.edj",
612 e_module_dir_get(mixer_context->module));
613 edje_object_file_set(o, buf, "icon");
614
615 return o;
616}
617
618static const char *
619_gc_id_new(const E_Gadcon_Client_Class *client_class EINA_UNUSED)
620{
621 return _gadcon_class.name;
622}
623
624static void
625_sink_event(int type, void *info)
626{
627 Emix_Sink *sink = info;
628 const Eina_List *l;
629
630 if (type == EMIX_SINK_REMOVED_EVENT)
631 {
632 if (sink == mixer_context->sink_default)
633 {
634 l = emix_sinks_get();
635 mixer_context->sink_default = l->data;
636 _mixer_gadget_update();
637 }
638 }
639 else if (type == EMIX_SINK_CHANGED_EVENT)
640 {
641 if (mixer_context->sink_default == sink)
642 {
643 _mixer_gadget_update();
644 _notify(sink->mute ? 0 : sink->volume.volumes[0]);
645 }
646 }
647 else
648 {
649 DBG("Sink added");
650 }
651}
652
653static void
654_disconnected(void)
655{
656 if (mixer_context) mixer_context->sink_default = NULL;
657 _mixer_gadget_update();
658}
659
660static void
661_ready(void)
662{
663 if (emix_sink_default_support())
664 mixer_context->sink_default = emix_sink_default_get();
665 else
666 mixer_context->sink_default = emix_sinks_get()->data;
667
668 _mixer_gadget_update();
669}
670
671static void
672_events_cb(void *data EINA_UNUSED, enum Emix_Event type, void *event_info)
673{
674 switch (type)
675 {
676 case EMIX_SINK_ADDED_EVENT:
677 case EMIX_SINK_CHANGED_EVENT:
678 case EMIX_SINK_REMOVED_EVENT:
679 _sink_event(type, event_info);
680 break;
681 case EMIX_DISCONNECTED_EVENT:
682 _disconnected();
683 break;
684 case EMIX_READY_EVENT:
685 _ready();
686 break;
687 default:
688 break;
689 }
690}
691
692static Eina_Bool
693_desklock_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *info)
694{
695 E_Event_Desklock *ev = info;
696 static Eina_Bool _was_mute = EINA_FALSE;
697
698 if (emix_config_desklock_mute_get() == EINA_FALSE)
699 return ECORE_CALLBACK_PASS_ON;
700
701 if (ev->on)
702 {
703 _was_mute = mixer_context->sink_default->mute;
704 if (!_was_mute)
705 emix_sink_mute_set((Emix_Sink *)mixer_context->sink_default, EINA_TRUE);
706 }
707 else
708 {
709 if (!_was_mute)
710 emix_sink_mute_set((Emix_Sink *)mixer_context->sink_default, EINA_FALSE);
711 }
712
713 return ECORE_CALLBACK_PASS_ON;
714}
715
716static void
717_backend_changed(const char *backend, void *data EINA_UNUSED)
718{
719 _disconnected();
720
721 if (emix_backend_set(backend) == EINA_FALSE)
722 ERR("Could not load backend: %s", backend);
723}
724
725E_API void *
726e_modapi_init(E_Module *m)
727{
728 Eina_List *l;
729 char buf[4096];
730 const char *backend;
731 Eina_Bool backend_loaded = EINA_FALSE;
732
733 _e_emix_log_domain = eina_log_domain_register("mixer", EINA_COLOR_RED);
734
735 if (!mixer_context)
736 {
737 mixer_context = E_NEW(Context, 1);
738
739 mixer_context->desklock_handler =
740 ecore_event_handler_add(E_EVENT_DESKLOCK, _desklock_cb, NULL);
741 mixer_context->module = m;
742 snprintf(buf, sizeof(buf), "%s/mixer.edj",
743 e_module_dir_get(mixer_context->module));
744 mixer_context->theme = strdup(buf);
745 }
746
747
748 EINA_SAFETY_ON_FALSE_RETURN_VAL(emix_init(), NULL);
749 emix_config_init(_backend_changed, NULL);
750 emix_event_callback_add(_events_cb, NULL);
751
752 backend = emix_config_backend_get();
753 if (backend && emix_backend_set(backend))
754 backend_loaded = EINA_TRUE;
755 else
756 {
757 if (backend)
758 WRN("Could not load %s, trying another one ...", backend);
759 EINA_LIST_FOREACH((Eina_List *)emix_backends_available(), l,
760 backend)
761 {
762 if (emix_backend_set(backend) == EINA_TRUE)
763 {
764 DBG("Loaded backend: %s!", backend);
765 backend_loaded = EINA_TRUE;
766 emix_config_backend_set(backend);
767 break;
768 }
769 }
770 }
771
772 if (!backend_loaded) goto err;
773
774 e_configure_registry_category_add("extensions", 90, _("Extensions"), NULL,
775 "preferences-extensions");
776 e_configure_registry_item_add("extensions/emix", 30, _("Mixer"), NULL,
777 "preferences-desktop-mixer",
778 emix_config_popup_new);
779
780 if (emix_sink_default_support())
781 mixer_context->sink_default = emix_sink_default_get();
782
783 e_gadcon_provider_register(&_gadcon_class);
784 _actions_register();
785
786 return m;
787
788err:
789 emix_config_shutdown();
790 emix_shutdown();
791 return NULL;
792}
793
794E_API int
795e_modapi_shutdown(E_Module *m EINA_UNUSED)
796{
797 _actions_unregister();
798 e_gadcon_provider_unregister((const E_Gadcon_Client_Class *)&_gadcon_class);
799
800 if (mixer_context)
801 {
802 free(mixer_context->theme);
803 E_FREE(mixer_context);
804 }
805
806 emix_event_callback_del(_events_cb);
807 emix_shutdown();
808 emix_config_shutdown();
809 return 1;
810}
811
812E_API int
813e_modapi_save(E_Module *m EINA_UNUSED)
814{
815 return 1;
816}
817
diff --git a/src/modules/mixer/e_mod_main.h b/src/modules/mixer/e_mod_main.h
new file mode 100644
index 0000000..bb624a1
--- /dev/null
+++ b/src/modules/mixer/e_mod_main.h
@@ -0,0 +1,25 @@
1#ifndef _E_MOD_MAIN_H_
2#define _E_MOD_MAIN_H_
3
4#define CONFIG_VERSION 1
5
6extern int _e_emix_log_domain;
7
8#undef DBG
9#undef INF
10#undef WRN
11#undef ERR
12#undef CRIT
13#define DBG(...) EINA_LOG_DOM_DBG(_e_emix_log_domain, __VA_ARGS__)
14#define INF(...) EINA_LOG_DOM_INF(_e_emix_log_domain, __VA_ARGS__)
15#define WRN(...) EINA_LOG_DOM_WARN(_e_emix_log_domain, __VA_ARGS__)
16#define ERR(...) EINA_LOG_DOM_ERR(_e_emix_log_domain, __VA_ARGS__)
17#define CRIT(...) EINA_LOG_DOM_CRIT(_e_emix_log_domain, __VA_ARGS__)
18
19E_API extern E_Module_Api e_modapi;
20
21E_API void *e_modapi_init(E_Module *m);
22E_API int e_modapi_shutdown(E_Module *m);
23E_API int e_modapi_save(E_Module *m);
24
25#endif /* _E_MOD_MAIN_H_ */
diff --git a/src/modules/mixer/emixer.c b/src/modules/mixer/emixer.c
new file mode 100644
index 0000000..61cceaa
--- /dev/null
+++ b/src/modules/mixer/emixer.c
@@ -0,0 +1,727 @@
1#include <Elementary.h>
2#include "emix.h"
3
4Evas_Object *win;
5Evas_Object *source_scroller, *sink_input_scroller, *sink_scroller;
6Evas_Object *source_box, *sink_input_box, *sink_box;
7
8Eina_List *source_list = NULL, *sink_input_list = NULL, *sink_list = NULL;
9
10//////////////////////////////////////////////////////////////////////////////
11
12static Eina_Bool
13_backend_init(const char *back)
14{
15 const Eina_List *l;
16 const char *name;
17
18 if (!back) back = "PULSEAUDIO";
19 if (emix_backend_set(back)) return EINA_TRUE;
20 EINA_LIST_FOREACH(emix_backends_available(), l, name)
21 {
22 if (emix_backend_set(name)) return EINA_TRUE;
23 }
24 return EINA_FALSE;
25}
26
27//////////////////////////////////////////////////////////////////////////////
28
29#define VOLSET(vol, srcvol, target, func) \
30 do { \
31 Emix_Volume _v; \
32 _v.channel_count = srcvol.channel_count; \
33 _v.volumes = calloc(srcvol.channel_count, sizeof(int)); \
34 if (_v.volumes) { \
35 unsigned int _i; \
36 for (_i = 0; _i < _v.channel_count; _i++) _v.volumes[_i] = vol; \
37 func(target, _v); \
38 free(_v.volumes); \
39 } \
40 } while (0)
41
42
43//////////////////////////////////////////////////////////////////////////////
44static void
45_cb_sink_port_change(void *data,
46 Evas_Object *obj,
47 void *event_info EINA_UNUSED)
48{
49 Emix_Port *port = data;
50 Evas_Object *bxv = evas_object_data_get(obj, "parent");
51 Emix_Sink *sink = evas_object_data_get(bxv, "sink");
52 elm_object_text_set(obj, port->description);
53 emix_sink_port_set(sink, port);
54}
55
56static void
57_cb_sink_volume_change(void *data,
58 Evas_Object *obj,
59 void *event_info EINA_UNUSED)
60{
61 Evas_Object *bxv = data;
62 Emix_Sink *sink = evas_object_data_get(bxv, "sink");
63 double vol = elm_slider_value_get(obj);
64 VOLSET(vol, sink->volume, sink, emix_sink_volume_set);
65}
66
67static void
68_cb_sink_mute_change(void *data,
69 Evas_Object *obj,
70 void *event_info EINA_UNUSED)
71{
72 Evas_Object *bxv = data;
73 Emix_Sink *sink = evas_object_data_get(bxv, "sink");
74 Evas_Object *sl = evas_object_data_get(bxv, "volume");
75 Eina_Bool mute = elm_check_state_get(obj);
76 elm_object_disabled_set(sl, mute);
77 emix_sink_mute_set(sink, mute);
78}
79
80static void
81_emix_sink_add(Emix_Sink *sink)
82{
83 Evas_Object *bxv, *bx, *lb, *ck, *sl, *hv, *sep;
84 const Eina_List *l;
85 Emix_Port *port;
86
87 bxv = elm_box_add(win);
88 sink_list = eina_list_append(sink_list, bxv);
89 evas_object_data_set(bxv, "sink", sink);
90 evas_object_size_hint_weight_set(bxv, EVAS_HINT_EXPAND, 0.0);
91 evas_object_size_hint_align_set(bxv, EVAS_HINT_FILL, 0.0);
92
93 bx = elm_box_add(win);
94 elm_box_horizontal_set(bx, EINA_TRUE);
95 evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
96 evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.0);
97 elm_box_pack_end(bxv, bx);
98 evas_object_show(bx);
99
100 lb = elm_label_add(win);
101 elm_object_text_set(lb, sink->name);
102 evas_object_size_hint_weight_set(lb, EVAS_HINT_EXPAND, 0.5);
103 evas_object_size_hint_align_set(lb, 0.0, 0.5);
104 elm_box_pack_end(bx, lb);
105 evas_object_show(lb);
106
107 hv = elm_hoversel_add(win);
108 evas_object_data_set(hv, "parent", bxv);
109 evas_object_data_set(bxv, "port", hv);
110 elm_hoversel_hover_parent_set(hv, win);
111 EINA_LIST_FOREACH(sink->ports, l, port)
112 {
113 elm_hoversel_item_add(hv, port->description,
114 NULL, ELM_ICON_NONE,
115 _cb_sink_port_change, port);
116 if (port->active) elm_object_text_set(hv, port->description);
117 }
118 evas_object_size_hint_weight_set(hv, 0.0, 0.5);
119 evas_object_size_hint_align_set(hv, EVAS_HINT_FILL, 0.5);
120 elm_box_pack_end(bx, hv);
121 evas_object_show(hv);
122
123 bx = elm_box_add(win);
124 elm_box_horizontal_set(bx, EINA_TRUE);
125 evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
126 evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.0);
127 elm_box_pack_end(bxv, bx);
128 evas_object_show(bx);
129
130 sl = elm_slider_add(win);
131 evas_object_data_set(bxv, "volume", sl);
132 elm_slider_min_max_set(sl, 0.0, 100.0);
133 elm_slider_span_size_set(sl, 100 * elm_config_scale_get());
134 elm_slider_unit_format_set(sl, "%1.0f");
135 elm_slider_indicator_format_set(sl, "%1.0f");
136 evas_object_size_hint_weight_set(sl, EVAS_HINT_EXPAND, 0.5);
137 evas_object_size_hint_align_set(sl, EVAS_HINT_FILL, 0.5);
138 elm_slider_value_set(sl, sink->volume.volumes[0]);
139 elm_box_pack_end(bx, sl);
140 evas_object_show(sl);
141 evas_object_smart_callback_add(sl, "changed", _cb_sink_volume_change, bxv);
142
143 ck = elm_check_add(win);
144 evas_object_data_set(bxv, "mute", ck);
145 elm_object_text_set(ck, "Mute");
146 elm_check_state_set(ck, sink->mute);
147 elm_object_disabled_set(sl, sink->mute);
148 elm_box_pack_end(bx, ck);
149 evas_object_show(ck);
150 evas_object_smart_callback_add(ck, "changed", _cb_sink_mute_change, bxv);
151
152 sep = elm_separator_add(win);
153 elm_separator_horizontal_set(sep, EINA_TRUE);
154 evas_object_size_hint_weight_set(sep, EVAS_HINT_EXPAND, 0.0);
155 evas_object_size_hint_align_set(sep, EVAS_HINT_FILL, 0.0);
156 elm_box_pack_end(bxv, sep);
157 evas_object_show(sep);
158
159 elm_box_pack_end(sink_box, bxv);
160 evas_object_show(bxv);
161}
162
163static void
164_emix_sink_del(Emix_Sink *sink)
165{
166 Eina_List *l;
167 Evas_Object *bxv;
168 EINA_LIST_FOREACH(sink_list, l, bxv)
169 {
170 if (evas_object_data_get(bxv, "sink") == sink)
171 {
172 sink_list = eina_list_remove_list(sink_list, l);
173 evas_object_del(bxv);
174 return;
175 }
176 }
177}
178
179static void
180_emix_sink_change(Emix_Sink *sink)
181{
182 const Eina_List *l;
183 Evas_Object *bxv, *hv, *ck, *sl;
184 Emix_Port *port;
185
186 EINA_LIST_FOREACH(sink_list, l, bxv)
187 {
188 if (evas_object_data_get(bxv, "sink") == sink) break;
189 }
190 if (!l) return;
191 hv = evas_object_data_get(bxv, "port");
192 elm_hoversel_clear(hv);
193 EINA_LIST_FOREACH(sink->ports, l, port)
194 {
195 elm_hoversel_item_add(hv, port->description,
196 NULL, ELM_ICON_NONE,
197 _cb_sink_port_change, port);
198 if (port->active) elm_object_text_set(hv, port->description);
199 }
200 sl = evas_object_data_get(bxv, "volume");
201 elm_slider_value_set(sl, sink->volume.volumes[0]);
202
203 ck = evas_object_data_get(bxv, "mute");
204 elm_check_state_set(ck, sink->mute);
205 elm_object_disabled_set(sl, sink->mute);
206}
207
208//////////////////////////////////////////////////////////////////////////////
209
210static void
211_cb_sink_input_port_change(void *data,
212 Evas_Object *obj,
213 void *event_info EINA_UNUSED)
214{
215 Emix_Sink *sink = data;
216 Evas_Object *bxv = evas_object_data_get(obj, "parent");
217 Emix_Sink_Input *input = evas_object_data_get(bxv, "input");
218 elm_object_text_set(obj, sink->name);
219 emix_sink_input_sink_change(input, sink);
220}
221
222static void
223_cb_sink_input_volume_change(void *data,
224 Evas_Object *obj,
225 void *event_info EINA_UNUSED)
226{
227 Evas_Object *bxv = data;
228 Emix_Sink_Input *input = evas_object_data_get(bxv, "input");
229 double vol = elm_slider_value_get(obj);
230 VOLSET(vol, input->volume, input, emix_sink_input_volume_set);
231}
232
233static void
234_cb_sink_input_mute_change(void *data,
235 Evas_Object *obj,
236 void *event_info EINA_UNUSED)
237{
238 Evas_Object *bxv = data;
239 Emix_Sink_Input *input = evas_object_data_get(bxv, "input");
240 Evas_Object *sl = evas_object_data_get(bxv, "volume");
241 Eina_Bool mute = elm_check_state_get(obj);
242 elm_object_disabled_set(sl, mute);
243 emix_sink_input_mute_set(input, mute);
244}
245
246static void
247_emix_sink_input_add(Emix_Sink_Input *input)
248{
249 Evas_Object *bxv, *bx, *lb, *ck, *sl, *hv, *sep;
250 const Eina_List *l;
251 Emix_Sink *sink;
252
253 bxv = elm_box_add(win);
254 sink_input_list = eina_list_append(sink_input_list, bxv);
255 evas_object_data_set(bxv, "input", input);
256 evas_object_size_hint_weight_set(bxv, EVAS_HINT_EXPAND, 0.0);
257 evas_object_size_hint_align_set(bxv, EVAS_HINT_FILL, 0.0);
258
259 bx = elm_box_add(win);
260 elm_box_horizontal_set(bx, EINA_TRUE);
261 evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
262 evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.0);
263 elm_box_pack_end(bxv, bx);
264 evas_object_show(bx);
265
266 lb = elm_label_add(win);
267 elm_object_text_set(lb, input->name);
268 evas_object_size_hint_weight_set(lb, EVAS_HINT_EXPAND, 0.5);
269 evas_object_size_hint_align_set(lb, 0.0, 0.5);
270 elm_box_pack_end(bx, lb);
271 evas_object_show(lb);
272
273 hv = elm_hoversel_add(win);
274 evas_object_data_set(hv, "parent", bxv);
275 evas_object_data_set(bxv, "port", hv);
276 elm_hoversel_hover_parent_set(hv, win);
277 EINA_LIST_FOREACH(emix_sinks_get(), l, sink)
278 {
279 elm_hoversel_item_add(hv, sink->name,
280 NULL, ELM_ICON_NONE,
281 _cb_sink_input_port_change, sink);
282 if (input->sink == sink) elm_object_text_set(hv, sink->name);
283 }
284 evas_object_size_hint_weight_set(hv, 0.0, 0.5);
285 evas_object_size_hint_align_set(hv, EVAS_HINT_FILL, 0.5);
286 elm_box_pack_end(bx, hv);
287 evas_object_show(hv);
288
289 bx = elm_box_add(win);
290 elm_box_horizontal_set(bx, EINA_TRUE);
291 evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
292 evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.0);
293 elm_box_pack_end(bxv, bx);
294 evas_object_show(bx);
295
296 sl = elm_slider_add(win);
297 evas_object_data_set(bxv, "volume", sl);
298 elm_slider_min_max_set(sl, 0.0, 100.0);
299 elm_slider_span_size_set(sl, 100 * elm_config_scale_get());
300 elm_slider_unit_format_set(sl, "%1.0f");
301 elm_slider_indicator_format_set(sl, "%1.0f");
302 evas_object_size_hint_weight_set(sl, EVAS_HINT_EXPAND, 0.5);
303 evas_object_size_hint_align_set(sl, EVAS_HINT_FILL, 0.5);
304 elm_slider_value_set(sl, input->volume.volumes[0]);
305 elm_box_pack_end(bx, sl);
306 evas_object_show(sl);
307 evas_object_smart_callback_add(sl, "changed",
308 _cb_sink_input_volume_change, bxv);
309
310 ck = elm_check_add(win);
311 evas_object_data_set(bxv, "mute", ck);
312 elm_object_text_set(ck, "Mute");
313 elm_check_state_set(ck, input->mute);
314 elm_object_disabled_set(sl, input->mute);
315 elm_box_pack_end(bx, ck);
316 evas_object_show(ck);
317 evas_object_smart_callback_add(ck, "changed",
318 _cb_sink_input_mute_change, bxv);
319
320 sep = elm_separator_add(win);
321 elm_separator_horizontal_set(sep, EINA_TRUE);
322 evas_object_size_hint_weight_set(sep, EVAS_HINT_EXPAND, 0.0);
323 evas_object_size_hint_align_set(sep, EVAS_HINT_FILL, 0.0);
324 elm_box_pack_end(bxv, sep);
325 evas_object_show(sep);
326
327 elm_box_pack_end(sink_input_box, bxv);
328 evas_object_show(bxv);
329}
330
331static void
332_emix_sink_input_del(Emix_Sink_Input *input)
333{
334 Eina_List *l;
335 Evas_Object *bxv;
336 EINA_LIST_FOREACH(sink_input_list, l, bxv)
337 {
338 if (evas_object_data_get(bxv, "input") == input)
339 {
340 sink_input_list = eina_list_remove_list(sink_input_list, l);
341 evas_object_del(bxv);
342 return;
343 }
344 }
345}
346
347static void
348_emix_sink_input_change(Emix_Sink_Input *input)
349{
350 const Eina_List *l;
351 Evas_Object *bxv, *hv, *ck, *sl;
352 Emix_Sink *sink;
353
354 EINA_LIST_FOREACH(sink_input_list, l, bxv)
355 {
356 if (evas_object_data_get(bxv, "input") == input) break;
357 }
358 if (!l) return;
359 hv = evas_object_data_get(bxv, "port");
360 elm_hoversel_clear(hv);
361 EINA_LIST_FOREACH(emix_sinks_get(), l, sink)
362 {
363 elm_hoversel_item_add(hv, sink->name,
364 NULL, ELM_ICON_NONE,
365 _cb_sink_input_port_change, sink);
366 if (input->sink == sink) elm_object_text_set(hv, sink->name);
367 }
368 sl = evas_object_data_get(bxv, "volume");
369 elm_slider_value_set(sl, input->volume.volumes[0]);
370
371 ck = evas_object_data_get(bxv, "mute");
372 elm_check_state_set(ck, input->mute);
373 elm_object_disabled_set(sl, input->mute);
374}
375
376//////////////////////////////////////////////////////////////////////////////
377
378static void
379_cb_source_volume_change(void *data,
380 Evas_Object *obj,
381 void *event_info EINA_UNUSED)
382{
383 Evas_Object *bxv = data;
384 Emix_Source *source = evas_object_data_get(bxv, "source");
385 double vol = elm_slider_value_get(obj);
386 VOLSET(vol, source->volume, source, emix_source_volume_set);
387}
388
389static void
390_cb_source_mute_change(void *data,
391 Evas_Object *obj,
392 void *event_info EINA_UNUSED)
393{
394 Evas_Object *bxv = data;
395 Emix_Source *source = evas_object_data_get(bxv, "source");
396 Evas_Object *sl = evas_object_data_get(bxv, "volume");
397 Eina_Bool mute = elm_check_state_get(obj);
398 elm_object_disabled_set(sl, mute);
399 emix_source_mute_set(source, mute);
400}
401
402static void
403_emix_source_add(Emix_Source *source)
404{
405 Evas_Object *bxv, *bx, *lb, *ck, *sl, *sep;
406
407 bxv = elm_box_add(win);
408 source_list = eina_list_append(source_list, bxv);
409 evas_object_data_set(bxv, "source", source);
410 evas_object_size_hint_weight_set(bxv, EVAS_HINT_EXPAND, 0.0);
411 evas_object_size_hint_align_set(bxv, EVAS_HINT_FILL, 0.0);
412
413 bx = elm_box_add(win);
414 elm_box_horizontal_set(bx, EINA_TRUE);
415 evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
416 evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.0);
417 elm_box_pack_end(bxv, bx);
418 evas_object_show(bx);
419
420 lb = elm_label_add(win);
421 elm_object_text_set(lb, source->name);
422 evas_object_size_hint_weight_set(lb, EVAS_HINT_EXPAND, 0.5);
423 evas_object_size_hint_align_set(lb, 0.0, 0.5);
424 elm_box_pack_end(bx, lb);
425 evas_object_show(lb);
426
427 bx = elm_box_add(win);
428 elm_box_horizontal_set(bx, EINA_TRUE);
429 evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
430 evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.0);
431 elm_box_pack_end(bxv, bx);
432 evas_object_show(bx);
433
434 sl = elm_slider_add(win);
435 evas_object_data_set(bxv, "volume", sl);
436 elm_slider_min_max_set(sl, 0.0, 100.0);
437 elm_slider_span_size_set(sl, 100 * elm_config_scale_get());
438 elm_slider_unit_format_set(sl, "%1.0f");
439 elm_slider_indicator_format_set(sl, "%1.0f");
440 evas_object_size_hint_weight_set(sl, EVAS_HINT_EXPAND, 0.5);
441 evas_object_size_hint_align_set(sl, EVAS_HINT_FILL, 0.5);
442 elm_slider_value_set(sl, source->volume.volumes[0]);
443 elm_box_pack_end(bx, sl);
444 evas_object_show(sl);
445 evas_object_smart_callback_add(sl, "changed",
446 _cb_source_volume_change, bxv);
447
448 ck = elm_check_add(win);
449 evas_object_data_set(bxv, "mute", ck);
450 elm_object_text_set(ck, "Mute");
451 elm_check_state_set(ck, source->mute);
452 elm_object_disabled_set(sl, source->mute);
453 elm_box_pack_end(bx, ck);
454 evas_object_show(ck);
455 evas_object_smart_callback_add(ck, "changed",
456 _cb_source_mute_change, bxv);
457
458 sep = elm_separator_add(win);
459 elm_separator_horizontal_set(sep, EINA_TRUE);
460 evas_object_size_hint_weight_set(sep, EVAS_HINT_EXPAND, 0.0);
461 evas_object_size_hint_align_set(sep, EVAS_HINT_FILL, 0.0);
462 elm_box_pack_end(bxv, sep);
463 evas_object_show(sep);
464
465 elm_box_pack_end(source_box, bxv);
466 evas_object_show(bxv);
467}
468
469static void
470_emix_source_del(Emix_Source *source)
471{
472 Eina_List *l;
473 Evas_Object *bxv;
474 EINA_LIST_FOREACH(source_list, l, bxv)
475 {
476 if (evas_object_data_get(bxv, "source") == source)
477 {
478 source_list = eina_list_remove_list(source_list, l);
479 evas_object_del(bxv);
480 return;
481 }
482 }
483}
484
485static void
486_emix_source_change(Emix_Source *source)
487{
488 const Eina_List *l;
489 Evas_Object *bxv, *ck, *sl;
490
491 EINA_LIST_FOREACH(source_list, l, bxv)
492 {
493 if (evas_object_data_get(bxv, "source") == source) break;
494 }
495 if (!l) return;
496 sl = evas_object_data_get(bxv, "volume");
497 elm_slider_value_set(sl, source->volume.volumes[0]);
498
499 ck = evas_object_data_get(bxv, "mute");
500 elm_check_state_set(ck, source->mute);
501 elm_object_disabled_set(sl, source->mute);
502}
503
504//////////////////////////////////////////////////////////////////////////////
505
506static void
507_cb_emix_event(void *data EINA_UNUSED, enum Emix_Event event, void *event_info)
508{
509 switch (event)
510 {
511 case EMIX_READY_EVENT:
512 break;
513 case EMIX_DISCONNECTED_EVENT:
514 elm_exit();
515 break;
516 case EMIX_SINK_ADDED_EVENT:
517 _emix_sink_add(event_info);
518 break;
519 case EMIX_SINK_REMOVED_EVENT:
520 _emix_sink_del(event_info);
521 break;
522 case EMIX_SINK_CHANGED_EVENT:
523 _emix_sink_change(event_info);
524 break;
525 case EMIX_SINK_INPUT_ADDED_EVENT:
526 _emix_sink_input_add(event_info);
527 break;
528 case EMIX_SINK_INPUT_REMOVED_EVENT:
529 _emix_sink_input_del(event_info);
530 break;
531 case EMIX_SINK_INPUT_CHANGED_EVENT:
532 _emix_sink_input_change(event_info);
533 break;
534 case EMIX_SOURCE_ADDED_EVENT:
535 _emix_source_add(event_info);
536 break;
537 case EMIX_SOURCE_REMOVED_EVENT:
538 _emix_source_del(event_info);
539 break;
540 case EMIX_SOURCE_CHANGED_EVENT:
541 _emix_source_change(event_info);
542 break;
543 default:
544 break;
545 }
546}
547
548//////////////////////////////////////////////////////////////////////////////
549
550static void
551_cb_playback(void *data EINA_UNUSED,
552 Evas_Object *obj EINA_UNUSED,
553 void *event_info EINA_UNUSED)
554{
555 evas_object_hide(source_scroller);
556 evas_object_show(sink_input_scroller);
557 evas_object_hide(sink_scroller);
558}
559
560static void
561_cb_outputs(void *data EINA_UNUSED,
562 Evas_Object *obj EINA_UNUSED,
563 void *event_info EINA_UNUSED)
564{
565 evas_object_hide(source_scroller);
566 evas_object_hide(sink_input_scroller);
567 evas_object_show(sink_scroller);
568}
569
570static void
571_cb_inputs(void *data EINA_UNUSED,
572 Evas_Object *obj EINA_UNUSED,
573 void *event_info EINA_UNUSED)
574{
575 evas_object_show(source_scroller);
576 evas_object_hide(sink_input_scroller);
577 evas_object_hide(sink_scroller);
578}
579
580//////////////////////////////////////////////////////////////////////////////
581
582static void
583_event_init(void)
584{
585 emix_event_callback_add(_cb_emix_event, NULL);
586}
587
588static void
589_fill_source(void)
590{
591 const Eina_List *l;
592 Emix_Source *source;
593
594 EINA_LIST_FOREACH(emix_sources_get(), l, source)
595 {
596 _emix_source_add(source);
597 }
598}
599
600static void
601_fill_sink_input(void)
602{
603 const Eina_List *l;
604 Emix_Sink_Input *input;
605
606 EINA_LIST_FOREACH(emix_sink_inputs_get(), l, input)
607 {
608 _emix_sink_input_add(input);
609 }
610}
611
612static void
613_fill_sink(void)
614{
615 const Eina_List *l;
616 Emix_Sink *sink;
617
618 EINA_LIST_FOREACH(emix_sinks_get(), l, sink)
619 {
620 _emix_sink_add(sink);
621 }
622}
623
624//////////////////////////////////////////////////////////////////////////////
625
626EAPI_MAIN int
627elm_main(int argc, char **argv)
628{
629 Evas_Object *tb, *tbar, *sc, *rect, *bx;
630 const char *back = NULL;
631
632 emix_init();
633 if (argc > 1) back = argv[1];
634 if (!_backend_init(back)) goto done;
635 _event_init();
636
637 elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED);
638
639 win = elm_win_util_standard_add("emix", "Mixer");
640 elm_win_autodel_set(win, EINA_TRUE);
641
642/*
643 icon = evas_object_image_add(evas_object_evas_get(mw->win));
644 snprintf(buf, sizeof(buf), "%s/icons/emixer.png",
645 elm_app_data_dir_get());
646 evas_object_image_file_set(icon, buf, NULL);
647 elm_win_icon_object_set(mw->win, icon);
648 elm_win_icon_name_set(mw->win, "emixer");
649 */
650
651 tb = elm_table_add(win);
652 evas_object_size_hint_weight_set(tb, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
653 elm_win_resize_object_add(win, tb);
654 evas_object_show(tb);
655
656 tbar = elm_toolbar_add(win);
657 elm_toolbar_select_mode_set(tbar, ELM_OBJECT_SELECT_MODE_ALWAYS);
658 elm_toolbar_homogeneous_set(tbar, EINA_TRUE);
659 evas_object_size_hint_weight_set(tbar, EVAS_HINT_EXPAND, 0.0);
660 evas_object_size_hint_align_set(tbar, EVAS_HINT_FILL, EVAS_HINT_FILL);
661
662 elm_toolbar_item_append(tbar, NULL, "Playback", _cb_playback, NULL);
663 elm_toolbar_item_append(tbar, NULL, "Outputs", _cb_outputs, NULL);
664 elm_toolbar_item_append(tbar, NULL, "Inputs", _cb_inputs, NULL);
665
666 elm_table_pack(tb, tbar, 0, 0, 1, 1);
667 evas_object_show(tbar);
668
669 sc = elm_scroller_add(win);
670 source_scroller = sc;
671 evas_object_size_hint_weight_set(sc, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
672 evas_object_size_hint_align_set(sc, EVAS_HINT_FILL, EVAS_HINT_FILL);
673 elm_table_pack(tb, sc, 0, 1, 1, 1);
674
675 sc = elm_scroller_add(win);
676 sink_input_scroller = sc;
677 evas_object_size_hint_weight_set(sc, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
678 evas_object_size_hint_align_set(sc, EVAS_HINT_FILL, EVAS_HINT_FILL);
679 elm_table_pack(tb, sc, 0, 1, 1, 1);
680 evas_object_show(sc);
681
682 sc = elm_scroller_add(win);
683 sink_scroller = sc;
684 evas_object_size_hint_weight_set(sc, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
685 evas_object_size_hint_align_set(sc, EVAS_HINT_FILL, EVAS_HINT_FILL);
686 elm_table_pack(tb, sc, 0, 1, 1, 1);
687
688 bx = elm_box_add(win);
689 source_box = bx;
690 evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
691 evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.0);
692 elm_object_content_set(source_scroller, bx);
693 evas_object_show(bx);
694
695 bx = elm_box_add(win);
696 sink_input_box = bx;
697 evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
698 evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.0);
699 elm_object_content_set(sink_input_scroller, bx);
700 evas_object_show(bx);
701
702 bx = elm_box_add(win);
703 sink_box = bx;
704 evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
705 evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.0);
706 elm_object_content_set(sink_scroller, bx);
707 evas_object_show(bx);
708
709 rect = evas_object_rectangle_add(evas_object_evas_get(win));
710 evas_object_size_hint_weight_set(rect, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
711 evas_object_size_hint_align_set(rect, EVAS_HINT_FILL, EVAS_HINT_FILL);
712 evas_object_size_hint_min_set(rect,
713 440 * elm_config_scale_get(),
714 220 * elm_config_scale_get());
715 elm_table_pack(tb, rect, 0, 1, 1, 1);
716
717 _fill_source();
718 _fill_sink_input();
719 _fill_sink();
720 evas_object_show(win);
721
722 elm_run();
723done:
724 emix_shutdown();
725 return 0;
726}
727ELM_MAIN()
diff --git a/src/modules/mixer/lib/backends/alsa/alsa.c b/src/modules/mixer/lib/backends/alsa/alsa.c
new file mode 100644
index 0000000..156522c
--- /dev/null
+++ b/src/modules/mixer/lib/backends/alsa/alsa.c
@@ -0,0 +1,523 @@
1#include "emix.h"
2#include <alsa/asoundlib.h>
3
4#define ERR(...) EINA_LOG_ERR(__VA_ARGS__)
5#define DBG(...) EINA_LOG_DBG(__VA_ARGS__)
6#define WRN(...) EINA_LOG_WARN(__VA_ARGS__)
7
8typedef struct _Context
9{
10 Emix_Event_Cb cb;
11 const void *userdata;
12 Eina_List *sinks;
13 Eina_List *sources;
14 Eina_List *cards;
15} Context;
16
17static Context *ctx = NULL;
18
19typedef struct _Alsa_Emix_Sink
20{
21 Emix_Sink sink;
22 const char *hw_name;
23 Eina_List *channels;
24} Alsa_Emix_Sink;
25
26typedef struct _Alsa_Emix_Source
27{
28 Emix_Source source;
29 const char *hw_name;
30 Eina_List *channels;
31} Alsa_Emix_Source;
32/*
33 * TODO problems:
34 *
35 * - mono stereo problem...
36 */
37
38/*
39 * util functions
40 */
41
42static int
43_alsa_mixer_sink_changed_cb(snd_mixer_t *ctl, unsigned int mask EINA_UNUSED,
44 snd_mixer_elem_t *elem EINA_UNUSED)
45{
46 Alsa_Emix_Sink *sink = snd_mixer_get_callback_private(ctl);
47
48 if (ctx->cb)
49 ctx->cb((void *)ctx->userdata, EMIX_SINK_CHANGED_EVENT,
50 (Emix_Sink *)sink);
51 return 0;
52}
53
54static int
55_alsa_mixer_source_changed_cb(snd_mixer_t *ctl, unsigned int mask EINA_UNUSED,
56 snd_mixer_elem_t *elem EINA_UNUSED)
57{
58 Alsa_Emix_Source *source = snd_mixer_get_callback_private(ctl);
59
60 if (ctx->cb)
61 ctx->cb((void *)ctx->userdata, EMIX_SOURCE_CHANGED_EVENT,
62 (Emix_Source *)source);
63 return 0;
64}
65
66static void
67_alsa_channel_volume_get(snd_mixer_elem_t *channel, int *v, Eina_Bool capture)
68{
69 long int min, max, vol;
70 int range, divide;
71
72 if (capture)
73 snd_mixer_selem_get_capture_volume_range(channel, &min, &max);
74 else
75 snd_mixer_selem_get_playback_volume_range(channel, &min, &max);
76
77 divide = 100 + min;
78 if (divide == 0)
79 {
80 divide = 1;
81 min++;
82 }
83
84 range = max - min;
85 if (range < 1)
86 return;
87
88 if (capture)
89 snd_mixer_selem_get_capture_volume(channel, 0, &vol);
90 else
91 snd_mixer_selem_get_playback_volume(channel, 0, &vol);
92
93 *v = (((vol + min) * divide) - ((double) range / 2)) / range + 0.5;
94}
95
96static void
97_alsa_channel_volume_set(snd_mixer_elem_t *channel, int v, Eina_Bool capture)
98{
99 long int vol, min, max, divide, range;
100 snd_mixer_selem_get_playback_volume_range(channel, &min, &max);
101
102 divide = 100 + min;
103 range = max - min;
104 if (range < 1)
105 return;
106
107 vol = (((v * range) + (range / 2)) / divide) - min;
108 if (!capture)
109 snd_mixer_selem_set_playback_volume_all(channel, vol);
110 else
111 snd_mixer_selem_set_capture_volume_all(channel, vol);
112}
113
114/*
115 * This will append a new device to the cards and call the ecore event for
116 * a new device!
117 */
118static snd_mixer_t *
119_alsa_card_create(char *addr)
120{
121 snd_mixer_t *control;
122
123 if (snd_mixer_open(&control, 0) < 0)
124 goto error_open;
125 if (snd_mixer_attach(control, addr) < 0)
126 goto error_load;
127 if (snd_mixer_selem_register(control, NULL, NULL) < 0)
128 goto error_load;
129 if (snd_mixer_load(control))
130 goto error_load;
131
132 return control;
133
134error_load:
135 snd_mixer_close(control);
136error_open:
137 return NULL;
138}
139
140static void
141_alsa_volume_create(Emix_Volume *volume, Eina_List *channels)
142{
143 unsigned int i = 0, count = eina_list_count(channels);
144 Eina_List *l;
145 snd_mixer_elem_t *elem;
146
147 volume->channel_count = count;
148 volume->volumes = calloc(count, sizeof(int));
149
150 EINA_LIST_FOREACH(channels, l, elem)
151 {
152 _alsa_channel_volume_get(elem, &(volume->volumes[i]), EINA_FALSE);
153 i++;
154 }
155}
156
157static void
158_alsa_sink_mute_get(Alsa_Emix_Sink *as)
159{
160 int i = 0;
161 snd_mixer_elem_t *elem;
162
163 elem = eina_list_data_get(as->channels);
164 snd_mixer_selem_get_playback_switch(elem, 0, &i);
165 as->sink.mute = !i;
166}
167
168static void
169_alsa_sources_mute_get(Alsa_Emix_Source *as)
170{
171 int i = 0;
172 snd_mixer_elem_t *elem;
173
174 elem = eina_list_data_get(as->channels);
175 snd_mixer_selem_get_capture_switch(elem, 0, &i);
176 as->source.mute = !i;
177}
178
179static Alsa_Emix_Sink*
180_alsa_device_sink_create(const char *name, const char* hw_name,
181 Eina_List *channels)
182{
183 Alsa_Emix_Sink *sink;
184
185 if (!(sink = calloc(1, sizeof(Alsa_Emix_Sink))))
186 {
187 ERR("Allocation Failed");
188 return NULL;
189 }
190 sink->sink.name = eina_stringshare_add(name);
191 _alsa_volume_create(&sink->sink.volume, channels);
192 sink->hw_name = eina_stringshare_add(hw_name);
193 sink->channels = channels;
194 _alsa_sink_mute_get(sink);
195 if (ctx->cb)
196 {
197 ctx->cb((void *)ctx->userdata, EMIX_SINK_ADDED_EVENT,
198 (Emix_Sink *)sink);
199
200 }
201 ctx->sinks = eina_list_append(ctx->sinks, sink);
202 return sink;
203}
204
205static Alsa_Emix_Source*
206_alsa_device_source_create(const char *name, const char* hw_name,
207 Eina_List *channels)
208{
209 Alsa_Emix_Source *source;
210
211 if (!(source = calloc(1, sizeof(Alsa_Emix_Source))))
212 {
213 ERR("Allocation Failed");
214 return NULL;
215 }
216 source->source.name = eina_stringshare_add(name);
217 _alsa_volume_create(&source->source.volume, channels);
218 source->hw_name = eina_stringshare_add(hw_name);
219 source->channels = channels;
220 _alsa_sources_mute_get(source);
221 if (ctx->cb)
222 ctx->cb((void *)ctx->userdata, EMIX_SOURCE_ADDED_EVENT,
223 (Emix_Sink *)source);
224 ctx->sources = eina_list_append(ctx->sources, source);
225 return source;
226}
227
228static void
229_alsa_device_sink_free(Alsa_Emix_Sink *sink)
230{
231 eina_stringshare_del(sink->hw_name);
232 eina_stringshare_del(sink->sink.name);
233 free(sink->sink.volume.volumes);
234 free(sink);
235}
236
237static void
238_alsa_device_source_free(Alsa_Emix_Source *source)
239{
240 eina_stringshare_del(source->hw_name);
241 eina_stringshare_del(source->source.name);
242 free(source->source.volume.volumes);
243 free(source);
244}
245
246static char*
247_alsa_cards_name_get(char *name)
248{
249 snd_ctl_t *control;
250 snd_ctl_card_info_t *hw_info;
251 char *result = NULL;
252
253 snd_ctl_card_info_alloca(&hw_info);
254
255 if (snd_ctl_open(&control, name, 0) < 0)
256 {
257 ERR("Failed to open device");
258 goto err;
259 }
260
261 if (snd_ctl_card_info(control, hw_info) < 0)
262 {
263 ERR("Failed to get card information");
264 goto err_open;
265 }
266
267 result = strdup(snd_ctl_card_info_get_name(hw_info));
268
269err_open:
270 snd_ctl_close(control);
271err:
272 return result;
273}
274
275static void
276_alsa_cards_refresh(void)
277{
278 int err, card_num = -1;
279 Eina_List *tmp_source = NULL, *tmp_sink = NULL;
280
281 while (((err = snd_card_next(&card_num)) == 0) && (card_num >= 0))
282 {
283 char buf[PATH_MAX];
284 char *device_name;
285 snd_mixer_t *mixer;
286 snd_mixer_elem_t *elem;
287 Alsa_Emix_Source *source;
288 Alsa_Emix_Sink *sink;
289
290 source = NULL;
291 sink = NULL;
292 tmp_source = NULL;
293 tmp_sink = NULL;
294
295 //generate card addr
296 snprintf(buf, sizeof(buf), "hw:%d", card_num);
297 //save the addr to see if there are missing devices in the cache list
298
299 mixer = _alsa_card_create(buf);
300 ctx->cards = eina_list_append(ctx->cards, mixer);
301 //get elements of the device
302 elem = snd_mixer_first_elem(mixer);
303 for (; elem; elem = snd_mixer_elem_next(elem))
304 {
305 //check if its a source or a sink
306 if (snd_mixer_selem_has_capture_volume(elem))
307 tmp_source = eina_list_append(tmp_source, elem);
308 else
309 tmp_sink = eina_list_append(tmp_sink, elem);
310 }
311
312 device_name = _alsa_cards_name_get(buf);
313 //create the sinks / sources
314 if (tmp_sink)
315 {
316 sink = _alsa_device_sink_create(device_name,
317 buf,
318 tmp_sink);
319 snd_mixer_set_callback(mixer, _alsa_mixer_sink_changed_cb);
320 snd_mixer_set_callback_private(mixer, sink);
321 }
322 if (tmp_source)
323 {
324 source = _alsa_device_source_create(device_name,
325 buf,
326 tmp_source);
327 snd_mixer_set_callback(mixer, _alsa_mixer_source_changed_cb);
328 snd_mixer_set_callback_private(mixer, source);
329 }
330 if (device_name)
331 free(device_name);
332 }
333}
334
335static Eina_Bool
336_alsa_init(Emix_Event_Cb cb, const void *data)
337{
338 EINA_SAFETY_ON_NULL_RETURN_VAL(cb, EINA_FALSE);
339 if (!ctx)
340 ctx = calloc(1, sizeof(Context));
341
342 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_FALSE);
343
344 ctx->cb = cb;
345 ctx->userdata = data;
346 _alsa_cards_refresh();
347
348 //call the event because the backend is now ready to use
349 ctx->cb((void *)ctx->userdata, EMIX_READY_EVENT, NULL);
350
351 return EINA_TRUE;
352}
353
354static void
355_alsa_shutdown(void)
356{
357 Alsa_Emix_Sink *sink;
358 Alsa_Emix_Source *source;
359 snd_mixer_t *mixer;
360
361 EINA_SAFETY_ON_NULL_RETURN(ctx);
362
363 EINA_LIST_FREE(ctx->sinks, sink)
364 _alsa_device_sink_free(sink);
365 EINA_LIST_FREE(ctx->sources, source)
366 _alsa_device_source_free(source);
367 EINA_LIST_FREE(ctx->cards, mixer)
368 snd_mixer_close(mixer);
369
370 free(ctx);
371 ctx = NULL;
372}
373
374static const Eina_List*
375_alsa_sources_get(void)
376{
377 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
378 return ctx->sources;
379}
380
381static void
382_alsa_sources_mute_set(Emix_Source *source, Eina_Bool mute)
383{
384 Alsa_Emix_Source *s = (Alsa_Emix_Source*) source;
385 Eina_List *node;
386 snd_mixer_elem_t *elem;
387
388 EINA_SAFETY_ON_FALSE_RETURN((ctx && source));
389
390 EINA_LIST_FOREACH(s->channels, node, elem)
391 {
392 if (!snd_mixer_selem_has_capture_switch(elem))
393 continue;
394 if (snd_mixer_selem_set_capture_switch_all(elem, !mute) < 0)
395 ERR("Failed to mute device\n");
396 }
397
398 source->mute = mute;
399 if (ctx->cb)
400 ctx->cb((void *)ctx->userdata, EMIX_SOURCE_CHANGED_EVENT,
401 (Emix_Source *)source);
402}
403
404static void
405_alsa_sources_volume_set(Emix_Source *source, Emix_Volume v)
406{
407 Alsa_Emix_Source *s = (Alsa_Emix_Source*) source;
408 unsigned int i;
409 snd_mixer_elem_t *elem;
410
411 EINA_SAFETY_ON_FALSE_RETURN((ctx && source));
412
413 if (v.channel_count != eina_list_count(s->channels))
414 {
415 ERR("Volume struct doesnt have the same length than the channels");
416 return;
417 }
418
419 for (i = 0; i < v.channel_count; i++ )
420 {
421 elem = eina_list_nth(s->channels, i);
422 _alsa_channel_volume_set(elem, v.volumes[i], EINA_FALSE);
423 }
424}
425
426
427static const Eina_List*
428_alsa_sinks_get(void)
429{
430 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
431 //FIXME fork or just return?
432/* Eina_List *result = NULL;
433 Eina_List *node;
434 void *data;
435 EINA_LIST_FOREACH(ctx->sinks, node, data)
436 {
437 result = eina_list_append(result, data);
438 }*/
439 return ctx->sinks;
440}
441
442static Eina_Bool
443_alsa_support(void)
444{
445 return EINA_FALSE;
446}
447
448static void
449_alsa_sink_mute_set(Emix_Sink *sink, Eina_Bool mute)
450{
451 Alsa_Emix_Sink *as = (Alsa_Emix_Sink*) sink;
452 Eina_List *node;
453 snd_mixer_elem_t *elem;
454
455 EINA_SAFETY_ON_FALSE_RETURN((ctx && sink));
456
457 EINA_LIST_FOREACH(as->channels, node, elem)
458 {
459 if (!snd_mixer_selem_has_playback_switch(elem))
460 continue;
461
462 if (snd_mixer_selem_set_playback_switch_all(elem, !mute) < 0)
463 ERR("Failed to set mute(%d) device(%p)", mute, elem);
464 }
465
466 sink->mute = mute;
467 if (ctx->cb)
468 ctx->cb((void *)ctx->userdata, EMIX_SINK_CHANGED_EVENT,
469 (Emix_Sink *)sink);
470}
471
472static void
473_alsa_sink_volume_set(Emix_Sink *sink, Emix_Volume v)
474{
475 Alsa_Emix_Sink *s = (Alsa_Emix_Sink *)sink;
476 unsigned int i;
477 snd_mixer_elem_t *elem;
478
479 EINA_SAFETY_ON_FALSE_RETURN((ctx && sink));
480
481 if (v.channel_count != eina_list_count(s->channels))
482 {
483 ERR("Volume struct doesnt have the same length than the channels");
484 return;
485 }
486
487 for (i = 0; i < v.channel_count; i++ )
488 {
489 elem = eina_list_nth(s->channels, i);
490 _alsa_channel_volume_set(elem, v.volumes[i], EINA_FALSE);
491 }
492}
493
494static Emix_Backend
495_alsa_backend =
496{
497 _alsa_init,
498 _alsa_shutdown,
499 _alsa_sinks_get,
500 _alsa_support, /*default support*/
501 NULL, /*get*/
502 NULL, /*set*/
503 _alsa_sink_mute_set, /*mute_set*/
504 _alsa_sink_volume_set, /*volume_set*/
505 NULL, /* port set */
506 _alsa_support, /*change support*/
507 NULL, /*sink input get*/
508 NULL,/*sink input mute set*/
509 NULL,/*sink input volume set*/
510 NULL,/*sink input sink change*/
511 _alsa_sources_get,/*source*/
512 _alsa_sources_mute_set,/* source mute set */
513 _alsa_sources_volume_set, /* source volume set */
514 NULL /* advanced options */
515};
516
517E_API Emix_Backend *
518emix_backend_alsa_get(void)
519{
520 return &_alsa_backend;
521}
522
523E_API const char *emix_backend_alsa_name = "ALSA";
diff --git a/src/modules/mixer/lib/backends/pulseaudio/pulse.c b/src/modules/mixer/lib/backends/pulseaudio/pulse.c
new file mode 100644
index 0000000..ab8e154
--- /dev/null
+++ b/src/modules/mixer/lib/backends/pulseaudio/pulse.c
@@ -0,0 +1,1047 @@
1#include <Eina.h>
2#include <Ecore.h>
3#include <pulse/pulseaudio.h>
4
5#include "emix.h"
6
7#define ERR(...) EINA_LOG_ERR(__VA_ARGS__)
8#define DBG(...) EINA_LOG_DBG(__VA_ARGS__)
9#define WRN(...) EINA_LOG_WARN(__VA_ARGS__)
10
11#define PA_VOLUME_TO_INT(_vol) \
12 (((_vol+1)*EMIX_VOLUME_MAX+PA_VOLUME_NORM/2)/PA_VOLUME_NORM)
13#define INT_TO_PA_VOLUME(_vol) \
14 (!_vol) ? 0 : ((PA_VOLUME_NORM*(_vol+1)-PA_VOLUME_NORM/2)/EMIX_VOLUME_MAX)
15
16typedef struct _Context
17{
18 pa_mainloop_api api;
19 pa_context *context;
20 pa_context_state_t state;
21 Emix_Event_Cb cb;
22 const void *userdata;
23 Ecore_Timer *connect;
24 int default_sink;
25
26 Eina_List *sinks, *sources, *inputs;
27 Eina_Bool connected;
28} Context;
29
30typedef struct _Sink
31{
32 Emix_Sink base;
33 int idx;
34} Sink;
35
36typedef struct _Sink_Input
37{
38 Emix_Sink_Input base;
39 const char *icon;
40 int idx;
41} Sink_Input;
42
43typedef struct _Source
44{
45 Emix_Source base;
46 int idx;
47} Source;
48
49static Context *ctx = NULL;
50extern pa_mainloop_api functable;
51
52static pa_cvolume
53_emix_volume_convert(const Emix_Volume volume)
54{
55 pa_cvolume vol;
56 unsigned int i;
57
58 vol.channels = volume.channel_count;
59 for (i = 0; i < volume.channel_count; i++)
60 vol.values[i] = INT_TO_PA_VOLUME(volume.volumes[i]);
61
62 return vol;
63}
64
65static Emix_Volume
66_pa_cvolume_convert(const pa_cvolume volume)
67{
68 Emix_Volume vol;
69 int i;
70
71 vol.volumes = calloc(volume.channels, sizeof(int));
72 if (!vol.volumes)
73 {
74 WRN("Could not allocate memory for volume");
75 vol.channel_count = 0;
76 return vol;
77 }
78
79 vol.channel_count = volume.channels;
80 for (i = 0; i < volume.channels; i++)
81 vol.volumes[i] = PA_VOLUME_TO_INT(volume.values[i]);
82
83 return vol;
84}
85
86static void
87_sink_del(Sink *sink)
88{
89 Emix_Port *port;
90
91 EINA_SAFETY_ON_NULL_RETURN(sink);
92 EINA_LIST_FREE(sink->base.ports, port)
93 {
94 eina_stringshare_del(port->name);
95 eina_stringshare_del(port->description);
96 free(port);
97 }
98
99 free(sink->base.volume.volumes);
100 eina_stringshare_del(sink->base.name);
101 free(sink);
102}
103
104static void
105_sink_input_del(Sink_Input *input)
106{
107 EINA_SAFETY_ON_NULL_RETURN(input);
108
109 free(input->base.volume.volumes);
110 eina_stringshare_del(input->base.name);
111 eina_stringshare_del(input->icon);
112 free(input);
113}
114
115static void
116_source_del(Source *source)
117{
118 EINA_SAFETY_ON_NULL_RETURN(source);
119
120 free(source->base.volume.volumes);
121 eina_stringshare_del(source->base.name);
122 free(source);
123}
124
125static void
126_sink_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol,
127 void *userdata EINA_UNUSED)
128{
129 Sink *sink;
130 Emix_Port *port;
131 uint32_t i;
132
133 if (eol < 0)
134 {
135 if (pa_context_errno(c) == PA_ERR_NOENTITY)
136 return;
137
138 ERR("Sink callback failure");
139 return;
140 }
141
142 if (eol > 0)
143 return;
144
145 DBG("sink index: %d\nsink name: %s", info->index,
146 info->name);
147
148 sink = calloc(1, sizeof(Sink));
149 sink->idx = info->index;
150 sink->base.name = eina_stringshare_add(info->description);
151 sink->base.volume = _pa_cvolume_convert(info->volume);
152 sink->base.mute = !!info->mute;
153
154 for (i = 0; i < info->n_ports; i++)
155 {
156 port = calloc(1, sizeof(Emix_Port));
157 if (!port)
158 {
159 WRN("Could not allocate memory for Sink's port");
160 continue;
161 }
162
163 port->available = !!info->ports[i]->available;
164 port->name = eina_stringshare_add(info->ports[i]->name);
165 port->description = eina_stringshare_add(info->ports[i]->description);
166 sink->base.ports = eina_list_append(sink->base.ports, port);
167 if (info->ports[i]->name == info->active_port->name)
168 port->active = EINA_TRUE;
169 }
170
171 ctx->sinks = eina_list_append(ctx->sinks, sink);
172 if (ctx->cb)
173 ctx->cb((void *)ctx->userdata, EMIX_SINK_ADDED_EVENT,
174 (Emix_Sink *)sink);
175}
176
177static void
178_sink_changed_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol,
179 void *userdata EINA_UNUSED)
180{
181 Sink *sink = NULL, *s;
182 Emix_Port *port;
183 uint32_t i;
184 Eina_List *l;
185
186 if (eol < 0)
187 {
188 if (pa_context_errno(c) == PA_ERR_NOENTITY)
189 return;
190
191 ERR("Sink callback failure");
192 return;
193 }
194
195 if (eol > 0)
196 return;
197
198 DBG("sink index: %d\nsink name: %s", info->index,
199 info->name);
200
201 EINA_LIST_FOREACH(ctx->sinks, l, s)
202 {
203 if (s->idx == (int)info->index)
204 {
205 sink = s;
206 break;
207 }
208 }
209
210 EINA_SAFETY_ON_NULL_RETURN(sink);
211
212 sink->base.name = eina_stringshare_add(info->description);
213 sink->base.volume = _pa_cvolume_convert(info->volume);
214 sink->base.mute = !!info->mute;
215
216 if (sink->base.ports)
217 {
218 EINA_LIST_FREE(sink->base.ports, port)
219 {
220 eina_stringshare_del(port->name);
221 eina_stringshare_del(port->description);
222 free(port);
223 }
224 }
225 for (i = 0; i < info->n_ports; i++)
226 {
227 port = calloc(1, sizeof(Emix_Port));
228 if (!port)
229 {
230 WRN("Could not allocate memory for Sink's port");
231 continue;
232 }
233
234 port->available = !!info->ports[i]->available;
235 port->name = eina_stringshare_add(info->ports[i]->name);
236 port->description = eina_stringshare_add(info->ports[i]->description);
237 sink->base.ports = eina_list_append(sink->base.ports, port);
238 if (info->ports[i]->name == info->active_port->name)
239 port->active = EINA_TRUE;
240 }
241
242 if (ctx->cb)
243 ctx->cb((void *)ctx->userdata, EMIX_SINK_CHANGED_EVENT,
244 (Emix_Sink *)sink);
245}
246
247static void
248_sink_remove_cb(int index, void *data EINA_UNUSED)
249{
250 Sink *sink;
251 Eina_List *l;
252 DBG("Removing sink: %d", index);
253
254 EINA_LIST_FOREACH(ctx->sinks, l, sink)
255 {
256 if (sink->idx == index)
257 {
258 if (ctx->cb)
259 ctx->cb((void *)ctx->userdata, EMIX_SINK_REMOVED_EVENT,
260 (Emix_Sink *)sink);
261 _sink_del(sink);
262 ctx->sinks = eina_list_remove_list(ctx->sinks, l);
263 break;
264 }
265 }
266}
267
268static const char *
269_icon_from_properties(pa_proplist *l)
270{
271 const char *t;
272
273 if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ICON_NAME)))
274 return t;
275
276 if ((t = pa_proplist_gets(l, PA_PROP_WINDOW_ICON_NAME)))
277 return t;
278
279 if ((t = pa_proplist_gets(l, PA_PROP_APPLICATION_ICON_NAME)))
280 return t;
281
282 if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ROLE)))
283 {
284
285 if (strcmp(t, "video") == 0 ||
286 strcmp(t, "phone") == 0)
287 return t;
288
289 if (strcmp(t, "music") == 0)
290 return "audio";
291
292 if (strcmp(t, "game") == 0)
293 return "applications-games";
294
295 if (strcmp(t, "event") == 0)
296 return "dialog-information";
297 }
298
299 return "audio-card";
300}
301
302static void
303_sink_input_cb(pa_context *c EINA_UNUSED, const pa_sink_input_info *info,
304 int eol, void *userdata EINA_UNUSED)
305{
306 Sink_Input *input;
307 Eina_List *l;
308 Sink *s;
309 EINA_SAFETY_ON_NULL_RETURN(ctx);
310
311 if (eol < 0)
312 {
313 if (pa_context_errno(c) == PA_ERR_NOENTITY)
314 return;
315
316 ERR("Sink input callback failure");
317 return;
318 }
319
320 if (eol > 0)
321 return;
322
323 input = calloc(1, sizeof(Sink_Input));
324 EINA_SAFETY_ON_NULL_RETURN(input);
325
326 DBG("sink input index: %d\nsink input name: %s", info->index,
327 info->name);
328
329 input->idx = info->index;
330 input->base.name = eina_stringshare_add(info->name);
331 input->base.volume = _pa_cvolume_convert(info->volume);
332 input->base.mute = !!info->mute;
333 EINA_LIST_FOREACH(ctx->sinks, l, s)
334 {
335 if (s->idx == (int)info->sink)
336 input->base.sink = (Emix_Sink *)s;
337 }
338 input->icon = eina_stringshare_add(_icon_from_properties(info->proplist));
339 ctx->inputs = eina_list_append(ctx->inputs, input);
340
341 if (ctx->cb)
342 ctx->cb((void *)ctx->userdata, EMIX_SINK_INPUT_ADDED_EVENT,
343 (Emix_Sink_Input *)input);
344}
345
346static void
347_sink_input_changed_cb(pa_context *c EINA_UNUSED,
348 const pa_sink_input_info *info, int eol,
349 void *userdata EINA_UNUSED)
350{
351 Sink_Input *input = NULL, *i;
352 Eina_List *l;
353
354 EINA_SAFETY_ON_NULL_RETURN(ctx);
355 if (eol < 0)
356 {
357 if (pa_context_errno(c) == PA_ERR_NOENTITY)
358 return;
359
360 ERR("Sink input changed callback failure");
361 return;
362 }
363
364 if (eol > 0)
365 return;
366
367 EINA_LIST_FOREACH(ctx->inputs, l, i)
368 {
369 if (i->idx == (int)info->index)
370 {
371 input = i;
372 break;
373 }
374 }
375
376 DBG("sink input changed index: %d\n", info->index);
377
378 if (!input)
379 {
380 input = calloc(1, sizeof(Sink_Input));
381 EINA_SAFETY_ON_NULL_RETURN(input);
382 ctx->inputs = eina_list_append(ctx->inputs, input);
383 }
384 input->idx = info->index;
385 input->base.volume = _pa_cvolume_convert(info->volume);
386 input->base.mute = !!info->mute;
387
388 if (ctx->cb)
389 ctx->cb((void *)ctx->userdata, EMIX_SINK_INPUT_CHANGED_EVENT,
390 (Emix_Sink_Input *)input);
391}
392
393static void
394_sink_input_remove_cb(int index, void *data EINA_UNUSED)
395{
396 Sink_Input *input;
397 Eina_List *l;
398 EINA_SAFETY_ON_NULL_RETURN(ctx);
399
400 DBG("Removing sink input: %d", index);
401
402 EINA_LIST_FOREACH(ctx->inputs, l, input)
403 {
404 if (input->idx == index)
405 {
406 if (ctx->cb)
407 ctx->cb((void *)ctx->userdata,
408 EMIX_SINK_INPUT_REMOVED_EVENT,
409 (Emix_Sink_Input *)input);
410 _sink_input_del(input);
411
412 ctx->inputs = eina_list_remove_list(ctx->inputs, l);
413 break;
414 }
415 }
416}
417
418static void
419_source_cb(pa_context *c EINA_UNUSED, const pa_source_info *info,
420 int eol, void *userdata EINA_UNUSED)
421{
422 Source *source;
423 EINA_SAFETY_ON_NULL_RETURN(ctx);
424
425 source = calloc(1, sizeof(Source));
426 EINA_SAFETY_ON_NULL_RETURN(source);
427
428 if (eol < 0)
429 {
430 if (pa_context_errno(c) == PA_ERR_NOENTITY)
431 return;
432
433 ERR("Source callback failure");
434 return;
435 }
436
437 if (eol > 0)
438 return;
439
440 source->idx = info->index;
441 source->base.name = eina_stringshare_add(info->name);
442 source->base.volume = _pa_cvolume_convert(info->volume);
443 source->base.mute = !!info->mute;
444
445 ctx->sources = eina_list_append(ctx->sources, source);
446 if (ctx->cb)
447 ctx->cb((void *)ctx->userdata, EMIX_SOURCE_ADDED_EVENT,
448 (Emix_Source *)source);
449}
450
451static void
452_source_changed_cb(pa_context *c EINA_UNUSED,
453 const pa_source_info *info, int eol,
454 void *userdata EINA_UNUSED)
455{
456 Source *source = NULL, *s;
457 Eina_List *l;
458 EINA_SAFETY_ON_NULL_RETURN(ctx);
459
460 if (eol < 0)
461 {
462 if (pa_context_errno(c) == PA_ERR_NOENTITY)
463 return;
464
465 ERR("Source changed callback failure");
466 return;
467 }
468
469 if (eol > 0)
470 return;
471
472 EINA_LIST_FOREACH(ctx->sources, l, s)
473 {
474 if (s->idx == (int)info->index)
475 {
476 source = s;
477 break;
478 }
479 }
480
481 DBG("source changed index: %d\n", info->index);
482
483 if (!source)
484 {
485 source = calloc(1, sizeof(Source));
486 EINA_SAFETY_ON_NULL_RETURN(source);
487 ctx->sources = eina_list_append(ctx->sources, source);
488 }
489 source->idx= info->index;
490 source->base.volume = _pa_cvolume_convert(info->volume);
491 source->base.mute = !!info->mute;
492
493 if (ctx->cb)
494 ctx->cb((void *)ctx->userdata, EMIX_SOURCE_CHANGED_EVENT,
495 (Emix_Source *)source);
496}
497
498static void
499_source_remove_cb(int index, void *data EINA_UNUSED)
500{
501 Source *source;
502 Eina_List *l;
503 EINA_SAFETY_ON_NULL_RETURN(ctx);
504
505 DBG("Removing source: %d", index);
506
507 EINA_LIST_FOREACH(ctx->sources, l, source)
508 {
509 if (source->idx == index)
510 {
511 if (ctx->cb)
512 ctx->cb((void *)ctx->userdata, EMIX_SOURCE_REMOVED_EVENT,
513 (Emix_Source *)source);
514
515 _source_del(source);
516 ctx->sources = eina_list_remove_list(ctx->sources, l);
517 break;
518 }
519 }
520}
521
522static void
523_sink_default_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol,
524 void *userdata EINA_UNUSED)
525{
526 if (eol < 0)
527 {
528 if (pa_context_errno(c) == PA_ERR_NOENTITY)
529 return;
530
531 ERR("Sink callback failure");
532 return;
533 }
534
535 if (eol > 0)
536 return;
537
538 DBG("sink index: %d\nsink name: %s", info->index,
539 info->name);
540
541 ctx->default_sink = info->index;
542 if (ctx->cb)
543 ctx->cb((void *)ctx->userdata, EMIX_READY_EVENT, NULL);
544}
545
546static void
547_server_info_cb(pa_context *c, const pa_server_info *info,
548 void *userdata)
549{
550 pa_operation *o;
551
552 if (!(o = pa_context_get_sink_info_by_name(c, info->default_sink_name,
553 _sink_default_cb, userdata)))
554 {
555 ERR("pa_context_get_sink_info_by_name() failed");
556 return;
557 }
558 pa_operation_unref(o);
559}
560
561static void
562_subscribe_cb(pa_context *c, pa_subscription_event_type_t t,
563 uint32_t index, void *data)
564{
565 pa_operation *o;
566
567 switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
568 case PA_SUBSCRIPTION_EVENT_SINK:
569 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
570 PA_SUBSCRIPTION_EVENT_REMOVE)
571 _sink_remove_cb(index, data);
572 else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
573 PA_SUBSCRIPTION_EVENT_NEW)
574 {
575 if (!(o = pa_context_get_sink_info_by_index(c, index,
576 _sink_cb, data)))
577 {
578 ERR("pa_context_get_sink_info_by_index() failed");
579 return;
580 }
581 pa_operation_unref(o);
582 }
583 else
584 {
585 if (!(o = pa_context_get_sink_info_by_index(c, index,
586 _sink_changed_cb,
587 data)))
588 {
589 ERR("pa_context_get_sink_info_by_index() failed");
590 return;
591 }
592 pa_operation_unref(o);
593 }
594 break;
595
596 case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
597 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
598 PA_SUBSCRIPTION_EVENT_REMOVE)
599 _sink_input_remove_cb(index, data);
600 else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
601 PA_SUBSCRIPTION_EVENT_NEW)
602 {
603 if (!(o = pa_context_get_sink_input_info(c, index,
604 _sink_input_cb, data)))
605 {
606 ERR("pa_context_get_sink_input_info() failed");
607 return;
608 }
609 pa_operation_unref(o);
610 }
611 else
612 {
613 if (!(o = pa_context_get_sink_input_info(c, index,
614 _sink_input_changed_cb,
615 data)))
616 {
617 ERR("pa_context_get_sink_input_info() failed");
618 return;
619 }
620 pa_operation_unref(o);
621 }
622 break;
623
624 case PA_SUBSCRIPTION_EVENT_SOURCE:
625 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
626 PA_SUBSCRIPTION_EVENT_REMOVE)
627 _source_remove_cb(index, data);
628 else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
629 PA_SUBSCRIPTION_EVENT_NEW)
630 {
631 if (!(o = pa_context_get_source_info_by_index(c, index,
632 _source_cb, data)))
633 {
634 ERR("pa_context_get_source_info() failed");
635 return;
636 }
637 pa_operation_unref(o);
638 }
639 else
640 {
641 if (!(o = pa_context_get_source_info_by_index(c, index,
642 _source_changed_cb,
643 data)))
644 {
645 ERR("pa_context_get_source_info() failed");
646 return;
647 }
648 pa_operation_unref(o);
649 }
650 break;
651
652 default:
653 WRN("Event not handled");
654 break;
655 }
656}
657
658static Eina_Bool _pulse_connect(void *data);
659static void _disconnect_cb();
660
661static void
662_pulse_pa_state_cb(pa_context *context, void *data)
663{
664 pa_operation *o;
665
666 switch (pa_context_get_state(context))
667 {
668 case PA_CONTEXT_UNCONNECTED:
669 case PA_CONTEXT_CONNECTING:
670 case PA_CONTEXT_AUTHORIZING:
671 case PA_CONTEXT_SETTING_NAME:
672 break;
673
674 case PA_CONTEXT_READY:
675 {
676 ctx->connect = NULL;
677 ctx->connected = EINA_TRUE;
678 pa_context_set_subscribe_callback(context, _subscribe_cb, ctx);
679 if (!(o = pa_context_subscribe(context, (pa_subscription_mask_t)
680 (PA_SUBSCRIPTION_MASK_SINK|
681 PA_SUBSCRIPTION_MASK_SOURCE|
682 PA_SUBSCRIPTION_MASK_SINK_INPUT|
683 PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT|
684 PA_SUBSCRIPTION_MASK_CLIENT|
685 PA_SUBSCRIPTION_MASK_SERVER|
686 PA_SUBSCRIPTION_MASK_CARD),
687 NULL, NULL)))
688 {
689 ERR("pa_context_subscribe() failed");
690 return;
691 }
692 pa_operation_unref(o);
693
694 if (!(o = pa_context_get_sink_info_list(context, _sink_cb, ctx)))
695 {
696 ERR("pa_context_get_sink_info_list() failed");
697 return;
698 }
699 pa_operation_unref(o);
700
701 if (!(o = pa_context_get_sink_input_info_list(context,
702 _sink_input_cb,
703 ctx)))
704 {
705 ERR("pa_context_get_sink_input_info_list() failed");
706 return;
707 }
708 pa_operation_unref(o);
709
710 if (!(o = pa_context_get_source_info_list(context, _source_cb,
711 ctx)))
712 {
713 ERR("pa_context_get_source_info_list() failed");
714 return;
715 }
716 pa_operation_unref(o);
717
718 if (!(o = pa_context_get_server_info(context, _server_info_cb,
719 ctx)))
720 {
721 ERR("pa_context_get_server_info() failed");
722 return;
723 }
724 pa_operation_unref(o);
725 break;
726 }
727
728 case PA_CONTEXT_FAILED:
729 WRN("PA_CONTEXT_FAILED");
730 if (!ctx->connect)
731 ctx->connect = ecore_timer_add(1.0, _pulse_connect, data);
732 goto err;
733 case PA_CONTEXT_TERMINATED:
734 ERR("PA_CONTEXT_TERMINATE:");
735 default:
736 if (ctx->connect)
737 {
738 ecore_timer_del(ctx->connect);
739 ctx->connect = NULL;
740 }
741 goto err;
742 }
743 return;
744
745err:
746 if (ctx->connected)
747 {
748 _disconnect_cb();
749 ctx->connected = EINA_FALSE;
750 }
751 pa_context_unref(ctx->context);
752 ctx->context = NULL;
753}
754
755static Eina_Bool
756_pulse_connect(void *data)
757{
758 pa_proplist *proplist;
759 Context *c = data;
760
761 proplist = pa_proplist_new();
762 pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "Efl Volume Control");
763 pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID,
764 "org.enlightenment.volumecontrol");
765 pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "audio-card");
766 c->context = pa_context_new_with_proplist(&(c->api), NULL, proplist);
767 if (!c->context)
768 {
769 WRN("Could not create the pulseaudio context");
770 goto err;
771 }
772
773 pa_context_set_state_callback(c->context, _pulse_pa_state_cb, c);
774 if (pa_context_connect(c->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0)
775 {
776 WRN("Could not connect to pulse");
777 goto err;
778 }
779
780 pa_proplist_free(proplist);
781 return ECORE_CALLBACK_DONE;
782
783 err:
784 pa_proplist_free(proplist);
785 return ECORE_CALLBACK_RENEW;
786}
787
788static void
789_shutdown(void)
790{
791 if (!ctx)
792 return;
793
794 if (ctx->connect)
795 {
796 ecore_timer_del(ctx->connect);
797 ctx->connect = NULL;
798 }
799 if (ctx->context)
800 pa_context_unref(ctx->context);
801 if (ctx->connected)
802 _disconnect_cb();
803 free(ctx);
804 ctx = NULL;
805}
806
807static Eina_Bool
808_init(Emix_Event_Cb cb, const void *data)
809{
810 if (ctx)
811 return EINA_TRUE;
812
813 ctx = calloc(1, sizeof(Context));
814 if (!ctx)
815 {
816 ERR("Could not create Epulse Context");
817 return EINA_FALSE;
818 }
819
820 ctx->api = functable;
821 ctx->api.userdata = ctx;
822
823 /* The reason of compares with EINA_TRUE is because ECORE_CALLBACK_RENEW
824 is EINA_TRUE. The function _pulse_connect returns ECORE_CALLBACK_RENEW
825 when could not connect to pulse.
826 */
827 if (_pulse_connect(ctx) == EINA_TRUE)
828 {
829 _shutdown();
830 return EINA_FALSE;
831 }
832
833 ctx->cb = cb;
834 ctx->userdata = data;
835
836 return EINA_TRUE;
837 }
838
839static void
840_disconnect_cb()
841{
842 Source *source;
843 Sink *sink;
844 Sink_Input *input;
845
846 if (ctx->cb)
847 ctx->cb((void *)ctx->userdata, EMIX_DISCONNECTED_EVENT, NULL);
848
849 EINA_LIST_FREE(ctx->sources, source)
850 _source_del(source);
851 EINA_LIST_FREE(ctx->sinks, sink)
852 _sink_del(sink);
853 EINA_LIST_FREE(ctx->inputs, input)
854 _sink_input_del(input);
855}
856
857static void
858_source_volume_set(Emix_Source *source, Emix_Volume volume)
859{
860 pa_operation* o;
861 pa_cvolume vol = _emix_volume_convert(volume);
862 Source *s = (Source *)source;
863 EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && source != NULL);
864
865 if (!(o = pa_context_set_source_volume_by_index(ctx->context,
866 s->idx, &vol,
867 NULL, NULL)))
868 ERR("pa_context_set_source_volume_by_index() failed");
869}
870
871static void
872_source_mute_set(Emix_Source *source, Eina_Bool mute)
873{
874 pa_operation* o;
875 Source *s = (Source *)source;
876 EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && source != NULL);
877
878 if (!(o = pa_context_set_source_mute_by_index(ctx->context,
879 s->idx, mute, NULL, NULL)))
880 ERR("pa_context_set_source_mute() failed");
881}
882
883static const Eina_List *
884_sinks_get(void)
885{
886 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
887 return ctx->sinks;
888}
889
890static const Eina_List *
891_sources_get(void)
892{
893 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
894 return ctx->sources;
895}
896
897static const Eina_List *
898_sink_inputs_get(void)
899{
900 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
901 return ctx->inputs;
902}
903
904static void
905_sink_volume_set(Emix_Sink *sink, Emix_Volume volume)
906{
907 pa_operation* o;
908 Sink *s = (Sink *)sink;
909 pa_cvolume vol = _emix_volume_convert(volume);
910 EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->context && sink != NULL));
911
912 if (!(o = pa_context_set_sink_volume_by_index(ctx->context,
913 s->idx, &vol, NULL, NULL)))
914 ERR("pa_context_set_sink_volume_by_index() failed");
915}
916
917static void
918_sink_mute_set(Emix_Sink *sink, Eina_Bool mute)
919{
920 pa_operation* o;
921 Sink *s = (Sink *)sink;
922 EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->context && sink != NULL));
923
924 if (!(o = pa_context_set_sink_mute_by_index(ctx->context,
925 s->idx, mute, NULL, NULL)))
926 ERR("pa_context_set_sink_mute() failed");
927}
928
929static void
930_sink_input_volume_set(Emix_Sink_Input *input, Emix_Volume volume)
931{
932 pa_operation* o;
933 Sink_Input *sink_input = (Sink_Input *)input;
934 pa_cvolume vol = _emix_volume_convert(volume);
935 EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && input != NULL);
936
937
938 if (!(o = pa_context_set_sink_input_volume(ctx->context,
939 sink_input->idx, &vol,
940 NULL, NULL)))
941 ERR("pa_context_set_sink_input_volume_by_index() failed");
942}
943
944static void
945_sink_input_mute_set(Emix_Sink_Input *input, Eina_Bool mute)
946{
947 pa_operation* o;
948 Sink_Input *sink_input = (Sink_Input *)input;
949 EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && input != NULL);
950
951 if (!(o = pa_context_set_sink_input_mute(ctx->context,
952 sink_input->idx, mute,
953 NULL, NULL)))
954 ERR("pa_context_set_sink_input_mute() failed");
955}
956
957static void
958_sink_input_move(Emix_Sink_Input *input, Emix_Sink *sink)
959{
960 pa_operation* o;
961 Sink *s = (Sink *)sink;
962 Sink_Input *i = (Sink_Input *)input;
963 EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && input != NULL
964 && sink != NULL);
965
966 if (!(o = pa_context_move_sink_input_by_index(ctx->context,
967 i->idx, s->idx, NULL,
968 NULL)))
969 ERR("pa_context_move_sink_input_by_index() failed");
970}
971
972static Eina_Bool
973_sink_port_set(Emix_Sink *sink, const Emix_Port *port)
974{
975 pa_operation* o;
976 Sink *s = (Sink *)sink;
977 EINA_SAFETY_ON_FALSE_RETURN_VAL(ctx && ctx->context &&
978 sink != NULL && port != NULL, EINA_FALSE);
979
980 if (!(o = pa_context_set_sink_port_by_index(ctx->context,
981 s->idx, port->name, NULL,
982 NULL)))
983 {
984 ERR("pa_context_set_source_port_by_index() failed");
985 return EINA_FALSE;
986 }
987 pa_operation_unref(o);
988
989 return EINA_TRUE;
990}
991
992static Eina_Bool
993_sink_default_support(void)
994{
995 return EINA_TRUE;
996}
997
998static const Emix_Sink *
999_sink_default_get(void)
1000{
1001 Sink *s;
1002 Eina_List *l;
1003
1004 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
1005 EINA_LIST_FOREACH(ctx->sinks, l, s)
1006 if (s->idx == ctx->default_sink)
1007 return (Emix_Sink *)s;
1008
1009 return NULL;
1010}
1011
1012static Eina_Bool
1013_sink_change_support(void)
1014{
1015 return EINA_TRUE;
1016}
1017
1018static Emix_Backend
1019_pulseaudio_backend =
1020{
1021 _init,
1022 _shutdown,
1023 _sinks_get,
1024 _sink_default_support,
1025 _sink_default_get,
1026 NULL,
1027 _sink_mute_set,
1028 _sink_volume_set,
1029 _sink_port_set,
1030 _sink_change_support,
1031 _sink_inputs_get,
1032 _sink_input_mute_set,
1033 _sink_input_volume_set,
1034 _sink_input_move,
1035 _sources_get,
1036 _source_mute_set,
1037 _source_volume_set,
1038 NULL,
1039};
1040
1041E_API Emix_Backend *
1042emix_backend_pulse_get(void)
1043{
1044 return &_pulseaudio_backend;
1045}
1046
1047E_API const char *emix_backend_pulse_name = "PULSEAUDIO";
diff --git a/src/modules/mixer/lib/backends/pulseaudio/pulse_ml.c b/src/modules/mixer/lib/backends/pulseaudio/pulse_ml.c
new file mode 100644
index 0000000..469aaca
--- /dev/null
+++ b/src/modules/mixer/lib/backends/pulseaudio/pulse_ml.c
@@ -0,0 +1,319 @@
1#ifdef HAVE_CONFIG_H
2#include <config.h>
3#endif
4
5#include <Eina.h>
6#include <Ecore.h>
7
8#include <ctype.h>
9#include <errno.h>
10
11#include <pulse/pulseaudio.h>
12
13#include <sys/time.h>
14#include <sys/types.h>
15#include <sys/socket.h>
16
17#define ERR(...) EINA_LOG_ERR(__VA_ARGS__)
18#define DBG(...) EINA_LOG_DBG(__VA_ARGS__)
19#define WRN(...) EINA_LOG_WARN(__VA_ARGS__)
20
21/* Ecore mainloop integration start */
22struct pa_io_event
23{
24 pa_mainloop_api *mainloop;
25 Ecore_Fd_Handler *handler;
26
27 void *userdata;
28
29 pa_io_event_flags_t flags;
30 pa_io_event_cb_t callback;
31 pa_io_event_destroy_cb_t destroy_callback;
32};
33
34static Ecore_Fd_Handler_Flags
35map_flags_to_ecore(pa_io_event_flags_t flags)
36{
37 return (Ecore_Fd_Handler_Flags)((flags & PA_IO_EVENT_INPUT ? ECORE_FD_READ : 0) |
38 (flags & PA_IO_EVENT_OUTPUT ? ECORE_FD_WRITE : 0) |
39 (flags & PA_IO_EVENT_ERROR ? ECORE_FD_ERROR : 0) |
40 (flags & PA_IO_EVENT_HANGUP ? ECORE_FD_READ : 0));
41}
42
43static Eina_Bool
44_ecore_io_wrapper(void *data, Ecore_Fd_Handler *handler)
45{
46 char buf[64];
47 pa_io_event_flags_t flags = 0;
48 pa_io_event *event = (pa_io_event *)data;
49 int fd = 0;
50
51 fd = ecore_main_fd_handler_fd_get(handler);
52 if (fd < 0) return ECORE_CALLBACK_RENEW;
53
54 if (ecore_main_fd_handler_active_get(handler, ECORE_FD_READ))
55 {
56 flags |= PA_IO_EVENT_INPUT;
57
58 /* Check for HUP and report */
59 if (recv(fd, buf, 64, MSG_PEEK))
60 {
61 if (errno == ESHUTDOWN || errno == ECONNRESET ||
62 errno == ECONNABORTED || errno == ENETRESET)
63 {
64 DBG("HUP condition detected");
65 flags |= PA_IO_EVENT_HANGUP;
66 }
67 }
68 }
69
70 if (ecore_main_fd_handler_active_get(handler, ECORE_FD_WRITE))
71 flags |= PA_IO_EVENT_OUTPUT;
72 if (ecore_main_fd_handler_active_get(handler, ECORE_FD_ERROR))
73 flags |= PA_IO_EVENT_ERROR;
74
75 event->callback(event->mainloop, event, fd, flags, event->userdata);
76
77 return ECORE_CALLBACK_RENEW;
78}
79
80static pa_io_event *
81_ecore_pa_io_new(pa_mainloop_api *api, int fd, pa_io_event_flags_t flags,
82 pa_io_event_cb_t cb, void *userdata)
83{
84 pa_io_event *event;
85
86 event = calloc(1, sizeof(pa_io_event));
87 event->mainloop = api;
88 event->userdata = userdata;
89 event->callback = cb;
90 event->flags = flags;
91 event->handler = ecore_main_fd_handler_add(fd, map_flags_to_ecore(flags),
92 _ecore_io_wrapper, event,
93 NULL, NULL);
94
95 return event;
96}
97
98static void
99_ecore_pa_io_enable(pa_io_event *event, pa_io_event_flags_t flags)
100{
101 event->flags = flags;
102 ecore_main_fd_handler_active_set(event->handler, map_flags_to_ecore(flags));
103}
104
105static void
106_ecore_pa_io_free(pa_io_event *event)
107{
108 ecore_main_fd_handler_del(event->handler);
109 free(event);
110}
111
112static void
113_ecore_pa_io_set_destroy(pa_io_event *event, pa_io_event_destroy_cb_t cb)
114{
115 event->destroy_callback = cb;
116}
117
118/* Timed events */
119struct pa_time_event
120{
121 pa_mainloop_api *mainloop;
122 Ecore_Timer *timer;
123 struct timeval tv;
124
125 void *userdata;
126
127 pa_time_event_cb_t callback;
128 pa_time_event_destroy_cb_t destroy_callback;
129};
130
131Eina_Bool
132_ecore_time_wrapper(void *data)
133{
134 pa_time_event *event = (pa_time_event *)data;
135
136 event->callback(event->mainloop, event, &event->tv, event->userdata);
137
138 return ECORE_CALLBACK_CANCEL;
139}
140
141pa_time_event *
142_ecore_pa_time_new(pa_mainloop_api *api, const struct timeval *tv, pa_time_event_cb_t cb, void *userdata)
143{
144 pa_time_event *event;
145 struct timeval now;
146 double interval;
147
148 event = calloc(1, sizeof(pa_time_event));
149 event->mainloop = api;
150 event->userdata = userdata;
151 event->callback = cb;
152 event->tv = *tv;
153
154 if (gettimeofday(&now, NULL) == -1)
155 {
156 ERR("Failed to get the current time!");
157 free(event);
158 return NULL;
159 }
160
161 interval = (tv->tv_sec - now.tv_sec) + (tv->tv_usec - now.tv_usec) / 1000;
162 event->timer = ecore_timer_add(interval, _ecore_time_wrapper, event);
163
164 return event;
165}
166
167void
168_ecore_pa_time_restart(pa_time_event *event, const struct timeval *tv)
169{
170 struct timeval now;
171 double interval;
172
173 /* If tv is NULL disable timer */
174 if (!tv)
175 {
176 ecore_timer_del(event->timer);
177 event->timer = NULL;
178 return;
179 }
180
181 event->tv = *tv;
182
183 if (gettimeofday(&now, NULL) == -1)
184 {
185 ERR("Failed to get the current time!");
186 return;
187 }
188
189 interval = (tv->tv_sec - now.tv_sec) + (tv->tv_usec - now.tv_usec) / 1000;
190 if (event->timer)
191 {
192 event->timer = ecore_timer_add(interval, _ecore_time_wrapper, event);
193 }
194 else
195 {
196 ecore_timer_interval_set(event->timer, interval);
197 ecore_timer_reset(event->timer);
198 }
199}
200
201void
202_ecore_pa_time_free(pa_time_event *event)
203{
204 if (event->timer)
205 ecore_timer_del(event->timer);
206
207 event->timer = NULL;
208
209 free(event);
210}
211
212void
213_ecore_pa_time_set_destroy(pa_time_event *event, pa_time_event_destroy_cb_t cb)
214{
215 event->destroy_callback = cb;
216}
217
218/* Deferred events */
219struct pa_defer_event
220{
221 pa_mainloop_api *mainloop;
222 Ecore_Idler *idler;
223
224 void *userdata;
225
226 pa_defer_event_cb_t callback;
227 pa_defer_event_destroy_cb_t destroy_callback;
228};
229
230Eina_Bool
231_ecore_defer_wrapper(void *data)
232{
233 pa_defer_event *event = (pa_defer_event *)data;
234
235 event->idler = NULL;
236 event->callback(event->mainloop, event, event->userdata);
237
238 return ECORE_CALLBACK_CANCEL;
239}
240
241pa_defer_event *
242_ecore_pa_defer_new(pa_mainloop_api *api, pa_defer_event_cb_t cb, void *userdata)
243