Commit 69741042 authored by Arnaud Ferraris's avatar Arnaud Ferraris
Browse files

New upstream version 0.1.0

parents cb6b77ef 007b3412
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
[meson.build]
indent_size = 2
tab_size = 2
indent_style = space
[*.{c,h}]
indent_size = 4
tab_size = 4
indent_style = space
max_line_length = 80
[*.xml]
indent_size = 2
tab_size = 2
indent_style = space
......@@ -8,7 +8,7 @@
project (
'callaudiod',
'c',
version : '0.0.5',
version : '0.1.0',
license : 'LGPLv3+',
meson_version : '>= 0.50.0',
default_options :
......
......@@ -42,6 +42,9 @@ struct _CadPulse
gboolean has_voice_profile;
gchar *speaker_port;
GHashTable *sink_ports;
GHashTable *source_ports;
CallAudioMode current_mode;
};
......@@ -53,15 +56,25 @@ typedef struct _CadPulseOperation {
guint value;
} CadPulseOperation;
static const gchar *get_available_output(const pa_sink_info *sink, const gchar *exclude)
static void pulseaudio_cleanup(CadPulse *self);
static gboolean pulseaudio_connect(CadPulse *self);
/******************************************************************************
* Source management
*
* The following functions take care of monitoring and configuring the default
* source (input)
******************************************************************************/
static const gchar *get_available_source_port(const pa_source_info *source, const gchar *exclude)
{
pa_sink_port_info *available_port = NULL;
pa_source_port_info *available_port = NULL;
guint i;
g_debug("looking for available port excluding '%s'", exclude);
g_debug("looking for available input excluding '%s'", exclude);
for (i = 0; i < sink->n_ports; i++) {
pa_sink_port_info *port = sink->ports[i];
for (i = 0; i < source->n_ports; i++) {
pa_source_port_info *port = source->ports[i];
if ((exclude && strcmp(port->name, exclude) == 0) ||
port->available == PA_PORT_AVAILABLE_NO) {
......@@ -73,18 +86,63 @@ static const gchar *get_available_output(const pa_sink_info *sink, const gchar *
}
if (available_port) {
g_debug("found available port '%s'", available_port->name);
g_debug("found available input '%s'", available_port->name);
return available_port->name;
}
g_warning("no available port found!");
g_warning("no available input found!");
return NULL;
}
static void change_source_info(pa_context *ctx, const pa_source_info *info, int eol, void *data)
{
CadPulse *self = data;
const gchar *target_port;
pa_operation *op;
gboolean change = FALSE;
guint i;
if (eol != 0)
return;
if (!info) {
g_critical("PA returned no source info (eol=%d)", eol);
return;
}
if (info->index != self->source_id)
return;
for (i = 0; i < info->n_ports; i++) {
pa_source_port_info *port = info->ports[i];
if (port->available != PA_PORT_AVAILABLE_UNKNOWN) {
enum pa_port_available available;
available = GPOINTER_TO_INT(g_hash_table_lookup(self->source_ports, port->name));
if (available != port->available) {
g_hash_table_insert(self->source_ports, g_strdup(port->name),
GINT_TO_POINTER(port->available));
change = TRUE;
}
}
}
if (change) {
target_port = get_available_source_port(info, NULL);
if (target_port) {
op = pa_context_set_source_port_by_index(ctx, self->source_id,
target_port, NULL, NULL);
if (op)
pa_operation_unref(op);
}
}
}
static void process_new_source(CadPulse *self, const pa_source_info *info)
{
const gchar *prop;
int i;
prop = pa_proplist_gets(info->proplist, PA_PROP_DEVICE_CLASS);
if (prop && strcmp(prop, SINK_CLASS) != 0)
......@@ -93,35 +151,134 @@ static void process_new_source(CadPulse *self, const pa_source_info *info)
return;
self->source_id = info->index;
if (self->source_ports)
g_hash_table_destroy(self->source_ports);
self->source_ports = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
for (i = 0; i < info->n_ports; i++) {
pa_source_port_info *port = info->ports[i];
if (port->available != PA_PORT_AVAILABLE_UNKNOWN) {
g_hash_table_insert (self->source_ports,
g_strdup(port->name),
GINT_TO_POINTER(port->available));
}
}
g_debug("SOURCE: idx=%u name='%s'", info->index, info->name);
}
static void process_sink_ports(CadPulse *self, const pa_sink_info *info)
static void init_source_info(pa_context *ctx, const pa_source_info *info, int eol, void *data)
{
int i;
CadPulse *self = data;
const gchar *target_port;
pa_operation *op;
if (eol != 0)
return;
if (!info) {
g_critical("PA returned no source info (eol=%d)", eol);
return;
}
process_new_source(self, info);
if (self->source_id < 0)
return;
target_port = get_available_source_port(info, NULL);
if (target_port) {
op = pa_context_set_source_port_by_index(ctx, self->source_id,
target_port, NULL, NULL);
if (op)
pa_operation_unref(op);
}
}
/******************************************************************************
* Sink management
*
* The following functions take care of monitoring and configuring the default
* sink (output)
******************************************************************************/
static const gchar *get_available_sink_port(const pa_sink_info *sink, const gchar *exclude)
{
pa_sink_port_info *available_port = NULL;
guint i;
g_debug("looking for available output excluding '%s'", exclude);
for (i = 0; i < sink->n_ports; i++) {
pa_sink_port_info *port = sink->ports[i];
if ((exclude && strcmp(port->name, exclude) == 0) ||
port->available == PA_PORT_AVAILABLE_NO) {
continue;
}
if (!available_port || port->priority > available_port->priority)
available_port = port;
}
if (available_port) {
g_debug("found available output '%s'", available_port->name);
return available_port->name;
}
g_warning("no available output found!");
return NULL;
}
static void change_sink_info(pa_context *ctx, const pa_sink_info *info, int eol, void *data)
{
CadPulse *self = data;
const gchar *target_port;
pa_operation *op;
gboolean change = FALSE;
guint i;
if (eol != 0)
return;
if (!info) {
g_critical("PA returned no sink info (eol=%d)", eol);
return;
}
if (info->index != self->sink_id)
return;
for (i = 0; i < info->n_ports; i++) {
pa_sink_port_info *port = info->ports[i];
if (strstr(port->name, SND_USE_CASE_DEV_SPEAKER) != NULL) {
if (self->speaker_port) {
if (strcmp(port->name, self->speaker_port) != 0) {
g_free(self->speaker_port);
self->speaker_port = g_strdup(port->name);
}
} else {
self->speaker_port = g_strdup(port->name);
if (port->available != PA_PORT_AVAILABLE_UNKNOWN) {
enum pa_port_available available;
available = GPOINTER_TO_INT(g_hash_table_lookup(self->sink_ports, port->name));
if (available != port->available) {
g_hash_table_insert(self->sink_ports, g_strdup(port->name),
GINT_TO_POINTER(port->available));
change = TRUE;
}
}
}
g_debug("SINK: speaker_port='%s'", self->speaker_port);
if (change) {
target_port = get_available_sink_port(info, NULL);
if (target_port) {
op = pa_context_set_sink_port_by_index(ctx, self->sink_id,
target_port, NULL, NULL);
if (op)
pa_operation_unref(op);
}
}
}
static void process_new_sink(CadPulse *self, const pa_sink_info *info)
{
const gchar *prop;
guint i;
prop = pa_proplist_gets(info->proplist, PA_PROP_DEVICE_CLASS);
if (prop && strcmp(prop, SINK_CLASS) != 0)
......@@ -130,30 +287,41 @@ static void process_new_sink(CadPulse *self, const pa_sink_info *info)
return;
self->sink_id = info->index;
if (self->sink_ports)
g_hash_table_destroy(self->sink_ports);
self->sink_ports = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
g_debug("SINK: idx=%u name='%s'", info->index, info->name);
process_sink_ports(self, info);
}
static void init_source_info(pa_context *ctx, const pa_source_info *info, int eol, void *data)
{
CadPulse *self = data;
for (i = 0; i < info->n_ports; i++) {
pa_sink_port_info *port = info->ports[i];
if (eol != 0)
return;
if (strstr(port->name, SND_USE_CASE_DEV_SPEAKER) != NULL) {
if (self->speaker_port) {
if (strcmp(port->name, self->speaker_port) != 0) {
g_free(self->speaker_port);
self->speaker_port = g_strdup(port->name);
}
} else {
self->speaker_port = g_strdup(port->name);
}
}
if (!info) {
g_critical("PA returned no source info (eol=%d)", eol);
return;
if (port->available != PA_PORT_AVAILABLE_UNKNOWN) {
g_hash_table_insert (self->sink_ports,
g_strdup(port->name),
GINT_TO_POINTER(port->available));
}
}
process_new_source(self, info);
g_debug("SINK: speaker_port='%s'", self->speaker_port);
}
static void init_sink_info(pa_context *ctx, const pa_sink_info *info, int eol, void *data)
{
CadPulse *self = data;
const gchar *target_port;
pa_operation *op;
if (eol != 0)
return;
......@@ -164,8 +332,26 @@ static void init_sink_info(pa_context *ctx, const pa_sink_info *info, int eol, v
}
process_new_sink(self, info);
if (self->sink_id < 0)
return;
target_port = get_available_sink_port(info, NULL);
if (target_port) {
g_debug(" Using sink port '%s'", target_port);
op = pa_context_set_sink_port_by_index(ctx, self->sink_id,
target_port, NULL, NULL);
if (op)
pa_operation_unref(op);
}
}
/******************************************************************************
* Card management
*
* The following functions take care of gathering information about the default
* sound card
******************************************************************************/
static void init_card_info(pa_context *ctx, const pa_card_info *info, int eol, void *data)
{
CadPulse *self = data;
......@@ -206,22 +392,55 @@ static void init_card_info(pa_context *ctx, const pa_card_info *info, int eol, v
g_debug("CARD: %s voice profile", self->has_voice_profile ? "has" : "doesn't have");
}
static void init_cards_list(CadPulse *self)
{
pa_operation *op;
self->card_id = self->sink_id = self->source_id = -1;
op = pa_context_get_card_info_list(self->ctx, init_card_info, self);
if (op)
pa_operation_unref(op);
op = pa_context_get_sink_info_list(self->ctx, init_sink_info, self);
if (op)
pa_operation_unref(op);
op = pa_context_get_source_info_list(self->ctx, init_source_info, self);
if (op)
pa_operation_unref(op);
}
/******************************************************************************
* PulseAudio management
*
* The following functions configure the PulseAudio connection and monitor the
* state of PulseAudio objects
******************************************************************************/
static void init_module_info(pa_context *ctx, const pa_module_info *info, int eol, void *data)
{
pa_operation *op;
if (eol != 0)
return;
if (!info) {
g_critical("PA returned no module info (eol=%d)", eol);
return;
}
g_debug("MODULE: idx=%u name='%s'", info->index, info->name);
if (strcmp(info->name, "module-switch-on-port-available") == 0) {
g_debug("MODULE: unloading '%s'", info->name);
op = pa_context_unload_module(ctx, info->index, NULL, NULL);
if (op)
pa_operation_unref(op);
}
}
static void init_pulseaudio_objects(CadPulse *self)
{
pa_operation *op;
self->card_id = self->sink_id = self->source_id = -1;
self->sink_ports = self->source_ports = NULL;
op = pa_context_get_card_info_list(self->ctx, init_card_info, self);
if (op)
pa_operation_unref(op);
op = pa_context_get_module_info_list(self->ctx, init_module_info, self);
if (op)
pa_operation_unref(op);
op = pa_context_get_sink_info_list(self->ctx, init_sink_info, self);
if (op)
pa_operation_unref(op);
op = pa_context_get_source_info_list(self->ctx, init_source_info, self);
if (op)
pa_operation_unref(op);
}
static void changed_cb(pa_context *ctx, pa_subscription_event_type_t type, uint32_t idx, void *data)
{
......@@ -234,6 +453,8 @@ static void changed_cb(pa_context *ctx, pa_subscription_event_type_t type, uint3
if (idx == self->sink_id && kind == PA_SUBSCRIPTION_EVENT_REMOVE) {
g_debug("sink %u removed", idx);
self->sink_id = -1;
g_hash_table_destroy(self->sink_ports);
self->sink_ports = NULL;
} else if (kind == PA_SUBSCRIPTION_EVENT_NEW) {
g_debug("new sink %u", idx);
op = pa_context_get_sink_info_by_index(ctx, idx, init_sink_info, self);
......@@ -245,6 +466,8 @@ static void changed_cb(pa_context *ctx, pa_subscription_event_type_t type, uint3
if (idx == self->source_id && kind == PA_SUBSCRIPTION_EVENT_REMOVE) {
g_debug("source %u removed", idx);
self->source_id = -1;
g_hash_table_destroy(self->source_ports);
self->source_ports = NULL;
} else if (kind == PA_SUBSCRIPTION_EVENT_NEW) {
g_debug("new sink %u", idx);
op = pa_context_get_source_info_by_index(ctx, idx, init_source_info, self);
......@@ -252,16 +475,28 @@ static void changed_cb(pa_context *ctx, pa_subscription_event_type_t type, uint3
pa_operation_unref(op);
}
break;
case PA_SUBSCRIPTION_EVENT_CARD:
if (idx == self->card_id && kind == PA_SUBSCRIPTION_EVENT_CHANGE) {
g_debug("card %u changed", idx);
if (self->sink_id != -1) {
op = pa_context_get_sink_info_by_index(ctx, self->sink_id,
change_sink_info, self);
if (op)
pa_operation_unref(op);
}
if (self->source_id != -1) {
op = pa_context_get_source_info_by_index(ctx, self->source_id,
change_source_info, self);
if (op)
pa_operation_unref(op);
}
}
break;
default:
break;
}
}
static void subscribe_cb(pa_context *ctx, int success, void *data)
{
g_debug("subscribe returned %d", success);
}
static void pulse_state_cb(pa_context *ctx, void *data)
{
CadPulse *self = data;
......@@ -276,25 +511,33 @@ static void pulse_state_cb(pa_context *ctx, void *data)
g_debug("PA not ready");
break;
case PA_CONTEXT_FAILED:
g_error("Error in PulseAudio context: %s", pa_strerror(pa_context_errno(ctx)));
g_critical("Error in PulseAudio context: %s", pa_strerror(pa_context_errno(ctx)));
pulseaudio_cleanup(self);
g_idle_add(G_SOURCE_FUNC(pulseaudio_connect), self);
break;
case PA_CONTEXT_TERMINATED:
case PA_CONTEXT_READY:
pa_context_set_state_callback(ctx, NULL, NULL);
pa_context_set_subscribe_callback(ctx, changed_cb, self);
pa_context_subscribe(ctx,
PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE,
subscribe_cb, self);
PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_CARD,
NULL, self);
g_debug("PA is ready, initializing cards list");
init_cards_list(self);
init_pulseaudio_objects(self);
break;
}
}
static void constructed(GObject *object)
static void pulseaudio_cleanup(CadPulse *self)
{
if (self->ctx) {
pa_context_disconnect(self->ctx);
pa_context_unref(self->ctx);
self->ctx = NULL;
}
}
static gboolean pulseaudio_connect(CadPulse *self)
{
GObjectClass *parent_class = g_type_class_peek(G_TYPE_OBJECT);
CadPulse *self = CAD_PULSE(object);
pa_proplist *props;
int err;
......@@ -305,11 +548,13 @@ static void constructed(GObject *object)
err = pa_proplist_sets(props, PA_PROP_APPLICATION_NAME, APPLICATION_NAME);
err = pa_proplist_sets(props, PA_PROP_APPLICATION_ID, APPLICATION_ID);
self->loop = pa_glib_mainloop_new(NULL);
if (!self->loop)
self->loop = pa_glib_mainloop_new(NULL);
if (!self->loop)
g_error ("Error creating PulseAudio main loop");
self->ctx = pa_context_new(pa_glib_mainloop_get_api(self->loop), APPLICATION_NAME);
if (!self->ctx)
self->ctx = pa_context_new(pa_glib_mainloop_get_api(self->loop), APPLICATION_NAME);
if (!self->ctx)
g_error ("Error creating PulseAudio context");
......@@ -318,6 +563,20 @@ static void constructed(GObject *object)
if (err < 0)
g_error ("Error connecting to PulseAudio context: %s", pa_strerror(err));
return G_SOURCE_REMOVE;
}
/******************************************************************************
* GObject base functions
******************************************************************************/
static void constructed(GObject *object)
{
GObjectClass *parent_class = g_type_class_peek(G_TYPE_OBJECT);
CadPulse *self = CAD_PULSE(object);
pulseaudio_connect(self);
parent_class->constructed(object);
}
......@@ -330,11 +589,9 @@ static void dispose(GObject *object)
if (self->speaker_port)
g_free(self->speaker_port);
if (self->ctx) {
pa_context_disconnect(self->ctx);
pa_context_unref(self->ctx);
self->ctx = NULL;
pulseaudio_cleanup(self);
if (self->loop) {
pa_glib_mainloop_free(self->loop);
self->loop = NULL;
}
......@@ -367,6 +624,13 @@ CadPulse *cad_pulse_get_default(void)
return pulse;
}
/******************************************************************************
* Commands management
*
* The following functions handle external requests to switch mode, output port
* or microphone status
******************************************************************************/
static void operation_complete_cb(pa_context *ctx, int success, void *data)
{
CadPulseOperation *operation = data;
......@@ -453,9 +717,9 @@ static void set_output_port(pa_context *ctx, const pa_sink_info *info, int eol,
* be selected anyway.
*/
if (operation->value == CALL_AUDIO_MODE_CALL)
target_port = get_available_output(info, operation->pulse->speaker_port);
target_port = get_available_sink_port(info, operation->pulse->speaker_port);
else
target_port = get_available_output(info, NULL);
target_port = get_available_sink_port(info, NULL);
} else {
/*
* When forcing speaker output, we simply select the speaker port.
......@@ -466,7 +730,7 @@ static void set_output_port(pa_context *ctx, const pa_sink_info *info, int eol,
if (operation->value)
target_port = operation->pulse->speaker_port;
else
target_port = get_available_output(info, operation->pulse->speaker_port);
target_port = get_available_sink_port(info, operation->pulse->speaker_port);
}
g_debug("active port is '%s', target port is '%s'", info->active_port->name, target_port);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment