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