[pulseaudio-commits] 6 commits - configure.ac PROTOCOL src/daemon src/Makefile.am src/modules src/pulse src/pulsecore src/utils

Arun Raghavan arun at kemper.freedesktop.org
Sat Mar 10 22:57:31 PST 2012


 PROTOCOL                                                    |   23 
 configure.ac                                                |    2 
 src/Makefile.am                                             |   13 
 src/daemon/default.pa.in                                    |    2 
 src/modules/alsa/alsa-mixer.c                               |  284 ++++++++----
 src/modules/alsa/alsa-mixer.h                               |   28 +
 src/modules/alsa/alsa-sink.c                                |    7 
 src/modules/alsa/alsa-source.c                              |    7 
 src/modules/alsa/alsa-util.c                                |  125 +++++
 src/modules/alsa/alsa-util.h                                |    4 
 src/modules/alsa/mixer/paths/analog-input-dock-mic.conf     |    3 
 src/modules/alsa/mixer/paths/analog-input-front-mic.conf    |    3 
 src/modules/alsa/mixer/paths/analog-input-internal-mic.conf |   16 
 src/modules/alsa/mixer/paths/analog-input-linein.conf       |    3 
 src/modules/alsa/mixer/paths/analog-input-mic.conf          |    3 
 src/modules/alsa/mixer/paths/analog-input-rear-mic.conf     |    3 
 src/modules/alsa/mixer/paths/analog-output-headphones.conf  |    5 
 src/modules/alsa/mixer/paths/analog-output-speaker.conf     |    4 
 src/modules/alsa/mixer/paths/analog-output.conf.common      |    8 
 src/modules/alsa/mixer/paths/hdmi-output-0.conf             |    6 
 src/modules/alsa/mixer/paths/hdmi-output-1.conf             |    6 
 src/modules/alsa/mixer/paths/hdmi-output-2.conf             |    6 
 src/modules/alsa/mixer/paths/hdmi-output-3.conf             |    6 
 src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules     |   26 -
 src/modules/alsa/mixer/profile-sets/default.conf            |    1 
 src/modules/alsa/mixer/profile-sets/extra-hdmi.conf         |  158 ++++++
 src/modules/alsa/module-alsa-card.c                         |  132 +++++
 src/modules/module-switch-on-port-available.c               |  236 +++++++++
 src/pulse/def.h                                             |   14 
 src/pulse/introspect.c                                      |  110 ++++
 src/pulse/introspect.h                                      |   16 
 src/pulsecore/device-port.c                                 |    5 
 src/pulsecore/protocol-native.c                             |   30 +
 src/utils/pactl.c                                           |   19 
 34 files changed, 1190 insertions(+), 124 deletions(-)

New commits:
commit e02cb7fb2e7865affed612693935c7fd698e3a6b
Author: David Henningsson <david.henningsson at canonical.com>
Date:   Thu Feb 23 07:17:07 2012 +0100

    alsa-mixer: Make speaker get available=no when headphones are plugged in
    
    While developing the new UI we had to ask ourselves the question of whether
    "speakers" should be considered available when headphones are plugged in.
    In most cases, they are not available and therefore we should list them
    as such.
    
    OTOH, we don't want unplugging the headphones to be considered an act of
    wanting to use the speakers (the user might prefer HDMI), and there might
    be line-outs that keeps the speakers from unmuting anyway. So, at this point,
    I think the most reasonable would be to make the speakers have
    PA_PORT_AVAILABLE_NO when headphones are plugged in and
    PA_PORT_AVAILABLE_UNKNOWN when they are not. But we might want to revisit
    this decision once we have the priority lists up and running.
    
    The same reasoning applies for "Internal Mic", which should become unavailable
    when any other mic is plugged in.
    
    Signed-off-by: David Henningsson <david.henningsson at canonical.com>

diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index 1813ad9..59a9ac9 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -1724,6 +1724,8 @@ static pa_alsa_jack* jack_get(pa_alsa_path *p, const char *section) {
             goto finish;
 
     j = pa_xnew0(pa_alsa_jack, 1);
+    j->state_unplugged = PA_PORT_AVAILABLE_NO;
+    j->state_plugged = PA_PORT_AVAILABLE_YES;
     j->path = p;
     j->name = pa_xstrdup(section);
     j->alsa_name = pa_sprintf_malloc("%s Jack", section);
@@ -2187,6 +2189,45 @@ static int element_parse_override_map(
     return 0;
 }
 
+static int jack_parse_state(
+        const char *filename,
+        unsigned line,
+        const char *section,
+        const char *lvalue,
+        const char *rvalue,
+        void *data,
+        void *userdata) {
+
+    pa_alsa_path *p = userdata;
+    pa_alsa_jack *j;
+    pa_port_available_t pa;
+
+    if (!(j = jack_get(p, section))) {
+        pa_log("[%s:%u] state makes no sense in '%s'", filename, line, section);
+        return -1;
+    }
+
+    if (!strcmp(rvalue,"yes"))
+	pa = PA_PORT_AVAILABLE_YES;
+    else if (!strcmp(rvalue,"no"))
+	pa = PA_PORT_AVAILABLE_NO;
+    else if (!strcmp(rvalue,"unknown"))
+	pa = PA_PORT_AVAILABLE_UNKNOWN;
+    else {
+        pa_log("[%s:%u] state must be 'yes','no' or 'unknown' in '%s'", filename, line, section);
+        return -1;
+    }
+
+    if (!strcmp(lvalue, "state.unplugged"))
+        j->state_unplugged = pa;
+    else {
+        j->state_plugged = pa;
+        pa_assert(!strcmp(lvalue, "state.plugged"));
+    }
+
+    return 0;
+}
+
 static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) {
     snd_mixer_selem_id_t *sid;
     snd_mixer_elem_t *me;
@@ -2380,6 +2421,10 @@ pa_alsa_path* pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa
         { "priority",            option_parse_priority,             NULL, NULL },
         { "name",                option_parse_name,                 NULL, NULL },
 
+        /* [Jack ...] */
+        { "state.plugged",       jack_parse_state,                  NULL, NULL },
+        { "state.unplugged",     jack_parse_state,                  NULL, NULL },
+
         /* [Element ...] */
         { "switch",              element_parse_switch,              NULL, NULL },
         { "volume",              element_parse_volume,              NULL, NULL },
diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h
index 1912ba1..59bd3fb 100644
--- a/src/modules/alsa/alsa-mixer.h
+++ b/src/modules/alsa/alsa-mixer.h
@@ -164,6 +164,7 @@ struct pa_alsa_jack {
     pa_bool_t has_control; /* is the jack itself present? */
     pa_bool_t plugged_in; /* is this jack currently plugged in? */
     snd_hctl_elem_t *hctl_elem; /* Jack detection handle */
+    pa_port_available_t state_unplugged, state_plugged;
 
     pa_alsa_required_t required;
     pa_alsa_required_t required_any;
diff --git a/src/modules/alsa/mixer/paths/analog-input-internal-mic.conf b/src/modules/alsa/mixer/paths/analog-input-internal-mic.conf
index dd40075..ba15f1c 100644
--- a/src/modules/alsa/mixer/paths/analog-input-internal-mic.conf
+++ b/src/modules/alsa/mixer/paths/analog-input-internal-mic.conf
@@ -23,6 +23,22 @@
 priority = 89
 name = analog-input-microphone-internal
 
+[Jack Mic]
+state.plugged = no
+state.unplugged = unknown
+
+[Jack Dock Mic]
+state.plugged = no
+state.unplugged = unknown
+
+[Jack Front Mic]
+state.plugged = no
+state.unplugged = unknown
+
+[Jack Rear Mic]
+state.plugged = no
+state.unplugged = unknown
+
 [Element Internal Mic Boost]
 required-any = any
 switch = select
diff --git a/src/modules/alsa/mixer/paths/analog-output-speaker.conf b/src/modules/alsa/mixer/paths/analog-output-speaker.conf
index 0ba3e91..9c58ed9 100644
--- a/src/modules/alsa/mixer/paths/analog-output-speaker.conf
+++ b/src/modules/alsa/mixer/paths/analog-output-speaker.conf
@@ -22,6 +22,10 @@
 priority = 100
 name = analog-output-speaker
 
+[Jack Headphone]
+state.plugged = no
+state.unplugged = unknown
+
 [Element Hardware Master]
 switch = mute
 volume = merge
diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c
index c5bffcd..3493465 100644
--- a/src/modules/alsa/module-alsa-card.c
+++ b/src/modules/alsa/module-alsa-card.c
@@ -288,7 +288,7 @@ static void report_port_state(pa_device_port *p, struct userdata *u)
         if (p != jack->path->port)
             continue;
 
-        cpa = jack->plugged_in ? PA_PORT_AVAILABLE_YES : PA_PORT_AVAILABLE_NO;
+        cpa = jack->plugged_in ? jack->state_plugged : jack->state_unplugged;
 
         /* "Yes" and "no" trumphs "unknown" if we have more than one jack */
         if (cpa == PA_PORT_AVAILABLE_UNKNOWN)
diff --git a/src/modules/module-switch-on-port-available.c b/src/modules/module-switch-on-port-available.c
index 9149b6d..0a115d3 100644
--- a/src/modules/module-switch-on-port-available.c
+++ b/src/modules/module-switch-on-port-available.c
@@ -120,6 +120,9 @@ static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port
     pa_source *source;
     pa_bool_t is_active_profile, is_active_port;
 
+    if (port->available == PA_PORT_AVAILABLE_UNKNOWN)
+        return PA_HOOK_OK;
+
     pa_log_debug("finding port %s", port->name);
 
     PA_IDXSET_FOREACH(card, c->cards, state)
diff --git a/src/pulsecore/device-port.c b/src/pulsecore/device-port.c
index 30fb025..50c99b8 100644
--- a/src/pulsecore/device-port.c
+++ b/src/pulsecore/device-port.c
@@ -39,10 +39,11 @@ void pa_device_port_set_available(pa_device_port *p, pa_port_available_t status)
     if (p->available == status)
         return;
 
-    pa_assert(status != PA_PORT_AVAILABLE_UNKNOWN);
+/*    pa_assert(status != PA_PORT_AVAILABLE_UNKNOWN); */
 
     p->available = status;
-    pa_log_debug("Setting port %s to status %s", p->name, status == PA_PORT_AVAILABLE_YES ? "yes" : "no");
+    pa_log_debug("Setting port %s to status %s", p->name, status == PA_PORT_AVAILABLE_YES ? "yes" :
+       status == PA_PORT_AVAILABLE_NO ? "no" : "unknown");
 
     /* Post subscriptions to the card which owns us */
     pa_assert_se(core = p->core);

commit 793f46320e98aa10dca16bcc1b3a421a4f2b6b7e
Author: David Henningsson <david.henningsson at canonical.com>
Date:   Thu Feb 23 07:17:06 2012 +0100

    introspect: Expose port info per card to clients
    
    For volume control UIs to be able to show ports in inactive profiles,
    expose all ports together with the card info. This includes updating
    the protocol and the client API to show the connection between ports
    and for which profiles the ports are relevant.
    
    Update protocol to 26.
    
    Signed-off-by: David Henningsson <david.henningsson at canonical.com>

diff --git a/PROTOCOL b/PROTOCOL
index f437829..acd53ba 100644
--- a/PROTOCOL
+++ b/PROTOCOL
@@ -299,6 +299,29 @@ The field is added once for every port.
 When port availability changes, send a subscription event for the
 owning card.
 
+## v26, implemented by >= 2.0
+
+In reply from PA_COMMAND_GET_CARD_INFO (and thus
+PA_COMMAND_GET_CARD_INFO_LIST), the following is added:
+
+    uint32_t n_ports
+
+...followed by n_ports extended port entries, which look like this:
+
+    string name
+    string description
+    uint32_t priority
+    uint32_t available
+    uint8_t direction
+    proplist
+    uint32_t n_profiles
+    string profile_name_1
+    ...
+    string profile_name_n
+
+Profile names must match earlier sent profile names for the same card.
+
+
 #### If you just changed the protocol, read this
 ## module-tunnel depends on the sink/source/sink-input/source-input protocol
 ## internals, so if you changed these, you might have broken module-tunnel.
diff --git a/configure.ac b/configure.ac
index c16aaa9..5474bc8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -36,7 +36,7 @@ AC_SUBST(PA_MINOR, pa_minor)
 AC_SUBST(PA_MAJORMINOR, pa_major.pa_minor)
 
 AC_SUBST(PA_API_VERSION, 12)
-AC_SUBST(PA_PROTOCOL_VERSION, 25)
+AC_SUBST(PA_PROTOCOL_VERSION, 26)
 
 # The stable ABI for client applications, for the version info x:y:z
 # always will hold y=z
diff --git a/src/pulse/def.h b/src/pulse/def.h
index 7ca0c4b..b939319 100644
--- a/src/pulse/def.h
+++ b/src/pulse/def.h
@@ -124,6 +124,20 @@ typedef enum pa_context_flags {
 #define PA_CONTEXT_NOFAIL PA_CONTEXT_NOFAIL
 /** \endcond */
 
+/** Direction bitfield - while we currently do not expose anything bidirectional,
+  one should test against the bit instead of the value (e g if (d & PA_DIRECTION_OUTPUT)),
+  because we might add bidirectional stuff in the future. \since 2.0
+*/
+typedef enum pa_direction {
+    PA_DIRECTION_OUTPUT = 0x0001U,  /**< Output direction */
+    PA_DIRECTION_INPUT = 0x0002U    /**< Input direction */
+} pa_direction_t;
+
+/** \cond fulldocs */
+#define PA_DIRECTION_OUTPUT PA_DIRECTION_OUTPUT
+#define PA_DIRECTION_INPUT PA_DIRECTION_INPUT
+/** \endcond */
+
 /** The type of device we are dealing with */
 typedef enum pa_device_type {
     PA_DEVICE_TYPE_SINK,     /**< Playback device */
diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c
index c8bf7ca..38a9d1c 100644
--- a/src/pulse/introspect.c
+++ b/src/pulse/introspect.c
@@ -763,9 +763,101 @@ pa_operation* pa_context_get_client_info_list(pa_context *c, pa_client_info_cb_t
 
 /*** Card info ***/
 
+static void card_info_free(pa_card_info* i)
+{
+    if (i->proplist)
+        pa_proplist_free(i->proplist);
+
+    pa_xfree(i->profiles);
+
+    if (i->ports) {
+        uint32_t j;
+
+        for (j = 0; j < i->n_ports; j++) {
+            if (i->ports[j]) {
+                if (i->ports[j]->profiles)
+                    pa_xfree(i->ports[j]->profiles);
+                if (i->ports[j]->proplist)
+                    pa_proplist_free(i->ports[j]->proplist);
+            }
+        }
+
+        pa_xfree(i->ports[0]);
+        pa_xfree(i->ports);
+    }
+}
+
+static int fill_card_port_info(pa_tagstruct* t, pa_card_info* i)
+{
+    uint32_t j, k, l;
+
+    if (pa_tagstruct_getu32(t, &i->n_ports) < 0)
+        return -PA_ERR_PROTOCOL;
+
+    if (i->n_ports == 0) {
+        i->ports = NULL;
+        return 0;
+    }
+
+    i->ports = pa_xnew0(pa_card_port_info*, i->n_ports+1);
+    i->ports[0] = pa_xnew0(pa_card_port_info, i->n_ports);
+
+    for (j = 0; j < i->n_ports; j++) {
+        uint8_t direction;
+        uint32_t available;
+        pa_card_port_info* port = i->ports[j] = &i->ports[0][j];
+
+        port->proplist = pa_proplist_new();
+
+        if (pa_tagstruct_gets(t, &port->name) < 0 ||
+            pa_tagstruct_gets(t, &port->description) < 0 ||
+            pa_tagstruct_getu32(t, &port->priority) < 0 ||
+            pa_tagstruct_getu32(t, &available) < 0 ||
+            pa_tagstruct_getu8(t, &direction) < 0 ||
+            pa_tagstruct_get_proplist(t, port->proplist) < 0 ||
+            pa_tagstruct_getu32(t, &port->n_profiles) < 0) {
+
+            return -PA_ERR_PROTOCOL;
+        }
+
+        if (available > PA_PORT_AVAILABLE_YES ||
+            direction > PA_DIRECTION_OUTPUT + PA_DIRECTION_INPUT) {
+
+            return -PA_ERR_PROTOCOL;
+        }
+
+        port->direction = direction;
+        port->available = available;
+
+        if (port->n_profiles > 0) {
+            port->profiles = pa_xnew0(pa_card_profile_info*, i->n_profiles+1);
+
+            for (k = 0; k < port->n_profiles; k++) {
+                const char* profilename;
+
+                if (pa_tagstruct_gets(t, &profilename) < 0)
+                    return -PA_ERR_PROTOCOL;
+
+                for (l = 0; l < i->n_profiles; l++) {
+                    if (pa_streq(i->profiles[l].name, profilename)) {
+                        port->profiles[k] = &i->profiles[l];
+                        break;
+                    }
+                }
+
+                if (l >= i->n_profiles)
+                    return -PA_ERR_PROTOCOL;
+            }
+        }
+    }
+
+    return 0;
+}
+
 static void context_get_card_info_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
     pa_operation *o = userdata;
     int eol = 1;
+    pa_card_info i;
 
     pa_assert(pd);
     pa_assert(o);
@@ -782,7 +874,6 @@ static void context_get_card_info_callback(pa_pdispatch *pd, uint32_t command, u
     } else {
 
         while (!pa_tagstruct_eof(t)) {
-            pa_card_info i;
             uint32_t j;
             const char*ap;
 
@@ -795,6 +886,7 @@ static void context_get_card_info_callback(pa_pdispatch *pd, uint32_t command, u
                 pa_tagstruct_getu32(t, &i.n_profiles) < 0) {
 
                 pa_context_fail(o->context, PA_ERR_PROTOCOL);
+                card_info_free(&i);
                 goto finish;
             }
 
@@ -810,7 +902,7 @@ static void context_get_card_info_callback(pa_pdispatch *pd, uint32_t command, u
                         pa_tagstruct_getu32(t, &i.profiles[j].priority) < 0) {
 
                         pa_context_fail(o->context, PA_ERR_PROTOCOL);
-                        pa_xfree(i.profiles);
+                        card_info_free(&i);
                         goto finish;
                     }
                 }
@@ -826,8 +918,7 @@ static void context_get_card_info_callback(pa_pdispatch *pd, uint32_t command, u
                 pa_tagstruct_get_proplist(t, i.proplist) < 0) {
 
                 pa_context_fail(o->context, PA_ERR_PROTOCOL);
-                pa_xfree(i.profiles);
-                pa_proplist_free(i.proplist);
+                card_info_free(&i);
                 goto finish;
             }
 
@@ -839,13 +930,20 @@ static void context_get_card_info_callback(pa_pdispatch *pd, uint32_t command, u
                     }
             }
 
+            if (o->context->version >= 26) {
+                if (fill_card_port_info(t, &i) < 0) {
+                    pa_context_fail(o->context, PA_ERR_PROTOCOL);
+                    card_info_free(&i);
+                    goto finish;
+                }
+            }
+
             if (o->callback) {
                 pa_card_info_cb_t cb = (pa_card_info_cb_t) o->callback;
                 cb(o->context, &i, 0, o->userdata);
             }
 
-            pa_proplist_free(i.proplist);
-            pa_xfree(i.profiles);
+            card_info_free(&i);
         }
     }
 
diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h
index b1fd6d4..224432c 100644
--- a/src/pulse/introspect.h
+++ b/src/pulse/introspect.h
@@ -454,6 +454,20 @@ typedef struct pa_card_profile_info {
     uint32_t priority;                  /**< The higher this value is the more useful this profile is as a default */
 } pa_card_profile_info;
 
+/** Stores information about a specific port of a card.  Please
+ * note that this structure can be extended as part of evolutionary
+ * API updates at any time in any new release. \since 2.0 */
+typedef struct pa_card_port_info {
+    const char *name;                   /**< Name of this port */
+    const char *description;            /**< Description of this port */
+    uint32_t priority;                  /**< The higher this value is the more useful this port is as a default */
+    int available;                      /**< A \link pa_port_available_t, indicating availability status of this port. */
+    int direction;                      /**< This is a \link pa_direction_t enum, indicating the direction of this port. */
+    uint32_t n_profiles;                /**< Number of entries in profile array */
+    pa_card_profile_info** profiles;    /**< Array of pointers available profile, or NULL. Array is terminated by an entry set to NULL. */
+    pa_proplist *proplist;              /**< Property list */
+} pa_card_port_info;
+
 /** Stores information about cards. Please note that this structure
  * can be extended as part of evolutionary API updates at any time in
  * any new release.  \since 0.9.15 */
@@ -466,6 +480,8 @@ typedef struct pa_card_info {
     pa_card_profile_info* profiles;      /**< Array of available profile, or NULL. Array is terminated by an entry with name set to NULL. Number of entries is stored in n_profiles */
     pa_card_profile_info* active_profile; /**< Pointer to active profile in the array, or NULL */
     pa_proplist *proplist;               /**< Property list */
+    uint32_t n_ports;                    /**< Number of entries in port array */
+    pa_card_port_info **ports;           /**< Array of pointers to ports, or NULL. Array is terminated by an entry set to NULL. */
 } pa_card_info;
 
 /** Callback prototype for pa_context_get_card_info_...() \since 0.9.15 */
diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c
index cbd5fef..e4e1809 100644
--- a/src/pulsecore/protocol-native.c
+++ b/src/pulsecore/protocol-native.c
@@ -3258,6 +3258,36 @@ static void card_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_car
 
     pa_tagstruct_puts(t, card->active_profile ? card->active_profile->name : NULL);
     pa_tagstruct_put_proplist(t, card->proplist);
+
+    if (c->version < 26)
+        return;
+
+    if (card->ports) {
+        pa_device_port* port;
+        pa_proplist* proplist = pa_proplist_new(); /* For now - push an empty proplist */
+
+        pa_tagstruct_putu32(t, pa_hashmap_size(card->ports));
+
+        PA_HASHMAP_FOREACH(port, card->ports, state) {
+            pa_tagstruct_puts(t, port->name);
+            pa_tagstruct_puts(t, port->description);
+            pa_tagstruct_putu32(t, port->priority);
+            pa_tagstruct_putu32(t, port->available);
+            pa_tagstruct_putu8(t, /* FIXME: port->direction */ (port->is_input ? PA_DIRECTION_INPUT : 0) | (port->is_output ? PA_DIRECTION_OUTPUT : 0));
+            pa_tagstruct_put_proplist(t, proplist);
+
+            if (port->profiles) {
+                void* state2;
+                pa_tagstruct_putu32(t, pa_hashmap_size(port->profiles));
+                PA_HASHMAP_FOREACH(p, port->profiles, state2)
+                    pa_tagstruct_puts(t, p->name);
+            } else
+                pa_tagstruct_putu32(t, 0);
+        }
+
+        pa_proplist_free(proplist);
+    } else
+        pa_tagstruct_putu32(t, 0);
 }
 
 static void module_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_module *module) {
diff --git a/src/utils/pactl.c b/src/utils/pactl.c
index bf16534..fc5a238 100644
--- a/src/utils/pactl.c
+++ b/src/utils/pactl.c
@@ -582,6 +582,25 @@ static void get_card_info_callback(pa_context *c, const pa_card_info *i, int is_
         printf(_("\tActive Profile: %s\n"),
                i->active_profile->name);
 
+    if (i->ports) {
+        pa_card_port_info **p;
+
+        printf(_("\tPorts:\n"));
+        for (p = i->ports; *p; p++) {
+            pa_card_profile_info **pr = (*p)->profiles;
+            printf(_("\t\t%s: %s (priority %u)\n"), (*p)->name, (*p)->description, (*p)->priority);
+            if (pr) {
+                printf(_("\t\t\tPart of profile(s): %s"), pa_strnull((*pr)->name));
+                pr++;
+                while (*pr) {
+                    printf(", %s", pa_strnull((*pr)->name));
+                    pr++;
+                }
+                printf("\n");
+            }
+        }
+    }
+
     pa_xfree(pl);
 }
 

commit 752ae7285e0fafb5a9b7eba16f925f88980f8100
Author: David Henningsson <david.henningsson at canonical.com>
Date:   Thu Feb 23 07:17:05 2012 +0100

    conf: Load switch-on-port-available module by default
    
    Signed-off-by: David Henningsson <david.henningsson at canonical.com>

diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in
index f2f22e8..633bb77 100755
--- a/src/daemon/default.pa.in
+++ b/src/daemon/default.pa.in
@@ -190,6 +190,8 @@ ifelse(@HAVE_X11@, 1, [dnl
 #.endif
 ])dnl
 
+load-module module-switch-on-port-available
+
 ### Make some devices default
 #set-default-sink output
 #set-default-source input

commit d1ce4c0aea23af9d3007cb91e3a5c76ff67bc7cc
Author: David Henningsson <david.henningsson at canonical.com>
Date:   Thu Feb 23 07:17:04 2012 +0100

    Add a new module switch-on-port-available that acts on port changes
    
    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.

diff --git a/src/Makefile.am b/src/Makefile.am
index f232151..b07c60a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -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
index 0000000..9149b6d
--- /dev/null
+++ b/src/modules/module-switch-on-port-available.c
@@ -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);
+}

commit 56018683b147740129538737722590bc5824dbe1
Author: David Henningsson <david.henningsson at canonical.com>
Date:   Thu Feb 23 07:17:03 2012 +0100

    alsa: Add port information to HDMI profiles
    
    For Nvidia and Intel, support probing of up to four HDMI devices.
    Also add port information to all HDMI profiles.
    
    Signed-off-by: David Henningsson <david.henningsson at canonical.com>

diff --git a/src/Makefile.am b/src/Makefile.am
index 130d33b..f232151 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1091,6 +1091,7 @@ modlibexec_LTLIBRARIES += \
 
 dist_alsaprofilesets_DATA = \
 		modules/alsa/mixer/profile-sets/default.conf \
+		modules/alsa/mixer/profile-sets/extra-hdmi.conf \
 		modules/alsa/mixer/profile-sets/maudio-fasttrack-pro.conf \
 		modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf \
 		modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf \
@@ -1128,7 +1129,11 @@ dist_alsapaths_DATA = \
 		modules/alsa/mixer/paths/analog-output-headphones-2.conf \
 		modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf \
 		modules/alsa/mixer/paths/analog-output-mono.conf \
-		modules/alsa/mixer/paths/iec958-stereo-output.conf
+		modules/alsa/mixer/paths/iec958-stereo-output.conf \
+		modules/alsa/mixer/paths/hdmi-output-0.conf \
+		modules/alsa/mixer/paths/hdmi-output-1.conf \
+		modules/alsa/mixer/paths/hdmi-output-2.conf \
+		modules/alsa/mixer/paths/hdmi-output-3.conf
 
 endif
 
diff --git a/src/modules/alsa/mixer/paths/hdmi-output-0.conf b/src/modules/alsa/mixer/paths/hdmi-output-0.conf
new file mode 100644
index 0000000..46ee4fd
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/hdmi-output-0.conf
@@ -0,0 +1,6 @@
+[General]
+description = HDMI / DisplayPort
+priority = 59
+
+[Jack HDMI/DP,pcm=3]
+required-any = any
diff --git a/src/modules/alsa/mixer/paths/hdmi-output-1.conf b/src/modules/alsa/mixer/paths/hdmi-output-1.conf
new file mode 100644
index 0000000..da8fcb4
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/hdmi-output-1.conf
@@ -0,0 +1,6 @@
+[General]
+description = HDMI / DisplayPort 2
+priority = 58
+
+[Jack HDMI/DP,pcm=7]
+required-any = any
diff --git a/src/modules/alsa/mixer/paths/hdmi-output-2.conf b/src/modules/alsa/mixer/paths/hdmi-output-2.conf
new file mode 100644
index 0000000..5fcddc6
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/hdmi-output-2.conf
@@ -0,0 +1,6 @@
+[General]
+description = HDMI / DisplayPort 3
+priority = 57
+
+[Jack HDMI/DP,pcm=8]
+required-any = any
diff --git a/src/modules/alsa/mixer/paths/hdmi-output-3.conf b/src/modules/alsa/mixer/paths/hdmi-output-3.conf
new file mode 100644
index 0000000..8200787
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/hdmi-output-3.conf
@@ -0,0 +1,6 @@
+[General]
+description = HDMI / DisplayPort 4
+priority = 56
+
+[Jack HDMI/DP,pcm=9]
+required-any = any
diff --git a/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules b/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules
index e6cfef5..fc332c3 100644
--- a/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules
+++ b/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules
@@ -10,7 +10,7 @@
 # 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.
+# Lesser 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,
@@ -19,15 +19,23 @@
 SUBSYSTEM!="sound", GOTO="pulseaudio_end"
 ACTION!="change", GOTO="pulseaudio_end"
 KERNEL!="card*", GOTO="pulseaudio_end"
+SUBSYSTEMS=="usb", GOTO="pulseaudio_check_usb"
 
 SUBSYSTEMS=="platform", DRIVERS=="thinkpad_acpi", ENV{PULSE_IGNORE}="1"
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1978", ENV{PULSE_PROFILE_SET}="native-instruments-audio8dj.conf"
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="0839", ENV{PULSE_PROFILE_SET}="native-instruments-audio4dj.conf"
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="baff", ENV{PULSE_PROFILE_SET}="native-instruments-traktorkontrol-s4.conf"
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="4711", ENV{PULSE_PROFILE_SET}="native-instruments-korecontroller.conf"
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1011", ENV{PULSE_PROFILE_SET}="native-instruments-traktor-audio6.conf"
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1021", ENV{PULSE_PROFILE_SET}="native-instruments-traktor-audio10.conf"
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="0763", ATTRS{idProduct}=="2012", ENV{PULSE_PROFILE_SET}="maudio-fasttrack-pro.conf"
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="045e", ATTRS{idProduct}=="02bb", ENV{PULSE_PROFILE_SET}="kinect-audio.conf"
+
+# NVidia and Intel HDAs often have more than one HDMI codec/port on the same card
+ATTRS{vendor}=="0x10de", ENV{PULSE_PROFILE_SET}="extra-hdmi.conf"
+ATTRS{vendor}=="0x8086", ENV{PULSE_PROFILE_SET}="extra-hdmi.conf"
+GOTO="pulseaudio_end"
+
+LABEL="pulseaudio_check_usb"
+ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1978", ENV{PULSE_PROFILE_SET}="native-instruments-audio8dj.conf"
+ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="0839", ENV{PULSE_PROFILE_SET}="native-instruments-audio4dj.conf"
+ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="baff", ENV{PULSE_PROFILE_SET}="native-instruments-traktorkontrol-s4.conf"
+ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="4711", ENV{PULSE_PROFILE_SET}="native-instruments-korecontroller.conf"
+ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1011", ENV{PULSE_PROFILE_SET}="native-instruments-traktor-audio6.conf"
+ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1021", ENV{PULSE_PROFILE_SET}="native-instruments-traktor-audio10.conf"
+ATTRS{idVendor}=="0763", ATTRS{idProduct}=="2012", ENV{PULSE_PROFILE_SET}="maudio-fasttrack-pro.conf"
+ATTRS{idVendor}=="045e", ATTRS{idProduct}=="02bb", ENV{PULSE_PROFILE_SET}="kinect-audio.conf"
 
 LABEL="pulseaudio_end"
diff --git a/src/modules/alsa/mixer/profile-sets/default.conf b/src/modules/alsa/mixer/profile-sets/default.conf
index d1d4ba3..5ac322b 100644
--- a/src/modules/alsa/mixer/profile-sets/default.conf
+++ b/src/modules/alsa/mixer/profile-sets/default.conf
@@ -181,6 +181,7 @@ device-strings = hdmi:%f
 channel-map = left,right
 priority = 4
 direction = output
+paths-output = hdmi-output-0
 
 ; An example for defining multiple-sink profiles
 #[Profile output:analog-stereo+output:iec958-stereo+input:analog-stereo]
diff --git a/src/modules/alsa/mixer/profile-sets/extra-hdmi.conf b/src/modules/alsa/mixer/profile-sets/extra-hdmi.conf
new file mode 100644
index 0000000..d0bff6a
--- /dev/null
+++ b/src/modules/alsa/mixer/profile-sets/extra-hdmi.conf
@@ -0,0 +1,158 @@
+# This file is part of PulseAudio.
+#
+# 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.
+
+; This is a profile for Nvidia and Intel cards - some cards have four HDMI codecs,
+; and which ones are working seems to vary a lot between GPU boards. In addition,
+; Nvidia and Intel make southbridges as well, so we need to keep the existing
+; analog profiles.
+; (And by not adding all these extra profiles to default.conf, we make sure
+; there is no performance hit for non-Nvidia/Intel cards.)
+
+[General]
+auto-profiles = yes
+
+[Mapping analog-mono]
+device-strings = hw:%f
+channel-map = mono
+paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-headphones analog-output-headphones-2 analog-output-mono analog-output-lfe-on-mono
+paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line
+priority = 1
+
+[Mapping analog-stereo]
+device-strings = front:%f hw:%f
+channel-map = left,right
+paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-headphones analog-output-headphones-2 analog-output-mono analog-output-lfe-on-mono
+paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line
+priority = 10
+
+[Mapping analog-surround-40]
+device-strings = surround40:%f
+channel-map = front-left,front-right,rear-left,rear-right
+paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-lfe-on-mono
+priority = 7
+direction = output
+
+[Mapping analog-surround-41]
+device-strings = surround41:%f
+channel-map = front-left,front-right,rear-left,rear-right,lfe
+paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-lfe-on-mono
+priority = 8
+direction = output
+
+[Mapping analog-surround-50]
+device-strings = surround50:%f
+channel-map = front-left,front-right,rear-left,rear-right,front-center
+paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-lfe-on-mono
+priority = 7
+direction = output
+
+[Mapping analog-surround-51]
+device-strings = surround51:%f
+channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe
+paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-lfe-on-mono
+priority = 8
+direction = output
+
+[Mapping analog-surround-71]
+device-strings = surround71:%f
+channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right
+description = Analog Surround 7.1
+paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-lfe-on-mono
+priority = 7
+direction = output
+
+[Mapping iec958-stereo]
+device-strings = iec958:%f
+channel-map = left,right
+paths-input = iec958-stereo-input
+paths-output = iec958-stereo-output
+priority = 5
+
+[Mapping iec958-ac3-surround-40]
+device-strings = a52:%f
+channel-map = front-left,front-right,rear-left,rear-right
+priority = 2
+direction = output
+
+[Mapping iec958-ac3-surround-51]
+device-strings = a52:%f
+channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe
+priority = 3
+direction = output
+
+[Mapping hdmi-stereo]
+device-strings = hdmi:%f
+description = Digital Stereo (HDMI)
+paths-output = hdmi-output-0
+channel-map = left,right
+priority = 4
+direction = output
+
+[Mapping hdmi-surround]
+description = Digital Surround 5.1 (HDMI)
+device-strings = hdmi:%f
+paths-output = hdmi-output-0
+channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe
+priority = 3
+direction = output
+
+[Mapping hdmi-stereo-extra1]
+description = Digital Stereo (HDMI)
+device-strings = hdmi:%f,1
+paths-output = hdmi-output-1
+channel-map = left,right
+priority = 2
+direction = output
+
+[Mapping hdmi-surround-extra1]
+description = Digital Surround 5.1 (HDMI)
+device-strings = hdmi:%f,1
+paths-output = hdmi-output-1
+channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe
+priority = 1
+direction = output
+
+[Mapping hdmi-stereo-extra2]
+description = Digital Stereo (HDMI)
+device-strings = hdmi:%f,2
+paths-output = hdmi-output-2
+channel-map = left,right
+priority = 2
+direction = output
+
+[Mapping hdmi-surround-extra2]
+description = Digital Surround 5.1 (HDMI)
+device-strings = hdmi:%f,2
+paths-output = hdmi-output-2
+channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe
+priority = 1
+direction = output
+
+[Mapping hdmi-stereo-extra3]
+description = Digital Stereo (HDMI)
+device-strings = hdmi:%f,3
+paths-output = hdmi-output-3
+channel-map = left,right
+priority = 2
+direction = output
+
+[Mapping hdmi-surround-extra3]
+description = Digital Surround 5.1 (HDMI)
+device-strings = hdmi:%f,3
+paths-output = hdmi-output-3
+channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe
+priority = 1
+direction = output

commit dedf1340c611c3d9b17a08e0ee18ae22afac623c
Author: David Henningsson <david.henningsson at canonical.com>
Date:   Thu Feb 23 07:17:02 2012 +0100

    alsa: Jack detection kcontrol implementation
    
    Support the new jack detection interface implemented in Linux 3.3
    (and Ubuntu's 3.2 kernel).
    
    Jacks are probed and detected using the snd_hctl_* commands, which
    means we need to listen to them using fdlists. As this detection
    needs to be active even if there is currently no sink for the jack,
    so this polling is done on the card level.
    
    Also add configuration support in paths, like this:
    [Jack Headphone]
    required-any = any
    
    ...where 'Jack Headphone' should match 'Headphone Jack' as given by
    ALSA (as seen in e g 'amixer controls').
    "Required", "required-any" and "required-absent" is supported. Using
    required-any, one can have several ports even though there is no
    other indication in the mixer that this path exists.
    
    Signed-off-by: David Henningsson <david.henningsson at canonical.com>

diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index 0e53550..1813ad9 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -72,6 +72,7 @@ struct pa_alsa_fdlist {
     struct pollfd *work_fds;
 
     snd_mixer_t *mixer;
+    snd_hctl_t *hctl;
 
     pa_mainloop_api *m;
     pa_defer_event *defer;
@@ -92,7 +93,7 @@ static void io_cb(pa_mainloop_api *a, pa_io_event *e, int fd, pa_io_event_flags_
 
     pa_assert(a);
     pa_assert(fdl);
-    pa_assert(fdl->mixer);
+    pa_assert(fdl->mixer || fdl->hctl);
     pa_assert(fdl->fds);
     pa_assert(fdl->work_fds);
 
@@ -119,15 +120,24 @@ static void io_cb(pa_mainloop_api *a, pa_io_event *e, int fd, pa_io_event_flags_
 
     pa_assert(i != fdl->num_fds);
 
-    if ((err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents)) < 0) {
+    if (fdl->hctl)
+        err = snd_hctl_poll_descriptors_revents(fdl->hctl, fdl->work_fds, fdl->num_fds, &revents);
+    else
+        err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents);
+
+    if (err < 0) {
         pa_log_error("Unable to get poll revent: %s", pa_alsa_strerror(err));
         return;
     }
 
     a->defer_enable(fdl->defer, 1);
 
-    if (revents)
-        snd_mixer_handle_events(fdl->mixer);
+    if (revents) {
+        if (fdl->hctl)
+            snd_hctl_handle_events(fdl->hctl);
+        else
+            snd_mixer_handle_events(fdl->mixer);
+    }
 }
 
 static void defer_cb(pa_mainloop_api *a, pa_defer_event *e, void *userdata) {
@@ -138,11 +148,16 @@ static void defer_cb(pa_mainloop_api *a, pa_defer_event *e, void *userdata) {
 
     pa_assert(a);
     pa_assert(fdl);
-    pa_assert(fdl->mixer);
+    pa_assert(fdl->mixer || fdl->hctl);
 
     a->defer_enable(fdl->defer, 0);
 
-    if ((n = snd_mixer_poll_descriptors_count(fdl->mixer)) < 0) {
+    if (fdl->hctl)
+        n = snd_hctl_poll_descriptors_count(fdl->hctl);
+    else
+        n = snd_mixer_poll_descriptors_count(fdl->mixer);
+
+    if (n < 0) {
         pa_log("snd_mixer_poll_descriptors_count() failed: %s", pa_alsa_strerror(n));
         return;
     }
@@ -159,7 +174,12 @@ static void defer_cb(pa_mainloop_api *a, pa_defer_event *e, void *userdata) {
 
     memset(fdl->work_fds, 0, sizeof(struct pollfd) * num_fds);
 
-    if ((err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds)) < 0) {
+    if (fdl->hctl)
+        err = snd_hctl_poll_descriptors(fdl->hctl, fdl->work_fds, num_fds);
+    else
+        err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds);
+
+    if (err < 0) {
         pa_log_error("Unable to get poll descriptors: %s", pa_alsa_strerror(err));
         return;
     }
@@ -228,12 +248,15 @@ void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) {
     pa_xfree(fdl);
 }
 
-int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api *m) {
+/* We can listen to either a snd_hctl_t or a snd_mixer_t, but not both */
+int pa_alsa_fdlist_set_handle(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, snd_hctl_t *hctl_handle, pa_mainloop_api *m) {
     pa_assert(fdl);
-    pa_assert(mixer_handle);
+    pa_assert(hctl_handle || mixer_handle);
+    pa_assert(!(hctl_handle && mixer_handle));
     pa_assert(m);
     pa_assert(!fdl->m);
 
+    fdl->hctl = hctl_handle;
     fdl->mixer = mixer_handle;
     fdl->m = m;
     fdl->defer = m->defer_new(m, defer_cb, fdl);
@@ -355,81 +378,7 @@ int pa_alsa_set_mixer_rtpoll(struct pa_alsa_mixer_pdata *pd, snd_mixer_t *mixer,
     return 0;
 }
 
-static int prepare_mixer(snd_mixer_t *mixer, const char *dev) {
-    int err;
-
-    pa_assert(mixer);
-    pa_assert(dev);
-
-    if ((err = snd_mixer_attach(mixer, dev)) < 0) {
-        pa_log_info("Unable to attach to mixer %s: %s", dev, pa_alsa_strerror(err));
-        return -1;
-    }
-
-    if ((err = snd_mixer_selem_register(mixer, NULL, NULL)) < 0) {
-        pa_log_warn("Unable to register mixer: %s", pa_alsa_strerror(err));
-        return -1;
-    }
-
-    if ((err = snd_mixer_load(mixer)) < 0) {
-        pa_log_warn("Unable to load mixer: %s", pa_alsa_strerror(err));
-        return -1;
-    }
-
-    pa_log_info("Successfully attached to mixer '%s'", dev);
-    return 0;
-}
-
-snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) {
-    int err;
-    snd_mixer_t *m;
-    const char *dev;
-    snd_pcm_info_t* info;
-    snd_pcm_info_alloca(&info);
-
-    pa_assert(pcm);
-
-    if ((err = snd_mixer_open(&m, 0)) < 0) {
-        pa_log("Error opening mixer: %s", pa_alsa_strerror(err));
-        return NULL;
-    }
-
-    /* First, try by name */
-    if ((dev = snd_pcm_name(pcm)))
-        if (prepare_mixer(m, dev) >= 0) {
-            if (ctl_device)
-                *ctl_device = pa_xstrdup(dev);
-
-            return m;
-        }
-
-    /* Then, try by card index */
-    if (snd_pcm_info(pcm, info) >= 0) {
-        char *md;
-        int card_idx;
-
-        if ((card_idx = snd_pcm_info_get_card(info)) >= 0) {
-
-            md = pa_sprintf_malloc("hw:%i", card_idx);
 
-            if (!dev || !pa_streq(dev, md))
-                if (prepare_mixer(m, md) >= 0) {
-
-                    if (ctl_device)
-                        *ctl_device = md;
-                    else
-                        pa_xfree(md);
-
-                    return m;
-                }
-
-            pa_xfree(md);
-        }
-    }
-
-    snd_mixer_close(m);
-    return NULL;
-}
 
 static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_MAX] = {
     [PA_CHANNEL_POSITION_MONO] = SND_MIXER_SCHN_MONO, /* The ALSA name is just an alias! */
@@ -523,6 +472,14 @@ static void decibel_fix_free(pa_alsa_decibel_fix *db_fix) {
     pa_xfree(db_fix);
 }
 
+static void jack_free(pa_alsa_jack *j) {
+    pa_assert(j);
+
+    pa_xfree(j->alsa_name);
+    pa_xfree(j->name);
+    pa_xfree(j);
+}
+
 static void element_free(pa_alsa_element *e) {
     pa_alsa_option *o;
     pa_assert(e);
@@ -540,11 +497,17 @@ static void element_free(pa_alsa_element *e) {
 }
 
 void pa_alsa_path_free(pa_alsa_path *p) {
+    pa_alsa_jack *j;
     pa_alsa_element *e;
     pa_alsa_setting *s;
 
     pa_assert(p);
 
+    while ((j = p->jacks)) {
+        PA_LLIST_REMOVE(pa_alsa_jack, p->jacks, j);
+        jack_free(j);
+    }
+
     while ((e = p->elements)) {
         PA_LLIST_REMOVE(pa_alsa_element, p->elements, e);
         element_free(e);
@@ -1689,6 +1652,26 @@ static int element_probe(pa_alsa_element *e, snd_mixer_t *m) {
     return 0;
 }
 
+static int jack_probe(pa_alsa_jack *j, snd_hctl_t *h) {
+    pa_assert(h);
+    pa_assert(j);
+    pa_assert(j->path);
+
+    j->has_control = pa_alsa_find_jack(h, j->alsa_name) != NULL;
+
+    if (j->has_control) {
+        if (j->required_absent != PA_ALSA_REQUIRED_IGNORE)
+            return -1;
+        if (j->required_any != PA_ALSA_REQUIRED_IGNORE)
+            j->path->req_any_present = TRUE;
+    } else {
+        if (j->required != PA_ALSA_REQUIRED_IGNORE)
+            return -1;
+    }
+
+    return 0;
+}
+
 static pa_alsa_element* element_get(pa_alsa_path *p, const char *section, pa_bool_t prefixed) {
     pa_alsa_element *e;
 
@@ -1726,6 +1709,32 @@ finish:
     return e;
 }
 
+static pa_alsa_jack* jack_get(pa_alsa_path *p, const char *section) {
+    pa_alsa_jack *j;
+
+    if (!pa_startswith(section, "Jack "))
+        return NULL;
+    section += 5;
+
+    if (p->last_jack && pa_streq(p->last_jack->name, section))
+        return p->last_jack;
+
+    PA_LLIST_FOREACH(j, p->jacks)
+        if (pa_streq(j->name, section))
+            goto finish;
+
+    j = pa_xnew0(pa_alsa_jack, 1);
+    j->path = p;
+    j->name = pa_xstrdup(section);
+    j->alsa_name = pa_sprintf_malloc("%s Jack", section);
+    PA_LLIST_INSERT_AFTER(pa_alsa_jack, p->jacks, p->last_jack, j);
+
+finish:
+    p->last_jack = j;
+    return j;
+}
+
+
 static pa_alsa_option* option_get(pa_alsa_path *p, const char *section) {
     char *en;
     const char *on;
@@ -1949,13 +1958,15 @@ static int element_parse_required(
     pa_alsa_path *p = userdata;
     pa_alsa_element *e;
     pa_alsa_option *o;
+    pa_alsa_jack *j;
     pa_alsa_required_t req;
 
     pa_assert(p);
 
     e = element_get(p, section, TRUE);
     o = option_get(p, section);
-    if (!e && !o) {
+    j = jack_get(p, section);
+    if (!e && !o && !j) {
         pa_log("[%s:%u] Required makes no sense in '%s'", filename, line, section);
         return -1;
     }
@@ -1980,6 +1991,8 @@ static int element_parse_required(
             e->required_absent = req;
         if (o)
             o->required_absent = req;
+        if (j)
+            j->required_absent = req;
     }
     else if (pa_streq(lvalue, "required-any")) {
         if (e) {
@@ -1990,12 +2003,19 @@ static int element_parse_required(
             o->required_any = req;
             o->element->path->has_req_any = TRUE;
         }
+        if (j) {
+            j->required_any = req;
+            j->path->has_req_any = TRUE;
+        }
+
     }
     else {
         if (e)
             e->required = req;
         if (o)
             o->required = req;
+        if (j)
+            j->required = req;
     }
 
     return 0;
@@ -2564,8 +2584,9 @@ static void path_create_settings(pa_alsa_path *p) {
     element_create_settings(p->elements, NULL);
 }
 
-int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t ignore_dB) {
+int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, snd_hctl_t *hctl, pa_bool_t ignore_dB) {
     pa_alsa_element *e;
+    pa_alsa_jack *j;
     double min_dB[PA_CHANNEL_POSITION_MAX], max_dB[PA_CHANNEL_POSITION_MAX];
     pa_channel_position_t t;
     pa_channel_position_mask_t path_volume_channels = 0;
@@ -2582,6 +2603,15 @@ int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t ignore_dB) {
 
     pa_log_debug("Probing path '%s'", p->name);
 
+    PA_LLIST_FOREACH(j, p->jacks) {
+        if (jack_probe(j, hctl) < 0) {
+            p->supported = FALSE;
+            pa_log_debug("Probe of jack '%s' failed.", j->alsa_name);
+            return -1;
+        }
+        pa_log_debug("Probe of jack '%s' succeeded (%s)", j->alsa_name, j->has_control ? "found!" : "not found");
+    }
+
     PA_LLIST_FOREACH(e, p->elements) {
         if (element_probe(e, m) < 0) {
             p->supported = FALSE;
@@ -2676,6 +2706,12 @@ void pa_alsa_setting_dump(pa_alsa_setting *s) {
                  s->priority);
 }
 
+void pa_alsa_jack_dump(pa_alsa_jack *j) {
+    pa_assert(j);
+
+    pa_log_debug("Jack %s, alsa_name='%s', detection %s", j->name, j->alsa_name, j->has_control ? "possible" : "unavailable");
+}
+
 void pa_alsa_option_dump(pa_alsa_option *o) {
     pa_assert(o);
 
@@ -2711,6 +2747,7 @@ void pa_alsa_element_dump(pa_alsa_element *e) {
 
 void pa_alsa_path_dump(pa_alsa_path *p) {
     pa_alsa_element *e;
+    pa_alsa_jack *j;
     pa_alsa_setting *s;
     pa_assert(p);
 
@@ -2731,6 +2768,9 @@ void pa_alsa_path_dump(pa_alsa_path *p) {
     PA_LLIST_FOREACH(e, p->elements)
         pa_alsa_element_dump(e);
 
+    PA_LLIST_FOREACH(j, p->jacks)
+        pa_alsa_jack_dump(j);
+
     PA_LLIST_FOREACH(s, p->settings)
         pa_alsa_setting_dump(s);
 }
@@ -3084,11 +3124,32 @@ static void path_set_condense(pa_alsa_path_set *ps, snd_mixer_t *m) {
 
         PA_HASHMAP_FOREACH(p2, ps->paths, state2) {
             pa_alsa_element *ea, *eb;
+            pa_alsa_jack *ja, *jb;
             pa_bool_t is_subset = TRUE;
 
             if (p == p2)
                 continue;
 
+            /* If a has a jack that b does not have, a is not a subset */
+            PA_LLIST_FOREACH(ja, p->jacks) {
+                pa_bool_t exists = FALSE;
+
+                if (!ja->has_control)
+                    continue;
+
+                PA_LLIST_FOREACH(jb, p2->jacks) {
+                    if (jb->has_control && !strcmp(jb->alsa_name, ja->alsa_name)) {
+                        exists = TRUE;
+                        break;
+                    }
+                }
+
+                if (!exists) {
+                    is_subset = FALSE;
+                    break;
+                }
+            }
+
             /* Compare the elements of each set... */
             pa_assert_se(ea = p->elements);
             pa_assert_se(eb = p2->elements);
@@ -3721,6 +3782,7 @@ static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile,
     snd_pcm_t *pcm_handle;
     pa_alsa_path_set *ps;
     snd_mixer_t *mixer_handle;
+    snd_hctl_t *hctl_handle;
 
     if (direction == PA_ALSA_DIRECTION_OUTPUT) {
         if (m->output_path_set)
@@ -3739,8 +3801,8 @@ static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile,
 
     pa_assert(pcm_handle);
 
-    mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL);
-    if (!mixer_handle) {
+    mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL, &hctl_handle);
+    if (!mixer_handle || !hctl_handle) {
          /* Cannot open mixer, remove all entries */
         while (pa_hashmap_steal_first(ps->paths));
         return;
@@ -3748,7 +3810,7 @@ static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile,
 
 
     PA_HASHMAP_FOREACH(p, ps->paths, state) {
-        if (pa_alsa_path_probe(p, mixer_handle, m->profile_set->ignore_dB) < 0) {
+        if (pa_alsa_path_probe(p, mixer_handle, hctl_handle, m->profile_set->ignore_dB) < 0) {
             pa_hashmap_remove(ps->paths, p);
         }
     }
@@ -4404,6 +4466,7 @@ static pa_device_port* device_port_alsa_init(pa_hashmap *ports,
         data = PA_DEVICE_PORT_DATA(p);
         data->path = path;
         data->setting = setting;
+        path->port = p;
     }
 
     p->is_input |= path->direction == PA_ALSA_DIRECTION_ANY || path->direction == PA_ALSA_DIRECTION_INPUT;
diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h
index d4c7d65..1912ba1 100644
--- a/src/modules/alsa/alsa-mixer.h
+++ b/src/modules/alsa/alsa-mixer.h
@@ -38,6 +38,7 @@ typedef struct pa_alsa_mixer_pdata pa_alsa_mixer_pdata;
 typedef struct pa_alsa_setting pa_alsa_setting;
 typedef struct pa_alsa_option pa_alsa_option;
 typedef struct pa_alsa_element pa_alsa_element;
+typedef struct pa_alsa_jack pa_alsa_jack;
 typedef struct pa_alsa_path pa_alsa_path;
 typedef struct pa_alsa_path_set pa_alsa_path_set;
 typedef struct pa_alsa_mapping pa_alsa_mapping;
@@ -154,11 +155,27 @@ struct pa_alsa_element {
     pa_alsa_decibel_fix *db_fix;
 };
 
+struct pa_alsa_jack {
+    pa_alsa_path *path;
+    PA_LLIST_FIELDS(pa_alsa_jack);
+
+    char *name; /* E g "Headphone" */
+    char *alsa_name; /* E g "Headphone Jack" */
+    pa_bool_t has_control; /* is the jack itself present? */
+    pa_bool_t plugged_in; /* is this jack currently plugged in? */
+    snd_hctl_elem_t *hctl_elem; /* Jack detection handle */
+
+    pa_alsa_required_t required;
+    pa_alsa_required_t required_any;
+    pa_alsa_required_t required_absent;
+};
+
 /* A path wraps a series of elements into a single entity which can be
  * used to control it as if it had a single volume slider, a single
  * mute switch and a single list of selectable options. */
 struct pa_alsa_path {
     pa_alsa_direction_t direction;
+    pa_device_port* port;
 
     char *name;
     char *description;
@@ -181,9 +198,11 @@ struct pa_alsa_path {
     pa_alsa_element *last_element;
     pa_alsa_option *last_option;
     pa_alsa_setting *last_setting;
+    pa_alsa_jack *last_jack;
 
     PA_LLIST_HEAD(pa_alsa_element, elements);
     PA_LLIST_HEAD(pa_alsa_setting, settings);
+    PA_LLIST_HEAD(pa_alsa_jack, jacks);
 };
 
 /* A path set is simply a set of paths that are applicable to a
@@ -197,12 +216,12 @@ int pa_alsa_setting_select(pa_alsa_setting *s, snd_mixer_t *m);
 void pa_alsa_setting_dump(pa_alsa_setting *s);
 
 void pa_alsa_option_dump(pa_alsa_option *o);
-
+void pa_alsa_jack_dump(pa_alsa_jack *j);
 void pa_alsa_element_dump(pa_alsa_element *e);
 
 pa_alsa_path *pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa_direction_t direction);
 pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction);
-int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t ignore_dB);
+int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, snd_hctl_t *hctl, pa_bool_t ignore_dB);
 void pa_alsa_path_dump(pa_alsa_path *p);
 int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v);
 int pa_alsa_path_get_mute(pa_alsa_path *path, snd_mixer_t *m, pa_bool_t *muted);
@@ -299,11 +318,11 @@ void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, cons
 void pa_alsa_profile_set_free(pa_alsa_profile_set *s);
 void pa_alsa_profile_set_dump(pa_alsa_profile_set *s);
 
-snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device);
+snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device, snd_hctl_t **hctl);
 
 pa_alsa_fdlist *pa_alsa_fdlist_new(void);
 void pa_alsa_fdlist_free(pa_alsa_fdlist *fdl);
-int pa_alsa_fdlist_set_mixer(pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m);
+int pa_alsa_fdlist_set_handle(pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, snd_hctl_t *hctl_handle, pa_mainloop_api* m);
 
 /* Alternative for handling alsa mixer events in io-thread. */
 
diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index 846fd45..c88f4cf 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -1841,11 +1841,12 @@ static void set_sink_name(pa_sink_new_data *data, pa_modargs *ma, const char *de
 }
 
 static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, pa_bool_t ignore_dB) {
+    snd_hctl_t *hctl;
 
     if (!mapping && !element)
         return;
 
-    if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device))) {
+    if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device, &hctl))) {
         pa_log_info("Failed to find a working mixer device.");
         return;
     }
@@ -1855,7 +1856,7 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char
         if (!(u->mixer_path = pa_alsa_path_synthesize(element, PA_ALSA_DIRECTION_OUTPUT)))
             goto fail;
 
-        if (pa_alsa_path_probe(u->mixer_path, u->mixer_handle, ignore_dB) < 0)
+        if (pa_alsa_path_probe(u->mixer_path, u->mixer_handle, hctl, ignore_dB) < 0)
             goto fail;
 
         pa_log_debug("Probed mixer path %s:", u->mixer_path->name);
@@ -1946,7 +1947,7 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) {
             u->mixer_fdl = pa_alsa_fdlist_new();
             mixer_callback = ctl_mixer_callback;
 
-            if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, u->core->mainloop) < 0) {
+            if (pa_alsa_fdlist_set_handle(u->mixer_fdl, u->mixer_handle, NULL, u->core->mainloop) < 0) {
                 pa_log("Failed to initialize file descriptor monitoring");
                 return -1;
             }
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index bc6d723..3e59340 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -1579,11 +1579,12 @@ static void set_source_name(pa_source_new_data *data, pa_modargs *ma, const char
 }
 
 static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, pa_bool_t ignore_dB) {
+    snd_hctl_t *hctl;
 
     if (!mapping && !element)
         return;
 
-    if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device))) {
+    if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device, &hctl))) {
         pa_log_info("Failed to find a working mixer device.");
         return;
     }
@@ -1593,7 +1594,7 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char
         if (!(u->mixer_path = pa_alsa_path_synthesize(element, PA_ALSA_DIRECTION_INPUT)))
             goto fail;
 
-        if (pa_alsa_path_probe(u->mixer_path, u->mixer_handle, ignore_dB) < 0)
+        if (pa_alsa_path_probe(u->mixer_path, u->mixer_handle, hctl, ignore_dB) < 0)
             goto fail;
 
         pa_log_debug("Probed mixer path %s:", u->mixer_path->name);
@@ -1683,7 +1684,7 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) {
             u->mixer_fdl = pa_alsa_fdlist_new();
             mixer_callback = ctl_mixer_callback;
 
-            if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, u->core->mainloop) < 0) {
+            if (pa_alsa_fdlist_set_handle(u->mixer_fdl, u->mixer_handle, NULL, u->core->mainloop) < 0) {
                 pa_log("Failed to initialize file descriptor monitoring");
                 return -1;
             }
diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c
index d961fbc..742f38f 100644
--- a/src/modules/alsa/alsa-util.c
+++ b/src/modules/alsa/alsa-util.c
@@ -1440,3 +1440,128 @@ pa_bool_t pa_alsa_may_tsched(pa_bool_t want) {
 
     return TRUE;
 }
+
+snd_hctl_elem_t* pa_alsa_find_jack(snd_hctl_t *hctl, const char* jack_name)
+{
+    snd_ctl_elem_id_t *id;
+
+    snd_ctl_elem_id_alloca(&id);
+    snd_ctl_elem_id_clear(id);
+    snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD);
+    snd_ctl_elem_id_set_name(id, jack_name);
+
+    return snd_hctl_find_elem(hctl, id);
+}
+
+static int prepare_mixer(snd_mixer_t *mixer, const char *dev, snd_hctl_t **hctl) {
+    int err;
+
+    pa_assert(mixer);
+    pa_assert(dev);
+
+    if ((err = snd_mixer_attach(mixer, dev)) < 0) {
+        pa_log_info("Unable to attach to mixer %s: %s", dev, pa_alsa_strerror(err));
+        return -1;
+    }
+
+    /* Note: The hctl handle returned should not be freed.
+       It is closed/freed by alsa-lib on snd_mixer_close/free */
+    if (hctl && (err = snd_mixer_get_hctl(mixer, dev, hctl)) < 0) {
+        pa_log_info("Unable to get hctl of mixer %s: %s", dev, pa_alsa_strerror(err));
+        return -1;
+    }
+
+    if ((err = snd_mixer_selem_register(mixer, NULL, NULL)) < 0) {
+        pa_log_warn("Unable to register mixer: %s", pa_alsa_strerror(err));
+        return -1;
+    }
+
+    if ((err = snd_mixer_load(mixer)) < 0) {
+        pa_log_warn("Unable to load mixer: %s", pa_alsa_strerror(err));
+        return -1;
+    }
+
+    pa_log_info("Successfully attached to mixer '%s'", dev);
+    return 0;
+}
+
+snd_mixer_t *pa_alsa_open_mixer(int alsa_card_index, char **ctl_device, snd_hctl_t **hctl) {
+    int err;
+    snd_mixer_t *m;
+    char *md;
+    snd_pcm_info_t* info;
+    snd_pcm_info_alloca(&info);
+
+    if ((err = snd_mixer_open(&m, 0)) < 0) {
+        pa_log("Error opening mixer: %s", pa_alsa_strerror(err));
+        return NULL;
+    }
+
+    /* Then, try by card index */
+    md = pa_sprintf_malloc("hw:%i", alsa_card_index);
+    if (prepare_mixer(m, md, hctl) >= 0) {
+
+        if (ctl_device)
+            *ctl_device = md;
+        else
+            pa_xfree(md);
+
+        return m;
+    }
+
+    pa_xfree(md);
+
+    snd_mixer_close(m);
+    return NULL;
+}
+
+snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device, snd_hctl_t **hctl) {
+    int err;
+    snd_mixer_t *m;
+    const char *dev;
+    snd_pcm_info_t* info;
+    snd_pcm_info_alloca(&info);
+
+    pa_assert(pcm);
+
+    if ((err = snd_mixer_open(&m, 0)) < 0) {
+        pa_log("Error opening mixer: %s", pa_alsa_strerror(err));
+        return NULL;
+    }
+
+    /* First, try by name */
+    if ((dev = snd_pcm_name(pcm)))
+        if (prepare_mixer(m, dev, hctl) >= 0) {
+            if (ctl_device)
+                *ctl_device = pa_xstrdup(dev);
+
+            return m;
+        }
+
+    /* Then, try by card index */
+    if (snd_pcm_info(pcm, info) >= 0) {
+        char *md;
+        int card_idx;
+
+        if ((card_idx = snd_pcm_info_get_card(info)) >= 0) {
+
+            md = pa_sprintf_malloc("hw:%i", card_idx);
+
+            if (!dev || !pa_streq(dev, md))
+                if (prepare_mixer(m, md, hctl) >= 0) {
+
+                    if (ctl_device)
+                        *ctl_device = md;
+                    else
+                        pa_xfree(md);
+
+                    return m;
+                }
+
+            pa_xfree(md);
+        }
+    }
+
+    snd_mixer_close(m);
+    return NULL;
+}
diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h
index f8d0518..a4beed2 100644
--- a/src/modules/alsa/alsa-util.h
+++ b/src/modules/alsa/alsa-util.h
@@ -142,4 +142,8 @@ const char* pa_alsa_strerror(int errnum);
 
 pa_bool_t pa_alsa_may_tsched(pa_bool_t want);
 
+snd_hctl_elem_t* pa_alsa_find_jack(snd_hctl_t *hctl, const char* jack_name);
+
+snd_mixer_t *pa_alsa_open_mixer(int alsa_card_index, char **ctl_device, snd_hctl_t **hctl);
+
 #endif
diff --git a/src/modules/alsa/mixer/paths/analog-input-dock-mic.conf b/src/modules/alsa/mixer/paths/analog-input-dock-mic.conf
index afac273..240b5f0 100644
--- a/src/modules/alsa/mixer/paths/analog-input-dock-mic.conf
+++ b/src/modules/alsa/mixer/paths/analog-input-dock-mic.conf
@@ -22,6 +22,9 @@
 priority = 78
 name = analog-input-microphone-dock
 
+[Jack Dock Mic]
+required-any = any
+
 [Element Dock Mic Boost]
 required-any = any
 switch = select
diff --git a/src/modules/alsa/mixer/paths/analog-input-front-mic.conf b/src/modules/alsa/mixer/paths/analog-input-front-mic.conf
index 852e7e9..0b069f9 100644
--- a/src/modules/alsa/mixer/paths/analog-input-front-mic.conf
+++ b/src/modules/alsa/mixer/paths/analog-input-front-mic.conf
@@ -22,6 +22,9 @@
 priority = 85
 name = analog-input-microphone-front
 
+[Jack Front Mic]
+required-any = any
+
 [Element Front Mic Boost]
 required-any = any
 switch = select
diff --git a/src/modules/alsa/mixer/paths/analog-input-linein.conf b/src/modules/alsa/mixer/paths/analog-input-linein.conf
index a432d6e..6abafcb 100644
--- a/src/modules/alsa/mixer/paths/analog-input-linein.conf
+++ b/src/modules/alsa/mixer/paths/analog-input-linein.conf
@@ -21,6 +21,9 @@
 [General]
 priority = 81
 
+[Jack Line]
+required-any = any
+
 [Element Capture]
 switch = mute
 volume = merge
diff --git a/src/modules/alsa/mixer/paths/analog-input-mic.conf b/src/modules/alsa/mixer/paths/analog-input-mic.conf
index 4cebc4e..8aaf0cb 100644
--- a/src/modules/alsa/mixer/paths/analog-input-mic.conf
+++ b/src/modules/alsa/mixer/paths/analog-input-mic.conf
@@ -22,6 +22,9 @@
 priority = 87
 name = analog-input-microphone
 
+[Jack Mic]
+required-any = any
+
 [Element Mic Boost]
 required-any = any
 switch = select
diff --git a/src/modules/alsa/mixer/paths/analog-input-rear-mic.conf b/src/modules/alsa/mixer/paths/analog-input-rear-mic.conf
index e2b2671..1e6fa57 100644
--- a/src/modules/alsa/mixer/paths/analog-input-rear-mic.conf
+++ b/src/modules/alsa/mixer/paths/analog-input-rear-mic.conf
@@ -22,6 +22,9 @@
 priority = 82
 name = analog-input-microphone-rear
 
+[Jack Rear Mic]
+required-any = any
+
 [Element Rear Mic Boost]
 required-any = any
 switch = select
diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones.conf b/src/modules/alsa/mixer/paths/analog-output-headphones.conf
index 7f95f0a..2860f28 100644
--- a/src/modules/alsa/mixer/paths/analog-output-headphones.conf
+++ b/src/modules/alsa/mixer/paths/analog-output-headphones.conf
@@ -22,6 +22,9 @@
 priority = 90
 name = analog-output-headphones
 
+[Jack Headphone]
+required-any = any
+
 [Element Hardware Master]
 switch = mute
 volume = merge
@@ -39,7 +42,7 @@ switch = off
 volume = off
 
 [Element Headphone]
-required = any
+required-any = any
 switch = mute
 volume = merge
 override-map.1 = all
diff --git a/src/modules/alsa/mixer/paths/analog-output.conf.common b/src/modules/alsa/mixer/paths/analog-output.conf.common
index 9333800..160f222 100644
--- a/src/modules/alsa/mixer/paths/analog-output.conf.common
+++ b/src/modules/alsa/mixer/paths/analog-output.conf.common
@@ -70,7 +70,7 @@
 ; [Element ...]                          # For each element that we shall control
 ; required = ignore | switch | volume | enumeration | any     # If set, require this element to be of this kind and available,
 ;                                                             # otherwise don't consider this path valid for the card
-; required-any = ignore | switch | volume | enumeration | any # If set, at least one of the elements with required-any in this
+; required-any = ignore | switch | volume | enumeration | any # If set, at least one of the elements or jacks with required-any in this
 ;                                                             # path must be present, otherwise this path is invalid for the card
 ; required-absent = ignore | switch | volume                  # If set, require this element to not be of this kind and not
 ;                                                             # available, otherwise don't consider this path valid for the card
@@ -100,6 +100,12 @@
 ;                                        # channel mask. A channel mask may either be the name of a single channel, or the words "all-left",
 ;                                        # "all-right", "all-center", "all-front", "all-rear", and "all" to encode a specific subset of
 ;                                        # channels in a mask
+; [Jack ...]                        # For each jack that we will use for jack detection
+;                                   # The name 'Jack Foo' must match ALSA's 'Foo Jack' control.
+; required = ignore | any           # If not set to ignore, make the path invalid if this jack control is not present.
+; required-absent = ignore | any    # If not set to ignore, make the path invalid if this jack control is present.
+; required-any = ignore | any       # If not set to ignore, make the path invalid if no jack controls and no elements with
+;                                   # the required-any are present.
 
 [Element PCM]
 switch = mute
diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c
index 92483e1..c5bffcd 100644
--- a/src/modules/alsa/module-alsa-card.c
+++ b/src/modules/alsa/module-alsa-card.c
@@ -105,6 +105,12 @@ struct userdata {
     pa_module *module;
 
     char *device_id;
+    int alsa_card_index;
+
+    snd_mixer_t *mixer_handle;
+    snd_hctl_t *hctl_handle;
+    pa_hashmap *jacks;
+    pa_alsa_fdlist *mixer_fdl;
 
     pa_card *card;
 
@@ -267,6 +273,115 @@ static void init_profile(struct userdata *u) {
             am->source = pa_alsa_source_new(u->module, u->modargs, __FILE__, u->card, am);
 }
 
+static void report_port_state(pa_device_port *p, struct userdata *u)
+{
+    void *state;
+    pa_alsa_jack *jack;
+    pa_port_available_t pa = PA_PORT_AVAILABLE_UNKNOWN;
+
+    PA_HASHMAP_FOREACH(jack, u->jacks, state) {
+        pa_port_available_t cpa;
+
+        if (!jack->path)
+            continue;
+
+        if (p != jack->path->port)
+            continue;
+
+        cpa = jack->plugged_in ? PA_PORT_AVAILABLE_YES : PA_PORT_AVAILABLE_NO;
+
+        /* "Yes" and "no" trumphs "unknown" if we have more than one jack */
+        if (cpa == PA_PORT_AVAILABLE_UNKNOWN)
+            continue;
+
+        if ((cpa == PA_PORT_AVAILABLE_NO && pa == PA_PORT_AVAILABLE_YES) ||
+            (pa == PA_PORT_AVAILABLE_NO && cpa == PA_PORT_AVAILABLE_YES))
+            pa_log_warn("Availability of port '%s' is inconsistent!", p->name);
+        else
+            pa = cpa;
+    }
+
+    pa_device_port_set_available(p, pa);
+}
+
+static int report_jack_state(snd_hctl_elem_t *elem, unsigned int mask)
+{
+    struct userdata *u = snd_hctl_elem_get_callback_private(elem);
+    snd_ctl_elem_value_t *elem_value;
+    pa_bool_t plugged_in;
+    void *state;
+    pa_alsa_jack *jack;
+
+    pa_assert(u);
+
+    if (mask == SND_CTL_EVENT_MASK_REMOVE)
+        return 0;
+
+    snd_ctl_elem_value_alloca(&elem_value);
+    if (snd_hctl_elem_read(elem, elem_value) < 0) {
+        pa_log_warn("Failed to read jack detection from '%s'", pa_strnull(snd_hctl_elem_get_name(elem)));
+        return 0;
+    }
+
+    plugged_in = !!snd_ctl_elem_value_get_boolean(elem_value, 0);
+
+    pa_log_debug("Jack '%s' is now %s", pa_strnull(snd_hctl_elem_get_name(elem)), plugged_in ? "plugged in" : "unplugged");
+
+    PA_HASHMAP_FOREACH(jack, u->jacks, state)
+        if (jack->hctl_elem == elem) {
+            jack->plugged_in = plugged_in;
+            pa_assert(jack->path && jack->path->port);
+            report_port_state(jack->path->port, u);
+        }
+    return 0;
+}
+
+static void init_jacks(struct userdata *u) {
+    void *state;
+    pa_alsa_path* path;
+    pa_alsa_jack* jack;
+
+    u->jacks = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+
+    /* See if we have any jacks */
+    if (u->profile_set->output_paths)
+        PA_HASHMAP_FOREACH(path, u->profile_set->output_paths, state)
+            PA_LLIST_FOREACH(jack, path->jacks)
+                if (jack->has_control)
+                    pa_hashmap_put(u->jacks, jack, jack);
+
+    if (u->profile_set->input_paths)
+        PA_HASHMAP_FOREACH(path, u->profile_set->input_paths, state)
+            PA_LLIST_FOREACH(jack, path->jacks)
+                if (jack->has_control)
+                    pa_hashmap_put(u->jacks, jack, jack);
+
+    pa_log_debug("Found %d jacks.", pa_hashmap_size(u->jacks));
+
+    if (pa_hashmap_size(u->jacks) == 0)
+        return;
+
+    u->mixer_fdl = pa_alsa_fdlist_new();
+
+    u->mixer_handle = pa_alsa_open_mixer(u->alsa_card_index, NULL, &u->hctl_handle);
+    if (u->mixer_handle && pa_alsa_fdlist_set_handle(u->mixer_fdl, NULL, u->hctl_handle, u->core->mainloop) >= 0) {
+        PA_HASHMAP_FOREACH(jack, u->jacks, state) {
+            jack->hctl_elem = pa_alsa_find_jack(u->hctl_handle, jack->alsa_name);
+            if (!jack->hctl_elem) {
+                pa_log_warn("Jack '%s' seems to have disappeared.", jack->alsa_name);
+                jack->has_control = FALSE;
+                continue;
+            }
+            snd_hctl_elem_set_callback_private(jack->hctl_elem, u);
+            snd_hctl_elem_set_callback(jack->hctl_elem, report_jack_state);
+            report_jack_state(jack->hctl_elem, 0);
+        }
+
+    } else
+        pa_log("Failed to open hctl/mixer for jack detection");
+
+}
+
 static void set_card_name(pa_card_new_data *data, pa_modargs *ma, const char *device_id) {
     char *t;
     const char *n;
@@ -296,7 +411,6 @@ static void set_card_name(pa_card_new_data *data, pa_modargs *ma, const char *de
 int pa__init(pa_module *m) {
     pa_card_new_data data;
     pa_modargs *ma;
-    int alsa_card_index;
     pa_bool_t ignore_dB = FALSE;
     struct userdata *u;
     pa_reserve_wrapper *reserve = NULL;
@@ -325,8 +439,8 @@ int pa__init(pa_module *m) {
     u->device_id = pa_xstrdup(pa_modargs_get_value(ma, "device_id", DEFAULT_DEVICE_ID));
     u->modargs = ma;
 
-    if ((alsa_card_index = snd_card_get_index(u->device_id)) < 0) {
-        pa_log("Card '%s' doesn't exist: %s", u->device_id, pa_alsa_strerror(alsa_card_index));
+    if ((u->alsa_card_index = snd_card_get_index(u->device_id)) < 0) {
+        pa_log("Card '%s' doesn't exist: %s", u->device_id, pa_alsa_strerror(u->alsa_card_index));
         goto fail;
     }
 
@@ -343,7 +457,7 @@ int pa__init(pa_module *m) {
     }
 
 #ifdef HAVE_UDEV
-    fn = pa_udev_get_property(alsa_card_index, "PULSE_PROFILE_SET");
+    fn = pa_udev_get_property(u->alsa_card_index, "PULSE_PROFILE_SET");
 #endif
 
     if (pa_modargs_get_value(ma, "profile_set", NULL)) {
@@ -366,7 +480,7 @@ int pa__init(pa_module *m) {
     data.driver = __FILE__;
     data.module = m;
 
-    pa_alsa_init_proplist_card(m->core, data.proplist, alsa_card_index);
+    pa_alsa_init_proplist_card(m->core, data.proplist, u->alsa_card_index);
 
     pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_id);
     pa_alsa_init_description(data.proplist);
@@ -418,6 +532,7 @@ int pa__init(pa_module *m) {
     u->card->set_profile = card_set_profile;
 
     init_profile(u);
+    init_jacks(u);
 
     if (reserve)
         pa_reserve_wrapper_unref(reserve);
@@ -469,6 +584,13 @@ void pa__done(pa_module*m) {
     if (!(u = m->userdata))
         goto finish;
 
+    if (u->mixer_fdl)
+        pa_alsa_fdlist_free(u->mixer_fdl);
+    if (u->mixer_handle)
+        snd_mixer_close(u->mixer_handle);
+    if (u->jacks)
+        pa_hashmap_free(u->jacks, NULL, NULL);
+
     if (u->card && u->card->sinks) {
         pa_sink *s;
 



More information about the pulseaudio-commits mailing list