Add a new module switch-on-port-available that acts on port changes
authorDavid Henningsson <david.henningsson@canonical.com>
Thu, 23 Feb 2012 06:17:04 +0000 (07:17 +0100)
committerArun Raghavan <arun.raghavan@collabora.co.uk>
Sun, 11 Mar 2012 06:53:46 +0000 (12:23 +0530)
This module tries to switch to a port when availability changes to
"YES", and tries to switch away when availability changes to "NO".

Once there is a priority list infrastructure in place and ready,
this functionality might be redundant, but this will do as an
interim solution.

src/Makefile.am
src/modules/module-switch-on-port-available.c [new file with mode: 0644]

index f232151..b07c60a 100644 (file)
@@ -1013,6 +1013,7 @@ modlibexec_LTLIBRARIES += \
                module-virtual-source.la \
                module-virtual-surround-sink.la \
                module-switch-on-connect.la \
+               module-switch-on-port-available.la \
                module-filter-apply.la \
                module-filter-heuristics.la
 
@@ -1319,6 +1320,7 @@ SYMDEF_FILES = \
                module-virtual-source-symdef.h \
                module-virtual-surround-sink-symdef.h \
                module-switch-on-connect-symdef.h \
+               module-switch-on-port-available-symdef.h \
                module-filter-apply-symdef.h \
                module-filter-heuristics-symdef.h
 
@@ -1485,6 +1487,10 @@ module_switch_on_connect_la_SOURCES = modules/module-switch-on-connect.c
 module_switch_on_connect_la_LDFLAGS = $(MODULE_LDFLAGS)
 module_switch_on_connect_la_LIBADD = $(MODULE_LIBADD)
 
+module_switch_on_port_available_la_SOURCES = modules/module-switch-on-port-available.c
+module_switch_on_port_available_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_switch_on_port_available_la_LIBADD = $(MODULE_LIBADD)
+
 module_filter_apply_la_SOURCES = modules/module-filter-apply.c
 module_filter_apply_la_LDFLAGS = $(MODULE_LDFLAGS)
 module_filter_apply_la_LIBADD = $(MODULE_LIBADD)
diff --git a/src/modules/module-switch-on-port-available.c b/src/modules/module-switch-on-port-available.c
new file mode 100644 (file)
index 0000000..9149b6d
--- /dev/null
@@ -0,0 +1,233 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2006 Lennart Poettering
+  Copyright 2011 Canonical Ltd
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core.h>
+#include <pulsecore/device-port.h>
+#include <pulsecore/hashmap.h>
+
+#include "module-switch-on-port-available-symdef.h"
+
+struct userdata {
+     pa_hook_slot *callback_slot;
+};
+
+static pa_device_port* find_best_port(pa_hashmap *ports) {
+    void *state;
+    pa_device_port* port, *result = NULL;
+
+    PA_HASHMAP_FOREACH(port, ports, state) {
+        if (result == NULL ||
+            result->available == PA_PORT_AVAILABLE_NO ||
+            (port->available != PA_PORT_AVAILABLE_NO && port->priority > result->priority)) {
+            result = port;
+        }
+    }
+
+    return result;
+}
+
+static pa_bool_t try_to_switch_profile(pa_card *card, pa_device_port *port) {
+    pa_card_profile *best_profile = NULL, *profile;
+    void *state;
+
+    pa_log_debug("Finding best profile");
+
+    if (port->profiles)
+        PA_HASHMAP_FOREACH(profile, port->profiles, state) {
+            if (best_profile && best_profile->priority >= profile->priority)
+                continue;
+
+            /* We make a best effort to keep other direction unchanged */
+            if (card->active_profile && !port->is_input) {
+                if (card->active_profile->n_sources != profile->n_sources)
+                    continue;
+
+                if (card->active_profile->max_source_channels != profile->max_source_channels)
+                    continue;
+            }
+
+            if (card->active_profile && !port->is_output) {
+                if (card->active_profile->n_sinks != profile->n_sinks)
+                    continue;
+
+                if (card->active_profile->max_sink_channels != profile->max_sink_channels)
+                    continue;
+            }
+
+            best_profile = profile;
+        }
+
+    if (!best_profile) {
+        pa_log_debug("No suitable profile found");
+        return FALSE;
+    }
+
+    if (pa_card_set_profile(card, best_profile->name, FALSE) != 0) {
+        pa_log_debug("Could not set profile %s", best_profile->name);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static void find_sink_and_source(pa_card *card, pa_device_port *port, pa_sink **si, pa_source **so)
+{
+    pa_sink *sink = NULL;
+    pa_source *source = NULL;
+    uint32_t state;
+
+    if (port->is_output)
+        PA_IDXSET_FOREACH(sink, card->sinks, state)
+            if (sink->ports && port == pa_hashmap_get(sink->ports, port->name))
+                break;
+
+    if (port->is_input)
+        PA_IDXSET_FOREACH(source, card->sources, state)
+            if (source->ports && port == pa_hashmap_get(source->ports, port->name))
+                break;
+
+    *si = sink;
+    *so = source;
+}
+
+static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port *port, void* userdata) {
+    uint32_t state;
+    pa_card* card;
+    pa_sink *sink;
+    pa_source *source;
+    pa_bool_t is_active_profile, is_active_port;
+
+    pa_log_debug("finding port %s", port->name);
+
+    PA_IDXSET_FOREACH(card, c->cards, state)
+        if (card->ports && port == pa_hashmap_get(card->ports, port->name))
+            break;
+
+    if (!card) {
+        pa_log_warn("Did not find port %s in array of cards", port->name);
+        return PA_HOOK_OK;
+    }
+
+    find_sink_and_source(card, port, &sink, &source);
+
+    is_active_profile = port->profiles && card->active_profile &&
+        card->active_profile == pa_hashmap_get(port->profiles, card->active_profile->name);
+    is_active_port = (sink && sink->active_port == port) || (source && source->active_port == port);
+
+    if (port->available == PA_PORT_AVAILABLE_NO && !is_active_port)
+        return PA_HOOK_OK;
+
+    if (port->available == PA_PORT_AVAILABLE_YES) {
+        if (is_active_port)
+            return PA_HOOK_OK;
+
+        if (!is_active_profile) {
+            if (!try_to_switch_profile(card, port))
+                return PA_HOOK_OK;
+
+            pa_assert(card->active_profile == pa_hashmap_get(port->profiles, card->active_profile->name));
+
+            /* Now that profile has changed, our sink and source pointers must be updated */
+            find_sink_and_source(card, port, &sink, &source);
+        }
+
+        if (source)
+            pa_source_set_port(source, port->name, FALSE);
+        if (sink)
+            pa_sink_set_port(sink, port->name, FALSE);
+    }
+
+    if (port->available == PA_PORT_AVAILABLE_NO) {
+        if (sink) {
+            pa_device_port *p2 = find_best_port(sink->ports);
+
+            if (p2 && p2->available != PA_PORT_AVAILABLE_NO)
+                pa_sink_set_port(sink, p2->name, FALSE);
+            else {
+                /* Maybe try to switch to another profile? */
+            }
+        }
+
+        if (source) {
+            pa_device_port *p2 = find_best_port(source->ports);
+
+            if (p2 && p2->available != PA_PORT_AVAILABLE_NO)
+                pa_source_set_port(source, p2->name, FALSE);
+            else {
+                /* Maybe try to switch to another profile? */
+            }
+        }
+    }
+
+    return PA_HOOK_OK;
+}
+
+static void handle_all_unavailable(pa_core *core) {
+    pa_card *card;
+    uint32_t state;
+
+    PA_IDXSET_FOREACH(card, core->cards, state) {
+        pa_device_port *port;
+        void *state2;
+
+        if (!card->ports)
+            continue;
+
+        PA_HASHMAP_FOREACH(port, card->ports, state2) {
+            if (port->available == PA_PORT_AVAILABLE_NO)
+                port_available_hook_callback(core, port, NULL);
+        }
+    }
+}
+
+int pa__init(pa_module*m) {
+    struct userdata *u;
+
+    pa_assert(m);
+
+    m->userdata = u = pa_xnew(struct userdata, 1);
+
+    u->callback_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_PORT_AVAILABLE_CHANGED],
+                                       PA_HOOK_LATE, (pa_hook_cb_t) port_available_hook_callback, u);
+
+    handle_all_unavailable(m->core);
+
+    return 0;
+}
+
+void pa__done(pa_module*m) {
+    struct userdata *u;
+
+    pa_assert(m);
+
+    if (!(u = m->userdata))
+        return;
+
+    if (u->callback_slot)
+        pa_hook_slot_free(u->callback_slot);
+
+    pa_xfree(u);
+}