raster pushed a commit to branch master.

http://git.enlightenment.org/core/enlightenment.git/commit/?id=9376490ba8980ca6016c5b9f87dd91902688bb37

commit 9376490ba8980ca6016c5b9f87dd91902688bb37
Author: Carsten Haitzler (Rasterman) <ras...@rasterman.com>
Date:   Mon Jun 7 15:18:06 2021 +0100

    mixer - show a mic/recording slider if someothing is and icons
    
    so now display icons for clients/apps that are playing anything back
    right now next to the vu meter. also if some app is recording
    something, display a slider for mic/input source volume and a list of
    icons that are recording too and a vu meter too for this (95% of the
    time things wont record so hide it then and only show when something
    does).
---
 src/modules/mixer/backend.c                       | 219 ++++++++++-
 src/modules/mixer/backend.h                       |  11 +
 src/modules/mixer/e_mod_main.c                    | 437 +++++++++++++++++++++-
 src/modules/mixer/lib/backends/alsa/alsa.c        |  14 +-
 src/modules/mixer/lib/backends/pulseaudio/pulse.c | 309 ++++++++++++++-
 src/modules/mixer/lib/emix.c                      |  39 ++
 src/modules/mixer/lib/emix.h                      |  29 ++
 7 files changed, 1020 insertions(+), 38 deletions(-)

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);

-- 


Reply via email to