e mixer - replace with epulse/emixer
authorCarsten Haitzler (Rasterman) <raster@rasterman.com>
Mon, 8 Jun 2015 12:00:23 +0000 (21:00 +0900)
committerCarsten Haitzler (Rasterman) <raster@rasterman.com>
Mon, 8 Jun 2015 12:15:09 +0000 (21:15 +0900)
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.

14 files changed:
configure.ac
po/POTFILES.in
src/modules/Makefile_mixer.mk
src/modules/mixer/.gitignore [new file with mode: 0644]
src/modules/mixer/e_mod_config.c [new file with mode: 0644]
src/modules/mixer/e_mod_config.h [new file with mode: 0644]
src/modules/mixer/e_mod_main.c [new file with mode: 0644]
src/modules/mixer/e_mod_main.h [new file with mode: 0644]
src/modules/mixer/emixer.c [new file with mode: 0644]
src/modules/mixer/lib/backends/alsa/alsa.c [new file with mode: 0644]
src/modules/mixer/lib/backends/pulseaudio/pulse.c [new file with mode: 0644]
src/modules/mixer/lib/backends/pulseaudio/pulse_ml.c [new file with mode: 0644]
src/modules/mixer/lib/emix.c [new file with mode: 0644]
src/modules/mixer/lib/emix.h [new file with mode: 0644]

index 6b7afda..3a59fcb 100644 (file)
@@ -722,28 +722,13 @@ define([CHECK_MODULE_NOTIFICATION],
 
 
 AM_CONDITIONAL(HAVE_ALSA, false)
+AM_CONDITIONAL(HAVE_PULSE, false)
 define([CHECK_MODULE_MIXER],
 [
-  if test "x$enable_alsa" = "x" || test "x$enable_alsa" = "xdefault" || test "x$enable_alsa" = "xyes"; then
-    AC_E_CHECK_PKG(ALSA, [alsa >= 1.0.8],
-    [ SOUND_CFLAGS="$ALSA_CFLAGS -DHAVE_ALSA $SOUND_CFLAGS"
-      SOUND_LIBS="$ALSA_LIBS $SOUND_LDFLAGS"
-    ],
-    [ if test "x$enable_alsa" = "xyes"; then
-        AC_MSG_ERROR([alsa library >= 1.0.8 not found])
-      else
-        AC_MSG_WARN([alsa library development files not present. no alsa support.])
-      fi
-    ])
-  else
-    have_alsa=no
-  fi
-
-  if test "$have_alsa" = "yes"; then
-    AC_DEFINE(HAVE_ALSA, 1, [Define if the ALSA output plugin should be built])
-  else
-    have_alsa=no
-  fi
+  AC_E_CHECK_PKG(ALSA, [alsa >= 1.0.8],
+    [ ], [ ])
+ AC_E_CHECK_PKG([PULSE], [libpulse-simple libpulse],
+    [ ], [ ])
 ])
 
 SHM_OPEN_LIBS=""
index c59c782..890250e 100644 (file)
@@ -238,10 +238,8 @@ src/modules/ibox/e_mod_config.c
 src/modules/ibox/e_mod_main.c
 src/modules/lokker/e_mod_main.c
 src/modules/lokker/lokker.c
-src/modules/mixer/app_mixer.c
-src/modules/mixer/conf_gadget.c
-src/modules/mixer/conf_module.c
 src/modules/mixer/e_mod_main.c
+src/modules/mixer/e_mod_config.c
 src/modules/music-control/e_mod_main.c
 src/modules/music-control/ui.c
 src/modules/notification/e_mod_config.c
index c5815d9..9540171 100644 (file)
@@ -5,42 +5,36 @@ mixerdir = $(MDIR)/mixer
 mixer_DATA = src/modules/mixer/e-module-mixer.edj \
             src/modules/mixer/module.desktop
 
-
 mixerpkgdir = $(MDIR)/mixer/$(MODULE_ARCH)
 mixerpkg_LTLIBRARIES = src/modules/mixer/module.la
 
-src_modules_mixer_module_la_CPPFLAGS = $(MOD_CPPFLAGS) @SOUND_CFLAGS@
-
-src_modules_mixer_module_la_LDFLAGS = $(MOD_LDFLAGS)
-src_modules_mixer_module_la_SOURCES = src/modules/mixer/e_mod_main.c \
-                         src/modules/mixer/e_mod_main.h \
-                         src/modules/mixer/e_mod_mixer.h \
-                         src/modules/mixer/e_mod_mixer.c \
-                         src/modules/mixer/app_mixer.c \
-                         src/modules/mixer/conf_gadget.c \
-                         src/modules/mixer/conf_module.c \
-                         src/modules/mixer/msg.c \
-                         src/modules/mixer/Pulse.h \
-                         src/modules/mixer/pa.h \
-                         src/modules/mixer/pa.c \
-                         src/modules/mixer/serial.c \
-                         src/modules/mixer/sink.c \
-                         src/modules/mixer/sys_pulse.c \
-                         src/modules/mixer/tag.c
+emixerlib = src/modules/mixer/lib/emix.c src/modules/mixer/lib/emix.h
 
 if HAVE_ALSA
-src_modules_mixer_module_la_SOURCES += src/modules/mixer/sys_alsa.c
-else
-src_modules_mixer_module_la_SOURCES += src/modules/mixer/sys_dummy.c
+emixerlib += src/modules/mixer/lib/backends/alsa/alsa.c
 endif
 
-src_modules_mixer_module_la_LIBADD = $(MOD_LIBS) @SOUND_LIBS@
-
-if HAVE_ENOTIFY
-src_modules_mixer_module_la_CPPFLAGS += @ENOTIFY_CFLAGS@
-src_modules_mixer_module_la_LIBADD += @ENOTIFY_LIBS@
+if HAVE_PULSE
+emixerlib += src/modules/mixer/lib/backends/pulseaudio/pulse_ml.c
+emixerlib += src/modules/mixer/lib/backends/pulseaudio/pulse.c
 endif
 
+src_modules_mixer_emixerdir = $(mixerpkgdir)
+src_modules_mixer_emixer_PROGRAMS = src/modules/mixer/emixer
+src_modules_mixer_emixer_SOURCES = src/modules/mixer/emixer.c \
+                          $(emixerlib)
+src_modules_mixer_emixer_CPPFLAGS = $(MOD_CPPFLAGS) @e_cflags@ -I$(top_srcdir)/src/modules/mixer/lib
+src_modules_mixer_emixer_LDADD = $(MOD_LIBS) @PULSE_LIBS@ @ALSA_LIBS@
+
+src_modules_mixer_module_la_CPPFLAGS = $(MOD_CPPFLAGS) @e_cflags@ @ALSA_CFLAGS@ @PULSE_CFLAGS@ -I$(top_srcdir)/src/modules/mixer/lib
+src_modules_mixer_module_la_LDFLAGS = $(MOD_LDFLAGS)
+src_modules_mixer_module_la_SOURCES = src/modules/mixer/e_mod_main.c \
+                         src/modules/mixer/e_mod_main.h \
+                         src/modules/mixer/e_mod_config.c \
+                         src/modules/mixer/e_mod_config.h \
+                          $(emixerlib)
+src_modules_mixer_module_la_LIBADD = $(MOD_LIBS) @PULSE_LIBS@ @ALSA_LIBS@
+
 PHONIES += mixer install-mixer
 mixer: $(mixerpkg_LTLIBRARIES) $(mixer_DATA)
 install-mixer: install-mixerDATA install-mixerpkgLTLIBRARIES
diff --git a/src/modules/mixer/.gitignore b/src/modules/mixer/.gitignore
new file mode 100644 (file)
index 0000000..aaded20
--- /dev/null
@@ -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 (file)
index 0000000..057b59a
--- /dev/null
@@ -0,0 +1,201 @@
+#include "e.h"
+#include "e_mod_config.h"
+#include "e_mod_main.h"
+#include "emix.h"
+
+typedef struct _Emix_Config
+{
+   const char *backend;
+   int notify;
+   int mute;
+
+   emix_config_backend_changed cb;
+   const void *userdata;
+} Emix_Config;
+
+struct _E_Config_Dialog_Data
+{
+   Emix_Config config;
+   Evas_Object *list;
+};
+
+static E_Config_DD *cd;
+static Emix_Config *_config;
+
+static E_Config_DD*
+_emix_config_dd_new(void)
+{
+   E_Config_DD *result = E_CONFIG_DD_NEW("Emix_Config", Emix_Config);
+
+   E_CONFIG_VAL(result, Emix_Config, backend, STR);
+   E_CONFIG_VAL(result, Emix_Config, notify, INT);
+   E_CONFIG_VAL(result, Emix_Config, mute, INT);
+
+   return result;
+}
+
+const char *
+emix_config_backend_get(void)
+{
+   return _config->backend;
+}
+
+void
+emix_config_backend_set(const char *backend)
+{
+   eina_stringshare_replace(&_config->backend, backend);
+   e_config_domain_save("module.emix", cd, _config);
+}
+
+Eina_Bool
+emix_config_notify_get(void)
+{
+   return _config->notify;
+}
+
+Eina_Bool
+emix_config_desklock_mute_get(void)
+{
+   return _config->mute;
+}
+
+static void
+_config_set(Emix_Config *config)
+{
+   if ((config->backend) && (_config->backend != config->backend))
+     eina_stringshare_replace(&_config->backend, config->backend);
+
+   _config->notify = config->notify;
+   _config->mute = config->mute;
+
+   DBG("SAVING CONFIG %s %d %d", _config->backend, config->notify,
+       config->mute);
+   e_config_domain_save("module.emix", cd, config);
+}
+
+void
+emix_config_init(emix_config_backend_changed cb, const void *userdata)
+{
+   const Eina_List *l;
+
+   EINA_SAFETY_ON_FALSE_RETURN(emix_init());
+   cd = _emix_config_dd_new();
+   _config = e_config_domain_load("module.emix", cd);
+   if (!_config)
+     {
+        _config = E_NEW(Emix_Config, 1);
+        l = emix_backends_available();
+        if (l)
+          _config->backend = eina_stringshare_add(l->data);
+     }
+
+   _config->cb = cb;
+   _config->userdata = userdata;
+   DBG("Config loaded, backend to use: %s", _config->backend);
+}
+
+void
+emix_config_shutdown(void)
+{
+   E_CONFIG_DD_FREE(cd);
+   if (_config->backend)
+     eina_stringshare_del(_config->backend);
+   free(_config);
+   emix_shutdown();
+}
+
+static void*
+_create_data(E_Config_Dialog *cfg EINA_UNUSED)
+{
+   E_Config_Dialog_Data *d;
+
+   d = E_NEW(E_Config_Dialog_Data, 1);
+   d->config.backend = eina_stringshare_add(_config->backend);
+   d->config.notify = _config->notify;
+   d->config.mute = _config->mute;
+
+   return d;
+}
+
+static void
+_free_data(E_Config_Dialog *c EINA_UNUSED, E_Config_Dialog_Data *cf)
+{
+   eina_stringshare_del(cf->config.backend);
+   free(cf);
+}
+
+static Evas_Object *
+_basic_create_widgets(E_Config_Dialog *cfd EINA_UNUSED, Evas *evas,
+                      E_Config_Dialog_Data *cfdata)
+{
+   Evas_Object *o, *l;
+   const Eina_List *node;
+   char *name;
+   int i = 0;
+
+   o = e_widget_list_add(evas, 0, 0);
+
+   l = e_widget_check_add(evas, "Notify on volume change", &cfdata->config.notify);
+   e_widget_list_object_append(o, l, 0, 0, 0);
+
+   l = e_widget_check_add(evas, "Mute on lock", &cfdata->config.mute);
+   e_widget_list_object_append(o, l, 0, 0, 0);
+
+   l = e_widget_label_add(evas, "Backend to use:");
+   e_widget_list_object_append(o, l, 0, 0, 0);
+
+   cfdata->list = l = e_widget_ilist_add(evas, 0, 0, NULL);
+   e_widget_ilist_multi_select_set(l, EINA_FALSE);
+   e_widget_size_min_set(l, 100, 100);
+   EINA_LIST_FOREACH(emix_backends_available(), node, name)
+     {
+        e_widget_ilist_append(l, NULL, name, NULL, NULL, NULL);
+        i ++;
+        if (_config->backend && !strcmp(_config->backend, name))
+          e_widget_ilist_selected_set(l, i);
+     }
+   e_widget_ilist_go(l);
+   e_widget_ilist_thaw(l);
+   e_widget_list_object_append(o, l, 1, 1, 0);
+
+   return o;
+}
+
+static int
+_basic_apply_data(E_Config_Dialog *cfd EINA_UNUSED,
+                  E_Config_Dialog_Data *cfdata)
+{
+   char *new_backend = eina_list_nth(
+                            emix_backends_available(),
+                            e_widget_ilist_selected_get(cfdata->list));
+
+   eina_stringshare_replace(&cfdata->config.backend, new_backend);
+
+   _config_set(&cfdata->config);
+   if (_config->cb)
+      _config->cb(new_backend, (void *)_config->userdata);
+   return 1;
+}
+
+E_Config_Dialog*
+emix_config_popup_new(Evas_Object *comp, const char *p EINA_UNUSED)
+{
+   E_Config_Dialog *cfd;
+   E_Config_Dialog_View *v;
+
+   if (e_config_dialog_find("E", "windows/emix"))
+      return NULL;
+
+   v = E_NEW(E_Config_Dialog_View, 1);
+   v->create_cfdata = _create_data;
+   v->free_cfdata = _free_data;
+   v->basic.apply_cfdata = _basic_apply_data;
+   v->basic.create_widgets = _basic_create_widgets;
+
+   cfd = e_config_dialog_new(comp,
+                             "Emix Configuration",
+                             "E", "windows/emix",
+                             NULL,
+                             0, v, NULL);
+   return cfd;
+}
diff --git a/src/modules/mixer/e_mod_config.h b/src/modules/mixer/e_mod_config.h
new file mode 100644 (file)
index 0000000..c725753
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef E_MOD_CONFIG_H
+#define E_MOD_CONFIG_H
+
+#include <e.h>
+
+typedef void (*emix_config_backend_changed)(const char *backend, void *data);
+typedef void (*emix_config_meter_changed)(Eina_Bool enable, void *data);
+
+void emix_config_init(emix_config_backend_changed cb, const void *userdata);
+void emix_config_shutdown(void);
+const char *emix_config_backend_get(void);
+void emix_config_backend_set(const char *backend);
+Eina_Bool emix_config_desklock_mute_get(void);
+Eina_Bool emix_config_meter_get(void);
+Eina_Bool emix_config_notify_get(void);
+E_Config_Dialog* emix_config_popup_new(Evas_Object *comp, const char*p);
+
+#endif
diff --git a/src/modules/mixer/e_mod_main.c b/src/modules/mixer/e_mod_main.c
new file mode 100644 (file)
index 0000000..0a7c4fb
--- /dev/null
@@ -0,0 +1,817 @@
+#include <e.h>
+#include <Eina.h>
+#include "emix.h"
+#include "e_mod_main.h"
+#include "e_mod_config.h"
+
+#define VOLUME_STEP 5
+
+int _e_emix_log_domain;
+
+/* module requirements */
+E_API E_Module_Api e_modapi =
+   {
+      E_MODULE_API_VERSION,
+      "Mixer"
+   };
+
+/* necessary forward delcaration */
+static E_Gadcon_Client *_gc_init(E_Gadcon *gc, const char *name,
+                                 const char *id, const char *style);
+static void             _gc_shutdown(E_Gadcon_Client *gcc);
+static void             _gc_orient(E_Gadcon_Client *gcc,
+                                   E_Gadcon_Orient orient);
+static const char      *_gc_label(const E_Gadcon_Client_Class *client_class);
+static Evas_Object     *_gc_icon(const E_Gadcon_Client_Class *client_class,
+                                 Evas *evas);
+static const char      *_gc_id_new(const E_Gadcon_Client_Class *client_class);
+
+static const E_Gadcon_Client_Class _gadcon_class =
+   {
+      GADCON_CLIENT_CLASS_VERSION,
+      "emix",
+      {
+         _gc_init, _gc_shutdown,
+         _gc_orient, _gc_label, _gc_icon, _gc_id_new, NULL,
+         e_gadcon_site_is_not_toolbar
+      },
+      E_GADCON_CLIENT_STYLE_PLAIN
+   };
+
+typedef struct _Context Context;
+struct _Context
+{
+   char *theme;
+   Ecore_Exe *emixer;
+   Ecore_Event_Handler *desklock_handler;
+   Ecore_Event_Handler *emix_event_handler;
+   const Emix_Sink *sink_default;
+   E_Module *module;
+   Eina_List *instances;
+   E_Menu *menu;
+   unsigned int notification_id;
+
+   struct {
+      E_Action *incr;
+      E_Action *decr;
+      E_Action *mute;
+   } actions;
+};
+
+typedef struct _Instance Instance;
+struct _Instance
+{
+   E_Gadcon_Client *gcc;
+   E_Gadcon_Orient orient;
+
+   E_Gadcon_Popup *popup;
+   Evas *evas;
+   Evas_Object *gadget;
+   Evas_Object *list;
+   Evas_Object *slider;
+   Evas_Object *check;
+
+   Eina_Bool mute;
+};
+
+static Context *mixer_context = NULL;
+
+static void
+_notify_cb(void *data EINA_UNUSED, unsigned int id)
+{
+   mixer_context->notification_id = id;
+}
+
+static void
+_notify(const int val)
+{
+   E_Notification_Notify n;
+   char *icon, buf[56];
+   int ret;
+
+   if (!emix_config_notify_get())
+     return;
+
+   memset(&n, 0, sizeof(E_Notification_Notify));
+   if (val > EMIX_VOLUME_MAX || val < 0)
+     return;
+
+   ret = snprintf(buf, (sizeof(buf) - 1), "%s: %d%%", _("New volume"), val);
+   if ((ret < 0) || ((unsigned int)ret > sizeof(buf)))
+     return;
+   //Names are taken from FDO icon naming scheme
+   if (val == 0)
+     icon = "audio-volume-muted";
+   else if ((val > 33) && (val < 66))
+     icon = "audio-volume-medium";
+   else if (val < 33)
+     icon = "audio-volume-low";
+   else
+     icon = "audio-volume-high";
+
+   n.app_name = _("Emix");
+   n.replaces_id = mixer_context->notification_id;
+   n.icon.icon = icon;
+   n.summary = _("Volume changed");
+   n.body = buf;
+   n.timeout = 2000;
+   e_notification_client_send(&n, _notify_cb, NULL);
+}
+
+static void
+_mixer_popup_update(Instance *inst, int mute, int vol)
+{
+   elm_check_state_set(inst->check, !!mute);
+   elm_slider_value_set(inst->slider, vol);
+}
+
+static void _popup_del(Instance *inst);
+
+static void
+_mixer_gadget_update(void)
+{
+   Edje_Message_Int_Set *msg;
+   Instance *inst;
+   Eina_List *l;
+
+   EINA_LIST_FOREACH(mixer_context->instances, l, inst)
+     {
+        msg = alloca(sizeof(Edje_Message_Int_Set) + (2 * sizeof(int)));
+        msg->count = 3;
+
+        if (!mixer_context->sink_default)
+          {
+             msg->val[0] = EINA_FALSE;
+             msg->val[1] = 0;
+             msg->val[2] = 0;
+             if (inst->popup)
+               _popup_del(inst);
+          }
+        else
+          {
+             int vol = 0;
+             unsigned int i = 0;
+             for (i = 0; i <
+                  mixer_context->sink_default->volume.channel_count; i++)
+               vol += mixer_context->sink_default->volume.volumes[i];
+             if (mixer_context->sink_default->volume.channel_count)
+               vol /= mixer_context->sink_default->volume.channel_count;
+             msg->val[0] = mixer_context->sink_default->mute;
+             msg->val[1] = vol;
+             msg->val[2] = msg->val[1];
+             if (inst->popup)
+               _mixer_popup_update(inst, mixer_context->sink_default->mute,
+                                    msg->val[1]);
+          }
+        edje_object_message_send(inst->gadget, EDJE_MESSAGE_INT_SET, 0, msg);
+        edje_object_signal_emit(inst->gadget, "e,action,volume,change", "e");
+     }
+}
+
+static void
+_volume_increase_cb(E_Object *obj EINA_UNUSED, const char *params EINA_UNUSED)
+{
+   unsigned int i;
+   EINA_SAFETY_ON_NULL_RETURN(mixer_context->sink_default);
+   Emix_Volume volume;
+
+   Emix_Sink *s = (Emix_Sink *)mixer_context->sink_default;
+   volume.channel_count = s->volume.channel_count;
+   volume.volumes = calloc(s->volume.channel_count, sizeof(int));
+   for (i = 0; i < volume.channel_count; i++)
+     {
+        if (s->volume.volumes[i] < EMIX_VOLUME_MAX - VOLUME_STEP)
+          volume.volumes[i] = s->volume.volumes[i] + VOLUME_STEP;
+        else if (s->volume.volumes[i] < EMIX_VOLUME_MAX)
+          volume.volumes[i] = EMIX_VOLUME_MAX;
+        else
+          volume.volumes[i] = s->volume.volumes[i];
+     }
+
+   emix_sink_volume_set(s, volume);
+   free(volume.volumes);
+}
+
+static void
+_volume_decrease_cb(E_Object *obj EINA_UNUSED, const char *params EINA_UNUSED)
+{
+   unsigned int i;
+   EINA_SAFETY_ON_NULL_RETURN(mixer_context->sink_default);
+   Emix_Volume volume;
+
+   Emix_Sink *s = (Emix_Sink *)mixer_context->sink_default;
+   volume.channel_count = s->volume.channel_count;
+   volume.volumes = calloc(s->volume.channel_count, sizeof(int));
+   for (i = 0; i < volume.channel_count; i++)
+     {
+        if (s->volume.volumes[i] > VOLUME_STEP)
+          volume.volumes[i] = s->volume.volumes[i] - VOLUME_STEP;
+        else if (s->volume.volumes[i] < VOLUME_STEP)
+          volume.volumes[i] = 0;
+        else
+          volume.volumes[i] = s->volume.volumes[i];
+     }
+
+   emix_sink_volume_set(s, volume);
+   free(volume.volumes);
+}
+
+static void
+_volume_mute_cb(E_Object *obj EINA_UNUSED, const char *params EINA_UNUSED)
+{
+   EINA_SAFETY_ON_NULL_RETURN(mixer_context->sink_default);
+
+   Emix_Sink *s = (Emix_Sink *)mixer_context->sink_default;
+   Eina_Bool mute = !s->mute;
+   emix_sink_mute_set(s, mute);
+}
+
+static void
+_actions_register(void)
+{
+   mixer_context->actions.incr = e_action_add("volume_increase");
+   if (mixer_context->actions.incr)
+     {
+        mixer_context->actions.incr->func.go = _volume_increase_cb;
+        e_action_predef_name_set("Mixer", _("Increase Volume"),
+                                 "volume_increase", NULL, NULL, 0);
+     }
+
+   mixer_context->actions.decr = e_action_add("volume_decrease");
+   if (mixer_context->actions.decr)
+     {
+        mixer_context->actions.decr->func.go = _volume_decrease_cb;
+        e_action_predef_name_set("Mixer", _("Decrease Volume"),
+                                 "volume_decrease", NULL, NULL, 0);
+     }
+
+   mixer_context->actions.mute = e_action_add("volume_mute");
+   if (mixer_context->actions.mute)
+     {
+        mixer_context->actions.mute->func.go = _volume_mute_cb;
+        e_action_predef_name_set("Mixer", _("Mute volume"), "volume_mute",
+                                 NULL, NULL, 0);
+     }
+
+   e_comp_canvas_keys_ungrab();
+   e_comp_canvas_keys_grab();
+}
+
+static void
+_actions_unregister(void)
+{
+   if (mixer_context->actions.incr)
+     {
+        e_action_predef_name_del("Mixer", _("Increase Volume"));
+        e_action_del("volume_increase");
+        mixer_context->actions.incr = NULL;
+     }
+
+   if (mixer_context->actions.decr)
+     {
+        e_action_predef_name_del("Mixer", _("Decrease Volume"));
+        e_action_del("volume_decrease");
+        mixer_context->actions.decr = NULL;
+     }
+
+   if (mixer_context->actions.mute)
+     {
+        e_action_predef_name_del("Mixer", _("Mute Volume"));
+        e_action_del("volume_mute");
+        mixer_context->actions.mute = NULL;
+     }
+
+   e_comp_canvas_keys_ungrab();
+   e_comp_canvas_keys_grab();
+}
+
+static void
+_popup_del(Instance *inst)
+{
+   inst->slider = NULL;
+   inst->check = NULL;
+   E_FREE_FUNC(inst->popup, e_object_del);
+}
+
+static void
+_popup_del_cb(void *obj)
+{
+   _popup_del(e_object_data_get(obj));
+}
+
+static void
+_popup_comp_del_cb(void *data, Evas_Object *obj EINA_UNUSED)
+{
+   Instance *inst = data;
+
+   E_FREE_FUNC(inst->popup, e_object_del);
+}
+
+static Eina_Bool
+_emixer_del_cb(void *data EINA_UNUSED, int type EINA_UNUSED,
+               void *info EINA_UNUSED)
+{
+   mixer_context->emixer = NULL;
+   if (mixer_context->emix_event_handler)
+      ecore_event_handler_del(mixer_context->emix_event_handler);
+
+   return EINA_TRUE;
+}
+
+static void
+_emixer_exec_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
+{
+   Instance *inst = data;
+   char buf[PATH_MAX];
+
+   _popup_del(inst);
+   if (mixer_context->emixer)
+      return;
+
+   snprintf(buf, sizeof(buf), "%s/%s/emixer %s",
+            e_module_dir_get(mixer_context->module),
+            MODULE_ARCH, emix_config_backend_get());
+   mixer_context->emixer = ecore_exe_run(buf, NULL);
+   if (mixer_context->emix_event_handler)
+      ecore_event_handler_del(mixer_context->emix_event_handler);
+   mixer_context->emix_event_handler =
+      ecore_event_handler_add(ECORE_EXE_EVENT_DEL, _emixer_del_cb, NULL);
+}
+
+static void
+_check_changed_cb(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED,
+                  void *event EINA_UNUSED)
+{
+   Emix_Sink *s = (Emix_Sink *)mixer_context->sink_default;
+   emix_sink_mute_set(s, !s->mute);
+   /*
+    *TODO: is it really necessary ? or it will be update
+    *      with the sink changed hanlder
+    */
+   _mixer_gadget_update();
+}
+
+static void
+_slider_changed_cb(void *data EINA_UNUSED, Evas_Object *obj,
+                   void *event EINA_UNUSED)
+{
+   int val;
+   Emix_Volume v;
+   unsigned int i;
+   Emix_Sink *s = (Emix_Sink *)mixer_context->sink_default;
+
+   val = (int)elm_slider_value_get(obj);
+   v.volumes = calloc(s->volume.channel_count, sizeof(int));
+   v.channel_count = s->volume.channel_count;
+   for (i = 0; i < s->volume.channel_count; i++)
+     v.volumes[i] = val;
+
+   emix_sink_volume_set(s, v);
+}
+
+static Evas_Object *
+_popup_add_slider(void)
+{
+   unsigned int volume, i;
+   unsigned int channels = mixer_context->sink_default->volume.channel_count;
+
+   Evas_Object *slider = elm_slider_add(e_comp->elm);
+   evas_object_size_hint_align_set(slider, EVAS_HINT_FILL, EVAS_HINT_FILL);
+   evas_object_size_hint_weight_set(slider, EVAS_HINT_EXPAND, 0.0);
+
+   for (volume = 0, i = 0; i < channels; i++)
+     volume += mixer_context->sink_default->volume.volumes[i];
+
+   if (channels)
+     volume = volume / channels;
+
+   evas_object_show(slider);
+   elm_slider_min_max_set(slider, 0.0, (double) EMIX_VOLUME_MAX);
+   evas_object_smart_callback_add(slider, "changed", _slider_changed_cb,
+                                  NULL);
+
+   elm_slider_value_set(slider, volume);
+   return slider;
+}
+
+static void
+_sink_selected_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
+{
+   Emix_Sink *s = data;
+
+   mixer_context->sink_default = s;
+   _mixer_gadget_update();
+}
+
+static void
+_popup_new(Instance *inst)
+{
+   Evas_Object *button, *list, *icon;
+   Emix_Sink *s;
+   Eina_List *l;
+
+   EINA_SAFETY_ON_NULL_RETURN(mixer_context->sink_default);
+
+   inst->popup = e_gadcon_popup_new(inst->gcc, 0);
+   list = elm_box_add(e_comp->elm);
+
+   inst->list = elm_list_add(e_comp->elm);
+   evas_object_size_hint_align_set(inst->list, EVAS_HINT_FILL, EVAS_HINT_FILL);
+   evas_object_size_hint_weight_set(inst->list, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+   evas_object_show(inst->list);
+
+   EINA_LIST_FOREACH((Eina_List *)emix_sinks_get(), l, s)
+     {
+        Elm_Object_Item *it;
+
+        it = elm_list_item_append(inst->list, s->name, NULL, NULL, _sink_selected_cb, s);
+        if (mixer_context->sink_default == s)
+          elm_list_item_selected_set(it, EINA_TRUE);
+     }
+   elm_box_pack_end(list, inst->list);
+
+   inst->slider = _popup_add_slider();
+   elm_box_pack_end(list, inst->slider);
+   evas_object_show(inst->slider);
+
+   inst->mute = (int) mixer_context->sink_default->mute;
+
+   inst->check = elm_check_add(e_comp->elm);
+   elm_object_text_set(inst->check, _("Mute"));
+   elm_check_state_pointer_set(inst->check, &(inst->mute));
+   evas_object_smart_callback_add(inst->check, "changed", _check_changed_cb,
+                                  NULL);
+   elm_box_pack_end(list, inst->check);
+   evas_object_show(inst->check);
+
+   icon = elm_icon_add(e_comp->elm);
+   elm_icon_standard_set(icon, "preferences-system");
+
+   button = elm_button_add(e_comp->elm);
+   evas_object_size_hint_align_set(button, EVAS_HINT_FILL, EVAS_HINT_FILL);
+   evas_object_size_hint_weight_set(button, EVAS_HINT_EXPAND, 0.0);
+   elm_object_part_content_set(button, "icon", icon);
+   evas_object_smart_callback_add(button, "clicked", _emixer_exec_cb, inst);
+   elm_box_pack_end(list, button);
+   evas_object_show(button);
+
+   evas_object_size_hint_min_set(list, 208, 208);
+
+
+   e_gadcon_popup_content_set(inst->popup, list);
+   e_comp_object_util_autoclose(inst->popup->comp_object,
+     _popup_comp_del_cb, NULL, inst);
+   e_gadcon_popup_show(inst->popup);
+   e_object_data_set(E_OBJECT(inst->popup), inst);
+   E_OBJECT_DEL_SET(inst->popup, _popup_del_cb);
+}
+
+static void
+_menu_cb(void *data, E_Menu *menu EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
+{
+   _emixer_exec_cb(data, NULL, NULL);
+}
+
+static void
+_settings_cb(void *data EINA_UNUSED, E_Menu *menu EINA_UNUSED,
+             E_Menu_Item *mi EINA_UNUSED)
+{
+   emix_config_popup_new(NULL, NULL);
+}
+
+static void
+_menu_new(Instance *inst, Evas_Event_Mouse_Down *ev)
+{
+   E_Zone *zone;
+   E_Menu *m;
+   E_Menu_Item *mi;
+   int x, y;
+
+   zone = e_zone_current_get();
+
+   m = e_menu_new();
+
+   mi = e_menu_item_new(m);
+   e_menu_item_label_set(mi, _("Advanced"));
+   e_util_menu_item_theme_icon_set(mi, "configure");
+   e_menu_item_callback_set(mi, _menu_cb, inst);
+
+   mi = e_menu_item_new(m);
+   e_menu_item_label_set(mi, _("Settings"));
+   e_util_menu_item_theme_icon_set(mi, "configure");
+   e_menu_item_callback_set(mi, _settings_cb, inst);
+
+   m = e_gadcon_client_util_menu_items_append(inst->gcc, m, 0);
+
+   e_gadcon_canvas_zone_geometry_get(inst->gcc->gadcon, &x, &y, NULL, NULL);
+   e_menu_activate_mouse(m, zone, x + ev->output.x, y + ev->output.y,
+                         1, 1, E_MENU_POP_DIRECTION_AUTO, ev->timestamp);
+   evas_event_feed_mouse_up(inst->gcc->gadcon->evas, ev->button,
+                            EVAS_BUTTON_NONE, ev->timestamp, NULL);
+}
+
+static void
+_mouse_down_cb(void *data, Evas *evas EINA_UNUSED,
+               Evas_Object *obj EINA_UNUSED, void *event)
+{
+   Instance *inst = data;
+   Evas_Event_Mouse_Down *ev = event;
+
+   if (ev->button == 1)
+     {
+        if (!inst->popup)
+          _popup_new(inst);
+     }
+   else if (ev->button == 2)
+     {
+        _volume_mute_cb(NULL, NULL);
+     }
+   else if (ev->button == 3)
+     {
+        _menu_new(inst, ev);
+     }
+}
+
+static void
+_mouse_wheel_cb(void *data EINA_UNUSED, Evas *evas EINA_UNUSED,
+                Evas_Object *obj EINA_UNUSED, void *event)
+{
+   Evas_Event_Mouse_Wheel *ev = event;
+
+   if (ev->z > 0)
+     _volume_decrease_cb(NULL, NULL);
+   else if (ev->z < 0)
+     _volume_increase_cb(NULL, NULL);
+}
+
+/*
+ * Gadcon functions
+ */
+static E_Gadcon_Client *
+_gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style)
+{
+   E_Gadcon_Client *gcc;
+   Instance *inst;
+
+   inst = E_NEW(Instance, 1);
+
+   inst->gadget = edje_object_add(gc->evas);
+   inst->evas = gc->evas;
+   e_theme_edje_object_set(inst->gadget,
+                           "base/theme/modules/mixer",
+                           "e/modules/mixer/main");
+
+   gcc = e_gadcon_client_new(gc, name, id, style, inst->gadget);
+   gcc->data = inst;
+   inst->gcc = gcc;
+
+   evas_object_event_callback_add(inst->gadget, EVAS_CALLBACK_MOUSE_DOWN,
+                                  _mouse_down_cb, inst);
+   evas_object_event_callback_add(inst->gadget, EVAS_CALLBACK_MOUSE_WHEEL,
+                                  _mouse_wheel_cb, inst);
+   mixer_context->instances = eina_list_append(mixer_context->instances, inst);
+
+   if (mixer_context->sink_default)
+     _mixer_gadget_update();
+
+   return gcc;
+}
+
+static void
+_gc_shutdown(E_Gadcon_Client *gcc)
+{
+   Instance *inst;
+
+   inst = gcc->data;
+   evas_object_del(inst->gadget);
+   mixer_context->instances = eina_list_remove(mixer_context->instances, inst);
+   free(inst);
+}
+
+static void
+_gc_orient(E_Gadcon_Client *gcc, E_Gadcon_Orient orient EINA_UNUSED)
+{
+   e_gadcon_client_aspect_set(gcc, 16, 16);
+   e_gadcon_client_min_size_set(gcc, 16, 16);
+}
+
+static const char *
+_gc_label(const E_Gadcon_Client_Class *client_class EINA_UNUSED)
+{
+   return "Mixer";
+}
+
+static Evas_Object *
+_gc_icon(const E_Gadcon_Client_Class *client_class EINA_UNUSED, Evas *evas)
+{
+   Evas_Object *o;
+   char buf[4096] = { 0 };
+
+   o = edje_object_add(evas);
+   snprintf(buf, sizeof(buf), "%s/e-module-mixer.edj",
+            e_module_dir_get(mixer_context->module));
+   edje_object_file_set(o, buf, "icon");
+
+   return o;
+}
+
+static const char *
+_gc_id_new(const E_Gadcon_Client_Class *client_class EINA_UNUSED)
+{
+   return _gadcon_class.name;
+}
+
+static void
+_sink_event(int type, void *info)
+{
+   Emix_Sink *sink = info;
+   const Eina_List *l;
+
+   if (type == EMIX_SINK_REMOVED_EVENT)
+     {
+        if (sink == mixer_context->sink_default)
+          {
+             l = emix_sinks_get();
+             mixer_context->sink_default = l->data;
+             _mixer_gadget_update();
+          }
+     }
+   else if (type == EMIX_SINK_CHANGED_EVENT)
+     {
+        if (mixer_context->sink_default == sink)
+          {
+             _mixer_gadget_update();
+             _notify(sink->mute ? 0 : sink->volume.volumes[0]);
+          }
+     }
+   else
+     {
+        DBG("Sink added");
+     }
+}
+
+static void
+_disconnected(void)
+{
+   if (mixer_context) mixer_context->sink_default = NULL;
+   _mixer_gadget_update();
+}
+
+static void
+_ready(void)
+{
+   if (emix_sink_default_support())
+     mixer_context->sink_default = emix_sink_default_get();
+   else
+     mixer_context->sink_default = emix_sinks_get()->data;
+
+   _mixer_gadget_update();
+}
+
+static void
+_events_cb(void *data EINA_UNUSED, enum Emix_Event type, void *event_info)
+{
+   switch (type)
+     {
+      case EMIX_SINK_ADDED_EVENT:
+      case EMIX_SINK_CHANGED_EVENT:
+      case EMIX_SINK_REMOVED_EVENT:
+         _sink_event(type, event_info);
+         break;
+      case EMIX_DISCONNECTED_EVENT:
+         _disconnected();
+         break;
+      case EMIX_READY_EVENT:
+         _ready();
+         break;
+      default:
+         break;
+     }
+}
+
+static Eina_Bool
+_desklock_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *info)
+{
+   E_Event_Desklock *ev = info;
+   static Eina_Bool _was_mute = EINA_FALSE;
+
+   if (emix_config_desklock_mute_get() == EINA_FALSE)
+     return ECORE_CALLBACK_PASS_ON;
+
+   if (ev->on)
+     {
+        _was_mute = mixer_context->sink_default->mute;
+        if (!_was_mute)
+          emix_sink_mute_set((Emix_Sink *)mixer_context->sink_default, EINA_TRUE);
+     }
+   else
+     {
+        if (!_was_mute)
+          emix_sink_mute_set((Emix_Sink *)mixer_context->sink_default, EINA_FALSE);
+     }
+
+   return ECORE_CALLBACK_PASS_ON;
+}
+
+static void
+_backend_changed(const char *backend, void *data EINA_UNUSED)
+{
+   _disconnected();
+
+   if (emix_backend_set(backend) == EINA_FALSE)
+     ERR("Could not load backend: %s", backend);
+}
+
+E_API void *
+e_modapi_init(E_Module *m)
+{
+   Eina_List *l;
+   char buf[4096];
+   const char *backend;
+   Eina_Bool backend_loaded = EINA_FALSE;
+
+   _e_emix_log_domain = eina_log_domain_register("mixer", EINA_COLOR_RED);
+
+   if (!mixer_context)
+     {
+        mixer_context = E_NEW(Context, 1);
+
+        mixer_context->desklock_handler =
+           ecore_event_handler_add(E_EVENT_DESKLOCK, _desklock_cb, NULL);
+        mixer_context->module = m;
+        snprintf(buf, sizeof(buf), "%s/mixer.edj",
+                 e_module_dir_get(mixer_context->module));
+        mixer_context->theme = strdup(buf);
+     }
+
+
+   EINA_SAFETY_ON_FALSE_RETURN_VAL(emix_init(), NULL);
+   emix_config_init(_backend_changed, NULL);
+   emix_event_callback_add(_events_cb, NULL);
+
+   backend = emix_config_backend_get();
+   if (backend && emix_backend_set(backend))
+      backend_loaded = EINA_TRUE;
+   else
+     {
+        if (backend)
+           WRN("Could not load %s, trying another one ...", backend);
+        EINA_LIST_FOREACH((Eina_List *)emix_backends_available(), l,
+                          backend)
+          {
+             if (emix_backend_set(backend) == EINA_TRUE)
+               {
+                  DBG("Loaded backend: %s!", backend);
+                  backend_loaded = EINA_TRUE;
+                  emix_config_backend_set(backend);
+                  break;
+               }
+          }
+     }
+
+   if (!backend_loaded) goto err;
+
+   e_configure_registry_category_add("extensions", 90, _("Extensions"), NULL,
+                                     "preferences-extensions");
+   e_configure_registry_item_add("extensions/emix", 30, _("Mixer"), NULL,
+                                 "preferences-desktop-mixer",
+                                 emix_config_popup_new);
+
+   if (emix_sink_default_support())
+      mixer_context->sink_default = emix_sink_default_get();
+
+   e_gadcon_provider_register(&_gadcon_class);
+   _actions_register();
+
+   return m;
+
+err:
+   emix_config_shutdown();
+   emix_shutdown();
+   return NULL;
+}
+
+E_API int
+e_modapi_shutdown(E_Module *m EINA_UNUSED)
+{
+   _actions_unregister();
+   e_gadcon_provider_unregister((const E_Gadcon_Client_Class *)&_gadcon_class);
+
+   if (mixer_context)
+     {
+        free(mixer_context->theme);
+        E_FREE(mixer_context);
+     }
+
+   emix_event_callback_del(_events_cb);
+   emix_shutdown();
+   emix_config_shutdown();
+   return 1;
+}
+
+E_API int
+e_modapi_save(E_Module *m EINA_UNUSED)
+{
+   return 1;
+}
+
diff --git a/src/modules/mixer/e_mod_main.h b/src/modules/mixer/e_mod_main.h
new file mode 100644 (file)
index 0000000..bb624a1
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef _E_MOD_MAIN_H_
+#define _E_MOD_MAIN_H_
+
+#define CONFIG_VERSION 1
+
+extern int _e_emix_log_domain;
+
+#undef DBG
+#undef INF
+#undef WRN
+#undef ERR
+#undef CRIT
+#define DBG(...) EINA_LOG_DOM_DBG(_e_emix_log_domain, __VA_ARGS__)
+#define INF(...) EINA_LOG_DOM_INF(_e_emix_log_domain, __VA_ARGS__)
+#define WRN(...) EINA_LOG_DOM_WARN(_e_emix_log_domain, __VA_ARGS__)
+#define ERR(...) EINA_LOG_DOM_ERR(_e_emix_log_domain, __VA_ARGS__)
+#define CRIT(...) EINA_LOG_DOM_CRIT(_e_emix_log_domain, __VA_ARGS__)
+
+E_API extern E_Module_Api e_modapi;
+
+E_API void *e_modapi_init(E_Module *m);
+E_API int   e_modapi_shutdown(E_Module *m);
+E_API int   e_modapi_save(E_Module *m);
+
+#endif /* _E_MOD_MAIN_H_ */
diff --git a/src/modules/mixer/emixer.c b/src/modules/mixer/emixer.c
new file mode 100644 (file)
index 0000000..61cceaa
--- /dev/null
@@ -0,0 +1,727 @@
+#include <Elementary.h>
+#include "emix.h"
+
+Evas_Object *win;
+Evas_Object *source_scroller, *sink_input_scroller, *sink_scroller;
+Evas_Object *source_box, *sink_input_box, *sink_box;
+
+Eina_List *source_list = NULL, *sink_input_list = NULL, *sink_list = NULL;
+
+//////////////////////////////////////////////////////////////////////////////
+
+static Eina_Bool
+_backend_init(const char *back)
+{
+   const Eina_List *l;
+   const char *name;
+
+   if (!back) back = "PULSEAUDIO";
+   if (emix_backend_set(back)) return EINA_TRUE;
+   EINA_LIST_FOREACH(emix_backends_available(), l, name)
+     {
+        if (emix_backend_set(name)) return EINA_TRUE;
+     }
+   return EINA_FALSE;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+#define VOLSET(vol, srcvol, target, func) \
+   do { \
+      Emix_Volume _v; \
+      _v.channel_count = srcvol.channel_count; \
+      _v.volumes = calloc(srcvol.channel_count, sizeof(int)); \
+      if (_v.volumes) { \
+         unsigned int _i; \
+         for (_i = 0; _i < _v.channel_count; _i++) _v.volumes[_i] = vol; \
+         func(target, _v); \
+         free(_v.volumes); \
+      } \
+   } while (0)
+
+
+//////////////////////////////////////////////////////////////////////////////
+static void
+_cb_sink_port_change(void *data,
+                     Evas_Object *obj,
+                     void *event_info EINA_UNUSED)
+{
+   Emix_Port *port = data;
+   Evas_Object *bxv = evas_object_data_get(obj, "parent");
+   Emix_Sink *sink = evas_object_data_get(bxv, "sink");
+   elm_object_text_set(obj, port->description);
+   emix_sink_port_set(sink, port);
+}
+
+static void
+_cb_sink_volume_change(void *data,
+                       Evas_Object *obj,
+                       void *event_info EINA_UNUSED)
+{
+   Evas_Object *bxv = data;
+   Emix_Sink *sink = evas_object_data_get(bxv, "sink");
+   double vol = elm_slider_value_get(obj);
+   VOLSET(vol, sink->volume, sink, emix_sink_volume_set);
+}
+
+static void
+_cb_sink_mute_change(void *data,
+                     Evas_Object *obj,
+                     void *event_info EINA_UNUSED)
+{
+   Evas_Object *bxv = data;
+   Emix_Sink *sink = evas_object_data_get(bxv, "sink");
+   Evas_Object *sl = evas_object_data_get(bxv, "volume");
+   Eina_Bool mute = elm_check_state_get(obj);
+   elm_object_disabled_set(sl, mute);
+   emix_sink_mute_set(sink, mute);
+}
+
+static void
+_emix_sink_add(Emix_Sink *sink)
+{
+   Evas_Object *bxv, *bx, *lb, *ck, *sl, *hv, *sep;
+   const Eina_List *l;
+   Emix_Port *port;
+
+   bxv = elm_box_add(win);
+   sink_list = eina_list_append(sink_list, bxv);
+   evas_object_data_set(bxv, "sink", sink);
+   evas_object_size_hint_weight_set(bxv, EVAS_HINT_EXPAND, 0.0);
+   evas_object_size_hint_align_set(bxv, EVAS_HINT_FILL, 0.0);
+
+   bx = elm_box_add(win);
+   elm_box_horizontal_set(bx, EINA_TRUE);
+   evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
+   evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.0);
+   elm_box_pack_end(bxv, bx);
+   evas_object_show(bx);
+
+   lb = elm_label_add(win);
+   elm_object_text_set(lb, sink->name);
+   evas_object_size_hint_weight_set(lb, EVAS_HINT_EXPAND, 0.5);
+   evas_object_size_hint_align_set(lb, 0.0, 0.5);
+   elm_box_pack_end(bx, lb);
+   evas_object_show(lb);
+
+   hv = elm_hoversel_add(win);
+   evas_object_data_set(hv, "parent", bxv);
+   evas_object_data_set(bxv, "port", hv);
+   elm_hoversel_hover_parent_set(hv, win);
+   EINA_LIST_FOREACH(sink->ports, l, port)
+     {
+        elm_hoversel_item_add(hv, port->description,
+                              NULL, ELM_ICON_NONE,
+                              _cb_sink_port_change, port);
+        if (port->active) elm_object_text_set(hv, port->description);
+     }
+   evas_object_size_hint_weight_set(hv, 0.0, 0.5);
+   evas_object_size_hint_align_set(hv, EVAS_HINT_FILL, 0.5);
+   elm_box_pack_end(bx, hv);
+   evas_object_show(hv);
+
+   bx = elm_box_add(win);
+   elm_box_horizontal_set(bx, EINA_TRUE);
+   evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
+   evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.0);
+   elm_box_pack_end(bxv, bx);
+   evas_object_show(bx);
+
+   sl = elm_slider_add(win);
+   evas_object_data_set(bxv, "volume", sl);
+   elm_slider_min_max_set(sl, 0.0, 100.0);
+   elm_slider_span_size_set(sl, 100 * elm_config_scale_get());
+   elm_slider_unit_format_set(sl, "%1.0f");
+   elm_slider_indicator_format_set(sl, "%1.0f");
+   evas_object_size_hint_weight_set(sl, EVAS_HINT_EXPAND, 0.5);
+   evas_object_size_hint_align_set(sl, EVAS_HINT_FILL, 0.5);
+   elm_slider_value_set(sl, sink->volume.volumes[0]);
+   elm_box_pack_end(bx, sl);
+   evas_object_show(sl);
+   evas_object_smart_callback_add(sl, "changed", _cb_sink_volume_change, bxv);
+
+   ck = elm_check_add(win);
+   evas_object_data_set(bxv, "mute", ck);
+   elm_object_text_set(ck, "Mute");
+   elm_check_state_set(ck, sink->mute);
+   elm_object_disabled_set(sl, sink->mute);
+   elm_box_pack_end(bx, ck);
+   evas_object_show(ck);
+   evas_object_smart_callback_add(ck, "changed", _cb_sink_mute_change, bxv);
+
+   sep = elm_separator_add(win);
+   elm_separator_horizontal_set(sep, EINA_TRUE);
+   evas_object_size_hint_weight_set(sep, EVAS_HINT_EXPAND, 0.0);
+   evas_object_size_hint_align_set(sep, EVAS_HINT_FILL, 0.0);
+   elm_box_pack_end(bxv, sep);
+   evas_object_show(sep);
+
+   elm_box_pack_end(sink_box, bxv);
+   evas_object_show(bxv);
+}
+
+static void
+_emix_sink_del(Emix_Sink *sink)
+{
+   Eina_List *l;
+   Evas_Object *bxv;
+   EINA_LIST_FOREACH(sink_list, l, bxv)
+     {
+        if (evas_object_data_get(bxv, "sink") == sink)
+          {
+             sink_list = eina_list_remove_list(sink_list, l);
+             evas_object_del(bxv);
+             return;
+          }
+     }
+}
+
+static void
+_emix_sink_change(Emix_Sink *sink)
+{
+   const Eina_List *l;
+   Evas_Object *bxv, *hv, *ck, *sl;
+   Emix_Port *port;
+
+   EINA_LIST_FOREACH(sink_list, l, bxv)
+     {
+        if (evas_object_data_get(bxv, "sink") == sink) break;
+     }
+   if (!l) return;
+   hv = evas_object_data_get(bxv, "port");
+   elm_hoversel_clear(hv);
+   EINA_LIST_FOREACH(sink->ports, l, port)
+     {
+        elm_hoversel_item_add(hv, port->description,
+                              NULL, ELM_ICON_NONE,
+                              _cb_sink_port_change, port);
+        if (port->active) elm_object_text_set(hv, port->description);
+     }
+   sl = evas_object_data_get(bxv, "volume");
+   elm_slider_value_set(sl, sink->volume.volumes[0]);
+
+   ck = evas_object_data_get(bxv, "mute");
+   elm_check_state_set(ck, sink->mute);
+   elm_object_disabled_set(sl, sink->mute);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+static void
+_cb_sink_input_port_change(void *data,
+                           Evas_Object *obj,
+                           void *event_info EINA_UNUSED)
+{
+   Emix_Sink *sink = data;
+   Evas_Object *bxv = evas_object_data_get(obj, "parent");
+   Emix_Sink_Input *input = evas_object_data_get(bxv, "input");
+   elm_object_text_set(obj, sink->name);
+   emix_sink_input_sink_change(input, sink);
+}
+
+static void
+_cb_sink_input_volume_change(void *data,
+                             Evas_Object *obj,
+                             void *event_info EINA_UNUSED)
+{
+   Evas_Object *bxv = data;
+   Emix_Sink_Input *input = evas_object_data_get(bxv, "input");
+   double vol = elm_slider_value_get(obj);
+   VOLSET(vol, input->volume, input, emix_sink_input_volume_set);
+}
+
+static void
+_cb_sink_input_mute_change(void *data,
+                           Evas_Object *obj,
+                           void *event_info EINA_UNUSED)
+{
+   Evas_Object *bxv = data;
+   Emix_Sink_Input *input = evas_object_data_get(bxv, "input");
+   Evas_Object *sl = evas_object_data_get(bxv, "volume");
+   Eina_Bool mute = elm_check_state_get(obj);
+   elm_object_disabled_set(sl, mute);
+   emix_sink_input_mute_set(input, mute);
+}
+
+static void
+_emix_sink_input_add(Emix_Sink_Input *input)
+{
+   Evas_Object *bxv, *bx, *lb, *ck, *sl, *hv, *sep;
+   const Eina_List *l;
+   Emix_Sink *sink;
+
+   bxv = elm_box_add(win);
+   sink_input_list = eina_list_append(sink_input_list, bxv);
+   evas_object_data_set(bxv, "input", input);
+   evas_object_size_hint_weight_set(bxv, EVAS_HINT_EXPAND, 0.0);
+   evas_object_size_hint_align_set(bxv, EVAS_HINT_FILL, 0.0);
+
+   bx = elm_box_add(win);
+   elm_box_horizontal_set(bx, EINA_TRUE);
+   evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
+   evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.0);
+   elm_box_pack_end(bxv, bx);
+   evas_object_show(bx);
+
+   lb = elm_label_add(win);
+   elm_object_text_set(lb, input->name);
+   evas_object_size_hint_weight_set(lb, EVAS_HINT_EXPAND, 0.5);
+   evas_object_size_hint_align_set(lb, 0.0, 0.5);
+   elm_box_pack_end(bx, lb);
+   evas_object_show(lb);
+
+   hv = elm_hoversel_add(win);
+   evas_object_data_set(hv, "parent", bxv);
+   evas_object_data_set(bxv, "port", hv);
+   elm_hoversel_hover_parent_set(hv, win);
+   EINA_LIST_FOREACH(emix_sinks_get(), l, sink)
+     {
+        elm_hoversel_item_add(hv, sink->name,
+                              NULL, ELM_ICON_NONE,
+                              _cb_sink_input_port_change, sink);
+        if (input->sink == sink) elm_object_text_set(hv, sink->name);
+     }
+   evas_object_size_hint_weight_set(hv, 0.0, 0.5);
+   evas_object_size_hint_align_set(hv, EVAS_HINT_FILL, 0.5);
+   elm_box_pack_end(bx, hv);
+   evas_object_show(hv);
+
+   bx = elm_box_add(win);
+   elm_box_horizontal_set(bx, EINA_TRUE);
+   evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
+   evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.0);
+   elm_box_pack_end(bxv, bx);
+   evas_object_show(bx);
+
+   sl = elm_slider_add(win);
+   evas_object_data_set(bxv, "volume", sl);
+   elm_slider_min_max_set(sl, 0.0, 100.0);
+   elm_slider_span_size_set(sl, 100 * elm_config_scale_get());
+   elm_slider_unit_format_set(sl, "%1.0f");
+   elm_slider_indicator_format_set(sl, "%1.0f");
+   evas_object_size_hint_weight_set(sl, EVAS_HINT_EXPAND, 0.5);
+   evas_object_size_hint_align_set(sl, EVAS_HINT_FILL, 0.5);
+   elm_slider_value_set(sl, input->volume.volumes[0]);
+   elm_box_pack_end(bx, sl);
+   evas_object_show(sl);
+   evas_object_smart_callback_add(sl, "changed",
+                                  _cb_sink_input_volume_change, bxv);
+
+   ck = elm_check_add(win);
+   evas_object_data_set(bxv, "mute", ck);
+   elm_object_text_set(ck, "Mute");
+   elm_check_state_set(ck, input->mute);
+   elm_object_disabled_set(sl, input->mute);
+   elm_box_pack_end(bx, ck);
+   evas_object_show(ck);
+   evas_object_smart_callback_add(ck, "changed",
+                                  _cb_sink_input_mute_change, bxv);
+
+   sep = elm_separator_add(win);
+   elm_separator_horizontal_set(sep, EINA_TRUE);
+   evas_object_size_hint_weight_set(sep, EVAS_HINT_EXPAND, 0.0);
+   evas_object_size_hint_align_set(sep, EVAS_HINT_FILL, 0.0);
+   elm_box_pack_end(bxv, sep);
+   evas_object_show(sep);
+
+   elm_box_pack_end(sink_input_box, bxv);
+   evas_object_show(bxv);
+}
+
+static void
+_emix_sink_input_del(Emix_Sink_Input *input)
+{
+   Eina_List *l;
+   Evas_Object *bxv;
+   EINA_LIST_FOREACH(sink_input_list, l, bxv)
+     {
+        if (evas_object_data_get(bxv, "input") == input)
+          {
+             sink_input_list = eina_list_remove_list(sink_input_list, l);
+             evas_object_del(bxv);
+             return;
+          }
+     }
+}
+
+static void
+_emix_sink_input_change(Emix_Sink_Input *input)
+{
+   const Eina_List *l;
+   Evas_Object *bxv, *hv, *ck, *sl;
+   Emix_Sink *sink;
+
+   EINA_LIST_FOREACH(sink_input_list, l, bxv)
+     {
+        if (evas_object_data_get(bxv, "input") == input) break;
+     }
+   if (!l) return;
+   hv = evas_object_data_get(bxv, "port");
+   elm_hoversel_clear(hv);
+   EINA_LIST_FOREACH(emix_sinks_get(), l, sink)
+     {
+        elm_hoversel_item_add(hv, sink->name,
+                              NULL, ELM_ICON_NONE,
+                              _cb_sink_input_port_change, sink);
+        if (input->sink == sink) elm_object_text_set(hv, sink->name);
+     }
+   sl = evas_object_data_get(bxv, "volume");
+   elm_slider_value_set(sl, input->volume.volumes[0]);
+
+   ck = evas_object_data_get(bxv, "mute");
+   elm_check_state_set(ck, input->mute);
+   elm_object_disabled_set(sl, input->mute);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+static void
+_cb_source_volume_change(void *data,
+                         Evas_Object *obj,
+                         void *event_info EINA_UNUSED)
+{
+   Evas_Object *bxv = data;
+   Emix_Source *source = evas_object_data_get(bxv, "source");
+   double vol = elm_slider_value_get(obj);
+   VOLSET(vol, source->volume, source, emix_source_volume_set);
+}
+
+static void
+_cb_source_mute_change(void *data,
+                       Evas_Object *obj,
+                       void *event_info EINA_UNUSED)
+{
+   Evas_Object *bxv = data;
+   Emix_Source *source = evas_object_data_get(bxv, "source");
+   Evas_Object *sl = evas_object_data_get(bxv, "volume");
+   Eina_Bool mute = elm_check_state_get(obj);
+   elm_object_disabled_set(sl, mute);
+   emix_source_mute_set(source, mute);
+}
+
+static void
+_emix_source_add(Emix_Source *source)
+{
+   Evas_Object *bxv, *bx, *lb, *ck, *sl, *sep;
+
+   bxv = elm_box_add(win);
+   source_list = eina_list_append(source_list, bxv);
+   evas_object_data_set(bxv, "source", source);
+   evas_object_size_hint_weight_set(bxv, EVAS_HINT_EXPAND, 0.0);
+   evas_object_size_hint_align_set(bxv, EVAS_HINT_FILL, 0.0);
+
+   bx = elm_box_add(win);
+   elm_box_horizontal_set(bx, EINA_TRUE);
+   evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
+   evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.0);
+   elm_box_pack_end(bxv, bx);
+   evas_object_show(bx);
+
+   lb = elm_label_add(win);
+   elm_object_text_set(lb, source->name);
+   evas_object_size_hint_weight_set(lb, EVAS_HINT_EXPAND, 0.5);
+   evas_object_size_hint_align_set(lb, 0.0, 0.5);
+   elm_box_pack_end(bx, lb);
+   evas_object_show(lb);
+
+   bx = elm_box_add(win);
+   elm_box_horizontal_set(bx, EINA_TRUE);
+   evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
+   evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.0);
+   elm_box_pack_end(bxv, bx);
+   evas_object_show(bx);
+
+   sl = elm_slider_add(win);
+   evas_object_data_set(bxv, "volume", sl);
+   elm_slider_min_max_set(sl, 0.0, 100.0);
+   elm_slider_span_size_set(sl, 100 * elm_config_scale_get());
+   elm_slider_unit_format_set(sl, "%1.0f");
+   elm_slider_indicator_format_set(sl, "%1.0f");
+   evas_object_size_hint_weight_set(sl, EVAS_HINT_EXPAND, 0.5);
+   evas_object_size_hint_align_set(sl, EVAS_HINT_FILL, 0.5);
+   elm_slider_value_set(sl, source->volume.volumes[0]);
+   elm_box_pack_end(bx, sl);
+   evas_object_show(sl);
+   evas_object_smart_callback_add(sl, "changed",
+                                  _cb_source_volume_change, bxv);
+
+   ck = elm_check_add(win);
+   evas_object_data_set(bxv, "mute", ck);
+   elm_object_text_set(ck, "Mute");
+   elm_check_state_set(ck, source->mute);
+   elm_object_disabled_set(sl, source->mute);
+   elm_box_pack_end(bx, ck);
+   evas_object_show(ck);
+   evas_object_smart_callback_add(ck, "changed",
+                                  _cb_source_mute_change, bxv);
+
+   sep = elm_separator_add(win);
+   elm_separator_horizontal_set(sep, EINA_TRUE);
+   evas_object_size_hint_weight_set(sep, EVAS_HINT_EXPAND, 0.0);
+   evas_object_size_hint_align_set(sep, EVAS_HINT_FILL, 0.0);
+   elm_box_pack_end(bxv, sep);
+   evas_object_show(sep);
+
+   elm_box_pack_end(source_box, bxv);
+   evas_object_show(bxv);
+}
+
+static void
+_emix_source_del(Emix_Source *source)
+{
+   Eina_List *l;
+   Evas_Object *bxv;
+   EINA_LIST_FOREACH(source_list, l, bxv)
+     {
+        if (evas_object_data_get(bxv, "source") == source)
+          {
+             source_list = eina_list_remove_list(source_list, l);
+             evas_object_del(bxv);
+             return;
+          }
+     }
+}
+
+static void
+_emix_source_change(Emix_Source *source)
+{
+   const Eina_List *l;
+   Evas_Object *bxv, *ck, *sl;
+
+   EINA_LIST_FOREACH(source_list, l, bxv)
+     {
+        if (evas_object_data_get(bxv, "source") == source) break;
+     }
+   if (!l) return;
+   sl = evas_object_data_get(bxv, "volume");
+   elm_slider_value_set(sl, source->volume.volumes[0]);
+
+   ck = evas_object_data_get(bxv, "mute");
+   elm_check_state_set(ck, source->mute);
+   elm_object_disabled_set(sl, source->mute);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+static void
+_cb_emix_event(void *data EINA_UNUSED, enum Emix_Event event, void *event_info)
+{
+   switch (event)
+     {
+      case EMIX_READY_EVENT:
+        break;
+      case EMIX_DISCONNECTED_EVENT:
+        elm_exit();
+        break;
+      case EMIX_SINK_ADDED_EVENT:
+        _emix_sink_add(event_info);
+        break;
+      case EMIX_SINK_REMOVED_EVENT:
+        _emix_sink_del(event_info);
+        break;
+      case EMIX_SINK_CHANGED_EVENT:
+        _emix_sink_change(event_info);
+        break;
+      case EMIX_SINK_INPUT_ADDED_EVENT:
+        _emix_sink_input_add(event_info);
+        break;
+      case EMIX_SINK_INPUT_REMOVED_EVENT:
+        _emix_sink_input_del(event_info);
+        break;
+      case EMIX_SINK_INPUT_CHANGED_EVENT:
+        _emix_sink_input_change(event_info);
+        break;
+      case EMIX_SOURCE_ADDED_EVENT:
+        _emix_source_add(event_info);
+        break;
+      case EMIX_SOURCE_REMOVED_EVENT:
+        _emix_source_del(event_info);
+        break;
+      case EMIX_SOURCE_CHANGED_EVENT:
+        _emix_source_change(event_info);
+        break;
+      default:
+        break;
+     }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+static void
+_cb_playback(void *data EINA_UNUSED,
+             Evas_Object *obj EINA_UNUSED,
+             void *event_info EINA_UNUSED)
+{
+   evas_object_hide(source_scroller);
+   evas_object_show(sink_input_scroller);
+   evas_object_hide(sink_scroller);
+}
+
+static void
+_cb_outputs(void *data EINA_UNUSED,
+            Evas_Object *obj EINA_UNUSED,
+            void *event_info EINA_UNUSED)
+{
+   evas_object_hide(source_scroller);
+   evas_object_hide(sink_input_scroller);
+   evas_object_show(sink_scroller);
+}
+
+static void
+_cb_inputs(void *data EINA_UNUSED,
+           Evas_Object *obj EINA_UNUSED,
+           void *event_info EINA_UNUSED)
+{
+   evas_object_show(source_scroller);
+   evas_object_hide(sink_input_scroller);
+   evas_object_hide(sink_scroller);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+static void
+_event_init(void)
+{
+   emix_event_callback_add(_cb_emix_event, NULL);
+}
+
+static void
+_fill_source(void)
+{
+   const Eina_List *l;
+   Emix_Source *source;
+
+   EINA_LIST_FOREACH(emix_sources_get(), l, source)
+     {
+        _emix_source_add(source);
+     }
+}
+
+static void
+_fill_sink_input(void)
+{
+   const Eina_List *l;
+   Emix_Sink_Input *input;
+
+   EINA_LIST_FOREACH(emix_sink_inputs_get(), l, input)
+     {
+        _emix_sink_input_add(input);
+     }
+}
+
+static void
+_fill_sink(void)
+{
+   const Eina_List *l;
+   Emix_Sink *sink;
+
+   EINA_LIST_FOREACH(emix_sinks_get(), l, sink)
+     {
+        _emix_sink_add(sink);
+     }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+EAPI_MAIN int
+elm_main(int argc, char **argv)
+{
+   Evas_Object *tb, *tbar, *sc, *rect, *bx;
+   const char *back = NULL;
+
+   emix_init();
+   if (argc > 1) back = argv[1];
+   if (!_backend_init(back)) goto done;
+   _event_init();
+
+   elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED);
+
+   win = elm_win_util_standard_add("emix", "Mixer");
+   elm_win_autodel_set(win, EINA_TRUE);
+
+/*
+   icon = evas_object_image_add(evas_object_evas_get(mw->win));
+   snprintf(buf, sizeof(buf), "%s/icons/emixer.png",
+            elm_app_data_dir_get());
+   evas_object_image_file_set(icon, buf, NULL);
+   elm_win_icon_object_set(mw->win, icon);
+   elm_win_icon_name_set(mw->win, "emixer");
+ */
+
+   tb = elm_table_add(win);
+   evas_object_size_hint_weight_set(tb, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+   elm_win_resize_object_add(win, tb);
+   evas_object_show(tb);
+
+   tbar = elm_toolbar_add(win);
+   elm_toolbar_select_mode_set(tbar, ELM_OBJECT_SELECT_MODE_ALWAYS);
+   elm_toolbar_homogeneous_set(tbar, EINA_TRUE);
+   evas_object_size_hint_weight_set(tbar, EVAS_HINT_EXPAND, 0.0);
+   evas_object_size_hint_align_set(tbar, EVAS_HINT_FILL, EVAS_HINT_FILL);
+
+   elm_toolbar_item_append(tbar, NULL, "Playback", _cb_playback, NULL);
+   elm_toolbar_item_append(tbar, NULL, "Outputs", _cb_outputs, NULL);
+   elm_toolbar_item_append(tbar, NULL, "Inputs", _cb_inputs, NULL);
+
+   elm_table_pack(tb, tbar, 0, 0, 1, 1);
+   evas_object_show(tbar);
+
+   sc = elm_scroller_add(win);
+   source_scroller = sc;
+   evas_object_size_hint_weight_set(sc, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+   evas_object_size_hint_align_set(sc, EVAS_HINT_FILL, EVAS_HINT_FILL);
+   elm_table_pack(tb, sc, 0, 1, 1, 1);
+
+   sc = elm_scroller_add(win);
+   sink_input_scroller = sc;
+   evas_object_size_hint_weight_set(sc, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+   evas_object_size_hint_align_set(sc, EVAS_HINT_FILL, EVAS_HINT_FILL);
+   elm_table_pack(tb, sc, 0, 1, 1, 1);
+   evas_object_show(sc);
+
+   sc = elm_scroller_add(win);
+   sink_scroller = sc;
+   evas_object_size_hint_weight_set(sc, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+   evas_object_size_hint_align_set(sc, EVAS_HINT_FILL, EVAS_HINT_FILL);
+   elm_table_pack(tb, sc, 0, 1, 1, 1);
+
+   bx = elm_box_add(win);
+   source_box = bx;
+   evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
+   evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.0);
+   elm_object_content_set(source_scroller, bx);
+   evas_object_show(bx);
+
+   bx = elm_box_add(win);
+   sink_input_box = bx;
+   evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
+   evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.0);
+   elm_object_content_set(sink_input_scroller, bx);
+   evas_object_show(bx);
+
+   bx = elm_box_add(win);
+   sink_box = bx;
+   evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
+   evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.0);
+   elm_object_content_set(sink_scroller, bx);
+   evas_object_show(bx);
+
+   rect = evas_object_rectangle_add(evas_object_evas_get(win));
+   evas_object_size_hint_weight_set(rect, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+   evas_object_size_hint_align_set(rect, EVAS_HINT_FILL, EVAS_HINT_FILL);
+   evas_object_size_hint_min_set(rect,
+                                 440 * elm_config_scale_get(),
+                                 220 * elm_config_scale_get());
+   elm_table_pack(tb, rect, 0, 1, 1, 1);
+
+   _fill_source();
+   _fill_sink_input();
+   _fill_sink();
+   evas_object_show(win);
+
+   elm_run();
+done:
+   emix_shutdown();
+   return 0;
+}
+ELM_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 (file)
index 0000000..156522c
--- /dev/null
@@ -0,0 +1,523 @@
+#include "emix.h"
+#include <alsa/asoundlib.h>
+
+#define ERR(...)      EINA_LOG_ERR(__VA_ARGS__)
+#define DBG(...)      EINA_LOG_DBG(__VA_ARGS__)
+#define WRN(...)      EINA_LOG_WARN(__VA_ARGS__)
+
+typedef struct _Context
+{
+   Emix_Event_Cb cb;
+   const void *userdata;
+   Eina_List *sinks;
+   Eina_List *sources;
+   Eina_List *cards;
+} Context;
+
+static Context *ctx = NULL;
+
+typedef struct _Alsa_Emix_Sink
+{
+  Emix_Sink sink;
+  const char *hw_name;
+  Eina_List *channels;
+} Alsa_Emix_Sink;
+
+typedef struct _Alsa_Emix_Source
+{
+  Emix_Source source;
+  const char *hw_name;
+  Eina_List *channels;
+} Alsa_Emix_Source;
+/*
+ * TODO problems:
+ *
+ * - mono stereo problem...
+ */
+
+/*
+ * util functions
+ */
+
+static int
+_alsa_mixer_sink_changed_cb(snd_mixer_t *ctl, unsigned int mask EINA_UNUSED,
+                            snd_mixer_elem_t *elem EINA_UNUSED)
+{
+   Alsa_Emix_Sink *sink = snd_mixer_get_callback_private(ctl);
+
+   if (ctx->cb)
+     ctx->cb((void *)ctx->userdata, EMIX_SINK_CHANGED_EVENT,
+             (Emix_Sink *)sink);
+   return 0;
+}
+
+static int
+_alsa_mixer_source_changed_cb(snd_mixer_t *ctl, unsigned int mask EINA_UNUSED,
+                              snd_mixer_elem_t *elem EINA_UNUSED)
+{
+   Alsa_Emix_Source *source = snd_mixer_get_callback_private(ctl);
+
+   if (ctx->cb)
+     ctx->cb((void *)ctx->userdata, EMIX_SOURCE_CHANGED_EVENT,
+             (Emix_Source *)source);
+   return 0;
+}
+
+static void
+_alsa_channel_volume_get(snd_mixer_elem_t *channel, int *v, Eina_Bool capture)
+{
+   long int min, max, vol;
+   int range, divide;
+
+   if (capture)
+     snd_mixer_selem_get_capture_volume_range(channel, &min, &max);
+   else
+     snd_mixer_selem_get_playback_volume_range(channel, &min, &max);
+
+   divide = 100 + min;
+   if (divide == 0)
+     {
+        divide = 1;
+        min++;
+     }
+
+   range = max - min;
+   if (range < 1)
+     return;
+
+   if (capture)
+     snd_mixer_selem_get_capture_volume(channel, 0, &vol);
+   else
+     snd_mixer_selem_get_playback_volume(channel, 0, &vol);
+
+   *v = (((vol + min) * divide) - ((double) range / 2)) / range + 0.5;
+}
+
+static void
+_alsa_channel_volume_set(snd_mixer_elem_t *channel, int v, Eina_Bool capture)
+{
+   long int vol, min, max, divide, range;
+   snd_mixer_selem_get_playback_volume_range(channel, &min, &max);
+
+   divide = 100 + min;
+   range = max - min;
+   if (range < 1)
+     return;
+
+   vol = (((v * range) + (range / 2)) / divide) - min;
+   if (!capture)
+     snd_mixer_selem_set_playback_volume_all(channel, vol);
+   else
+     snd_mixer_selem_set_capture_volume_all(channel, vol);
+}
+
+/*
+ * This will append a new device to the cards and call the ecore event for
+ * a new device!
+ */
+static snd_mixer_t *
+_alsa_card_create(char *addr)
+{
+   snd_mixer_t *control;
+
+   if (snd_mixer_open(&control, 0) < 0)
+     goto error_open;
+   if (snd_mixer_attach(control, addr) < 0)
+     goto error_load;
+   if (snd_mixer_selem_register(control, NULL, NULL) < 0)
+     goto error_load;
+   if (snd_mixer_load(control))
+     goto error_load;
+
+   return control;
+
+error_load:
+   snd_mixer_close(control);
+error_open:
+   return NULL;
+}
+
+static void
+_alsa_volume_create(Emix_Volume *volume, Eina_List *channels)
+{
+   unsigned int i = 0, count = eina_list_count(channels);
+   Eina_List *l;
+   snd_mixer_elem_t *elem;
+
+   volume->channel_count = count;
+   volume->volumes = calloc(count, sizeof(int));
+
+   EINA_LIST_FOREACH(channels, l, elem)
+     {
+        _alsa_channel_volume_get(elem, &(volume->volumes[i]), EINA_FALSE);
+        i++;
+     }
+}
+
+static void
+_alsa_sink_mute_get(Alsa_Emix_Sink *as)
+{
+   int i = 0;
+   snd_mixer_elem_t *elem;
+
+   elem = eina_list_data_get(as->channels);
+   snd_mixer_selem_get_playback_switch(elem, 0, &i);
+   as->sink.mute = !i;
+}
+
+static void
+_alsa_sources_mute_get(Alsa_Emix_Source *as)
+{
+   int i = 0;
+   snd_mixer_elem_t *elem;
+
+   elem = eina_list_data_get(as->channels);
+   snd_mixer_selem_get_capture_switch(elem, 0, &i);
+   as->source.mute = !i;
+}
+
+static Alsa_Emix_Sink*
+_alsa_device_sink_create(const char *name, const char* hw_name,
+                         Eina_List *channels)
+{
+   Alsa_Emix_Sink *sink;
+
+   if (!(sink = calloc(1, sizeof(Alsa_Emix_Sink))))
+     {
+        ERR("Allocation Failed");
+        return NULL;
+     }
+   sink->sink.name = eina_stringshare_add(name);
+   _alsa_volume_create(&sink->sink.volume, channels);
+   sink->hw_name = eina_stringshare_add(hw_name);
+   sink->channels = channels;
+   _alsa_sink_mute_get(sink);
+   if (ctx->cb)
+     {
+        ctx->cb((void *)ctx->userdata, EMIX_SINK_ADDED_EVENT,
+                (Emix_Sink *)sink);
+
+     }
+   ctx->sinks = eina_list_append(ctx->sinks, sink);
+   return sink;
+}
+
+static Alsa_Emix_Source*
+_alsa_device_source_create(const char *name, const char* hw_name,
+                           Eina_List *channels)
+{
+   Alsa_Emix_Source *source;
+
+   if (!(source = calloc(1, sizeof(Alsa_Emix_Source))))
+     {
+        ERR("Allocation Failed");
+        return NULL;
+     }
+   source->source.name = eina_stringshare_add(name);
+   _alsa_volume_create(&source->source.volume, channels);
+   source->hw_name = eina_stringshare_add(hw_name);
+   source->channels = channels;
+   _alsa_sources_mute_get(source);
+   if (ctx->cb)
+     ctx->cb((void *)ctx->userdata, EMIX_SOURCE_ADDED_EVENT,
+                  (Emix_Sink *)source);
+   ctx->sources = eina_list_append(ctx->sources, source);
+   return source;
+}
+
+static void
+_alsa_device_sink_free(Alsa_Emix_Sink *sink)
+{
+   eina_stringshare_del(sink->hw_name);
+   eina_stringshare_del(sink->sink.name);
+   free(sink->sink.volume.volumes);
+   free(sink);
+}
+
+static void
+_alsa_device_source_free(Alsa_Emix_Source *source)
+{
+   eina_stringshare_del(source->hw_name);
+   eina_stringshare_del(source->source.name);
+   free(source->source.volume.volumes);
+   free(source);
+}
+
+static char*
+_alsa_cards_name_get(char *name)
+{
+   snd_ctl_t *control;
+   snd_ctl_card_info_t *hw_info;
+   char *result = NULL;
+
+   snd_ctl_card_info_alloca(&hw_info);
+
+   if (snd_ctl_open(&control, name, 0) < 0)
+     {
+        ERR("Failed to open device");
+        goto err;
+     }
+
+   if (snd_ctl_card_info(control, hw_info) < 0)
+     {
+        ERR("Failed to get card information");
+        goto err_open;
+     }
+
+   result = strdup(snd_ctl_card_info_get_name(hw_info));
+
+err_open:
+   snd_ctl_close(control);
+err:
+   return result;
+}
+
+static void
+_alsa_cards_refresh(void)
+{
+   int err, card_num = -1;
+   Eina_List *tmp_source = NULL, *tmp_sink = NULL;
+
+   while (((err = snd_card_next(&card_num)) == 0) && (card_num >= 0))
+     {
+        char buf[PATH_MAX];
+        char *device_name;
+        snd_mixer_t *mixer;
+        snd_mixer_elem_t *elem;
+        Alsa_Emix_Source *source;
+        Alsa_Emix_Sink *sink;
+
+        source = NULL;
+        sink = NULL;
+        tmp_source = NULL;
+        tmp_sink = NULL;
+
+        //generate card addr
+        snprintf(buf, sizeof(buf), "hw:%d", card_num);
+        //save the addr to see if there are missing devices in the cache list
+
+        mixer = _alsa_card_create(buf);
+        ctx->cards = eina_list_append(ctx->cards, mixer);
+        //get elements of the device
+        elem = snd_mixer_first_elem(mixer);
+        for (; elem; elem = snd_mixer_elem_next(elem))
+          {
+             //check if its a source or a sink
+             if (snd_mixer_selem_has_capture_volume(elem))
+               tmp_source = eina_list_append(tmp_source, elem);
+             else
+               tmp_sink = eina_list_append(tmp_sink, elem);
+          }
+
+        device_name = _alsa_cards_name_get(buf);
+        //create the sinks / sources
+        if (tmp_sink)
+          {
+             sink = _alsa_device_sink_create(device_name,
+                                             buf,
+                                             tmp_sink);
+             snd_mixer_set_callback(mixer, _alsa_mixer_sink_changed_cb);
+             snd_mixer_set_callback_private(mixer, sink);
+          }
+        if (tmp_source)
+          {
+             source = _alsa_device_source_create(device_name,
+                                                 buf,
+                                                 tmp_source);
+             snd_mixer_set_callback(mixer, _alsa_mixer_source_changed_cb);
+             snd_mixer_set_callback_private(mixer, source);
+          }
+        if (device_name)
+          free(device_name);
+     }
+}
+
+static Eina_Bool
+_alsa_init(Emix_Event_Cb cb, const void *data)
+{
+   EINA_SAFETY_ON_NULL_RETURN_VAL(cb, EINA_FALSE);
+   if (!ctx)
+      ctx = calloc(1, sizeof(Context));
+
+   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_FALSE);
+
+   ctx->cb = cb;
+   ctx->userdata = data;
+   _alsa_cards_refresh();
+
+   //call the event because the backend is now ready to use
+   ctx->cb((void *)ctx->userdata, EMIX_READY_EVENT, NULL);
+
+   return EINA_TRUE;
+}
+
+static void
+_alsa_shutdown(void)
+{
+   Alsa_Emix_Sink *sink;
+   Alsa_Emix_Source *source;
+   snd_mixer_t *mixer;
+
+   EINA_SAFETY_ON_NULL_RETURN(ctx);
+
+   EINA_LIST_FREE(ctx->sinks, sink)
+      _alsa_device_sink_free(sink);
+   EINA_LIST_FREE(ctx->sources, source)
+      _alsa_device_source_free(source);
+   EINA_LIST_FREE(ctx->cards, mixer)
+      snd_mixer_close(mixer);
+
+   free(ctx);
+   ctx = NULL;
+}
+
+static const Eina_List*
+_alsa_sources_get(void)
+{
+   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
+   return ctx->sources;
+}
+
+static void
+_alsa_sources_mute_set(Emix_Source *source, Eina_Bool mute)
+{
+   Alsa_Emix_Source *s = (Alsa_Emix_Source*) source;
+   Eina_List *node;
+   snd_mixer_elem_t *elem;
+
+   EINA_SAFETY_ON_FALSE_RETURN((ctx && source));
+
+   EINA_LIST_FOREACH(s->channels, node, elem)
+     {
+       if (!snd_mixer_selem_has_capture_switch(elem))
+         continue;
+       if (snd_mixer_selem_set_capture_switch_all(elem, !mute) < 0)
+         ERR("Failed to mute device\n");
+     }
+
+   source->mute = mute;
+   if (ctx->cb)
+     ctx->cb((void *)ctx->userdata, EMIX_SOURCE_CHANGED_EVENT,
+                  (Emix_Source *)source);
+}
+
+static void
+_alsa_sources_volume_set(Emix_Source *source, Emix_Volume v)
+{
+   Alsa_Emix_Source *s = (Alsa_Emix_Source*) source;
+   unsigned int i;
+   snd_mixer_elem_t *elem;
+
+   EINA_SAFETY_ON_FALSE_RETURN((ctx && source));
+
+   if (v.channel_count != eina_list_count(s->channels))
+     {
+        ERR("Volume struct doesnt have the same length than the channels");
+        return;
+     }
+
+   for (i = 0; i < v.channel_count; i++ )
+     {
+        elem = eina_list_nth(s->channels, i);
+        _alsa_channel_volume_set(elem, v.volumes[i], EINA_FALSE);
+     }
+}
+
+
+static const Eina_List*
+_alsa_sinks_get(void)
+{
+   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
+   //FIXME fork or just return?
+/*   Eina_List *result = NULL;
+   Eina_List *node;
+   void *data;
+   EINA_LIST_FOREACH(ctx->sinks, node, data)
+     {
+        result = eina_list_append(result, data);
+     }*/
+   return ctx->sinks;
+}
+
+static Eina_Bool
+_alsa_support(void)
+{
+  return EINA_FALSE;
+}
+
+static void
+_alsa_sink_mute_set(Emix_Sink *sink, Eina_Bool mute)
+{
+   Alsa_Emix_Sink *as = (Alsa_Emix_Sink*) sink;
+   Eina_List *node;
+   snd_mixer_elem_t *elem;
+
+   EINA_SAFETY_ON_FALSE_RETURN((ctx && sink));
+
+   EINA_LIST_FOREACH(as->channels, node, elem)
+     {
+       if (!snd_mixer_selem_has_playback_switch(elem))
+         continue;
+
+       if (snd_mixer_selem_set_playback_switch_all(elem, !mute) < 0)
+         ERR("Failed to set mute(%d) device(%p)", mute, elem);
+     }
+
+   sink->mute = mute;
+   if (ctx->cb)
+     ctx->cb((void *)ctx->userdata, EMIX_SINK_CHANGED_EVENT,
+                  (Emix_Sink *)sink);
+}
+
+static void
+_alsa_sink_volume_set(Emix_Sink *sink, Emix_Volume v)
+{
+   Alsa_Emix_Sink *s = (Alsa_Emix_Sink *)sink;
+   unsigned int i;
+   snd_mixer_elem_t *elem;
+
+   EINA_SAFETY_ON_FALSE_RETURN((ctx && sink));
+
+   if (v.channel_count != eina_list_count(s->channels))
+     {
+        ERR("Volume struct doesnt have the same length than the channels");
+        return;
+     }
+
+   for (i = 0; i < v.channel_count; i++ )
+     {
+        elem = eina_list_nth(s->channels, i);
+        _alsa_channel_volume_set(elem, v.volumes[i], EINA_FALSE);
+     }
+}
+
+static Emix_Backend
+_alsa_backend =
+{
+   _alsa_init,
+   _alsa_shutdown,
+   _alsa_sinks_get,
+   _alsa_support, /*default support*/
+   NULL, /*get*/
+   NULL, /*set*/
+   _alsa_sink_mute_set, /*mute_set*/
+   _alsa_sink_volume_set, /*volume_set*/
+   NULL, /* port set */
+   _alsa_support, /*change support*/
+   NULL, /*sink input get*/
+   NULL,/*sink input mute set*/
+   NULL,/*sink input volume set*/
+   NULL,/*sink input sink change*/
+   _alsa_sources_get,/*source*/
+   _alsa_sources_mute_set,/* source mute set */
+   _alsa_sources_volume_set, /* source volume set */
+   NULL /* advanced options */
+};
+
+E_API Emix_Backend *
+emix_backend_alsa_get(void)
+{
+   return &_alsa_backend;
+}
+
+E_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 (file)
index 0000000..ab8e154
--- /dev/null
@@ -0,0 +1,1047 @@
+#include <Eina.h>
+#include <Ecore.h>
+#include <pulse/pulseaudio.h>
+
+#include "emix.h"
+
+#define ERR(...)      EINA_LOG_ERR(__VA_ARGS__)
+#define DBG(...)      EINA_LOG_DBG(__VA_ARGS__)
+#define WRN(...)      EINA_LOG_WARN(__VA_ARGS__)
+
+#define PA_VOLUME_TO_INT(_vol) \
+   (((_vol+1)*EMIX_VOLUME_MAX+PA_VOLUME_NORM/2)/PA_VOLUME_NORM)
+#define INT_TO_PA_VOLUME(_vol) \
+   (!_vol) ? 0 : ((PA_VOLUME_NORM*(_vol+1)-PA_VOLUME_NORM/2)/EMIX_VOLUME_MAX)
+
+typedef struct _Context
+{
+   pa_mainloop_api api;
+   pa_context *context;
+   pa_context_state_t state;
+   Emix_Event_Cb cb;
+   const void *userdata;
+   Ecore_Timer *connect;
+   int default_sink;
+
+   Eina_List *sinks, *sources, *inputs;
+   Eina_Bool connected;
+} Context;
+
+typedef struct _Sink
+{
+   Emix_Sink base;
+   int idx;
+} Sink;
+
+typedef struct _Sink_Input
+{
+   Emix_Sink_Input base;
+   const char *icon;
+   int idx;
+} Sink_Input;
+
+typedef struct _Source
+{
+   Emix_Source base;
+   int idx;
+} Source;
+
+static Context *ctx = NULL;
+extern pa_mainloop_api functable;
+
+static pa_cvolume
+_emix_volume_convert(const Emix_Volume volume)
+{
+   pa_cvolume vol;
+   unsigned int i;
+
+   vol.channels = volume.channel_count;
+   for (i = 0; i < volume.channel_count; i++)
+      vol.values[i] = INT_TO_PA_VOLUME(volume.volumes[i]);
+
+   return vol;
+}
+
+static Emix_Volume
+_pa_cvolume_convert(const pa_cvolume volume)
+{
+   Emix_Volume vol;
+   int i;
+
+   vol.volumes = calloc(volume.channels, sizeof(int));
+   if (!vol.volumes)
+     {
+        WRN("Could not allocate memory for volume");
+        vol.channel_count = 0;
+        return vol;
+     }
+
+   vol.channel_count = volume.channels;
+   for (i = 0; i < volume.channels; i++)
+     vol.volumes[i] = PA_VOLUME_TO_INT(volume.values[i]);
+
+   return vol;
+}
+
+static void
+_sink_del(Sink *sink)
+{
+   Emix_Port *port;
+
+   EINA_SAFETY_ON_NULL_RETURN(sink);
+   EINA_LIST_FREE(sink->base.ports, port)
+     {
+        eina_stringshare_del(port->name);
+        eina_stringshare_del(port->description);
+        free(port);
+     }
+
+   free(sink->base.volume.volumes);
+   eina_stringshare_del(sink->base.name);
+   free(sink);
+}
+
+static void
+_sink_input_del(Sink_Input *input)
+{
+   EINA_SAFETY_ON_NULL_RETURN(input);
+
+   free(input->base.volume.volumes);
+   eina_stringshare_del(input->base.name);
+   eina_stringshare_del(input->icon);
+   free(input);
+}
+
+static void
+_source_del(Source *source)
+{
+   EINA_SAFETY_ON_NULL_RETURN(source);
+
+   free(source->base.volume.volumes);
+   eina_stringshare_del(source->base.name);
+   free(source);
+}
+
+static void
+_sink_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol,
+         void *userdata EINA_UNUSED)
+{
+   Sink *sink;
+   Emix_Port *port;
+   uint32_t i;
+
+   if (eol < 0)
+     {
+        if (pa_context_errno(c) == PA_ERR_NOENTITY)
+           return;
+
+        ERR("Sink callback failure");
+        return;
+     }
+
+   if (eol > 0)
+      return;
+
+   DBG("sink index: %d\nsink name: %s", info->index,
+       info->name);
+
+   sink = calloc(1, sizeof(Sink));
+   sink->idx = info->index;
+   sink->base.name = eina_stringshare_add(info->description);
+   sink->base.volume = _pa_cvolume_convert(info->volume);
+   sink->base.mute = !!info->mute;
+
+   for (i = 0; i < info->n_ports; i++)
+     {
+        port = calloc(1, sizeof(Emix_Port));
+        if (!port)
+          {
+             WRN("Could not allocate memory for Sink's port");
+             continue;
+          }
+
+        port->available = !!info->ports[i]->available;
+        port->name = eina_stringshare_add(info->ports[i]->name);
+        port->description =  eina_stringshare_add(info->ports[i]->description);
+        sink->base.ports = eina_list_append(sink->base.ports, port);
+        if (info->ports[i]->name == info->active_port->name)
+           port->active = EINA_TRUE;
+     }
+
+   ctx->sinks = eina_list_append(ctx->sinks, sink);
+   if (ctx->cb)
+      ctx->cb((void *)ctx->userdata, EMIX_SINK_ADDED_EVENT,
+              (Emix_Sink *)sink);
+}
+
+static void
+_sink_changed_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol,
+                 void *userdata EINA_UNUSED)
+{
+   Sink *sink = NULL, *s;
+   Emix_Port *port;
+   uint32_t i;
+   Eina_List *l;
+
+   if (eol < 0)
+     {
+        if (pa_context_errno(c) == PA_ERR_NOENTITY)
+           return;
+
+        ERR("Sink callback failure");
+        return;
+     }
+
+   if (eol > 0)
+      return;
+
+   DBG("sink index: %d\nsink name: %s", info->index,
+       info->name);
+
+   EINA_LIST_FOREACH(ctx->sinks, l, s)
+     {
+        if (s->idx == (int)info->index)
+          {
+             sink = s;
+             break;
+          }
+     }
+
+   EINA_SAFETY_ON_NULL_RETURN(sink);
+
+   sink->base.name = eina_stringshare_add(info->description);
+   sink->base.volume = _pa_cvolume_convert(info->volume);
+   sink->base.mute = !!info->mute;
+
+   if (sink->base.ports)
+     {
+        EINA_LIST_FREE(sink->base.ports, port)
+          {
+             eina_stringshare_del(port->name);
+             eina_stringshare_del(port->description);
+             free(port);
+          }
+     }
+   for (i = 0; i < info->n_ports; i++)
+     {
+        port = calloc(1, sizeof(Emix_Port));
+        if (!port)
+          {
+             WRN("Could not allocate memory for Sink's port");
+             continue;
+          }
+
+        port->available = !!info->ports[i]->available;
+        port->name = eina_stringshare_add(info->ports[i]->name);
+        port->description =  eina_stringshare_add(info->ports[i]->description);
+        sink->base.ports = eina_list_append(sink->base.ports, port);
+        if (info->ports[i]->name == info->active_port->name)
+           port->active = EINA_TRUE;
+     }
+
+   if (ctx->cb)
+     ctx->cb((void *)ctx->userdata, EMIX_SINK_CHANGED_EVENT,
+                  (Emix_Sink *)sink);
+}
+
+static void
+_sink_remove_cb(int index, void *data EINA_UNUSED)
+{
+   Sink *sink;
+   Eina_List *l;
+   DBG("Removing sink: %d", index);
+
+   EINA_LIST_FOREACH(ctx->sinks, l, sink)
+     {
+        if (sink->idx == index)
+          {
+             if (ctx->cb)
+               ctx->cb((void *)ctx->userdata, EMIX_SINK_REMOVED_EVENT,
+                            (Emix_Sink *)sink);
+             _sink_del(sink);
+             ctx->sinks = eina_list_remove_list(ctx->sinks, l);
+             break;
+          }
+     }
+}
+
+static const char *
+_icon_from_properties(pa_proplist *l)
+{
+   const char *t;
+
+   if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ICON_NAME)))
+      return t;
+
+   if ((t = pa_proplist_gets(l, PA_PROP_WINDOW_ICON_NAME)))
+      return t;
+
+   if ((t = pa_proplist_gets(l, PA_PROP_APPLICATION_ICON_NAME)))
+      return t;
+
+   if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ROLE)))
+     {
+
+        if (strcmp(t, "video") == 0 ||
+            strcmp(t, "phone") == 0)
+           return t;
+
+        if (strcmp(t, "music") == 0)
+           return "audio";
+
+        if (strcmp(t, "game") == 0)
+           return "applications-games";
+
+        if (strcmp(t, "event") == 0)
+           return "dialog-information";
+     }
+
+   return "audio-card";
+}
+
+static void
+_sink_input_cb(pa_context *c EINA_UNUSED, const pa_sink_input_info *info,
+               int eol, void *userdata EINA_UNUSED)
+{
+   Sink_Input *input;
+   Eina_List *l;
+   Sink *s;
+   EINA_SAFETY_ON_NULL_RETURN(ctx);
+
+   if (eol < 0)
+     {
+        if (pa_context_errno(c) == PA_ERR_NOENTITY)
+          return;
+
+        ERR("Sink input callback failure");
+        return;
+     }
+
+   if (eol > 0)
+      return;
+
+   input = calloc(1, sizeof(Sink_Input));
+   EINA_SAFETY_ON_NULL_RETURN(input);
+
+   DBG("sink input index: %d\nsink input name: %s", info->index,
+       info->name);
+
+   input->idx = info->index;
+   input->base.name = eina_stringshare_add(info->name);
+   input->base.volume = _pa_cvolume_convert(info->volume);
+   input->base.mute = !!info->mute;
+   EINA_LIST_FOREACH(ctx->sinks, l, s)
+     {
+        if (s->idx == (int)info->sink)
+          input->base.sink = (Emix_Sink *)s;
+     }
+   input->icon = eina_stringshare_add(_icon_from_properties(info->proplist));
+   ctx->inputs = eina_list_append(ctx->inputs, input);
+
+   if (ctx->cb)
+     ctx->cb((void *)ctx->userdata, EMIX_SINK_INPUT_ADDED_EVENT,
+             (Emix_Sink_Input *)input);
+}
+
+static void
+_sink_input_changed_cb(pa_context *c EINA_UNUSED,
+                       const pa_sink_input_info *info, int eol,
+                       void *userdata EINA_UNUSED)
+{
+   Sink_Input *input = NULL, *i;
+   Eina_List *l;
+
+   EINA_SAFETY_ON_NULL_RETURN(ctx);
+   if (eol < 0)
+     {
+        if (pa_context_errno(c) == PA_ERR_NOENTITY)
+           return;
+
+        ERR("Sink input changed callback failure");
+        return;
+     }
+
+   if (eol > 0)
+      return;
+
+   EINA_LIST_FOREACH(ctx->inputs, l, i)
+     {
+        if (i->idx == (int)info->index)
+          {
+            input = i;
+            break;
+          }
+     }
+
+   DBG("sink input changed index: %d\n", info->index);
+
+   if (!input)
+     {
+        input = calloc(1, sizeof(Sink_Input));
+        EINA_SAFETY_ON_NULL_RETURN(input);
+        ctx->inputs = eina_list_append(ctx->inputs, input);
+     }
+   input->idx = info->index;
+   input->base.volume = _pa_cvolume_convert(info->volume);
+   input->base.mute = !!info->mute;
+
+   if (ctx->cb)
+     ctx->cb((void *)ctx->userdata, EMIX_SINK_INPUT_CHANGED_EVENT,
+             (Emix_Sink_Input *)input);
+}
+
+static void
+_sink_input_remove_cb(int index, void *data EINA_UNUSED)
+{
+   Sink_Input *input;
+   Eina_List *l;
+   EINA_SAFETY_ON_NULL_RETURN(ctx);
+
+   DBG("Removing sink input: %d", index);
+
+   EINA_LIST_FOREACH(ctx->inputs, l, input)
+     {
+        if (input->idx == index)
+          {
+             if (ctx->cb)
+               ctx->cb((void *)ctx->userdata,
+                       EMIX_SINK_INPUT_REMOVED_EVENT,
+                       (Emix_Sink_Input *)input);
+             _sink_input_del(input);
+
+             ctx->inputs = eina_list_remove_list(ctx->inputs, l);
+             break;
+          }
+     }
+}
+
+static void
+_source_cb(pa_context *c EINA_UNUSED, const pa_source_info *info,
+           int eol, void *userdata EINA_UNUSED)
+{
+   Source *source;
+   EINA_SAFETY_ON_NULL_RETURN(ctx);
+
+   source = calloc(1, sizeof(Source));
+   EINA_SAFETY_ON_NULL_RETURN(source);
+
+   if (eol < 0)
+     {
+        if (pa_context_errno(c) == PA_ERR_NOENTITY)
+           return;
+
+        ERR("Source callback failure");
+        return;
+     }
+
+   if (eol > 0)
+      return;
+
+   source->idx = info->index;
+   source->base.name = eina_stringshare_add(info->name);
+   source->base.volume = _pa_cvolume_convert(info->volume);
+   source->base.mute = !!info->mute;
+
+   ctx->sources = eina_list_append(ctx->sources, source);
+   if (ctx->cb)
+     ctx->cb((void *)ctx->userdata, EMIX_SOURCE_ADDED_EVENT,
+             (Emix_Source *)source);
+}
+
+static void
+_source_changed_cb(pa_context *c EINA_UNUSED,
+                       const pa_source_info *info, int eol,
+                       void *userdata EINA_UNUSED)
+{
+   Source *source = NULL, *s;
+   Eina_List *l;
+   EINA_SAFETY_ON_NULL_RETURN(ctx);
+
+   if (eol < 0)
+     {
+        if (pa_context_errno(c) == PA_ERR_NOENTITY)
+           return;
+
+        ERR("Source changed callback failure");
+        return;
+     }
+
+   if (eol > 0)
+      return;
+
+   EINA_LIST_FOREACH(ctx->sources, l, s)
+     {
+        if (s->idx == (int)info->index)
+          {
+             source = s;
+             break;
+          }
+     }
+
+   DBG("source changed index: %d\n", info->index);
+
+   if (!source)
+     {
+        source = calloc(1, sizeof(Source)); 
+        EINA_SAFETY_ON_NULL_RETURN(source);
+        ctx->sources = eina_list_append(ctx->sources, source);
+     }
+   source->idx= info->index;
+   source->base.volume = _pa_cvolume_convert(info->volume);
+   source->base.mute = !!info->mute;
+
+   if (ctx->cb)
+     ctx->cb((void *)ctx->userdata, EMIX_SOURCE_CHANGED_EVENT,
+                  (Emix_Source *)source);
+}
+
+static void
+_source_remove_cb(int index, void *data EINA_UNUSED)
+{
+   Source *source;
+   Eina_List *l;
+   EINA_SAFETY_ON_NULL_RETURN(ctx);
+
+   DBG("Removing source: %d", index);
+
+   EINA_LIST_FOREACH(ctx->sources, l, source)
+     {
+        if (source->idx == index)
+          {
+             if (ctx->cb)
+               ctx->cb((void *)ctx->userdata, EMIX_SOURCE_REMOVED_EVENT,
+                       (Emix_Source *)source);
+
+             _source_del(source);
+             ctx->sources = eina_list_remove_list(ctx->sources, l);
+             break;
+          }
+     }
+}
+
+static void
+_sink_default_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol,
+                 void *userdata EINA_UNUSED)
+{
+   if (eol < 0)
+     {
+        if (pa_context_errno(c) == PA_ERR_NOENTITY)
+           return;
+
+        ERR("Sink callback failure");
+        return;
+     }
+
+   if (eol > 0)
+      return;
+
+   DBG("sink index: %d\nsink name: %s", info->index,
+       info->name);
+
+   ctx->default_sink = info->index;
+   if (ctx->cb)
+      ctx->cb((void *)ctx->userdata, EMIX_READY_EVENT, NULL);
+}
+
+static void
+_server_info_cb(pa_context *c, const pa_server_info *info,
+           void *userdata)
+{
+   pa_operation *o;
+
+   if (!(o = pa_context_get_sink_info_by_name(c, info->default_sink_name,
+                                              _sink_default_cb, userdata)))
+     {
+        ERR("pa_context_get_sink_info_by_name() failed");
+        return;
+     }
+   pa_operation_unref(o);
+}
+
+static void
+_subscribe_cb(pa_context *c, pa_subscription_event_type_t t,
+              uint32_t index, void *data)
+{
+   pa_operation *o;
+
+   switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
+    case PA_SUBSCRIPTION_EVENT_SINK:
+       if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
+           PA_SUBSCRIPTION_EVENT_REMOVE)
+          _sink_remove_cb(index, data);
+       else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
+                PA_SUBSCRIPTION_EVENT_NEW)
+         {
+            if (!(o = pa_context_get_sink_info_by_index(c, index,
+                                                        _sink_cb, data)))
+              {
+                 ERR("pa_context_get_sink_info_by_index() failed");
+                 return;
+              }
+            pa_operation_unref(o);
+         }
+       else
+         {
+            if (!(o = pa_context_get_sink_info_by_index(c, index,
+                                                        _sink_changed_cb,
+                                                        data)))
+              {
+                 ERR("pa_context_get_sink_info_by_index() failed");
+                 return;
+              }
+            pa_operation_unref(o);
+         }
+       break;
+
+    case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
+       if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
+           PA_SUBSCRIPTION_EVENT_REMOVE)
+          _sink_input_remove_cb(index, data);
+       else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
+                PA_SUBSCRIPTION_EVENT_NEW)
+         {
+            if (!(o = pa_context_get_sink_input_info(c, index,
+                                                     _sink_input_cb, data)))
+              {
+                 ERR("pa_context_get_sink_input_info() failed");
+                 return;
+              }
+            pa_operation_unref(o);
+         }
+       else
+         {
+            if (!(o = pa_context_get_sink_input_info(c, index,
+                                                     _sink_input_changed_cb,
+                                                     data)))
+              {
+                 ERR("pa_context_get_sink_input_info() failed");
+                 return;
+              }
+            pa_operation_unref(o);
+         }
+       break;
+
+    case PA_SUBSCRIPTION_EVENT_SOURCE:
+       if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
+           PA_SUBSCRIPTION_EVENT_REMOVE)
+          _source_remove_cb(index, data);
+       else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
+                PA_SUBSCRIPTION_EVENT_NEW)
+         {
+            if (!(o = pa_context_get_source_info_by_index(c, index,
+                                                          _source_cb, data)))
+              {
+                 ERR("pa_context_get_source_info() failed");
+                 return;
+              }
+            pa_operation_unref(o);
+         }
+       else
+         {
+            if (!(o = pa_context_get_source_info_by_index(c, index,
+                                                          _source_changed_cb,
+                                                          data)))
+              {
+                 ERR("pa_context_get_source_info() failed");
+                 return;
+              }
+            pa_operation_unref(o);
+         }
+       break;
+
+    default:
+       WRN("Event not handled");
+       break;
+   }
+}
+
+static Eina_Bool _pulse_connect(void *data);
+static void _disconnect_cb();
+
+static void
+_pulse_pa_state_cb(pa_context *context, void *data)
+{
+   pa_operation *o;
+
+   switch (pa_context_get_state(context))
+     {
+      case PA_CONTEXT_UNCONNECTED:
+      case PA_CONTEXT_CONNECTING:
+      case PA_CONTEXT_AUTHORIZING:
+      case PA_CONTEXT_SETTING_NAME:
+         break;
+
+      case PA_CONTEXT_READY:
+         {
+            ctx->connect = NULL;
+            ctx->connected = EINA_TRUE;
+            pa_context_set_subscribe_callback(context, _subscribe_cb, ctx);
+            if (!(o = pa_context_subscribe(context, (pa_subscription_mask_t)
+                                           (PA_SUBSCRIPTION_MASK_SINK|
+                                            PA_SUBSCRIPTION_MASK_SOURCE|
+                                            PA_SUBSCRIPTION_MASK_SINK_INPUT|
+                                            PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT|
+                                            PA_SUBSCRIPTION_MASK_CLIENT|
+                                            PA_SUBSCRIPTION_MASK_SERVER|
+                                            PA_SUBSCRIPTION_MASK_CARD),
+                                           NULL, NULL)))
+              {
+                 ERR("pa_context_subscribe() failed");
+                 return;
+              }
+            pa_operation_unref(o);
+
+            if (!(o = pa_context_get_sink_info_list(context, _sink_cb, ctx)))
+              {
+                 ERR("pa_context_get_sink_info_list() failed");
+                 return;
+              }
+            pa_operation_unref(o);
+
+            if (!(o = pa_context_get_sink_input_info_list(context,
+                                                          _sink_input_cb,
+                                                          ctx)))
+              {
+                 ERR("pa_context_get_sink_input_info_list() failed");
+                 return;
+              }
+            pa_operation_unref(o);
+
+            if (!(o = pa_context_get_source_info_list(context, _source_cb,
+                                                      ctx)))
+              {
+                 ERR("pa_context_get_source_info_list() failed");
+                 return;
+              }
+            pa_operation_unref(o);
+
+            if (!(o = pa_context_get_server_info(context, _server_info_cb,
+                                                 ctx)))
+              {
+                 ERR("pa_context_get_server_info() failed");
+                 return;
+              }
+            pa_operation_unref(o);
+            break;
+         }
+
+      case PA_CONTEXT_FAILED:
+         WRN("PA_CONTEXT_FAILED");
+         if (!ctx->connect)
+           ctx->connect = ecore_timer_add(1.0, _pulse_connect, data);
+         goto err;
+      case PA_CONTEXT_TERMINATED:
+         ERR("PA_CONTEXT_TERMINATE:");
+      default:
+         if (ctx->connect)
+           {
+              ecore_timer_del(ctx->connect);
+              ctx->connect = NULL;
+           }
+         goto err;
+     }
+   return;
+
+err:
+   if (ctx->connected)
+     {
+        _disconnect_cb();
+        ctx->connected = EINA_FALSE;
+     }
+   pa_context_unref(ctx->context);
+   ctx->context = NULL;
+}
+
+static Eina_Bool
+_pulse_connect(void *data)
+{
+   pa_proplist *proplist;
+   Context *c = data;
+
+   proplist = pa_proplist_new();
+   pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "Efl Volume Control");
+   pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID,
+                    "org.enlightenment.volumecontrol");
+   pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "audio-card");
+   c->context = pa_context_new_with_proplist(&(c->api), NULL, proplist);
+   if (!c->context)
+     {
+        WRN("Could not create the pulseaudio context");
+        goto err;
+     }
+
+   pa_context_set_state_callback(c->context, _pulse_pa_state_cb, c);
+   if (pa_context_connect(c->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0)
+     {
+        WRN("Could not connect to pulse");
+        goto err;
+     }
+
+   pa_proplist_free(proplist);
+   return ECORE_CALLBACK_DONE;
+
+ err:
+   pa_proplist_free(proplist);
+   return ECORE_CALLBACK_RENEW;
+}
+
+static void
+_shutdown(void)
+{
+   if (!ctx)
+      return;
+
+   if (ctx->connect)
+     {
+        ecore_timer_del(ctx->connect);
+        ctx->connect = NULL;
+     }
+   if (ctx->context)
+      pa_context_unref(ctx->context);
+   if (ctx->connected)
+      _disconnect_cb();
+   free(ctx);
+   ctx = NULL;
+}
+
+static Eina_Bool
+_init(Emix_Event_Cb cb, const void *data)
+{
+   if (ctx)
+     return EINA_TRUE;
+
+   ctx = calloc(1, sizeof(Context));
+   if (!ctx)
+     {
+        ERR("Could not create Epulse Context");
+        return EINA_FALSE;
+     }
+
+   ctx->api = functable;
+   ctx->api.userdata = ctx;
+
+   /* The reason of compares with EINA_TRUE is because ECORE_CALLBACK_RENEW
+      is EINA_TRUE. The function _pulse_connect returns ECORE_CALLBACK_RENEW
+      when could not connect to pulse.
+   */
+   if (_pulse_connect(ctx) == EINA_TRUE)
+     {
+        _shutdown();
+        return EINA_FALSE;
+     }
+
+   ctx->cb = cb;
+   ctx->userdata = data;
+
+   return EINA_TRUE;
+ }
+
+static void
+_disconnect_cb()
+{
+   Source *source;
+   Sink *sink;
+   Sink_Input *input;
+
+   if (ctx->cb)
+      ctx->cb((void *)ctx->userdata, EMIX_DISCONNECTED_EVENT, NULL);
+
+   EINA_LIST_FREE(ctx->sources, source)
+      _source_del(source);
+   EINA_LIST_FREE(ctx->sinks, sink)
+      _sink_del(sink);
+   EINA_LIST_FREE(ctx->inputs, input)
+      _sink_input_del(input);
+}
+
+static void
+_source_volume_set(Emix_Source *source, Emix_Volume volume)
+{
+   pa_operation* o;
+   pa_cvolume vol = _emix_volume_convert(volume);
+   Source *s = (Source *)source;
+   EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && source != NULL);
+
+   if (!(o = pa_context_set_source_volume_by_index(ctx->context,
+                                                   s->idx, &vol,
+                                                   NULL, NULL)))
+      ERR("pa_context_set_source_volume_by_index() failed");
+}
+
+static void
+_source_mute_set(Emix_Source *source, Eina_Bool mute)
+{
+   pa_operation* o;
+   Source *s = (Source *)source;
+   EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && source != NULL);
+
+   if (!(o = pa_context_set_source_mute_by_index(ctx->context,
+                                                 s->idx, mute, NULL, NULL)))
+      ERR("pa_context_set_source_mute() failed");
+}
+
+static const Eina_List *
+_sinks_get(void)
+{
+   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
+   return ctx->sinks;
+}
+
+static const Eina_List *
+_sources_get(void)
+{
+   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
+   return ctx->sources;
+}
+
+static const Eina_List *
+_sink_inputs_get(void)
+{
+   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
+   return ctx->inputs;
+}
+
+static void
+_sink_volume_set(Emix_Sink *sink, Emix_Volume volume)
+{
+   pa_operation* o;
+   Sink *s = (Sink *)sink;
+   pa_cvolume vol = _emix_volume_convert(volume);
+   EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->context && sink != NULL));
+
+   if (!(o = pa_context_set_sink_volume_by_index(ctx->context,
+                                                 s->idx, &vol, NULL, NULL)))
+      ERR("pa_context_set_sink_volume_by_index() failed");
+}
+
+static void
+_sink_mute_set(Emix_Sink *sink, Eina_Bool mute)
+{
+   pa_operation* o;
+   Sink *s = (Sink *)sink;
+   EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->context && sink != NULL));
+
+   if (!(o = pa_context_set_sink_mute_by_index(ctx->context,
+                                               s->idx, mute, NULL, NULL)))
+      ERR("pa_context_set_sink_mute() failed");
+}
+
+static void
+_sink_input_volume_set(Emix_Sink_Input *input, Emix_Volume volume)
+{
+   pa_operation* o;
+   Sink_Input *sink_input = (Sink_Input *)input;
+   pa_cvolume vol = _emix_volume_convert(volume);
+   EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && input != NULL);
+
+
+   if (!(o = pa_context_set_sink_input_volume(ctx->context,
+                                              sink_input->idx, &vol,
+                                              NULL, NULL)))
+      ERR("pa_context_set_sink_input_volume_by_index() failed");
+}
+
+static void
+_sink_input_mute_set(Emix_Sink_Input *input, Eina_Bool mute)
+{
+   pa_operation* o;
+   Sink_Input *sink_input = (Sink_Input *)input;
+   EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && input != NULL);
+
+   if (!(o = pa_context_set_sink_input_mute(ctx->context,
+                                            sink_input->idx, mute,
+                                            NULL, NULL)))
+      ERR("pa_context_set_sink_input_mute() failed");
+}
+
+static void
+_sink_input_move(Emix_Sink_Input *input, Emix_Sink *sink)
+{
+   pa_operation* o;
+   Sink *s = (Sink *)sink;
+   Sink_Input *i = (Sink_Input *)input;
+   EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && input != NULL
+                               && sink != NULL);
+
+   if (!(o = pa_context_move_sink_input_by_index(ctx->context,
+                                                 i->idx, s->idx, NULL,
+                                                 NULL)))
+      ERR("pa_context_move_sink_input_by_index() failed");
+}
+
+static Eina_Bool
+_sink_port_set(Emix_Sink *sink, const Emix_Port *port)
+{
+   pa_operation* o;
+   Sink *s = (Sink *)sink;
+   EINA_SAFETY_ON_FALSE_RETURN_VAL(ctx && ctx->context &&
+                                   sink != NULL && port != NULL, EINA_FALSE);
+
+   if (!(o = pa_context_set_sink_port_by_index(ctx->context,
+                                               s->idx, port->name, NULL,
+                                               NULL)))
+     {
+        ERR("pa_context_set_source_port_by_index() failed");
+        return EINA_FALSE;
+     }
+   pa_operation_unref(o);
+
+   return EINA_TRUE;
+}
+
+static Eina_Bool
+_sink_default_support(void)
+{
+   return EINA_TRUE;
+}
+
+static const Emix_Sink *
+_sink_default_get(void)
+{
+   Sink *s;
+   Eina_List *l;
+
+   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
+   EINA_LIST_FOREACH(ctx->sinks, l, s)
+      if (s->idx == ctx->default_sink)
+        return (Emix_Sink *)s;
+
+   return NULL;
+}
+
+static Eina_Bool
+_sink_change_support(void)
+{
+   return EINA_TRUE;
+}
+
+static Emix_Backend
+_pulseaudio_backend =
+{
+   _init,
+   _shutdown,
+   _sinks_get,
+   _sink_default_support,
+   _sink_default_get,
+   NULL,
+   _sink_mute_set,
+   _sink_volume_set,
+   _sink_port_set,
+   _sink_change_support,
+   _sink_inputs_get,
+   _sink_input_mute_set,
+   _sink_input_volume_set,
+   _sink_input_move,
+   _sources_get,
+   _source_mute_set,
+   _source_volume_set,
+   NULL,
+};
+
+E_API Emix_Backend *
+emix_backend_pulse_get(void)
+{
+   return &_pulseaudio_backend;
+}
+
+E_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 (file)
index 0000000..469aaca
--- /dev/null
@@ -0,0 +1,319 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <Eina.h>
+#include <Ecore.h>
+
+#include <ctype.h>
+#include <errno.h>
+
+#include <pulse/pulseaudio.h>
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#define ERR(...)      EINA_LOG_ERR(__VA_ARGS__)
+#define DBG(...)      EINA_LOG_DBG(__VA_ARGS__)
+#define WRN(...)      EINA_LOG_WARN(__VA_ARGS__)
+
+/* Ecore mainloop integration start */
+struct pa_io_event
+{
+   pa_mainloop_api                *mainloop;
+   Ecore_Fd_Handler               *handler;
+
+   void                           *userdata;
+
+   pa_io_event_flags_t             flags;
+   pa_io_event_cb_t                callback;
+   pa_io_event_destroy_cb_t        destroy_callback;
+};
+
+static Ecore_Fd_Handler_Flags
+map_flags_to_ecore(pa_io_event_flags_t flags)
+{
+   return (Ecore_Fd_Handler_Flags)((flags & PA_IO_EVENT_INPUT ? ECORE_FD_READ : 0)   |
+                                   (flags & PA_IO_EVENT_OUTPUT ? ECORE_FD_WRITE : 0) |
+                                   (flags & PA_IO_EVENT_ERROR ? ECORE_FD_ERROR : 0)  |
+                                   (flags & PA_IO_EVENT_HANGUP ? ECORE_FD_READ : 0));
+}
+
+static Eina_Bool
+_ecore_io_wrapper(void *data, Ecore_Fd_Handler *handler)
+{
+   char buf[64];
+   pa_io_event_flags_t flags = 0;
+   pa_io_event *event = (pa_io_event *)data;
+   int fd = 0;
+
+   fd = ecore_main_fd_handler_fd_get(handler);
+   if (fd < 0) return ECORE_CALLBACK_RENEW;
+
+   if (ecore_main_fd_handler_active_get(handler, ECORE_FD_READ))
+     {
+        flags |= PA_IO_EVENT_INPUT;
+
+        /* Check for HUP and report */
+        if (recv(fd, buf, 64, MSG_PEEK))
+          {
+             if (errno == ESHUTDOWN || errno == ECONNRESET ||
+                 errno == ECONNABORTED || errno == ENETRESET)
+               {
+                  DBG("HUP condition detected");
+                  flags |= PA_IO_EVENT_HANGUP;
+               }
+          }
+     }
+
+   if (ecore_main_fd_handler_active_get(handler, ECORE_FD_WRITE))
+      flags |= PA_IO_EVENT_OUTPUT;
+   if (ecore_main_fd_handler_active_get(handler, ECORE_FD_ERROR))
+      flags |= PA_IO_EVENT_ERROR;
+
+   event->callback(event->mainloop, event, fd, flags, event->userdata);
+
+   return ECORE_CALLBACK_RENEW;
+}
+
+static pa_io_event *
+_ecore_pa_io_new(pa_mainloop_api *api, int fd, pa_io_event_flags_t flags,
+                 pa_io_event_cb_t cb, void *userdata)
+{
+   pa_io_event *event;
+
+   event = calloc(1, sizeof(pa_io_event));
+   event->mainloop = api;
+   event->userdata = userdata;
+   event->callback = cb;
+   event->flags = flags;
+   event->handler = ecore_main_fd_handler_add(fd, map_flags_to_ecore(flags),
+                                              _ecore_io_wrapper, event,
+                                              NULL, NULL);
+
+   return event;
+}
+
+static void
+_ecore_pa_io_enable(pa_io_event *event, pa_io_event_flags_t flags)
+{
+   event->flags = flags;
+   ecore_main_fd_handler_active_set(event->handler, map_flags_to_ecore(flags));
+}
+
+static void
+_ecore_pa_io_free(pa_io_event *event)
+{
+   ecore_main_fd_handler_del(event->handler);
+   free(event);
+}
+
+static void
+_ecore_pa_io_set_destroy(pa_io_event *event, pa_io_event_destroy_cb_t cb)
+{
+   event->destroy_callback = cb;
+}
+
+/* Timed events */
+struct pa_time_event
+{
+   pa_mainloop_api *mainloop;
+   Ecore_Timer                    *timer;
+   struct timeval                  tv;
+
+   void                           *userdata;
+
+   pa_time_event_cb_t              callback;
+   pa_time_event_destroy_cb_t      destroy_callback;
+};
+
+Eina_Bool
+_ecore_time_wrapper(void *data)
+{
+   pa_time_event *event = (pa_time_event *)data;
+
+   event->callback(event->mainloop, event, &event->tv, event->userdata);
+
+   return ECORE_CALLBACK_CANCEL;
+}
+
+pa_time_event *
+_ecore_pa_time_new(pa_mainloop_api *api, const struct timeval *tv, pa_time_event_cb_t cb, void *userdata)
+{
+   pa_time_event *event;
+   struct timeval now;
+   double interval;
+
+   event = calloc(1, sizeof(pa_time_event));
+   event->mainloop = api;
+   event->userdata = userdata;
+   event->callback = cb;
+   event->tv = *tv;
+
+   if (gettimeofday(&now, NULL) == -1)
+     {
+        ERR("Failed to get the current time!");
+        free(event);
+        return NULL;
+     }
+
+   interval = (tv->tv_sec - now.tv_sec) + (tv->tv_usec - now.tv_usec) / 1000;
+   event->timer = ecore_timer_add(interval, _ecore_time_wrapper, event);
+
+   return event;
+}
+
+void
+_ecore_pa_time_restart(pa_time_event *event, const struct timeval *tv)
+{
+   struct timeval now;
+   double interval;
+
+   /* If tv is NULL disable timer */
+   if (!tv)
+     {
+        ecore_timer_del(event->timer);
+        event->timer = NULL;
+        return;
+     }
+
+   event->tv = *tv;
+
+   if (gettimeofday(&now, NULL) == -1)
+     {
+        ERR("Failed to get the current time!");
+        return;
+     }
+
+   interval = (tv->tv_sec - now.tv_sec) + (tv->tv_usec - now.tv_usec) / 1000;
+   if (event->timer)
+     {
+        event->timer = ecore_timer_add(interval, _ecore_time_wrapper, event);
+     }
+   else
+     {
+        ecore_timer_interval_set(event->timer, interval);
+        ecore_timer_reset(event->timer);
+     }
+}
+
+void
+_ecore_pa_time_free(pa_time_event *event)
+{
+   if (event->timer)
+      ecore_timer_del(event->timer);
+
+   event->timer = NULL;
+
+   free(event);
+}
+
+void
+_ecore_pa_time_set_destroy(pa_time_event *event, pa_time_event_destroy_cb_t cb)
+{
+   event->destroy_callback = cb;
+}
+
+/* Deferred events */
+struct pa_defer_event
+{
+   pa_mainloop_api *mainloop;
+   Ecore_Idler                    *idler;
+
+   void                           *userdata;
+
+   pa_defer_event_cb_t             callback;
+   pa_defer_event_destroy_cb_t     destroy_callback;
+};
+
+Eina_Bool
+_ecore_defer_wrapper(void *data)
+{
+   pa_defer_event *event = (pa_defer_event *)data;
+
+   event->idler = NULL;
+   event->callback(event->mainloop, event, event->userdata);
+
+   return ECORE_CALLBACK_CANCEL;
+}
+
+pa_defer_event *
+_ecore_pa_defer_new(pa_mainloop_api *api, pa_defer_event_cb_t cb, void *userdata)
+{
+   pa_defer_event *event;
+
+   event = calloc(1, sizeof(pa_defer_event));
+   event->mainloop = api;
+   event->userdata = userdata;
+   event->callback = cb;
+
+   event->idler = ecore_idler_add(_ecore_defer_wrapper, event);
+
+   return event;
+}
+
+void
+_ecore_pa_defer_enable(pa_defer_event *event, int b)
+{
+   if (!b && event->idler)
+     {
+        ecore_idler_del(event->idler);
+        event->idler = NULL;
+     }
+   else if (b && !event->idler)
+     {
+        event->idler = ecore_idler_add(_ecore_defer_wrapper, event);
+     }
+}
+
+void
+_ecore_pa_defer_free(pa_defer_event *event)
+{
+   if (event->idler)
+      ecore_idler_del(event->idler);
+
+   event->idler = NULL;
+
+   free(event);
+}
+
+void
+_ecore_pa_defer_set_destroy(pa_defer_event *event,
+                            pa_defer_event_destroy_cb_t cb)
+{
+   event->destroy_callback = cb;
+}
+
+static void
+_ecore_pa_quit(pa_mainloop_api *api EINA_UNUSED, int retval EINA_UNUSED)
+{
+   /* FIXME: Need to clean up timers, etc.? */
+   WRN("Not quitting mainloop, although PA requested it");
+}
+
+/* Function table for PA mainloop integration */
+const pa_mainloop_api functable = {
+   .userdata = NULL,
+
+   .io_new = _ecore_pa_io_new,
+   .io_enable = _ecore_pa_io_enable,
+   .io_free = _ecore_pa_io_free,
+   .io_set_destroy = _ecore_pa_io_set_destroy,
+
+   .time_new = _ecore_pa_time_new,
+   .time_restart = _ecore_pa_time_restart,
+   .time_free = _ecore_pa_time_free,
+   .time_set_destroy = _ecore_pa_time_set_destroy,
+
+   .defer_new = _ecore_pa_defer_new,
+   .defer_enable = _ecore_pa_defer_enable,
+   .defer_free = _ecore_pa_defer_free,
+   .defer_set_destroy = _ecore_pa_defer_set_destroy,
+
+   .quit = _ecore_pa_quit,
+};
+
+/* *****************************************************
+ * Ecore mainloop integration end
+ */
diff --git a/src/modules/mixer/lib/emix.c b/src/modules/mixer/lib/emix.c
new file mode 100644 (file)
index 0000000..41babec
--- /dev/null
@@ -0,0 +1,395 @@
+#include <Ecore.h>
+#include <Eina.h>
+
+#include "config.h"
+#include "emix.h"
+
+#ifdef HAVE_PULSE
+E_API Emix_Backend *emix_backend_pulse_get(void);
+E_API const char    *emix_backend_pulse_name;
+#endif
+#ifdef HAVE_ALSA
+E_API Emix_Backend *emix_backend_alsa_get(void);
+E_API const char    *emix_backend_alsa_name;
+#endif
+
+static int _log_domain;
+
+#define CRIT(...)     EINA_LOG_DOM_CRIT(_log_domain, __VA_ARGS__)
+#define ERR(...)      EINA_LOG_DOM_ERR(_log_domain, __VA_ARGS__)
+#define WRN(...)      EINA_LOG_DOM_WARN(_log_domain, __VA_ARGS__)
+#define INF(...)      EINA_LOG_DOM_INFO(_log_domain, __VA_ARGS__)
+#define DBG(...)      EINA_LOG_DOM_DBG(_log_domain, __VA_ARGS__)
+
+struct Callback_Data
+{
+   Emix_Event_Cb cb;
+   const void *data;
+};
+
+typedef struct Context
+{
+   /* Valid backends *.so */
+   Eina_Array *backends;
+   Eina_List *backends_names;
+   Eina_List *callbacks;
+
+   Emix_Backend *loaded;
+} Context;
+
+typedef struct _Back
+{
+   Emix_Backend *(*backend_get) (void);
+   const char     *backend_name;
+} Back;
+
+static int _init_count = 0;
+static Context *ctx = NULL;
+
+static void
+_events_cb(void *data EINA_UNUSED, enum Emix_Event event, void *event_info)
+{
+   Eina_List *l;
+   struct Callback_Data *callback;
+
+   EINA_LIST_FOREACH(ctx->callbacks, l, callback)
+      callback->cb((void *)callback->data, event, event_info);
+}
+
+Eina_Bool
+emix_init(void)
+{
+   Back *back;
+
+   if (_init_count > 0)
+      goto end;
+
+   if (!eina_init())
+     {
+        fprintf(stderr, "Could not init eina\n");
+        return EINA_FALSE;
+     }
+
+   _log_domain = eina_log_domain_register("emix", NULL);
+   if (_log_domain < 0)
+     {
+        EINA_LOG_CRIT("Could not create log domain 'emix'");
+        goto err_log;
+     }
+
+   if (!ecore_init())
+     {
+        CRIT("Could not init ecore");
+        goto err_ecore;
+     }
+
+   ctx = calloc(1, sizeof(Context));
+   if (!ctx)
+     {
+        ERR("Could not create Epulse Context");
+        goto err_ecore;
+     }
+   ctx->backends = eina_array_new(2);
+#ifdef HAVE_PULSE
+   back = calloc(1, sizeof(Back));
+   if (back)
+     {
+        back->backend_get = emix_backend_pulse_get;
+        back->backend_name = emix_backend_pulse_name;
+        eina_array_push(ctx->backends, back);
+        ctx->backends_names = eina_list_append(ctx->backends_names,
+                                               back->backend_name);
+     }
+#endif
+#ifdef HAVE_ALSA
+   back = calloc(1, sizeof(Back));
+   if (back)
+     {
+        back->backend_get = emix_backend_alsa_get;
+        back->backend_name = emix_backend_alsa_name;
+        eina_array_push(ctx->backends, back);
+        ctx->backends_names = eina_list_append(ctx->backends_names,
+                                               back->backend_name);
+     }
+#endif
+
+   if (ctx->backends == NULL)
+     {
+        ERR("Could not find any valid backend");
+        goto err;
+     }
+
+ end:
+   _init_count++;
+   return EINA_TRUE;
+ err:
+   free(ctx);
+   ctx = NULL;
+ err_ecore:
+   eina_log_domain_unregister(_log_domain);
+   _log_domain = -1;
+ err_log:
+   eina_shutdown();
+   return EINA_FALSE;
+}
+
+void
+emix_shutdown()
+{
+   unsigned int i;
+   Eina_Array_Iterator iterator;
+   Back *back;
+
+   if (_init_count == 0)
+      return;
+
+   _init_count--;
+   if (_init_count > 0)
+      return;
+
+   if (ctx->loaded && ctx->loaded->ebackend_shutdown)
+      ctx->loaded->ebackend_shutdown();
+
+   eina_list_free(ctx->backends_names);
+   EINA_ARRAY_ITER_NEXT(ctx->backends, i, back, iterator)
+     {
+        free(back);
+     }
+   eina_array_free(ctx->backends);
+   free(ctx);
+   ctx = NULL;
+
+   ecore_shutdown();
+   eina_shutdown();
+}
+
+const Eina_List*
+emix_backends_available(void)
+{
+   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
+   return ctx->backends_names;
+}
+
+Eina_Bool
+emix_backend_set(const char *backend)
+{
+   Eina_List *l;
+   const char *name;
+   unsigned int i = 0;
+   Back *back;
+
+   EINA_SAFETY_ON_FALSE_RETURN_VAL((ctx && backend), EINA_FALSE);
+   if (ctx->loaded && ctx->loaded->ebackend_shutdown)
+     {
+        ctx->loaded->ebackend_shutdown();
+        ctx->loaded = NULL;
+     }
+
+   EINA_LIST_FOREACH(ctx->backends_names, l, name)
+     {
+        if (!strncmp(name, backend, strlen(name)))
+          break;
+        i++;
+     }
+
+   if (i == eina_list_count(ctx->backends_names))
+     {
+        CRIT("Requested backend not found (%s)", backend);
+        return EINA_FALSE;
+     }
+
+   back = eina_array_data_get(ctx->backends, i);
+   ctx->loaded = back->backend_get();
+
+   if (!ctx->loaded || !ctx->loaded->ebackend_init)
+      return EINA_FALSE;
+
+   return ctx->loaded->ebackend_init(_events_cb, NULL);
+}
+
+const Eina_List*
+emix_sinks_get(void)
+{
+   EINA_SAFETY_ON_FALSE_RETURN_VAL((ctx && ctx->loaded &&
+                                    ctx->loaded->ebackend_sinks_get),
+                                    NULL);
+   return ctx->loaded->ebackend_sinks_get();
+}
+
+Eina_Bool
+emix_sink_default_support(void)
+{
+   EINA_SAFETY_ON_FALSE_RETURN_VAL((ctx && ctx->loaded &&
+                                    ctx->loaded->ebackend_sink_default_support),
+                                   EINA_FALSE);
+   return ctx->loaded->ebackend_sink_default_support();
+}
+
+const Emix_Sink*
+emix_sink_default_get(void)
+{
+   EINA_SAFETY_ON_FALSE_RETURN_VAL((ctx && ctx->loaded &&
+                                    ctx->loaded->ebackend_sink_default_get),
+                                   NULL);
+   return ctx->loaded->ebackend_sink_default_get();
+}
+
+void
+emix_sink_default_set(Emix_Sink *sink)
+{
+   EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->loaded &&
+                                ctx->loaded->ebackend_sink_default_set &&
+                                sink));
+
+   ctx->loaded->ebackend_sink_default_set(sink);
+}
+
+Eina_Bool
+emix_sink_port_set(Emix_Sink *sink, Emix_Port *port)
+{
+   EINA_SAFETY_ON_FALSE_RETURN_VAL((ctx && ctx->loaded &&
+                                    ctx->loaded->ebackend_sink_port_set &&
+                                    sink && port), EINA_FALSE);
+
+   return ctx->loaded->ebackend_sink_port_set(sink, port);
+}
+
+void
+emix_sink_mute_set(Emix_Sink *sink, Eina_Bool mute)
+{
+   EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->loaded &&
+                                ctx->loaded->ebackend_sink_mute_set &&
+                                sink));
+
+   ctx->loaded->ebackend_sink_mute_set(sink, mute);
+}
+
+void
+emix_sink_volume_set(Emix_Sink *sink, Emix_Volume volume)
+{
+   EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->loaded &&
+                                ctx->loaded->ebackend_sink_volume_set &&
+                                sink));
+
+   ctx->loaded->ebackend_sink_volume_set(sink, volume);
+}
+
+Eina_Bool
+emix_sink_change_support(void)
+{
+   EINA_SAFETY_ON_FALSE_RETURN_VAL((ctx && ctx->loaded &&
+                                    ctx->loaded->ebackend_sink_change_support),
+                                    EINA_FALSE);
+   return ctx->loaded->ebackend_sink_change_support();
+}
+
+const Eina_List*
+emix_sink_inputs_get(void)
+{
+   EINA_SAFETY_ON_FALSE_RETURN_VAL((ctx && ctx->loaded &&
+                                    ctx->loaded->ebackend_sink_inputs_get),
+                                    NULL);
+   return ctx->loaded->ebackend_sink_inputs_get();
+}
+
+void
+emix_sink_input_mute_set(Emix_Sink_Input *input, Eina_Bool mute)
+{
+   EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->loaded &&
+                                ctx->loaded->ebackend_sink_input_mute_set &&
+                                input));
+
+   ctx->loaded->ebackend_sink_input_mute_set(input, mute);
+}
+
+void
+emix_sink_input_volume_set(Emix_Sink_Input *input, Emix_Volume volume)
+{
+   EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->loaded &&
+                                ctx->loaded->ebackend_sink_input_volume_set &&
+                                input));
+
+   ctx->loaded->ebackend_sink_input_volume_set(input, volume);
+}
+
+void
+emix_sink_input_sink_change(Emix_Sink_Input *input, Emix_Sink *sink)
+{
+   EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->loaded &&
+                                ctx->loaded->ebackend_sink_input_sink_change &&
+                                input && sink));
+
+   ctx->loaded->ebackend_sink_input_sink_change(input, sink);
+}
+
+const Eina_List*
+emix_sources_get(void)
+{
+   EINA_SAFETY_ON_FALSE_RETURN_VAL((ctx && ctx->loaded &&
+                                    ctx->loaded->ebackend_sources_get), NULL);
+
+   return ctx->loaded->ebackend_sources_get();
+}
+
+void
+emix_source_mute_set(Emix_Source *source, Eina_Bool mute)
+{
+   EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->loaded &&
+                                ctx->loaded->ebackend_source_mute_set &&
+                                source));
+
+   ctx->loaded->ebackend_source_mute_set(source, mute);
+}
+
+void
+emix_source_volume_set(Emix_Source *source, Emix_Volume volume)
+{
+   EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->loaded &&
+                                ctx->loaded->ebackend_source_volume_set &&
+                                source));
+
+   ctx->loaded->ebackend_source_volume_set(source, volume);
+}
+
+Evas_Object *
+emix_advanced_options_add(Evas_Object *parent)
+{
+   EINA_SAFETY_ON_FALSE_RETURN_VAL((ctx && ctx->loaded && parent &&
+                             ctx->loaded->ebackend_advanced_options_add), NULL);
+
+   return ctx->loaded->ebackend_advanced_options_add(parent);
+}
+
+Eina_Bool
+emix_event_callback_add(Emix_Event_Cb cb, const void *data)
+{
+   struct Callback_Data *callback;
+   EINA_SAFETY_ON_FALSE_RETURN_VAL((ctx && cb), EINA_FALSE);
+
+   callback = calloc(1, sizeof(*callback));
+   callback->cb = cb;
+   callback->data = data;
+
+   ctx->callbacks = eina_list_append(ctx->callbacks, callback);
+   return EINA_TRUE;
+}
+
+Eina_Bool
+emix_event_callback_del(Emix_Event_Cb cb)
+{
+   struct Callback_Data *callback;
+   Eina_List *l;
+   EINA_SAFETY_ON_FALSE_RETURN_VAL((ctx && cb), EINA_FALSE);
+
+   EINA_LIST_FOREACH(ctx->callbacks, l, callback)
+     {
+        if (callback->cb == cb)
+          {
+             ctx->callbacks = eina_list_remove_list(ctx->callbacks, l);
+             free(callback);
+             return EINA_TRUE;
+          }
+     }
+
+   return EINA_FALSE;
+}
diff --git a/src/modules/mixer/lib/emix.h b/src/modules/mixer/lib/emix.h
new file mode 100644 (file)
index 0000000..887a8cb
--- /dev/null
@@ -0,0 +1,143 @@
+#ifndef EMIX_H
+#define EMIX_H
+
+#include <Eina.h>
+#include <Evas.h>
+
+#ifdef E_API
+#undef E_API
+#endif
+
+#ifdef __GNUC__
+# if __GNUC__ >= 4
+#  define E_API __attribute__ ((visibility("default")))
+# else
+#  define E_API
+# endif
+#else
+# define E_API
+#endif
+
+
+#define EMIX_VOLUME_MAX 100
+
+enum Emix_Event {
+   EMIX_READY_EVENT = 0,
+   EMIX_DISCONNECTED_EVENT,
+   EMIX_SINK_ADDED_EVENT,
+   EMIX_SINK_REMOVED_EVENT,
+   EMIX_SINK_CHANGED_EVENT,
+   EMIX_SINK_INPUT_ADDED_EVENT,
+   EMIX_SINK_INPUT_REMOVED_EVENT,
+   EMIX_SINK_INPUT_CHANGED_EVENT,
+   EMIX_SOURCE_ADDED_EVENT,
+   EMIX_SOURCE_REMOVED_EVENT,
+   EMIX_SOURCE_CHANGED_EVENT
+};
+
+typedef struct _Emix_Volume {
+   unsigned int channel_count;
+   // the index of the field is the id of the channel, the value the volume
+   int *volumes;
+} Emix_Volume;
+
+typedef struct _Emix_Port {
+   Eina_Bool active;
+   Eina_Bool available;
+   const char *name;
+   const char *description;
+} Emix_Port;
+
+typedef struct _Emix_Sink {
+   const char *name;
+   Emix_Volume volume;
+   Eina_Bool mute;
+   Eina_List *ports;
+} Emix_Sink;
+
+typedef struct _Emix_Sink_Input {
+   const char *name;
+   Emix_Volume volume;
+   Eina_Bool mute;
+   Emix_Sink *sink;
+} Emix_Sink_Input;
+
+typedef struct _Emix_Source {
+   const char *name;
+   Emix_Volume volume;
+   Eina_Bool mute;
+} Emix_Source;
+
+typedef void (*Emix_Event_Cb)(void *data, enum Emix_Event event,
+                              void *event_info);
+
+typedef struct _Emix_Backend {
+   Eina_Bool             (*ebackend_init)(Emix_Event_Cb cb, const void *data);
+   void                  (*ebackend_shutdown)(void);
+
+   const Eina_List*      (*ebackend_sinks_get)(void);
+   Eina_Bool             (*ebackend_sink_default_support)(void);
+   const Emix_Sink*      (*ebackend_sink_default_get)(void);
+   void                  (*ebackend_sink_default_set)(Emix_Sink *sink);
+   void                  (*ebackend_sink_mute_set)(Emix_Sink *sink,
+                                                   Eina_Bool mute);
+   void                  (*ebackend_sink_volume_set)(Emix_Sink *sink,
+                                                     Emix_Volume volume);
+   Eina_Bool             (*ebackend_sink_port_set)(Emix_Sink *sink,
+                                                   const Emix_Port *port);
+   Eina_Bool             (*ebackend_sink_change_support)(void);
+
+   const Eina_List*      (*ebackend_sink_inputs_get)(void);
+   void                  (*ebackend_sink_input_mute_set)(
+                                        Emix_Sink_Input *input, Eina_Bool mute);
+   void                  (*ebackend_sink_input_volume_set)(
+                                    Emix_Sink_Input *input, Emix_Volume volume);
+   void                  (*ebackend_sink_input_sink_change)(
+                                       Emix_Sink_Input *input, Emix_Sink *sink);
+
+   const Eina_List*      (*ebackend_sources_get)(void);
+   void                  (*ebackend_source_mute_set)(Emix_Source *source,
+                                                     Eina_Bool bool);
+   void                  (*ebackend_source_volume_set)(Emix_Source *source,
+                                                       Emix_Volume volume);
+
+   Evas_Object*          (*ebackend_advanced_options_add)(Evas_Object *parent);
+} Emix_Backend;
+
+
+E_API Eina_Bool           emix_init(void);
+E_API void                emix_shutdown(void);
+E_API const Eina_List*    emix_backends_available(void);
+E_API Eina_Bool           emix_backend_set(const char *backend);
+
+E_API Eina_Bool           emix_event_callback_add(Emix_Event_Cb cb,
+                                                 const void *data);
+E_API Eina_Bool           emix_event_callback_del(Emix_Event_Cb cb);
+
+E_API const Eina_List*    emix_sinks_get(void);
+E_API Eina_Bool           emix_sink_default_support(void);
+E_API const Emix_Sink*    emix_sink_default_get(void);
+E_API Eina_Bool           emix_sink_port_set(Emix_Sink *sink, Emix_Port *port);
+E_API void                emix_sink_default_set(Emix_Sink *sink);
+E_API void                emix_sink_mute_set(Emix_Sink *sink, Eina_Bool mute);
+E_API void                emix_sink_volume_set(Emix_Sink *sink,
+                                              Emix_Volume volume);
+E_API Eina_Bool           emix_sink_change_support(void);
+
+E_API const Eina_List*    emix_sink_inputs_get(void);
+E_API void                emix_sink_input_mute_set(Emix_Sink_Input *input,
+                                                  Eina_Bool mute);
+E_API void                emix_sink_input_volume_set(Emix_Sink_Input *input,
+                                                    Emix_Volume volume);
+E_API void                emix_sink_input_sink_change(Emix_Sink_Input *input,
+                                                     Emix_Sink *sink);
+
+E_API const Eina_List*    emix_sources_get(void);
+E_API void                emix_source_mute_set(Emix_Source *source,
+                                                  Eina_Bool mute);
+E_API void                emix_source_volume_set(Emix_Source *source,
+                                                Emix_Volume volume);
+
+E_API Evas_Object*        emix_advanced_options_add(Evas_Object *parent);
+
+#endif  /* EMIX_H */