diff --git a/configure.in b/configure.in
index 8e9fca779..133117857 100644
--- a/configure.in
+++ b/configure.in
@@ -262,6 +262,35 @@ AC_SUBST(edje_cc)
AC_DEFINE(E_INTERNAL, 1, "This define can be used to wrap internal E stuff, as config.h isn't exported")
+dnl Check for Alsa
+AC_ARG_ENABLE(alsa, AS_HELP_STRING([--enable-alsa],
+ [enable support for alsa(default=autodetect)]),
+ [ enable_alsa=$enableval ], [ enable_alsa=default ])
+
+AM_CONDITIONAL(HAVE_ALSA, false)
+if test "x$enable_alsa" = "xdefault" || test "x$enable_alsa" = "xyes"; then
+ PKG_CHECK_MODULES(ALSA, [alsa >= 1.0.8],
+ [ SOUND_CFLAGS="$ALSA_CFLAGS -DHAVE_ALSA $SOUND_CFLAGS"
+ SOUND_LDFLAGS="$ALSA_LIBS $SOUND_LDFLAGS"
+ AM_CONDITIONAL(HAVE_ALSA, true)
+ have_alsa=yes ],
+ [ if test "x$enable_alsa" = "xyes"; then
+ AC_MSG_ERROR([alsa library >= 1.0.8 not found])
+ 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_SUBST(SOUND_CFLAGS)
+AC_SUBST(SOUND_LDFLAGS)
+
AC_OUTPUT([
Makefile
enlightenment.spec
@@ -375,6 +404,8 @@ src/modules/conf_interaction/Makefile
src/modules/conf_interaction/module.desktop
src/modules/gadman/Makefile
src/modules/gadman/module.desktop
+src/modules/mixer/Makefile
+src/modules/mixer/module.desktop
src/preload/Makefile
data/Makefile
data/fonts/Makefile
diff --git a/data/themes/Makefile.am b/data/themes/Makefile.am
index 56a8e8bef..a03529afa 100644
--- a/data/themes/Makefile.am
+++ b/data/themes/Makefile.am
@@ -62,7 +62,8 @@ default_deskpreview.edc \
default_fontpreview.edc \
default_wizard.edc \
default_toolbar.edc \
-default_slidesel.edc
+default_slidesel.edc \
+default_mixer.edc
default.edj: Makefile $(EXTRA_DIST)
$(EDJE_CC) $(EDJE_FLAGS) \
diff --git a/data/themes/default.edc b/data/themes/default.edc
index c7dc249b5..cf64f43c3 100644
--- a/data/themes/default.edc
+++ b/data/themes/default.edc
@@ -86,4 +86,5 @@ collections {
#include "default_wizard.edc"
#include "default_toolbar.edc"
#include "default_slidesel.edc"
+#include "default_mixer.edc"
}
diff --git a/data/themes/default_mixer.edc b/data/themes/default_mixer.edc
new file mode 100644
index 000000000..dbe41f301
--- /dev/null
+++ b/data/themes/default_mixer.edc
@@ -0,0 +1,246 @@
+images {
+ image: "e17_mixer_base.png" COMP;
+ image: "e17_mixer_mute.png" COMP;
+ image: "e17_mixer_left_low.png" COMP;
+ image: "e17_mixer_left_medium.png" COMP;
+ image: "e17_mixer_left_high.png" COMP;
+ image: "e17_mixer_right_low.png" COMP;
+ image: "e17_mixer_right_medium.png" COMP;
+ image: "e17_mixer_right_high.png" COMP;
+}
+
+group {
+ name: "e/modules/mixer/main";
+ max: 128 128;
+ min: 1 1;
+
+ script {
+ public message(Msg_Type:type, id, ...) {
+ if ((type == MSG_INT_SET) && (id == 0)) {
+ new mute, left, right;
+
+ mute = getarg(2);
+ left = getarg(3);
+ right = getarg(4);
+
+ if (mute)
+ run_program(PROGRAM:"mute");
+ else
+ run_program(PROGRAM:"unmute");
+
+ if (left <= 0)
+ run_program(PROGRAM:"left_none");
+ else if (left < 33)
+ run_program(PROGRAM:"left_low");
+ else if (left < 66)
+ run_program(PROGRAM:"left_medium");
+ else if (left >= 66)
+ run_program(PROGRAM:"left_high");
+
+ if (right <= 0)
+ run_program(PROGRAM:"right_none");
+ else if (right < 33)
+ run_program(PROGRAM:"right_low");
+ else if (right < 66)
+ run_program(PROGRAM:"right_medium");
+ else if (right >= 66)
+ run_program(PROGRAM:"right_high");
+ }
+ }
+ }
+
+ parts {
+ part {
+ name: "base";
+ mouse_events: 0;
+ type: RECT;
+ description {
+ state: "default" 0.0;
+ color: 255 255 255 0;
+ aspect: 1 1;
+ aspect_preference: BOTH;
+ }
+ }
+
+ part {
+ name: "speaker";
+ mouse_events: 0;
+ type: IMAGE;
+ description {
+ state: "default" 0.0;
+ aspect: 1 1;
+ aspect_preference: BOTH;
+ rel1.to: "base";
+ rel2.to: "base";
+ image.normal: "e17_mixer_base.png";
+ }
+ }
+
+ part {
+ name: "left";
+ mouse_events: 0;
+ type: IMAGE;
+ description {
+ state: "default" 0.0;
+ visible: 0;
+ aspect: 1 1;
+ aspect_preference: BOTH;
+ rel1.to: "base";
+ rel2.to: "base";
+ image.normal: "e17_mixer_left_low.png";
+ }
+ description {
+ state: "low" 0.0;
+ inherit: "default" 0.0;
+ visible: 1;
+ }
+ description {
+ state: "medium" 0.0;
+ inherit: "default" 0.0;
+ visible: 1;
+ image.normal: "e17_mixer_left_medium.png";
+ }
+ description {
+ state: "high" 0.0;
+ inherit: "default" 0.0;
+ visible: 1;
+ image.normal: "e17_mixer_left_high.png";
+ }
+ }
+
+ part {
+ name: "right";
+ mouse_events: 0;
+ type: IMAGE;
+ description {
+ state: "default" 0.0;
+ visible: 0;
+ aspect: 1 1;
+ aspect_preference: BOTH;
+ rel1.to: "base";
+ rel2.to: "base";
+ image.normal: "e17_mixer_right_low.png";
+ }
+ description {
+ state: "low" 0.0;
+ inherit: "default" 0.0;
+ visible: 1;
+ }
+ description {
+ state: "medium" 0.0;
+ inherit: "default" 0.0;
+ visible: 1;
+ image.normal: "e17_mixer_right_medium.png";
+ }
+ description {
+ state: "high" 0.0;
+ inherit: "default" 0.0;
+ visible: 1;
+ image.normal: "e17_mixer_right_high.png";
+ }
+ }
+
+ part {
+ name: "mute";
+ mouse_events: 0;
+ type: IMAGE;
+ description {
+ state: "default" 0.0;
+ aspect: 1 1;
+ aspect_preference: BOTH;
+ visible: 0;
+ rel1.to: "base";
+ rel2.to: "base";
+ image.normal: "e17_mixer_mute.png";
+ }
+ description {
+ state: "active" 0.0;
+ inherit: "default" 0.0;
+ visible: 1;
+ }
+ }
+
+ part {
+ name: "over";
+ type: RECT;
+ description {
+ state: "default" 0.0;
+ rel1.to: "base";
+ rel2.to: "base";
+ color: 255 255 255 0;
+ }
+ }
+ }
+
+ programs {
+ program {
+ name: "mute";
+ action: STATE_SET "active" 0.0;
+ transition: LINEAR 0.1;
+ target: "mute";
+ }
+
+ program {
+ name: "unmute";
+ action: STATE_SET "default" 0.0;
+ transition: LINEAR 0.1;
+ target: "mute";
+ }
+
+ program {
+ name: "left_none";
+ action: STATE_SET "default" 0.0;
+ transition: LINEAR 0.1;
+ target: "left";
+ }
+
+ program {
+ name: "left_low";
+ action: STATE_SET "low" 0.0;
+ transition: LINEAR 0.1;
+ target: "left";
+ }
+
+ program {
+ name: "left_medium";
+ action: STATE_SET "medium" 0.0;
+ transition: LINEAR 0.1;
+ target: "left";
+ }
+
+ program {
+ name: "left_high";
+ action: STATE_SET "high" 0.0;
+ transition: LINEAR 0.1;
+ target: "left";
+ }
+
+ program {
+ name: "right_none";
+ action: STATE_SET "default" 0.0;
+ transition: LINEAR 0.1;
+ target: "right";
+ }
+
+ program {
+ name: "right_low";
+ action: STATE_SET "low" 0.0;
+ transition: LINEAR 0.1;
+ target: "right";
+ }
+
+ program {
+ name: "right_medium";
+ action: STATE_SET "medium" 0.0;
+ transition: LINEAR 0.1;
+ target: "right";
+ }
+
+ program {
+ name: "right_high";
+ action: STATE_SET "high" 0.0;
+ transition: LINEAR 0.1;
+ target: "right";
+ }
+ }
+}
diff --git a/data/themes/images/e17_mixer_base.png b/data/themes/images/e17_mixer_base.png
new file mode 100644
index 000000000..d4b216307
Binary files /dev/null and b/data/themes/images/e17_mixer_base.png differ
diff --git a/data/themes/images/e17_mixer_left_high.png b/data/themes/images/e17_mixer_left_high.png
new file mode 100644
index 000000000..f7f060a5b
Binary files /dev/null and b/data/themes/images/e17_mixer_left_high.png differ
diff --git a/data/themes/images/e17_mixer_left_low.png b/data/themes/images/e17_mixer_left_low.png
new file mode 100644
index 000000000..97425a784
Binary files /dev/null and b/data/themes/images/e17_mixer_left_low.png differ
diff --git a/data/themes/images/e17_mixer_left_medium.png b/data/themes/images/e17_mixer_left_medium.png
new file mode 100644
index 000000000..63e846819
Binary files /dev/null and b/data/themes/images/e17_mixer_left_medium.png differ
diff --git a/data/themes/images/e17_mixer_module_icon.png b/data/themes/images/e17_mixer_module_icon.png
new file mode 100644
index 000000000..50f9e451a
Binary files /dev/null and b/data/themes/images/e17_mixer_module_icon.png differ
diff --git a/data/themes/images/e17_mixer_mute.png b/data/themes/images/e17_mixer_mute.png
new file mode 100644
index 000000000..43ccfb03a
Binary files /dev/null and b/data/themes/images/e17_mixer_mute.png differ
diff --git a/data/themes/images/e17_mixer_right_high.png b/data/themes/images/e17_mixer_right_high.png
new file mode 100644
index 000000000..359159fc6
Binary files /dev/null and b/data/themes/images/e17_mixer_right_high.png differ
diff --git a/data/themes/images/e17_mixer_right_low.png b/data/themes/images/e17_mixer_right_low.png
new file mode 100644
index 000000000..bc33659ce
Binary files /dev/null and b/data/themes/images/e17_mixer_right_low.png differ
diff --git a/data/themes/images/e17_mixer_right_medium.png b/data/themes/images/e17_mixer_right_medium.png
new file mode 100644
index 000000000..89feab5a0
Binary files /dev/null and b/data/themes/images/e17_mixer_right_medium.png differ
diff --git a/src/modules/Makefile.am b/src/modules/Makefile.am
index bd756b9f1..19d0fb10b 100644
--- a/src/modules/Makefile.am
+++ b/src/modules/Makefile.am
@@ -51,6 +51,7 @@ conf_paths \
conf_mime \
conf_interaction \
gadman \
+mixer \
conf_window_remembers
if HAVE_TEMPERATURE
diff --git a/src/modules/mixer/Makefile.am b/src/modules/mixer/Makefile.am
new file mode 100644
index 000000000..cb550e7ae
--- /dev/null
+++ b/src/modules/mixer/Makefile.am
@@ -0,0 +1,39 @@
+MAINTAINERCLEANFILES = Makefile.in
+MODULE = mixer
+
+# data files for the module
+filesdir = $(libdir)/enlightenment/modules/$(MODULE)
+files_DATA = e-module-$(MODULE).edj module.desktop
+
+EXTRA_DIST = $(files_DATA)
+
+# the module .so file
+INCLUDES = -I. \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/src/modules/$(MODULE) \
+ -I$(top_srcdir)/src/bin \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/modules \
+ @e_cflags@ @SOUND_CFLAGS@
+pkgdir = $(libdir)/enlightenment/modules/$(MODULE)/$(MODULE_ARCH)
+pkg_LTLIBRARIES = module.la
+
+module_la_SOURCES = e_mod_main.h \
+ e_mod_main.c \
+ e_mod_system.h \
+ conf_gadget.c \
+ conf_module.c \
+ app_mixer.c
+
+if HAVE_ALSA
+module_la_SOURCES += sys_alsa.c
+else
+module_la_SOURCES += sys_dummy.c
+endif
+
+module_la_LIBADD = @SOUND_LDFLAGS@ @e_libs@ @dlopen_libs@
+module_la_LDFLAGS = -module -avoid-version
+module_la_DEPENDENCIES = $(top_builddir)/config.h
+
+uninstall:
+ rm -rf $(DESTDIR)$(libdir)/enlightenment/modules/$(MODULE)
diff --git a/src/modules/mixer/app_mixer.c b/src/modules/mixer/app_mixer.c
new file mode 100644
index 000000000..85e37bebd
--- /dev/null
+++ b/src/modules/mixer/app_mixer.c
@@ -0,0 +1,614 @@
+#include "e_mod_main.h"
+
+extern const char _Name[];
+
+typedef struct E_Mixer_App_Dialog_Data
+{
+ E_Mixer_System *sys;
+ char *card;
+ char *channel_name;
+ int lock_sliders;
+ Evas_List *cards;
+ Evas_List *channels_infos;
+ struct channel_info *channel_info;
+ E_Mixer_Channel_State state;
+
+ struct e_mixer_app_ui
+ {
+ Evas_Object *hlayout;
+ struct e_mixer_app_ui_cards
+ {
+ Evas_Object *frame;
+ Evas_Object *list;
+ } cards;
+ struct e_mixer_app_ui_channels
+ {
+ Evas_Object *frame;
+ Evas_Object *list;
+ } channels;
+ struct e_mixer_app_ui_channel_editor
+ {
+ Evas_Object *frame;
+ Evas_Object *label_card;
+ Evas_Object *card;
+ Evas_Object *label_channel;
+ Evas_Object *channel;
+ Evas_Object *label_type;
+ Evas_Object *type;
+ Evas_Object *label_left;
+ Evas_Object *left;
+ Evas_Object *label_right;
+ Evas_Object *right;
+ Evas_Object *mute;
+ Evas_Object *lock_sliders;
+ } channel_editor;
+ } ui;
+
+ struct
+ {
+ void *data;
+ void (*func)(E_Dialog *dialog, void *data);
+ } del;
+} E_Mixer_App_Dialog_Data;
+
+struct channel_info
+{
+ int has_capture;
+ char *name;
+ E_Mixer_Channel *id;
+ E_Mixer_App_Dialog_Data *app;
+};
+
+static void
+_cb_changed_left(void *data, Evas_Object *obj)
+{
+ E_Mixer_App_Dialog_Data *app = data;
+ E_Mixer_Channel_State *state;
+
+ state = &app->state;
+ if (app->lock_sliders && (state->left != state->right))
+ {
+ state->right = state->left;
+ e_widget_slider_value_int_set(app->ui.channel_editor.right,
+ state->right);
+ }
+
+ e_mixer_system_set_volume(app->sys, app->channel_info->id,
+ state->left, state->right);
+}
+
+static void
+_cb_changed_right(void *data, Evas_Object *obj)
+{
+ E_Mixer_App_Dialog_Data *app = data;
+ E_Mixer_Channel_State *state;
+
+ state = &app->state;
+ if (app->lock_sliders && (state->right != state->left))
+ {
+ state->left = state->right;
+ e_widget_slider_value_int_set(app->ui.channel_editor.left,
+ state->left);
+ }
+
+ e_mixer_system_set_volume(app->sys, app->channel_info->id,
+ state->left, state->right);
+}
+
+static void
+_cb_changed_mute(void *data, Evas_Object *obj)
+{
+ E_Mixer_App_Dialog_Data *app = data;
+
+ e_mixer_system_set_mute(app->sys, app->channel_info->id, app->state.mute);
+}
+
+static void
+_cb_changed_lock_sliders(void *data, Evas_Object *obj)
+{
+ E_Mixer_App_Dialog_Data *app = data;
+ E_Mixer_Channel_State *state;
+
+ if (!app->lock_sliders)
+ return;
+
+ state = &app->state;
+ if (state->left == state->right)
+ return;
+
+ state->left = state->right = (state->left + state->right) / 2;
+
+ e_widget_slider_value_int_set(app->ui.channel_editor.left, state->left);
+ e_widget_slider_value_int_set(app->ui.channel_editor.right, state->right);
+ e_mixer_system_set_volume(app->sys, app->channel_info->id,
+ state->left, state->right);
+}
+
+static void
+_update_channel_editor_state(E_Mixer_App_Dialog_Data *app, const E_Mixer_Channel_State state)
+{
+ struct e_mixer_app_ui_channel_editor *ui;
+
+ ui = &app->ui.channel_editor;
+
+ e_widget_slider_value_int_set(ui->left, state.left);
+ e_widget_slider_value_int_set(ui->right, state.right);
+
+ if (e_mixer_system_can_mute(app->sys, app->channel_info->id))
+ {
+ e_widget_disabled_set(ui->mute, 0);
+ e_widget_check_checked_set(ui->mute, state.mute);
+ }
+ else
+ {
+ e_widget_disabled_set(ui->mute, 1);
+ e_widget_check_checked_set(ui->mute, 0);
+ }
+}
+
+static void
+_populate_channel_editor(E_Mixer_App_Dialog_Data *app)
+{
+ struct e_mixer_app_ui_channel_editor *ui;
+ E_Mixer_Channel_State state;
+ char *card_name;
+
+ ui = &app->ui.channel_editor;
+
+ card_name = e_mixer_system_get_card_name(app->card);
+ e_widget_entry_text_set(ui->card, card_name);
+ if (card_name)
+ free(card_name);
+
+ e_widget_entry_text_set(ui->channel, app->channel_name);
+
+ if (e_mixer_system_has_capture(app->sys, app->channel_info->id))
+ e_widget_entry_text_set(ui->type, D_("Capture"));
+ else
+ e_widget_entry_text_set(ui->type, D_("Playback"));
+
+ e_mixer_system_get_state(app->sys, app->channel_info->id, &state);
+ _update_channel_editor_state(app, state);
+
+ app->lock_sliders = (state.left == state.right);
+ e_widget_check_checked_set(ui->lock_sliders, app->lock_sliders);
+}
+
+static void
+_cb_channel_selected(void *data)
+{
+ struct channel_info *info = data;
+ E_Mixer_App_Dialog_Data *app;
+
+ app = info->app;
+ app->channel_info = info;
+ _populate_channel_editor(app);
+}
+
+static int
+_channel_info_cmp(void *data_a, void *data_b)
+{
+ struct channel_info *a = data_a, *b = data_b;
+
+ if (a->has_capture < b->has_capture)
+ return -1;
+ else if (a->has_capture > b->has_capture)
+ return 1;
+
+ return strcmp(a->name, b->name);
+}
+
+static Evas_List *
+_channels_info_new(E_Mixer_System *sys)
+{
+ Evas_List *channels, *channels_infos, *l;
+
+ channels = e_mixer_system_get_channels(sys);
+ channels_infos = NULL;
+ for (l = channels; l != NULL; l = l->next)
+ {
+ struct channel_info *info;
+
+ info = malloc(sizeof(*info));
+ info->id = l->data;
+ info->name = e_mixer_system_get_channel_name(sys, info->id);
+ info->has_capture = e_mixer_system_has_capture(sys, info->id);
+
+ channels_infos = evas_list_append(channels_infos, info);
+ }
+ e_mixer_system_free_channels(channels);
+
+ return evas_list_sort(channels_infos, -1, _channel_info_cmp);
+}
+
+static void
+_channels_info_free(Evas_List *list)
+{
+ Evas_List *l;
+
+ for (l = list; l != NULL; l = l->next)
+ {
+ struct channel_info *info = l->data;
+ free(info->name);
+ free(info);
+ }
+
+ evas_list_free(list);
+}
+
+static int
+_cb_system_update(void *data, E_Mixer_System *sys)
+{
+ E_Mixer_App_Dialog_Data *app;
+ E_Mixer_Channel_State state;
+
+ app = data;
+
+ if ((!app->sys) || (!app->channel_info))
+ return 1;
+
+ e_mixer_system_get_state(app->sys, app->channel_info->id, &state);
+ _update_channel_editor_state(app, state);
+
+ return 1;
+}
+
+
+static void
+_populate_channels(E_Mixer_App_Dialog_Data *app)
+{
+ Evas_List *l;
+ Evas_Object *ilist;
+ int header_input;
+ int i;
+
+ ilist = app->ui.channels.list;
+ edje_freeze();
+ e_widget_ilist_freeze(ilist);
+ e_widget_ilist_clear(ilist);
+
+ if (app->sys)
+ e_mixer_system_del(app->sys);
+ app->sys = e_mixer_system_new(app->card);
+ e_mixer_system_callback_set(app->sys, _cb_system_update, app);
+
+ if (app->channel_name)
+ free(app->channel_name);
+ app->channel_name = e_mixer_system_get_default_channel_name(app->sys);
+
+ if (app->channels_infos)
+ _channels_info_free(app->channels_infos);
+ app->channels_infos = _channels_info_new(app->sys);
+
+ if (app->channels_infos)
+ {
+ struct channel_info *info = app->channels_infos->data;
+ if (info->has_capture)
+ {
+ e_widget_ilist_header_append(ilist, NULL, D_("Input"));
+ header_input = 1;
+ i = 1;
+ }
+ else
+ {
+ e_widget_ilist_header_append(ilist, NULL, D_("Output"));
+ header_input = 0;
+ i = 1;
+ }
+ }
+
+ for (l = app->channels_infos; l != NULL; l = l->next, i++)
+ {
+ struct channel_info *info = l->data;
+
+ if ((!header_input) && info->has_capture)
+ {
+ e_widget_ilist_header_append(ilist, NULL, D_("Input"));
+ header_input = 1;
+ i++;
+ }
+
+ info->app = app;
+ e_widget_ilist_append(ilist, NULL, info->name, _cb_channel_selected,
+ info, info->name);
+ if (app->channel_name && info->name &&
+ (strcmp(app->channel_name, info->name) == 0))
+ {
+ e_widget_ilist_selected_set(ilist, i);
+ app->channel_info = info;
+ }
+ }
+
+ e_widget_ilist_go(ilist);
+ e_widget_ilist_thaw(ilist);
+ edje_thaw();
+}
+
+static void
+select_card(E_Mixer_App_Dialog_Data *app)
+{
+ _populate_channels(app);
+ e_widget_ilist_selected_set(app->ui.channels.list, 1);
+}
+
+static void
+_cb_card_selected(void *data)
+{
+ select_card(data);
+}
+
+static void
+_create_cards(E_Dialog *dialog, Evas *evas, E_Mixer_App_Dialog_Data *app)
+{
+ struct e_mixer_app_ui_cards *ui;
+ Evas_List *l;
+
+ app->card = e_mixer_system_get_default_card();
+ app->cards = e_mixer_system_get_cards();
+ if (evas_list_count(app->cards) < 2)
+ return;
+
+ ui = &app->ui.cards;
+ ui->list = e_widget_tlist_add(evas, &app->card);
+ e_widget_tlist_go(ui->list);
+ for (l = app->cards; l != NULL; l = l->next)
+ {
+ char *card, *card_name;
+
+ card = l->data;
+ card_name = e_mixer_system_get_card_name(card);
+
+ e_widget_tlist_append(ui->list, card_name, _cb_card_selected,
+ app, card);
+
+ free(card_name);
+ }
+
+ ui->frame = e_widget_framelist_add(evas, D_("Cards"), 0);
+ e_widget_framelist_object_append(ui->frame, ui->list);
+ e_widget_list_object_append(app->ui.hlayout, ui->frame, 1, 0, 0.0);
+}
+
+static void
+_create_channels(E_Dialog *dialog, Evas *evas, E_Mixer_App_Dialog_Data *app)
+{
+ struct e_mixer_app_ui_channels *ui;
+
+ ui = &app->ui.channels;
+ ui->list = e_widget_ilist_add(evas, 24, 24, &app->channel_name);
+ e_widget_min_size_set(ui->list, 180, 100);
+ e_widget_ilist_go(ui->list);
+
+ ui->frame = e_widget_framelist_add(evas, D_("Channels"), 0);
+ e_widget_framelist_object_append(ui->frame, ui->list);
+ e_widget_list_object_append(app->ui.hlayout, ui->frame, 1, 1, 0.0);
+}
+
+static void
+_create_channel_editor(E_Dialog *dialog, Evas *evas, E_Mixer_App_Dialog_Data *app)
+{
+ struct e_mixer_app_ui_channel_editor *ui;
+
+ ui = &app->ui.channel_editor;
+
+ ui->label_card = e_widget_label_add(evas, D_("Card:"));
+ ui->card = e_widget_entry_add(evas, NULL, NULL, NULL, NULL);
+ e_widget_entry_readonly_set(ui->card, 1);
+
+ ui->label_channel = e_widget_label_add(evas, D_("Channel:"));
+ ui->channel = e_widget_entry_add(evas, NULL, NULL, NULL, NULL);
+ e_widget_entry_readonly_set(ui->channel, 1);
+
+ ui->label_type = e_widget_label_add(evas, D_("Type:"));
+ ui->type = e_widget_entry_add(evas, NULL, NULL, NULL, NULL);
+ e_widget_entry_readonly_set(ui->type, 1);
+
+ ui->label_left = e_widget_label_add(evas, D_("Left:"));
+ ui->left = e_widget_slider_add(evas, 1, 0, "%3.0f", 0.0, 100.0, 10.0, 100.0,
+ NULL, &app->state.left, 150);
+ e_widget_on_change_hook_set(ui->left, _cb_changed_left, app);
+
+ ui->label_right = e_widget_label_add(evas, D_("Right:"));
+ ui->right = e_widget_slider_add(evas, 1, 0, "%3.0f", 0.0, 100.0, 10.0, 100.0,
+ NULL, &app->state.right, 150);
+ e_widget_on_change_hook_set(ui->right, _cb_changed_right, app);
+
+ ui->mute = e_widget_check_add(evas, D_("Mute"), &app->state.mute);
+ e_widget_on_change_hook_set(ui->mute, _cb_changed_mute, app);
+
+ ui->lock_sliders = e_widget_check_add(evas, D_("Lock Sliders"),
+ &app->lock_sliders);
+ e_widget_on_change_hook_set(ui->lock_sliders, _cb_changed_lock_sliders, app);
+
+ ui->frame = e_widget_framelist_add(evas, D_("Edit"), 0);
+ e_widget_framelist_object_append(ui->frame, ui->label_card);
+ e_widget_framelist_object_append(ui->frame, ui->card);
+ e_widget_framelist_object_append(ui->frame, ui->label_channel);
+ e_widget_framelist_object_append(ui->frame, ui->channel);
+ e_widget_framelist_object_append(ui->frame, ui->label_type);
+ e_widget_framelist_object_append(ui->frame, ui->type);
+ e_widget_framelist_object_append(ui->frame, ui->label_left);
+ e_widget_framelist_object_append(ui->frame, ui->left);
+ e_widget_framelist_object_append(ui->frame, ui->label_right);
+ e_widget_framelist_object_append(ui->frame, ui->right);
+ e_widget_framelist_object_append(ui->frame, ui->mute);
+ e_widget_framelist_object_append(ui->frame, ui->lock_sliders);
+
+ e_widget_list_object_append(app->ui.hlayout, ui->frame, 1, 1, 0.5);
+}
+
+static void
+_create_ui(E_Dialog *dialog, E_Mixer_App_Dialog_Data *app)
+{
+ struct e_mixer_app_ui *ui;
+ Evas *evas;
+ int mw, mh;
+
+ evas = e_win_evas_get(dialog->win);
+
+ ui = &app->ui;
+
+ ui->hlayout = e_widget_list_add(evas, 0, 1);
+ _create_cards(dialog, evas, app);
+ _create_channels(dialog, evas, app);
+ _create_channel_editor(dialog, evas, app);
+
+ if (ui->cards.list)
+ e_widget_tlist_selected_set(ui->cards.list, 0);
+ else
+ select_card(app);
+ e_widget_ilist_selected_set(ui->channels.list, 1);
+
+ e_widget_min_size_get(ui->hlayout, &mw, &mh);
+ if (mw < 300)
+ mw = 300;
+ if (mh < 200)
+ mh = 200;
+ e_dialog_content_set(dialog, ui->hlayout, mw, mh);
+}
+
+static void
+_mixer_app_dialog_del(E_Dialog *dialog, E_Mixer_App_Dialog_Data *app)
+{
+ if (app->del.func)
+ app->del.func(dialog, app->del.data);
+
+ if (app->card)
+ free(app->card);
+ if (app->channel_name)
+ free(app->channel_name);
+ if (app->cards)
+ e_mixer_system_free_cards(app->cards);
+ if (app->channels_infos)
+ _channels_info_free(app->channels_infos);
+ e_mixer_system_del(app->sys);
+
+ e_util_defer_object_del(E_OBJECT(dialog));
+ dialog->data = NULL;
+ E_FREE(app);
+}
+
+static void
+_cb_win_del(E_Win *win)
+{
+ E_Dialog *dialog;
+ E_Mixer_App_Dialog_Data *app;
+
+ dialog = win->data;
+ app = dialog->data;
+
+ _mixer_app_dialog_del(dialog, app);
+}
+
+static void
+_cb_dialog_dismiss(void *data, E_Dialog *dialog)
+{
+ _mixer_app_dialog_del(dialog, data);
+}
+
+E_Dialog *
+e_mixer_app_dialog_new(E_Container *con, void (*func)(E_Dialog *dialog, void *data), void *data)
+{
+ E_Mixer_App_Dialog_Data *app;
+ E_Dialog *dialog;
+
+ dialog = e_dialog_new(con, _Name, "e_mixer_app_dialog");
+ if (!dialog)
+ return NULL;
+
+ app = E_NEW(E_Mixer_App_Dialog_Data, 1);
+ if (!app)
+ {
+ e_object_del(E_OBJECT(dialog));
+ return NULL;
+ }
+
+ dialog->data = app;
+ app->del.data = data;
+ app->del.func = func;
+
+ e_dialog_title_set(dialog, D_(_Name));
+ e_dialog_resizable_set(dialog, 1);
+
+ e_win_delete_callback_set(dialog->win, _cb_win_del);
+
+ _create_ui(dialog, app);
+
+ e_dialog_button_add(dialog, D_("Close"), NULL, _cb_dialog_dismiss, app);
+ e_dialog_button_focus_num(dialog, 1);
+ e_win_centered_set(dialog->win, 1);
+ e_dialog_show(dialog);
+
+ return dialog;
+}
+
+static inline int
+_find_card_by_name(E_Mixer_App_Dialog_Data *app, const char *card_name)
+{
+ Evas_List *l;
+ int i;
+
+ for (i = 0, l = app->cards; l != NULL; i++, l = l->next)
+ if (strcmp(card_name, l->data) == 0)
+ return i;
+
+ return -1;
+}
+
+static inline int
+_find_channel_by_name(E_Mixer_App_Dialog_Data *app, const char *channel_name)
+{
+ Evas_List *l;
+ int i, header_input;
+
+ if (app->channels_infos)
+ {
+ struct channel_info *info = app->channels_infos->data;
+
+ header_input = !!info->has_capture;
+ i = 1;
+ }
+
+ for (l = app->channels_infos; l != NULL; l = l->next, i++)
+ {
+ struct channel_info *info = l->data;
+
+ if ((!header_input) && info->has_capture)
+ {
+ header_input = 1;
+ i++;
+ }
+
+ if (strcmp(channel_name, info->name) == 0)
+ return i;
+ }
+
+ return -1;
+}
+
+int
+e_mixer_app_dialog_select(E_Dialog *dialog, const char *card_name, const char *channel_name)
+{
+ E_Mixer_App_Dialog_Data *app;
+ int n;
+
+ if (!dialog)
+ return 0;
+
+ app = dialog->data;
+ if (!app)
+ return 0;
+
+ n = _find_card_by_name(app, card_name);
+ if (n < 0)
+ return 0;
+ if (app->ui.cards.list)
+ e_widget_tlist_selected_set(app->ui.cards.list, n);
+
+ n = _find_channel_by_name(app, channel_name);
+ if (n < 0)
+ return 0;
+ e_widget_ilist_selected_set(app->ui.channels.list, n);
+
+ return 1;
+}
diff --git a/src/modules/mixer/conf_gadget.c b/src/modules/mixer/conf_gadget.c
new file mode 100644
index 000000000..928dda856
--- /dev/null
+++ b/src/modules/mixer/conf_gadget.c
@@ -0,0 +1,389 @@
+#include "e_mod_main.h"
+
+extern const char _Name[];
+
+struct _E_Config_Dialog_Data
+{
+ int lock_sliders;
+ int show_locked;
+ int card_num;
+ int channel;
+ const char *card;
+ const char *channel_name;
+ Evas_List *cards;
+ Evas_List *cards_names;
+ Evas_List *channels_names;
+ struct mixer_config_ui
+ {
+ Evas_Object *table;
+ struct mixer_config_ui_general
+ {
+ Evas_Object *frame;
+ Evas_Object *lock_sliders;
+ Evas_Object *show_locked;
+ } general;
+ struct mixer_config_ui_cards
+ {
+ Evas_Object *frame;
+ E_Radio_Group *radio;
+ } cards;
+ struct mixer_config_ui_channels
+ {
+ Evas_Object *frame;
+ Evas_Object *scroll;
+ Evas_Object *list;
+ E_Radio_Group *radio;
+ Evas_List *radios;
+ } channels;
+ } ui;
+ E_Mixer_Gadget_Config *conf;
+};
+
+static void
+_mixer_fill_cards_info(E_Config_Dialog_Data *cfdata)
+{
+ Evas_List *l;
+ int i;
+
+ cfdata->card_num = -1;
+ cfdata->cards = e_mixer_system_get_cards();
+ cfdata->cards_names = NULL;
+ for (l = cfdata->cards, i = 0; l != NULL; l = l->next, i++)
+ {
+ char *card, *name;
+
+ card = l->data;
+ name = e_mixer_system_get_card_name(card);
+ if ((cfdata->card_num < 0) && card && cfdata->card &&
+ (strcmp(card, cfdata->card) == 0))
+ cfdata->card_num = i;
+
+ cfdata->cards_names = evas_list_append(cfdata->cards_names, name);
+ }
+
+ if (cfdata->card_num < 0)
+ cfdata->card_num = 0;
+}
+
+static void
+_mixer_fill_channels_info(E_Config_Dialog_Data *cfdata)
+{
+ E_Mixer_System *sys;
+ Evas_List *l;
+ int i;
+
+ sys = e_mixer_system_new(cfdata->card);
+ if (!sys)
+ return;
+
+ cfdata->channel = 0;
+ cfdata->channel_name = evas_stringshare_add(cfdata->conf->channel_name);
+ cfdata->channels_names = e_mixer_system_get_channels_names(sys);
+ for (l = cfdata->channels_names, i = 0; l != NULL; l = l->next, i++)
+ {
+ char *channel;
+
+ channel = l->data;
+ if (channel && cfdata->channel_name &&
+ (strcmp(channel, cfdata->channel_name) == 0))
+ {
+ cfdata->channel = i;
+ break;
+ }
+ }
+ e_mixer_system_del(sys);
+}
+
+static void *
+_create_data(E_Config_Dialog *dialog)
+{
+ E_Config_Dialog_Data *cfdata;
+ E_Mixer_Gadget_Config *conf;
+
+ cfdata = E_NEW(E_Config_Dialog_Data, 1);
+ if (!cfdata)
+ return NULL;
+
+ conf = dialog->data;
+ cfdata->conf = conf;
+ cfdata->lock_sliders = conf->lock_sliders;
+ cfdata->show_locked = conf->show_locked;
+ cfdata->card = evas_stringshare_add(conf->card);
+ _mixer_fill_cards_info(cfdata);
+ _mixer_fill_channels_info(cfdata);
+
+ return cfdata;
+}
+
+static void
+_free_data(E_Config_Dialog *dialog, E_Config_Dialog_Data *cfdata)
+{
+ E_Mixer_Gadget_Config *conf;
+ Evas_List *l;
+
+ conf = dialog->data;
+ if (conf)
+ conf->dialog = NULL;
+
+ if (!cfdata)
+ return;
+
+ for (l = cfdata->cards_names; l != NULL; l = l->next)
+ if (l->data)
+ free(l->data);
+ evas_list_free(cfdata->cards_names);
+
+ if (cfdata->channels_names)
+ e_mixer_system_free_channels_names(cfdata->channels_names);
+ if (cfdata->cards)
+ e_mixer_system_free_cards(cfdata->cards);
+
+ if (cfdata->card)
+ evas_stringshare_del(cfdata->card);
+ if (cfdata->channel_name)
+ evas_stringshare_del(cfdata->channel_name);
+
+ evas_list_free(cfdata->ui.channels.radios);
+
+ E_FREE(cfdata);
+}
+
+static int
+_basic_apply(E_Config_Dialog *dialog, E_Config_Dialog_Data *cfdata)
+{
+ E_Mixer_Gadget_Config *conf;
+ const char *card, *channel;
+
+ conf = dialog->data;
+ conf->lock_sliders = cfdata->lock_sliders;
+ conf->show_locked = cfdata->show_locked;
+
+ card = evas_list_nth(cfdata->cards, cfdata->card_num);
+ if (card)
+ {
+ if (conf->card && (strcmp(card, conf->card) != 0))
+ evas_stringshare_del(conf->card);
+ conf->card = evas_stringshare_add(card);
+ }
+
+ channel = evas_list_nth(cfdata->channels_names, cfdata->channel);
+ if (channel)
+ {
+ if (conf->channel_name && (strcmp(channel, conf->channel_name) != 0))
+ evas_stringshare_del(conf->channel_name);
+ conf->channel_name = evas_stringshare_add(channel);
+ }
+
+ e_mixer_update(conf->instance);
+ return 1;
+}
+
+static void
+_lock_change(void *data, Evas_Object *obj, void *event)
+{
+ E_Config_Dialog_Data *cfdata;
+
+ cfdata = data;
+ e_widget_disabled_set(cfdata->ui.general.show_locked, !cfdata->lock_sliders);
+}
+
+static void
+_basic_create_general(Evas *evas, E_Config_Dialog_Data *cfdata)
+{
+ struct mixer_config_ui_general *ui;
+
+ ui = &cfdata->ui.general;
+
+ ui->frame = e_widget_framelist_add(evas, D_("General Settings"), 0);
+
+ ui->lock_sliders = e_widget_check_add(
+ evas, D_("Lock Sliders"), &cfdata->lock_sliders);
+ evas_object_smart_callback_add(
+ ui->lock_sliders, "changed", _lock_change, cfdata);
+ e_widget_framelist_object_append(ui->frame, ui->lock_sliders);
+
+ ui->show_locked = e_widget_check_add(
+ evas, D_("Show both sliders when locked"), &cfdata->show_locked);
+ e_widget_disabled_set(ui->show_locked, !cfdata->lock_sliders);
+ e_widget_framelist_object_append(ui->frame, ui->show_locked);
+}
+
+static void
+_clear_channels(E_Config_Dialog_Data *cfdata)
+{
+ Evas_List *l;
+
+ for (l = cfdata->ui.channels.radios; l != NULL; l = l->next)
+ evas_object_del(l->data);
+ cfdata->ui.channels.radios = evas_list_free(cfdata->ui.channels.radios);
+}
+
+static void
+_fill_channels(Evas *evas, E_Config_Dialog_Data *cfdata)
+{
+ struct mixer_config_ui_channels *ui;
+ Evas_Object *selected;
+ Evas_Coord mw, mh;
+ Evas_List *l;
+ int i;
+
+ ui = &cfdata->ui.channels;
+ ui->radio = e_widget_radio_group_new(&cfdata->channel);
+ for (i = 0, l = cfdata->channels_names; l != NULL; l = l->next, i++)
+ {
+ Evas_Object *ow;
+ const char *name;
+
+ name = l->data;
+ if (!name)
+ continue;
+
+ ow = e_widget_radio_add(evas, name, i, ui->radio);
+ ui->radios = evas_list_append(ui->radios, ow);
+ e_widget_list_object_append(ui->list, ow, 1, 1, 0.0);
+ }
+
+ e_widget_min_size_get(ui->list, &mw, &mh);
+ evas_object_resize(ui->list, mw, mh);
+
+ selected = evas_list_nth(ui->radios, cfdata->channel);
+ if (selected)
+ {
+ Evas_Coord x, y, w, h, lx, ly;
+ evas_object_geometry_get(selected, &x, &y, &w, &h);
+ evas_object_geometry_get(ui->list, &lx, &ly, NULL, NULL);
+ x -= lx;
+ y -= ly - 10;
+ h += 20;
+ e_widget_scrollframe_child_region_show(ui->scroll, x, y, w, h);
+ }
+}
+
+static void
+_channel_scroll_set_min_size(struct mixer_config_ui_channels *ui)
+{
+ Evas_Coord w, h;
+ int len;
+
+ len = evas_list_count(ui->radios);
+ if (len < 1)
+ return;
+
+ e_widget_min_size_get(ui->list, &w, &h);
+ h = 4 * h / len;
+ e_widget_min_size_set(ui->scroll, w, h);
+}
+
+static void
+_basic_create_channels(Evas *evas, E_Config_Dialog_Data *cfdata)
+{
+ struct mixer_config_ui_channels *ui;
+
+ ui = &cfdata->ui.channels;
+ ui->list = e_widget_list_add(evas, 1, 0);
+ ui->scroll = e_widget_scrollframe_simple_add(evas, ui->list);
+ ui->frame = e_widget_framelist_add(evas, D_("Channels"), 0);
+
+ _fill_channels(evas, cfdata);
+
+ _channel_scroll_set_min_size(ui);
+ e_widget_framelist_object_append(ui->frame, ui->scroll);
+}
+
+static void
+_card_change(void *data, Evas_Object *obj, void *event)
+{
+ E_Config_Dialog_Data *cfdata;
+ Evas *evas;
+ char *card;
+
+ cfdata = data;
+
+ if (cfdata->card)
+ evas_stringshare_del(cfdata->card);
+
+ e_mixer_system_free_channels_names(cfdata->channels_names);
+ if (cfdata->channel_name)
+ evas_stringshare_del(cfdata->channel_name);
+
+ card = evas_list_nth(cfdata->cards, cfdata->card_num);
+ cfdata->card = evas_stringshare_add(card);
+ _mixer_fill_channels_info(cfdata);
+
+ evas = evas_object_evas_get(obj);
+ _clear_channels(cfdata);
+ _fill_channels(evas, cfdata);
+}
+
+static void
+_basic_create_cards(Evas *evas, E_Config_Dialog_Data *cfdata)
+{
+ struct mixer_config_ui_cards *ui;
+ Evas_List *l;
+ int i;
+
+ ui = &cfdata->ui.cards;
+
+ ui->frame = e_widget_framelist_add(evas, D_("Sound Cards"), 0);
+ ui->radio = e_widget_radio_group_new(&cfdata->card_num);
+ for (i = 0, l = cfdata->cards_names; l != NULL; l = l->next, i++)
+ {
+ Evas_Object *ow;
+ const char *card;
+
+ card = l->data;
+ if (!card)
+ continue;
+
+ ow = e_widget_radio_add(evas, card, i, ui->radio);
+ e_widget_framelist_object_append(ui->frame, ow);
+ evas_object_smart_callback_add(ow, "changed", _card_change, cfdata);
+ }
+}
+
+static Evas_Object *
+_basic_create(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata)
+{
+ if (!cfdata)
+ return NULL;
+
+ cfdata->ui.table = e_widget_table_add(evas, 0);
+ _basic_create_general(evas, cfdata);
+ _basic_create_cards(evas, cfdata);
+ _basic_create_channels(evas, cfdata);
+
+ e_widget_table_object_append(cfdata->ui.table, cfdata->ui.general.frame,
+ 0, 0, 1, 1, 1, 1, 1, 0);
+ e_widget_table_object_append(cfdata->ui.table, cfdata->ui.cards.frame,
+ 0, 1, 1, 1, 1, 1, 1, 0);
+ e_widget_table_object_append(cfdata->ui.table, cfdata->ui.channels.frame,
+ 0, 2, 1, 1, 1, 1, 1, 1);
+
+ return cfdata->ui.table;
+}
+
+E_Config_Dialog *
+e_mixer_config_dialog_new(E_Container *con, E_Mixer_Gadget_Config *conf)
+{
+ E_Config_Dialog *dialog;
+ E_Config_Dialog_View *view;
+
+ if (e_config_dialog_find(_Name, "e_mixer_config_dialog_new"))
+ return NULL;
+
+ view = E_NEW(E_Config_Dialog_View, 1);
+ if (!view)
+ return NULL;
+
+ view->create_cfdata = _create_data;
+ view->free_cfdata = _free_data;
+ view->basic.create_widgets = _basic_create;
+ view->basic.apply_cfdata = _basic_apply;
+
+ dialog = e_config_dialog_new(con, D_("Mixer Configuration"),
+ _Name, "e_mixer_config_dialog_new",
+ e_mixer_theme_path(), 0, view, conf);
+ e_dialog_resizable_set(dialog->dia, 1);
+
+ return dialog;
+}
diff --git a/src/modules/mixer/conf_module.c b/src/modules/mixer/conf_module.c
new file mode 100644
index 000000000..80fa88306
--- /dev/null
+++ b/src/modules/mixer/conf_module.c
@@ -0,0 +1,192 @@
+#include "e_mod_main.h"
+
+extern const char _Name[];
+
+struct _E_Config_Dialog_Data
+{
+ int default_instance;
+ struct mixer_config_ui
+ {
+ Evas_Object *list;
+ struct mixer_config_ui_general
+ {
+ Evas_Object *frame;
+ E_Radio_Group *radio;
+ } general;
+ } ui;
+};
+
+static int
+_find_default_instance_index(E_Mixer_Module_Context *ctxt)
+{
+ Evas_List *l;
+ int i;
+
+ for (i = 0, l = ctxt->instances; l != NULL; l = l->next, i++)
+ if (l->data == ctxt->default_instance)
+ return i;
+
+ return 0;
+}
+
+static void *
+_create_data(E_Config_Dialog *dialog)
+{
+ E_Config_Dialog_Data *cfdata;
+ E_Mixer_Module_Context *ctxt;
+
+ cfdata = E_NEW(E_Config_Dialog_Data, 1);
+ if (!cfdata)
+ return NULL;
+
+ ctxt = dialog->data;
+ cfdata->default_instance = _find_default_instance_index(ctxt);
+
+ return cfdata;
+}
+
+static void
+_free_data(E_Config_Dialog *dialog, E_Config_Dialog_Data *cfdata)
+{
+ E_Mixer_Module_Context *ctxt;
+
+ ctxt = dialog->data;
+ if (ctxt)
+ ctxt->conf_dialog = NULL;
+
+ E_FREE(cfdata);
+}
+
+static int
+_basic_apply(E_Config_Dialog *dialog, E_Config_Dialog_Data *cfdata)
+{
+ E_Mixer_Module_Context *ctxt;
+
+ ctxt = dialog->data;
+ ctxt->default_instance = evas_list_nth(ctxt->instances,
+ cfdata->default_instance);
+ if (ctxt->default_instance)
+ {
+ E_Mixer_Module_Config *conf;
+ const char *id;
+
+ conf = ctxt->conf;
+ if (conf->default_gc_id)
+ evas_stringshare_del(conf->default_gc_id);
+
+ id = ctxt->default_instance->gcc->cf->id;
+ conf->default_gc_id = evas_stringshare_add(id);
+ }
+
+ return 1;
+}
+
+static void
+_basic_create_general(E_Config_Dialog *dialog, Evas *evas, E_Config_Dialog_Data *cfdata)
+{
+ struct mixer_config_ui_general *ui;
+ E_Mixer_Module_Context *ctxt;
+ Evas_Object *label;
+ Evas_List *l;
+ int i;
+
+ ui = &cfdata->ui.general;
+ ctxt = dialog->data;
+
+ ui->frame = e_widget_framelist_add(evas, D_("General Settings"), 0);
+
+ label = e_widget_label_add(evas, D_("Mixer to use for global actions:"));
+ e_widget_framelist_object_append(ui->frame, label);
+
+ ui->radio = e_widget_radio_group_new(&cfdata->default_instance);
+ for (i = 0, l = ctxt->instances; l != NULL; l = l->next, i++)
+ {
+ E_Mixer_Instance *inst;
+ E_Mixer_Gadget_Config *conf;
+ Evas_Object *o;
+ char name[128];
+ char *card_name;
+
+ inst = l->data;
+ conf = inst->conf;
+
+ card_name = e_mixer_system_get_card_name(conf->card);
+ snprintf(name, sizeof(name), "%s: %s", card_name, conf->channel_name);
+ free(card_name);
+
+ o = e_widget_radio_add(evas, name, i, ui->radio);
+ e_widget_framelist_object_append(ui->frame, o);
+ }
+
+ e_widget_list_object_append(cfdata->ui.list, ui->frame, 1, 1, 0.5);
+}
+
+static void
+cb_mixer_app_del(E_Dialog *dialog, void *data)
+{
+ E_Mixer_Module_Context *ctxt = data;
+ ctxt->mixer_dialog = NULL;
+}
+
+static void
+cb_mixer_call(void *data, void *data2)
+{
+ E_Mixer_Module_Context *ctxt = data;
+ E_Container *con;
+
+ if (ctxt->mixer_dialog)
+ {
+ e_dialog_show(ctxt->mixer_dialog);
+ return;
+ }
+
+ con = e_container_current_get(e_manager_current_get());
+ ctxt->mixer_dialog = e_mixer_app_dialog_new(con, cb_mixer_app_del, ctxt);
+}
+
+static void
+_basic_create_mixer_call(E_Config_Dialog *dialog, Evas *evas, E_Config_Dialog_Data *cfdata)
+{
+ Evas_Object *button;
+
+ button = e_widget_button_add(evas, D_("Launch mixer..."), NULL,
+ cb_mixer_call, dialog->data, NULL);
+ e_widget_list_object_append(cfdata->ui.list, button, 0, 0, 0.0);
+}
+
+static Evas_Object *
+_basic_create(E_Config_Dialog *dialog, Evas *evas, E_Config_Dialog_Data *cfdata)
+{
+ if (!cfdata)
+ return NULL;
+
+ cfdata->ui.list = e_widget_list_add(evas, 0, 0);
+ _basic_create_general(dialog, evas, cfdata);
+ _basic_create_mixer_call(dialog, evas, cfdata);
+ return cfdata->ui.list;
+}
+
+E_Config_Dialog *
+e_mixer_config_module_dialog_new(E_Container *con, E_Mixer_Module_Context *ctxt)
+{
+ E_Config_Dialog *dialog;
+ E_Config_Dialog_View *view;
+
+ if (e_config_dialog_find(_Name, "e_mixer_config_module_dialog_new"))
+ return NULL;
+
+ view = E_NEW(E_Config_Dialog_View, 1);
+ if (!view)
+ return NULL;
+
+ view->create_cfdata = _create_data;
+ view->free_cfdata = _free_data;
+ view->basic.create_widgets = _basic_create;
+ view->basic.apply_cfdata = _basic_apply;
+
+ dialog = e_config_dialog_new(con, D_("Mixer Module Configuration"),
+ _Name, "e_mixer_config_module_dialog_new",
+ e_mixer_theme_path(), 0, view, ctxt);
+
+ return dialog;
+}
diff --git a/src/modules/mixer/e-module-mixer.edj b/src/modules/mixer/e-module-mixer.edj
new file mode 100644
index 000000000..6b2108ab0
Binary files /dev/null and b/src/modules/mixer/e-module-mixer.edj differ
diff --git a/src/modules/mixer/e_mod_main.c b/src/modules/mixer/e_mod_main.c
new file mode 100644
index 000000000..035365b49
--- /dev/null
+++ b/src/modules/mixer/e_mod_main.c
@@ -0,0 +1,1252 @@
+#include "e_mod_main.h"
+#include "e_mod_system.h"
+
+static E_Module *mixer_mod = NULL;
+static char tmpbuf[PATH_MAX]; /* general purpose buffer, just use immediately */
+
+static const char _conf_domain[] = "module.mixer";
+static const char _name[] = "mixer";
+const char _Name[] = "Mixer";
+
+const char *
+e_mixer_theme_path(void)
+{
+#define TF "/e-module-mixer.edj"
+ int dirlen;
+
+ dirlen = strlen(mixer_mod->dir);
+ if (dirlen >= sizeof(tmpbuf) - sizeof(TF))
+ return NULL;
+
+ memcpy(tmpbuf, mixer_mod->dir, dirlen);
+ memcpy(tmpbuf + dirlen, TF, sizeof(TF));
+
+ return tmpbuf;
+#undef TF
+}
+
+static int
+_mixer_gadget_configuration_defaults(E_Mixer_Gadget_Config *conf)
+{
+ E_Mixer_System *sys;
+ char *card, *channel;
+
+ card = e_mixer_system_get_default_card();
+ if (!card)
+ return 0;
+
+ sys = e_mixer_system_new(card);
+ if (!sys)
+ {
+ free(card);
+ return 0;
+ }
+
+ channel = e_mixer_system_get_default_channel_name(sys);
+ e_mixer_system_del(sys);
+
+ if (!channel)
+ {
+ free(card);
+ return 0;
+ }
+
+ conf->card = evas_stringshare_add(card);
+ conf->channel_name = evas_stringshare_add(channel);
+ conf->lock_sliders = 1;
+ conf->show_locked = 0;
+
+ free(card);
+ free(channel);
+
+ return 1;
+}
+
+static E_Mixer_Gadget_Config *
+_mixer_gadget_configuration_new(E_Mixer_Module_Config *mod_conf, const char *id)
+{
+ E_Mixer_Gadget_Config *conf;
+
+ conf = E_NEW(E_Mixer_Gadget_Config, 1);
+ if (!conf)
+ return NULL;
+
+ if (!_mixer_gadget_configuration_defaults(conf))
+ {
+ E_FREE(conf);
+ return NULL;
+ }
+
+ conf->id = evas_stringshare_add(id);
+ mod_conf->gadgets = evas_hash_direct_add(mod_conf->gadgets, conf->id, conf);
+
+ return conf;
+}
+
+static inline void
+_mixer_gadget_configuration_free_int(E_Mixer_Gadget_Config *conf)
+{
+ if (conf->dialog)
+ e_object_del(E_OBJECT(conf->dialog));
+
+ if (conf->card)
+ evas_stringshare_del(conf->card);
+ if (conf->channel_name)
+ evas_stringshare_del(conf->channel_name);
+
+ evas_stringshare_del(conf->id);
+ free(conf);
+}
+
+static void
+_mixer_gadget_configuration_free(E_Mixer_Module_Config *mod_conf, E_Mixer_Gadget_Config *conf)
+{
+ if (!mod_conf)
+ return;
+ if (!conf)
+ return;
+ mod_conf->gadgets = evas_hash_del(mod_conf->gadgets, conf->id, conf);
+ _mixer_gadget_configuration_free_int(conf);
+}
+
+static Evas_Bool
+_mixer_gadget_configuration_free_foreach(const Evas_Hash *hash, const char *key, void *hdata, void *fdata)
+{
+ _mixer_gadget_configuration_free_int(hdata);
+ return 1;
+}
+
+static int
+_mixer_module_configuration_alert(void *data)
+{
+ e_util_dialog_show(D_("Mixer Configuration Updated"), data);
+ return 0;
+}
+
+static E_Mixer_Module_Config *
+_mixer_module_configuration_new(void)
+{
+ E_Mixer_Module_Config *conf;
+
+ conf = E_NEW(E_Mixer_Module_Config, 1);
+ if (!conf)
+ return NULL;
+
+ conf->version = MOD_CONF_VERSION;
+
+ return conf;
+}
+
+static void
+_mixer_module_configuration_free(E_Mixer_Module_Config *conf)
+{
+ if (!conf)
+ return;
+
+ evas_hash_foreach(conf->gadgets,
+ _mixer_gadget_configuration_free_foreach, NULL);
+ evas_hash_free(conf->gadgets);
+ free(conf);
+}
+
+static void
+_mixer_popup_update(E_Mixer_Instance *inst)
+{
+ E_Mixer_Channel_State *state;
+
+ state = &inst->mixer_state;
+
+ if (inst->ui.left)
+ e_slider_value_set(inst->ui.left, state->left);
+ if (inst->ui.right)
+ e_slider_value_set(inst->ui.right, state->right);
+ if (inst->ui.mute)
+ e_widget_check_checked_set(inst->ui.mute, state->mute);
+}
+
+static void
+_mixer_gadget_update(E_Mixer_Instance *inst)
+{
+ Edje_Message_Int_Set *msg;
+
+ if (!inst)
+ return;
+
+ e_mixer_system_get_state(inst->sys, inst->channel, &inst->mixer_state);
+
+ msg = alloca(sizeof(Edje_Message_Int_Set) + 2 * sizeof(int));
+ msg->count = 3;
+ msg->val[0] = inst->mixer_state.mute;
+ msg->val[1] = inst->mixer_state.left;
+ msg->val[2] = inst->mixer_state.right;
+ edje_object_message_send(inst->ui.gadget, EDJE_MESSAGE_INT_SET, 0, msg);
+
+ edje_object_signal_emit(inst->ui.gadget, "e,action,volume,change", "");
+
+ if (inst->popup)
+ _mixer_popup_update(inst);
+}
+
+static void
+_mixer_volume_increase(E_Mixer_Instance *inst)
+{
+ E_Mixer_Channel_State *state;
+
+ state = &inst->mixer_state;
+ e_mixer_system_get_volume(inst->sys, inst->channel,
+ &state->left, &state->right);
+ if (state->left >= 0)
+ {
+ if (state->left < 95)
+ state->left += 5;
+ else
+ state->left = 100;
+ }
+
+ if (state->right >= 0)
+ {
+ if (state->right < 95)
+ state->right += 5;
+ else
+ state->right = 100;
+ }
+
+ e_mixer_system_set_volume(inst->sys, inst->channel,
+ state->left, state->right);
+ _mixer_gadget_update(inst);
+}
+
+static void
+_mixer_volume_decrease(E_Mixer_Instance *inst)
+{
+ E_Mixer_Channel_State *state;
+
+ state = &inst->mixer_state;
+ e_mixer_system_get_volume(inst->sys, inst->channel,
+ &state->left, &state->right);
+ if (state->left >= 0)
+ {
+ if (state->left > 5)
+ state->left -= 5;
+ else
+ state->left = 0;
+ }
+ if (state->right >= 0)
+ {
+ if (state->right > 5)
+ state->right -= 5;
+ else
+ state->right = 0;
+ }
+
+ e_mixer_system_set_volume(inst->sys, inst->channel,
+ state->left, state->right);
+ _mixer_gadget_update(inst);
+}
+
+static void
+_mixer_toggle_mute(E_Mixer_Instance *inst)
+{
+ E_Mixer_Channel_State *state;
+
+ if (!e_mixer_system_can_mute(inst->sys, inst->channel))
+ return;
+
+ state = &inst->mixer_state;
+ e_mixer_system_get_mute(inst->sys, inst->channel, &state->mute);
+ state->mute = !state->mute;
+ e_mixer_system_set_mute(inst->sys, inst->channel, state->mute);
+ _mixer_gadget_update(inst);
+}
+
+static void
+_mixer_popup_cb_volume_left_change(void *data, Evas_Object *obj, void *event)
+{
+ E_Mixer_Instance *inst;
+ E_Mixer_Channel_State *state;
+
+ inst = data;
+ if (!inst)
+ return;
+
+ state = &inst->mixer_state;
+ e_mixer_system_get_volume(inst->sys, inst->channel,
+ &state->left, &state->right);
+
+ state->left = (int)e_slider_value_get(obj);
+ if (inst->conf->lock_sliders)
+ {
+ state->right = state->left;
+ e_slider_value_set(inst->ui.right, state->right);
+ }
+
+ e_mixer_system_set_volume(inst->sys, inst->channel,
+ state->left, state->right);
+ _mixer_gadget_update(inst);
+}
+
+static void
+_mixer_popup_cb_volume_right_change(void *data, Evas_Object *obj, void *event)
+{
+ E_Mixer_Instance *inst;
+ E_Mixer_Channel_State *state;
+
+ inst = data;
+ if (!inst)
+ return;
+
+ state = &inst->mixer_state;
+ e_mixer_system_get_volume(inst->sys, inst->channel,
+ &state->left, &state->right);
+
+ state->right = (int)e_slider_value_get(obj);
+ if (inst->conf->lock_sliders)
+ {
+ state->left = state->right;
+ e_slider_value_set(inst->ui.left, state->left);
+ }
+
+ e_mixer_system_set_volume(inst->sys, inst->channel,
+ state->left, state->right);
+ _mixer_gadget_update(inst);
+}
+
+static void
+_mixer_popup_cb_mute_change(void *data, Evas_Object *obj, void *event)
+{
+ E_Mixer_Instance *inst;
+ E_Mixer_Channel_State *state;
+
+ inst = data;
+ if (!inst)
+ return;
+
+ state = &inst->mixer_state;
+ state->mute = e_widget_check_checked_get(obj);
+ e_mixer_system_set_mute(inst->sys, inst->channel, state->mute);
+
+ _mixer_gadget_update(inst);
+}
+
+static void
+_mixer_popup_cb_resize(Evas_Object *obj, int *w, int *h)
+{
+ int mw, mh;
+
+ e_widget_min_size_get(obj, &mw, &mh);
+ if (mh < 200) mh = 200;
+ if (mw < 60) mw = 60;
+ if (*w) *w = (mw + 8);
+ if (*h) *h = (mh + 8);
+}
+
+static Evas_Object *
+_mixer_popup_add_slider(E_Mixer_Instance *inst, int value, void (*cb) (void *data, Evas_Object *obj, void *event_info))
+{
+ Evas_Object *slider;
+
+ slider = e_slider_add(inst->popup->win->evas);
+ evas_object_show(slider);
+ e_slider_orientation_set(slider, 0);
+ e_slider_value_set(slider, value);
+ e_slider_value_range_set(slider, 0.0, 100.0);
+ e_slider_value_format_display_set(slider, NULL);
+ evas_object_smart_callback_add(slider, "changed", cb, inst);
+
+ return slider;
+}
+
+static void
+_mixer_app_cb_del(E_Dialog *dialog, void *data)
+{
+ E_Mixer_Module_Context *ctxt = data;
+ ctxt->mixer_dialog = NULL;
+}
+
+static void _mixer_popup_del(E_Mixer_Instance *inst);
+
+static int
+_mixer_popup_input_window_mouse_up_cb(void *data, int type, void *event)
+{
+ Ecore_X_Event_Mouse_Button_Up *ev = event;
+ E_Mixer_Instance *inst = data;
+
+ if (ev->win != inst->ui.input.win)
+ return 1;
+
+ _mixer_popup_del(inst);
+
+ return 1;
+}
+
+static int
+_mixer_popup_input_window_key_down_cb(void *data, int type, void *event)
+{
+ Ecore_X_Event_Key_Down *ev = event;
+ E_Mixer_Instance *inst = data;
+ const char *keysym;
+
+ if (ev->win != inst->ui.input.win)
+ return 1;
+
+ keysym = ev->keysymbol;
+ if (strcmp(keysym, "Escape") == 0)
+ _mixer_popup_del(inst);
+ else if (strcmp(keysym, "Up") == 0)
+ _mixer_volume_increase(inst);
+ else if (strcmp(keysym, "Down") == 0)
+ _mixer_volume_decrease(inst);
+ else if ((strcmp(keysym, "Return") == 0) ||
+ (strcmp(keysym, "KP_Enter") == 0))
+ _mixer_toggle_mute(inst);
+ else
+ _mixer_popup_del(inst); /* XXX really? */
+
+ return 1;
+}
+
+static void
+_mixer_popup_input_window_destroy(E_Mixer_Instance *inst)
+{
+ ecore_x_window_del(inst->ui.input.win);
+ inst->ui.input.win = 0;
+
+ ecore_event_handler_del(inst->ui.input.mouse_up);
+ inst->ui.input.mouse_up = NULL;
+
+ ecore_event_handler_del(inst->ui.input.key_down);
+ inst->ui.input.key_down = NULL;
+}
+
+static void
+_mixer_popup_input_window_create(E_Mixer_Instance *inst)
+{
+ Ecore_X_Window_Configure_Mask mask;
+ Ecore_X_Window w, popup_w;
+ E_Manager *man;
+
+ man = e_manager_current_get();
+
+ w = ecore_x_window_input_new(man->root, 0, 0, man->w, man->h);
+ mask = (ECORE_X_WINDOW_CONFIGURE_MASK_STACK_MODE |
+ ECORE_X_WINDOW_CONFIGURE_MASK_SIBLING);
+ popup_w = inst->popup->win->evas_win;
+ ecore_x_window_configure(w, mask, 0, 0, 0, 0, 0, popup_w,
+ ECORE_X_WINDOW_STACK_BELOW);
+ ecore_x_window_show(w);
+
+ inst->ui.input.mouse_up =
+ ecore_event_handler_add(ECORE_X_EVENT_MOUSE_BUTTON_UP,
+ _mixer_popup_input_window_mouse_up_cb, inst);
+
+ inst->ui.input.key_down =
+ ecore_event_handler_add(ECORE_X_EVENT_KEY_DOWN,
+ _mixer_popup_input_window_key_down_cb, inst);
+
+ inst->ui.input.win = w;
+}
+
+static void
+_mixer_popup_del(E_Mixer_Instance *inst)
+{
+ _mixer_popup_input_window_destroy(inst);
+ e_object_del(E_OBJECT(inst->popup));
+ inst->ui.label = NULL;
+ inst->ui.left = NULL;
+ inst->ui.right = NULL;
+ inst->ui.mute = NULL;
+ inst->ui.table = NULL;
+ inst->ui.button = NULL;
+ inst->popup = NULL;
+}
+
+static void
+_mixer_app_select_current(E_Dialog *dialog, E_Mixer_Instance *inst)
+{
+ E_Mixer_Gadget_Config *conf = inst->conf;
+
+ e_mixer_app_dialog_select(dialog, conf->card, conf->channel_name);
+}
+
+
+static void
+_mixer_popup_cb_mixer(void *data, void *data2)
+{
+ E_Mixer_Instance *inst = data;
+ E_Mixer_Module_Context *ctxt;
+ E_Container *con;
+
+ _mixer_popup_del(inst);
+
+ ctxt = mixer_mod->data;
+ if (ctxt->mixer_dialog)
+ {
+ _mixer_app_select_current(ctxt->mixer_dialog, inst);
+ e_dialog_show(ctxt->mixer_dialog);
+ return;
+ }
+
+ con = e_container_current_get(e_manager_current_get());
+ ctxt->mixer_dialog = e_mixer_app_dialog_new(
+ con, _mixer_app_cb_del, ctxt);
+
+ _mixer_app_select_current(ctxt->mixer_dialog, inst);
+}
+
+static void
+_mixer_popup_new(E_Mixer_Instance *inst)
+{
+ E_Mixer_Channel_State *state;
+ Evas *evas;
+ int colspan;
+
+ if (inst->conf->dialog)
+ return;
+
+ state = &inst->mixer_state;
+ e_mixer_system_get_state(inst->sys, inst->channel, state);
+
+ if ((state->right >= 0) &&
+ (inst->conf->show_locked || (!inst->conf->lock_sliders)))
+ colspan = 2;
+ else
+ colspan = 1;
+
+ inst->popup = e_gadcon_popup_new(inst->gcc, _mixer_popup_cb_resize);
+ evas = inst->popup->win->evas;
+
+ inst->ui.table = e_widget_table_add(evas, 0);
+
+ inst->ui.label = e_widget_label_add(evas, inst->conf->channel_name);
+ e_widget_table_object_append(inst->ui.table, inst->ui.label,
+ 0, 0, colspan, 1, 0, 0, 0, 0);
+
+ if (state->left >= 0)
+ {
+ inst->ui.left = _mixer_popup_add_slider(
+ inst, state->left, _mixer_popup_cb_volume_left_change);
+ e_widget_table_object_append(inst->ui.table, inst->ui.left,
+ 0, 1, 1, 1, 1, 1, 1, 1);
+ }
+ else
+ inst->ui.left = NULL;
+
+ if ((state->right >= 0) &&
+ (inst->conf->show_locked || (!inst->conf->lock_sliders)))
+ {
+ inst->ui.right = _mixer_popup_add_slider(
+ inst, state->right, _mixer_popup_cb_volume_right_change);
+ e_widget_table_object_append(inst->ui.table, inst->ui.right,
+ 1, 1, 1, 1, 1, 1, 1, 1);
+ }
+ else
+ inst->ui.right = NULL;
+
+ if (e_mixer_system_can_mute(inst->sys, inst->channel))
+ {
+ inst->ui.mute = e_widget_check_add(evas, D_("Mute"), &state->mute);
+ evas_object_show(inst->ui.mute);
+ e_widget_table_object_append(inst->ui.table, inst->ui.mute,
+ 0, 2, colspan, 1, 1, 1, 1, 0);
+ evas_object_smart_callback_add(inst->ui.mute, "changed",
+ _mixer_popup_cb_mute_change, inst);
+ }
+ else
+ inst->ui.mute = NULL;
+
+ inst->ui.button = e_widget_button_add(evas, D_(_Name), NULL,
+ _mixer_popup_cb_mixer, inst, NULL);
+ e_widget_table_object_append(inst->ui.table, inst->ui.button,
+ 0, 7, colspan, 1, 1, 1, 1, 0);
+
+ e_gadcon_popup_content_set(inst->popup, inst->ui.table);
+ e_gadcon_popup_show(inst->popup);
+ _mixer_popup_input_window_create(inst);
+}
+
+static void
+_mixer_menu_cb_post(void *data, E_Menu *menu)
+{
+ E_Mixer_Instance *inst;
+
+ inst = data;
+ if ((!inst) || (!inst->menu))
+ return;
+ if (inst->menu)
+ {
+ e_object_del(E_OBJECT(inst->menu));
+ inst->menu = NULL;
+ }
+}
+
+static void
+_mixer_menu_cb_cfg(void *data, E_Menu *menu, E_Menu_Item *mi)
+{
+ E_Mixer_Instance *inst;
+ E_Container *con;
+
+ inst = data;
+ if (!inst)
+ return;
+ if (inst->popup)
+ _mixer_popup_del(inst);
+ con = e_container_current_get(e_manager_current_get());
+ inst->conf->dialog = e_mixer_config_dialog_new(con, inst->conf);
+}
+
+static void
+_mixer_menu_new(E_Mixer_Instance *inst, Evas_Event_Mouse_Down *ev)
+{
+ E_Zone *zone;
+ E_Menu *mn;
+ E_Menu_Item *mi;
+ int x, y;
+
+ zone = e_util_zone_current_get(e_manager_current_get());
+
+ mn = e_menu_new();
+ e_menu_post_deactivate_callback_set(mn, _mixer_menu_cb_post, inst);
+ inst->menu = mn;
+
+ mi = e_menu_item_new(mn);
+ e_menu_item_label_set(mi, D_("Configuration"));
+ e_util_menu_item_edje_icon_set(mi, "enlightenment/configuration");
+ e_menu_item_callback_set(mi, _mixer_menu_cb_cfg, inst);
+
+ e_gadcon_client_util_menu_items_append(inst->gcc, mn, 0);
+ e_gadcon_canvas_zone_geometry_get(inst->gcc->gadcon, &x, &y, NULL, NULL);
+ e_menu_activate_mouse(mn, 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
+_mixer_cb_mouse_down(void *data, Evas *evas, Evas_Object *obj, void *event)
+{
+ E_Mixer_Instance *inst;
+ Evas_Event_Mouse_Down *ev;
+
+ inst = data;
+ if (!inst)
+ return;
+
+ ev = event;
+ if (ev->button == 1)
+ {
+ if (!inst->popup)
+ _mixer_popup_new(inst);
+ else
+ _mixer_popup_del(inst);
+ }
+ else if (ev->button == 2)
+ _mixer_toggle_mute(inst);
+ else if ((ev->button == 3) && (!inst->menu))
+ _mixer_menu_new(inst, ev);
+}
+
+static void
+_mixer_cb_mouse_wheel(void *data, Evas *evas, Evas_Object *obj, void *event)
+{
+ E_Mixer_Instance *inst;
+ Evas_Event_Mouse_Wheel *ev;
+
+ inst = data;
+ if (!inst)
+ return;
+
+ ev = event;
+ if (ev->direction == 0)
+ {
+ if (ev->z > 0)
+ _mixer_volume_decrease(inst);
+ else if (ev->z < 0)
+ _mixer_volume_increase(inst);
+ }
+}
+
+static int
+_mixer_sys_setup(E_Mixer_Instance *inst)
+{
+ E_Mixer_Gadget_Config *conf;
+
+ conf = inst->conf;
+
+ if (inst->sys)
+ e_mixer_system_del(inst->sys);
+
+ inst->sys = e_mixer_system_new(conf->card);
+ if (!inst->sys)
+ {
+ inst->channel = NULL;
+ return 0;
+ }
+
+ inst->channel = e_mixer_system_get_channel_by_name(inst->sys,
+ conf->channel_name);
+ return inst->channel != NULL;
+}
+
+static int
+_mixer_system_cb_update(void *data, E_Mixer_System *sys)
+{
+ E_Mixer_Instance *inst;
+
+ inst = data;
+ e_mixer_system_get_state(inst->sys, inst->channel, &inst->mixer_state);
+ _mixer_gadget_update(inst);
+
+ return 1;
+}
+
+int
+e_mixer_update(E_Mixer_Instance *inst)
+{
+ int r;
+
+ e_modapi_save(mixer_mod);
+ if ((!inst) || (!inst->conf))
+ return 0;
+
+ r = _mixer_sys_setup(inst);
+ if (r)
+ e_mixer_system_callback_set(inst->sys, _mixer_system_cb_update, inst);
+
+ return r;
+}
+
+static int
+_mixer_sys_setup_default_card(E_Mixer_Instance *inst)
+{
+ E_Mixer_Gadget_Config *conf;
+ char *card;
+
+ conf = inst->conf;
+ if (conf->card)
+ evas_stringshare_del(conf->card);
+
+ card = e_mixer_system_get_default_card();
+ if (!card)
+ goto error;
+
+ inst->sys = e_mixer_system_new(card);
+ if (!inst->sys)
+ goto system_error;
+
+ conf->card = evas_stringshare_add(card);
+ free(card);
+ return 1;
+
+
+ system_error:
+ free(card);
+ error:
+ conf->card = NULL;
+ return 0;
+}
+
+static int
+_mixer_sys_setup_default_channel(E_Mixer_Instance *inst)
+{
+ E_Mixer_Gadget_Config *conf;
+ char *channel_name;
+
+ conf = inst->conf;
+ if (conf->channel_name)
+ evas_stringshare_del(conf->channel_name);
+
+ channel_name = e_mixer_system_get_default_channel_name(inst->sys);
+ if (!channel_name)
+ goto error;
+
+ inst->channel = e_mixer_system_get_channel_by_name(inst->sys, channel_name);
+ if (!inst->channel)
+ goto system_error;
+
+ conf->channel_name = evas_stringshare_add(channel_name);
+ free(channel_name);
+ return 1;
+
+ system_error:
+ free(channel_name);
+ error:
+ conf->channel_name = NULL;
+ return 0;
+}
+
+static int
+_mixer_sys_setup_defaults(E_Mixer_Instance *inst)
+{
+ if ((!inst->sys) && (!_mixer_sys_setup_default_card(inst)))
+ return 0;
+
+ return _mixer_sys_setup_default_channel(inst);
+}
+
+/* Gadcon Api Functions */
+static void _mixer_module_configuration_setup(E_Mixer_Module_Context *ctxt);
+
+static E_Gadcon_Client *
+_gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style)
+{
+ E_Mixer_Instance *inst;
+ E_Mixer_Module_Context *ctxt;
+ E_Mixer_Gadget_Config *conf;
+
+ if (!mixer_mod)
+ return NULL;
+
+ ctxt = mixer_mod->data;
+ if (!ctxt->conf)
+ {
+ _mixer_module_configuration_setup(ctxt);
+ if (!ctxt->conf)
+ return NULL;
+ }
+
+ conf = evas_hash_find(ctxt->conf->gadgets, id);
+ if (!conf)
+ {
+ conf = _mixer_gadget_configuration_new(ctxt->conf, id);
+ if (!conf)
+ return NULL;
+ }
+
+ inst = E_NEW(E_Mixer_Instance, 1);
+ inst->conf = conf;
+ conf->instance = inst;
+ if ((!_mixer_sys_setup(inst)) && (!_mixer_sys_setup_defaults(inst)))
+ {
+ if (inst->sys)
+ e_mixer_system_del(inst->sys);
+ _mixer_gadget_configuration_free(ctxt->conf, conf);
+ E_FREE(inst);
+ return NULL;
+ }
+ e_mixer_system_callback_set(inst->sys, _mixer_system_cb_update, inst);
+
+ inst->ui.gadget = edje_object_add(gc->evas);
+ if (!e_theme_edje_object_set(inst->ui.gadget, "base/theme/modules/mixer",
+ "e/modules/mixer/main"))
+ edje_object_file_set(inst->ui.gadget, e_mixer_theme_path(),
+ "e/modules/mixer/main");
+
+ inst->gcc = e_gadcon_client_new(gc, name, id, style, inst->ui.gadget);
+ inst->gcc->data = inst;
+
+ evas_object_event_callback_add(inst->ui.gadget, EVAS_CALLBACK_MOUSE_DOWN,
+ _mixer_cb_mouse_down, inst);
+ evas_object_event_callback_add(inst->ui.gadget, EVAS_CALLBACK_MOUSE_WHEEL,
+ _mixer_cb_mouse_wheel, inst);
+
+ e_mixer_system_get_state(inst->sys, inst->channel, &inst->mixer_state);
+ _mixer_gadget_update(inst);
+
+ if (!ctxt->conf->default_gc_id)
+ {
+ ctxt->conf->default_gc_id = evas_stringshare_add(id);
+ ctxt->default_instance = inst;
+ }
+ else if ((!ctxt->default_instance) ||
+ (strcmp(id, ctxt->conf->default_gc_id) == 0))
+ ctxt->default_instance = inst;
+
+ ctxt->instances = evas_list_append(ctxt->instances, inst);
+
+ return inst->gcc;
+}
+
+static void
+_gc_shutdown(E_Gadcon_Client *gcc)
+{
+ E_Mixer_Module_Context *ctxt;
+ E_Mixer_Instance *inst;
+
+ if (!mixer_mod)
+ return;
+
+ ctxt = mixer_mod->data;
+ if (!ctxt)
+ return;
+
+ inst = gcc->data;
+ if (!inst)
+ return;
+
+ if (inst->menu)
+ {
+ e_menu_post_deactivate_callback_set(inst->menu, NULL, NULL);
+ e_object_del(E_OBJECT(inst->menu));
+ }
+ evas_object_del(inst->ui.gadget);
+ e_mixer_system_channel_del(inst->channel);
+ e_mixer_system_del(inst->sys);
+
+ inst->conf->instance = NULL;
+ ctxt->instances = evas_list_remove(ctxt->instances, inst);
+
+ E_FREE(inst);
+}
+
+static void
+_gc_orient(E_Gadcon_Client *gcc)
+{
+ e_gadcon_client_aspect_set(gcc, 16, 16);
+ e_gadcon_client_min_size_set(gcc, 16, 16);
+}
+
+static char *
+_gc_label(void)
+{
+ return D_(_Name);
+}
+
+static Evas_Object *
+_gc_icon(Evas *evas)
+{
+ Evas_Object *o;
+
+ o = edje_object_add(evas);
+ edje_object_file_set(o, e_mixer_theme_path(), "icon");
+ return o;
+}
+
+static const char *
+_gc_id_new(void)
+{
+ E_Mixer_Module_Context *ctxt;
+ Evas_List *instances;
+
+ if (!mixer_mod)
+ return NULL;
+
+ ctxt = mixer_mod->data;
+ if (!ctxt)
+ return NULL;
+
+ instances = ctxt->instances;
+ snprintf(tmpbuf, sizeof(tmpbuf), "mixer.%d", evas_list_count(instances));
+ return tmpbuf;
+}
+
+static const E_Gadcon_Client_Class _gc_class =
+{
+ GADCON_CLIENT_CLASS_VERSION, _name,
+ {_gc_init, _gc_shutdown, _gc_orient, _gc_label, _gc_icon, _gc_id_new, NULL},
+ E_GADCON_CLIENT_STYLE_PLAIN
+};
+
+
+
+EAPI E_Module_Api e_modapi = {E_MODULE_API_VERSION, _Name};
+
+static void
+_mixer_cb_volume_increase(E_Object *obj, const char *params)
+{
+ E_Mixer_Module_Context *ctxt;
+
+ if (!mixer_mod)
+ return;
+
+ ctxt = mixer_mod->data;
+ if (!ctxt->conf)
+ return;
+
+ if (ctxt->default_instance)
+ _mixer_volume_increase(ctxt->default_instance);
+}
+
+static void
+_mixer_cb_volume_decrease(E_Object *obj, const char *params)
+{
+ E_Mixer_Module_Context *ctxt;
+
+ if (!mixer_mod)
+ return;
+
+ ctxt = mixer_mod->data;
+ if (!ctxt->conf)
+ return;
+
+ if (ctxt->default_instance)
+ _mixer_volume_decrease(ctxt->default_instance);
+}
+
+static void
+_mixer_cb_volume_mute(E_Object *obj, const char *params)
+{
+ E_Mixer_Module_Context *ctxt;
+
+ if (!mixer_mod)
+ return;
+
+ ctxt = mixer_mod->data;
+ if (!ctxt->conf)
+ return;
+
+ if (ctxt->default_instance)
+ _mixer_toggle_mute(ctxt->default_instance);
+}
+
+static E_Config_Dialog *
+_mixer_module_config(E_Container *con, const char *params __UNUSED__)
+{
+ E_Mixer_Module_Context *ctxt;
+
+ if (!mixer_mod)
+ return NULL;
+
+ ctxt = mixer_mod->data;
+ if (!ctxt)
+ return NULL;
+
+ if (ctxt->conf_dialog)
+ return NULL;
+
+ if (!ctxt->conf)
+ {
+ _mixer_module_configuration_setup(ctxt);
+ if (!ctxt->conf)
+ return NULL;
+ }
+
+ ctxt->conf_dialog = e_mixer_config_module_dialog_new(con, ctxt);
+ return ctxt->conf_dialog;
+}
+
+static const char _reg_cat[] = "extensions";
+static const char _reg_item[] = "extensions/e";
+
+static void
+_mixer_configure_registry_register(void)
+{
+ e_configure_registry_category_add(_reg_cat, 90, D_("Extensions"), NULL,
+ "enlightenment/extensions");
+ e_configure_registry_item_add(_reg_item, 30, D_(_Name), NULL,
+ "enlightenment/e",
+ _mixer_module_config);
+}
+
+static void
+_mixer_configure_registry_unregister(void)
+{
+ e_configure_registry_item_del(_reg_item);
+ e_configure_registry_category_del(_reg_cat);
+}
+
+static E_Config_DD *
+_mixer_module_configuration_descriptor_new(E_Config_DD *gadget_conf_edd)
+{
+ E_Config_DD *conf_edd;
+
+ conf_edd = E_CONFIG_DD_NEW("Mixer_Module_Config", E_Mixer_Module_Config);
+ if (!conf_edd)
+ return NULL;
+ E_CONFIG_VAL(conf_edd, E_Mixer_Module_Config, version, INT);
+ E_CONFIG_VAL(conf_edd, E_Mixer_Module_Config, default_gc_id, STR);
+ E_CONFIG_HASH(conf_edd, E_Mixer_Module_Config, gadgets, gadget_conf_edd);
+
+ return conf_edd;
+}
+
+static inline void
+_mixer_module_configuration_descriptor_free(E_Config_DD *conf_edd)
+{
+ if (!conf_edd)
+ return;
+ E_CONFIG_DD_FREE(conf_edd);
+}
+
+static E_Config_DD *
+_mixer_gadget_configuration_descriptor_new(void)
+{
+ E_Config_DD *conf_edd;
+
+ conf_edd = E_CONFIG_DD_NEW("Mixer_Gadget_Config", E_Mixer_Gadget_Config);
+ if (!conf_edd)
+ return NULL;
+ E_CONFIG_VAL(conf_edd, E_Mixer_Gadget_Config, lock_sliders, INT);
+ E_CONFIG_VAL(conf_edd, E_Mixer_Gadget_Config, show_locked, INT);
+ E_CONFIG_VAL(conf_edd, E_Mixer_Gadget_Config, card, STR);
+ E_CONFIG_VAL(conf_edd, E_Mixer_Gadget_Config, channel_name, STR);
+
+ return conf_edd;
+}
+
+static inline void
+_mixer_gadget_configuration_descriptor_free(E_Config_DD *conf_edd)
+{
+ if (!conf_edd)
+ return;
+ E_CONFIG_DD_FREE(conf_edd);
+}
+
+static E_Mixer_Module_Config *
+_mixer_module_configuration_load(E_Config_DD *module_conf_edd)
+{
+ E_Mixer_Module_Config *conf;
+
+ conf = e_config_domain_load(_conf_domain, module_conf_edd);
+ if (!conf)
+ return _mixer_module_configuration_new();
+
+ if (conf->version != MOD_CONF_VERSION)
+ {
+ _mixer_module_configuration_free(conf);
+ conf = _mixer_module_configuration_new();
+ if (!conf)
+ return NULL;
+
+ ecore_timer_add(1.0, _mixer_module_configuration_alert,
+ D_("Mixer Module Configuration data changed.
"
+ "Your old configuration has been replaced with "
+ "new default.
Sorry for the inconvenience."));
+ return conf;
+ }
+
+ return conf;
+}
+
+static void
+_mixer_module_configuration_setup(E_Mixer_Module_Context *ctxt)
+{
+ E_Config_DD *module_edd, *gadget_edd;
+
+ gadget_edd = _mixer_gadget_configuration_descriptor_new();
+ module_edd = _mixer_module_configuration_descriptor_new(gadget_edd);
+ ctxt->gadget_conf_edd = gadget_edd;
+ ctxt->module_conf_edd = module_edd;
+ ctxt->conf = _mixer_module_configuration_load(module_edd);
+}
+
+static const char _act_increase[] = "volume_increase";
+static const char _act_decrease[] = "volume_decrease";
+static const char _act_mute[] = "volume_mute";
+static const char _lbl_increase[] = "Increase Volume";
+static const char _lbl_decrease[] = "Decrease Volume";
+static const char _lbl_mute[] = "Mute Volume";
+
+static void
+_mixer_actions_register(E_Mixer_Module_Context *ctxt)
+{
+ ctxt->actions.incr = e_action_add(_act_increase);
+ if (ctxt->actions.incr)
+ {
+ ctxt->actions.incr->func.go = _mixer_cb_volume_increase;
+ e_action_predef_name_set(D_(_Name), D_(_lbl_increase), _act_increase,
+ NULL, NULL, 0);
+ }
+
+ ctxt->actions.decr = e_action_add(_act_decrease);
+ if (ctxt->actions.decr)
+ {
+ ctxt->actions.decr->func.go = _mixer_cb_volume_decrease;
+ e_action_predef_name_set(D_(_Name), D_(_lbl_decrease), _act_decrease,
+ NULL, NULL, 0);
+ }
+
+ ctxt->actions.mute = e_action_add(_act_mute);
+ if (ctxt->actions.mute)
+ {
+ ctxt->actions.mute->func.go = _mixer_cb_volume_mute;
+ e_action_predef_name_set(D_(_Name), D_(_lbl_mute), _act_mute,
+ NULL, NULL, 0);
+ }
+}
+
+static void
+_mixer_actions_unregister(E_Mixer_Module_Context *ctxt)
+{
+ if (ctxt->actions.incr)
+ {
+ e_action_predef_name_del(D_(_Name), D_(_lbl_increase));
+ e_action_del(_act_increase);
+ }
+
+ if (ctxt->actions.decr)
+ {
+ e_action_predef_name_del(D_(_Name), D_(_lbl_decrease));
+ e_action_del(_act_decrease);
+ }
+
+ if (ctxt->actions.mute)
+ {
+ e_action_predef_name_del(D_(_Name), D_(_lbl_mute));
+ e_action_del(_act_mute);
+ }
+}
+
+EAPI void *
+e_modapi_init(E_Module *m)
+{
+ E_Mixer_Module_Context *ctxt;
+
+ ctxt = E_NEW(E_Mixer_Module_Context, 1);
+ if (!ctxt)
+ return NULL;
+
+ _mixer_configure_registry_register();
+ _mixer_actions_register(ctxt);
+ e_gadcon_provider_register(&_gc_class);
+ mixer_mod = m;
+ return ctxt;
+}
+
+static void
+_mixer_instances_free(E_Mixer_Module_Context *ctxt)
+{
+ while (ctxt->instances)
+ {
+ E_Mixer_Instance *inst;
+
+ inst = ctxt->instances->data;
+ e_object_del(E_OBJECT(inst->gcc));
+ }
+}
+
+EAPI int
+e_modapi_shutdown(E_Module *m)
+{
+ E_Mixer_Module_Context *ctxt;
+
+ ctxt = m->data;
+ if (!ctxt)
+ return 0;
+
+ _mixer_instances_free(ctxt);
+
+ if (ctxt->conf_dialog)
+ e_object_del(E_OBJECT(ctxt->conf_dialog));
+
+ if (ctxt->mixer_dialog)
+ e_object_del(E_OBJECT(ctxt->mixer_dialog));
+
+ _mixer_configure_registry_unregister();
+ _mixer_actions_unregister(ctxt);
+ e_gadcon_provider_unregister(&_gc_class);
+
+ if (ctxt->conf)
+ {
+ _mixer_module_configuration_free(ctxt->conf);
+ _mixer_gadget_configuration_descriptor_free(ctxt->gadget_conf_edd);
+ _mixer_module_configuration_descriptor_free(ctxt->module_conf_edd);
+ }
+
+ E_FREE(ctxt);
+ mixer_mod = NULL;
+ return 1;
+}
+
+EAPI int
+e_modapi_save(E_Module *m)
+{
+ E_Mixer_Module_Context *ctxt;
+
+ ctxt = m->data;
+ if (!ctxt)
+ return 0;
+ if (!ctxt->conf)
+ return 1;
+
+ return e_config_domain_save(_conf_domain, ctxt->module_conf_edd, ctxt->conf);
+}
diff --git a/src/modules/mixer/e_mod_main.h b/src/modules/mixer/e_mod_main.h
new file mode 100644
index 000000000..5bc2f022c
--- /dev/null
+++ b/src/modules/mixer/e_mod_main.h
@@ -0,0 +1,89 @@
+#define D_(str) dgettext(PACKAGE, str)
+
+#ifndef E_MOD_MAIN_H
+#define E_MOD_MAIN_H
+
+#include "config.h"
+#include "e_mod_system.h"
+#include
+
+#define MOD_CONF_VERSION 3
+
+typedef struct E_Mixer_Gadget_Config
+{
+ int lock_sliders;
+ int show_locked;
+ const char *card;
+ const char *channel_name;
+ const char *id;
+ E_Config_Dialog *dialog;
+ struct E_Mixer_Instance *instance;
+} E_Mixer_Gadget_Config;
+
+typedef struct E_Mixer_Module_Config
+{
+ int version;
+ const char *default_gc_id;
+ Evas_Hash *gadgets;
+} E_Mixer_Module_Config;
+
+typedef struct E_Mixer_Instance
+{
+ E_Gadcon_Client *gcc;
+ E_Gadcon_Popup *popup;
+ E_Menu *menu;
+
+ struct
+ {
+ Evas_Object *gadget;
+ Evas_Object *label;
+ Evas_Object *left;
+ Evas_Object *right;
+ Evas_Object *mute;
+ Evas_Object *table;
+ Evas_Object *button;
+ struct
+ {
+ Ecore_X_Window win;
+ Ecore_Event_Handler *mouse_up;
+ Ecore_Event_Handler *key_down;
+ } input;
+ } ui;
+
+ E_Mixer_System *sys;
+ E_Mixer_Channel *channel;
+ E_Mixer_Channel_State mixer_state;
+ E_Mixer_Gadget_Config *conf;
+} E_Mixer_Instance;
+
+typedef struct E_Mixer_Module_Context
+{
+ E_Config_DD *module_conf_edd;
+ E_Config_DD *gadget_conf_edd;
+ E_Mixer_Module_Config *conf;
+ E_Config_Dialog *conf_dialog;
+ E_Mixer_Instance *default_instance;
+ Evas_List *instances;
+ E_Dialog *mixer_dialog;
+ struct st_mixer_actions
+ {
+ E_Action *incr;
+ E_Action *decr;
+ E_Action *mute;
+ } actions;
+} E_Mixer_Module_Context;
+
+EAPI extern E_Module_Api e_modapi;
+EAPI void *e_modapi_init(E_Module *m);
+EAPI int e_modapi_shutdown(E_Module *m);
+EAPI int e_modapi_save(E_Module *m);
+
+E_Config_Dialog *e_mixer_config_module_dialog_new(E_Container *con, E_Mixer_Module_Context *ctxt);
+E_Config_Dialog *e_mixer_config_dialog_new(E_Container *con, E_Mixer_Gadget_Config *conf);
+E_Dialog *e_mixer_app_dialog_new(E_Container *con, void (*func)(E_Dialog *dialog, void *data), void *data);
+int e_mixer_app_dialog_select(E_Dialog *dialog, const char *card_name, const char *channel_name);
+
+int e_mixer_update(E_Mixer_Instance *inst);
+const char *e_mixer_theme_path(void);
+
+#endif
diff --git a/src/modules/mixer/e_mod_system.h b/src/modules/mixer/e_mod_system.h
new file mode 100644
index 000000000..e65566115
--- /dev/null
+++ b/src/modules/mixer/e_mod_system.h
@@ -0,0 +1,48 @@
+#ifndef E_MOD_SYSTEM_H
+#define E_MOD_SYSTEM_H
+
+#include
+
+typedef void E_Mixer_System;
+typedef void E_Mixer_Channel;
+
+struct E_Mixer_Channel_State
+{
+ int mute;
+ int left;
+ int right;
+};
+typedef struct E_Mixer_Channel_State E_Mixer_Channel_State;
+
+Evas_List *e_mixer_system_get_cards(void);
+void e_mixer_system_free_cards(Evas_List *cards);
+char *e_mixer_system_get_default_card(void);
+char *e_mixer_system_get_card_name(const char *card);
+
+
+E_Mixer_System *e_mixer_system_new(const char *card);
+void e_mixer_system_del(E_Mixer_System *self);
+
+int e_mixer_system_callback_set(E_Mixer_System *self, int (*func)(void *data, E_Mixer_System *self), void *data);
+
+Evas_List *e_mixer_system_get_channels(E_Mixer_System *self);
+void e_mixer_system_free_channels(Evas_List *channels);
+Evas_List *e_mixer_system_get_channels_names(E_Mixer_System *self);
+void e_mixer_system_free_channels_names(Evas_List *channels_names);
+char *e_mixer_system_get_default_channel_name(E_Mixer_System *self);
+E_Mixer_Channel *e_mixer_system_get_channel_by_name(E_Mixer_System *self, const char *name);
+char *e_mixer_system_get_channel_name(E_Mixer_System *self, E_Mixer_Channel *channel);
+void e_mixer_system_channel_del(E_Mixer_Channel *channel);
+
+
+int e_mixer_system_get_state(E_Mixer_System *self, E_Mixer_Channel *channel, E_Mixer_Channel_State *state);
+int e_mixer_system_set_state(E_Mixer_System *self, E_Mixer_Channel *channel, const E_Mixer_Channel_State *state);
+int e_mixer_system_get_volume(E_Mixer_System *self, E_Mixer_Channel *channel, int *left, int *right);
+int e_mixer_system_set_volume(E_Mixer_System *self, E_Mixer_Channel *channel, int left, int right);
+int e_mixer_system_get_mute(E_Mixer_System *self, E_Mixer_Channel *channel, int *mute);
+int e_mixer_system_set_mute(E_Mixer_System *self, E_Mixer_Channel *channel, int mute);
+int e_mixer_system_can_mute(E_Mixer_System *self, E_Mixer_Channel *channel);
+int e_mixer_system_has_capture(E_Mixer_System *self, E_Mixer_Channel *channel);
+
+
+#endif /* E_MOD_SYSTEM_H */
diff --git a/src/modules/mixer/module.desktop.in b/src/modules/mixer/module.desktop.in
new file mode 100644
index 000000000..54ff6d45b
--- /dev/null
+++ b/src/modules/mixer/module.desktop.in
@@ -0,0 +1,5 @@
+[Desktop Entry]
+Type=Link
+Name=Mixer
+Icon=e-module-mixer
+Comment=Mixer Gadget
A module to provide a mixer for changing volume.
diff --git a/src/modules/mixer/sys_alsa.c b/src/modules/mixer/sys_alsa.c
new file mode 100644
index 000000000..75fd7b0a4
--- /dev/null
+++ b/src/modules/mixer/sys_alsa.c
@@ -0,0 +1,618 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include "e_mod_system.h"
+
+struct e_mixer_callback_desc
+{
+ int (*func)(void *data, E_Mixer_System *self);
+ void *data;
+ E_Mixer_System *self;
+ Ecore_Idler *idler;
+ Evas_List *handlers;
+};
+
+
+static int _mixer_callback_add(E_Mixer_System *self, int (*func)(void *data, E_Mixer_System *self), void *data);
+static int _mixer_callback_del(E_Mixer_System *self, struct e_mixer_callback_desc *desc);
+
+
+static int
+_cb_dispatch(void *data)
+{
+ struct e_mixer_callback_desc *desc;
+ int r;
+
+ desc = data;
+ snd_mixer_handle_events(desc->self);
+ r = desc->func(desc->data, desc->self);
+ desc->idler = NULL;
+
+ if (!r)
+ _mixer_callback_del(desc->self, desc); /* desc is invalid then. */
+
+ return 0;
+}
+
+static int
+_cb_fd_handler(void *data, Ecore_Fd_Handler *fd_handler)
+{
+ struct e_mixer_callback_desc *desc;
+
+ desc = data;
+
+ if (ecore_main_fd_handler_active_get(fd_handler, ECORE_FD_ERROR))
+ {
+ desc->handlers = evas_list_remove(desc->handlers, fd_handler);
+ if (!desc->handlers)
+ {
+ E_Mixer_System *s;
+ int (*f)(void *, E_Mixer_System *);
+ void *d;
+
+ s = desc->self;
+ f = desc->func;
+ d = desc->data;
+ _mixer_callback_del(s, desc);
+ _mixer_callback_add(s, f, d);
+ }
+ return 0;
+ }
+
+ if (!desc->idler)
+ desc->idler = ecore_idler_add(_cb_dispatch, desc);
+ return 1;
+}
+
+static int
+_mixer_callback_add(E_Mixer_System *self, int (*func)(void *data, E_Mixer_System *self), void *data)
+{
+ struct e_mixer_callback_desc *desc;
+ struct pollfd *pfds;
+ int len;
+
+ len = snd_mixer_poll_descriptors_count(self);
+ if (len <= 0)
+ return 0;
+
+ desc = malloc(sizeof(struct e_mixer_callback_desc));
+ if (!desc)
+ return 0;
+
+ desc->func = func;
+ desc->data = data;
+ desc->self = self;
+ desc->idler = NULL;
+ desc->handlers = NULL;
+
+ pfds = alloca(len * sizeof(struct pollfd));
+ len = snd_mixer_poll_descriptors(self, pfds, len);
+ if (len <= 0)
+ {
+ free(desc);
+ return 0;
+ }
+
+ while (len > 0)
+ {
+ Ecore_Fd_Handler *fd_handler;
+
+ len--;
+ fd_handler = ecore_main_fd_handler_add(
+ pfds[len].fd, ECORE_FD_READ, _cb_fd_handler, desc, NULL, NULL);
+ desc->handlers = evas_list_prepend(desc->handlers, fd_handler);
+ }
+
+ snd_mixer_set_callback_private(self, desc);
+
+ return 1;
+}
+
+static int
+_mixer_callback_del(E_Mixer_System *self, struct e_mixer_callback_desc *desc)
+{
+ Evas_List *l;
+
+ snd_mixer_set_callback_private(self, NULL);
+
+ for (l = desc->handlers; l != NULL; l = l->next)
+ ecore_main_fd_handler_del(l->data);
+
+ evas_list_free(desc->handlers);
+ free(desc);
+
+ return 1;
+}
+
+static int
+_mixer_callback_replace(E_Mixer_System *self, struct e_mixer_callback_desc *desc, int (*func)(void *data, E_Mixer_System *self), void *data)
+{
+ desc->func = func;
+ desc->data = data;
+
+ return 1;
+}
+
+E_Mixer_System *
+e_mixer_system_new(const char *name)
+{
+ snd_mixer_t *handle;
+ int err;
+
+ if (!name)
+ return NULL;
+
+ err = snd_mixer_open(&handle, 0);
+ if (err < 0)
+ goto error_open;
+
+ err = snd_mixer_attach(handle, name);
+ if (err < 0)
+ goto error_load;
+
+ err = snd_mixer_selem_register(handle, NULL, NULL);
+ if (err < 0)
+ goto error_load;
+
+ err = snd_mixer_load(handle);
+ if (err < 0)
+ goto error_load;
+
+ return handle;
+
+ error_load:
+ snd_mixer_close(handle);
+ error_open:
+ fprintf(stderr, "MIXER: Cannot get hardware info: %s\n", snd_strerror(err));
+ return NULL;
+}
+
+void
+e_mixer_system_del(E_Mixer_System *self)
+{
+ struct e_mixer_callback_desc *desc;
+
+ if (self <= 0)
+ return;
+
+ desc = snd_mixer_get_callback_private(self);
+ if (desc)
+ _mixer_callback_del(self, desc);
+
+ snd_mixer_close(self);
+}
+
+int
+e_mixer_system_callback_set(E_Mixer_System *self, int (*func)(void *data, E_Mixer_System *self), void *data)
+{
+ struct e_mixer_callback_desc *desc;
+
+ if (!self)
+ return 0;
+
+ desc = snd_mixer_get_callback_private(self);
+ if (!desc)
+ {
+ if (func)
+ return _mixer_callback_add(self, func, data);
+ return 1;
+ }
+ else
+ {
+ if (func)
+ return _mixer_callback_replace(self, desc, func, data);
+ else
+ return _mixer_callback_del(self, desc);
+ }
+}
+
+Evas_List *
+e_mixer_system_get_cards(void)
+{
+ int err, card_num;
+ Evas_List *cards;
+
+ cards = NULL;
+ card_num = -1;
+ while (((err = snd_card_next(&card_num)) == 0) && (card_num >= 0))
+ {
+ snd_ctl_t *control;
+ char buf[256];
+
+ snprintf(buf, sizeof(buf), "hw:%d", card_num);
+
+ if (snd_ctl_open(&control, buf, 0) < 0)
+ break;
+ snd_ctl_close(control);
+ cards = evas_list_append(cards, strdup(buf));
+ }
+
+ if (err < 0)
+ fprintf(stderr, "MIXER: Cannot get available card number: %s\n",
+ snd_strerror(err));
+
+ return cards;
+}
+
+void
+e_mixer_system_free_cards(Evas_List *cards)
+{
+ Evas_List *e;
+
+ for (e = cards; e != NULL; e = e->next)
+ free(e->data);
+
+ evas_list_free(cards);
+}
+
+char *
+e_mixer_system_get_default_card(void)
+{
+ static const char buf[] = "hw:0";
+ snd_ctl_t *control;
+
+ if (snd_ctl_open(&control, buf, 0) < 0)
+ return NULL;
+ snd_ctl_close(control);
+ return strdup(buf);
+}
+
+char *
+e_mixer_system_get_card_name(const char *card)
+{
+ snd_ctl_card_info_t *hw_info;
+ const char *name;
+ snd_ctl_t *control;
+ int err;
+
+ if (!card)
+ return NULL;
+
+ snd_ctl_card_info_alloca(&hw_info);
+
+ err = snd_ctl_open(&control, card, 0);
+ if (err < 0)
+ return NULL;
+
+ err = snd_ctl_card_info(control, hw_info);
+ if (err < 0)
+ {
+ fprintf(stderr, "MIXER: Cannot get hardware info: %s: %s\n", card,
+ snd_strerror(err));
+ snd_ctl_close(control);
+ return NULL;
+ }
+
+ snd_ctl_close(control);
+ name = snd_ctl_card_info_get_name(hw_info);
+ if (!name)
+ {
+ fprintf(stderr, "MIXER: Cannot get hardware name: %s\n", card);
+ return NULL;
+ }
+
+ return strdup(name);
+}
+
+Evas_List *
+e_mixer_system_get_channels(E_Mixer_System *self)
+{
+ Evas_List *channels;
+ snd_mixer_elem_t *elem;
+
+ if (!self)
+ return NULL;
+
+ channels = NULL;
+
+ elem = snd_mixer_first_elem(self);
+ for (; elem != NULL; elem = snd_mixer_elem_next(elem))
+ {
+ if ((!snd_mixer_selem_is_active(elem)) ||
+ (!snd_mixer_selem_has_playback_volume(elem)))
+ continue;
+
+ channels = evas_list_append(channels, elem);
+ }
+
+ return channels;
+}
+
+void
+e_mixer_system_free_channels(Evas_List *channels)
+{
+ evas_list_free(channels);
+}
+
+Evas_List *
+e_mixer_system_get_channels_names(E_Mixer_System *self)
+{
+ Evas_List *channels;
+ snd_mixer_elem_t *elem;
+ snd_mixer_selem_id_t *sid;
+
+ if (!self)
+ return NULL;
+
+ channels = NULL;
+ snd_mixer_selem_id_alloca(&sid);
+
+ elem = snd_mixer_first_elem(self);
+ for (; elem != NULL; elem = snd_mixer_elem_next(elem))
+ {
+ const char *name;
+ if ((!snd_mixer_selem_is_active(elem)) ||
+ (!snd_mixer_selem_has_playback_volume(elem)))
+ continue;
+
+ snd_mixer_selem_get_id(elem, sid);
+ name = snd_mixer_selem_id_get_name(sid);
+ if (name)
+ channels = evas_list_append(channels, strdup(name));
+ }
+
+ return channels;
+}
+
+void
+e_mixer_system_free_channels_names(Evas_List *channels_names)
+{
+ Evas_List *e;
+
+ for (e = channels_names; e != NULL; e = e->next)
+ free(e->data);
+
+ evas_list_free(channels_names);
+}
+
+char *
+e_mixer_system_get_default_channel_name(E_Mixer_System *self)
+{
+ snd_mixer_elem_t *elem;
+ snd_mixer_selem_id_t *sid;
+
+ if (!self)
+ return NULL;
+
+ snd_mixer_selem_id_alloca(&sid);
+
+ elem = snd_mixer_first_elem(self);
+ for (; elem != NULL; elem = snd_mixer_elem_next(elem))
+ {
+ const char *name;
+ if ((!snd_mixer_selem_is_active(elem)) ||
+ (!snd_mixer_selem_has_playback_volume(elem)))
+ continue;
+
+ snd_mixer_selem_get_id(elem, sid);
+ name = snd_mixer_selem_id_get_name(sid);
+ if (name)
+ return strdup(name);
+ }
+
+ return NULL;
+}
+
+E_Mixer_Channel *
+e_mixer_system_get_channel_by_name(E_Mixer_System *self, const char *name)
+{
+ snd_mixer_elem_t *elem;
+ snd_mixer_selem_id_t *sid;
+
+ if ((!self) || (!name))
+ return NULL;
+
+ snd_mixer_selem_id_alloca(&sid);
+
+ elem = snd_mixer_first_elem(self);
+ for (; elem != NULL; elem = snd_mixer_elem_next(elem))
+ {
+ const char *n;
+ if ((!snd_mixer_selem_is_active(elem)) ||
+ (!snd_mixer_selem_has_playback_volume(elem)))
+ continue;
+
+ snd_mixer_selem_get_id(elem, sid);
+ n = snd_mixer_selem_id_get_name(sid);
+ if (n && (strcmp(n, name) == 0))
+ return elem;
+ }
+
+ return NULL;
+}
+
+void
+e_mixer_system_channel_del(E_Mixer_Channel *channel)
+{
+}
+
+char *
+e_mixer_system_get_channel_name(E_Mixer_System *self, E_Mixer_Channel *channel)
+{
+ snd_mixer_selem_id_t *sid;
+ const char *n;
+ char *name;
+
+ if ((!self) || (!channel))
+ return NULL;
+
+ snd_mixer_selem_id_alloca(&sid);
+ snd_mixer_selem_get_id(channel, sid);
+ n = snd_mixer_selem_id_get_name(sid);
+ if (n)
+ name = strdup(n);
+ else
+ name = NULL;
+
+ return name;
+}
+
+int
+e_mixer_system_get_volume(E_Mixer_System *self, E_Mixer_Channel *channel, int *left, int *right)
+{
+ long lvol, rvol, range, min, max;
+
+ if ((!self) || (!channel) || (!left) || (!right))
+ return 0;
+
+ snd_mixer_handle_events(self);
+ snd_mixer_selem_get_playback_volume_range(channel, &min, &max);
+ range = max - min;
+ if (range < 1)
+ return 0;
+
+ if (snd_mixer_selem_has_playback_channel(channel, 0))
+ snd_mixer_selem_get_playback_volume(channel, 0, &lvol);
+ else
+ lvol = min;
+
+ if (snd_mixer_selem_has_playback_channel(channel, 1))
+ snd_mixer_selem_get_playback_volume(channel, 1, &rvol);
+ else
+ rvol = min;
+
+ if (snd_mixer_selem_is_playback_mono(channel) ||
+ snd_mixer_selem_has_playback_volume_joined(channel))
+ rvol = lvol;
+
+ *left = rint((double)(lvol - min) * 100 / (double)range);
+ *right = rint((double)(rvol - min) * 100 / (double)range);
+
+ return 1;
+}
+
+int
+e_mixer_system_set_volume(E_Mixer_System *self, E_Mixer_Channel *channel, int left, int right)
+{
+ long range, min, max, div;
+ int mode;
+
+ if ((!self) || (!channel))
+ return 0;
+
+ snd_mixer_handle_events(self);
+ snd_mixer_selem_get_playback_volume_range(channel, &min, &max);
+ div = 100 + min;
+ if (div == 0)
+ {
+ div = 1; /* no zero-division */
+ min++;
+ }
+
+ range = max - min;
+ if (range < 1)
+ return 0;
+
+ mode = 0;
+ if (left >= 0)
+ {
+ left = (((range * left) + (range / 2)) / div) - min;
+ mode |= 1;
+ }
+
+ if (right >= 0)
+ {
+ right = (((range * right) + (range / 2)) / div) - min;
+ mode |= 2;
+ }
+
+ if (mode & 1)
+ snd_mixer_selem_set_playback_volume(channel, 0, left);
+
+ if ((!snd_mixer_selem_is_playback_mono(channel)) &&
+ (!snd_mixer_selem_has_playback_volume_joined(channel)) &&
+ (mode & 2))
+ {
+ if (snd_mixer_selem_has_playback_channel(channel, 1))
+ snd_mixer_selem_set_playback_volume(channel, 1, right);
+ }
+
+ return 1;
+}
+
+int
+e_mixer_system_can_mute(E_Mixer_System *self, E_Mixer_Channel *channel)
+{
+ if ((!self) || (!channel))
+ return 0;
+
+ snd_mixer_handle_events(self);
+ return (snd_mixer_selem_has_playback_switch(channel) ||
+ snd_mixer_selem_has_playback_switch_joined(channel));
+}
+
+int
+e_mixer_system_get_mute(E_Mixer_System *self, E_Mixer_Channel *channel, int *mute)
+{
+ if ((!self) || (!channel) || (!mute))
+ return 0;
+
+ snd_mixer_handle_events(self);
+ if (snd_mixer_selem_has_playback_switch(channel) ||
+ snd_mixer_selem_has_playback_switch_joined(channel))
+ {
+ int m;
+
+ /* XXX: not checking for return, always returns 0 even if it worked.
+ * alsamixer also don't check it. Bug?
+ */
+ snd_mixer_selem_get_playback_switch(channel, 0, &m);
+ *mute = !m;
+ }
+ else
+ *mute = 0;
+
+ return 1;
+}
+
+int
+e_mixer_system_set_mute(E_Mixer_System *self, E_Mixer_Channel *channel, int mute)
+{
+ if ((!self) || (!channel))
+ return 0;
+
+ snd_mixer_handle_events(self);
+ if (snd_mixer_selem_has_playback_switch(channel) ||
+ snd_mixer_selem_has_playback_switch_joined(channel))
+ return snd_mixer_selem_set_playback_switch_all(channel, !mute);
+ else
+ return 0;
+}
+
+int
+e_mixer_system_get_state(E_Mixer_System *self, E_Mixer_Channel *channel, E_Mixer_Channel_State *state)
+{
+ int r;
+
+ if (!state)
+ return 0;
+
+ r = e_mixer_system_get_mute(self, channel, &state->mute);
+ r &= e_mixer_system_get_volume(self, channel, &state->left, &state->right);
+ return r;
+}
+
+int
+e_mixer_system_set_state(E_Mixer_System *self, E_Mixer_Channel *channel, const E_Mixer_Channel_State *state)
+{
+ int r;
+
+ if (!state)
+ return 0;
+
+ r = e_mixer_system_set_mute(self, channel, state->mute);
+ r &= e_mixer_system_set_volume(self, channel, state->left, state->right);
+ return r;
+}
+
+int
+e_mixer_system_has_capture(E_Mixer_System *self, E_Mixer_Channel *channel)
+{
+ if ((!self) || (!channel))
+ return 0;
+
+ return snd_mixer_selem_has_capture_switch(channel);
+}
diff --git a/src/modules/mixer/sys_dummy.c b/src/modules/mixer/sys_dummy.c
new file mode 100644
index 000000000..783e4a123
--- /dev/null
+++ b/src/modules/mixer/sys_dummy.c
@@ -0,0 +1,166 @@
+#include "e_mod_system.h"
+#include
+#include
+
+static const char _name[] = "dummy";
+
+E_Mixer_System *
+e_mixer_system_new(const char *name)
+{
+ if (strcmp(name, _name) == 0)
+ return (E_Mixer_System *)-1;
+ else
+ return NULL;
+}
+
+void
+e_mixer_system_del(E_Mixer_System *self)
+{
+}
+
+int
+e_mixer_system_callback_set(E_Mixer_System *self, int (*func)(void *data, E_Mixer_System *self), void *data)
+{
+ return 0;
+}
+
+Evas_List *
+e_mixer_system_get_cards(void)
+{
+ return evas_list_append(NULL, _name);
+}
+
+void
+e_mixer_system_free_cards(Evas_List *cards)
+{
+ evas_list_free(cards);
+}
+
+char *
+e_mixer_system_get_default_card(void)
+{
+ return strdup(_name);
+}
+
+char *
+e_mixer_system_get_card_name(const char *card)
+{
+ if (strcmp(card, _name) == 0)
+ return strdup(_name);
+ else
+ return NULL;
+}
+
+Evas_List *
+e_mixer_system_get_channels(E_Mixer_System *self)
+{
+ return evas_list_append(NULL, (void *)-2);
+}
+
+void
+e_mixer_system_free_channels(Evas_List *channels)
+{
+ evas_list_free(channels);
+}
+
+Evas_List *
+e_mixer_system_get_channels_names(E_Mixer_System *self)
+{
+ return evas_list_append(NULL, _name);
+}
+
+void
+e_mixer_system_free_channels_names(Evas_List *channels_names)
+{
+ evas_list_free(channels_names);
+}
+
+char *
+e_mixer_system_get_default_channel_name(E_Mixer_System *self)
+{
+ return strdup(_name);
+}
+
+E_Mixer_Channel *
+e_mixer_system_get_channel_by_name(E_Mixer_System *self, const char *name)
+{
+ if (strcmp(name, _name) == 0)
+ return (E_Mixer_Channel *)-2;
+ else
+ return NULL;
+}
+
+void
+e_mixer_system_channel_del(E_Mixer_Channel *channel)
+{
+}
+
+char *
+e_mixer_system_get_channel_name(E_Mixer_System *self, E_Mixer_Channel *channel)
+{
+ if (channel == (E_Mixer_Channel *)-2)
+ return strdup(_name);
+ else
+ return NULL;
+}
+
+int
+e_mixer_system_get_volume(E_Mixer_System *self, E_Mixer_Channel *channel, int *left, int *right)
+{
+ if (left)
+ *left = 0;
+ if (right)
+ *right = 0;
+
+ return 1;
+}
+
+int
+e_mixer_system_set_volume(E_Mixer_System *self, E_Mixer_Channel *channel, int left, int right)
+{
+ return 0;
+}
+
+int
+e_mixer_system_can_mute(E_Mixer_System *self, E_Mixer_Channel *channel)
+{
+ return 1;
+}
+
+int
+e_mixer_system_get_mute(E_Mixer_System *self, E_Mixer_Channel *channel, int *mute)
+{
+ if (mute)
+ *mute = 1;
+
+ return 1;
+}
+
+int
+e_mixer_system_set_mute(E_Mixer_System *self, E_Mixer_Channel *channel, int mute)
+{
+ return 0;
+}
+
+int
+e_mixer_system_get_state(E_Mixer_System *self, E_Mixer_Channel *channel, E_Mixer_Channel_State *state)
+{
+ const E_Mixer_Channel_State def = {1, 0, 0};
+
+ if (state)
+ *state = def;
+
+ return 1;
+}
+
+int
+e_mixer_system_set_state(E_Mixer_System *self, E_Mixer_Channel *channel, const E_Mixer_Channel_State *state)
+{
+ return 0;
+}
+
+int
+e_mixer_system_has_capture(E_Mixer_System *self, E_Mixer_Channel *channel)
+{
+ return 0;
+}