enlightenment/src/modules/mixer/lib/backends/pulseaudio/pulse.c

2187 lines
60 KiB
C

#include "e.h"
#include "emix.h"
#include <pulse/pulseaudio.h>
#undef ERR
#undef DBG
#undef WRN
#define ERR(...) EINA_LOG_ERR(__VA_ARGS__)
#define DBG(...) EINA_LOG_DBG(__VA_ARGS__)
#define WRN(...) EINA_LOG_WARN(__VA_ARGS__)
#define PA_VOLUME_TO_INT(_vol) \
(((_vol * EMIX_VOLUME_BARRIER) / \
(double)PA_VOLUME_NORM) + 0.5)
#define INT_TO_PA_VOLUME(_vol) \
(((PA_VOLUME_NORM * _vol) / \
(double)EMIX_VOLUME_BARRIER) + 0.5)
typedef struct _Context
{
pa_mainloop_api api;
pa_context *context;
pa_context_state_t state;
Emix_Event_Cb cb;
const void *userdata;
Ecore_Timer *connect;
Eina_List *sinks, *sources, *inputs, *outputs, *cards;
Eina_Bool connected;
} Context;
typedef struct _Sink
{
Emix_Sink base;
int idx, monitor_idx;
const char *pulse_name;
const char *monitor_source_name;
int mon_count;
pa_stream *mon_stream;
Eina_Bool running : 1;
} Sink;
typedef struct _Sink_Input
{
Emix_Sink_Input base;
int idx, sink_idx;
int mon_count;
pa_stream *mon_stream;
Eina_Bool running : 1;
} Sink_Input;
typedef struct _Source
{
Emix_Source base;
int idx;
const char *pulse_name;
int mon_count;
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;
uint32_t priority;
} Profile;
typedef struct _Card
{
Emix_Card base;
int idx;
} Card;
static Context *ctx = NULL;
extern pa_mainloop_api functable;
static void _sink_monitor_begin(Sink *sink);
static void _sink_monitor_end(Sink *sink);
static void _sink_input_monitor_begin(Sink_Input *i);
static void _sink_input_monitor_end(Sink_Input *i);
static void _source_monitor_begin(Source *s);
static void _source_monitor_end(Source *s);
static pa_cvolume
_emix_volume_convert(const Emix_Volume *volume)
{
pa_cvolume vol;
unsigned int i;
vol.channels = volume->channel_count;
for (i = 0; i < volume->channel_count; i++)
vol.values[i] = INT_TO_PA_VOLUME(volume->volumes[i]);
return vol;
}
static void
_pa_cvolume_convert(const pa_cvolume *volume, Emix_Volume *vol)
{
int i;
if (vol->volumes) free(vol->volumes);
vol->volumes = calloc(volume->channels, sizeof(int));
if (!vol->volumes)
{
WRN("Could not allocate memory for volume");
vol->channel_count = 0;
return;
}
vol->channel_count = volume->channels;
for (i = 0; i < volume->channels; i++)
vol->volumes[i] = PA_VOLUME_TO_INT(volume->values[i]);
}
static void
_sink_del(Sink *sink)
{
unsigned int i;
Emix_Port *port;
EINA_SAFETY_ON_NULL_RETURN(sink);
EINA_LIST_FREE(sink->base.ports, port)
{
eina_stringshare_del(port->name);
eina_stringshare_del(port->description);
free(port);
}
free(sink->base.volume.volumes);
for(i = 0; i < sink->base.volume.channel_count; ++i)
eina_stringshare_del(sink->base.volume.channel_names[i]);
free(sink->base.volume.channel_names);
eina_stringshare_del(sink->base.name);
eina_stringshare_del(sink->pulse_name);
eina_stringshare_del(sink->monitor_source_name);
if (sink->mon_stream) pa_stream_disconnect(sink->mon_stream);
free(sink);
}
static void
_sink_input_del(Sink_Input *input)
{
unsigned int i;
EINA_SAFETY_ON_NULL_RETURN(input);
free(input->base.volume.volumes);
for(i = 0; i < input->base.volume.channel_count; ++i)
eina_stringshare_del(input->base.volume.channel_names[i]);
free(input->base.volume.channel_names);
eina_stringshare_del(input->base.name);
eina_stringshare_del(input->base.icon);
if (input->mon_stream) pa_stream_disconnect(input->mon_stream);
free(input);
}
static void
_source_del(Source *source)
{
unsigned int i;
EINA_SAFETY_ON_NULL_RETURN(source);
free(source->base.volume.volumes);
for(i = 0; i < source->base.volume.channel_count; ++i)
eina_stringshare_del(source->base.volume.channel_names[i]);
free(source->base.volume.channel_names);
eina_stringshare_del(source->base.name);
eina_stringshare_del(source->pulse_name);
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)
{
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_state_running_set(Sink *sink, Eina_Bool running)
{
if (running)
{
if ((!sink->running) && (sink->mon_count > 0))
{
sink->running = running;
_sink_monitor_begin(sink);
return;
}
}
else
{
if ((sink->running) && (sink->mon_count > 0))
{
sink->running = running;
_sink_monitor_end(sink);
return;
}
}
sink->running = running;
}
static void
_sink_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol,
void *userdata EINA_UNUSED)
{
Sink *sink;
Emix_Port *port;
uint32_t i;
if (eol < 0)
{
if (pa_context_errno(c) == PA_ERR_NOENTITY)
return;
ERR("Sink callback failure");
return;
}
if (eol > 0)
return;
DBG("sink index: %d\nsink name: %s", info->index,
info->name);
sink = calloc(1, sizeof(Sink));
sink->idx = info->index;
sink->monitor_idx = info->monitor_source;
sink->pulse_name = eina_stringshare_add(info->name);
sink->base.name = eina_stringshare_add(info->description);
_pa_cvolume_convert(&info->volume, &sink->base.volume);
sink->base.volume.channel_names = calloc(sink->base.volume.channel_count, sizeof(Emix_Channel));
for (i = 0; i < sink->base.volume.channel_count; ++i)
sink->base.volume.channel_names[i] = eina_stringshare_add(pa_channel_position_to_pretty_string(info->channel_map.map[i]));
sink->base.mute = !!info->mute;
sink->monitor_source_name = eina_stringshare_add(info->monitor_source_name);
for (i = 0; i < info->n_ports; i++)
{
port = calloc(1, sizeof(Emix_Port));
if (!port)
{
WRN("Could not allocate memory for Sink's port");
continue;
}
port->available = !!info->ports[i]->available;
port->name = eina_stringshare_add(info->ports[i]->name);
port->description = eina_stringshare_add(info->ports[i]->description);
sink->base.ports = eina_list_append(sink->base.ports, port);
if (info->ports[i]->name == info->active_port->name)
port->active = EINA_TRUE;
}
if (info->state == PA_SINK_RUNNING) _sink_state_running_set(sink, EINA_TRUE);
else _sink_state_running_set(sink, EINA_FALSE);
ctx->sinks = eina_list_append(ctx->sinks, sink);
if (ctx->cb)
ctx->cb((void *)ctx->userdata, EMIX_SINK_ADDED_EVENT,
(Emix_Sink *)sink);
}
static void
_sink_changed_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol,
void *userdata EINA_UNUSED)
{
Sink *sink = NULL, *s;
Emix_Port *port;
uint32_t i;
Eina_List *l;
if (eol < 0)
{
if (pa_context_errno(c) == PA_ERR_NOENTITY)
return;
ERR("Sink callback failure");
return;
}
if (eol > 0)
return;
DBG("sink index: %d\nsink name: %s", info->index,
info->name);
EINA_LIST_FOREACH(ctx->sinks, l, s)
{
if (s->idx == (int)info->index)
{
sink = s;
break;
}
}
EINA_SAFETY_ON_NULL_RETURN(sink);
eina_stringshare_replace(&sink->base.name, info->description);
if (sink->base.volume.channel_count != info->volume.channels)
{
for (i = 0; i < sink->base.volume.channel_count; ++i)
eina_stringshare_del(sink->base.volume.channel_names[i]);
free(sink->base.volume.channel_names);
sink->base.volume.channel_names = calloc(info->volume.channels, sizeof(Emix_Channel));
}
_pa_cvolume_convert(&info->volume, &sink->base.volume);
for (i = 0; i < sink->base.volume.channel_count; ++i)
eina_stringshare_replace(&sink->base.volume.channel_names[i],
pa_channel_position_to_pretty_string(info->channel_map.map[i]));
sink->base.mute = !!info->mute;
if (sink->base.ports)
{
EINA_LIST_FREE(sink->base.ports, port)
{
eina_stringshare_del(port->name);
eina_stringshare_del(port->description);
free(port);
}
}
for (i = 0; i < info->n_ports; i++)
{
port = calloc(1, sizeof(Emix_Port));
if (!port)
{
WRN("Could not allocate memory for Sink's port");
continue;
}
port->available = !!info->ports[i]->available;
port->name = eina_stringshare_add(info->ports[i]->name);
port->description = eina_stringshare_add(info->ports[i]->description);
sink->base.ports = eina_list_append(sink->base.ports, port);
if (info->ports[i]->name == info->active_port->name)
port->active = EINA_TRUE;
}
sink->monitor_idx = info->monitor_source;
if (info->state == PA_SINK_RUNNING) _sink_state_running_set(sink, EINA_TRUE);
else _sink_state_running_set(sink, EINA_FALSE);
if (ctx->cb)
ctx->cb((void *)ctx->userdata, EMIX_SINK_CHANGED_EVENT,
(Emix_Sink *)sink);
}
static void
_sink_remove_cb(int index, void *data EINA_UNUSED)
{
Sink *sink;
Eina_List *l;
DBG("Removing sink: %d", index);
EINA_LIST_FOREACH(ctx->sinks, l, sink)
{
if (sink->idx == index)
{
ctx->sinks = eina_list_remove_list(ctx->sinks, l);
if (ctx->cb)
ctx->cb((void *)ctx->userdata, EMIX_SINK_REMOVED_EVENT,
(Emix_Sink *)sink);
_sink_del(sink);
break;
}
}
}
static const char *
_icon_from_properties(pa_proplist *l)
{
const char *t;
if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ICON_NAME))) return t;
if ((t = pa_proplist_gets(l, PA_PROP_WINDOW_ICON_NAME))) return t;
if ((t = pa_proplist_gets(l, PA_PROP_APPLICATION_ICON_NAME))) return t;
if ((t = pa_proplist_gets(l, PA_PROP_APPLICATION_ICON))) return t;
if ((t = pa_proplist_gets(l, PA_PROP_APPLICATION_ID))) return t;
if ((t = pa_proplist_gets(l, PA_PROP_APPLICATION_NAME))) return t;
if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ROLE)))
{
if ((!strcmp(t, "video")) || (!strcmp(t, "phone"))) return t;
if (!strcmp(t, "music")) return "multimedia-player";
if (!strcmp(t, "game")) return "applications-games";
if (!strcmp(t, "event")) return "dialog-information";
}
return NULL;
}
static void
_sink_input_state_running_set(Sink_Input *input, Eina_Bool running)
{
if (running)
{
if ((!input->running) && (input->mon_count > 0))
{
input->running = running;
_sink_input_monitor_begin(input);
return;
}
}
else
{
if ((input->running) && (input->mon_count > 0))
{
input->running = running;
_sink_input_monitor_end(input);
return;
}
}
input->running = running;
}
static void
_sink_input_cb(pa_context *c EINA_UNUSED, const pa_sink_input_info *info,
int eol, void *userdata EINA_UNUSED)
{
Sink_Input *input;
Eina_List *l;
Sink *s;
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("Sink input callback failure");
return;
}
if (eol > 0)
return;
input = calloc(1, sizeof(Sink_Input));
EINA_SAFETY_ON_NULL_RETURN(input);
DBG("sink input index: %d\nsink input name: %s", info->index,
info->name);
input->idx = info->index;
input->sink_idx = info->sink;
Eina_Strbuf *input_name;
input_name = eina_strbuf_new();
const char *application = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_NAME);
if (application)
{
eina_strbuf_append(input_name, application);
eina_strbuf_append(input_name, ":");
eina_strbuf_append(input_name, info->name);
}
else if (info->name)
{
eina_strbuf_append(input_name, info->name);
}
input->base.name = eina_stringshare_add(eina_strbuf_string_get(input_name));
eina_strbuf_free(input_name);
_pa_cvolume_convert(&info->volume, &input->base.volume);
input->base.volume.channel_names = calloc(input->base.volume.channel_count, sizeof(Emix_Channel));
for (i = 0; i < input->base.volume.channel_count; ++i)
input->base.volume.channel_names[i] = eina_stringshare_add(pa_channel_position_to_pretty_string(info->channel_map.map[i]));
input->base.mute = !!info->mute;
EINA_LIST_FOREACH(ctx->sinks, l, s)
{
if (s->idx == (int)info->sink)
input->base.sink = (Emix_Sink *)s;
}
input->base.icon = eina_stringshare_add(_icon_from_properties(info->proplist));
ctx->inputs = eina_list_append(ctx->inputs, input);
if ((t = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_PROCESS_ID)))
{
input->base.pid = atoi(t);
}
if (!info->corked) _sink_input_state_running_set(input, EINA_TRUE);
else _sink_input_state_running_set(input, EINA_FALSE);
if (ctx->cb)
ctx->cb((void *)ctx->userdata, EMIX_SINK_INPUT_ADDED_EVENT,
(Emix_Sink_Input *)input);
}
static void
_sink_input_changed_cb(pa_context *c EINA_UNUSED,
const pa_sink_input_info *info, int eol,
void *userdata EINA_UNUSED)
{
Sink_Input *input = NULL, *si;
Sink *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("Sink input changed callback failure");
return;
}
if (eol > 0)
return;
EINA_LIST_FOREACH(ctx->inputs, l, si)
{
if (si->idx == (int)info->index)
{
input = si;
break;
}
}
DBG("sink input changed index: %d\n", info->index);
if (!input)
{
input = calloc(1, sizeof(Sink_Input));
EINA_SAFETY_ON_NULL_RETURN(input);
ctx->inputs = eina_list_append(ctx->inputs, input);
}
input->idx = info->index;
input->sink_idx = info->sink;
if (input->base.volume.channel_count != info->volume.channels)
{
for (i = 0; i < input->base.volume.channel_count; ++i)
eina_stringshare_del(input->base.volume.channel_names[i]);
free(input->base.volume.channel_names);
input->base.volume.channel_names = calloc(info->volume.channels, sizeof(Emix_Channel));
}
_pa_cvolume_convert(&info->volume, &input->base.volume);
for (i = 0; i < input->base.volume.channel_count; ++i)
eina_stringshare_replace(&input->base.volume.channel_names[i],
pa_channel_position_to_pretty_string(info->channel_map.map[i]));
input->base.mute = !!info->mute;
EINA_LIST_FOREACH(ctx->sinks, l, s)
{
if (s->idx == (int)info->sink)
input->base.sink = (Emix_Sink *)s;
}
if ((t = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_PROCESS_ID)))
{
input->base.pid = atoi(t);
}
if (!info->corked) _sink_input_state_running_set(input, EINA_TRUE);
else _sink_input_state_running_set(input, EINA_FALSE);
if (ctx->cb)
ctx->cb((void *)ctx->userdata, EMIX_SINK_INPUT_CHANGED_EVENT,
(Emix_Sink_Input *)input);
if (input->mon_count > 0)
{
_sink_input_monitor_end(input);
if (input->running) _sink_input_monitor_begin(input);
}
}
static void
_sink_input_remove_cb(int index, void *data EINA_UNUSED)
{
Sink_Input *input;
Eina_List *l;
EINA_SAFETY_ON_NULL_RETURN(ctx);
DBG("Removing sink input: %d", index);
EINA_LIST_FOREACH(ctx->inputs, l, input)
{
if (input->idx == index)
{
ctx->inputs = eina_list_remove_list(ctx->inputs, l);
if (ctx->cb)
ctx->cb((void *)ctx->userdata,
EMIX_SINK_INPUT_REMOVED_EVENT,
(Emix_Sink_Input *)input);
_sink_input_del(input);
break;
}
}
}
static void
_source_cb(pa_context *c EINA_UNUSED, const pa_source_info *info,
int eol, void *userdata EINA_UNUSED)
{
Source *source;
unsigned int i;
size_t len;
EINA_SAFETY_ON_NULL_RETURN(ctx);
if (eol < 0)
{
if (pa_context_errno(c) == PA_ERR_NOENTITY)
return;
ERR("Source callback failure");
return;
}
if (eol > 0)
return;
len = strlen(info->name);
if (len > 8)
{
const char *s = info->name + len - 8;
if (!strcmp(s, ".monitor")) return;
}
source = calloc(1, sizeof(Source));
EINA_SAFETY_ON_NULL_RETURN(source);
source->idx = info->index;
source->pulse_name = eina_stringshare_add(info->name);
source->base.name = eina_stringshare_add(info->description);
_pa_cvolume_convert(&info->volume, &source->base.volume);
source->base.volume.channel_names = calloc(source->base.volume.channel_count, sizeof(Emix_Channel));
for (i = 0; i < source->base.volume.channel_count; ++i)
source->base.volume.channel_names[i] = eina_stringshare_add(pa_channel_position_to_pretty_string(info->channel_map.map[i]));
source->base.mute = !!info->mute;
ctx->sources = eina_list_append(ctx->sources, source);
if (ctx->cb)
ctx->cb((void *)ctx->userdata, EMIX_SOURCE_ADDED_EVENT,
(Emix_Source *)source);
}
static void
_source_changed_cb(pa_context *c EINA_UNUSED,
const pa_source_info *info, int eol,
void *userdata EINA_UNUSED)
{
Source *source = NULL, *s;
Eina_List *l;
unsigned int i;
EINA_SAFETY_ON_NULL_RETURN(ctx);
if (eol < 0)
{
if (pa_context_errno(c) == PA_ERR_NOENTITY)
return;
ERR("Source changed callback failure");
return;
}
if (eol > 0)
return;
EINA_LIST_FOREACH(ctx->sources, l, s)
{
if (s->idx == (int)info->index)
{
source = s;
break;
}
}
DBG("source changed index: %d\n", info->index);
if (!source)
{
source = calloc(1, sizeof(Source));
EINA_SAFETY_ON_NULL_RETURN(source);
ctx->sources = eina_list_append(ctx->sources, source);
}
source->idx = info->index;
if (source->base.volume.channel_count != info->volume.channels)
{
for (i = 0; i < source->base.volume.channel_count; ++i)
eina_stringshare_del(source->base.volume.channel_names[i]);
free(source->base.volume.channel_names);
source->base.volume.channel_names = calloc(info->volume.channels, sizeof(Emix_Channel));
}
_pa_cvolume_convert(&info->volume, &source->base.volume);
for (i = 0; i < source->base.volume.channel_count; ++i)
eina_stringshare_replace(&source->base.volume.channel_names[i],
pa_channel_position_to_pretty_string(info->channel_map.map[i]));
source->base.mute = !!info->mute;
if (ctx->cb)
ctx->cb((void *)ctx->userdata, EMIX_SOURCE_CHANGED_EVENT,
(Emix_Source *)source);
}
static void
_source_remove_cb(int index, void *data EINA_UNUSED)
{
Source *source;
Eina_List *l;
EINA_SAFETY_ON_NULL_RETURN(ctx);
DBG("Removing source: %d", index);
EINA_LIST_FOREACH(ctx->sources, l, source)
{
if (source->idx == index)
{
ctx->sources = eina_list_remove_list(ctx->sources, l);
if (ctx->cb)
ctx->cb((void *)ctx->userdata, EMIX_SOURCE_REMOVED_EVENT,
(Emix_Source *)source);
_source_del(source);
break;
}
}
}
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")) ||
(!strcmp(info->name, "Peak detect"))))
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)
{
Sink *sink;
Eina_List *l;
if (eol < 0)
{
if (pa_context_errno(c) == PA_ERR_NOENTITY)
return;
ERR("Sink callback failure");
return;
}
if (eol > 0)
return;
DBG("sink index: %d\nsink name: %s", info->index,
info->name);
EINA_LIST_FOREACH(ctx->sinks, l, sink)
{
Eina_Bool prev_default = sink->base.default_sink;
sink->base.default_sink = (uint32_t)sink->idx == info->index;
if (ctx->cb && (prev_default != sink->base.default_sink))
ctx->cb((void *)ctx->userdata, EMIX_SINK_CHANGED_EVENT,
(Emix_Sink *)sink);
}
if (ctx->cb)
ctx->cb((void *)ctx->userdata, EMIX_READY_EVENT, NULL);
}
static void
_source_default_cb(pa_context *c EINA_UNUSED, const pa_source_info *info, int eol,
void *userdata EINA_UNUSED)
{
Source *source;
Eina_List *l;
if (eol < 0)
{
if (pa_context_errno(c) == PA_ERR_NOENTITY)
return;
ERR("Source callback failure");
return;
}
if (eol > 0)
return;
DBG("source index: %d\nsource name: %s", info->index,
info->name);
EINA_LIST_FOREACH(ctx->sources, l, source)
{
Eina_Bool prev_default = source->base.default_source;
source->base.default_source = (uint32_t)source->idx == info->index;
if (ctx->cb && prev_default != source->base.default_source)
ctx->cb((void *)ctx->userdata, EMIX_SOURCE_CHANGED_EVENT,
(Emix_Source *)source);
}
if (ctx->cb)
ctx->cb((void *)ctx->userdata, EMIX_READY_EVENT, NULL);
}
static void
_server_info_cb(pa_context *c, const pa_server_info *info,
void *userdata)
{
pa_operation *o;
// this seems to trap old errors then forget to update defaults as below
// if (pa_context_errno(c) != PA_OK)
// {
// WRN("Could not get pa server info, error: %d", pa_context_errno(c));
// return;
// }
EINA_SAFETY_ON_NULL_RETURN(info);
if (!(o = pa_context_get_sink_info_by_name(c, info->default_sink_name,
_sink_default_cb, userdata)))
{
ERR("pa_context_get_sink_info_by_name() failed");
return;
}
pa_operation_unref(o);
if (!(o = pa_context_get_source_info_by_name(c, info->default_source_name,
_source_default_cb, userdata)))
{
ERR("pa_context_get_source_info_by_name() failed");
return;
}
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)
{
pa_operation *o;
switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
case PA_SUBSCRIPTION_EVENT_SINK:
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
PA_SUBSCRIPTION_EVENT_REMOVE)
_sink_remove_cb(index, data);
else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
PA_SUBSCRIPTION_EVENT_NEW)
{
if (!(o = pa_context_get_sink_info_by_index(c, index,
_sink_cb, data)))
{
ERR("pa_context_get_sink_info_by_index() failed");
return;
}
pa_operation_unref(o);
}
else
{
if (!(o = pa_context_get_sink_info_by_index(c, index,
_sink_changed_cb,
data)))
{
ERR("pa_context_get_sink_info_by_index() failed");
return;
}
pa_operation_unref(o);
}
break;
case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
PA_SUBSCRIPTION_EVENT_REMOVE)
_sink_input_remove_cb(index, data);
else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
PA_SUBSCRIPTION_EVENT_NEW)
{
if (!(o = pa_context_get_sink_input_info(c, index,
_sink_input_cb, data)))
{
ERR("pa_context_get_sink_input_info() failed");
return;
}
pa_operation_unref(o);
}
else
{
if (!(o = pa_context_get_sink_input_info(c, index,
_sink_input_changed_cb,
data)))
{
ERR("pa_context_get_sink_input_info() failed");
return;
}
pa_operation_unref(o);
}
break;
case PA_SUBSCRIPTION_EVENT_SOURCE:
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
PA_SUBSCRIPTION_EVENT_REMOVE)
_source_remove_cb(index, data);
else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
PA_SUBSCRIPTION_EVENT_NEW)
{
if (!(o = pa_context_get_source_info_by_index(c, index,
_source_cb, data)))
{
ERR("pa_context_get_source_info() failed");
return;
}
pa_operation_unref(o);
}
else
{
if (!(o = pa_context_get_source_info_by_index(c, index,
_source_changed_cb,
data)))
{
ERR("pa_context_get_source_info() failed");
return;
}
pa_operation_unref(o);
}
break;
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)))
{
ERR("pa_context_get_server_info() failed");
return;
}
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;
}
}
static Eina_Bool _pulse_connect(void *data);
static void _disconnect_cb(void);
static void
_pulse_pa_state_cb(pa_context *context, void *data)
{
pa_operation *o;
switch (pa_context_get_state(context))
{
case PA_CONTEXT_UNCONNECTED:
case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
break;
case PA_CONTEXT_READY:
ctx->connect = NULL;
ctx->connected = EINA_TRUE;
pa_context_set_subscribe_callback(context, _subscribe_cb, ctx);
if (!(o = pa_context_subscribe(context, (pa_subscription_mask_t)
(PA_SUBSCRIPTION_MASK_SINK|
PA_SUBSCRIPTION_MASK_SOURCE|
PA_SUBSCRIPTION_MASK_SINK_INPUT|
PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT|
PA_SUBSCRIPTION_MASK_CLIENT|
PA_SUBSCRIPTION_MASK_SERVER|
PA_SUBSCRIPTION_MASK_CARD),
NULL, NULL)))
{
ERR("pa_context_subscribe() failed");
return;
}
pa_operation_unref(o);
if (!(o = pa_context_get_sink_info_list(context, _sink_cb, ctx)))
{
ERR("pa_context_get_sink_info_list() failed");
return;
}
pa_operation_unref(o);
if (!(o = pa_context_get_sink_input_info_list(context,
_sink_input_cb,
ctx)))
{
ERR("pa_context_get_sink_input_info_list() failed");
return;
}
pa_operation_unref(o);
if (!(o = pa_context_get_source_info_list(context, _source_cb,
ctx)))
{
ERR("pa_context_get_source_info_list() failed");
return;
}
pa_operation_unref(o);
if (!(o = pa_context_get_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)))
{
ERR("pa_context_get_server_info() failed");
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:
WRN("PA_CONTEXT_FAILED");
if (!ctx->connect)
ctx->connect = ecore_timer_loop_add(1, _pulse_connect, data);
goto err;
case PA_CONTEXT_TERMINATED:
ERR("PA_CONTEXT_TERMINATE:");
EINA_FALLTHROUGH;
/* no break */
default:
if (ctx->connect)
{
ecore_timer_del(ctx->connect);
ctx->connect = NULL;
}
goto err;
}
return;
err:
if (ctx->connected)
{
_disconnect_cb();
ctx->connected = EINA_FALSE;
}
pa_context_unref(ctx->context);
ctx->context = NULL;
}
static Eina_Bool
_pulse_connect(void *data)
{
pa_proplist *proplist;
Context *c = data;
Eina_Bool ret = ECORE_CALLBACK_DONE;
printf("PULSE CONN...\n");
proplist = pa_proplist_new();
pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "Efl Volume Control");
pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID,
"org.enlightenment.volumecontrol");
pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "audio-card");
#if !defined(EMIXER_BUILD) && defined(HAVE_WAYLAND) && !defined(HAVE_WAYLAND_ONLY)
char *display = NULL;
if (e_comp->comp_type != E_PIXMAP_TYPE_X)
{
display = getenv("DISPLAY");
if (display) display = strdup(display);
e_env_unset("DISPLAY");
}
#endif
c->context = pa_context_new_with_proplist(&(c->api), NULL, proplist);
if (c->context)
{
pa_context_set_state_callback(c->context, _pulse_pa_state_cb, c);
if (pa_context_connect(c->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0)
{
ret = EINA_TRUE;
ERR("Could not connect to pulse");
}
}
#if !defined(EMIXER_BUILD) && defined(HAVE_WAYLAND) && !defined(HAVE_WAYLAND_ONLY)
if (e_comp->comp_type != E_PIXMAP_TYPE_X)
{
if (display)
{
e_env_set("DISPLAY", display);
free(display);
}
}
#endif
pa_proplist_free(proplist);
return ret;
}
static Eina_Bool pulse_started = EINA_FALSE;
static void
_shutdown(void)
{
if (!ctx)
return;
if (pulse_started)
{
ecore_exe_run("pulseaudio -k", NULL);
pulse_started = EINA_FALSE;
}
if (ctx->connect)
{
ecore_timer_del(ctx->connect);
ctx->connect = NULL;
}
if (ctx->context)
pa_context_unref(ctx->context);
if (ctx->connected)
_disconnect_cb();
free(ctx);
ctx = NULL;
}
static Eina_Bool
_init(Emix_Event_Cb cb, const void *data)
{
if (ctx)
return EINA_TRUE;
ctx = calloc(1, sizeof(Context));
if (!ctx)
{
ERR("Could not create Epulse Context");
return EINA_FALSE;
}
ctx->api = functable;
ctx->api.userdata = ctx;
if (_pulse_connect(ctx) == EINA_TRUE) // true == failed and try again
{
if (!pulse_started)
ecore_exe_run("pulseaudio --start", NULL);
pulse_started = EINA_TRUE;
}
ctx->cb = cb;
ctx->userdata = data;
return EINA_TRUE;
}
static void
_disconnect_cb()
{
Source *source;
Sink *sink;
Sink_Input *input;
Card *card;
if (ctx->cb)
ctx->cb((void *)ctx->userdata, EMIX_DISCONNECTED_EVENT, NULL);
EINA_LIST_FREE(ctx->sources, source)
_source_del(source);
EINA_LIST_FREE(ctx->sinks, sink)
_sink_del(sink);
EINA_LIST_FREE(ctx->inputs, input)
_sink_input_del(input);
EINA_LIST_FREE(ctx->cards, card)
_card_del(card);
}
static void
_source_volume_set(Emix_Source *source, Emix_Volume *volume)
{
pa_operation* o;
pa_cvolume vol = _emix_volume_convert(volume);
Source *s = (Source *)source;
EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && source != NULL);
if (!(o = pa_context_set_source_volume_by_index(ctx->context,
s->idx, &vol,
NULL, NULL)))
ERR("pa_context_set_source_volume_by_index() failed");
}
static void
_source_mute_set(Emix_Source *source, Eina_Bool mute)
{
pa_operation* o;
Source *s = (Source *)source;
EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && source != NULL);
if (!(o = pa_context_set_source_mute_by_index(ctx->context,
s->idx, mute, NULL, NULL)))
ERR("pa_context_set_source_mute() failed");
}
static const Eina_List *
_sinks_get(void)
{
EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
return ctx->sinks;
}
static const Eina_List *
_sources_get(void)
{
EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
return ctx->sources;
}
static void
_sink_volume_set(Emix_Sink *sink, Emix_Volume *volume)
{
pa_operation* o;
Sink *s = (Sink *)sink;
pa_cvolume vol = _emix_volume_convert(volume);
EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->context && sink != NULL));
if (!(o = pa_context_set_sink_volume_by_index(ctx->context,
s->idx, &vol, NULL, NULL)))
ERR("pa_context_set_sink_volume_by_index() failed");
}
static void
_sink_mute_set(Emix_Sink *sink, Eina_Bool mute)
{
pa_operation* o;
Sink *s = (Sink *)sink;
EINA_SAFETY_ON_FALSE_RETURN((ctx && ctx->context && sink != NULL));
if (!(o = pa_context_set_sink_mute_by_index(ctx->context,
s->idx, mute, NULL, NULL)))
ERR("pa_context_set_sink_mute() failed");
}
static 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)
{
pa_operation* o;
Sink_Input *sink_input = (Sink_Input *)input;
pa_cvolume vol = _emix_volume_convert(volume);
EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && input != NULL);
if (!(o = pa_context_set_sink_input_volume(ctx->context,
sink_input->idx, &vol,
NULL, NULL)))
ERR("pa_context_set_sink_input_volume_by_index() failed");
}
static void
_sink_input_mute_set(Emix_Sink_Input *input, Eina_Bool mute)
{
pa_operation* o;
Sink_Input *sink_input = (Sink_Input *)input;
EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && input != NULL);
if (!(o = pa_context_set_sink_input_mute(ctx->context,
sink_input->idx, mute,
NULL, NULL)))
ERR("pa_context_set_sink_input_mute() failed");
}
static void
_sink_input_move(Emix_Sink_Input *input, Emix_Sink *sink)
{
pa_operation* o;
Sink *s = (Sink *)sink;
Sink_Input *i = (Sink_Input *)input;
EINA_SAFETY_ON_FALSE_RETURN(ctx && ctx->context && input != NULL
&& sink != NULL);
if (!(o = pa_context_move_sink_input_by_index(ctx->context,
i->idx, s->idx, NULL,
NULL)))
ERR("pa_context_move_sink_input_by_index() failed");
}
static 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)
{
pa_operation* o;
Sink *s = (Sink *)sink;
EINA_SAFETY_ON_FALSE_RETURN_VAL(ctx && ctx->context &&
sink != NULL && port != NULL, EINA_FALSE);
if (!(o = pa_context_set_sink_port_by_index(ctx->context,
s->idx, port->name, NULL,
NULL)))
{
ERR("pa_context_set_source_port_by_index() failed");
return EINA_FALSE;
}
pa_operation_unref(o);
return EINA_TRUE;
}
static Eina_Bool
_sink_default_support(void)
{
return EINA_TRUE;
}
static const Emix_Sink *
_sink_default_get(void)
{
Sink *s;
Eina_List *l;
EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
EINA_LIST_FOREACH(ctx->sinks, l, s)
if (s->base.default_sink)
return (Emix_Sink *)s;
return NULL;
}
static void
_sink_default_set(Emix_Sink *sink)
{
Sink *s = (Sink *)sink;
pa_operation* o;
DBG("Set default sink: %s", sink->name);
if (!(o = pa_context_set_default_sink(ctx->context, s->pulse_name, NULL, NULL))) {
ERR("pa_context_set_default_sink() failed");
return;
}
pa_operation_unref(o);
}
static Eina_Bool
_source_default_support(void)
{
return EINA_TRUE;
}
static const Emix_Source *
_source_default_get(void)
{
Source *s;
Eina_List *l;
EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
EINA_LIST_FOREACH(ctx->sources, l, s)
if (s->base.default_source)
return (Emix_Source *)s;
return NULL;
}
static void
_source_default_set(Emix_Source *source)
{
Source *s = (Source *)source;
pa_operation* o;
DBG("Set default sink: %s", source->name);
if (!(o = pa_context_set_default_source(ctx->context, s->pulse_name, NULL, NULL))) {
ERR("pa_context_set_default_source() failed");
return;
}
pa_operation_unref(o);
}
static Eina_Bool
_sink_change_support(void)
{
return EINA_TRUE;
}
static int
_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 void
_sink_mon_read(pa_stream *stream, size_t bytes EINA_UNUSED, void *data)
{
Sink *s = data;
size_t rbytes;
const void *buf = NULL;
if (pa_stream_peek(stream, &buf, &rbytes) == 0)
{
if ((!buf) && (rbytes))
{
pa_stream_drop(stream);
return;
}
s->base.mon_num = (unsigned int)((rbytes / sizeof(float)) / 2);
s->base.mon_buf = buf;
if (ctx->cb)
ctx->cb((void *)ctx->userdata, EMIX_SINK_MONITOR_EVENT, s);
pa_stream_drop(stream);
}
}
static void
_sink_monitor_begin(Sink *s)
{
pa_sample_spec samp;
pa_buffer_attr attr;
if (pa_context_get_server_protocol_version(ctx->context) < 13) return;
pa_sample_spec_init(&samp);
samp.format = PA_SAMPLE_FLOAT32;
samp.rate = 44100;
samp.channels = 2;
memset(&attr, 0, sizeof(attr));
attr.fragsize = sizeof(float) * 4096;
attr.maxlength = (uint32_t) -1;
s->mon_stream = pa_stream_new(ctx->context, "__e_mon", &samp, NULL);
pa_stream_set_read_callback(s->mon_stream, _sink_mon_read, s);
pa_context_set_source_mute_by_name(ctx->context, s->monitor_source_name, 0, NULL, NULL);
pa_stream_connect_record(s->mon_stream, s->monitor_source_name,
&attr,
PA_STREAM_NOFLAGS
| PA_STREAM_DONT_MOVE
| PA_STREAM_ADJUST_LATENCY
| PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND
);
}
static void
_sink_monitor_end(Sink *s)
{
if (s->mon_stream)
{
if (s->mon_stream) pa_stream_disconnect(s->mon_stream);
s->mon_stream = NULL;
}
}
static void
_sink_monitor_set(Emix_Sink *sink, Eina_Bool monitor)
{
Sink *s = (Sink *)sink;
EINA_SAFETY_ON_NULL_RETURN(ctx);
if (monitor) s->mon_count++;
else s->mon_count--;
if (s->mon_count < 0) s->mon_count = 0;
if (s->mon_count == 1)
{
if (s->running) _sink_monitor_begin(s);
}
else if (s->mon_count == 0) _sink_monitor_end(s);
}
static void
_sink_input_mon_read(pa_stream *stream, size_t bytes EINA_UNUSED, void *data)
{
Sink_Input *i = data;
size_t rbytes;
const void *buf = NULL;
if (pa_stream_peek(stream, &buf, &rbytes) == 0)
{
if ((!buf) && (rbytes))
{
pa_stream_drop(stream);
return;
}
i->base.mon_num = (unsigned int)((rbytes / sizeof(float)) / 2);
i->base.mon_buf = buf;
if (ctx->cb)
ctx->cb((void *)ctx->userdata, EMIX_SINK_INPUT_MONITOR_EVENT, i);
pa_stream_drop(stream);
}
}
static void
_sink_input_monitor_begin(Sink_Input *i)
{
pa_sample_spec samp;
pa_buffer_attr attr;
char buf[16];
Eina_List *l;
Sink *s;
unsigned int mon_idx = 0;
if (pa_context_get_server_protocol_version(ctx->context) < 13) return;
pa_sample_spec_init(&samp);
samp.format = PA_SAMPLE_FLOAT32;
samp.rate = 44100;
samp.channels = 2;
memset(&attr, 0, sizeof(attr));
attr.fragsize = sizeof(float) * 4096;
attr.maxlength = (uint32_t) -1;
i->mon_stream = pa_stream_new(ctx->context, "__e_mon", &samp, NULL);
pa_stream_set_monitor_stream(i->mon_stream, i->idx);
pa_stream_set_read_callback(i->mon_stream, _sink_input_mon_read, i);
EINA_LIST_FOREACH(ctx->sinks, l, s)
{
if (i->sink_idx == s->idx)
{
mon_idx = s->monitor_idx;
break;
}
}
if (!l) return;
snprintf(buf, sizeof(buf), "%i", mon_idx);
pa_context_set_source_mute_by_name(ctx->context, buf, 0, NULL, NULL);
pa_stream_connect_record(i->mon_stream, buf,
&attr,
PA_STREAM_NOFLAGS
| PA_STREAM_DONT_MOVE
| PA_STREAM_ADJUST_LATENCY
| PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND
);
}
static void
_sink_input_monitor_end(Sink_Input *i)
{
if (i->mon_stream)
{
if (i->mon_stream) pa_stream_disconnect(i->mon_stream);
i->mon_stream = NULL;
}
}
static void
_sink_input_monitor_set(Emix_Sink_Input *input, Eina_Bool monitor)
{
Sink_Input *i = (Sink_Input *)input;
EINA_SAFETY_ON_NULL_RETURN(ctx);
if (monitor) i->mon_count++;
else i->mon_count--;
if (i->mon_count < 0) i->mon_count = 0;
if (i->mon_count == 1)
{
if (i->running) _sink_input_monitor_begin(i);
}
else if (i->mon_count == 0) _sink_input_monitor_end(i);
}
static void
_source_mon_read(pa_stream *stream, size_t bytes EINA_UNUSED, void *data)
{
Source *s = data;
size_t rbytes;
const void *buf = NULL;
if (pa_stream_peek(stream, &buf, &rbytes) == 0)
{
if ((!buf) && (rbytes))
{
pa_stream_drop(stream);
return;
}
s->base.mon_num = (unsigned int)((rbytes / sizeof(float)) / 2);
s->base.mon_buf = buf;
if (ctx->cb)
ctx->cb((void *)ctx->userdata, EMIX_SOURCE_MONITOR_EVENT, s);
pa_stream_drop(stream);
}
}
static void
_source_monitor_begin(Source *s)
{
pa_sample_spec samp;
pa_buffer_attr attr;
char buf[16];
if (pa_context_get_server_protocol_version(ctx->context) < 13) return;
pa_sample_spec_init(&samp);
samp.format = PA_SAMPLE_FLOAT32;
samp.rate = 44100;
samp.channels = 2;
memset(&attr, 0, sizeof(attr));
attr.fragsize = sizeof(float) * 4096;
attr.maxlength = (uint32_t) -1;
s->mon_stream = pa_stream_new(ctx->context, "__e_mon", &samp, NULL);
pa_stream_set_read_callback(s->mon_stream, _source_mon_read, s);
snprintf(buf, sizeof(buf), "%i", s->idx);
pa_stream_connect_record(s->mon_stream, buf,
&attr,
PA_STREAM_NOFLAGS
| PA_STREAM_DONT_MOVE
| PA_STREAM_ADJUST_LATENCY
);
}
static void
_source_monitor_end(Source *s)
{
if (s->mon_stream)
{
if (s->mon_stream) pa_stream_disconnect(s->mon_stream);
s->mon_stream = NULL;
}
}
static void
_source_monitor_set(Emix_Source *source, Eina_Bool monitor)
{
Source *s = (Source *)source;
EINA_SAFETY_ON_NULL_RETURN(ctx);
if (monitor) s->mon_count++;
else s->mon_count--;
if (s->mon_count < 0) s->mon_count = 0;
if (s->mon_count == 1) _source_monitor_begin(s);
else if (s->mon_count == 0) _source_monitor_end(s);
}
static Emix_Backend
_pulseaudio_backend =
{
_init,
_shutdown,
_max_volume,
_sinks_get,
_sink_default_support,
_sink_default_get,
_sink_default_set,
_sink_mute_set,
_sink_volume_set,
_sink_port_set,
_sink_change_support,
_sink_inputs_get,
_sink_input_mute_set,
_sink_input_volume_set,
_sink_input_move,
_sources_get,
_source_default_support,
_source_default_get,
_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,
_sink_monitor_set,
_sink_input_monitor_set,
_source_monitor_set
};
E_API Emix_Backend *
emix_backend_pulse_get(void)
{
return &_pulseaudio_backend;
}
E_API const char *emix_backend_pulse_name = "PULSEAUDIO";