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=""
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
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
--- /dev/null
+#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;
+}
--- /dev/null
+#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
--- /dev/null
+#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;
+}
+
--- /dev/null
+#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_ */
--- /dev/null
+#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()
--- /dev/null
+#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";
--- /dev/null
+#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";
--- /dev/null
+#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
+ */
--- /dev/null
+#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;
+}
--- /dev/null
+#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 */