[pulseaudio-discuss] [PATCH 2/3] module-port-manager: Add new port manager module

David Henningsson david.henningsson at canonical.com
Tue Mar 10 07:05:58 PDT 2015


This module has two priority lists (one per direction), which it
uses for routing.

The lists contain several "rport"s, which are most often ports on
a card, but can also be sinks and sources without a card.

When default sink or source is changed, or when the active port is
changed on a sink or source, this is seen as "active user interaction",
and the priority list will be updated by moving the current active
rport to the top of the list (by increasing its priority).

Increased priorities will be saved in a pa_database, which has two
keys ("input" and "output") and a value which looks like
"name1\nprio1\nname2\nprio2\n".

Whenever anything relevant happens (such as port availability changes,
card, sink, source added or removed, or any of the above), routing
is updated so that existing streams are routed to the rport with
highest priority (that is also present and available).

Signed-off-by: David Henningsson <david.henningsson at canonical.com>
---
 src/Makefile.am                   |   8 +
 src/modules/module-port-manager.c | 788 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 796 insertions(+)
 create mode 100644 src/modules/module-port-manager.c

diff --git a/src/Makefile.am b/src/Makefile.am
index 67f8627..2f77974 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1148,6 +1148,7 @@ modlibexec_LTLIBRARIES += \
 		module-detect.la \
 		module-volume-restore.la \
 		module-device-manager.la \
+		module-port-manager.la \
 		module-device-restore.la \
 		module-stream-restore.la \
 		module-card-restore.la \
@@ -1489,6 +1490,7 @@ SYMDEF_FILES = \
 		module-jack-source-symdef.h \
 		module-volume-restore-symdef.h \
 		module-device-manager-symdef.h \
+		module-port-manager-symdef.h \
 		module-device-restore-symdef.h \
 		module-stream-restore-symdef.h \
 		module-card-restore-symdef.h \
@@ -1946,6 +1948,12 @@ module_device_manager_la_LDFLAGS = $(MODULE_LDFLAGS)
 module_device_manager_la_LIBADD = $(MODULE_LIBADD) libprotocol-native.la
 module_device_manager_la_CFLAGS = $(AM_CFLAGS)
 
+# port manager module
+module_port_manager_la_SOURCES = modules/module-port-manager.c
+module_port_manager_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_port_manager_la_LIBADD = $(MODULE_LIBADD)
+module_port_manager_la_CFLAGS = $(AM_CFLAGS)
+
 # Device volume/muted restore module
 module_device_restore_la_SOURCES = modules/module-device-restore.c
 module_device_restore_la_LDFLAGS = $(MODULE_LDFLAGS)
diff --git a/src/modules/module-port-manager.c b/src/modules/module-port-manager.c
new file mode 100644
index 0000000..df31416
--- /dev/null
+++ b/src/modules/module-port-manager.c
@@ -0,0 +1,788 @@
+/***
+  This file is part of PulseAudio.
+
+  Written by David Henningsson, Copyright 2015 Canonical Ltd.
+  (With a few pieces copy-pasted from module-device-manager and
+  module-switch-on-port-available.)
+
+  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, see <http://www.gnu.org/licenses/>.
+***/
+
+/*
+Module overview:
+
+This module has two priority lists (one per direction), which it
+uses for routing.
+
+The lists contain several "rport"s, which are most often ports on
+a card, but can also be sinks and sources without a card.
+
+When default sink or source is changed, or when the active port is
+changed on a sink or source, this is seen as "active user interaction",
+and the priority list will be updated by moving the current active
+rport to the top of the list (by increasing its priority).
+
+Increased priorities will be saved in a pa_database, which has two
+keys ("input" and "output") and a value which looks like
+"name1\nprio1\nname2\nprio2\n".
+
+Whenever anything relevant happens (such as port availability changes,
+card, sink, source added or removed, or any of the above), routing
+is updated so that existing streams are routed to the rport with
+highest priority (that is also present and available).
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <pulse/rtclock.h>
+#include <pulse/timeval.h>
+#include <pulsecore/core.h>
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/log.h>
+#include <pulsecore/strbuf.h>
+#include <pulsecore/database.h>
+#include <pulsecore/dynarray.h>
+#include <pulsecore/device-port.h>
+
+#include "module-port-manager-symdef.h"
+
+PA_MODULE_AUTHOR("David Henningsson");
+PA_MODULE_DESCRIPTION("Port based routing module");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(true);
+
+PA_MODULE_USAGE("");
+
+#define SAVE_INTERVAL (5 * PA_USEC_PER_SEC)
+
+typedef struct rport rport;
+typedef struct rplist rplist;
+
+/* An rport is most often a port that belongs to a card, but we also have sinks
+   and sources not belonging to a card. These do not have ports either.
+
+   (Note: it's theoretically possible for sinks without a card to have ports,
+   but at the time of this writing, there are no such backends. So that case
+   is unsupported, but should be possible to add in case it becomes necessary.) */
+struct rport {
+    const pa_device_port *port; /* One of port/sink/source are set in case it is currently in the system. If it only exists in the database, then all are NULL. */
+    pa_sink *sink;
+    pa_source *source;
+    char *name; /* card:%s+port:%s - sink:%s - source:%s */
+    bool manual; /* true if user has explicitly set this priority */
+    unsigned priority;
+};
+
+/* List of rports. */
+struct rplist {
+    pa_dynarray *l; /* Unsorted list. */
+    char *name; /* Database key */
+    pa_direction_t direction;
+    bool dirty; /* Needs to be saved to database */
+};
+
+typedef struct userdata userdata;
+struct userdata {
+    pa_core *core;
+    rplist input, output;
+    pa_database *database;
+    pa_time_event *save_time_event;
+    pa_dynarray *hooks;
+    bool routing_in_progress;
+};
+
+static void rport_free(rport *r) {
+    if (r) {
+        pa_xfree(r->name);
+        pa_xfree(r);
+    }
+}
+
+static void rplist_save(userdata *u, rplist *rl) {
+    pa_strbuf *buf;
+    pa_datum key, value;
+    rport *r;
+    unsigned idx;
+
+    pa_assert(u);
+    pa_assert(u->database);
+
+    if (!rl->dirty)
+        return;
+
+    buf = pa_strbuf_new();
+    PA_DYNARRAY_FOREACH(r, rl->l, idx)
+        if (r->manual)
+            pa_strbuf_printf(buf, "%s\n%u\n", r->name, r->priority);
+
+    key.data = rl->name;
+    key.size = strlen(rl->name);
+    value.data = pa_strbuf_tostring_free(buf);
+    value.size = strlen(value.data);
+    if (pa_database_set(u->database, &key, &value, true) != 0)
+        pa_log_warn("Could not save port-manager database, key %s", rl->name);
+
+    pa_xfree(value.data);
+    rl->dirty = false; /* Probably better to skip a save, than to get stuck in retry-save loops */
+}
+
+/* Note - does not free the rplist itself, only the contents of it. */
+static void rplist_free(userdata *u, rplist rl) {
+    rplist_save(u, &rl);
+    if (rl.l)
+        pa_dynarray_free(rl.l);
+    pa_xfree(rl.name);
+}
+
+static void save_time_callback(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *uu) {
+    userdata *u = uu;
+
+    pa_assert(e);
+    pa_assert(u);
+
+    pa_assert(e == u->save_time_event);
+    u->core->mainloop->time_free(u->save_time_event);
+    u->save_time_event = NULL;
+
+    rplist_save(u, &u->input);
+    rplist_save(u, &u->output);
+    pa_database_sync(u->database);
+    pa_log_debug("port-manager database stored to disk.");
+}
+
+static pa_sink *find_sink_for_port(const pa_device_port *port) {
+    pa_sink *sink = NULL;
+    uint32_t state;
+
+    pa_assert(port);
+    pa_assert(port->card);
+    pa_assert(port->direction == PA_DIRECTION_OUTPUT);
+
+    PA_IDXSET_FOREACH(sink, port->card->sinks, state)
+       if (port == pa_hashmap_get(sink->ports, port->name))
+            return sink;
+
+    return NULL;
+}
+
+static pa_source *find_source_for_port(const pa_device_port *port) {
+    pa_source *source = NULL;
+    uint32_t state;
+
+    pa_assert(port);
+    pa_assert(port->card);
+    pa_assert(port->direction == PA_DIRECTION_INPUT);
+
+    PA_IDXSET_FOREACH(source, port->card->sources, state)
+       if (port == pa_hashmap_get(source->ports, port->name))
+            return source;
+
+    return NULL;
+}
+
+static rport *rplist_find_by_name(rplist *rl, const char* name) {
+    uint32_t idx;
+    rport *r;
+    PA_DYNARRAY_FOREACH(r, rl->l, idx)
+        if (pa_streq(name, r->name))
+            return r;
+    return NULL;
+}
+
+static rport *rplist_find_best(rplist *rl) {
+    uint32_t idx;
+    rport *r, *rbest = NULL;
+
+    PA_DYNARRAY_FOREACH(r, rl->l, idx) {
+        if (r->port) {
+            //void *s = NULL;
+
+            if (r->port->available == PA_AVAILABLE_NO)
+                continue;
+
+            /* Disregard ports not completely set up */
+/*            s = rl->direction == PA_DIRECTION_INPUT ? (void *) find_source_for_port(r->port) : (void *) find_sink_for_port(r->port);
+            if (s == NULL)
+                continue; */
+        }
+        if (r->port == NULL && r->sink == NULL && r->source == NULL)
+            continue; /* It's in the database, but not physically present. */
+        if (rbest == NULL || r->priority > rbest->priority)
+            rbest = r;
+    }
+
+    return rbest;
+}
+
+static void rplist_dump(rplist *rl) {
+    uint32_t idx;
+    rport *r;
+
+    pa_log_debug("Current contents of port-manager list '%s':", rl->name);
+    PA_DYNARRAY_FOREACH(r, rl->l, idx)
+        pa_log_debug("%8u %s %s: %s",
+            r->priority,
+            r->manual ? "manual" : "(auto)",
+            r->port ? (r->port->available == PA_AVAILABLE_NO ? "N" : "Y") : r->sink || r->source ? "S" : " ",
+            r->name);
+    r = rplist_find_best(rl);
+    pa_log_debug("  The best one available is: %s", r ? r->name : "(none)");
+}
+
+static char *port_to_name(const pa_device_port *port) {
+    pa_assert(port);
+    pa_assert(port->card);
+
+    return pa_sprintf_malloc("card:%s+port:%s", port->card->name, port->name);
+}
+
+static char *sink_to_name(const pa_sink *sink) {
+    if (sink->active_port)
+        return port_to_name(sink->active_port);
+    return pa_sprintf_malloc("sink:%s", sink->name);
+}
+
+static char *source_to_name(const pa_source *source) {
+    if (source->active_port)
+        return port_to_name(source->active_port);
+    return pa_sprintf_malloc("source:%s", source->name);
+}
+
+static void route_sink_input(struct userdata *u, pa_sink_input *si, pa_sink *sink) {
+    pa_assert(u);
+
+    if (si->save_sink)
+        return;
+
+    /* Skip this if it is already in the process of being moved anyway */
+    if (!si->sink)
+        return;
+
+    /* It might happen that a stream and a sink are set up at the
+    same time, in which case we want to make sure we don't
+    interfere with that */
+    if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si)))
+        return;
+
+    if (si->sink != sink)
+        pa_sink_input_move_to(si, sink, false);
+}
+
+static void route_source_output(struct userdata *u, pa_source_output *so, pa_source *source) {
+    pa_assert(u);
+
+    if (so->save_source)
+        return;
+
+    if (so->direct_on_input)
+        return;
+
+    /* Skip this if it is already in the process of being moved anyway */
+    if (!so->source)
+        return;
+
+    /* It might happen that a stream and a source are set up at the
+    same time, in which case we want to make sure we don't
+    interfere with that */
+    if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so)))
+        return;
+
+    if (so->source != source)
+        pa_source_output_move_to(so, source, false);
+}
+
+static bool profile_good_for_input(pa_card_profile *profile) {
+    pa_assert(profile);
+
+    if (profile->card->active_profile->n_sinks != profile->n_sinks)
+        return false;
+
+    if (profile->card->active_profile->max_sink_channels != profile->max_sink_channels)
+        return false;
+
+    return true;
+}
+
+static bool profile_good_for_output(pa_card_profile *profile) {
+    pa_assert(profile);
+
+    if (profile->card->active_profile->n_sources != profile->n_sources)
+        return false;
+
+    if (profile->card->active_profile->max_source_channels != profile->max_source_channels)
+        return false;
+
+    return true;
+}
+
+static bool try_to_switch_profile(const pa_device_port *port) {
+    pa_card_profile *best_profile = NULL, *profile;
+    void *state;
+
+    PA_HASHMAP_FOREACH(profile, port->profiles, state) {
+        bool good = false;
+
+        if (best_profile && best_profile->priority >= profile->priority)
+            continue;
+
+        /* We make a best effort to keep other direction unchanged */
+        switch (port->direction) {
+            case PA_DIRECTION_OUTPUT:
+                good = profile_good_for_output(profile);
+                break;
+
+            case PA_DIRECTION_INPUT:
+                good = profile_good_for_input(profile);
+                break;
+        }
+
+        if (!good)
+            continue;
+
+        best_profile = profile;
+    }
+
+    if (!best_profile) {
+        pa_log_debug("No suitable profile found");
+        return false;
+    }
+
+    if (pa_card_set_profile(port->card, best_profile, false) != 0) {
+        pa_log_debug("Could not set profile %s", best_profile->name);
+        return false;
+    }
+
+    return true;
+}
+
+
+static void rplist_route(userdata *u, rplist *rl) {
+    uint32_t idx;
+    rport *r;
+
+    if (u->core->state == PA_CORE_SHUTDOWN || u->routing_in_progress)
+        return;
+
+    rplist_dump(rl);
+
+    r = rplist_find_best(rl);
+    if (r == NULL)
+        return;
+
+    u->routing_in_progress = true;
+
+    switch (rl->direction) {
+    case PA_DIRECTION_INPUT:
+        {
+            pa_source *s = r->source;
+            if (r->port) {
+                s = find_source_for_port(r->port);
+                if (!s && try_to_switch_profile(r->port))
+                    s = find_source_for_port(r->port);
+                if (s)
+                    pa_source_set_port(s, r->port->name, false);
+            }
+            if (s) {
+                pa_source_output *so;
+                PA_IDXSET_FOREACH(so, u->core->source_outputs, idx)
+                    route_source_output(u, so, s);
+            }
+            else
+                pa_log_info("Failed to activate a source for port '%s'", r->name);
+        }
+        break;
+    case PA_DIRECTION_OUTPUT:
+        {
+            pa_sink *s = r->sink;
+            if (r->port) {
+                s = find_sink_for_port(r->port);
+                if (!s && try_to_switch_profile(r->port))
+                    s = find_sink_for_port(r->port);
+                if (s)
+                    pa_sink_set_port(s, r->port->name, false);
+            }
+            if (s) {
+                pa_sink_input *si;
+                PA_IDXSET_FOREACH(si, u->core->sink_inputs, idx)
+                    route_sink_input(u, si, s);
+            }
+            else
+                pa_log_info("Failed to activate a sink for port '%s'", r->name);
+        }
+        break;
+    }
+    u->routing_in_progress = false;
+}
+
+static void rplist_set_to_highest(userdata *u, rplist *rl, rport *r) {
+    unsigned mprio = 0;
+    uint32_t idx;
+    rport *rr;
+
+    pa_assert(r);
+
+    PA_DYNARRAY_FOREACH(rr, rl->l, idx)
+        if (rr != r && rr->priority > mprio)
+            mprio = rr->priority;
+    if (mprio >= r->priority) {
+        pa_log_debug("Raising priority of %s from %u to %u", r->name, r->priority, mprio+1);
+        r->priority = mprio+1;
+        r->manual = true;
+
+        rl->dirty = true;
+        if (!u->save_time_event)
+            u->save_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, save_time_callback, u);
+    }
+}
+
+static void rplist_remove(userdata *u, rplist *rl, const pa_device_port *port, pa_sink *sink, pa_source *source) {
+    uint32_t idx;
+    rport *r;
+    bool change = false;
+
+    PA_DYNARRAY_FOREACH(r, rl->l, idx) {
+        if (port && port == r->port) {
+            r->port = NULL;
+            change = true;
+        }
+        if (sink && sink == r->sink) {
+            r->sink = NULL;
+            change = true;
+        }
+        if (source == r->source) {
+            r->source = NULL;
+            change = true;
+        }
+    }
+
+    if (change)
+        rplist_route(u, rl);
+}
+
+static void rplist_new_port(rplist *rl, const pa_device_port *port) {
+    rport *r;
+    char *name = port_to_name(port);
+
+    pa_assert(rl->direction == port->direction);
+
+    if ((r = rplist_find_by_name(rl, name))) {
+        r->port = port;
+        pa_xfree(name);
+        return;
+    }
+
+    r = pa_xnew0(rport, 1);
+    r->port = port;
+    r->name = name;
+    r->priority = port->priority;
+    pa_dynarray_append(rl->l, r);
+}
+
+static void rplist_new_card(rplist *rl, const pa_card *card) {
+    pa_device_port *port;
+    void *state;
+
+    PA_HASHMAP_FOREACH(port, card->ports, state)
+        if (port->direction == rl->direction)
+            rplist_new_port(rl, port);
+}
+
+static void rplist_new_sink(rplist *rl, pa_sink *s) {
+    rport *r;
+    char *name;
+
+    pa_assert(rl->direction == PA_DIRECTION_OUTPUT);
+
+    if (pa_hashmap_size(s->ports) > 0)
+        return;
+
+    name = sink_to_name(s);
+
+    if ((r = rplist_find_by_name(rl, name))) {
+        r->sink = s;
+        pa_xfree(name);
+        return;
+    }
+
+    r = pa_xnew0(rport, 1);
+    r->sink = s;
+    r->name = name;
+    r->priority = s->priority;
+    pa_dynarray_append(rl->l, r);
+}
+
+static void rplist_new_source(rplist *rl, pa_source *s) {
+    rport *r;
+    char *name;
+
+    pa_assert(rl->direction == PA_DIRECTION_INPUT);
+
+    if (pa_hashmap_size(s->ports) > 0)
+        return;
+
+    name = source_to_name(s);
+
+    if ((r = rplist_find_by_name(rl, name))) {
+        r->source = s;
+        pa_xfree(name);
+        return;
+    }
+
+    r = pa_xnew0(rport, 1);
+    r->source = s;
+    r->name = name;
+    r->priority = s->priority;
+    pa_dynarray_append(rl->l, r);
+}
+
+static void rplist_new_name(rplist *rl, const char *name, unsigned prio) {
+    rport *r;
+
+    if (!(r = rplist_find_by_name(rl, name))) {
+        r = pa_xnew0(rport, 1);
+        r->name = pa_xstrdup(name);
+        pa_dynarray_append(rl->l, r);
+    }
+    r->priority = prio;
+    r->manual = true;
+}
+
+static rplist rplist_create(userdata *u, const char *key, pa_direction_t dir) {
+    uint32_t state;
+    pa_card *card;
+    pa_sink *sink;
+    pa_source *source;
+    pa_datum db_key;
+    pa_datum db_value;
+    rplist r;
+
+    pa_assert(u);
+    pa_assert(u->database);
+    pa_assert(key);
+
+    r.l = pa_dynarray_new((pa_free_cb_t) rport_free);
+    r.name = pa_xstrdup(key);
+    r.direction = dir;
+    r.dirty = false;
+
+    PA_IDXSET_FOREACH(card, u->core->cards, state)
+        rplist_new_card(&r, card);
+
+    switch (dir) {
+    case PA_DIRECTION_INPUT:
+        PA_IDXSET_FOREACH(source, u->core->sources, state)
+            rplist_new_source(&r, source);
+        break;
+    case PA_DIRECTION_OUTPUT:
+        PA_IDXSET_FOREACH(sink, u->core->sinks, state)
+            rplist_new_sink(&r, sink);
+        break;
+    }
+
+    db_key.data = (char*) key;
+    db_key.size = strlen(key);
+    pa_zero(db_value);
+    if (pa_database_get(u->database, &db_key, &db_value) && db_value.data) {
+        char* c = pa_xstrndup(db_value.data, db_value.size);
+        char* dname;
+        const char* state2 = NULL;
+        while ((dname = pa_split(c, "\n", &state2))) {
+            uint32_t prio;
+            char *cprio = pa_split(c, "\n", &state2);
+            if (cprio == NULL) {
+                pa_log_warn("Skipping load of port-manager, '%s', '%s' (missing priority)", key, dname);
+                pa_xfree(dname);
+                continue;
+            }
+
+            if (pa_atou(cprio, &prio) >= 0)
+                rplist_new_name(&r, dname, prio);
+            else
+                pa_log_warn("Skipping load of port-manager, '%s', '%s' priority corrupt (%s)", key, dname, cprio);
+
+            pa_xfree(cprio);
+            pa_xfree(dname);
+        }
+        pa_xfree(c);
+    }
+    pa_datum_free(&db_value);
+
+    return r;
+}
+
+static void default_rport_changed(struct userdata *u, rplist *rl, const char *name) {
+    rport *r = rplist_find_by_name(rl, name);
+    if (r == NULL) {
+        pa_log("Asked to switch to %s, but could not find it in port-manager list '%s'", name, rl->name);
+        rplist_dump(rl);
+        return;
+    }
+
+    rplist_set_to_highest(u, rl, r);
+    rplist_route(u, rl);
+}
+
+static pa_hook_result_t sink_changed(pa_core *c, pa_sink *sink, struct userdata *u) {
+    char *name;
+
+    if (!sink || u->routing_in_progress)
+        return PA_HOOK_OK;
+    name = sink_to_name(sink);
+    default_rport_changed(u, &u->output, name);
+    pa_xfree(name);
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_changed(pa_core *c, pa_source *source, struct userdata *u) {
+    char *name;
+
+    if (!source || u->routing_in_progress)
+        return PA_HOOK_OK;
+    name = source_to_name(source);
+    default_rport_changed(u, &u->input, name);
+    pa_xfree(name);
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t card_unlink(pa_core *c, pa_card *card, struct userdata *u) {
+    pa_device_port *p;
+    void *state;
+
+    PA_HASHMAP_FOREACH(p, card->ports, state) {
+        if (p->direction == PA_DIRECTION_OUTPUT)
+            rplist_remove(u, &u->output, p, NULL, NULL);
+        else
+            rplist_remove(u, &u->input, p, NULL, NULL);
+    }
+
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_unlink(pa_core *c, pa_sink *sink, struct userdata *u) {
+    rplist_remove(u, &u->output, NULL, sink, NULL);
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_unlink(pa_core *c, pa_source *source, struct userdata *u) {
+    rplist_remove(u, &u->output, NULL, NULL, source);
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t card_put(pa_core *c, pa_card *card, struct userdata *u) {
+    pa_assert(card);
+
+    rplist_new_card(&u->input, card);
+    rplist_new_card(&u->output, card);
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_put(pa_core *c, pa_sink *sink, struct userdata *u) {
+    rplist_new_sink(&u->output, sink);
+    rplist_route(u, &u->output);
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_put(pa_core *c, pa_source *source, struct userdata *u) {
+    rplist_new_source(&u->input, source);
+    rplist_route(u, &u->input);
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t port_available_changed(pa_core *c, pa_device_port *port, struct userdata *u) {
+    rplist_route(u, port->direction == PA_DIRECTION_INPUT ? &u->input : &u->output);
+    return PA_HOOK_OK;
+}
+
+static void add_hook(userdata *u, enum pa_core_hook ch, pa_hook_priority_t prio, pa_hook_cb_t cb) {
+    pa_dynarray_append(u->hooks, pa_hook_connect(&u->core->hooks[ch], prio, cb, u));
+}
+
+int pa__init(pa_module *m) {
+    char *fname = NULL;
+    struct userdata* u = pa_xnew0(struct userdata, 1);
+
+    m->userdata = u;
+    u->core = m->core;
+
+    if (!(fname = pa_state_path("port-manager", true)))
+        goto fail;
+
+    if (!(u->database = pa_database_open(fname, true))) {
+        pa_log("Failed to open port-manager database '%s': %s", fname, pa_cstrerror(errno));
+        goto fail;
+    }
+    pa_xfree(fname);
+
+    u->output = rplist_create(u, "output", PA_DIRECTION_OUTPUT);
+    u->input = rplist_create(u, "input", PA_DIRECTION_INPUT);
+
+    u->hooks = pa_dynarray_new((pa_free_cb_t) pa_hook_slot_free);
+
+    /* These four count as "active user interaction", i e, move to highest */
+    add_hook(u, PA_CORE_HOOK_DEFAULT_SINK_CHANGED, PA_HOOK_NORMAL, (pa_hook_cb_t) sink_changed);
+    add_hook(u, PA_CORE_HOOK_DEFAULT_SOURCE_CHANGED, PA_HOOK_NORMAL, (pa_hook_cb_t) source_changed);
+    add_hook(u, PA_CORE_HOOK_SINK_PORT_CHANGED, PA_HOOK_NORMAL, (pa_hook_cb_t) sink_changed);
+    add_hook(u, PA_CORE_HOOK_SOURCE_PORT_CHANGED, PA_HOOK_NORMAL, (pa_hook_cb_t) source_changed);
+
+    /* PA_HOOK_LATE+5 is from module-device-manager - a little bit later than module-stream-restore, and a little bit earlier than module-intended-roles and module-rescue-streams */
+    add_hook(u, PA_CORE_HOOK_CARD_UNLINK, PA_HOOK_LATE+5, (pa_hook_cb_t) card_unlink);
+    add_hook(u, PA_CORE_HOOK_CARD_PUT, PA_HOOK_LATE+5, (pa_hook_cb_t) card_put);
+    add_hook(u, PA_CORE_HOOK_SINK_PUT, PA_HOOK_LATE+5, (pa_hook_cb_t) sink_put);
+    add_hook(u, PA_CORE_HOOK_SOURCE_PUT, PA_HOOK_LATE+5, (pa_hook_cb_t) source_put);
+    add_hook(u, PA_CORE_HOOK_SINK_UNLINK, PA_HOOK_LATE+5, (pa_hook_cb_t) sink_unlink);
+    add_hook(u, PA_CORE_HOOK_SOURCE_UNLINK, PA_HOOK_LATE+5, (pa_hook_cb_t) source_unlink);
+    add_hook(u, PA_CORE_HOOK_PORT_AVAILABLE_CHANGED, PA_HOOK_LATE+5, (pa_hook_cb_t) port_available_changed);
+
+    rplist_route(u, &u->output);
+    rplist_route(u, &u->input);
+
+    return 0;
+
+fail:
+    pa__done(m);
+
+    if (fname)
+        pa_xfree(fname);
+
+    return -1;
+}
+
+void pa__done(pa_module *m) {
+    struct userdata* u;
+
+    pa_assert(m);
+
+    if (!(u = m->userdata))
+        return;
+
+    if (u->hooks)
+        pa_dynarray_free(u->hooks);
+
+    if (u->save_time_event)
+        save_time_callback(NULL, u->save_time_event, NULL, u);
+
+    rplist_free(u, u->output);
+    rplist_free(u, u->input);
+
+    if (u->database)
+        pa_database_close(u->database);
+
+    pa_xfree(u);
+}
-- 
1.9.1



More information about the pulseaudio-discuss mailing list