diff --git a/src/modules/mixer/emixer.c b/src/modules/mixer/emixer.c index 1bcd96c1f..9b3644013 100644 --- a/src/modules/mixer/emixer.c +++ b/src/modules/mixer/emixer.c @@ -2,10 +2,10 @@ #include "emix.h" Evas_Object *win; -Evas_Object *source_scroller, *sink_input_scroller, *sink_scroller; -Evas_Object *source_box, *sink_input_box, *sink_box; +Evas_Object *source_scroller, *sink_input_scroller, *sink_scroller, *card_scroller; +Evas_Object *source_box, *sink_input_box, *sink_box, *card_box; -Eina_List *source_list = NULL, *sink_input_list = NULL, *sink_list = NULL; +Eina_List *source_list = NULL, *sink_input_list = NULL, *sink_list = NULL, *card_list = NULL; ////////////////////////////////////////////////////////////////////////////// @@ -514,6 +514,117 @@ _emix_source_change(Emix_Source *source) } ////////////////////////////////////////////////////////////////////////////// +static void +_cb_card_profile_change(void *data, + Evas_Object *obj, + void *event_info EINA_UNUSED) +{ + Emix_Profile *profile = data; + Evas_Object *bxv = evas_object_data_get(obj, "parent"); + Emix_Card *card = evas_object_data_get(bxv, "card"); + elm_object_text_set(obj, profile->description); + emix_card_profile_set(card, profile); +} + +static void +_emix_card_add(Emix_Card *card) +{ + Evas_Object *bxv, *bx, *lb, *hv, *sep; + Eina_List *l; + Emix_Profile *profile; + + bxv = elm_box_add(win); + card_list = eina_list_append(card_list, bxv); + evas_object_data_set(bxv, "card", card); + 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, card->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, "profile", hv); + elm_hoversel_hover_parent_set(hv, win); + EINA_LIST_FOREACH(card->profiles, l, profile) + { + if (!profile->plugged) continue; + elm_hoversel_item_add(hv, profile->description, + NULL, ELM_ICON_NONE, + _cb_card_profile_change, profile); + if (profile->active) elm_object_text_set(hv, profile->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); + + 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(card_box, bxv); + evas_object_show(bxv); +} + +static void +_emix_card_change(Emix_Card *card) +{ + const Eina_List *l; + Evas_Object *bxv, *hv; + Emix_Profile *profile; + + EINA_LIST_FOREACH(card_list, l, bxv) + { + if (evas_object_data_get(bxv, "card") == card) break; + } + if (!l) return; + hv = evas_object_data_get(bxv, "profile"); + elm_hoversel_clear(hv); + EINA_LIST_FOREACH(card->profiles, l, profile) + { + if (!profile->plugged) continue; + elm_hoversel_item_add(hv, profile->description, + NULL, ELM_ICON_NONE, + _cb_card_profile_change, profile); + if (profile->active) elm_object_text_set(hv, profile->description); + } +} + + +static void +_emix_card_del(Emix_Card *card) +{ + Eina_List *l; + Evas_Object *bxv; + EINA_LIST_FOREACH(card_list, l, bxv) + { + if (evas_object_data_get(bxv, "card") == card) + { + card_list = eina_list_remove_list(card_list, l); + evas_object_del(bxv); + return; + } + } +} + + +////////////////////////////////////////////////////////////////////////////// + static void _cb_emix_event(void *data EINA_UNUSED, enum Emix_Event event, void *event_info) @@ -552,6 +663,15 @@ _cb_emix_event(void *data EINA_UNUSED, enum Emix_Event event, void *event_info) case EMIX_SOURCE_CHANGED_EVENT: _emix_source_change(event_info); break; + case EMIX_CARD_ADDED_EVENT: + _emix_card_add(event_info); + break; + case EMIX_CARD_REMOVED_EVENT: + _emix_card_del(event_info); + break; + case EMIX_CARD_CHANGED_EVENT: + _emix_card_change(event_info); + break; default: break; } @@ -567,6 +687,7 @@ _cb_playback(void *data EINA_UNUSED, evas_object_hide(source_scroller); evas_object_show(sink_input_scroller); evas_object_hide(sink_scroller); + evas_object_hide(card_scroller); } static void @@ -577,6 +698,7 @@ _cb_outputs(void *data EINA_UNUSED, evas_object_hide(source_scroller); evas_object_hide(sink_input_scroller); evas_object_show(sink_scroller); + evas_object_hide(card_scroller); } static void @@ -587,8 +709,21 @@ _cb_inputs(void *data EINA_UNUSED, evas_object_show(source_scroller); evas_object_hide(sink_input_scroller); evas_object_hide(sink_scroller); + evas_object_hide(card_scroller); } +static void +_cb_card(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_hide(sink_scroller); + evas_object_show(card_scroller); +} + + ////////////////////////////////////////////////////////////////////////////// static void @@ -633,6 +768,18 @@ _fill_sink(void) } } +static void +_fill_card(void) +{ + const Eina_List *l; + Emix_Card *card; + + EINA_LIST_FOREACH(emix_cards_get(), l, card) + { + _emix_card_add(card); + } +} + ////////////////////////////////////////////////////////////////////////////// EAPI_MAIN int @@ -674,6 +821,7 @@ elm_main(int argc, char **argv) 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_toolbar_item_append(tbar, NULL, "Cards", _cb_card, NULL); elm_table_pack(tb, tbar, 0, 0, 1, 1); evas_object_show(tbar); @@ -697,6 +845,12 @@ elm_main(int argc, char **argv) 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); + card_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); @@ -718,6 +872,13 @@ elm_main(int argc, char **argv) elm_object_content_set(sink_scroller, bx); evas_object_show(bx); + bx = elm_box_add(win); + card_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(card_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); @@ -729,6 +890,7 @@ elm_main(int argc, char **argv) _fill_source(); _fill_sink_input(); _fill_sink(); + _fill_card(); evas_object_show(win); elm_run(); diff --git a/src/modules/mixer/lib/backends/alsa/alsa.c b/src/modules/mixer/lib/backends/alsa/alsa.c index 40d8fa69c..e09227db6 100644 --- a/src/modules/mixer/lib/backends/alsa/alsa.c +++ b/src/modules/mixer/lib/backends/alsa/alsa.c @@ -532,7 +532,9 @@ _alsa_backend = _alsa_sources_get,/*source*/ _alsa_sources_mute_set,/* source mute set */ _alsa_sources_volume_set, /* source volume set */ - NULL /* advanced options */ + NULL, /* advanced options */ + NULL, /* card list */ + NULL /* card profile set */ }; E_API Emix_Backend * diff --git a/src/modules/mixer/lib/backends/pulseaudio/pulse.c b/src/modules/mixer/lib/backends/pulseaudio/pulse.c index 73524a318..5c58749d1 100644 --- a/src/modules/mixer/lib/backends/pulseaudio/pulse.c +++ b/src/modules/mixer/lib/backends/pulseaudio/pulse.c @@ -27,7 +27,7 @@ typedef struct _Context Ecore_Timer *connect; int default_sink; - Eina_List *sinks, *sources, *inputs; + Eina_List *sinks, *sources, *inputs, *cards; Eina_Bool connected; } Context; @@ -50,6 +50,18 @@ typedef struct _Source int idx; } Source; +typedef struct _Profile +{ + Emix_Profile base; + uint32_t priority; +} Profile; + +typedef struct _Card +{ + Emix_Card base; + int idx; +} Card; + static Context *ctx = NULL; extern pa_mainloop_api functable; @@ -122,6 +134,22 @@ _source_del(Source *source) free(source); } +static void +_card_del(Card *card) +{ + Profile *profile; + EINA_SAFETY_ON_NULL_RETURN(card); + + EINA_LIST_FREE(card->base.profiles, profile) + { + eina_stringshare_del(profile->base.name); + eina_stringshare_del(profile->base.description); + free(profile); + } + eina_stringshare_del(card->base.name); + free(card); +} + static void _sink_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol, void *userdata EINA_UNUSED) @@ -600,6 +628,179 @@ _server_info_cb(pa_context *c, const pa_server_info *info, pa_operation_unref(o); } +static int +_profile_sort_cb(const Profile *p1, const Profile *p2) +{ + if (p1->priority < p2->priority) return 1; + if (p1->priority > p2->priority) return -1; + return 0; +} + +static void +_card_cb(pa_context *c, const pa_card_info *info, int eol, void *userdata EINA_UNUSED) +{ + Card *card; + const char *description; + uint32_t i, j; + Profile *profile; + + if (eol < 0) { + if (pa_context_errno(c) == PA_ERR_NOENTITY) + return; + + ERR("Card callback failure: %d", pa_context_errno(c)); + return; + } + if (eol > 0) + return; + + card = calloc(1, sizeof(Card)); + EINA_SAFETY_ON_NULL_RETURN(card); + + card->idx = info->index; + + description = pa_proplist_gets(info->proplist, PA_PROP_DEVICE_DESCRIPTION); + card->base.name = description + ? eina_stringshare_add(description) + : eina_stringshare_add(info->name); + + for (i = 0; i < info->n_ports; ++i) + { + for (j = 0; j < info->ports[i]->n_profiles; ++j) + { + profile = calloc(1, sizeof(Profile)); + profile->base.name = eina_stringshare_add(info->ports[i]->profiles[j]->name); + profile->base.description = eina_stringshare_add(info->ports[i]->profiles[j]->description); + profile->priority = info->ports[i]->profiles[j]->priority; + if ((info->ports[i]->available == PA_PORT_AVAILABLE_NO) + && ((strcmp("analog-output-speaker", profile->base.name)) + || (strcmp("analog-input-microphone-internal", profile->base.name)))) + profile->base.plugged = EINA_FALSE; + else + profile->base.plugged = EINA_TRUE; + + if (info->active_profile) + { + if (info->ports[i]->profiles[j]->name == info->active_profile->name) + profile->base.active = EINA_TRUE; + } + card->base.profiles = + eina_list_sorted_insert(card->base.profiles, + (Eina_Compare_Cb)_profile_sort_cb, + profile); + } + } + ctx->cards = eina_list_append(ctx->cards, card); + if (ctx->cb) + ctx->cb((void *)ctx->userdata, EMIX_CARD_ADDED_EVENT, + (Emix_Card *)card); +} + +static void +_card_changed_cb(pa_context *c EINA_UNUSED, const pa_card_info *info, int eol, + void *userdata EINA_UNUSED) +{ + Card *card = NULL, *ca; + Eina_List *l; + const char *description; + uint32_t i, j; + Profile *profile; + + if (eol < 0) + { + if (pa_context_errno(c) == PA_ERR_NOENTITY) + return; + + ERR("Card callback failure"); + return; + } + + if (eol > 0) + return; + + DBG("card index: %d\n", info->index); + + EINA_LIST_FOREACH(ctx->cards, l, ca) + { + if (ca->idx == (int)info->index) + { + card = ca; + break; + } + } + + EINA_SAFETY_ON_NULL_RETURN(card); + + description = pa_proplist_gets(info->proplist, PA_PROP_DEVICE_DESCRIPTION); + eina_stringshare_replace(&card->base.name, + (description + ? eina_stringshare_add(description) + : eina_stringshare_add(info->name))); + + EINA_LIST_FREE(card->base.profiles, profile) + { + eina_stringshare_del(profile->base.name); + eina_stringshare_del(profile->base.description); + free(profile); + } + for (i = 0; i < info->n_ports; ++i) + { + for (j = 0; j < info->ports[i]->n_profiles; ++j) + { + profile = calloc(1, sizeof(Profile)); + profile->base.name = eina_stringshare_add(info->ports[i]->profiles[j]->name); + profile->base.description = eina_stringshare_add(info->ports[i]->profiles[j]->description); + profile->priority = info->ports[i]->profiles[j]->priority; + if ((info->ports[i]->available == PA_PORT_AVAILABLE_NO) + && ((strcmp("analog-output-speaker", profile->base.name)) + || (strcmp("analog-input-microphone-internal", profile->base.name)))) + profile->base.plugged = EINA_FALSE; + else + profile->base.plugged = EINA_TRUE; + + if (info->active_profile) + { + if (info->ports[i]->profiles[j]->name == info->active_profile->name) + profile->base.active = EINA_TRUE; + } + card->base.profiles = + eina_list_sorted_insert(card->base.profiles, + (Eina_Compare_Cb)_profile_sort_cb, + profile); + } + } + + if (ctx->cb) + ctx->cb((void *)ctx->userdata, EMIX_CARD_CHANGED_EVENT, + (Emix_Card *)card); +} + +static void +_card_remove_cb(int index, void *data EINA_UNUSED) +{ + Card *card; + Eina_List *l; + EINA_SAFETY_ON_NULL_RETURN(ctx); + + DBG("Removing card: %d", index); + + EINA_LIST_FOREACH(ctx->cards, l, card) + { + if (card->idx == index) + { + ctx->cards = eina_list_remove_list(ctx->cards, l); + if (ctx->cb) + ctx->cb((void *)ctx->userdata, + EMIX_CARD_REMOVED_EVENT, + (Emix_Card *)card); + _card_del(card); + + break; + } + } +} + + static void _subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *data) @@ -699,6 +900,34 @@ _subscribe_cb(pa_context *c, pa_subscription_event_type_t t, } pa_operation_unref(o); break; + + case PA_SUBSCRIPTION_EVENT_CARD: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == + PA_SUBSCRIPTION_EVENT_REMOVE) + _card_remove_cb(index, data); + else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == + PA_SUBSCRIPTION_EVENT_NEW) + { + if (!(o = pa_context_get_card_info_by_index(c, index, _card_cb, + data))) + { + ERR("pa_context_get_card_info() failed"); + return; + } + pa_operation_unref(o); + } + else + { + if (!(o = pa_context_get_card_info_by_index(c, index, _card_changed_cb, + data))) + { + ERR("pa_context_get_card_info() failed"); + return; + } + pa_operation_unref(o); + } + break; + default: WRN("Event not handled"); break; @@ -771,6 +1000,17 @@ _pulse_pa_state_cb(pa_context *context, void *data) return; } pa_operation_unref(o); + + if (!(o = pa_context_get_card_info_list(context, _card_cb, + ctx))) + { + ERR("pa_context_get_server_info() failed"); + return; + } + pa_operation_unref(o); + + + break; case PA_CONTEXT_FAILED: @@ -908,6 +1148,7 @@ _disconnect_cb() Source *source; Sink *sink; Sink_Input *input; + Card *card; if (ctx->cb) ctx->cb((void *)ctx->userdata, EMIX_DISCONNECTED_EVENT, NULL); @@ -918,6 +1159,8 @@ _disconnect_cb() _sink_del(sink); EINA_LIST_FREE(ctx->inputs, input) _sink_input_del(input); + EINA_LIST_FREE(ctx->cards, card) + _card_del(card); } static void @@ -1087,6 +1330,34 @@ _max_volume(void) return 150; } +static const Eina_List * +_cards_get(void) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL); + return ctx->cards; +} + +static Eina_Bool +_card_profile_set(Emix_Card *card, const Emix_Profile *profile) +{ + pa_operation* o; + Card *c = (Card *)card; + Profile *p = (Profile *)profile; + EINA_SAFETY_ON_FALSE_RETURN_VAL(ctx && ctx->context && + (c != NULL) && (p != NULL), EINA_FALSE); + + if (!(o = pa_context_set_card_profile_by_index(ctx->context, + c->idx, p->base.name, NULL, + NULL))) + { + ERR("pa_context_set_card_profile_by_index() failed"); + return EINA_FALSE; + } + pa_operation_unref(o); + + return EINA_TRUE; +} + static Emix_Backend _pulseaudio_backend = { @@ -1109,6 +1380,8 @@ _pulseaudio_backend = _source_mute_set, _source_volume_set, NULL, + _cards_get, + _card_profile_set }; E_API Emix_Backend * diff --git a/src/modules/mixer/lib/emix.c b/src/modules/mixer/lib/emix.c index 898df3a3b..4d22fb509 100644 --- a/src/modules/mixer/lib/emix.c +++ b/src/modules/mixer/lib/emix.c @@ -33,6 +33,7 @@ typedef struct Context Eina_Array *backends; Eina_List *backends_names; Eina_List *callbacks; + Eina_List *configs; Emix_Backend *loaded; } Context; @@ -170,6 +171,13 @@ emix_backends_available(void) return ctx->backends_names; } +const Eina_List * +emix_configs_available(void) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL); + return ctx->configs; +} + int emix_max_volume_get(void) { @@ -402,3 +410,24 @@ emix_event_callback_del(Emix_Event_Cb cb) return EINA_FALSE; } + +const Eina_List* +emix_cards_get(void) +{ + EINA_SAFETY_ON_FALSE_RETURN_VAL((ctx && ctx->loaded && + ctx->loaded->ebackend_cards_get), NULL); + + return ctx->loaded->ebackend_cards_get(); +} + +Eina_Bool +emix_card_profile_set(Emix_Card *card, Emix_Profile *profile) +{ + EINA_SAFETY_ON_FALSE_RETURN_VAL((ctx && ctx->loaded && + ctx->loaded->ebackend_card_profile_set && + card && profile), EINA_FALSE); + + return ctx->loaded->ebackend_card_profile_set(card, profile); +} + + diff --git a/src/modules/mixer/lib/emix.h b/src/modules/mixer/lib/emix.h index 29dc17da2..94bfb89ed 100644 --- a/src/modules/mixer/lib/emix.h +++ b/src/modules/mixer/lib/emix.h @@ -36,7 +36,10 @@ enum Emix_Event { EMIX_SINK_INPUT_CHANGED_EVENT, EMIX_SOURCE_ADDED_EVENT, EMIX_SOURCE_REMOVED_EVENT, - EMIX_SOURCE_CHANGED_EVENT + EMIX_SOURCE_CHANGED_EVENT, + EMIX_CARD_ADDED_EVENT, + EMIX_CARD_REMOVED_EVENT, + EMIX_CARD_CHANGED_EVENT }; typedef struct _Emix_Volume { @@ -73,6 +76,18 @@ typedef struct _Emix_Source { Eina_Bool mute; } Emix_Source; +typedef struct _Emix_Profile { + const char *name; + const char *description; + Eina_Bool plugged; + Eina_Bool active; +} Emix_Profile; + +typedef struct _Emix_Card { + const char *name; + Eina_List *profiles; +} Emix_Card; + typedef void (*Emix_Event_Cb)(void *data, enum Emix_Event event, void *event_info); @@ -109,6 +124,8 @@ typedef struct _Emix_Backend { Emix_Volume volume); Evas_Object* (*ebackend_advanced_options_add)(Evas_Object *parent); + const Eina_List* (*ebackend_cards_get)(void); + Eina_Bool (*ebackend_card_profile_set)(Emix_Card *card, const Emix_Profile *profile); } Emix_Backend; ////////////////////////////////////////////////////////////////////////////// @@ -168,4 +185,7 @@ E_API void emix_source_volume_set(Emix_Source *source, Emix_Volume volume); E_API Evas_Object* emix_advanced_options_add(Evas_Object *parent); +E_API const Eina_List* emix_cards_get(void); +E_API Eina_Bool emix_card_profile_set(Emix_Card *card, Emix_Profile *profile); + #endif /* EMIX_H */