[gnome-settings-daemon] Bug 589825 – daemon is using 100% of CPU



commit c2179bd1a3c18fa56675c81f58df9e9c8896c8ab
Author: Bastien Nocera <hadess hadess net>
Date:   Tue Jul 28 11:17:43 2009 +0100

    Bug 589825 â?? daemon is using 100% of CPU
    
    Update gnome-volume-control code from gnome-media, update
    requirements to PulseAudio 0.9.15.

 configure.ac                                       |    2 +-
 plugins/media-keys/cut-n-paste/Makefile.am         |    2 +
 plugins/media-keys/cut-n-paste/gvc-mixer-card.c    |  493 ++++++++++++++++++++
 plugins/media-keys/cut-n-paste/gvc-mixer-card.h    |   86 ++++
 plugins/media-keys/cut-n-paste/gvc-mixer-control.c |  332 +++++++++++++-
 plugins/media-keys/cut-n-paste/gvc-mixer-control.h |    8 +
 plugins/media-keys/cut-n-paste/gvc-mixer-sink.c    |   32 ++
 plugins/media-keys/cut-n-paste/gvc-mixer-source.c  |   32 ++
 plugins/media-keys/cut-n-paste/gvc-mixer-stream.c  |  129 +++++
 plugins/media-keys/cut-n-paste/gvc-mixer-stream.h  |   20 +-
 10 files changed, 1118 insertions(+), 18 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 353250a..69cc93d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -246,7 +246,7 @@ AC_ARG_ENABLE(pulse,
        [WANT_PULSE=yes]) dnl Default value
 
 if test x$WANT_PULSE = xyes ; then
-       PA_REQUIRED_VERSION=0.9.12
+       PA_REQUIRED_VERSION=0.9.15
        PKG_CHECK_MODULES(PULSE, glib-2.0 libpulse >= $PA_REQUIRED_VERSION libpulse-mainloop-glib >= $PA_REQUIRED_VERSION,
              [have_pulse=true
               AC_DEFINE(HAVE_PULSE, 1, [Define if PULSE sound server should be used])],
diff --git a/plugins/media-keys/cut-n-paste/Makefile.am b/plugins/media-keys/cut-n-paste/Makefile.am
index f7f56f0..bc59a10 100644
--- a/plugins/media-keys/cut-n-paste/Makefile.am
+++ b/plugins/media-keys/cut-n-paste/Makefile.am
@@ -18,6 +18,8 @@ libgvc_la_SOURCES =			\
 	gvc-mixer-stream.c		\
 	gvc-channel-map.h		\
 	gvc-channel-map.c		\
+	gvc-mixer-card.c		\
+	gvc-mixer-card.h		\
 	gvc-mixer-sink.h		\
 	gvc-mixer-sink.c		\
 	gvc-mixer-source.h		\
diff --git a/plugins/media-keys/cut-n-paste/gvc-mixer-card.c b/plugins/media-keys/cut-n-paste/gvc-mixer-card.c
new file mode 100644
index 0000000..9037ff2
--- /dev/null
+++ b/plugins/media-keys/cut-n-paste/gvc-mixer-card.c
@@ -0,0 +1,493 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 William Jon McCann
+ * Copyright (C) 2009 Bastien Nocera
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "gvc-mixer-card.h"
+
+#define GVC_MIXER_CARD_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_CARD, GvcMixerCardPrivate))
+
+static guint32 card_serial = 1;
+
+struct GvcMixerCardPrivate
+{
+        pa_context    *pa_context;
+        guint          id;
+        guint          index;
+        char          *name;
+        char          *icon_name;
+        char          *profile;
+        char          *target_profile;
+        char          *human_profile;
+        GList         *profiles;
+};
+
+enum
+{
+        PROP_0,
+        PROP_ID,
+        PROP_PA_CONTEXT,
+        PROP_INDEX,
+        PROP_NAME,
+        PROP_ICON_NAME,
+        PROP_PROFILE,
+        PROP_HUMAN_PROFILE,
+};
+
+static void     gvc_mixer_card_class_init (GvcMixerCardClass *klass);
+static void     gvc_mixer_card_init       (GvcMixerCard      *mixer_card);
+static void     gvc_mixer_card_finalize   (GObject            *object);
+
+G_DEFINE_TYPE (GvcMixerCard, gvc_mixer_card, G_TYPE_OBJECT)
+
+static guint32
+get_next_card_serial (void)
+{
+        guint32 serial;
+
+        serial = card_serial++;
+
+        if ((gint32)card_serial < 0) {
+                card_serial = 1;
+        }
+
+        return serial;
+}
+
+pa_context *
+gvc_mixer_card_get_pa_context (GvcMixerCard *card)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_CARD (card), 0);
+        return card->priv->pa_context;
+}
+
+guint
+gvc_mixer_card_get_index (GvcMixerCard *card)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_CARD (card), 0);
+        return card->priv->index;
+}
+
+guint
+gvc_mixer_card_get_id (GvcMixerCard *card)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_CARD (card), 0);
+        return card->priv->id;
+}
+
+const char *
+gvc_mixer_card_get_name (GvcMixerCard *card)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_CARD (card), NULL);
+        return card->priv->name;
+}
+
+gboolean
+gvc_mixer_card_set_name (GvcMixerCard *card,
+                         const char     *name)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE);
+
+        g_free (card->priv->name);
+        card->priv->name = g_strdup (name);
+        g_object_notify (G_OBJECT (card), "name");
+
+        return TRUE;
+}
+
+const char *
+gvc_mixer_card_get_icon_name (GvcMixerCard *card)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_CARD (card), NULL);
+        return card->priv->icon_name;
+}
+
+gboolean
+gvc_mixer_card_set_icon_name (GvcMixerCard *card,
+                              const char     *icon_name)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE);
+
+        g_free (card->priv->icon_name);
+        card->priv->icon_name = g_strdup (icon_name);
+        g_object_notify (G_OBJECT (card), "icon-name");
+
+        return TRUE;
+}
+
+GvcMixerCardProfile *
+gvc_mixer_card_get_profile (GvcMixerCard *card)
+{
+        GList *l;
+
+        g_return_val_if_fail (GVC_IS_MIXER_CARD (card), NULL);
+        g_return_val_if_fail (card->priv->profiles != NULL, FALSE);
+
+        for (l = card->priv->profiles; l != NULL; l = l->next) {
+                GvcMixerCardProfile *p = l->data;
+                if (g_str_equal (card->priv->profile, p->profile)) {
+                        return p;
+                }
+        }
+
+        g_assert_not_reached ();
+
+        return NULL;
+}
+
+gboolean
+gvc_mixer_card_set_profile (GvcMixerCard *card,
+                            const char     *profile)
+{
+        GList *l;
+
+        g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE);
+        g_return_val_if_fail (card->priv->profiles != NULL, FALSE);
+
+        g_free (card->priv->profile);
+        card->priv->profile = g_strdup (profile);
+
+        g_free (card->priv->human_profile);
+        card->priv->human_profile = NULL;
+
+        for (l = card->priv->profiles; l != NULL; l = l->next) {
+                GvcMixerCardProfile *p = l->data;
+                if (g_str_equal (card->priv->profile, p->profile)) {
+                        card->priv->human_profile = g_strdup (p->human_profile);
+                        break;
+                }
+        }
+
+        g_object_notify (G_OBJECT (card), "profile");
+
+        return TRUE;
+}
+
+static void
+_pa_context_set_card_profile_by_index_cb (pa_context                       *context,
+                                          int                               success,
+                                          void                             *userdata)
+{
+        GvcMixerCard *card = GVC_MIXER_CARD (userdata);
+
+        g_assert (card->priv->target_profile);
+
+        if (success > 0) {
+                gvc_mixer_card_set_profile (card, card->priv->target_profile);
+        } else {
+                g_debug ("Failed to switch profile on '%s' from '%s' to '%s'",
+                         card->priv->name,
+                         card->priv->profile,
+                         card->priv->target_profile);
+        }
+        g_free (card->priv->target_profile);
+        card->priv->target_profile = NULL;
+}
+
+gboolean
+gvc_mixer_card_change_profile (GvcMixerCard *card,
+                               const char *profile)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE);
+        g_return_val_if_fail (card->priv->profiles != NULL, FALSE);
+
+        /* Same profile, or already requested? */
+        if (g_strcmp0 (card->priv->profile, profile) == 0)
+                return TRUE;
+        if (g_strcmp0 (profile, card->priv->target_profile) == 0)
+                return TRUE;
+
+        if (card->priv->profile != NULL) {
+                pa_operation *o;
+
+                g_free (card->priv->target_profile);
+                card->priv->target_profile = g_strdup (profile);
+
+                o = pa_context_set_card_profile_by_index (card->priv->pa_context,
+                                                          card->priv->index,
+                                                          card->priv->target_profile,
+                                                          _pa_context_set_card_profile_by_index_cb,
+                                                          card);
+
+                if (o == NULL) {
+                        g_warning ("pa_context_set_card_profile_by_index() failed");
+                        return FALSE;
+                }
+
+                pa_operation_unref (o);
+        } else {
+                g_assert (card->priv->human_profile == NULL);
+                card->priv->profile = g_strdup (profile);
+        }
+
+        return TRUE;
+}
+
+const GList *
+gvc_mixer_card_get_profiles (GvcMixerCard *card)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE);
+        return card->priv->profiles;
+}
+
+static int
+sort_profiles (GvcMixerCardProfile *a,
+               GvcMixerCardProfile *b)
+{
+        if (a->priority == b->priority)
+                return 0;
+        if (a->priority > b->priority)
+                return 1;
+        return -1;
+}
+
+gboolean
+gvc_mixer_card_set_profiles (GvcMixerCard *card,
+                             GList        *profiles)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE);
+        g_return_val_if_fail (card->priv->profiles == NULL, FALSE);
+
+        card->priv->profiles = g_list_sort (profiles, (GCompareFunc) sort_profiles);
+
+        return TRUE;
+}
+
+static void
+gvc_mixer_card_set_property (GObject       *object,
+                             guint          prop_id,
+                             const GValue  *value,
+                             GParamSpec    *pspec)
+{
+        GvcMixerCard *self = GVC_MIXER_CARD (object);
+
+        switch (prop_id) {
+        case PROP_PA_CONTEXT:
+                self->priv->pa_context = g_value_get_pointer (value);
+                break;
+        case PROP_INDEX:
+                self->priv->index = g_value_get_ulong (value);
+                break;
+        case PROP_ID:
+                self->priv->id = g_value_get_ulong (value);
+                break;
+        case PROP_NAME:
+                gvc_mixer_card_set_name (self, g_value_get_string (value));
+                break;
+        case PROP_ICON_NAME:
+                gvc_mixer_card_set_icon_name (self, g_value_get_string (value));
+                break;
+        case PROP_PROFILE:
+                gvc_mixer_card_set_profile (self, g_value_get_string (value));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+gvc_mixer_card_get_property (GObject     *object,
+                             guint        prop_id,
+                             GValue      *value,
+                             GParamSpec  *pspec)
+{
+        GvcMixerCard *self = GVC_MIXER_CARD (object);
+
+        switch (prop_id) {
+        case PROP_PA_CONTEXT:
+                g_value_set_pointer (value, self->priv->pa_context);
+                break;
+        case PROP_INDEX:
+                g_value_set_ulong (value, self->priv->index);
+                break;
+        case PROP_ID:
+                g_value_set_ulong (value, self->priv->id);
+                break;
+        case PROP_NAME:
+                g_value_set_string (value, self->priv->name);
+                break;
+        case PROP_ICON_NAME:
+                g_value_set_string (value, self->priv->icon_name);
+                break;
+        case PROP_PROFILE:
+                g_value_set_string (value, self->priv->profile);
+                break;
+        case PROP_HUMAN_PROFILE:
+                g_value_set_string (value, self->priv->human_profile);
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static GObject *
+gvc_mixer_card_constructor (GType                  type,
+                            guint                  n_construct_properties,
+                            GObjectConstructParam *construct_params)
+{
+        GObject       *object;
+        GvcMixerCard *self;
+
+        object = G_OBJECT_CLASS (gvc_mixer_card_parent_class)->constructor (type, n_construct_properties, construct_params);
+
+        self = GVC_MIXER_CARD (object);
+
+        self->priv->id = get_next_card_serial ();
+
+        return object;
+}
+
+static void
+gvc_mixer_card_class_init (GvcMixerCardClass *klass)
+{
+        GObjectClass   *gobject_class = G_OBJECT_CLASS (klass);
+
+        gobject_class->constructor = gvc_mixer_card_constructor;
+        gobject_class->finalize = gvc_mixer_card_finalize;
+
+        gobject_class->set_property = gvc_mixer_card_set_property;
+        gobject_class->get_property = gvc_mixer_card_get_property;
+
+        g_object_class_install_property (gobject_class,
+                                         PROP_INDEX,
+                                         g_param_spec_ulong ("index",
+                                                             "Index",
+                                                             "The index for this card",
+                                                             0, G_MAXULONG, 0,
+                                                             G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
+        g_object_class_install_property (gobject_class,
+                                         PROP_ID,
+                                         g_param_spec_ulong ("id",
+                                                             "id",
+                                                             "The id for this card",
+                                                             0, G_MAXULONG, 0,
+                                                             G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
+        g_object_class_install_property (gobject_class,
+                                         PROP_PA_CONTEXT,
+                                         g_param_spec_pointer ("pa-context",
+                                                               "PulseAudio context",
+                                                               "The PulseAudio context for this card",
+                                                               G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
+        g_object_class_install_property (gobject_class,
+                                         PROP_NAME,
+                                         g_param_spec_string ("name",
+                                                              "Name",
+                                                              "Name to display for this card",
+                                                              NULL,
+                                                              G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
+        g_object_class_install_property (gobject_class,
+                                         PROP_ICON_NAME,
+                                         g_param_spec_string ("icon-name",
+                                                              "Icon Name",
+                                                              "Name of icon to display for this card",
+                                                              NULL,
+                                                              G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
+        g_object_class_install_property (gobject_class,
+                                         PROP_PROFILE,
+                                         g_param_spec_string ("profile",
+                                                              "Profile",
+                                                              "Name of current profile for this card",
+                                                              NULL,
+                                                              G_PARAM_READWRITE));
+        g_object_class_install_property (gobject_class,
+                                         PROP_HUMAN_PROFILE,
+                                         g_param_spec_string ("human-profile",
+                                                              "Profile (Human readable)",
+                                                              "Name of current profile for this card in human readable form",
+                                                              NULL,
+                                                              G_PARAM_READABLE));
+
+        g_type_class_add_private (klass, sizeof (GvcMixerCardPrivate));
+}
+
+static void
+gvc_mixer_card_init (GvcMixerCard *card)
+{
+        card->priv = GVC_MIXER_CARD_GET_PRIVATE (card);
+}
+
+GvcMixerCard *
+gvc_mixer_card_new (pa_context *context,
+                    guint       index)
+{
+        GObject *object;
+
+        object = g_object_new (GVC_TYPE_MIXER_CARD,
+                               "index", index,
+                               "pa-context", context,
+                               NULL);
+        return GVC_MIXER_CARD (object);
+}
+
+static void
+free_profile (GvcMixerCardProfile *p)
+{
+        g_free (p->profile);
+        g_free (p->human_profile);
+        g_free (p->status);
+        g_free (p);
+}
+
+static void
+gvc_mixer_card_finalize (GObject *object)
+{
+        GvcMixerCard *mixer_card;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GVC_IS_MIXER_CARD (object));
+
+        mixer_card = GVC_MIXER_CARD (object);
+
+        g_return_if_fail (mixer_card->priv != NULL);
+
+        g_free (mixer_card->priv->name);
+        mixer_card->priv->name = NULL;
+
+        g_free (mixer_card->priv->icon_name);
+        mixer_card->priv->icon_name = NULL;
+
+        g_free (mixer_card->priv->target_profile);
+        mixer_card->priv->target_profile = NULL;
+
+        g_free (mixer_card->priv->profile);
+        mixer_card->priv->profile = NULL;
+
+        g_free (mixer_card->priv->human_profile);
+        mixer_card->priv->human_profile = NULL;
+
+        g_list_foreach (mixer_card->priv->profiles, (GFunc) free_profile, NULL);
+        g_list_free (mixer_card->priv->profiles);
+        mixer_card->priv->profiles = NULL;
+
+        G_OBJECT_CLASS (gvc_mixer_card_parent_class)->finalize (object);
+}
+
diff --git a/plugins/media-keys/cut-n-paste/gvc-mixer-card.h b/plugins/media-keys/cut-n-paste/gvc-mixer-card.h
new file mode 100644
index 0000000..3da3edb
--- /dev/null
+++ b/plugins/media-keys/cut-n-paste/gvc-mixer-card.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GVC_MIXER_CARD_H
+#define __GVC_MIXER_CARD_H
+
+#include <glib-object.h>
+#include <pulse/pulseaudio.h>
+
+G_BEGIN_DECLS
+
+#define GVC_TYPE_MIXER_CARD         (gvc_mixer_card_get_type ())
+#define GVC_MIXER_CARD(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_CARD, GvcMixerCard))
+#define GVC_MIXER_CARD_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_CARD, GvcMixerCardClass))
+#define GVC_IS_MIXER_CARD(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_CARD))
+#define GVC_IS_MIXER_CARD_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_CARD))
+#define GVC_MIXER_CARD_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_CARD, GvcMixerCardClass))
+
+typedef struct GvcMixerCardPrivate GvcMixerCardPrivate;
+
+typedef struct
+{
+        GObject                parent;
+        GvcMixerCardPrivate   *priv;
+} GvcMixerCard;
+
+typedef struct
+{
+        GObjectClass           parent_class;
+
+        /* vtable */
+} GvcMixerCardClass;
+
+typedef struct
+{
+        char *profile;
+        char *human_profile;
+        char *status;
+        guint priority;
+} GvcMixerCardProfile;
+
+GType                 gvc_mixer_card_get_type          (void);
+GvcMixerCard *        gvc_mixer_card_new               (pa_context   *context,
+                                                        guint         index);
+
+guint                 gvc_mixer_card_get_id            (GvcMixerCard *card);
+guint                 gvc_mixer_card_get_index         (GvcMixerCard *card);
+const char *          gvc_mixer_card_get_name          (GvcMixerCard *card);
+const char *          gvc_mixer_card_get_icon_name     (GvcMixerCard *card);
+GvcMixerCardProfile * gvc_mixer_card_get_profile       (GvcMixerCard *card);
+const GList *         gvc_mixer_card_get_profiles      (GvcMixerCard *card);
+
+pa_context *          gvc_mixer_card_get_pa_context    (GvcMixerCard *card);
+gboolean              gvc_mixer_card_change_profile    (GvcMixerCard *card,
+                                                        const char *profile);
+
+/* private */
+gboolean              gvc_mixer_card_set_name          (GvcMixerCard *card,
+                                                        const char   *name);
+gboolean              gvc_mixer_card_set_icon_name     (GvcMixerCard *card,
+                                                        const char   *name);
+gboolean              gvc_mixer_card_set_profile       (GvcMixerCard *card,
+                                                        const char   *profile);
+gboolean              gvc_mixer_card_set_profiles      (GvcMixerCard *card,
+                                                        GList        *profiles);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_CARD_H */
diff --git a/plugins/media-keys/cut-n-paste/gvc-mixer-control.c b/plugins/media-keys/cut-n-paste/gvc-mixer-control.c
index 92b0286..0b5feee 100644
--- a/plugins/media-keys/cut-n-paste/gvc-mixer-control.c
+++ b/plugins/media-keys/cut-n-paste/gvc-mixer-control.c
@@ -39,9 +39,12 @@
 #include "gvc-mixer-sink-input.h"
 #include "gvc-mixer-source-output.h"
 #include "gvc-mixer-event-role.h"
+#include "gvc-mixer-card.h"
 
 #define GVC_MIXER_CONTROL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_CONTROL, GvcMixerControlPrivate))
 
+#define RECONNECT_DELAY 5
+
 struct GvcMixerControlPrivate
 {
         pa_glib_mainloop *pa_mainloop;
@@ -66,6 +69,7 @@ struct GvcMixerControlPrivate
         GHashTable       *sink_inputs; /* routable output streams */
         GHashTable       *source_outputs; /* routable input streams */
         GHashTable       *clients;
+        GHashTable       *cards;
 
         GvcMixerStream   *new_default_stream; /* new default stream, used in gvc_mixer_control_set_default_sink () */
 };
@@ -75,6 +79,8 @@ enum {
         READY,
         STREAM_ADDED,
         STREAM_REMOVED,
+        CARD_ADDED,
+        CARD_REMOVED,
         DEFAULT_SINK_CHANGED,
         DEFAULT_SOURCE_CHANGED,
         LAST_SIGNAL
@@ -240,17 +246,30 @@ gvc_mixer_control_get_default_source (GvcMixerControl *control)
         return stream;
 }
 
+static gpointer
+gvc_mixer_control_lookup_id (GHashTable *hash_table,
+                             guint       id)
+{
+        return g_hash_table_lookup (hash_table,
+                                    GUINT_TO_POINTER (id));
+}
+
 GvcMixerStream *
 gvc_mixer_control_lookup_stream_id (GvcMixerControl *control,
                                     guint            id)
 {
-        GvcMixerStream *stream;
+        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+
+        return gvc_mixer_control_lookup_id (control->priv->all_streams, id);
+}
 
+GvcMixerCard *
+gvc_mixer_control_lookup_card_id (GvcMixerControl *control,
+                                  guint            id)
+{
         g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 
-        stream = g_hash_table_lookup (control->priv->all_streams,
-                                      GUINT_TO_POINTER (id));
-        return stream;
+        return gvc_mixer_control_lookup_id (control->priv->cards, id);
 }
 
 static void
@@ -264,6 +283,50 @@ listify_hash_values_hfunc (gpointer key,
 }
 
 static int
+gvc_name_collate (const char *namea,
+                  const char *nameb)
+{
+        if (nameb == NULL && namea == NULL)
+                return 0;
+        if (nameb == NULL)
+                return 1;
+        if (namea == NULL)
+                return -1;
+
+        return g_utf8_collate (namea, nameb);
+}
+
+static int
+gvc_card_collate (GvcMixerCard *a,
+                  GvcMixerCard *b)
+{
+        const char *namea;
+        const char *nameb;
+
+        g_return_val_if_fail (a == NULL || GVC_IS_MIXER_CARD (a), 0);
+        g_return_val_if_fail (b == NULL || GVC_IS_MIXER_CARD (b), 0);
+
+        namea = gvc_mixer_card_get_name (a);
+        nameb = gvc_mixer_card_get_name (b);
+
+        return gvc_name_collate (namea, nameb);
+}
+
+GSList *
+gvc_mixer_control_get_cards (GvcMixerControl *control)
+{
+        GSList *retval;
+
+        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+
+        retval = NULL;
+        g_hash_table_foreach (control->priv->cards,
+                              listify_hash_values_hfunc,
+                              &retval);
+        return g_slist_sort (retval, (GCompareFunc) gvc_card_collate);
+}
+
+static int
 gvc_stream_collate (GvcMixerStream *a,
                     GvcMixerStream *b)
 {
@@ -276,14 +339,7 @@ gvc_stream_collate (GvcMixerStream *a,
         namea = gvc_mixer_stream_get_name (a);
         nameb = gvc_mixer_stream_get_name (b);
 
-        if (nameb == NULL && namea == NULL)
-                return 0;
-        if (nameb == NULL)
-                return 1;
-        if (namea == NULL)
-                return -1;
-
-        return g_utf8_collate (namea, nameb);
+        return gvc_name_collate (namea, nameb);
 }
 
 GSList *
@@ -579,10 +635,27 @@ update_sink (GvcMixerControl    *control,
         stream = g_hash_table_lookup (control->priv->sinks,
                                       GUINT_TO_POINTER (info->index));
         if (stream == NULL) {
+#if PA_MICRO > 15
+                GList *list = NULL;
+                guint i;
+#endif /* PA_MICRO > 15 */
+
                 map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
                 stream = gvc_mixer_sink_new (control->priv->pa_context,
                                              info->index,
                                              map);
+#if PA_MICRO > 15
+                for (i = 0; i < info->n_ports; i++) {
+                        GvcMixerStreamPort *port;
+
+                        port = g_new0 (GvcMixerStreamPort, 1);
+                        port->port = g_strdup (info->ports[i]->name);
+                        port->human_port = g_strdup (info->ports[i]->description);
+                        port->priority = info->ports[i]->priority;
+                        list = g_list_prepend (list, port);
+                }
+                gvc_mixer_stream_set_ports (stream, list);
+#endif /* PA_MICRO > 15 */
                 g_object_unref (map);
                 is_new = TRUE;
         } else if (gvc_mixer_stream_is_running (stream)) {
@@ -598,6 +671,10 @@ update_sink (GvcMixerControl    *control,
         gvc_mixer_stream_set_volume (stream, (guint)max_volume);
         gvc_mixer_stream_set_is_muted (stream, info->mute);
         gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SINK_DECIBEL_VOLUME));
+#if PA_MICRO > 15
+        if (info->active_port != NULL)
+                gvc_mixer_stream_set_port (stream, info->active_port->name);
+#endif /* PA_MICRO > 15 */
 
         if (is_new) {
                 g_hash_table_insert (control->priv->sinks,
@@ -642,11 +719,29 @@ update_source (GvcMixerControl      *control,
         stream = g_hash_table_lookup (control->priv->sources,
                                       GUINT_TO_POINTER (info->index));
         if (stream == NULL) {
+#if PA_MICRO > 15
+                GList *list = NULL;
+                guint i;
+#endif /* PA_MICRO > 15 */
                 GvcChannelMap *map;
+
                 map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
                 stream = gvc_mixer_source_new (control->priv->pa_context,
                                                info->index,
                                                map);
+#if PA_MICRO > 15
+                for (i = 0; i < info->n_ports; i++) {
+                        GvcMixerStreamPort *port;
+
+                        port = g_new0 (GvcMixerStreamPort, 1);
+                        port->port = g_strdup (info->ports[i]->name);
+                        port->human_port = g_strdup (info->ports[i]->description);
+                        port->priority = info->ports[i]->priority;
+                        list = g_list_prepend (list, port);
+                }
+                gvc_mixer_stream_set_ports (stream, list);
+#endif /* PA_MICRO > 15 */
+
                 g_object_unref (map);
                 is_new = TRUE;
         } else if (gvc_mixer_stream_is_running (stream)) {
@@ -664,6 +759,10 @@ update_source (GvcMixerControl      *control,
         gvc_mixer_stream_set_is_muted (stream, info->mute);
         gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SOURCE_DECIBEL_VOLUME));
         gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume);
+#if PA_MICRO > 15
+        if (info->active_port != NULL)
+                gvc_mixer_stream_set_port (stream, info->active_port->name);
+#endif /* PA_MICRO > 15 */
 
         if (is_new) {
                 g_hash_table_insert (control->priv->sources,
@@ -872,6 +971,117 @@ update_client (GvcMixerControl      *control,
                              g_strdup (info->name));
 }
 
+static char *
+card_num_streams_to_status (guint sinks,
+                            guint sources)
+{
+        char *sinks_str;
+        char *sources_str;
+        char *ret;
+
+        if (sinks == 0 && sources == 0) {
+                /* translators:
+                 * The device has been disabled */
+                return g_strdup (_("Disabled"));
+        }
+        if (sinks == 0) {
+                sinks_str = NULL;
+        } else {
+                /* translators:
+                 * The number of sound outputs on a particular device */
+                sinks_str = g_strdup_printf (ngettext ("%u Output",
+                                                       "%u Outputs",
+                                                       sinks),
+                                             sinks);
+        }
+        if (sources == 0) {
+                sources_str = NULL;
+        } else {
+                /* translators:
+                 * The number of sound inputs on a particular device */
+                sources_str = g_strdup_printf (ngettext ("%u Input",
+                                                         "%u Inputs",
+                                                         sources),
+                                               sources);
+        }
+        if (sources_str == NULL)
+                return sinks_str;
+        if (sinks_str == NULL)
+                return sources_str;
+        ret = g_strdup_printf ("%s / %s", sinks_str, sources_str);
+        g_free (sinks_str);
+        g_free (sources_str);
+        return ret;
+}
+
+static void
+update_card (GvcMixerControl      *control,
+             const pa_card_info   *info)
+{
+        GvcMixerCard *card;
+        gboolean      is_new;
+#if 1
+        guint i;
+        const char *key;
+        void *state;
+
+        g_debug ("Udpating card %s (index: %u driver: %s):",
+                 info->name, info->index, info->driver);
+
+        for (i = 0; i < info->n_profiles; i++) {
+                struct pa_card_profile_info pi = info->profiles[i];
+                gboolean is_default;
+
+                is_default = (g_strcmp0 (pi.name, info->active_profile->name) == 0);
+                g_debug ("\tProfile '%s': %d sources %d sinks%s",
+                         pi.name, pi.n_sources, pi.n_sinks,
+                         is_default ? " (Current)" : "");
+        }
+        state = NULL;
+        key = pa_proplist_iterate (info->proplist, &state);
+        while (key != NULL) {
+                g_debug ("\tProperty: '%s' = '%s'",
+                        key, pa_proplist_gets (info->proplist, key));
+                key = pa_proplist_iterate (info->proplist, &state);
+        }
+#endif
+        card = g_hash_table_lookup (control->priv->cards,
+                                    GUINT_TO_POINTER (info->index));
+        if (card == NULL) {
+                GList *list = NULL;
+
+                for (i = 0; i < info->n_profiles; i++) {
+                        struct pa_card_profile_info pi = info->profiles[i];
+                        GvcMixerCardProfile *profile;
+
+                        profile = g_new0 (GvcMixerCardProfile, 1);
+                        profile->profile = g_strdup (pi.name);
+                        profile->human_profile = g_strdup (pi.description);
+                        profile->status = card_num_streams_to_status (pi.n_sinks, pi.n_sources);
+                        profile->priority = pi.priority;
+                        list = g_list_prepend (list, profile);
+                }
+                card = gvc_mixer_card_new (control->priv->pa_context,
+                                           info->index);
+                gvc_mixer_card_set_profiles (card, list);
+                is_new = TRUE;
+        }
+
+        gvc_mixer_card_set_name (card, pa_proplist_gets (info->proplist, "device.description"));
+        gvc_mixer_card_set_icon_name (card, pa_proplist_gets (info->proplist, "device.icon_name"));
+        gvc_mixer_card_set_profile (card, info->active_profile->name);
+
+        if (is_new) {
+                g_hash_table_insert (control->priv->cards,
+                                     GUINT_TO_POINTER (info->index),
+                                     g_object_ref (card));
+        }
+        g_signal_emit (G_OBJECT (control),
+                       signals[CARD_ADDED],
+                       0,
+                       info->index);
+}
+
 static void
 _pa_context_get_sink_info_cb (pa_context         *context,
                               const pa_sink_info *i,
@@ -897,7 +1107,6 @@ _pa_context_get_sink_info_cb (pa_context         *context,
         update_sink (control, i);
 }
 
-
 static void
 _pa_context_get_source_info_cb (pa_context           *context,
                                 const pa_source_info *i,
@@ -999,6 +1208,30 @@ _pa_context_get_client_info_cb (pa_context           *context,
 }
 
 static void
+_pa_context_get_card_info_by_index_cb (pa_context *context,
+                                       const pa_card_info *i,
+                                       int eol,
+                                       void *userdata)
+{
+        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
+
+        if (eol < 0) {
+                if (pa_context_errno (context) == PA_ERR_NOENTITY)
+                        return;
+
+                g_warning ("Card callback failure");
+                return;
+        }
+
+        if (eol > 0) {
+                dec_outstanding (control);
+                return;
+        }
+
+        update_card (control, i);
+}
+
+static void
 _pa_context_get_server_info_cb (pa_context           *context,
                                 const pa_server_info *i,
                                 void                 *userdata)
@@ -1154,6 +1387,30 @@ req_update_client_info (GvcMixerControl *control,
 }
 
 static void
+req_update_card (GvcMixerControl *control,
+                 int              index)
+{
+        pa_operation *o;
+
+        if (index < 0) {
+                o = pa_context_get_card_info_list (control->priv->pa_context,
+                                                   _pa_context_get_card_info_by_index_cb,
+                                                   control);
+        } else {
+                o = pa_context_get_card_info_by_index (control->priv->pa_context,
+                                                       index,
+                                                       _pa_context_get_card_info_by_index_cb,
+                                                       control);
+        }
+
+        if (o == NULL) {
+                g_warning ("pa_context_get_card_info_by_index() failed");
+                return;
+        }
+        pa_operation_unref (o);
+}
+
+static void
 req_update_sink_info (GvcMixerControl *control,
                       int              index)
 {
@@ -1258,6 +1515,19 @@ remove_client (GvcMixerControl *control,
 }
 
 static void
+remove_card (GvcMixerControl *control,
+             guint            index)
+{
+        g_hash_table_remove (control->priv->cards,
+                             GUINT_TO_POINTER (index));
+
+        g_signal_emit (G_OBJECT (control),
+                       signals[CARD_REMOVED],
+                       0,
+                       index);
+}
+
+static void
 remove_sink (GvcMixerControl *control,
              guint            index)
 {
@@ -1392,6 +1662,14 @@ _pa_context_subscribe_cb (pa_context                  *context,
         case PA_SUBSCRIPTION_EVENT_SERVER:
                 req_update_server_info (control, index);
                 break;
+
+        case PA_SUBSCRIPTION_EVENT_CARD:
+                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+                        remove_card (control, index);
+                } else {
+                        req_update_card (control, index);
+                }
+                break;
         }
 }
 
@@ -1410,7 +1688,8 @@ gvc_mixer_control_ready (GvcMixerControl *control)
                                    PA_SUBSCRIPTION_MASK_SINK_INPUT|
                                    PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT|
                                    PA_SUBSCRIPTION_MASK_CLIENT|
-                                   PA_SUBSCRIPTION_MASK_SERVER),
+                                   PA_SUBSCRIPTION_MASK_SERVER|
+                                   PA_SUBSCRIPTION_MASK_CARD),
                                   NULL,
                                   NULL);
 
@@ -1426,6 +1705,7 @@ gvc_mixer_control_ready (GvcMixerControl *control)
         req_update_source_info (control, -1);
         req_update_sink_input_info (control, -1);
         req_update_source_output_info (control, -1);
+        req_update_card (control, -1);
 
         control->priv->n_outstanding = 6;
 
@@ -1441,7 +1721,6 @@ gvc_mixer_control_ready (GvcMixerControl *control)
                                                         _pa_ext_stream_restore_subscribe_cb,
                                                         control);
 
-
                 o = pa_ext_stream_restore_subscribe (control->priv->pa_context,
                                                      1,
                                                      NULL,
@@ -1548,7 +1827,7 @@ _pa_context_state_cb (pa_context *context,
         case PA_CONTEXT_FAILED:
                 g_warning ("Connection failed, reconnecting...");
                 if (control->priv->reconnect_id == 0)
-                        control->priv->reconnect_id = g_idle_add (idle_reconnect, control);
+                        control->priv->reconnect_id = g_timeout_add_seconds (RECONNECT_DELAY, idle_reconnect, control);
                 break;
 
         case PA_CONTEXT_TERMINATED:
@@ -1640,6 +1919,10 @@ gvc_mixer_control_dispose (GObject *object)
                 g_hash_table_destroy (control->priv->clients);
                 control->priv->clients = NULL;
         }
+        if (control->priv->cards != NULL) {
+                g_hash_table_destroy (control->priv->cards);
+                control->priv->cards = NULL;
+        }
 
         G_OBJECT_CLASS (gvc_mixer_control_parent_class)->dispose (object);
 }
@@ -1702,6 +1985,22 @@ gvc_mixer_control_class_init (GvcMixerControlClass *klass)
                               NULL, NULL,
                               g_cclosure_marshal_VOID__UINT,
                               G_TYPE_NONE, 1, G_TYPE_UINT);
+        signals [CARD_ADDED] =
+                g_signal_new ("card-added",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GvcMixerControlClass, card_added),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__UINT,
+                              G_TYPE_NONE, 1, G_TYPE_UINT);
+        signals [CARD_REMOVED] =
+                g_signal_new ("card-removed",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GvcMixerControlClass, card_removed),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__UINT,
+                              G_TYPE_NONE, 1, G_TYPE_UINT);
         signals [DEFAULT_SINK_CHANGED] =
                 g_signal_new ("default-sink-changed",
                               G_TYPE_FROM_CLASS (klass),
@@ -1738,6 +2037,7 @@ gvc_mixer_control_init (GvcMixerControl *control)
         control->priv->sources = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
         control->priv->sink_inputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
         control->priv->source_outputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
+        control->priv->cards = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
 
         control->priv->clients = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_free);
 }
diff --git a/plugins/media-keys/cut-n-paste/gvc-mixer-control.h b/plugins/media-keys/cut-n-paste/gvc-mixer-control.h
index 3de9e62..d2f7968 100644
--- a/plugins/media-keys/cut-n-paste/gvc-mixer-control.h
+++ b/plugins/media-keys/cut-n-paste/gvc-mixer-control.h
@@ -24,6 +24,7 @@
 #include <glib-object.h>
 #include <pulse/pulseaudio.h>
 #include "gvc-mixer-stream.h"
+#include "gvc-mixer-card.h"
 
 G_BEGIN_DECLS
 
@@ -52,6 +53,10 @@ typedef struct
                                         guint            id);
         void (*stream_removed)         (GvcMixerControl *control,
                                         guint            id);
+        void (*card_added)             (GvcMixerControl *control,
+                                        guint            id);
+        void (*card_removed)           (GvcMixerControl *control,
+                                        guint            id);
         void (*default_sink_changed)   (GvcMixerControl *control,
                                         guint            id);
         void (*default_source_changed) (GvcMixerControl *control,
@@ -67,6 +72,7 @@ gboolean            gvc_mixer_control_close               (GvcMixerControl *cont
 gboolean            gvc_mixer_control_is_ready            (GvcMixerControl *control);
 
 pa_context *        gvc_mixer_control_get_pa_context      (GvcMixerControl *control);
+GSList *            gvc_mixer_control_get_cards           (GvcMixerControl *control);
 GSList *            gvc_mixer_control_get_streams         (GvcMixerControl *control);
 GSList *            gvc_mixer_control_get_sinks           (GvcMixerControl *control);
 GSList *            gvc_mixer_control_get_sources         (GvcMixerControl *control);
@@ -75,6 +81,8 @@ GSList *            gvc_mixer_control_get_source_outputs  (GvcMixerControl *cont
 
 GvcMixerStream *    gvc_mixer_control_lookup_stream_id    (GvcMixerControl *control,
                                                            guint            id);
+GvcMixerCard   *    gvc_mixer_control_lookup_card_id      (GvcMixerControl *control,
+                                                           guint            id);
 
 GvcMixerStream *    gvc_mixer_control_get_default_sink     (GvcMixerControl *control);
 GvcMixerStream *    gvc_mixer_control_get_default_source   (GvcMixerControl *control);
diff --git a/plugins/media-keys/cut-n-paste/gvc-mixer-sink.c b/plugins/media-keys/cut-n-paste/gvc-mixer-sink.c
index 06e5af6..5e95f63 100644
--- a/plugins/media-keys/cut-n-paste/gvc-mixer-sink.c
+++ b/plugins/media-keys/cut-n-paste/gvc-mixer-sink.c
@@ -106,6 +106,37 @@ gvc_mixer_sink_change_is_muted (GvcMixerStream *stream,
         return TRUE;
 }
 
+static gboolean
+gvc_mixer_sink_change_port (GvcMixerStream *stream,
+                            const char     *port)
+{
+#if PA_MICRO > 15
+        pa_operation *o;
+        guint         index;
+        pa_context   *context;
+
+        index = gvc_mixer_stream_get_index (stream);
+        context = gvc_mixer_stream_get_pa_context (stream);
+
+        o = pa_context_set_sink_port_by_index (context,
+                                               index,
+                                               port,
+                                               NULL,
+                                               NULL);
+
+        if (o == NULL) {
+                g_warning ("pa_context_set_sink_port_by_index() failed: %s", pa_strerror(pa_context_errno(context)));
+                return FALSE;
+        }
+
+        pa_operation_unref(o);
+
+        return TRUE;
+#else
+	return FALSE;
+#endif /* PA_MICRO > 15 */
+}
+
 static GObject *
 gvc_mixer_sink_constructor (GType                  type,
                             guint                  n_construct_properties,
@@ -132,6 +163,7 @@ gvc_mixer_sink_class_init (GvcMixerSinkClass *klass)
         object_class->finalize = gvc_mixer_sink_finalize;
 
         stream_class->push_volume = gvc_mixer_sink_push_volume;
+        stream_class->change_port = gvc_mixer_sink_change_port;
         stream_class->change_is_muted = gvc_mixer_sink_change_is_muted;
 
         g_type_class_add_private (klass, sizeof (GvcMixerSinkPrivate));
diff --git a/plugins/media-keys/cut-n-paste/gvc-mixer-source.c b/plugins/media-keys/cut-n-paste/gvc-mixer-source.c
index ae02d85..d13be9d 100644
--- a/plugins/media-keys/cut-n-paste/gvc-mixer-source.c
+++ b/plugins/media-keys/cut-n-paste/gvc-mixer-source.c
@@ -106,6 +106,37 @@ gvc_mixer_source_change_is_muted (GvcMixerStream *stream,
         return TRUE;
 }
 
+static gboolean
+gvc_mixer_source_change_port (GvcMixerStream *stream,
+                              const char     *port)
+{
+#if PA_MICRO > 15
+        pa_operation *o;
+        guint         index;
+        pa_context   *context;
+
+        index = gvc_mixer_stream_get_index (stream);
+        context = gvc_mixer_stream_get_pa_context (stream);
+
+        o = pa_context_set_source_port_by_index (context,
+                                                 index,
+                                                 port,
+                                                 NULL,
+                                                 NULL);
+
+        if (o == NULL) {
+                g_warning ("pa_context_set_source_port_by_index() failed: %s", pa_strerror(pa_context_errno(context)));
+                return FALSE;
+        }
+
+        pa_operation_unref(o);
+
+        return TRUE;
+#else
+	return FALSE;
+#endif /* PA_MICRO > 15 */
+}
+
 static GObject *
 gvc_mixer_source_constructor (GType                  type,
                             guint                  n_construct_properties,
@@ -133,6 +164,7 @@ gvc_mixer_source_class_init (GvcMixerSourceClass *klass)
 
         stream_class->push_volume = gvc_mixer_source_push_volume;
         stream_class->change_is_muted = gvc_mixer_source_change_is_muted;
+        stream_class->change_port = gvc_mixer_source_change_port;
 
         g_type_class_add_private (klass, sizeof (GvcMixerSourcePrivate));
 }
diff --git a/plugins/media-keys/cut-n-paste/gvc-mixer-stream.c b/plugins/media-keys/cut-n-paste/gvc-mixer-stream.c
index e5cfb19..caea0f1 100644
--- a/plugins/media-keys/cut-n-paste/gvc-mixer-stream.c
+++ b/plugins/media-keys/cut-n-paste/gvc-mixer-stream.c
@@ -51,6 +51,9 @@ struct GvcMixerStreamPrivate
         gboolean       is_virtual;
         pa_volume_t    base_volume;
         pa_operation  *change_volume_op;
+        char          *port;
+        char          *human_port;
+        GList         *ports;
 };
 
 enum
@@ -70,6 +73,7 @@ enum
         PROP_CAN_DECIBEL,
         PROP_IS_EVENT_STREAM,
         PROP_IS_VIRTUAL,
+        PROP_PORT,
 };
 
 static void     gvc_mixer_stream_class_init (GvcMixerStreamClass *klass);
@@ -396,6 +400,92 @@ gvc_mixer_stream_set_base_volume (GvcMixerStream *stream,
         return TRUE;
 }
 
+GvcMixerStreamPort *
+gvc_mixer_stream_get_port (GvcMixerStream *stream)
+{
+        GList *l;
+
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL);
+        g_return_val_if_fail (stream->priv->ports != NULL, NULL);
+
+        for (l = stream->priv->ports; l != NULL; l = l->next) {
+                GvcMixerStreamPort *p = l->data;
+                if (g_strcmp0 (stream->priv->port, p->port) == 0) {
+                        return p;
+                }
+        }
+
+        g_assert_not_reached ();
+
+        return NULL;
+}
+
+gboolean
+gvc_mixer_stream_set_port (GvcMixerStream *stream,
+                           const char     *port)
+{
+        GList *l;
+
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+        g_return_val_if_fail (stream->priv->ports != NULL, FALSE);
+
+        g_free (stream->priv->port);
+        stream->priv->port = g_strdup (port);
+
+        g_free (stream->priv->human_port);
+        stream->priv->human_port = NULL;
+
+        for (l = stream->priv->ports; l != NULL; l = l->next) {
+                GvcMixerStreamPort *p = l->data;
+                if (g_str_equal (stream->priv->port, p->port)) {
+                        stream->priv->human_port = g_strdup (p->human_port);
+                        break;
+                }
+        }
+
+        g_object_notify (G_OBJECT (stream), "port");
+
+        return TRUE;
+}
+
+gboolean
+gvc_mixer_stream_change_port (GvcMixerStream *stream,
+                              const char     *port)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+        return GVC_MIXER_STREAM_GET_CLASS (stream)->change_port (stream, port);
+}
+
+const GList *
+gvc_mixer_stream_get_ports (GvcMixerStream *stream)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+        return stream->priv->ports;
+}
+
+static int
+sort_ports (GvcMixerStreamPort *a,
+            GvcMixerStreamPort *b)
+{
+        if (a->priority == b->priority)
+                return 0;
+        if (a->priority > b->priority)
+                return 1;
+        return -1;
+}
+
+gboolean
+gvc_mixer_stream_set_ports (GvcMixerStream *stream,
+                            GList          *ports)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+        g_return_val_if_fail (stream->priv->ports == NULL, FALSE);
+
+        stream->priv->ports = g_list_sort (ports, (GCompareFunc) sort_ports);
+
+        return TRUE;
+}
+
 static void
 gvc_mixer_stream_set_property (GObject       *object,
                                guint          prop_id,
@@ -447,6 +537,9 @@ gvc_mixer_stream_set_property (GObject       *object,
         case PROP_CAN_DECIBEL:
                 gvc_mixer_stream_set_can_decibel (self, g_value_get_boolean (value));
                 break;
+        case PROP_PORT:
+                gvc_mixer_stream_set_port (self, g_value_get_string (value));
+                break;
         default:
                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                 break;
@@ -506,6 +599,9 @@ gvc_mixer_stream_get_property (GObject     *object,
         case PROP_CAN_DECIBEL:
                 g_value_set_boolean (value, self->priv->can_decibel);
                 break;
+        case PROP_PORT:
+                g_value_set_string (value, self->priv->port);
+                break;
         default:
                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                 break;
@@ -530,6 +626,13 @@ gvc_mixer_stream_constructor (GType                  type,
 }
 
 static gboolean
+gvc_mixer_stream_real_change_port (GvcMixerStream *stream,
+                                   const char     *port)
+{
+        return FALSE;
+}
+
+static gboolean
 gvc_mixer_stream_real_push_volume (GvcMixerStream *stream, gpointer *op)
 {
         return FALSE;
@@ -593,6 +696,7 @@ gvc_mixer_stream_class_init (GvcMixerStreamClass *klass)
         gobject_class->get_property = gvc_mixer_stream_get_property;
 
         klass->push_volume = gvc_mixer_stream_real_push_volume;
+        klass->change_port = gvc_mixer_stream_real_change_port;
         klass->change_is_muted = gvc_mixer_stream_real_change_is_muted;
 
         g_object_class_install_property (gobject_class,
@@ -693,6 +797,13 @@ gvc_mixer_stream_class_init (GvcMixerStreamClass *klass)
                                                                "Whether the stream is virtual",
                                                                FALSE,
                                                                G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
+        g_object_class_install_property (gobject_class,
+                                         PROP_PORT,
+                                         g_param_spec_string ("port",
+                                                              "Port",
+                                                              "The name of the current port for this stream",
+                                                              NULL,
+                                                              G_PARAM_READWRITE));
         g_type_class_add_private (klass, sizeof (GvcMixerStreamPrivate));
 }
 
@@ -703,6 +814,14 @@ gvc_mixer_stream_init (GvcMixerStream *stream)
 }
 
 static void
+free_port (GvcMixerStreamPort *p)
+{
+        g_free (p->port);
+        g_free (p->human_port);
+        g_free (p);
+}
+
+static void
 gvc_mixer_stream_finalize (GObject *object)
 {
         GvcMixerStream *mixer_stream;
@@ -726,6 +845,16 @@ gvc_mixer_stream_finalize (GObject *object)
         g_free (mixer_stream->priv->icon_name);
         mixer_stream->priv->icon_name = NULL;
 
+        g_free (mixer_stream->priv->port);
+        mixer_stream->priv->port = NULL;
+
+        g_free (mixer_stream->priv->human_port);
+        mixer_stream->priv->human_port = NULL;
+
+        g_list_foreach (mixer_stream->priv->ports, (GFunc) free_port, NULL);
+        g_list_free (mixer_stream->priv->ports);
+        mixer_stream->priv->ports = NULL;
+
        if (mixer_stream->priv->change_volume_op) {
                pa_operation_unref(mixer_stream->priv->change_volume_op);
                mixer_stream->priv->change_volume_op = NULL;
diff --git a/plugins/media-keys/cut-n-paste/gvc-mixer-stream.h b/plugins/media-keys/cut-n-paste/gvc-mixer-stream.h
index 4171ca3..4ae2d34 100644
--- a/plugins/media-keys/cut-n-paste/gvc-mixer-stream.h
+++ b/plugins/media-keys/cut-n-paste/gvc-mixer-stream.h
@@ -48,17 +48,31 @@ typedef struct
         GObjectClass           parent_class;
 
         /* vtable */
-        gboolean (*push_volume)   (GvcMixerStream *stream, gpointer *operation);
+        gboolean (*push_volume)     (GvcMixerStream *stream,
+                                     gpointer *operation);
         gboolean (*change_is_muted) (GvcMixerStream *stream,
                                      gboolean        is_muted);
+        gboolean (*change_port)     (GvcMixerStream *stream,
+                                     const char     *port);
 } GvcMixerStreamClass;
 
+typedef struct
+{
+        char *port;
+        char *human_port;
+        guint priority;
+} GvcMixerStreamPort;
+
 GType               gvc_mixer_stream_get_type        (void);
 
 pa_context *        gvc_mixer_stream_get_pa_context  (GvcMixerStream *stream);
 guint               gvc_mixer_stream_get_index       (GvcMixerStream *stream);
 guint               gvc_mixer_stream_get_id          (GvcMixerStream *stream);
 GvcChannelMap *     gvc_mixer_stream_get_channel_map (GvcMixerStream *stream);
+GvcMixerStreamPort *gvc_mixer_stream_get_port        (GvcMixerStream *stream);
+const GList *       gvc_mixer_stream_get_ports       (GvcMixerStream *stream);
+gboolean            gvc_mixer_stream_change_port     (GvcMixerStream *stream,
+                                                      const char     *port);
 
 pa_volume_t         gvc_mixer_stream_get_volume      (GvcMixerStream *stream);
 gdouble             gvc_mixer_stream_get_decibel     (GvcMixerStream *stream);
@@ -100,6 +114,10 @@ gboolean            gvc_mixer_stream_set_application_id (GvcMixerStream *stream,
                                                          const char *application_id);
 gboolean            gvc_mixer_stream_set_base_volume (GvcMixerStream *stream,
                                                       pa_volume_t     base_volume);
+gboolean            gvc_mixer_stream_set_port        (GvcMixerStream *stream,
+                                                      const char     *port);
+gboolean            gvc_mixer_stream_set_ports       (GvcMixerStream *stream,
+                                                      GList          *ports);
 
 G_END_DECLS
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]