<commit_before>/*
 * module-murphy-ivi -- PulseAudio module for providing audio routing support
 * Copyright (c) 2012, Intel Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU Lesser General Public License,
 * version 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St - Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 */
#include <stdio.h>
#include <assert.h>

#include <pulsecore/pulsecore-config.h>

#include <pulsecore/hashmap.h>
#include <pulsecore/idxset.h>
#include <pulsecore/client.h>
#include <pulsecore/core-util.h>
#include <pulsecore/log.h>
#include <pulsecore/card.h>
#include <pulsecore/device-port.h>
#include <pulsecore/sink-input.h>
#include <pulsecore/source-output.h>
#include <pulsecore/strbuf.h>


#include "discover.h"
#include "node.h"
#include "audiomgr.h"
#include "router.h"
#include "constrain.h"
#include "multiplex.h"
#include "loopback.h"
#include "fader.h"
#include "classify.h"
#include "utils.h"
#include "extapi.h"
#include "stream-state.h"
#include "murphyif.h"

#define MAX_CARD_TARGET   4
#define MAX_NAME_LENGTH   256

#define ACTIVE_PORT       NULL

/* Bluetooth service class */
#define BIT(x)    (1U << (x))

#define BT_SERVICE_MASK          0xffe
#define BT_SERVICE_INFORMATION   BIT(23) /**< WEB-server, WAP-server, etc */
#define BT_SERVICE_TELEPHONY     BIT(22) /**< Modem, Headset, etc*/
#define BT_SERVICE_AUDIO         BIT(21) /**< Speaker, Microphone, Headset */
#define BT_SERVICE_OBJECT_XFER   BIT(20) /**< v-Inbox, v-Folder, etc */
#define BT_SERVICE_CAPTURING     BIT(19) /**< Scanner, Microphone, etc */
#define BT_SERVICE_RENDERING     BIT(18) /**< Printing, Speaker, etc */
#define BT_SERVICE_NETWORKING    BIT(17) /**< LAN, Ad hoc, etc */
#define BT_SERVICE_POSITIONING   BIT(16) /**< Location identification */


typedef struct {
    struct userdata *u;
    uint32_t index;
} card_check_t;

typedef struct {
    struct userdata *u;
    pa_muxnode *mux;
    pa_loopnode *loop;
} source_cleanup_t;

typedef struct {
    struct userdata *u;
    uint32_t index;
} stream_uncork_t;

static const char combine_pattern[]   = "Simultaneous output on ";
static const char loopback_outpatrn[] = "Loopback from ";
static const char loopback_inpatrn[]  = "Loopback to ";

static void handle_alsa_card(struct userdata *, pa_card *);
static void handle_bluetooth_card(struct userdata *, pa_card *);
static bool get_bluetooth_port_availability(mir_node *, pa_device_port *);

static void handle_udev_loaded_card(struct userdata *, pa_card *,
                                    mir_node *, char *);
static void handle_card_ports(struct userdata *, mir_node *,
                              pa_card *, pa_card_profile *);

static mir_node *create_node(struct userdata *, mir_node *, bool *);
static void destroy_node(struct userdata *, mir_node *);
static bool update_node_availability(struct userdata *, mir_node *,
                                          bool);
static bool update_node_availability_by_device(struct userdata *,
                                                    mir_direction,
                                                    void *, pa_device_port *,
                                                    bool);

static void parse_profile_name(pa_card_profile *,
                               char **, char **, char *, int);

static char *node_key(struct userdata *, mir_direction,
                      void *, pa_device_port *, char *, size_t);

static pa_sink *make_output_prerouting(struct userdata *, mir_node *,
                                       pa_channel_map *, const char *,
                                       mir_node **);
static pa_source *make_input_prerouting(struct userdata *, mir_node *,
                                        const char *, mir_node **);

static mir_node_type get_stream_routing_class(pa_proplist *);

static void set_bluetooth_profile(struct userdata *, pa_card *, pa_direction_t);


static void schedule_deferred_routing(struct userdata *);
static void schedule_card_check(struct userdata *, pa_card *);
static void schedule_source_cleanup(struct userdata *, mir_node *);
#if 0
static void schedule_stream_uncorking(struct userdata *, pa_sink *);
#endif

static void pa_hashmap_node_free(void *node, void *u)
{
    mir_node_destroy(u, node);
}


struct pa_discover *pa_discover_init(struct userdata *u)
{
    pa_discover *discover = pa_xnew0(pa_discover, 1);

    discover->chmin = 1;
    discover->chmax = 2;
    discover->selected = true;

    discover->nodes.byname = pa_hashmap_new(pa_idxset_string_hash_func,
                                            pa_idxset_string_compare_func);
    discover->nodes.byptr  = pa_hashmap_new(pa_idxset_trivial_hash_func,
                                            pa_idxset_trivial_compare_func);
    return discover;
}

void pa_discover_done(struct userdata *u)
{
    pa_discover *discover;
    void *state;
    mir_node *node;

    if (u && (discover = u->discover)) {
        PA_HASHMAP_FOREACH(node, discover->nodes.byname, state) {
            mir_node_destroy(u, node);
        }
        pa_hashmap_free(discover->nodes.byname);
        pa_hashmap_free(discover->nodes.byptr);
        pa_xfree(discover);
        u->discover = NULL;
    }
}

void pa_discover_domain_up(struct userdata *u)
{
    pa_discover *discover;
    mir_node    *node;
    void        *state;

    pa_assert(u);
    pa_assert_se((discover = u->discover));

    PA_HASHMAP_FOREACH(node, discover->nodes.byname, state) {
        node->amid = AM_ID_INVALID;

        if (node->visible && node->available) {
            pa_audiomgr_register_node(u, node);
            extapi_signal_node_change(u);
        }
    }
}

void pa_discover_domain_down(struct userdata *u)
{
}

void pa_discover_add_card(struct userdata *u, pa_card *card)
{
    const char *bus;

    pa_assert(u);
    pa_assert(card);

    if (!(bus = pa_utils_get_card_bus(card))) {
        pa_log_debug("ignoring card '%s' due to lack of '%s' property",
                     pa_utils_get_card_name(card), PA_PROP_DEVICE_BUS);
        return;
    }

    if (pa_streq(bus, "pci") || pa_streq(bus, "usb")) {
        handle_alsa_card(u, card);
        return;
    }
    else if (pa_streq(bus, "bluetooth")) {
        handle_bluetooth_card(u, card);
        return;
    }

    pa_log_debug("ignoring card '%s' due to unsupported bus type '%s'",
                 pa_utils_get_card_name(card), bus);
}

void pa_discover_remove_card(struct userdata *u, pa_card *card)
{
    const char  *bus;
    pa_discover *discover;
    mir_node    *node;
    void        *state;

    pa_assert(u);
    pa_assert(card);
    pa_assert_se((discover = u->discover));

    if (!(bus = pa_utils_get_card_bus(card)))
        bus = "<unknown>";


    PA_HASHMAP_FOREACH(node, discover->nodes.byname, state) {
        if (node->implement == mir_device &&
            node->pacard.index == card->index)
        {
            if (pa_streq(bus, "pci") || pa_streq(bus, "usb"))
                mir_constrain_destroy(u, node->paname);

            destroy_node(u, node);
        }
    }

    if (pa_streq(bus, "bluetooth"))
        mir_constrain_destroy(u, card->name);
}

void pa_discover_profile_changed(struct userdata *u, pa_card *card)
{
    pa_core         *core;
    pa_card_profile *prof;
    pa_sink         *sink;
    pa_source       *source;
    pa_discover     *discover;
    const char      *bus;
    bool        pci;
    bool        usb;
    bool        bluetooth;
    uint32_t         stamp;
    mir_node        *node;
    void            *state;
    uint32_t         index;
    bool        need_routing;

    pa_assert(u);
    pa_assert(card);
    pa_assert_se((core = u->core));
    pa_assert_se((discover = u->discover));


    if ((bus = pa_utils_get_card_bus(card)) == NULL) {
        pa_log_debug("ignoring profile change on card '%s' due to lack of '%s'"
                     "property", pa_utils_get_card_name(card),
                     PA_PROP_DEVICE_BUS);
        return;
    }

    pci = pa_streq(bus, "pci");
    usb = pa_streq(bus, "usb");
    bluetooth = pa_streq(bus, "bluetooth");

    if (!pci && !usb && !bluetooth) {
        pa_log_debug("ignoring profile change on card '%s' due to unsupported "
                     "bus type '%s'", pa_utils_get_card_name(card), bus);
        u->state.sink = u->state.source = PA_IDXSET_INVALID;
        return;
    }

    if ((index = u->state.sink) != PA_IDXSET_INVALID) {
        if ((sink = pa_idxset_get_by_index(core->sinks, index)))
            pa_discover_add_sink(u, sink, true);
        else
            pa_log_debug("sink.%u is gone", index);
        u->state.sink = PA_IDXSET_INVALID;
    }

    if ((index = u->state.source) != PA_IDXSET_INVALID) {
        if ((source = pa_idxset_get_by_index(core->sources, index)))
            pa_discover_add_source(u, source);
        else
            pa_log_debug("source.%u is gone", index);
        u->state.source = PA_IDXSET_INVALID;
    }

    if (bluetooth) {
        pa_assert_se((prof = card->active_profile));

        pa_log_debug("bluetooth profile changed to '%s' on card '%s'",
                     prof->name, card->name);

        if (!prof->n_sinks && !prof->n_sources) {
            /* switched off but not unloaded yet */
            need_routing = false;

            PA_HASHMAP_FOREACH(node, discover->nodes.byname, state) {
                if (node->implement == mir_device &&
                    node->pacard.index == card->index)
                {
                    if (node->type != mir_bluetooth_a2dp &&
                        node->type != mir_bluetooth_sco)
                    {
                        if (node->available) {
                            node->available = false;
                            need_routing = true;
                        }
                    }
                }
            }

            if (need_routing)
                schedule_deferred_routing(u);
        }
    }
    else {
        pa_log_debug("alsa profile changed to '%s' on card '%s'",
                     card->active_profile->name, card->name);

        stamp = pa_utils_get_stamp();

        handle_alsa_card(u, card);

        PA_HASHMAP_FOREACH(node, discover->nodes.byname, state) {
            if (node->implement == mir_device &&
                node->pacard.index == card->index &&
                node->stamp < stamp)
            {
                destroy_node(u, node);
            }
        }
    }

}

void pa_discover_port_available_changed(struct userdata *u,
                                        pa_device_port  *port)
{
    pa_core       *core;
    pa_sink       *sink;
    pa_source     *source;
    mir_node      *node;
    uint32_t       idx;
    bool      available;
    const char    *state;
    bool      btport;
    bool      route;
    pa_direction_t direction;
    void          *iter;

    pa_assert(u);
    pa_assert(port);
    pa_assert_se((core = u->core));

    switch (port->available) {
    case PA_PORT_AVAILABLE_NO:    state = "not available";  break;
    case PA_PORT_AVAILABLE_YES:   state = "available";      break;
    default:                      state = "unknown";        break;
    }

    pa_log_debug("port '%s' availabilty changed to %s. Updating",
                 port->name, state);

    btport = false;
    route = false;
    direction = 0;
    iter = NULL;

    while ((node = pa_utils_get_node_from_port(u, port, &iter))) {
        btport = true;
        available = get_bluetooth_port_availability(node, port);
        route |= update_node_availability(u, node, available);
        direction |= (node->direction == mir_input) ? PA_DIRECTION_INPUT : PA_DIRECTION_OUTPUT;
    }

    if (btport)
        set_bluetooth_profile(u, port->card, direction);
    else {
        switch (port->available) {
        case PA_PORT_AVAILABLE_NO:    available = false;    break;
        case PA_PORT_AVAILABLE_YES:   available = true;     break;
        default:                      /* do nothing */      return;
        }

        if (port->direction == PA_DIRECTION_OUTPUT) {
            PA_IDXSET_FOREACH(sink, core->sinks, idx) {
                if (sink->ports) {
                    if (port == pa_hashmap_get(sink->ports, port->name)) {
                        pa_log_debug("   sink '%s'", sink->name);
                        route |= update_node_availability_by_device(
                                                     u, mir_output,
                                                     sink, port,
                                                     available);
                    }
                }
            }
        }

        if (port->direction == PA_DIRECTION_INPUT) {
            PA_IDXSET_FOREACH(source, core->sources, idx) {
                if (source->ports) {
                    if (port == pa_hashmap_get(source->ports, port->name)) {
                        pa_log_debug("   source '%s'", source->name);
                        route |= update_node_availability_by_device(
                                                      u, mir_input,
                                                      source, port,
                                                      available);
                    }
                }
            }
        }
    }

    if (route)
        mir_router_make_routing(u);
}

void pa_discover_add_sink(struct userdata *u, pa_sink *sink, bool route)
{
    static pa_nodeset_resdef def_resdef = {0, {0, 0}};

    pa_core           *core;
    pa_discover       *discover;
    pa_module         *module;
    mir_node          *node;
    pa_card           *card;
    char              *key;
    char               kbf[256];
    char               nbf[2048];
    const char        *loopback_role;
    pa_nodeset_map    *map;
    pa_nodeset_resdef *resdef;
    bool          make_rset;
    pa_nodeset_resdef  rdbuf;
    pa_source         *ns;
    mir_node           data;
    mir_node_type      type;

    pa_assert(u);
    pa_assert(sink);
    pa_assert_se((core = u->core));
    pa_assert_se((discover = u->discover));

    module = sink->module;

    if ((card = sink->card)) {
        if (!(key = node_key(u, mir_output,sink,ACTIVE_PORT, kbf,sizeof(kbf))))
            return;
        if (!(node = pa_discover_find_node_by_key(u, key))) {
            if (u->state.profile)
                pa_log_debug("can't find node for sink (key '%s')", key);
            else
                u->state.sink = sink->index;
            return;
        }
        pa_log_debug("node for '%s' found (key %s). Updating with sink data",
                     node->paname, node->key);
        node->paidx = sink->index;
        node->available = true;
        pa_discover_add_node_to_ptr_hash(u, sink, node);

        if ((loopback_role = pa_classify_loopback_stream(node))) {
            if (!(ns = pa_utils_get_null_source(u))) {
                pa_log("Can't load loopback module: no initial null source");
                return;
            }

            map = pa_nodeset_get_map_by_role(u, loopback_role);
            make_rset = (map && map->resdef);
            resdef = make_rset ? map->resdef : &def_resdef;

            node->loop = pa_loopback_create(u->loopback, core,
                                            PA_LOOPBACK_SINK, node->index,
                                            ns->index, sink->index,
                                            loopback_role,
                                            resdef->priority,
                                            resdef->flags.rset,
                                            resdef->flags.audio);

            mir_node_print(node, nbf, sizeof(nbf));
            pa_log_debug("updated node:\n%s", nbf);

            if (make_rset)
                pa_murphyif_create_resource_set(u, node, resdef);
        }

        if (route) {
            type = node->type;

            if (type != mir_bluetooth_a2dp && type != mir_bluetooth_sco)
                mir_router_make_routing(u);
            else {
                if (!u->state.profile)
                    schedule_deferred_routing(u);
            }
        }
    }
    else if (!module || !pa_streq(module->name, "module-combine-sink")) {
        memset(&data, 0, sizeof(data));
        data.key = pa_xstrdup(sink->name);
        data.direction = mir_output;
        data.implement = mir_device;
        data.channels  = sink->channel_map.channels;
        data.available = true;
        data.paidx     = sink->index;

        if (sink == pa_utils_get_null_sink(u)) {
            data.visible = false;
            data.type = mir_null;
            data.amname = pa_xstrdup("Silent");
            data.amid = AM_ID_INVALID;
            data.paname = pa_xstrdup(sink->name);
        }
        else if (pa_classify_node_by_property(&data, sink->proplist)) {
            data.privacy = mir_public;
            data.visible = true;
            data.amname = pa_xstrdup(mir_node_type_str(data.type));
            data.amid = AM_ID_INVALID;
            data.paname = pa_xstrdup(sink->name);
        }
        else {
            pa_xfree(data.key); /* for now */
            pa_log_info("currently we do not support statically loaded "
                        "sinks without " PA_PROP_NODE_TYPE " property");
            return;
        }

        create_node(u, &data, NULL);
    }
}


void pa_discover_remove_sink(struct userdata *u, pa_sink *sink)
{
    pa_discover    *discover;
    mir_node       *node;
    char           *name;
    mir_node_type   type;

    pa_assert(u);
    pa_assert(sink);
    pa_assert_se((discover = u->discover));

    name = pa_utils_get_sink_name(sink);

    if (!(node = pa_hashmap_get(discover->nodes.byptr, sink)))
        pa_log_debug("can't find node for sink (name '%s')", name);
    else {
        pa_log_debug("node found for '%s'. Reseting sink data", name);
        pa_murphyif_destroy_resource_set(u, node);
        schedule_source_cleanup(u, node);
        node->paidx = PA_IDXSET_INVALID;
        pa_hashmap_remove(discover->nodes.byptr, sink);

        type = node->type;

        if (sink->card) {
            if (type != mir_bluetooth_a2dp && type != mir_bluetooth_sco)
                node->available = false;
            else {
                if (!u->state.profile)
                    schedule_deferred_routing(u);
            }
        }
        else {
            pa_log_info("currently we do not support statically loaded sinks");
        }
    }
}


void pa_discover_add_source(struct userdata *u, pa_source *source)
{
    static pa_nodeset_resdef def_resdef = {0, {0, 0}};

    pa_core           *core;
    pa_discover       *discover;
    mir_node          *node;
    pa_card           *card;
    char              *key;
    char               kbf[256];
    char               nbf[2048];
    const char        *loopback_role;
    pa_nodeset_map    *map;
    pa_nodeset_resdef *resdef;
    bool          make_rset;
    uint32_t           sink_index;
    pa_sink           *ns;
    mir_node           data;

    pa_assert(u);
    pa_assert(source);
    pa_assert_se((core = u->core));
    pa_assert_se((discover = u->discover));

    if ((card = source->card)) {
        if (!(key = node_key(u,mir_input,source,ACTIVE_PORT,kbf,sizeof(kbf))))
            return;
        if (!(node = pa_discover_find_node_by_key(u, key))) {
            if (u->state.profile)
                pa_log_debug("can't find node for source (key '%s')", key);
            else
                u->state.source = source->index;
            return;
        }
        pa_log_debug("node for '%s' found. Updating with source data",
                     node->amname);
        node->paidx = source->index;
        node->available = true;
        pa_discover_add_node_to_ptr_hash(u, source, node);
        if ((loopback_role = pa_classify_loopback_stream(node))) {
            if (!(ns = pa_utils_get_null_sink(u))) {
                pa_log("Can't load loopback module: no initial null sink");
                return;
            }

            map = pa_nodeset_get_map_by_role(u, loopback_role);
            make_rset = (map && map->resdef);
            resdef = make_rset ? map->resdef : &def_resdef;

            node->loop = pa_loopback_create(u->loopback, core,
                                            PA_LOOPBACK_SOURCE, node->index,
                                            source->index, ns->index,
                                            loopback_role,
                                            resdef->priority,
                                            resdef->flags.rset,
                                            resdef->flags.audio);
            if (node->loop) {
                sink_index = pa_loopback_get_sink_index(core, node->loop);
                node->mux = pa_multiplex_find_by_sink(u->multiplex,sink_index);
            }

            mir_node_print(node, nbf, sizeof(nbf));
            pa_log_debug("updated node:\n%s", nbf);

            if (make_rset)
                pa_murphyif_create_resource_set(u, node, resdef);

            pa_fader_apply_volume_limits(u, node->stamp);
        }
    }
    else {
        memset(&data, 0, sizeof(data));
        data.key = pa_xstrdup(source->name);
        data.direction = mir_input;
        data.implement = mir_device;
        data.channels  = source->channel_map.channels;
        data.available = true;

        if (source == pa_utils_get_null_source(u)) {
            data.visible = false;
            data.type = mir_null;
            data.amname = pa_xstrdup("Silent");
            data.amid = AM_ID_INVALID;
            data.paname = pa_xstrdup(source->name);
            data.paidx = source->index;
        }
        else if (pa_classify_node_by_property(&data, source->proplist)) {
            data.visible = true;
            data.amname = pa_xstrdup(mir_node_type_str(data.type));
            data.amid   = AM_ID_INVALID;
            data.paname = pa_xstrdup(source->name);
        }
        else {
            pa_xfree(data.key); /* for now */
            pa_log_info("currently we do not support statically loaded "
                        "sources without " PA_PROP_NODE_TYPE " property");
            return;
        }

        create_node(u, &data, NULL);
    }
}


void pa_discover_remove_source(struct userdata *u, pa_source *source)
{
    pa_discover    *discover;
    mir_node       *node;
    char           *name;
    mir_node_type   type;

    pa_assert(u);
    pa_assert(source);
    pa_assert_se((discover = u->discover));

    name = pa_utils_get_source_name(source);

    if (!(node = pa_hashmap_get(discover->nodes.byptr, source)))
        pa_log_debug("can't find node for source (name '%s')", name);
    else {
        pa_log_debug("node found. Reseting source data");
        pa_murphyif_destroy_resource_set(u, node);
        schedule_source_cleanup(u, node);
        node->paidx = PA_IDXSET_INVALID;
        pa_hashmap_remove(discover->nodes.byptr, source);

        type = node->type;

        if (source->card) {
            if (type != mir_bluetooth_sco)
                node->available = false;
            else {
                if (!u->state.profile)
                    schedule_deferred_routing(u);
            }
        }
        else {
            pa_log_info("currently we do not support statically "
                        "loaded sources");
        }
    }
}


void pa_discover_register_sink_input(struct userdata *u, pa_sink_input *sinp)
{
    pa_core           *core;
    pa_discover       *discover;
    pa_proplist       *pl;
    char              *name;
    const char        *media;
    mir_node_type      type;
    mir_node           data;
    mir_node          *node;
    mir_node          *target;
    char               key[256];
    pa_sink           *sink;
    const char        *role;
    pa_nodeset_resdef *resdef;

    pa_assert(u);
    pa_assert(sinp);
    pa_assert_se((core = u->core));
    pa_assert_se((discover = u->discover));
    pa_assert_se((pl = sinp->proplist));

    if ((media = pa_proplist_gets(sinp->proplist, PA_PROP_MEDIA_NAME))) {
        if (!strncmp(media, combine_pattern, sizeof(combine_pattern)-1)) {
            pa_log_debug("Seems to be a combine stream. Nothing to do ...");
            return;
        }
        if (!strncmp(media, loopback_outpatrn, sizeof(loopback_outpatrn)-1)) {
            pa_log_debug("Seems to be a loopback stream. Nothing to do ...");
            return;
        }
    }

    name = pa_utils_get_sink_input_name(sinp);

    pa_log_debug("registering input stream '%s'", name);

    if (!(type = pa_classify_guess_stream_node_type(u, pl, &resdef))) {
        pa_log_debug("cant find stream class for '%s'. "
                     "Leaving it alone", name);
        return;
    }

    pa_utils_set_stream_routing_properties(pl, type, NULL);

    snprintf(key, sizeof(key), "stream_input.%d", sinp->index);

    memset(&data, 0, sizeof(data));
    data.key       = key;
    data.direction = mir_input;
    data.implement = mir_stream;
    data.channels  = sinp->channel_map.channels;
    data.type      = type;
    data.zone      = pa_utils_get_zone(sinp->proplist);
    data.visible   = true;
    data.available = true;
    data.amname    = name;
    data.amdescr   = (char *)pa_proplist_gets(pl, PA_PROP_MEDIA_NAME);
    data.amid      = AM_ID_INVALID;
    data.paname    = name;
    data.paidx     = sinp->index;
    data.rsetid    = (char *)pa_proplist_gets(pl, PA_PROP_RESOURCE_SET_ID);

    /*
     * here we can't guess whether the application requested an explicit
     * route by sepcifying the target sink @ stream creation time.
     *
     * the brute force solution: we make a default route for this stream
     * possibly overwiriting the orginal app request :(
     */
    /* this will set data.mux */
    role = pa_proplist_gets(sinp->proplist, PA_PROP_MEDIA_ROLE);
    sink = make_output_prerouting(u, &data, &sinp->channel_map, role, &target);

    node = create_node(u, &data, NULL);
    pa_assert(node);
    pa_discover_add_node_to_ptr_hash(u, sinp, node);

    if (sink && target) {
        pa_log_debug("move stream to sink %u (%s)", sink->index, sink->name);

        if (pa_sink_input_move_to(sinp, sink, false) < 0)
            pa_log("failed to route '%s' => '%s'",node->amname,target->amname);
        else
            pa_audiomgr_add_default_route(u, node, target);
    }
}

bool pa_discover_preroute_sink_input(struct userdata *u,
                                     pa_sink_input_new_data *data)
{
    pa_core           *core;
    pa_module         *m;
    pa_proplist       *pl;
    pa_discover       *discover;
    pa_multiplex      *multiplex;
    mir_node           fake;
    pa_sink           *sink;
    pa_sink_input     *sinp;
    const char        *mnam;
    const char        *role;
    mir_node_type      type;
    mir_node          *node;
    pa_muxnode        *mux;
    pa_nodeset_resdef *resdef;
    bool               loopback;

    pa_assert(u);
    pa_assert(data);
    pa_assert_se((core = u->core));
    pa_assert_se((discover = u->discover));
    pa_assert_se((multiplex = u->multiplex));
    pa_assert_se((pl = data->proplist));

    mnam = (m = data->module) ? m->name : "";

    if (pa_streq(mnam, "module-combine-sink")) {
        loopback = false;
        type = mir_node_type_unknown;

        if (!(mux  = pa_multiplex_find_by_module(multiplex, m)) ||
            !(sink = pa_idxset_get_by_index(core->sinks, mux->sink_index)) ||
            !(sinp = pa_idxset_first(sink->inputs, NULL)) ||
            !(type = pa_utils_get_stream_class(sinp->proplist)))
        {
            pa_log_debug("can't figure out the type of multiplex stream");
        }
        else {
            pa_utils_set_stream_routing_properties(data->proplist, type, NULL);
        }
    }
    else {
        loopback = pa_streq(mnam, "module-loopback");

        if (loopback) {
            if (!(node = pa_utils_get_node_from_data(u, mir_input, data))) {
                pa_log_debug("can't find loopback node for sink-input");
                return true;
            }

            if (node->direction == mir_output) {
                pa_log_debug("refuse to preroute loopback sink-input "
                             "(current route: sink %u @ %p)", data->sink ?
                             data->sink->index : PA_IDXSET_INVALID,data->sink);
                return true;
            }

            data->sink = NULL;

            type = pa_classify_guess_stream_node_type(u, pl, NULL);
        }
        else {
            type = pa_classify_guess_stream_node_type(u, pl, &resdef);

            pa_utils_set_resource_properties(pl, resdef);

            if (pa_stream_state_start_corked(u, data, resdef)) {
                pa_log_debug("start corked");
            }
        }

        pa_utils_set_stream_routing_properties(pl, type, data->sink);
    }

    if (!data->sink) {
        memset(&fake, 0, sizeof(fake));
        fake.direction = mir_input;
        fake.implement = mir_stream;
        fake.channels  = data->channel_map.channels;
        fake.type      = type;
        fake.zone      = pa_utils_get_zone(data->proplist);
        fake.visible   = true;
        fake.available = true;
        fake.amname    = "<preroute sink-input>";
        fake.amid      = AM_ID_INVALID;
        fake.paidx     = PA_IDXSET_INVALID;

        role = pa_proplist_gets(data->proplist, PA_PROP_MEDIA_ROLE);
        sink = make_output_prerouting(u, &fake, &data->channel_map, role,NULL);

        if (sink) {
#if 0
            if (fake.mux && !(data->flags & PA_SINK_INPUT_START_CORKED)) {
                data->flags |= PA_SINK_INPUT_START_CORKED;
                schedule_stream_uncorking(u, sink);
            }
#endif

            if (pa_sink_input_new_data_set_sink(data, sink, false))
                pa_log_debug("set sink %u for new sink-input", sink->index);
            else {
                pa_log("can't set sink %u for new sink-input", sink->index);
                                                      /* copes wit NULL mux */
                pa_multiplex_destroy(u->multiplex, core, fake.mux);
                return false;
            }
        }
    }

    if (loopback && data->sink && data->sink->module) {
        /* no ramp needed */
        if (pa_streq(data->sink->module->name, "module-combine-sink"))
            return true;
    }

    pa_log_debug("set sink-input ramp-muted");
    data->flags |= PA_SINK_INPUT_START_RAMP_MUTED;

    return true;
}
<commit_msg>add remap node to hash map<commit_after>/*
 * module-murphy-ivi -- PulseAudio module for providing audio routing support
 * Copyright (c) 2012, Intel Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU Lesser General Public License,
 * version 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St - Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 */
#include <stdio.h>
#include <assert.h>

#include <pulsecore/pulsecore-config.h>

#include <pulsecore/hashmap.h>
#include <pulsecore/idxset.h>
#include <pulsecore/client.h>
#include <pulsecore/core-util.h>
#include <pulsecore/log.h>
#include <pulsecore/card.h>
#include <pulsecore/device-port.h>
#include <pulsecore/sink-input.h>
#include <pulsecore/source-output.h>
#include <pulsecore/strbuf.h>


#include "discover.h"
#include "node.h"
#include "audiomgr.h"
#include "router.h"
#include "constrain.h"
#include "multiplex.h"
#include "loopback.h"
#include "fader.h"
#include "classify.h"
#include "utils.h"
#include "extapi.h"
#include "stream-state.h"
#include "murphyif.h"

#define MAX_CARD_TARGET   4
#define MAX_NAME_LENGTH   256

#define ACTIVE_PORT       NULL

/* Bluetooth service class */
#define BIT(x)    (1U << (x))

#define BT_SERVICE_MASK          0xffe
#define BT_SERVICE_INFORMATION   BIT(23) /**< WEB-server, WAP-server, etc */
#define BT_SERVICE_TELEPHONY     BIT(22) /**< Modem, Headset, etc*/
#define BT_SERVICE_AUDIO         BIT(21) /**< Speaker, Microphone, Headset */
#define BT_SERVICE_OBJECT_XFER   BIT(20) /**< v-Inbox, v-Folder, etc */
#define BT_SERVICE_CAPTURING     BIT(19) /**< Scanner, Microphone, etc */
#define BT_SERVICE_RENDERING     BIT(18) /**< Printing, Speaker, etc */
#define BT_SERVICE_NETWORKING    BIT(17) /**< LAN, Ad hoc, etc */
#define BT_SERVICE_POSITIONING   BIT(16) /**< Location identification */


typedef struct {
    struct userdata *u;
    uint32_t index;
} card_check_t;

typedef struct {
    struct userdata *u;
    pa_muxnode *mux;
    pa_loopnode *loop;
} source_cleanup_t;

typedef struct {
    struct userdata *u;
    uint32_t index;
} stream_uncork_t;

static const char combine_pattern[]   = "Simultaneous output on ";
static const char loopback_outpatrn[] = "Loopback from ";
static const char loopback_inpatrn[]  = "Loopback to ";

static void handle_alsa_card(struct userdata *, pa_card *);
static void handle_bluetooth_card(struct userdata *, pa_card *);
static bool get_bluetooth_port_availability(mir_node *, pa_device_port *);

static void handle_udev_loaded_card(struct userdata *, pa_card *,
                                    mir_node *, char *);
static void handle_card_ports(struct userdata *, mir_node *,
                              pa_card *, pa_card_profile *);

static mir_node *create_node(struct userdata *, mir_node *, bool *);
static void destroy_node(struct userdata *, mir_node *);
static bool update_node_availability(struct userdata *, mir_node *,
                                          bool);
static bool update_node_availability_by_device(struct userdata *,
                                                    mir_direction,
                                                    void *, pa_device_port *,
                                                    bool);

static void parse_profile_name(pa_card_profile *,
                               char **, char **, char *, int);

static char *node_key(struct userdata *, mir_direction,
                      void *, pa_device_port *, char *, size_t);

static pa_sink *make_output_prerouting(struct userdata *, mir_node *,
                                       pa_channel_map *, const char *,
                                       mir_node **);
static pa_source *make_input_prerouting(struct userdata *, mir_node *,
                                        const char *, mir_node **);

static mir_node_type get_stream_routing_class(pa_proplist *);

static void set_bluetooth_profile(struct userdata *, pa_card *, pa_direction_t);


static void schedule_deferred_routing(struct userdata *);
static void schedule_card_check(struct userdata *, pa_card *);
static void schedule_source_cleanup(struct userdata *, mir_node *);
#if 0
static void schedule_stream_uncorking(struct userdata *, pa_sink *);
#endif

static void pa_hashmap_node_free(void *node, void *u)
{
    mir_node_destroy(u, node);
}


struct pa_discover *pa_discover_init(struct userdata *u)
{
    pa_discover *discover = pa_xnew0(pa_discover, 1);

    discover->chmin = 1;
    discover->chmax = 2;
    discover->selected = true;

    discover->nodes.byname = pa_hashmap_new(pa_idxset_string_hash_func,
                                            pa_idxset_string_compare_func);
    discover->nodes.byptr  = pa_hashmap_new(pa_idxset_trivial_hash_func,
                                            pa_idxset_trivial_compare_func);
    return discover;
}

void pa_discover_done(struct userdata *u)
{
    pa_discover *discover;
    void *state;
    mir_node *node;

    if (u && (discover = u->discover)) {
        PA_HASHMAP_FOREACH(node, discover->nodes.byname, state) {
            mir_node_destroy(u, node);
        }
        pa_hashmap_free(discover->nodes.byname);
        pa_hashmap_free(discover->nodes.byptr);
        pa_xfree(discover);
        u->discover = NULL;
    }
}

void pa_discover_domain_up(struct userdata *u)
{
    pa_discover *discover;
    mir_node    *node;
    void        *state;

    pa_assert(u);
    pa_assert_se((discover = u->discover));

    PA_HASHMAP_FOREACH(node, discover->nodes.byname, state) {
        node->amid = AM_ID_INVALID;

        if (node->visible && node->available) {
            pa_audiomgr_register_node(u, node);
            extapi_signal_node_change(u);
        }
    }
}

void pa_discover_domain_down(struct userdata *u)
{
}

void pa_discover_add_card(struct userdata *u, pa_card *card)
{
    const char *bus;

    pa_assert(u);
    pa_assert(card);

    if (!(bus = pa_utils_get_card_bus(card))) {
        pa_log_debug("ignoring card '%s' due to lack of '%s' property",
                     pa_utils_get_card_name(card), PA_PROP_DEVICE_BUS);
        return;
    }

    if (pa_streq(bus, "pci") || pa_streq(bus, "usb")) {
        handle_alsa_card(u, card);
        return;
    }
    else if (pa_streq(bus, "bluetooth")) {
        handle_bluetooth_card(u, card);
        return;
    }

    pa_log_debug("ignoring card '%s' due to unsupported bus type '%s'",
                 pa_utils_get_card_name(card), bus);
}

void pa_discover_remove_card(struct userdata *u, pa_card *card)
{
    const char  *bus;
    pa_discover *discover;
    mir_node    *node;
    void        *state;

    pa_assert(u);
    pa_assert(card);
    pa_assert_se((discover = u->discover));

    if (!(bus = pa_utils_get_card_bus(card)))
        bus = "<unknown>";


    PA_HASHMAP_FOREACH(node, discover->nodes.byname, state) {
        if (node->implement == mir_device &&
            node->pacard.index == card->index)
        {
            if (pa_streq(bus, "pci") || pa_streq(bus, "usb"))
                mir_constrain_destroy(u, node->paname);

            destroy_node(u, node);
        }
    }

    if (pa_streq(bus, "bluetooth"))
        mir_constrain_destroy(u, card->name);
}

void pa_discover_profile_changed(struct userdata *u, pa_card *card)
{
    pa_core         *core;
    pa_card_profile *prof;
    pa_sink         *sink;
    pa_source       *source;
    pa_discover     *discover;
    const char      *bus;
    bool        pci;
    bool        usb;
    bool        bluetooth;
    uint32_t         stamp;
    mir_node        *node;
    void            *state;
    uint32_t         index;
    bool        need_routing;

    pa_assert(u);
    pa_assert(card);
    pa_assert_se((core = u->core));
    pa_assert_se((discover = u->discover));


    if ((bus = pa_utils_get_card_bus(card)) == NULL) {
        pa_log_debug("ignoring profile change on card '%s' due to lack of '%s'"
                     "property", pa_utils_get_card_name(card),
                     PA_PROP_DEVICE_BUS);
        return;
    }

    pci = pa_streq(bus, "pci");
    usb = pa_streq(bus, "usb");
    bluetooth = pa_streq(bus, "bluetooth");

    if (!pci && !usb && !bluetooth) {
        pa_log_debug("ignoring profile change on card '%s' due to unsupported "
                     "bus type '%s'", pa_utils_get_card_name(card), bus);
        u->state.sink = u->state.source = PA_IDXSET_INVALID;
        return;
    }

    if ((index = u->state.sink) != PA_IDXSET_INVALID) {
        if ((sink = pa_idxset_get_by_index(core->sinks, index)))
            pa_discover_add_sink(u, sink, true);
        else
            pa_log_debug("sink.%u is gone", index);
        u->state.sink = PA_IDXSET_INVALID;
    }

    if ((index = u->state.source) != PA_IDXSET_INVALID) {
        if ((source = pa_idxset_get_by_index(core->sources, index)))
            pa_discover_add_source(u, source);
        else
            pa_log_debug("source.%u is gone", index);
        u->state.source = PA_IDXSET_INVALID;
    }

    if (bluetooth) {
        pa_assert_se((prof = card->active_profile));

        pa_log_debug("bluetooth profile changed to '%s' on card '%s'",
                     prof->name, card->name);

        if (!prof->n_sinks && !prof->n_sources) {
            /* switched off but not unloaded yet */
            need_routing = false;

            PA_HASHMAP_FOREACH(node, discover->nodes.byname, state) {
                if (node->implement == mir_device &&
                    node->pacard.index == card->index)
                {
                    if (node->type != mir_bluetooth_a2dp &&
                        node->type != mir_bluetooth_sco)
                    {
                        if (node->available) {
                            node->available = false;
                            need_routing = true;
                        }
                    }
                }
            }

            if (need_routing)
                schedule_deferred_routing(u);
        }
    }
    else {
        pa_log_debug("alsa profile changed to '%s' on card '%s'",
                     card->active_profile->name, card->name);

        stamp = pa_utils_get_stamp();

        handle_alsa_card(u, card);

        PA_HASHMAP_FOREACH(node, discover->nodes.byname, state) {
            if (node->implement == mir_device &&
                node->pacard.index == card->index &&
                node->stamp < stamp)
            {
                destroy_node(u, node);
            }
        }
    }

}

void pa_discover_port_available_changed(struct userdata *u,
                                        pa_device_port  *port)
{
    pa_core       *core;
    pa_sink       *sink;
    pa_source     *source;
    mir_node      *node;
    uint32_t       idx;
    bool      available;
    const char    *state;
    bool      btport;
    bool      route;
    pa_direction_t direction;
    void          *iter;

    pa_assert(u);
    pa_assert(port);
    pa_assert_se((core = u->core));

    switch (port->available) {
    case PA_PORT_AVAILABLE_NO:    state = "not available";  break;
    case PA_PORT_AVAILABLE_YES:   state = "available";      break;
    default:                      state = "unknown";        break;
    }

    pa_log_debug("port '%s' availabilty changed to %s. Updating",
                 port->name, state);

    btport = false;
    route = false;
    direction = 0;
    iter = NULL;

    while ((node = pa_utils_get_node_from_port(u, port, &iter))) {
        btport = true;
        available = get_bluetooth_port_availability(node, port);
        route |= update_node_availability(u, node, available);
        direction |= (node->direction == mir_input) ? PA_DIRECTION_INPUT : PA_DIRECTION_OUTPUT;
    }

    if (btport)
        set_bluetooth_profile(u, port->card, direction);
    else {
        switch (port->available) {
        case PA_PORT_AVAILABLE_NO:    available = false;    break;
        case PA_PORT_AVAILABLE_YES:   available = true;     break;
        default:                      /* do nothing */      return;
        }

        if (port->direction == PA_DIRECTION_OUTPUT) {
            PA_IDXSET_FOREACH(sink, core->sinks, idx) {
                if (sink->ports) {
                    if (port == pa_hashmap_get(sink->ports, port->name)) {
                        pa_log_debug("   sink '%s'", sink->name);
                        route |= update_node_availability_by_device(
                                                     u, mir_output,
                                                     sink, port,
                                                     available);
                    }
                }
            }
        }

        if (port->direction == PA_DIRECTION_INPUT) {
            PA_IDXSET_FOREACH(source, core->sources, idx) {
                if (source->ports) {
                    if (port == pa_hashmap_get(source->ports, port->name)) {
                        pa_log_debug("   source '%s'", source->name);
                        route |= update_node_availability_by_device(
                                                      u, mir_input,
                                                      source, port,
                                                      available);
                    }
                }
            }
        }
    }

    if (route)
        mir_router_make_routing(u);
}

void pa_discover_add_sink(struct userdata *u, pa_sink *sink, bool route)
{
    static pa_nodeset_resdef def_resdef = {0, {0, 0}};

    pa_core           *core;
    pa_discover       *discover;
    pa_module         *module;
    mir_node          *node;
    pa_card           *card;
    char              *key;
    char               kbf[256];
    char               nbf[2048];
    const char        *loopback_role;
    pa_nodeset_map    *map;
    pa_nodeset_resdef *resdef;
    bool          make_rset;
    pa_nodeset_resdef  rdbuf;
    pa_source         *ns;
    mir_node           data;
    mir_node_type      type;
    bool               add_to_hash;

    pa_assert(u);
    pa_assert(sink);
    pa_assert_se((core = u->core));
    pa_assert_se((discover = u->discover));

    module = sink->module;

    if ((card = sink->card)) {
        if (!(key = node_key(u, mir_output,sink,ACTIVE_PORT, kbf,sizeof(kbf))))
            return;
        if (!(node = pa_discover_find_node_by_key(u, key))) {
            if (u->state.profile)
                pa_log_debug("can't find node for sink (key '%s')", key);
            else
                u->state.sink = sink->index;
            return;
        }
        pa_log_debug("node for '%s' found (key %s). Updating with sink data",
                     node->paname, node->key);
        node->paidx = sink->index;
        node->available = true;
        pa_discover_add_node_to_ptr_hash(u, sink, node);

        if ((loopback_role = pa_classify_loopback_stream(node))) {
            if (!(ns = pa_utils_get_null_source(u))) {
                pa_log("Can't load loopback module: no initial null source");
                return;
            }

            map = pa_nodeset_get_map_by_role(u, loopback_role);
            make_rset = (map && map->resdef);
            resdef = make_rset ? map->resdef : &def_resdef;

            node->loop = pa_loopback_create(u->loopback, core,
                                            PA_LOOPBACK_SINK, node->index,
                                            ns->index, sink->index,
                                            loopback_role,
                                            resdef->priority,
                                            resdef->flags.rset,
                                            resdef->flags.audio);

            mir_node_print(node, nbf, sizeof(nbf));
            pa_log_debug("updated node:\n%s", nbf);

            if (make_rset)
                pa_murphyif_create_resource_set(u, node, resdef);
        }

        if (route) {
            type = node->type;

            if (type != mir_bluetooth_a2dp && type != mir_bluetooth_sco)
                mir_router_make_routing(u);
            else {
                if (!u->state.profile)
                    schedule_deferred_routing(u);
            }
        }
    }
    else if (!module || !pa_streq(module->name, "module-combine-sink")) {
        add_to_hash = false;

        memset(&data, 0, sizeof(data));
        data.key = pa_xstrdup(sink->name);
        data.direction = mir_output;
        data.implement = mir_device;
        data.channels  = sink->channel_map.channels;
        data.available = true;
        data.paidx     = sink->index;

        if (sink == pa_utils_get_null_sink(u)) {
            data.visible = false;
            data.type = mir_null;
            data.amname = pa_xstrdup("Silent");
            data.amid = AM_ID_INVALID;
            data.paname = pa_xstrdup(sink->name);
        }
        else if (pa_classify_node_by_property(&data, sink->proplist)) {
            data.privacy = mir_public;
            data.visible = true;
            data.amname = pa_xstrdup(mir_node_type_str(data.type));
            data.amid = AM_ID_INVALID;
            data.paname = pa_xstrdup(sink->name);

            add_to_hash = true;
        }
        else {
            pa_xfree(data.key); /* for now */
            pa_log_info("currently we do not support statically loaded "
                        "sinks without " PA_PROP_NODE_TYPE " property");
            return;
        }

        node = create_node(u, &data, NULL);

        if (add_to_hash)
            pa_discover_add_node_to_ptr_hash(u, sink, node);
    }
}


void pa_discover_remove_sink(struct userdata *u, pa_sink *sink)
{
    pa_discover    *discover;
    mir_node       *node;
    char           *name;
    mir_node_type   type;

    pa_assert(u);
    pa_assert(sink);
    pa_assert_se((discover = u->discover));

    name = pa_utils_get_sink_name(sink);

    if (!(node = pa_hashmap_get(discover->nodes.byptr, sink)))
        pa_log_debug("can't find node for sink (name '%s')", name);
    else {
        pa_log_debug("node found for '%s'. Reseting sink data", name);
        pa_murphyif_destroy_resource_set(u, node);
        schedule_source_cleanup(u, node);
        node->paidx = PA_IDXSET_INVALID;
        pa_hashmap_remove(discover->nodes.byptr, sink);

        type = node->type;

        if (sink->card) {
            if (type != mir_bluetooth_a2dp && type != mir_bluetooth_sco)
                node->available = false;
            else {
                if (!u->state.profile)
                    schedule_deferred_routing(u);
            }
        }
        else {
            pa_log_info("currently we do not support statically loaded sinks");
        }
    }
}


void pa_discover_add_source(struct userdata *u, pa_source *source)
{
    static pa_nodeset_resdef def_resdef = {0, {0, 0}};

    pa_core           *core;
    pa_discover       *discover;
    mir_node          *node;
    pa_card           *card;
    char              *key;
    char               kbf[256];
    char               nbf[2048];
    const char        *loopback_role;
    pa_nodeset_map    *map;
    pa_nodeset_resdef *resdef;
    bool          make_rset;
    uint32_t           sink_index;
    pa_sink           *ns;
    mir_node           data;

    pa_assert(u);
    pa_assert(source);
    pa_assert_se((core = u->core));
    pa_assert_se((discover = u->discover));

    if ((card = source->card)) {
        if (!(key = node_key(u,mir_input,source,ACTIVE_PORT,kbf,sizeof(kbf))))
            return;
        if (!(node = pa_discover_find_node_by_key(u, key))) {
            if (u->state.profile)
                pa_log_debug("can't find node for source (key '%s')", key);
            else
                u->state.source = source->index;
            return;
        }
        pa_log_debug("node for '%s' found. Updating with source data",
                     node->amname);
        node->paidx = source->index;
        node->available = true;
        pa_discover_add_node_to_ptr_hash(u, source, node);
        if ((loopback_role = pa_classify_loopback_stream(node))) {
            if (!(ns = pa_utils_get_null_sink(u))) {
                pa_log("Can't load loopback module: no initial null sink");
                return;
            }

            map = pa_nodeset_get_map_by_role(u, loopback_role);
            make_rset = (map && map->resdef);
            resdef = make_rset ? map->resdef : &def_resdef;

            node->loop = pa_loopback_create(u->loopback, core,
                                            PA_LOOPBACK_SOURCE, node->index,
                                            source->index, ns->index,
                                            loopback_role,
                                            resdef->priority,
                                            resdef->flags.rset,
                                            resdef->flags.audio);
            if (node->loop) {
                sink_index = pa_loopback_get_sink_index(core, node->loop);
                node->mux = pa_multiplex_find_by_sink(u->multiplex,sink_index);
            }

            mir_node_print(node, nbf, sizeof(nbf));
            pa_log_debug("updated node:\n%s", nbf);

            if (make_rset)
                pa_murphyif_create_resource_set(u, node, resdef);

            pa_fader_apply_volume_limits(u, node->stamp);
        }
    }
    else {
        memset(&data, 0, sizeof(data));
        data.key = pa_xstrdup(source->name);
        data.direction = mir_input;
        data.implement = mir_device;
        data.channels  = source->channel_map.channels;
        data.available = true;

        if (source == pa_utils_get_null_source(u)) {
            data.visible = false;
            data.type = mir_null;
            data.amname = pa_xstrdup("Silent");
            data.amid = AM_ID_INVALID;
            data.paname = pa_xstrdup(source->name);
            data.paidx = source->index;
        }
        else if (pa_classify_node_by_property(&data, source->proplist)) {
            data.visible = true;
            data.amname = pa_xstrdup(mir_node_type_str(data.type));
            data.amid   = AM_ID_INVALID;
            data.paname = pa_xstrdup(source->name);
        }
        else {
            pa_xfree(data.key); /* for now */
            pa_log_info("currently we do not support statically loaded "
                        "sources without " PA_PROP_NODE_TYPE " property");
            return;
        }

        create_node(u, &data, NULL);
    }
}


void pa_discover_remove_source(struct userdata *u, pa_source *source)
{
    pa_discover    *discover;
    mir_node       *node;
    char           *name;
    mir_node_type   type;

    pa_assert(u);
    pa_assert(source);
    pa_assert_se((discover = u->discover));

    name = pa_utils_get_source_name(source);

    if (!(node = pa_hashmap_get(discover->nodes.byptr, source)))
        pa_log_debug("can't find node for source (name '%s')", name);
    else {
        pa_log_debug("node found. Reseting source data");
        pa_murphyif_destroy_resource_set(u, node);
        schedule_source_cleanup(u, node);
        node->paidx = PA_IDXSET_INVALID;
        pa_hashmap_remove(discover->nodes.byptr, source);

        type = node->type;

        if (source->card) {
            if (type != mir_bluetooth_sco)
                node->available = false;
            else {
                if (!u->state.profile)
                    schedule_deferred_routing(u);
            }
        }
        else {
            pa_log_info("currently we do not support statically "
                        "loaded sources");
        }
    }
}


void pa_discover_register_sink_input(struct userdata *u, pa_sink_input *sinp)
{
    pa_core           *core;
    pa_discover       *discover;
    pa_proplist       *pl;
    char              *name;
    const char        *media;
    mir_node_type      type;
    mir_node           data;
    mir_node          *node;
    mir_node          *target;
    char               key[256];
    pa_sink           *sink;
    const char        *role;
    pa_nodeset_resdef *resdef;

    pa_assert(u);
    pa_assert(sinp);
    pa_assert_se((core = u->core));
    pa_assert_se((discover = u->discover));
    pa_assert_se((pl = sinp->proplist));

    if ((media = pa_proplist_gets(sinp->proplist, PA_PROP_MEDIA_NAME))) {
        if (!strncmp(media, combine_pattern, sizeof(combine_pattern)-1)) {
            pa_log_debug("Seems to be a combine stream. Nothing to do ...");
            return;
        }
        if (!strncmp(media, loopback_outpatrn, sizeof(loopback_outpatrn)-1)) {
            pa_log_debug("Seems to be a loopback stream. Nothing to do ...");
            return;
        }
    }

    name = pa_utils_get_sink_input_name(sinp);

    pa_log_debug("registering input stream '%s'", name);

    if (!(type = pa_classify_guess_stream_node_type(u, pl, &resdef))) {
        pa_log_debug("cant find stream class for '%s'. "
                     "Leaving it alone", name);
        return;
    }

    pa_utils_set_stream_routing_properties(pl, type, NULL);

    snprintf(key, sizeof(key), "stream_input.%d", sinp->index);

    memset(&data, 0, sizeof(data));
    data.key       = key;
    data.direction = mir_input;
    data.implement = mir_stream;
    data.channels  = sinp->channel_map.channels;
    data.type      = type;
    data.zone      = pa_utils_get_zone(sinp->proplist);
    data.visible   = true;
    data.available = true;
    data.amname    = name;
    data.amdescr   = (char *)pa_proplist_gets(pl, PA_PROP_MEDIA_NAME);
    data.amid      = AM_ID_INVALID;
    data.paname    = name;
    data.paidx     = sinp->index;
    data.rsetid    = (char *)pa_proplist_gets(pl, PA_PROP_RESOURCE_SET_ID);

    /*
     * here we can't guess whether the application requested an explicit
     * route by sepcifying the target sink @ stream creation time.
     *
     * the brute force solution: we make a default route for this stream
     * possibly overwiriting the orginal app request :(
     */
    /* this will set data.mux */
    role = pa_proplist_gets(sinp->proplist, PA_PROP_MEDIA_ROLE);
    sink = make_output_prerouting(u, &data, &sinp->channel_map, role, &target);

    node = create_node(u, &data, NULL);
    pa_assert(node);
    pa_discover_add_node_to_ptr_hash(u, sinp, node);

    if (sink && target) {
        pa_log_debug("move stream to sink %u (%s)", sink->index, sink->name);

        if (pa_sink_input_move_to(sinp, sink, false) < 0)
            pa_log("failed to route '%s' => '%s'",node->amname,target->amname);
        else
            pa_audiomgr_add_default_route(u, node, target);
    }
}

bool pa_discover_preroute_sink_input(struct userdata *u,
                                     pa_sink_input_new_data *data)
{
    pa_core           *core;
    pa_module         *m;
    pa_proplist       *pl;
    pa_discover       *discover;
    pa_multiplex      *multiplex;
    mir_node           fake;
    pa_sink           *sink;
    pa_sink_input     *sinp;
    const char        *mnam;
    const char        *role;
    mir_node_type      type;
    mir_node          *node;
    pa_muxnode        *mux;
    pa_nodeset_resdef *resdef;
    bool               loopback;
    bool               remap;

    pa_assert(u);
    pa_assert(data);
    pa_assert_se((core = u->core));
    pa_assert_se((discover = u->discover));
    pa_assert_se((multiplex = u->multiplex));
    pa_assert_se((pl = data->proplist));

    mnam = (m = data->module) ? m->name : "";

    if (pa_streq(mnam, "module-combine-sink")) {
        loopback = false;
        type = mir_node_type_unknown;

        if (!(mux  = pa_multiplex_find_by_module(multiplex, m)) ||
            !(sink = pa_idxset_get_by_index(core->sinks, mux->sink_index)) ||
            !(sinp = pa_idxset_first(sink->inputs, NULL)) ||
            !(type = pa_utils_get_stream_class(sinp->proplist)))
        {
            pa_log_debug("can't figure out the type of multiplex stream");
        }
        else {
            pa_utils_set_stream_routing_properties(data->proplist, type, NULL);
        }
    }
    else {
        loopback = pa_streq(mnam, "module-loopback");
        remap = false;

        if (loopback) {
            if (!(node = pa_utils_get_node_from_data(u, mir_input, data))) {
                pa_log_debug("can't find loopback node for sink-input");
                return true;
            }

            if (node->direction == mir_output) {
                pa_log_debug("refuse to preroute loopback sink-input "
                             "(current route: sink %u @ %p)", data->sink ?
                             data->sink->index : PA_IDXSET_INVALID,data->sink);
                return true;
            }

            data->sink = NULL;

            type = pa_classify_guess_stream_node_type(u, pl, NULL);
        }
        else {
            remap = pa_streq(mnam, "module-remap-sink");
            type = pa_classify_guess_stream_node_type(u, pl, &resdef);

            pa_utils_set_resource_properties(pl, resdef);

            if (pa_stream_state_start_corked(u, data, resdef)) {
                pa_log_debug("start corked");
            }
        }

        pa_utils_set_stream_routing_properties(pl, type, data->sink);
    }

    if (!data->sink) {
        memset(&fake, 0, sizeof(fake));
        fake.direction = mir_input;
        fake.implement = mir_stream;
        fake.channels  = data->channel_map.channels;
        fake.type      = type;
        fake.zone      = pa_utils_get_zone(data->proplist);
        fake.visible   = true;
        fake.available = true;
        fake.amname    = "<preroute sink-input>";
        fake.amid      = AM_ID_INVALID;
        fake.paidx     = PA_IDXSET_INVALID;

        role = pa_proplist_gets(data->proplist, PA_PROP_MEDIA_ROLE);
        sink = make_output_prerouting(u, &fake, &data->channel_map, role,NULL);

        if (sink) {
#if 0
            if (fake.mux && !(data->flags & PA_SINK_INPUT_START_CORKED)) {
                data->flags |= PA_SINK_INPUT_START_CORKED;
                schedule_stream_uncorking(u, sink);
            }
#endif

            if (pa_sink_input_new_data_set_sink(data, sink, false))
                pa_log_debug("set sink %u for new sink-input", sink->index);
            else {
                pa_log("can't set sink %u for new sink-input", sink->index);
                                                      /* copes wit NULL mux */
                pa_multiplex_destroy(u->multiplex, core, fake.mux);
                return false;
            }
        }
    }

    if (remap) {
        /* no ramp needed */
        return true;
    }
    if (loopback && data->sink && data->sink->module) {
        /* no ramp needed */
        if (pa_streq(data->sink->module->name, "module-combine-sink"))
            return true;
    }

    pa_log_debug("set sink-input ramp-muted");
    data->flags |= PA_SINK_INPUT_START_RAMP_MUTED;

    return true;
}

{'fraction_non_alphanumeric': 0.10976099134817573, 'fraction_numerical': 0.002921395206985666, 'mean_word_length': 1.8712323716471564, 'pattern_counts': {'":': 0, '<': 57, '<?xml version=': 0, '>': 431, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 112, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6049071', 'n_tokens_mistral': 19810, 'n_tokens_neox': 19232, 'n_words': 3973}