diff --git a/src/modules/mixer/backend.c b/src/modules/mixer/backend.c index b08693dc8..b3c6453e7 100644 --- a/src/modules/mixer/backend.c +++ b/src/modules/mixer/backend.c @@ -43,7 +43,6 @@ static void _sink_input_set(int volume, Eina_Bool muted, void *data); static int _sink_input_min_get(void *data); static int _sink_input_max_get(void *data); static const char *_sink_input_name_get(void *data); -static pid_t _get_ppid(pid_t pid); static void _sink_input_event(int type, Emix_Sink_Input *input); static void _events_cb(void *data, enum Emix_Event type, void *event_info); static Eina_Bool _desklock_cb(void *data, int type, void *info); @@ -85,6 +84,8 @@ static int _notification_id = 0; static Ecore_Exe *_emixer_exe = NULL; static Ecore_Event_Handler *_emix_exe_event_del_handler = NULL; +static const Emix_Source *_source_default = NULL; + static E_Action *_action_incr = NULL; static E_Action *_action_decr = NULL; static E_Action *_action_mute = NULL; @@ -133,7 +134,7 @@ _notify(const int val) } static void -_volume_increase_cb(E_Object *obj EINA_UNUSED, const char *params EINA_UNUSED) +_volume_adjust(int step) { unsigned int i; Emix_Volume volume; @@ -143,19 +144,21 @@ _volume_increase_cb(E_Object *obj EINA_UNUSED, const char *params EINA_UNUSED) if (!s->volume.channel_count) return; - if (BARRIER_CHECK(s->volume.volumes[0], s->volume.volumes[0] + VOLUME_STEP)) - return; + if (step > 0) + { + if (BARRIER_CHECK(s->volume.volumes[0], s->volume.volumes[0] + step)) + return; + } volume.channel_count = s->volume.channel_count; volume.volumes = calloc(s->volume.channel_count, sizeof(int)); for (i = 0; i < volume.channel_count; i++) { - if (s->volume.volumes[i] < (emix_max_volume_get()) - VOLUME_STEP) - volume.volumes[i] = s->volume.volumes[i] + VOLUME_STEP; - else if (s->volume.volumes[i] < emix_max_volume_get()) + volume.volumes[i] = s->volume.volumes[i] + step; + if (volume.volumes[i] < 0) + volume.volumes[i] = 0; + else if (volume.volumes[i] > emix_max_volume_get()) volume.volumes[i] = emix_max_volume_get(); - else - volume.volumes[i] = s->volume.volumes[i]; } emix_sink_volume_set(s, &volume); @@ -164,27 +167,47 @@ _volume_increase_cb(E_Object *obj EINA_UNUSED, const char *params EINA_UNUSED) free(volume.volumes); } +static void +_volume_increase_cb(E_Object *obj EINA_UNUSED, const char *params EINA_UNUSED) +{ + _volume_adjust(VOLUME_STEP); +} + static void _volume_decrease_cb(E_Object *obj EINA_UNUSED, const char *params EINA_UNUSED) +{ + _volume_adjust(-VOLUME_STEP); +} + +static void +_volume_source_adjust(int step) { unsigned int i; Emix_Volume volume; EINA_SAFETY_ON_NULL_RETURN(_sink_default); - Emix_Sink *s = (Emix_Sink *)_sink_default; + Emix_Source *s = (Emix_Source *)_source_default; + + if (!s->volume.channel_count) return; + + if (step > 0) + { + if (BARRIER_CHECK(s->volume.volumes[0], s->volume.volumes[0] + step)) + return; + } + volume.channel_count = s->volume.channel_count; volume.volumes = calloc(s->volume.channel_count, sizeof(int)); for (i = 0; i < volume.channel_count; i++) { - if (s->volume.volumes[i] > VOLUME_STEP) - volume.volumes[i] = s->volume.volumes[i] - VOLUME_STEP; - else if (s->volume.volumes[i] < VOLUME_STEP) + volume.volumes[i] = s->volume.volumes[i] + step; + if (volume.volumes[i] < 0) volume.volumes[i] = 0; - else - volume.volumes[i] = s->volume.volumes[i]; + else if (volume.volumes[i] > emix_max_volume_get()) + volume.volumes[i] = emix_max_volume_get(); } - emix_sink_volume_set((Emix_Sink *)_sink_default, &volume); + emix_source_volume_set(s, &volume); emix_config_save_state_get(); if (emix_config_save_get()) e_config_save_queue(); free(volume.volumes); @@ -497,8 +520,8 @@ _sink_input_name_get(void *data) return input->name; } -static pid_t -_get_ppid(pid_t pid) +EINTERN pid_t +backend_util_get_ppid(pid_t pid) { int fd; char buf[128]; @@ -564,7 +587,7 @@ _sink_input_event(int type, Emix_Sink_Input *input) } } if (found) break; - pid = _get_ppid(pid); + pid = backend_util_get_ppid(pid); } break; case EMIX_SINK_INPUT_REMOVED_EVENT: @@ -589,6 +612,75 @@ _sink_input_event(int type, Emix_Sink_Input *input) } } +static void +_source_event(int type, Emix_Source *source) +{ + const Eina_List *l; + + if (type == EMIX_SOURCE_REMOVED_EVENT) + { + if (source == _source_default) + { + l = emix_sources_get(); + if (l) _source_default = l->data; + else _source_default = NULL; + if (emix_config_save_get()) e_config_save_queue(); + _backend_changed(); + } + } + else if (type == EMIX_SOURCE_CHANGED_EVENT) + { + /* If pulseaudio changed the default sink, swap the UI to display it + instead of previously selected sink */ + if (source->default_source) + _source_default = source; + if (_source_default == source) + { + static int prev_vol = -1; + int vol; + + _backend_changed(); + if (source->mute || !source->volume.channel_count) vol = 0; + else vol = source->volume.volumes[0]; + if (vol != prev_vol) + { +// _notify(vol); + prev_vol = vol; + } + } + } + else + { + DBG("Source added"); + } + /* + Only safe the state if we are not in init mode, + If we are in init mode, this is a result of the restore call. + Restore iterates over a list of sinks which would get deleted in the + save_state_get call. + */ + if (!_backend_init_flag) + { + emix_config_save_state_get(); + if (emix_config_save_get()) e_config_save_queue(); +// ecore_event_add(E_EVENT_MIXER_SINKS_CHANGED, NULL, NULL, NULL); + } +} + +static void +_source_output_event(int type, Emix_Source_Output *output EINA_UNUSED) +{ + switch (type) + { + case EMIX_SOURCE_OUTPUT_ADDED_EVENT: + break; + case EMIX_SOURCE_OUTPUT_REMOVED_EVENT: + break; + case EMIX_SOURCE_OUTPUT_CHANGED_EVENT: + break; + } +} + static void _events_cb(void *data EINA_UNUSED, enum Emix_Event type, void *event_info) { @@ -610,6 +702,16 @@ _events_cb(void *data EINA_UNUSED, enum Emix_Event type, void *event_info) case EMIX_SINK_INPUT_CHANGED_EVENT: _sink_input_event(type, event_info); break; + case EMIX_SOURCE_ADDED_EVENT: + case EMIX_SOURCE_CHANGED_EVENT: + case EMIX_SOURCE_REMOVED_EVENT: + _source_event(type, event_info); + break; + case EMIX_SOURCE_OUTPUT_ADDED_EVENT: + case EMIX_SOURCE_OUTPUT_CHANGED_EVENT: + case EMIX_SOURCE_OUTPUT_REMOVED_EVENT: + _source_output_event(type, event_info); + break; default: break; @@ -988,7 +1090,7 @@ _e_client_add(void *data EINA_UNUSED, int type EINA_UNUSED, void *event) _client_sinks = eina_list_append(_client_sinks, sink); return ECORE_CALLBACK_PASS_ON; } - pid = _get_ppid(pid); + pid = backend_util_get_ppid(pid); } } return ECORE_CALLBACK_PASS_ON; @@ -1186,6 +1288,9 @@ backend_init(void) if (emix_sink_default_support()) _sink_default = emix_sink_default_get(); + if (emix_source_default_support()) + _source_default = emix_source_default_get(); + _actions_register(); _border_hook = e_int_client_menu_hook_add(_bd_hook, NULL); @@ -1348,3 +1453,77 @@ backend_sink_default_get(void) return _sink_default; } +////////////////////////////////////////////////////////////////////////////// + +EINTERN Eina_Bool +backend_source_active_get(void) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(_source_default, EINA_FALSE); + if (emix_source_outputs_get()) return EINA_TRUE; + return EINA_FALSE; +} + +EINTERN void +backend_source_volume_decrease(void) +{ + EINA_SAFETY_ON_NULL_RETURN(_source_default); + _volume_source_adjust(-VOLUME_STEP); +} + +EINTERN void +backend_source_volume_increase(void) +{ + EINA_SAFETY_ON_NULL_RETURN(_source_default); + _volume_source_adjust(VOLUME_STEP); +} + +EINTERN void +backend_source_mute_set(Eina_Bool mute) +{ + EINA_SAFETY_ON_NULL_RETURN(_source_default); + + DBG("Source default mute set %d", mute); + emix_source_mute_set((Emix_Source *)_source_default, mute); + emix_config_save_state_get(); + if (emix_config_save_get()) e_config_save_queue(); +} + +EINTERN Eina_Bool +backend_source_mute_get(void) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(_source_default, EINA_FALSE); + return _source_default->mute; +} + +EINTERN void +backend_source_volume_set(unsigned int volume) +{ + EINA_SAFETY_ON_NULL_RETURN(_source_default); + DBG("Sink default mute set %d", volume); + + VOLSET(volume, ((Emix_Source *)_source_default)->volume, + (Emix_Source *)_source_default, emix_source_volume_set); + emix_config_save_state_get(); + if (emix_config_save_get()) e_config_save_queue(); +} + +EINTERN unsigned int +backend_source_volume_get(void) +{ + unsigned int volume = 0, i; + + EINA_SAFETY_ON_NULL_RETURN_VAL(_source_default, 0); + for (i = 0; i < _source_default->volume.channel_count; i++) + volume += _source_default->volume.volumes[i]; + if (_source_default->volume.channel_count) + volume = volume / _source_default->volume.channel_count; + + DBG("Source default volume get %d", volume); + return volume; +} + +EINTERN const Emix_Source * +backend_source_default_get(void) +{ + return _source_default; +} diff --git a/src/modules/mixer/backend.h b/src/modules/mixer/backend.h index 9336ef111..90122f4d1 100644 --- a/src/modules/mixer/backend.h +++ b/src/modules/mixer/backend.h @@ -21,4 +21,15 @@ EINTERN Eina_Bool backend_mute_get(void); EINTERN void backend_sink_default_set(const Emix_Sink *s); EINTERN const Emix_Sink *backend_sink_default_get(void); +EINTERN Eina_Bool backend_source_active_get(void); +EINTERN void backend_source_volume_decrease(void); +EINTERN void backend_source_volume_increase(void); +EINTERN void backend_source_mute_set(Eina_Bool mute); +EINTERN Eina_Bool backend_source_mute_get(void); +EINTERN void backend_source_volume_set(unsigned int volume); +EINTERN unsigned int backend_source_volume_get(void); +EINTERN const Emix_Source *backend_source_default_get(void); + +EINTERN pid_t backend_util_get_ppid(pid_t pid); + #endif /* MIXER_GADGET_BACKEND */ diff --git a/src/modules/mixer/e_mod_main.c b/src/modules/mixer/e_mod_main.c index 8f608ca44..e85afccfc 100644 --- a/src/modules/mixer/e_mod_main.c +++ b/src/modules/mixer/e_mod_main.c @@ -51,6 +51,7 @@ typedef struct _Mon_Data Mon_Data; struct _Mon_Data { Emix_Sink *sink; + Emix_Source *source; Evas_Object *vu; Ecore_Animator *animator; float samp_max; @@ -70,9 +71,19 @@ struct _Instance Evas_Object *gadget; Evas_Object *list; Evas_Object *slider; + Evas_Object *slider_ic; Evas_Object *check; Evas_Object *vu; + Evas_Object *playback_box; + Evas_Object *recbox; + Evas_Object *recslider; + Evas_Object *reccheck; + Evas_Object *recbx; + Evas_Object *recic; + Evas_Object *recvu; + Evas_Object *recording_box; Mon_Data mon_data; + Mon_Data recmon_data; }; static Context *mixer_context = NULL; @@ -122,6 +133,29 @@ _sink_icon_find(const char *name) static void _sink_unmonitor(Instance *inst, Emix_Sink *s); static void _sink_monitor(Instance *inst, Emix_Sink *s); +static void _source_unmonitor(Instance *inst, Emix_Source *s); +static void _source_monitor(Instance *inst, Emix_Source *s); + +static void _popup_playback_box_refill(Instance *inst); +static void _popup_recording_fill(Instance *inst); + +static void +_cb_emix_event(void *data, enum Emix_Event event, void *event_info EINA_UNUSED) +{ + Instance *inst = data; + + if ((event == EMIX_SINK_INPUT_ADDED_EVENT) || + (event == EMIX_SINK_INPUT_REMOVED_EVENT)) + { + _popup_playback_box_refill(inst); + } + else if ((event == EMIX_SOURCE_OUTPUT_ADDED_EVENT) || + (event == EMIX_SOURCE_OUTPUT_REMOVED_EVENT)) + { + _popup_recording_fill(inst); + } +} + static Eina_Bool _cb_emix_monitor_update(void *data) { @@ -171,9 +205,70 @@ _cb_emix_sink_monitor_event(void *data, enum Emix_Event event, void *event_info) } } +static Eina_Bool +_cb_emix_source_monitor_update(void *data) +{ + Mon_Data *md = data; + + if (md->mon_update == 0) + { + md->mon_skips++; + if (md->mon_skips > 5) + { + elm_progressbar_value_set(md->vu, 0.0); + md->animator = NULL; + return EINA_FALSE; + } + return EINA_TRUE; + } + elm_progressbar_value_set(md->vu, md->samp_max); + md->mon_update = 0; + md->samp_max = 0; + md->mon_skips = 0; + md->mon_samps = 0; + return EINA_TRUE; +} + +static void +_cb_emix_source_monitor_event(void *data, enum Emix_Event event, void *event_info) +{ + Mon_Data *md = data; + Emix_Source *source = event_info; + + if (source != md->source) return; + if (event == EMIX_SOURCE_MONITOR_EVENT) + { + unsigned int i, num = source->mon_num * 2; + float samp, max = 0.0; + + for (i = 0; i < num; i++) + { + samp = fabs(source->mon_buf[i]); + if (samp > max) max = samp; + } + md->mon_samps += num; + if (md->samp_max < max) md->samp_max = max; + md->mon_update++; + if (!md->animator) + md->animator = ecore_animator_add(_cb_emix_source_monitor_update, md); + } +} + static void _mixer_popup_update(Instance *inst, int mute, int vol) { + Emix_Sink *s; + char *icname = NULL; + + s = (Emix_Sink *)backend_sink_default_get(); + if (s) + { + if (s->name) icname = _sink_icon_find(s->name); + if (!icname) icname = strdup("audio-volume"); + + elm_icon_standard_set(inst->slider_ic, icname); + free(icname); + } elm_check_state_set(inst->check, !!mute); elm_slider_value_set(inst->slider, vol); } @@ -235,9 +330,18 @@ static void _popup_del(Instance *inst) { inst->slider = NULL; + inst->slider_ic = NULL; inst->check = NULL; inst->list = NULL; inst->vu = NULL; + inst->playback_box = NULL; + inst->recbox = NULL; + inst->recslider = NULL; + inst->reccheck = NULL; + inst->recbx = NULL; + inst->recvu = NULL; + inst->recording_box = NULL; + emix_event_callback_del(_cb_emix_event, inst); if (inst->mon_data.sink) _sink_unmonitor(inst, inst->mon_data.sink); E_FREE_FUNC(inst->popup, e_object_del); } @@ -321,6 +425,39 @@ _sink_monitor(Instance *inst, Emix_Sink *s) emix_sink_monitor(md->sink, EINA_TRUE); } +static void +_source_unmonitor(Instance *inst, Emix_Source *s) +{ + Mon_Data *md = &(inst->recmon_data); + if (md->source != s) return; + emix_event_callback_del(_cb_emix_source_monitor_event, md); + if (md->animator) + { + ecore_animator_del(md->animator); + md->animator = NULL; + } + emix_source_monitor(md->source, EINA_FALSE); + md->source = NULL; + md->vu = NULL; + md->mon_update = 0; + md->samp_max = 0; + md->mon_skips = 0; + md->mon_samps = 0; +} + +static void +_source_monitor(Instance *inst, Emix_Source *s) +{ + Mon_Data *md = &(inst->recmon_data); + if (md->source == s) return; + + if (md->source) _source_unmonitor(inst, md->source); + md->source = s; + md->vu = inst->recvu; + emix_event_callback_add(_cb_emix_source_monitor_event, md); + emix_source_monitor(md->source, EINA_TRUE); +} + static Eina_Bool _mixer_sinks_changed(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED) { @@ -372,13 +509,260 @@ _cb_vu_format_cb(double v EINA_UNUSED) return ""; } +static void +_popup_playback_box_refill(Instance *inst) +{ + Evas_Object *ic; + Eina_List *children, *l, *del_list = NULL; + Eina_List *playbacks; + Evas_Object *o; + Emix_Sink_Input *s; + int num = 0; + + children = elm_box_children_get(inst->playback_box); + EINA_LIST_FOREACH(children, l, o) + { + // skip first item - it's the rect spacer + if (l->prev) del_list = eina_list_append(del_list, o); + } + EINA_LIST_FREE(del_list, o) evas_object_del(o); + playbacks = (Eina_List *)emix_sink_inputs_get(); + printf("MX: playbacks %p\n", playbacks); + EINA_LIST_FOREACH(playbacks, l, s) + { + E_Client *ec = NULL; + Eina_List *clients, *ll; + pid_t pid; + + ic = NULL; + pid = s->pid; + printf("MX: + PID %i\n", pid); + for (;;) + { + if ((pid <= 1) || (pid == getpid())) return; + + clients = e_client_focus_stack_get(); + EINA_LIST_FOREACH(clients, ll, ec) + { + if ((ec->netwm.pid == pid) && (!ec->parent)) + { + ic = e_client_icon_add(ec, e_comp->evas); + break; + } + } + pid = backend_util_get_ppid(pid); + if (ic) break; + } + if (!ic) + { + if (s->icon) + { + ic = elm_icon_add(e_comp->elm); + elm_icon_standard_set(ic, s->icon); + } + } + if (ic) + { + printf("MX: + %p\n", ic); + evas_object_size_hint_min_set(ic, 20 * e_scale, 20 * e_scale); + elm_box_pack_end(inst->playback_box, ic); + evas_object_show(ic); + } + + // max 8 app icons + num++; + if (num > 8) break; + } +} + +static void +_reccheck_changed_cb(void *data EINA_UNUSED, Evas_Object *obj, + void *event EINA_UNUSED) +{ + backend_source_mute_set(elm_check_state_get(obj)); +} + +static void +_recslider_changed_cb(void *data EINA_UNUSED, Evas_Object *obj, + void *event EINA_UNUSED) +{ + int val; + + val = (int)elm_slider_value_get(obj); + backend_source_volume_set(val); +} + +static void +_popup_recording_box_refill(Instance *inst) +{ + Evas_Object *ic; + Eina_List *children, *l, *del_list = NULL; + Eina_List *recordings; + Evas_Object *o; + Emix_Source_Output *s; + int num = 0; + + children = elm_box_children_get(inst->recording_box); + EINA_LIST_FOREACH(children, l, o) + { + // skip first item - it's the rect spacer + if (l->prev) del_list = eina_list_append(del_list, o); + } + EINA_LIST_FREE(del_list, o) evas_object_del(o); + recordings = (Eina_List *)emix_source_outputs_get(); + EINA_LIST_FOREACH(recordings, l, s) + { + E_Client *ec = NULL; + Eina_List *clients, *ll; + pid_t pid; + + ic = NULL; + pid = s->pid; + for (;;) + { + if ((pid <= 1) || (pid == getpid())) return; + + clients = e_client_focus_stack_get(); + EINA_LIST_FOREACH(clients, ll, ec) + { + if ((ec->netwm.pid == pid) && (!ec->parent)) + { + ic = e_client_icon_add(ec, e_comp->evas); + break; + } + } + pid = backend_util_get_ppid(pid); + if (ic) break; + } + if (!ic) + { + if (s->icon) + { + ic = elm_icon_add(e_comp->elm); + elm_icon_standard_set(ic, s->icon); + } + } + if (ic) + { + evas_object_size_hint_min_set(ic, 20 * e_scale, 20 * e_scale); + elm_box_pack_end(inst->recording_box, ic); + evas_object_show(ic); + } + + // max 8 app icons + num++; + if (num > 8) break; + } +} + +static void +_popup_recording_fill(Instance *inst) +{ + Emix_Source *ss; + + if (inst->recording_box) evas_object_del(inst->recording_box); + if (inst->recvu) evas_object_del(inst->recvu); + if (inst->recic) evas_object_del(inst->recic); + if (inst->recbx) evas_object_del(inst->recbx); + if (inst->reccheck) evas_object_del(inst->reccheck); + if (inst->recslider) evas_object_del(inst->recslider); + + inst->recslider = NULL; + inst->reccheck = NULL; + inst->recbx = NULL; + inst->recvu = NULL; + inst->recording_box = NULL; + + ss = (Emix_Source *)backend_source_default_get(); + if (ss) _source_unmonitor(inst, ss); + + if (backend_source_active_get()) + { + Evas_Object *bx, *r, *slider, *ic; + + bx = elm_box_add(e_comp->elm); + 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.5); + elm_box_pack_end(inst->recbox, bx); + evas_object_show(bx); + + inst->recvu = elm_progressbar_add(e_comp->elm); + elm_progressbar_unit_format_function_set(inst->recvu, _cb_vu_format_cb, NULL); + evas_object_size_hint_weight_set(inst->recvu, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(inst->recvu, EVAS_HINT_FILL, 0.5); + elm_box_pack_end(bx, inst->recvu); + evas_object_show(inst->recvu); + + inst->recording_box = elm_box_add(e_comp->elm); + elm_box_horizontal_set(inst->recording_box, EINA_TRUE); + evas_object_size_hint_weight_set(inst->recording_box, 0.0, 0.0); + evas_object_size_hint_align_set(inst->recording_box, 1.0, 0.5); + elm_box_pack_end(bx, inst->recording_box); + evas_object_show(inst->recording_box); + + r = evas_object_rectangle_add(evas_object_evas_get(e_comp->elm)); + evas_object_size_hint_min_set(r, 0, 20 * e_scale); + evas_object_color_set(r, 0, 0, 0, 0); + elm_box_pack_end(inst->recording_box, r); + + _popup_recording_box_refill(inst); + + bx = elm_box_add(e_comp->elm); + inst->recbx = bx; + 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(inst->recbox, bx); + evas_object_show(bx); + + ss = (Emix_Source *)backend_source_default_get(); + if (ss) + { + ic = elm_icon_add(e_comp->elm); + inst->recic = ic; + evas_object_size_hint_min_set(ic, 20 * e_scale, 20 * e_scale); + elm_icon_standard_set(ic, "audio-input-microphone"); + elm_box_pack_end(bx, ic); + evas_object_show(ic); + } + + slider = elm_slider_add(e_comp->elm); + inst->recslider = slider; + elm_slider_span_size_set(slider, 128 * elm_config_scale_get()); + elm_slider_unit_format_set(slider, "%1.0f"); + elm_slider_indicator_format_set(slider, "%1.0f"); + evas_object_size_hint_align_set(slider, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_size_hint_weight_set(slider, EVAS_HINT_EXPAND, 0.0); + evas_object_show(slider); + elm_slider_min_max_set(slider, 0.0, emix_max_volume_get()); + evas_object_smart_callback_add(slider, "changed", _recslider_changed_cb, NULL); + elm_slider_value_set(slider, backend_source_volume_get()); + elm_box_pack_end(bx, slider); + evas_object_show(slider); + + inst->reccheck = elm_check_add(e_comp->elm); + evas_object_size_hint_align_set(inst->reccheck, 0.5, EVAS_HINT_FILL); + elm_object_text_set(inst->reccheck, _("Mute")); + elm_check_state_set(inst->reccheck, backend_source_mute_get()); + evas_object_smart_callback_add(inst->reccheck, "changed", _reccheck_changed_cb, NULL); + elm_box_pack_end(bx, inst->reccheck); + evas_object_show(inst->reccheck); + + if (ss) _source_monitor(inst, ss); + } +} + static void _popup_new(Instance *inst) { - Evas_Object *button, *list, *slider, *bx, *ic; + Evas_Object *button, *list, *slider, *bx, *ic, *r; Emix_Sink *s; Eina_List *l; Elm_Object_Item *default_it = NULL; + char *icname = NULL; + + emix_event_callback_add(_cb_emix_event, inst); inst->popup = e_gadcon_popup_new(inst->gcc, 0); list = elm_box_add(e_comp->elm); @@ -391,19 +775,55 @@ _popup_new(Instance *inst) elm_box_pack_end(list, inst->list); bx = elm_box_add(e_comp->elm); + 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.5); + elm_box_pack_end(list, bx); + evas_object_show(bx); + inst->vu = elm_progressbar_add(e_comp->elm); elm_progressbar_unit_format_function_set(inst->vu, _cb_vu_format_cb, NULL); evas_object_size_hint_weight_set(inst->vu, EVAS_HINT_EXPAND, 0.0); - evas_object_size_hint_align_set(inst->vu, EVAS_HINT_FILL, 0.0); - elm_box_pack_end(list, inst->vu); + evas_object_size_hint_align_set(inst->vu, EVAS_HINT_FILL, 0.5); + elm_box_pack_end(bx, inst->vu); evas_object_show(inst->vu); + inst->playback_box = elm_box_add(e_comp->elm); + elm_box_horizontal_set(inst->playback_box, EINA_TRUE); + evas_object_size_hint_weight_set(inst->playback_box, 0.0, 0.0); + evas_object_size_hint_align_set(inst->playback_box, 1.0, 0.5); + elm_box_pack_end(bx, inst->playback_box); + evas_object_show(inst->playback_box); + + r = evas_object_rectangle_add(evas_object_evas_get(e_comp->elm)); + evas_object_size_hint_min_set(r, 0, 20 * e_scale); + evas_object_color_set(r, 0, 0, 0, 0); + elm_box_pack_end(inst->playback_box, r); + + _popup_playback_box_refill(inst); + + bx = elm_box_add(e_comp->elm); 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(list, bx); evas_object_show(bx); + s = (Emix_Sink *)backend_sink_default_get(); + if (s) + { + if (s->name) icname = _sink_icon_find(s->name); + if (!icname) icname = strdup("audio-volume"); + + ic = elm_icon_add(e_comp->elm); + evas_object_size_hint_min_set(ic, 20 * e_scale, 20 * e_scale); + elm_icon_standard_set(ic, icname); + free(icname); + elm_box_pack_end(bx, ic); + evas_object_show(ic); + inst->slider_ic = ic; + } + slider = elm_slider_add(e_comp->elm); inst->slider = slider; elm_slider_span_size_set(slider, 128 * elm_config_scale_get()); @@ -427,6 +847,14 @@ _popup_new(Instance *inst) elm_box_pack_end(bx, inst->check); evas_object_show(inst->check); + inst->recbox = elm_box_add(e_comp->elm); + evas_object_size_hint_align_set(inst->recbox, EVAS_HINT_FILL, 0.5); + evas_object_size_hint_weight_set(inst->recbox, EVAS_HINT_EXPAND, 0.0); + elm_box_pack_end(list, inst->recbox); + evas_object_show(inst->recbox); + + _popup_recording_fill(inst); + button = elm_button_add(e_comp->elm); evas_object_size_hint_align_set(button, EVAS_HINT_FILL, EVAS_HINT_FILL); evas_object_size_hint_weight_set(button, EVAS_HINT_EXPAND, 0.0); @@ -438,7 +866,6 @@ _popup_new(Instance *inst) EINA_LIST_FOREACH((Eina_List *)emix_sinks_get(), l, s) { Elm_Object_Item *it; - char *icname = NULL; if (s->name) icname = _sink_icon_find(s->name); if (!icname) icname = strdup("audio-volume"); @@ -456,7 +883,7 @@ _popup_new(Instance *inst) } elm_list_go(inst->list); - evas_object_size_hint_min_set(list, 240 * e_scale, 240 * e_scale); + evas_object_size_hint_min_set(list, 240 * e_scale, 280 * e_scale); e_gadcon_popup_content_set(inst->popup, list); e_comp_object_util_autoclose(inst->popup->comp_object, diff --git a/src/modules/mixer/lib/backends/alsa/alsa.c b/src/modules/mixer/lib/backends/alsa/alsa.c index 465f08094..867c0ea61 100644 --- a/src/modules/mixer/lib/backends/alsa/alsa.c +++ b/src/modules/mixer/lib/backends/alsa/alsa.c @@ -525,22 +525,26 @@ _alsa_backend = _alsa_sink_volume_set, /*volume_set*/ NULL, /* port set */ _alsa_support, /*change support*/ - NULL, /*sink input get*/ - NULL,/*sink input mute set*/ - NULL,/*sink input volume set*/ - NULL,/*sink input sink change*/ + NULL, /*sink inputs get*/ + NULL, /*sink input mute set*/ + NULL, /*sink input volume set*/ + NULL, /*sink input sink change*/ _alsa_sources_get,/*source*/ _alsa_support, /* source default support*/ NULL, /*get*/ NULL, /*set*/ _alsa_sources_mute_set,/* source mute set */ _alsa_sources_volume_set, /* source volume set */ + NULL, /*source outputs get*/ + NULL, /*source output mute set*/ + NULL, /*source output volume set*/ + NULL, /*source output source change*/ NULL, /* advanced options */ NULL, /* card list */ NULL, /* card profile set */ NULL, /* sink monitor set */ NULL, /* sink input monitor set */ - NULL /* ssource monitor set */ + NULL /* source monitor 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 521a17abf..7ac3be0de 100644 --- a/src/modules/mixer/lib/backends/pulseaudio/pulse.c +++ b/src/modules/mixer/lib/backends/pulseaudio/pulse.c @@ -26,7 +26,7 @@ typedef struct _Context const void *userdata; Ecore_Timer *connect; - Eina_List *sinks, *sources, *inputs, *cards; + Eina_List *sinks, *sources, *inputs, *outputs, *cards; Eina_Bool connected; } Context; @@ -59,6 +59,13 @@ typedef struct _Source pa_stream *mon_stream; } Source; +typedef struct _Source_Output +{ + Emix_Source_Output base; + int idx, source_idx; + Eina_Bool running : 1; +} Source_Output; + typedef struct _Profile { Emix_Profile base; @@ -168,6 +175,21 @@ _source_del(Source *source) free(source); } +static void +_source_output_del(Source_Output *output) +{ + unsigned int i; + EINA_SAFETY_ON_NULL_RETURN(output); + + free(output->base.volume.volumes); + for(i = 0; i < output->base.volume.channel_count; ++i) + eina_stringshare_del(output->base.volume.channel_names[i]); + free(output->base.volume.channel_names); + eina_stringshare_del(output->base.name); + eina_stringshare_del(output->base.icon); + free(output); +} + static void _card_del(Card *card) { @@ -728,6 +750,189 @@ _source_remove_cb(int index, void *data EINA_UNUSED) } } +static void +_source_output_state_running_set(Source_Output *output, Eina_Bool running) +{ + output->running = running; +} + +static void +_source_output_cb(pa_context *c EINA_UNUSED, const pa_source_output_info *info, + int eol, void *userdata EINA_UNUSED) +{ + Source_Output *output; + Eina_List *l; + Source *s; + const char *t; + unsigned int i; + EINA_SAFETY_ON_NULL_RETURN(ctx); + + if (eol < 0) + { + if (pa_context_errno(c) == PA_ERR_NOENTITY) + return; + + ERR("Source output callback failure"); + return; + } + + if (eol > 0) + return; + + if ((info->name) && (!strcmp(info->name, "__e_mon"))) return; + + output = calloc(1, sizeof(Source_Output)); + EINA_SAFETY_ON_NULL_RETURN(output); + + DBG("source output index: %d\nsink input name: %s", info->index, + info->name); + + output->idx = info->index; + output->source_idx = info->source; + + Eina_Strbuf *output_name; + + output_name = eina_strbuf_new(); + const char *application = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_NAME); + if (application) + { + eina_strbuf_append(output_name, application); + eina_strbuf_append(output_name, ":"); + eina_strbuf_append(output_name, info->name); + } + else if (info->name) + { + eina_strbuf_append(output_name, info->name); + } + output->base.name = eina_stringshare_add(eina_strbuf_string_get(output_name)); + eina_strbuf_free(output_name); + _pa_cvolume_convert(&info->volume, &output->base.volume); + output->base.volume.channel_names = calloc(output->base.volume.channel_count, sizeof(Emix_Channel)); + for (i = 0; i < output->base.volume.channel_count; ++i) + output->base.volume.channel_names[i] = eina_stringshare_add(pa_channel_position_to_pretty_string(info->channel_map.map[i])); + output->base.mute = !!info->mute; + EINA_LIST_FOREACH(ctx->sources, l, s) + { + if (s->idx == (int)info->source) + output->base.source = (Emix_Source *)s; + } + output->base.icon = eina_stringshare_add(_icon_from_properties(info->proplist)); + ctx->outputs = eina_list_append(ctx->outputs, output); + + if ((t = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_PROCESS_ID))) + { + output->base.pid = atoi(t); + } + if (!info->corked) _source_output_state_running_set(output, EINA_TRUE); + else _source_output_state_running_set(output, EINA_FALSE); + + if (ctx->cb) + ctx->cb((void *)ctx->userdata, EMIX_SOURCE_OUTPUT_ADDED_EVENT, + (Emix_Source_Output *)output); +} + +static void +_source_output_changed_cb(pa_context *c EINA_UNUSED, + const pa_source_output_info *info, int eol, + void *userdata EINA_UNUSED) +{ + Source_Output *output = NULL, *so; + Source *s = NULL; + Eina_List *l; + const char *t; + unsigned int i; + + EINA_SAFETY_ON_NULL_RETURN(ctx); + if (eol < 0) + { + if (pa_context_errno(c) == PA_ERR_NOENTITY) + return; + + ERR("Source output changed callback failure"); + return; + } + + if (eol > 0) + return; + + if ((info->name) && (!strcmp(info->name, "__e_mon"))) return; + + EINA_LIST_FOREACH(ctx->outputs, l, so) + { + if (so->idx == (int)info->index) + { + output = so; + break; + } + } + + DBG("source output changed index: %d\n", info->index); + + if (!output) + { + output = calloc(1, sizeof(Source_Output)); + EINA_SAFETY_ON_NULL_RETURN(output); + ctx->outputs = eina_list_append(ctx->outputs, output); + } + output->idx = info->index; + output->source_idx = info->source; + if (output->base.volume.channel_count != info->volume.channels) + { + for (i = 0; i < output->base.volume.channel_count; ++i) + eina_stringshare_del(output->base.volume.channel_names[i]); + free(output->base.volume.channel_names); + output->base.volume.channel_names = calloc(info->volume.channels, sizeof(Emix_Channel)); + } + _pa_cvolume_convert(&info->volume, &output->base.volume); + for (i = 0; i < output->base.volume.channel_count; ++i) + eina_stringshare_replace(&output->base.volume.channel_names[i], + pa_channel_position_to_pretty_string(info->channel_map.map[i])); + + output->base.mute = !!info->mute; + + EINA_LIST_FOREACH(ctx->sources, l, s) + { + if (s->idx == (int)info->source) + output->base.source = (Emix_Source *)s; + } + if ((t = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_PROCESS_ID))) + { + output->base.pid = atoi(t); + } + + if (!info->corked) _source_output_state_running_set(output, EINA_TRUE); + else _source_output_state_running_set(output, EINA_FALSE); + + if (ctx->cb) + ctx->cb((void *)ctx->userdata, EMIX_SOURCE_OUTPUT_CHANGED_EVENT, + (Emix_Source_Output *)output); +} + +static void +_source_output_remove_cb(int index, void *data EINA_UNUSED) +{ + Source_Output *output; + Eina_List *l; + EINA_SAFETY_ON_NULL_RETURN(ctx); + + DBG("Removing source output: %d", index); + + EINA_LIST_FOREACH(ctx->outputs, l, output) + { + if (output->idx == index) + { + ctx->outputs = eina_list_remove_list(ctx->outputs, l); + if (ctx->cb) + ctx->cb((void *)ctx->userdata, + EMIX_SOURCE_OUTPUT_REMOVED_EVENT, + (Emix_Source_Output *)output); + _source_output_del(output); + + break; + } + } +} + static void _sink_default_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol, void *userdata EINA_UNUSED) @@ -1093,6 +1298,33 @@ _subscribe_cb(pa_context *c, pa_subscription_event_type_t t, pa_operation_unref(o); } break; + case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == + PA_SUBSCRIPTION_EVENT_REMOVE) + _source_output_remove_cb(index, data); + else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == + PA_SUBSCRIPTION_EVENT_NEW) + { + if (!(o = pa_context_get_source_output_info(c, index, + _source_output_cb, data))) + { + ERR("pa_context_get_source_info() failed"); + return; + } + pa_operation_unref(o); + } + else + { + if (!(o = pa_context_get_source_output_info(c, index, + _source_output_changed_cb, + data))) + { + ERR("pa_context_get_source_info() failed"); + return; + } + pa_operation_unref(o); + } + break; case PA_SUBSCRIPTION_EVENT_SERVER: if (!(o = pa_context_get_server_info(c, _server_info_cb, data))) @@ -1195,6 +1427,14 @@ _pulse_pa_state_cb(pa_context *context, void *data) } pa_operation_unref(o); + if (!(o = pa_context_get_source_output_info_list(context, _source_output_cb, + ctx))) + { + ERR("pa_context_get_source_output_info_list() failed"); + return; + } + pa_operation_unref(o); + if (!(o = pa_context_get_server_info(context, _server_info_cb, ctx))) { @@ -1405,13 +1645,6 @@ _sources_get(void) return ctx->sources; } -static const Eina_List * -_sink_inputs_get(void) -{ - EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL); - return ctx->inputs; -} - static void _sink_volume_set(Emix_Sink *sink, Emix_Volume *volume) { @@ -1437,6 +1670,13 @@ _sink_mute_set(Emix_Sink *sink, Eina_Bool mute) ERR("pa_context_set_sink_mute() failed"); } +static const Eina_List * +_sink_inputs_get(void) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL); + return ctx->inputs; +} + static void _sink_input_volume_set(Emix_Sink_Input *input, Emix_Volume *volume) { @@ -1479,6 +1719,55 @@ _sink_input_move(Emix_Sink_Input *input, Emix_Sink *sink) ERR("pa_context_move_sink_input_by_index() failed"); } +static const Eina_List * +_source_outputs_get(void) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL); + return ctx->outputs; +} + +static void +_source_output_volume_set(Emix_Source_Output *output, Emix_Volume *volume) +{ + pa_operation* o; + Source_Output *source_output = (Source_Output *)output; + pa_cvolume vol = _emix_volume_convert(volume); + EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && output != NULL); + + if (!(o = pa_context_set_source_output_volume(ctx->context, + source_output->idx, &vol, + NULL, NULL))) + ERR("pa_context_set_source_output_volume_by_index() failed"); +} + +static void +_source_output_mute_set(Emix_Source_Output *output, Eina_Bool mute) +{ + pa_operation* o; + Source_Output *source_output = (Source_Output *)output; + EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && output != NULL); + + if (!(o = pa_context_set_source_output_mute(ctx->context, + source_output->idx, mute, + NULL, NULL))) + ERR("pa_context_set_source_output_mute() failed"); +} + +static void +_source_output_move(Emix_Source_Output *output, Emix_Source *source) +{ + pa_operation* o; + Source *s = (Source *)source; + Source_Output *i = (Source_Output *)output; + EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && output != NULL + && source != NULL); + + if (!(o = pa_context_move_source_output_by_index(ctx->context, + i->idx, s->idx, NULL, + NULL))) + ERR("pa_context_move_source_output_by_index() failed"); +} + static Eina_Bool _sink_port_set(Emix_Sink *sink, const Emix_Port *port) { @@ -1868,6 +2157,10 @@ _pulseaudio_backend = _source_default_set, _source_mute_set, _source_volume_set, + _source_outputs_get, + _source_output_mute_set, + _source_output_volume_set, + _source_output_move, NULL, _cards_get, _card_profile_set, diff --git a/src/modules/mixer/lib/emix.c b/src/modules/mixer/lib/emix.c index 5fb83aa0e..0509929c5 100644 --- a/src/modules/mixer/lib/emix.c +++ b/src/modules/mixer/lib/emix.c @@ -395,6 +395,45 @@ emix_source_volume_set(Emix_Source *source, Emix_Volume *volume) ctx->loaded->ebackend_source_volume_set(source, volume); } +const Eina_List* +emix_source_outputs_get(void) +{ + EINA_SAFETY_ON_FALSE_RETURN_VAL((ctx && ctx->loaded && + ctx->loaded->ebackend_source_outputs_get), + NULL); + return ctx->loaded->ebackend_source_outputs_get(); +} + +void +emix_source_output_mute_set(Emix_Source_Output *output, Eina_Bool mute) +{ + EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->loaded && + ctx->loaded->ebackend_source_output_mute_set && + output)); + + ctx->loaded->ebackend_source_output_mute_set(output, mute); +} + +void +emix_source_output_volume_set(Emix_Source_Output *output, Emix_Volume *volume) +{ + EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->loaded && + ctx->loaded->ebackend_source_output_volume_set && + output)); + + ctx->loaded->ebackend_source_output_volume_set(output, volume); +} + +void +emix_source_output_sink_change(Emix_Source_Output *output, Emix_Source *source) +{ + EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->loaded && + ctx->loaded->ebackend_source_output_source_change && + output && source)); + + ctx->loaded->ebackend_source_output_source_change(output, source); +} + Evas_Object * emix_advanced_options_add(Evas_Object *parent) { diff --git a/src/modules/mixer/lib/emix.h b/src/modules/mixer/lib/emix.h index 1e1387937..db9c14cd4 100644 --- a/src/modules/mixer/lib/emix.h +++ b/src/modules/mixer/lib/emix.h @@ -37,6 +37,9 @@ enum Emix_Event { EMIX_SOURCE_ADDED_EVENT, EMIX_SOURCE_REMOVED_EVENT, EMIX_SOURCE_CHANGED_EVENT, + EMIX_SOURCE_OUTPUT_ADDED_EVENT, + EMIX_SOURCE_OUTPUT_REMOVED_EVENT, + EMIX_SOURCE_OUTPUT_CHANGED_EVENT, EMIX_CARD_ADDED_EVENT, EMIX_CARD_REMOVED_EVENT, EMIX_CARD_CHANGED_EVENT, @@ -91,6 +94,15 @@ typedef struct _Emix_Source { const float *mon_buf; // LRLRLR unsigned char samples } Emix_Source; +typedef struct _Emix_Source_Output { + const char *name; + Emix_Volume volume; + Eina_Bool mute; + Emix_Source *source; + pid_t pid; + const char *icon; +} Emix_Source_Output; + typedef struct _Emix_Profile { const char *name; const char *description; @@ -141,6 +153,14 @@ typedef struct _Emix_Backend { void (*ebackend_source_volume_set)(Emix_Source *source, Emix_Volume *volume); + const Eina_List* (*ebackend_source_outputs_get)(void); + void (*ebackend_source_output_mute_set)( + Emix_Source_Output *output, Eina_Bool mute); + void (*ebackend_source_output_volume_set)( + Emix_Source_Output *output, Emix_Volume *volume); + void (*ebackend_source_output_source_change)( + Emix_Source_Output *output, Emix_Source *source); + 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); @@ -208,6 +228,15 @@ E_API void emix_source_mute_set(Emix_Source *source, Eina_Bool mute); E_API void emix_source_volume_set(Emix_Source *source, Emix_Volume *volume); + +E_API const Eina_List* emix_source_outputs_get(void); +E_API void emix_source_output_mute_set(Emix_Source_Output *output, + Eina_Bool mute); +E_API void emix_source_output_volume_set(Emix_Source_Output *output, + Emix_Volume *volume); +E_API void emix_source_output_sink_change(Emix_Source_Output *output, + Emix_Source *source); + E_API Evas_Object* emix_advanced_options_add(Evas_Object *parent); E_API const Eina_List* emix_cards_get(void);