From d2c3a4be634018a2b63f4c80a26f9024a0d3de47 Mon Sep 17 00:00:00 2001
From: Michal Ratajsky <michal.ratajsky@gmail.com>
Date: Sat, 7 Jun 2014 01:07:02 +0200
Subject: Weekly update

---
 backends/pulse/Makefile.am           |  14 +-
 backends/pulse/pulse-connection.c    | 764 +++++++++++++++++++++++++++++++++++
 backends/pulse/pulse-connection.h    | 102 +++++
 backends/pulse/pulse-device.c        | 251 ++++++++----
 backends/pulse/pulse-device.h        |  27 +-
 backends/pulse/pulse-sink-input.c    |   0
 backends/pulse/pulse-sink-input.h    |   0
 backends/pulse/pulse-sink.c          | 130 ++++++
 backends/pulse/pulse-sink.h          |  69 ++++
 backends/pulse/pulse-source-output.c |   0
 backends/pulse/pulse-source-output.h |   0
 backends/pulse/pulse-source.c        |   0
 backends/pulse/pulse-source.h        |   0
 backends/pulse/pulse-stream.c        | 259 ++++++++++++
 backends/pulse/pulse-stream.h        |  86 ++++
 backends/pulse/pulse-track.c         |   0
 backends/pulse/pulse-track.h         |   0
 backends/pulse/pulse.c               | 383 +++++++++---------
 backends/pulse/pulse.h               |   2 +
 19 files changed, 1801 insertions(+), 286 deletions(-)
 create mode 100644 backends/pulse/pulse-connection.c
 create mode 100644 backends/pulse/pulse-connection.h
 create mode 100644 backends/pulse/pulse-sink-input.c
 create mode 100644 backends/pulse/pulse-sink-input.h
 create mode 100644 backends/pulse/pulse-sink.c
 create mode 100644 backends/pulse/pulse-sink.h
 create mode 100644 backends/pulse/pulse-source-output.c
 create mode 100644 backends/pulse/pulse-source-output.h
 create mode 100644 backends/pulse/pulse-source.c
 create mode 100644 backends/pulse/pulse-source.h
 create mode 100644 backends/pulse/pulse-stream.c
 create mode 100644 backends/pulse/pulse-stream.h
 delete mode 100644 backends/pulse/pulse-track.c
 delete mode 100644 backends/pulse/pulse-track.h

(limited to 'backends/pulse')

diff --git a/backends/pulse/Makefile.am b/backends/pulse/Makefile.am
index 2f7cd6f..2cc7b5e 100644
--- a/backends/pulse/Makefile.am
+++ b/backends/pulse/Makefile.am
@@ -13,10 +13,20 @@ libmatemixer_pulse_la_CFLAGS =                                  \
 libmatemixer_pulse_la_SOURCES =                                 \
         pulse.c                                                 \
         pulse.h                                                 \
+        pulse-connection.c                                      \
+        pulse-connection.h                                      \
         pulse-device.c                                          \
         pulse-device.h                                          \
-        pulse-track.c                                           \
-        pulse-track.h
+        pulse-stream.c                                          \
+        pulse-stream.h                                          \
+        pulse-sink.c                                            \
+        pulse-sink.h                                            \
+        pulse-sink-input.c                                      \
+        pulse-sink-input.h                                      \
+        pulse-source.c                                          \
+        pulse-source.h                                          \
+        pulse-source-output.c                                   \
+        pulse-source-output.h
 
 libmatemixer_pulse_la_LIBADD =                                  \
         $(GLIB_LIBS)                                            \
diff --git a/backends/pulse/pulse-connection.c b/backends/pulse/pulse-connection.c
new file mode 100644
index 0000000..6c70490
--- /dev/null
+++ b/backends/pulse/pulse-connection.c
@@ -0,0 +1,764 @@
+/*
+ * Copyright (C) 2014 Michal Ratajsky <michal.ratajsky@gmail.com>
+ *
+ * This library 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 of the licence, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "pulse-connection.h"
+
+struct _MateMixerPulseConnectionPrivate
+{
+    gchar                 *server;
+    gboolean               reconnect;
+    gboolean               connected;
+    pa_context            *context;
+    pa_threaded_mainloop  *mainloop;
+};
+
+enum {
+    PROP_0,
+    PROP_SERVER,
+    PROP_RECONNECT,
+    PROP_CONNECTED,
+    N_PROPERTIES
+};
+
+enum {
+    LIST_ITEM_CARD,
+    LIST_ITEM_SINK,
+    LIST_ITEM_SOURCE,
+    LIST_ITEM_SINK_INPUT,
+    LIST_ITEM_SOURCE_OUTPUT,
+    CARD_ADDED,
+    CARD_REMOVED,
+    CARD_CHANGED,
+    SINK_ADDED,
+    SINK_REMOVED,
+    SINK_CHANGED,
+    SOURCE_ADDED,
+    SOURCE_REMOVED,
+    SOURCE_CHANGED,
+    N_SIGNALS
+};
+
+static GParamSpec *properties[N_PROPERTIES] = { NULL, };
+
+static guint signals[N_SIGNALS] = { 0, };
+
+G_DEFINE_TYPE (MateMixerPulseConnection, mate_mixer_pulse_connection, G_TYPE_OBJECT);
+
+static gchar *pulse_connection_get_name (void);
+
+static gboolean pulse_connection_process_operation (MateMixerPulseConnection *connection,
+                                                    pa_operation *o);
+
+static void pulse_connection_state_cb (pa_context *c, void *userdata);
+
+static void pulse_connection_subscribe_cb (pa_context *c,
+                                           pa_subscription_event_type_t t,
+                                            uint32_t idx,
+                                            void *userdata);
+
+static void pulse_connection_card_info_cb (pa_context *c,
+                                           const pa_card_info *info,
+                                           int eol,
+                                           void *userdata);
+
+static void pulse_connection_sink_info_cb (pa_context *c,
+                                            const pa_sink_info *info,
+                                            int eol,
+                                            void *userdata);
+
+static void pulse_connection_source_info_cb (pa_context *c,
+                                             const pa_source_info *info,
+                                             int eol,
+                                             void *userdata);
+
+static void pulse_connection_sink_input_info_cb (pa_context *c,
+                                                const pa_sink_input_info *info,
+                                                int eol,
+                                                void *userdata);
+
+static void pulse_connection_source_output_info_cb (pa_context *c,
+                                                    const pa_source_output_info *info,
+                                                    int eol,
+                                                    void *userdata);
+
+static void
+mate_mixer_pulse_connection_init (MateMixerPulseConnection *connection)
+{
+    connection->priv = G_TYPE_INSTANCE_GET_PRIVATE (
+        connection,
+        MATE_MIXER_TYPE_PULSE_CONNECTION,
+        MateMixerPulseConnectionPrivate);
+}
+
+static void
+mate_mixer_pulse_connection_get_property (GObject     *object,
+                                          guint        param_id,
+                                          GValue      *value,
+                                          GParamSpec  *pspec)
+{
+    MateMixerPulseConnection *connection;
+
+    connection = MATE_MIXER_PULSE_CONNECTION (object);
+
+    switch (param_id) {
+    case PROP_SERVER:
+        g_value_set_string (value, connection->priv->server);
+        break;
+    case PROP_RECONNECT:
+        g_value_set_boolean (value, connection->priv->reconnect);
+        break;
+    case PROP_CONNECTED:
+        g_value_set_boolean (value, connection->priv->connected);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+        break;
+    }
+}
+
+static void
+mate_mixer_pulse_connection_set_property (GObject       *object,
+                                          guint          param_id,
+                                          const GValue  *value,
+                                          GParamSpec    *pspec)
+{
+    MateMixerPulseConnection *connection;
+
+    connection = MATE_MIXER_PULSE_CONNECTION (object);
+
+    switch (param_id) {
+    case PROP_SERVER:
+        connection->priv->server = g_strdup (g_value_get_string (value));
+        break;
+    case PROP_RECONNECT:
+        connection->priv->reconnect = g_value_get_boolean (value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+        break;
+    }
+}
+
+static void
+mate_mixer_pulse_connection_finalize (GObject *object)
+{
+    MateMixerPulseConnection *connection;
+
+    connection = MATE_MIXER_PULSE_CONNECTION (object);
+
+    g_free (connection->priv->server);
+
+    G_OBJECT_CLASS (mate_mixer_pulse_connection_parent_class)->finalize (object);
+}
+
+static void
+mate_mixer_pulse_connection_class_init (MateMixerPulseConnectionClass *klass)
+{
+    GObjectClass *object_class;
+
+    object_class = G_OBJECT_CLASS (klass);
+    object_class->finalize     = mate_mixer_pulse_connection_finalize;
+    object_class->get_property = mate_mixer_pulse_connection_get_property;
+    object_class->set_property = mate_mixer_pulse_connection_set_property;
+
+    properties[PROP_SERVER] = g_param_spec_string (
+        "server",
+        "Server",
+        "PulseAudio server to connect to",
+        NULL,
+        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+    properties[PROP_RECONNECT] = g_param_spec_boolean (
+        "reconnect",
+        "Reconnect",
+        "Try to reconnect when connection to PulseAudio server is lost",
+        TRUE,
+        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+    properties[PROP_CONNECTED] = g_param_spec_boolean (
+        "connected",
+        "Connected",
+        "Connected to a PulseAudio server or not",
+        FALSE,
+        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    signals[LIST_ITEM_CARD] = g_signal_new (
+        "list-item-card",
+        G_TYPE_FROM_CLASS (object_class),
+        G_SIGNAL_RUN_LAST,
+        G_STRUCT_OFFSET (MateMixerPulseConnectionClass, list_item_card),
+        NULL,
+        NULL,
+        g_cclosure_marshal_VOID__POINTER,
+        G_TYPE_NONE,
+        1,
+        G_TYPE_POINTER);
+
+    signals[LIST_ITEM_SINK] = g_signal_new (
+        "list-item-sink",
+        G_TYPE_FROM_CLASS (object_class),
+        G_SIGNAL_RUN_LAST,
+        G_STRUCT_OFFSET (MateMixerPulseConnectionClass, list_item_sink),
+        NULL,
+        NULL,
+        g_cclosure_marshal_VOID__POINTER,
+        G_TYPE_NONE,
+        1,
+        G_TYPE_POINTER);
+
+    signals[LIST_ITEM_SINK_INPUT] = g_signal_new (
+        "list-item-sink-input",
+        G_TYPE_FROM_CLASS (object_class),
+        G_SIGNAL_RUN_LAST,
+        G_STRUCT_OFFSET (MateMixerPulseConnectionClass, list_item_sink_input),
+        NULL,
+        NULL,
+        g_cclosure_marshal_VOID__POINTER,
+        G_TYPE_NONE,
+        1,
+        G_TYPE_POINTER);
+
+    signals[LIST_ITEM_SOURCE] = g_signal_new (
+        "list-item-source",
+        G_TYPE_FROM_CLASS (object_class),
+        G_SIGNAL_RUN_LAST,
+        G_STRUCT_OFFSET (MateMixerPulseConnectionClass, list_item_source),
+        NULL,
+        NULL,
+        g_cclosure_marshal_VOID__POINTER,
+        G_TYPE_NONE,
+        1,
+        G_TYPE_POINTER);
+
+    signals[LIST_ITEM_SOURCE_OUTPUT] = g_signal_new (
+        "list-item-source-output",
+        G_TYPE_FROM_CLASS (object_class),
+        G_SIGNAL_RUN_LAST,
+        G_STRUCT_OFFSET (MateMixerPulseConnectionClass, list_item_source_output),
+        NULL,
+        NULL,
+        g_cclosure_marshal_VOID__POINTER,
+        G_TYPE_NONE,
+        1,
+        G_TYPE_POINTER);
+
+    g_object_class_install_properties (object_class, N_PROPERTIES, properties);
+
+    g_type_class_add_private (object_class, sizeof (MateMixerPulseConnectionPrivate));
+}
+
+// XXX: pass more info about application, provide API
+
+MateMixerPulseConnection *
+mate_mixer_pulse_connection_new (const gchar *server, const gchar *app_name)
+{
+    pa_threaded_mainloop      *mainloop;
+    pa_context                *context;
+    MateMixerPulseConnection  *connection;
+
+    mainloop = pa_threaded_mainloop_new ();
+    if (G_UNLIKELY (mainloop == NULL)) {
+        g_warning ("Failed to create PulseAudio main loop");
+        return NULL;
+    }
+
+    if (app_name != NULL) {
+        context = pa_context_new (
+            pa_threaded_mainloop_get_api (mainloop),
+            app_name);
+    } else {
+        gchar *name = pulse_connection_get_name ();
+
+        context = pa_context_new (
+            pa_threaded_mainloop_get_api (mainloop),
+            name);
+
+        g_free (name);
+    }
+
+    if (G_UNLIKELY (context == NULL)) {
+        g_warning ("Failed to create PulseAudio context");
+
+        pa_threaded_mainloop_free (mainloop);
+        return NULL;
+    }
+
+    connection = g_object_new (MATE_MIXER_TYPE_PULSE_CONNECTION,
+        "server",    server,
+        "reconnect", TRUE,
+        NULL);
+
+    connection->priv->mainloop = mainloop;
+    connection->priv->context = context;
+
+    return connection;
+}
+
+gboolean
+mate_mixer_pulse_connection_connect (MateMixerPulseConnection *connection)
+{
+    int ret;
+    pa_operation *o;
+
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_CONNECTION (connection), FALSE);
+
+    if (connection->priv->connected)
+        return TRUE;
+
+    /* Initiate a connection, this call does not guarantee the connection
+     * to be established and usable */
+    ret = pa_context_connect (connection->priv->context, NULL, PA_CONTEXT_NOFLAGS, NULL);
+    if (ret < 0) {
+        g_warning ("Failed to connect to PulseAudio server: %s", pa_strerror (ret));
+        return FALSE;
+    }
+
+    pa_threaded_mainloop_lock (connection->priv->mainloop);
+
+    /* Set callback for connection status changes; the callback is not really
+     * used when connecting the first time, it is only needed to signal
+     * a status change */
+    pa_context_set_state_callback (connection->priv->context,
+        pulse_connection_state_cb,
+        connection);
+
+    ret = pa_threaded_mainloop_start (connection->priv->mainloop);
+    if (ret < 0) {
+        g_warning ("Failed to start PulseAudio main loop: %s", pa_strerror (ret));
+
+        pa_context_disconnect (connection->priv->context);
+        pa_threaded_mainloop_unlock (connection->priv->mainloop);
+        return FALSE;
+    }
+
+    while (TRUE) {
+        /* Wait for a connection state which tells us whether the connection
+         * has been established or has failed */
+        pa_context_state_t state =
+            pa_context_get_state (connection->priv->context);
+
+        if (state == PA_CONTEXT_READY)
+            break;
+
+        if (state == PA_CONTEXT_FAILED ||
+            state == PA_CONTEXT_TERMINATED) {
+            g_warning ("Failed to connect to PulseAudio server: %s",
+                pa_strerror (pa_context_errno (connection->priv->context)));
+
+            pa_context_disconnect (connection->priv->context);
+            pa_threaded_mainloop_unlock (connection->priv->mainloop);
+            return FALSE;
+        }
+        pa_threaded_mainloop_wait (connection->priv->mainloop);
+    }
+
+    pa_context_set_subscribe_callback (connection->priv->context,
+        pulse_connection_subscribe_cb,
+        connection);
+
+    // XXX don't want notifications before the initial lists are downloaded
+
+    o = pa_context_subscribe (connection->priv->context,
+        PA_SUBSCRIPTION_MASK_CARD |
+        PA_SUBSCRIPTION_MASK_SINK |
+        PA_SUBSCRIPTION_MASK_SOURCE |
+        PA_SUBSCRIPTION_MASK_SINK_INPUT |
+        PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT,
+        NULL, NULL);
+    if (o == NULL)
+        g_warning ("Failed to subscribe to PulseAudio notifications: %s",
+            pa_strerror (pa_context_errno (connection->priv->context)));
+    else
+        pa_operation_unref (o);
+
+    pa_threaded_mainloop_unlock (connection->priv->mainloop);
+
+    connection->priv->connected = TRUE;
+
+    g_object_notify_by_pspec (G_OBJECT (connection), properties[PROP_CONNECTED]);
+    return TRUE;
+}
+
+void
+mate_mixer_pulse_connection_disconnect (MateMixerPulseConnection *connection)
+{
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_CONNECTION (connection), FALSE);
+
+    if (!connection->priv->connected)
+        return;
+
+    pa_context_disconnect (connection->priv->context);
+
+    connection->priv->connected = FALSE;
+
+    g_object_notify_by_pspec (G_OBJECT (connection), properties[PROP_CONNECTED]);
+}
+
+gboolean
+mate_mixer_pulse_connection_get_server_info (MateMixerPulseConnection *connection)
+{
+    // TODO
+    return TRUE;
+}
+
+gboolean
+mate_mixer_pulse_connection_get_card_list (MateMixerPulseConnection *connection)
+{
+    pa_operation *o;
+    gboolean ret;
+
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_CONNECTION (connection), FALSE);
+
+    pa_threaded_mainloop_lock (connection->priv->mainloop);
+
+    o = pa_context_get_card_info_list (
+        connection->priv->context,
+        pulse_connection_card_info_cb,
+        connection);
+
+    ret = pulse_connection_process_operation (connection, o);
+
+    pa_threaded_mainloop_unlock (connection->priv->mainloop);
+    return ret;
+}
+
+gboolean
+mate_mixer_pulse_connection_get_sink_list (MateMixerPulseConnection *connection)
+{
+    pa_operation *o;
+    gboolean ret;
+
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_CONNECTION (connection), FALSE);
+
+    pa_threaded_mainloop_lock (connection->priv->mainloop);
+
+    o = pa_context_get_sink_info_list (
+        connection->priv->context,
+        pulse_connection_sink_info_cb,
+        connection);
+
+    ret = pulse_connection_process_operation (connection, o);
+
+    pa_threaded_mainloop_unlock (connection->priv->mainloop);
+    return ret;
+}
+
+gboolean
+mate_mixer_pulse_connection_get_sink_input_list (MateMixerPulseConnection *connection)
+{
+    pa_operation *o;
+    gboolean ret;
+
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_CONNECTION (connection), FALSE);
+
+    pa_threaded_mainloop_lock (connection->priv->mainloop);
+
+    o = pa_context_get_sink_input_info_list (
+        connection->priv->context,
+        pulse_connection_sink_input_info_cb,
+        connection);
+
+    ret = pulse_connection_process_operation (connection, o);
+
+    pa_threaded_mainloop_unlock (connection->priv->mainloop);
+    return ret;
+}
+
+gboolean
+mate_mixer_pulse_connection_get_source_list (MateMixerPulseConnection *connection)
+{
+    pa_operation  *o;
+    gboolean       ret;
+
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_CONNECTION (connection), FALSE);
+
+    pa_threaded_mainloop_lock (connection->priv->mainloop);
+
+    o = pa_context_get_source_info_list (
+        connection->priv->context,
+        pulse_connection_source_info_cb,
+        connection);
+
+    ret = pulse_connection_process_operation (connection, o);
+
+    pa_threaded_mainloop_unlock (connection->priv->mainloop);
+    return ret;
+}
+
+gboolean
+mate_mixer_pulse_connection_get_source_output_list (MateMixerPulseConnection *connection)
+{
+    pa_operation *o;
+    gboolean ret;
+
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_CONNECTION (connection), FALSE);
+
+    pa_threaded_mainloop_lock (connection->priv->mainloop);
+
+    o = pa_context_get_source_output_info_list (
+        connection->priv->context,
+        pulse_connection_source_output_info_cb,
+        connection);
+
+    ret = pulse_connection_process_operation (connection, o);
+
+    pa_threaded_mainloop_unlock (connection->priv->mainloop);
+    return ret;
+}
+
+gboolean
+mate_mixer_pulse_connection_set_card_profile (MateMixerPulseConnection *connection,
+                                              const gchar              *card,
+                                              const gchar              *profile)
+{
+    pa_operation *o;
+    gboolean ret;
+
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_CONNECTION (connection), FALSE);
+
+    pa_threaded_mainloop_lock (connection->priv->mainloop);
+
+    o = pa_context_set_card_profile_by_name (
+        connection->priv->context,
+        card,
+        profile,
+        NULL, NULL);
+
+    // XXX maybe shouldn't wait for the completion
+
+    ret = pulse_connection_process_operation (connection, o);
+
+    pa_threaded_mainloop_unlock (connection->priv->mainloop);
+    return ret;
+}
+
+gboolean
+mate_mixer_pulse_connection_set_sink_mute (MateMixerPulseConnection *connection,
+                                           guint32 index,
+                                           gboolean mute)
+{
+    pa_operation *o;
+    gboolean ret;
+
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_CONNECTION (connection), FALSE);
+
+    pa_threaded_mainloop_lock (connection->priv->mainloop);
+
+    o = pa_context_set_sink_mute_by_index (
+        connection->priv->context,
+        index,
+        (int) mute,
+        NULL, NULL);
+
+    // XXX maybe shouldn't wait for the completion
+
+    ret = pulse_connection_process_operation (connection, o);
+
+    pa_threaded_mainloop_unlock (connection->priv->mainloop);
+    return ret;
+}
+
+static gboolean
+pulse_connection_process_operation (MateMixerPulseConnection *connection,
+                                    pa_operation *o)
+{
+    if (o == NULL) {
+        g_warning ("Failed to process PulseAudio operation: %s",
+            pa_strerror (pa_context_errno (connection->priv->context)));
+
+        return FALSE;
+    }
+
+    while (pa_operation_get_state (o) == PA_OPERATION_RUNNING)
+        pa_threaded_mainloop_wait (connection->priv->mainloop);
+
+    pa_operation_unref (o);
+    return TRUE;
+}
+
+static gchar *
+pulse_connection_get_name (void)
+{
+    const char *name_app;
+    char        name_buf[256];
+
+    /* Inspired by GStreamer's pulse plugin */
+    name_app = g_get_application_name ();
+    if (name_app != NULL)
+        return g_strdup (name_app);
+
+    if (pa_get_binary_name (name_buf, sizeof (name_buf)) != NULL)
+        return g_strdup (name_buf);
+
+    return g_strdup_printf ("libmatemixer-%lu", (gulong) getpid ());
+}
+
+static void
+pulse_connection_state_cb (pa_context *c, void *userdata)
+{
+    MateMixerPulseConnection  *connection;
+    pa_context_state_t         state;
+
+    connection = MATE_MIXER_PULSE_CONNECTION (userdata);
+
+    state = pa_context_get_state (c);
+    switch (state) {
+    case PA_CONTEXT_READY:
+        /* The connection is established, the context is ready to
+         * execute operations. */
+        if (!connection->priv->connected) {
+            connection->priv->connected = TRUE;
+
+            g_object_notify_by_pspec (
+                G_OBJECT (connection),
+                properties[PROP_CONNECTED]);
+        }
+        break;
+
+    case PA_CONTEXT_TERMINATED:
+        /* The connection was terminated cleanly. */
+        if (connection->priv->connected) {
+            connection->priv->connected = FALSE;
+
+            g_object_notify_by_pspec (
+                G_OBJECT (connection),
+                properties[PROP_CONNECTED]);
+
+            pa_context_disconnect (connection->priv->context);
+        }
+        break;
+
+    case PA_CONTEXT_FAILED:
+        break;
+
+    default:
+        break;
+    }
+
+    pa_threaded_mainloop_signal (connection->priv->mainloop, 0);
+}
+
+static void
+pulse_connection_subscribe_cb (pa_context *c,
+                               pa_subscription_event_type_t t,
+                               uint32_t idx,
+                               void *userdata)
+{
+    // TODO
+}
+
+static void
+pulse_connection_card_info_cb (pa_context *c,
+                               const pa_card_info *info,
+                               int eol,
+                               void *userdata)
+{
+    MateMixerPulseConnection *connection;
+
+    connection = MATE_MIXER_PULSE_CONNECTION (userdata);
+
+    if (!eol)
+        g_signal_emit (G_OBJECT (connection),
+            signals[LIST_ITEM_CARD],
+            0,
+            info);
+
+    pa_threaded_mainloop_signal (connection->priv->mainloop, 0);
+}
+
+static void
+pulse_connection_sink_info_cb (pa_context *c,
+                               const pa_sink_info *info,
+                               int eol,
+                               void *userdata)
+{
+    MateMixerPulseConnection *connection;
+
+    connection = MATE_MIXER_PULSE_CONNECTION (userdata);
+
+    if (!eol)
+        g_signal_emit (G_OBJECT (connection),
+            signals[LIST_ITEM_SINK],
+            0,
+            info);
+
+    pa_threaded_mainloop_signal (connection->priv->mainloop, 0);
+}
+
+static void
+pulse_connection_sink_input_info_cb (pa_context *c,
+                                     const pa_sink_input_info *info,
+                                     int eol,
+                                     void *userdata)
+{
+    MateMixerPulseConnection *connection;
+
+    connection = MATE_MIXER_PULSE_CONNECTION (userdata);
+
+    if (!eol)
+        g_signal_emit (G_OBJECT (connection),
+            signals[LIST_ITEM_SINK_INPUT],
+            0,
+            info);
+
+    pa_threaded_mainloop_signal (connection->priv->mainloop, 0);
+}
+
+static void
+pulse_connection_source_info_cb (pa_context *c,
+                                 const pa_source_info *info,
+                                 int eol,
+                                 void *userdata)
+{
+    MateMixerPulseConnection *connection;
+
+    connection = MATE_MIXER_PULSE_CONNECTION (userdata);
+
+    if (!eol)
+        g_signal_emit (G_OBJECT (connection),
+            signals[LIST_ITEM_SOURCE],
+            0,
+            info);
+
+    pa_threaded_mainloop_signal (connection->priv->mainloop, 0);
+}
+
+static void
+pulse_connection_source_output_info_cb (pa_context *c,
+                                        const pa_source_output_info *info,
+                                        int eol,
+                                        void *userdata)
+{
+    MateMixerPulseConnection *connection;
+
+    connection = MATE_MIXER_PULSE_CONNECTION (userdata);
+
+    if (!eol)
+        g_signal_emit (G_OBJECT (connection),
+            signals[LIST_ITEM_SOURCE_OUTPUT],
+            0,
+            info);
+
+    pa_threaded_mainloop_signal (connection->priv->mainloop, 0);
+}
diff --git a/backends/pulse/pulse-connection.h b/backends/pulse/pulse-connection.h
new file mode 100644
index 0000000..85fd0b7
--- /dev/null
+++ b/backends/pulse/pulse-connection.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2014 Michal Ratajsky <michal.ratajsky@gmail.com>
+ *
+ * This library 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 of the licence, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MATEMIXER_PULSE_CONNECTION_H
+#define MATEMIXER_PULSE_CONNECTION_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <pulse/pulseaudio.h>
+
+G_BEGIN_DECLS
+
+#define MATE_MIXER_TYPE_PULSE_CONNECTION            \
+        (mate_mixer_pulse_connection_get_type ())
+#define MATE_MIXER_PULSE_CONNECTION(o)              \
+        (G_TYPE_CHECK_INSTANCE_CAST ((o), MATE_MIXER_TYPE_PULSE_CONNECTION, MateMixerPulseConnection))
+#define MATE_MIXER_IS_PULSE_CONNECTION(o)           \
+        (G_TYPE_CHECK_INSTANCE_TYPE ((o), MATE_MIXER_TYPE_PULSE_CONNECTION))
+#define MATE_MIXER_PULSE_CONNECTION_CLASS(k)        \
+        (G_TYPE_CHECK_CLASS_CAST ((k), MATE_MIXER_TYPE_PULSE_CONNECTION, MateMixerPulseConnectionClass))
+#define MATE_MIXER_IS_PULSE_CONNECTION_CLASS(k)     \
+        (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), MATE_MIXER_TYPE_PULSE_CONNECTION))
+#define MATE_MIXER_PULSE_CONNECTION_GET_CLASS(o)    \
+        (G_TYPE_INSTANCE_GET_CLASS ((o), MATE_MIXER_TYPE_PULSE_CONNECTION, MateMixerPulseConnectionClass))
+
+typedef struct _MateMixerPulseConnection         MateMixerPulseConnection;
+typedef struct _MateMixerPulseConnectionClass    MateMixerPulseConnectionClass;
+typedef struct _MateMixerPulseConnectionPrivate  MateMixerPulseConnectionPrivate;
+
+struct _MateMixerPulseConnection
+{
+    GObject parent;
+
+    MateMixerPulseConnectionPrivate *priv;
+};
+
+struct _MateMixerPulseConnectionClass
+{
+    GObjectClass parent;
+
+    void (*disconnected) (MateMixerPulseConnection *connection);
+    void (*reconnected)  (MateMixerPulseConnection *connection);
+
+    void (*list_item_card) (MateMixerPulseConnection *connection,
+                            const pa_card_info *info);
+    void (*list_item_sink) (MateMixerPulseConnection *connection,
+                            const pa_sink_info *info);
+    void (*list_item_sink_input) (MateMixerPulseConnection *connection,
+                            const pa_sink_input_info *info);
+    void (*list_item_source) (MateMixerPulseConnection *connection,
+                            const pa_source_info *info);
+    void (*list_item_source_output) (MateMixerPulseConnection *connection,
+                            const pa_source_output_info *info);
+};
+
+GType mate_mixer_pulse_connection_get_type (void) G_GNUC_CONST;
+
+MateMixerPulseConnection *mate_mixer_pulse_connection_new (const gchar *server,
+                                                           const gchar *app_name);
+
+gboolean mate_mixer_pulse_connection_connect (MateMixerPulseConnection *connection);
+
+void mate_mixer_pulse_connection_disconnect (MateMixerPulseConnection *connection);
+
+gboolean mate_mixer_pulse_connection_get_server_info (MateMixerPulseConnection *connection);
+
+gboolean mate_mixer_pulse_connection_get_card_list (MateMixerPulseConnection *connection);
+
+gboolean mate_mixer_pulse_connection_get_sink_list (MateMixerPulseConnection *connection);
+
+gboolean mate_mixer_pulse_connection_get_sink_input_list (MateMixerPulseConnection *connection);
+
+gboolean mate_mixer_pulse_connection_get_source_list (MateMixerPulseConnection *connection);
+
+gboolean mate_mixer_pulse_connection_get_source_output_list (MateMixerPulseConnection *connection);
+
+gboolean mate_mixer_pulse_connection_set_card_profile (MateMixerPulseConnection *connection,
+                                                       const gchar              *device,
+                                                       const gchar              *profile);
+
+gboolean mate_mixer_pulse_connection_set_sink_mute (MateMixerPulseConnection *connection,
+                                                    guint32 index,
+                                                    gboolean mute);
+
+G_END_DECLS
+
+#endif /* MATEMIXER_PULSE_CONNECTION_H */
diff --git a/backends/pulse/pulse-device.c b/backends/pulse/pulse-device.c
index dbc287c..a411d7f 100644
--- a/backends/pulse/pulse-device.c
+++ b/backends/pulse/pulse-device.c
@@ -19,30 +19,31 @@
 #include <glib-object.h>
 
 #include <libmatemixer/matemixer-device.h>
-#include <libmatemixer/matemixer-device-port.h>
-#include <libmatemixer/matemixer-device-profile.h>
+#include <libmatemixer/matemixer-port.h>
+#include <libmatemixer/matemixer-profile.h>
 
 #include <pulse/pulseaudio.h>
 
+#include "pulse-connection.h"
 #include "pulse-device.h"
 
 struct _MateMixerPulseDevicePrivate
 {
-    guint32   index;
-    GList    *profiles;
-    GList    *ports;
-    gchar    *identifier;
-    gchar    *name;
-    gchar    *icon;
-
-    MateMixerDeviceProfile *active_profile;
+    guint32                   index;
+    gchar                    *name;
+    gchar                    *description;
+    GList                    *profiles;
+    GList                    *ports;
+    gchar                    *icon;
+    MateMixerProfile         *profile;
+    MateMixerPulseConnection *connection;
 };
 
 enum
 {
     PROP_0,
-    PROP_IDENTIFIER,
     PROP_NAME,
+    PROP_DESCRIPTION,
     PROP_ICON,
     PROP_ACTIVE_PROFILE,
     N_PROPERTIES
@@ -57,9 +58,12 @@ G_DEFINE_TYPE_WITH_CODE (MateMixerPulseDevice, mate_mixer_pulse_device, G_TYPE_O
 static void
 mate_mixer_device_interface_init (MateMixerDeviceInterface *iface)
 {
-    iface->list_tracks = mate_mixer_pulse_device_list_tracks;
-    iface->get_ports = mate_mixer_pulse_device_get_ports;
-    iface->get_profiles = mate_mixer_pulse_device_get_profiles;
+    iface->get_name = mate_mixer_pulse_device_get_name;
+    iface->get_description = mate_mixer_pulse_device_get_description;
+    iface->get_icon = mate_mixer_pulse_device_get_icon;
+    iface->list_streams = mate_mixer_pulse_device_list_streams;
+    iface->list_ports = mate_mixer_pulse_device_list_ports;
+    iface->list_profiles = mate_mixer_pulse_device_list_profiles;
     iface->get_active_profile = mate_mixer_pulse_device_get_active_profile;
     iface->set_active_profile = mate_mixer_pulse_device_set_active_profile;
 }
@@ -67,10 +71,9 @@ mate_mixer_device_interface_init (MateMixerDeviceInterface *iface)
 static void
 mate_mixer_pulse_device_init (MateMixerPulseDevice *device)
 {
-    device->priv = G_TYPE_INSTANCE_GET_PRIVATE (
-        device,
-        MATE_MIXER_TYPE_PULSE_DEVICE,
-        MateMixerPulseDevicePrivate);
+    device->priv = G_TYPE_INSTANCE_GET_PRIVATE (device,
+                                                MATE_MIXER_TYPE_PULSE_DEVICE,
+                                                MateMixerPulseDevicePrivate);
 }
 
 static void
@@ -84,17 +87,17 @@ mate_mixer_pulse_device_get_property (GObject     *object,
     device = MATE_MIXER_PULSE_DEVICE (object);
 
     switch (param_id) {
-    case PROP_IDENTIFIER:
-        g_value_set_string (value, device->priv->identifier);
-        break;
     case PROP_NAME:
         g_value_set_string (value, device->priv->name);
         break;
+    case PROP_DESCRIPTION:
+        g_value_set_string (value, device->priv->description);
+        break;
     case PROP_ICON:
         g_value_set_string (value, device->priv->icon);
         break;
     case PROP_ACTIVE_PROFILE:
-        g_value_set_object (value, device->priv->active_profile);
+        g_value_set_object (value, device->priv->profile);
         break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
@@ -113,20 +116,15 @@ mate_mixer_pulse_device_set_property (GObject       *object,
     device = MATE_MIXER_PULSE_DEVICE (object);
 
     switch (param_id) {
-    case PROP_IDENTIFIER:
-        device->priv->identifier = g_strdup (g_value_get_string (value));
-        break;
     case PROP_NAME:
         device->priv->name = g_strdup (g_value_get_string (value));
         break;
+    case PROP_DESCRIPTION:
+        device->priv->description = g_strdup (g_value_get_string (value));
+        break;
     case PROP_ICON:
         device->priv->icon = g_strdup (g_value_get_string (value));
         break;
-    case PROP_ACTIVE_PROFILE:
-        mate_mixer_pulse_device_set_active_profile (
-            MATE_MIXER_DEVICE (device),
-            g_value_get_object (value));
-        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
         break;
@@ -134,24 +132,38 @@ mate_mixer_pulse_device_set_property (GObject       *object,
 }
 
 static void
-mate_mixer_pulse_device_finalize (GObject *object)
+mate_mixer_pulse_device_dispose (GObject *object)
 {
     MateMixerPulseDevice *device;
 
     device = MATE_MIXER_PULSE_DEVICE (object);
 
-    g_free (device->priv->identifier);
-    g_free (device->priv->name);
-    g_free (device->priv->icon);
-
-    if (device->priv->profiles != NULL)
+    if (device->priv->profiles != NULL) {
         g_list_free_full (device->priv->profiles, g_object_unref);
+        device->priv->profiles = NULL;
+    }
 
-    if (device->priv->ports != NULL)
+    if (device->priv->ports != NULL) {
         g_list_free_full (device->priv->ports, g_object_unref);
+        device->priv->ports = NULL;
+    }
+
+    g_clear_object (&device->priv->profile);
+    g_clear_object (&device->priv->connection);
+
+    G_OBJECT_CLASS (mate_mixer_pulse_device_parent_class)->dispose (object);
+}
+
+static void
+mate_mixer_pulse_device_finalize (GObject *object)
+{
+    MateMixerPulseDevice *device;
 
-    if (device->priv->active_profile != NULL)
-        g_object_unref (device->priv->active_profile);
+    device = MATE_MIXER_PULSE_DEVICE (object);
+
+    g_free (device->priv->name);
+    g_free (device->priv->description);
+    g_free (device->priv->icon);
 
     G_OBJECT_CLASS (mate_mixer_pulse_device_parent_class)->finalize (object);
 }
@@ -162,12 +174,13 @@ mate_mixer_pulse_device_class_init (MateMixerPulseDeviceClass *klass)
     GObjectClass *object_class;
 
     object_class = G_OBJECT_CLASS (klass);
+    object_class->dispose      = mate_mixer_pulse_device_dispose;
     object_class->finalize     = mate_mixer_pulse_device_finalize;
     object_class->get_property = mate_mixer_pulse_device_get_property;
     object_class->set_property = mate_mixer_pulse_device_set_property;
 
-    g_object_class_override_property (object_class, PROP_IDENTIFIER, "identifier");
     g_object_class_override_property (object_class, PROP_NAME, "name");
+    g_object_class_override_property (object_class, PROP_DESCRIPTION, "description");
     g_object_class_override_property (object_class, PROP_ICON, "icon");
     g_object_class_override_property (object_class, PROP_ACTIVE_PROFILE, "active-profile");
 
@@ -175,19 +188,19 @@ mate_mixer_pulse_device_class_init (MateMixerPulseDeviceClass *klass)
 }
 
 MateMixerPulseDevice *
-mate_mixer_pulse_device_new (const pa_card_info *info)
+mate_mixer_pulse_device_new (MateMixerPulseConnection *connection, const pa_card_info *info)
 {
-    MateMixerPulseDevice    *device;
-    MateMixerDeviceProfile  *active_profile = NULL;
-    GList                   *profiles = NULL;
-    GList                   *ports = NULL;
-    guint32                  i;
+    MateMixerPulseDevice *device;
+    MateMixerProfile     *active_profile = NULL;
+    GList                *profiles = NULL;
+    GList                *ports = NULL;
+    guint32               i;
 
     g_return_val_if_fail (info != NULL, NULL);
 
     /* Create a list of card profiles */
     for (i = 0; i < info->n_profiles; i++) {
-        MateMixerDeviceProfile *profile;
+        MateMixerProfile *profile;
 
 #if PA_CHECK_VERSION(5, 0, 0)
         pa_card_profile_info2 *p_info = info->profiles2[i];
@@ -199,9 +212,9 @@ mate_mixer_pulse_device_new (const pa_card_info *info)
             continue;
 #else
         /* The old profile list is an array of structs, not pointers */
-        pa_card_profile_info  *p_info = &info->profiles[i];
+        pa_card_profile_info *p_info = &info->profiles[i];
 #endif
-        profile = mate_mixer_device_profile_new (
+        profile = mate_mixer_profile_new (
             p_info->name,
             p_info->description,
             p_info->priority);
@@ -222,28 +235,27 @@ mate_mixer_pulse_device_new (const pa_card_info *info)
 
     /* Create a list of card ports */
     for (i = 0; i < info->n_ports; i++) {
-        MateMixerDevicePort          *port;
-        MateMixerDevicePortDirection  direction = 0;
-        MateMixerDevicePortStatus     status = 0;
-        pa_card_port_info            *p_info = info->ports[i];
-
-        if (p_info->direction & PA_DIRECTION_INPUT)
-            direction |= MATE_MIXER_DEVICE_PORT_DIRECTION_INPUT;
-
-        if (p_info->direction & PA_DIRECTION_OUTPUT)
-            direction |= MATE_MIXER_DEVICE_PORT_DIRECTION_OUTPUT;
+        MateMixerPort       *port;
+        MateMixerPortStatus  status = MATE_MIXER_PORT_UNKNOWN_STATUS;
+        pa_card_port_info   *p_info = info->ports[i];
 
 #if PA_CHECK_VERSION(2, 0, 0)
-        if (p_info->available == PA_PORT_AVAILABLE_YES)
-            status |= MATE_MIXER_DEVICE_PORT_STATUS_AVAILABLE;
+        switch (p_info->available) {
+        case PA_PORT_AVAILABLE_YES:
+            status = MATE_MIXER_PORT_AVAILABLE;
+            break;
+        case PA_PORT_AVAILABLE_NO:
+            status = MATE_MIXER_PORT_UNAVAILABLE;
+            break;
+        default:
+            break;
+        }
 #endif
-        port = mate_mixer_device_port_new (
-            p_info->name,
-            p_info->description,
-            pa_proplist_gets (p_info->proplist, "device.icon_name"),
-            p_info->priority,
-            direction,
-            status);
+        port = mate_mixer_port_new (p_info->name,
+                                    p_info->description,
+                                    pa_proplist_gets (p_info->proplist, "device.icon_name"),
+                                    p_info->priority,
+                                    status);
 
         ports = g_list_prepend (ports, port);
     }
@@ -253,15 +265,21 @@ mate_mixer_pulse_device_new (const pa_card_info *info)
         ports = g_list_reverse (ports);
 
     device = g_object_new (MATE_MIXER_TYPE_PULSE_DEVICE,
-        "identifier",      info->name,
-        "name",            pa_proplist_gets (info->proplist, "device.description"),
-        "icon",            pa_proplist_gets (info->proplist, "device.icon_name"),
-        "active-profile",  active_profile,
-        NULL);
+                           "name", info->name,
+                           "description", pa_proplist_gets (info->proplist, "device.description"),
+                           "icon", pa_proplist_gets (info->proplist, "device.icon_name"),
+                           NULL);
+
+    if (profiles) {
+        device->priv->profiles = profiles;
+
+        if (G_LIKELY (active_profile))
+            device->priv->profile = g_object_ref (active_profile);
+    }
 
     device->priv->index = info->index;
-    device->priv->profiles = profiles;
     device->priv->ports = ports;
+    device->priv->connection = g_object_ref (connection);
 
     return device;
 }
@@ -276,45 +294,104 @@ mate_mixer_pulse_device_update (MateMixerPulseDevice *device, const pa_card_info
     return TRUE;
 }
 
+MateMixerPulseConnection *
+mate_mixer_pulse_device_get_connection (MateMixerPulseDevice *device)
+{
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), NULL);
+
+    return device->priv->connection;
+}
+
+guint32
+mate_mixer_pulse_device_get_index (MateMixerPulseDevice *device)
+{
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), 0);
+
+    return device->priv->index;
+}
+
+const gchar *
+mate_mixer_pulse_device_get_name (MateMixerDevice *device)
+{
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), NULL);
+
+    return MATE_MIXER_PULSE_DEVICE (device)->priv->name;
+}
+
+const gchar *
+mate_mixer_pulse_device_get_description (MateMixerDevice *device)
+{
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), NULL);
+
+    return MATE_MIXER_PULSE_DEVICE (device)->priv->description;
+}
+
+const gchar *
+mate_mixer_pulse_device_get_icon (MateMixerDevice *device)
+{
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), NULL);
+
+    return MATE_MIXER_PULSE_DEVICE (device)->priv->icon;
+}
+
 const GList *
-mate_mixer_pulse_device_list_tracks (MateMixerDevice *device)
+mate_mixer_pulse_device_list_streams (MateMixerDevice *device)
 {
     // TODO
     return NULL;
 }
 
 const GList *
-mate_mixer_pulse_device_get_ports (MateMixerDevice *device)
+mate_mixer_pulse_device_list_ports (MateMixerDevice *device)
 {
     g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), NULL);
 
-    return MATE_MIXER_PULSE_DEVICE (device)->priv->ports;
+    return (const GList *) MATE_MIXER_PULSE_DEVICE (device)->priv->ports;
 }
 
 const GList *
-mate_mixer_pulse_device_get_profiles (MateMixerDevice *device)
+mate_mixer_pulse_device_list_profiles (MateMixerDevice *device)
 {
     g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), NULL);
 
-    return MATE_MIXER_PULSE_DEVICE (device)->priv->profiles;
+    return (const GList *) MATE_MIXER_PULSE_DEVICE (device)->priv->profiles;
 }
 
-MateMixerDeviceProfile *
+MateMixerProfile *
 mate_mixer_pulse_device_get_active_profile (MateMixerDevice *device)
 {
     g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), NULL);
 
-    return MATE_MIXER_PULSE_DEVICE (device)->priv->active_profile;
+    return MATE_MIXER_PULSE_DEVICE (device)->priv->profile;
 }
 
 gboolean
-mate_mixer_pulse_device_set_active_profile (MateMixerDevice *device,
-                                            MateMixerDeviceProfile *profile)
+mate_mixer_pulse_device_set_active_profile (MateMixerDevice *device, const gchar *name)
 {
+    gboolean ret;
+    MateMixerPulseDevicePrivate *priv;
+
     g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), FALSE);
-    g_return_val_if_fail (MATE_MIXER_IS_DEVICE_PROFILE (profile), FALSE);
+    g_return_val_if_fail (name != NULL, FALSE);
 
-    // TODO
-    // pa_context_set_card_profile_by_index ()
-    return TRUE;
+    priv = MATE_MIXER_PULSE_DEVICE (device)->priv;
+    ret  = mate_mixer_pulse_connection_set_card_profile (priv->connection,
+                                                         priv->name,
+                                                         name);
+
+    // XXX decide to either confirm the change during the connection call or
+    // wait for a notification from Pulse
+/*
+    if (ret) {
+        if (priv->profile)
+            g_object_unref (priv->profile);
+
+        priv->profile = g_object_ref (profile);
+
+        g_object_notify_by_pspec (
+            G_OBJECT (device),
+            properties[PROP_ACTIVE_PROFILE]);
+    }
+*/
+    return ret;
 }
diff --git a/backends/pulse/pulse-device.h b/backends/pulse/pulse-device.h
index ab997fe..896b02b 100644
--- a/backends/pulse/pulse-device.h
+++ b/backends/pulse/pulse-device.h
@@ -22,10 +22,12 @@
 #include <glib-object.h>
 
 #include <libmatemixer/matemixer-device.h>
-#include <libmatemixer/matemixer-device-profile.h>
+#include <libmatemixer/matemixer-profile.h>
 
 #include <pulse/pulseaudio.h>
 
+#include "pulse-connection.h"
+
 G_BEGIN_DECLS
 
 #define MATE_MIXER_TYPE_PULSE_DEVICE            \
@@ -54,26 +56,35 @@ struct _MateMixerPulseDevice
 
 struct _MateMixerPulseDeviceClass
 {
-    GObjectClass parent;    
+    GObjectClass parent;
 };
 
 GType mate_mixer_pulse_device_get_type (void) G_GNUC_CONST;
 
-MateMixerPulseDevice    *mate_mixer_pulse_device_new (const pa_card_info *info);
+MateMixerPulseDevice    *mate_mixer_pulse_device_new (MateMixerPulseConnection *connection,
+                                                      const pa_card_info *info);
 
 gboolean                 mate_mixer_pulse_device_update (MateMixerPulseDevice *device,
                                                          const pa_card_info *info);
 
+MateMixerPulseConnection *mate_mixer_pulse_device_get_connection (MateMixerPulseDevice *device);
+
+guint32                  mate_mixer_pulse_device_get_index (MateMixerPulseDevice *device);
+
 /* Interface implementation */
-const GList             *mate_mixer_pulse_device_list_tracks (MateMixerDevice *device);
+const gchar *mate_mixer_pulse_device_get_name (MateMixerDevice *device);
+const gchar *mate_mixer_pulse_device_get_description (MateMixerDevice *device);
+const gchar *mate_mixer_pulse_device_get_icon (MateMixerDevice *device);
+
+const GList             *mate_mixer_pulse_device_list_streams (MateMixerDevice *device);
 
-const GList             *mate_mixer_pulse_device_get_ports (MateMixerDevice *device);
-const GList             *mate_mixer_pulse_device_get_profiles (MateMixerDevice *device);
+const GList             *mate_mixer_pulse_device_list_ports (MateMixerDevice *device);
+const GList             *mate_mixer_pulse_device_list_profiles (MateMixerDevice *device);
 
-MateMixerDeviceProfile  *mate_mixer_pulse_device_get_active_profile (MateMixerDevice *device);
+MateMixerProfile  *mate_mixer_pulse_device_get_active_profile (MateMixerDevice *device);
 
 gboolean                 mate_mixer_pulse_device_set_active_profile (MateMixerDevice *device,
-                                                                     MateMixerDeviceProfile *profile);
+                                                                     const gchar *name);
 
 G_END_DECLS
 
diff --git a/backends/pulse/pulse-sink-input.c b/backends/pulse/pulse-sink-input.c
new file mode 100644
index 0000000..e69de29
diff --git a/backends/pulse/pulse-sink-input.h b/backends/pulse/pulse-sink-input.h
new file mode 100644
index 0000000..e69de29
diff --git a/backends/pulse/pulse-sink.c b/backends/pulse/pulse-sink.c
new file mode 100644
index 0000000..64eb4c1
--- /dev/null
+++ b/backends/pulse/pulse-sink.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2014 Michal Ratajsky <michal.ratajsky@gmail.com>
+ *
+ * This library 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 of the licence, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <libmatemixer/matemixer-stream.h>
+#include <libmatemixer/matemixer-port.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "pulse-connection.h"
+#include "pulse-stream.h"
+#include "pulse-sink.h"
+
+struct _MateMixerPulseSinkPrivate
+{
+    guint32 index_monitor;
+};
+
+G_DEFINE_TYPE (MateMixerPulseSink, mate_mixer_pulse_sink, MATE_MIXER_TYPE_PULSE_STREAM);
+
+static void
+mate_mixer_pulse_sink_init (MateMixerPulseSink *sink)
+{
+    sink->priv = G_TYPE_INSTANCE_GET_PRIVATE (sink,
+                                              MATE_MIXER_TYPE_PULSE_SINK,
+                                              MateMixerPulseSinkPrivate);
+}
+
+static void
+mate_mixer_pulse_sink_class_init (MateMixerPulseSinkClass *klass)
+{
+    MateMixerPulseStreamClass *stream_class;
+
+    stream_class = MATE_MIXER_PULSE_STREAM_CLASS (klass);
+
+    stream_class->set_volume = mate_mixer_pulse_sink_set_volume;
+    stream_class->set_mute = mate_mixer_pulse_sink_set_mute;
+
+    g_type_class_add_private (G_OBJECT (klass), sizeof (MateMixerPulseSinkPrivate));
+}
+
+MateMixerPulseStream *
+mate_mixer_pulse_sink_new (MateMixerPulseConnection *connection, const pa_sink_info *info)
+{
+    MateMixerPulseStream *stream;
+    GList *ports = NULL;
+    int i;
+
+    for (i = 0; i < info->n_ports; i++) {
+        MateMixerPort       *port;
+        MateMixerPortStatus  status = MATE_MIXER_PORT_UNKNOWN_STATUS;
+        pa_sink_port_info   *p_info = info->ports[i];
+
+#if PA_CHECK_VERSION(2, 0, 0)
+        switch (p_info->available) {
+        case PA_PORT_AVAILABLE_YES:
+            status = MATE_MIXER_PORT_AVAILABLE;
+            break;
+        case PA_PORT_AVAILABLE_NO:
+            status = MATE_MIXER_PORT_UNAVAILABLE;
+            break;
+        default:
+            break;
+        }
+#endif
+        port = mate_mixer_port_new (p_info->name,
+                                    p_info->description,
+                                    NULL,
+                                    p_info->priority,
+                                    status);
+
+        ports = g_list_prepend (ports, port);
+    }
+
+    if (ports)
+        ports = g_list_reverse (ports);
+
+    stream = g_object_new (MATE_MIXER_TYPE_PULSE_STREAM,
+                           "connection", connection,
+                           "index", info->index,
+                           "name", info->name,
+                           "description", info->description,
+                           "channels", info->channel_map.channels,
+                           "mute", info->mute ? TRUE : FALSE,
+                           NULL);
+
+    return stream;
+}
+
+gboolean
+mate_mixer_pulse_sink_set_volume (MateMixerStream *stream, guint32 volume)
+{
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE);
+
+/*
+    return mate_mixer_pulse_connection_set_sink_volume (mate_mixer_pulse_stream_get_connection (MATE_MIXER_PULSE_STREAM (stream)),
+                                                        volume);
+*/
+    return TRUE;
+}
+
+gboolean
+mate_mixer_pulse_sink_set_mute (MateMixerStream *stream, gboolean mute)
+{
+    MateMixerPulseStream *pulse_stream;
+
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE);
+
+    pulse_stream = MATE_MIXER_PULSE_STREAM (stream);
+
+    return mate_mixer_pulse_connection_set_sink_mute (mate_mixer_pulse_stream_get_connection (pulse_stream),
+                                                      mate_mixer_pulse_stream_get_index (pulse_stream),
+                                                      mute);
+}
diff --git a/backends/pulse/pulse-sink.h b/backends/pulse/pulse-sink.h
new file mode 100644
index 0000000..ef2608f
--- /dev/null
+++ b/backends/pulse/pulse-sink.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 Michal Ratajsky <michal.ratajsky@gmail.com>
+ *
+ * This library 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 of the licence, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MATEMIXER_PULSE_SINK_H
+#define MATEMIXER_PULSE_SINK_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <libmatemixer/matemixer-stream.h>
+
+#include <pulse/pulseaudio.h>
+
+G_BEGIN_DECLS
+
+#define MATE_MIXER_TYPE_PULSE_SINK            \
+        (mate_mixer_pulse_sink_get_type ())
+#define MATE_MIXER_PULSE_SINK(o)              \
+        (G_TYPE_CHECK_INSTANCE_CAST ((o), MATE_MIXER_TYPE_PULSE_SINK, MateMixerPulseSink))
+#define MATE_MIXER_IS_PULSE_SINK(o)           \
+        (G_TYPE_CHECK_INSTANCE_TYPE ((o), MATE_MIXER_TYPE_PULSE_SINK))
+#define MATE_MIXER_PULSE_SINK_CLASS(k)        \
+        (G_TYPE_CHECK_CLASS_CAST ((k), MATE_MIXER_TYPE_PULSE_SINK, MateMixerPulseSinkClass))
+#define MATE_MIXER_IS_PULSE_SINK_CLASS(k)     \
+        (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), MATE_MIXER_TYPE_PULSE_SINK))
+#define MATE_MIXER_PULSE_SINK_GET_CLASS(o)    \
+        (G_TYPE_INSTANCE_GET_CLASS ((o), MATE_MIXER_TYPE_PULSE_SINK, MateMixerPulseSinkClass))
+
+typedef struct _MateMixerPulseSink         MateMixerPulseSink;
+typedef struct _MateMixerPulseSinkClass    MateMixerPulseSinkClass;
+typedef struct _MateMixerPulseSinkPrivate  MateMixerPulseSinkPrivate;
+
+struct _MateMixerPulseSink
+{
+    GObject parent;
+
+    MateMixerPulseSinkPrivate *priv;
+};
+
+struct _MateMixerPulseSinkClass
+{
+    GObjectClass parent;
+};
+
+GType mate_mixer_pulse_sink_get_type (void) G_GNUC_CONST;
+
+MateMixerPulseStream *mate_mixer_pulse_sink_new (MateMixerPulseConnection *connection,
+                                                const pa_sink_info *info);
+
+gboolean mate_mixer_pulse_sink_set_volume (MateMixerStream *stream, guint32 volume);
+gboolean mate_mixer_pulse_sink_set_mute (MateMixerStream *stream, gboolean mute);
+
+G_END_DECLS
+
+#endif /* MATEMIXER_PULSE_SINK_H */
diff --git a/backends/pulse/pulse-source-output.c b/backends/pulse/pulse-source-output.c
new file mode 100644
index 0000000..e69de29
diff --git a/backends/pulse/pulse-source-output.h b/backends/pulse/pulse-source-output.h
new file mode 100644
index 0000000..e69de29
diff --git a/backends/pulse/pulse-source.c b/backends/pulse/pulse-source.c
new file mode 100644
index 0000000..e69de29
diff --git a/backends/pulse/pulse-source.h b/backends/pulse/pulse-source.h
new file mode 100644
index 0000000..e69de29
diff --git a/backends/pulse/pulse-stream.c b/backends/pulse/pulse-stream.c
new file mode 100644
index 0000000..a9e01d1
--- /dev/null
+++ b/backends/pulse/pulse-stream.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2014 Michal Ratajsky <michal.ratajsky@gmail.com>
+ *
+ * This library 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 of the licence, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <libmatemixer/matemixer-stream.h>
+#include <libmatemixer/matemixer-port.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "pulse-connection.h"
+#include "pulse-stream.h"
+
+struct _MateMixerPulseStreamPrivate
+{
+    guint32                   index;
+    gchar                    *name;
+    gchar                    *description;
+    gchar                    *icon;
+    guint                     channels;
+    gboolean                  mute;
+    GList                    *ports;
+    MateMixerPort            *port;
+    MateMixerPulseConnection *connection;
+};
+
+enum
+{
+    PROP_0,
+    PROP_INDEX,
+    PROP_NAME,
+    PROP_DESCRIPTION,
+    PROP_ICON,
+    PROP_CHANNELS,
+    PROP_VOLUME,
+    PROP_MUTE,
+    N_PROPERTIES
+};
+
+static void mate_mixer_stream_interface_init   (MateMixerStreamInterface  *iface);
+static void mate_mixer_pulse_stream_class_init (MateMixerPulseStreamClass *klass);
+static void mate_mixer_pulse_stream_init       (MateMixerPulseStream      *stream);
+static void mate_mixer_pulse_stream_dispose    (GObject                   *object);
+static void mate_mixer_pulse_stream_finalize   (GObject                   *object);
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (MateMixerPulseStream, mate_mixer_pulse_stream, G_TYPE_OBJECT,
+                                  G_IMPLEMENT_INTERFACE (MATE_MIXER_TYPE_STREAM,
+                                                         mate_mixer_stream_interface_init))
+
+static void
+mate_mixer_stream_interface_init (MateMixerStreamInterface *iface)
+{
+    iface->get_name = mate_mixer_pulse_stream_get_name;
+    iface->get_description = mate_mixer_pulse_stream_get_description;
+    iface->get_icon = mate_mixer_pulse_stream_get_icon;
+    iface->list_ports = mate_mixer_pulse_stream_list_ports;
+}
+
+static void
+mate_mixer_pulse_stream_get_property (GObject     *object,
+                                      guint        param_id,
+                                      GValue      *value,
+                                      GParamSpec  *pspec)
+{
+    MateMixerPulseStream *stream;
+
+    stream = MATE_MIXER_PULSE_STREAM (object);
+
+    switch (param_id) {
+    case PROP_NAME:
+        g_value_set_string (value, stream->priv->name);
+        break;
+    case PROP_DESCRIPTION:
+        g_value_set_string (value, stream->priv->description);
+        break;
+    case PROP_ICON:
+        g_value_set_string (value, stream->priv->icon);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+        break;
+    }
+}
+
+static void
+mate_mixer_pulse_stream_set_property (GObject       *object,
+                                      guint          param_id,
+                                      const GValue  *value,
+                                      GParamSpec    *pspec)
+{
+    MateMixerPulseStream *stream;
+
+    stream = MATE_MIXER_PULSE_STREAM (object);
+
+    switch (param_id) {
+    case PROP_NAME:
+        stream->priv->name = g_strdup (g_value_get_string (value));
+        break;
+    case PROP_DESCRIPTION:
+        stream->priv->description = g_strdup (g_value_get_string (value));
+        break;
+    case PROP_ICON:
+        stream->priv->icon = g_strdup (g_value_get_string (value));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+        break;
+    }
+}
+
+static void
+mate_mixer_pulse_stream_class_init (MateMixerPulseStreamClass *klass)
+{
+    GObjectClass *object_class;
+
+    object_class = G_OBJECT_CLASS (klass);
+    object_class->dispose      = mate_mixer_pulse_stream_dispose;
+    object_class->finalize     = mate_mixer_pulse_stream_finalize;
+    object_class->get_property = mate_mixer_pulse_stream_get_property;
+    object_class->set_property = mate_mixer_pulse_stream_set_property;
+
+    g_object_class_override_property (object_class, PROP_NAME, "name");
+    g_object_class_override_property (object_class, PROP_DESCRIPTION, "description");
+    g_object_class_override_property (object_class, PROP_ICON, "icon");
+
+    g_type_class_add_private (object_class, sizeof (MateMixerPulseStreamPrivate));
+}
+
+static void
+mate_mixer_pulse_stream_init (MateMixerPulseStream *stream)
+{
+    stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (
+        stream,
+        MATE_MIXER_TYPE_PULSE_STREAM,
+        MateMixerPulseStreamPrivate);
+}
+
+static void
+mate_mixer_pulse_stream_dispose (GObject *object)
+{
+    MateMixerPulseStream *stream;
+
+    stream = MATE_MIXER_PULSE_STREAM (object);
+
+    if (stream->priv->ports) {
+        g_list_free_full (stream->priv->ports, g_object_unref);
+        stream->priv->ports = NULL;
+    }
+    g_clear_object (&stream->priv->connection);
+
+    G_OBJECT_CLASS (mate_mixer_pulse_stream_parent_class)->dispose (object);
+}
+
+static void
+mate_mixer_pulse_stream_finalize (GObject *object)
+{
+    MateMixerPulseStream *stream;
+
+    stream = MATE_MIXER_PULSE_STREAM (object);
+
+    g_free (stream->priv->name);
+    g_free (stream->priv->description);
+    g_free (stream->priv->icon);
+
+    G_OBJECT_CLASS (mate_mixer_pulse_stream_parent_class)->finalize (object);
+}
+
+MateMixerPulseConnection *
+mate_mixer_pulse_stream_get_connection (MateMixerPulseStream *stream)
+{
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), NULL);
+
+    return stream->priv->connection;
+}
+
+guint32
+mate_mixer_pulse_stream_get_index (MateMixerPulseStream *stream)
+{
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE);
+
+    return stream->priv->index;
+}
+
+const gchar *
+mate_mixer_pulse_stream_get_name (MateMixerStream *stream)
+{
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE);
+
+    return MATE_MIXER_PULSE_STREAM (stream)->priv->name;
+}
+
+const gchar *
+mate_mixer_pulse_stream_get_description (MateMixerStream *stream)
+{
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE);
+
+    return MATE_MIXER_PULSE_STREAM (stream)->priv->description;
+}
+
+const gchar *
+mate_mixer_pulse_stream_get_icon (MateMixerStream *stream)
+{
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE);
+
+    return MATE_MIXER_PULSE_STREAM (stream)->priv->icon;
+}
+
+MateMixerPort *
+mate_mixer_pulse_stream_get_active_port (MateMixerStream *stream)
+{
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE);
+
+    return MATE_MIXER_PULSE_STREAM (stream)->priv->port;
+}
+
+const GList *
+mate_mixer_pulse_stream_list_ports (MateMixerStream *stream)
+{
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE);
+
+    return MATE_MIXER_PULSE_STREAM (stream)->priv->ports;
+}
+
+gboolean
+mate_mixer_pulse_stream_set_volume (MateMixerStream *stream, guint32 volume)
+{
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE);
+
+    return MATE_MIXER_PULSE_STREAM_GET_CLASS (stream)->set_volume (stream, volume);
+}
+
+gboolean
+mate_mixer_pulse_stream_set_mute (MateMixerStream *stream, gboolean mute)
+{
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE);
+
+    return MATE_MIXER_PULSE_STREAM_GET_CLASS (stream)->set_mute (stream, mute);
+}
+
+gboolean
+mate_mixer_pulse_stream_set_active_port (MateMixerStream *stream, MateMixerPort *port)
+{
+    return TRUE;
+}
diff --git a/backends/pulse/pulse-stream.h b/backends/pulse/pulse-stream.h
new file mode 100644
index 0000000..3d3ee78
--- /dev/null
+++ b/backends/pulse/pulse-stream.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2014 Michal Ratajsky <michal.ratajsky@gmail.com>
+ *
+ * This library 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 of the licence, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MATEMIXER_PULSE_STREAM_H
+#define MATEMIXER_PULSE_STREAM_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <libmatemixer/matemixer-stream.h>
+#include <libmatemixer/matemixer-port.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "pulse-connection.h"
+
+G_BEGIN_DECLS
+
+#define MATE_MIXER_TYPE_PULSE_STREAM            \
+        (mate_mixer_pulse_stream_get_type ())
+#define MATE_MIXER_PULSE_STREAM(o)              \
+        (G_TYPE_CHECK_INSTANCE_CAST ((o), MATE_MIXER_TYPE_PULSE_STREAM, MateMixerPulseStream))
+#define MATE_MIXER_IS_PULSE_STREAM(o)           \
+        (G_TYPE_CHECK_INSTANCE_TYPE ((o), MATE_MIXER_TYPE_PULSE_STREAM))
+#define MATE_MIXER_PULSE_STREAM_CLASS(k)        \
+        (G_TYPE_CHECK_CLASS_CAST ((k), MATE_MIXER_TYPE_PULSE_STREAM, MateMixerPulseStreamClass))
+#define MATE_MIXER_IS_PULSE_STREAM_CLASS(k)     \
+        (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), MATE_MIXER_TYPE_PULSE_STREAM))
+#define MATE_MIXER_PULSE_STREAM_GET_CLASS(o)    \
+        (G_TYPE_INSTANCE_GET_CLASS ((o), MATE_MIXER_TYPE_PULSE_STREAM, MateMixerPulseStreamClass))
+
+typedef struct _MateMixerPulseStream         MateMixerPulseStream;
+typedef struct _MateMixerPulseStreamClass    MateMixerPulseStreamClass;
+typedef struct _MateMixerPulseStreamPrivate  MateMixerPulseStreamPrivate;
+
+struct _MateMixerPulseStream
+{
+    GObject parent;
+
+    MateMixerPulseStreamPrivate *priv;
+};
+
+struct _MateMixerPulseStreamClass
+{
+    GObjectClass parent;
+
+    gboolean (*set_volume) (MateMixerStream *stream, guint32 volume);
+    gboolean (*set_mute)   (MateMixerStream *stream, gboolean mute);
+};
+
+GType mate_mixer_pulse_stream_get_type (void) G_GNUC_CONST;
+
+MateMixerPulseConnection * mate_mixer_pulse_stream_get_connection  (MateMixerPulseStream *stream);
+guint32                    mate_mixer_pulse_stream_get_index       (MateMixerPulseStream *stream);
+
+/* Interface implementation */
+const gchar *              mate_mixer_pulse_stream_get_name        (MateMixerStream      *stream);
+const gchar *              mate_mixer_pulse_stream_get_description (MateMixerStream      *stream);
+const gchar *              mate_mixer_pulse_stream_get_icon (MateMixerStream *stream);
+
+MateMixerPort *            mate_mixer_pulse_stream_get_active_port (MateMixerStream *stream);
+gboolean                   mate_mixer_pulse_stream_set_active_port (MateMixerStream *stream,
+                                                                    MateMixerPort   *port);
+const GList *              mate_mixer_pulse_stream_list_ports      (MateMixerStream      *stream);
+gboolean                   mate_mixer_pulse_stream_set_mute        (MateMixerStream      *stream,
+                                                                    gboolean              mute);
+gboolean                   mate_mixer_pulse_stream_set_volume      (MateMixerStream      *stream,
+                                                                    guint32               volume);
+
+G_END_DECLS
+
+#endif /* MATEMIXER_PULSE_STREAM_H */
diff --git a/backends/pulse/pulse-track.c b/backends/pulse/pulse-track.c
deleted file mode 100644
index e69de29..0000000
diff --git a/backends/pulse/pulse-track.h b/backends/pulse/pulse-track.h
deleted file mode 100644
index e69de29..0000000
diff --git a/backends/pulse/pulse.c b/backends/pulse/pulse.c
index d306577..59c5935 100644
--- a/backends/pulse/pulse.c
+++ b/backends/pulse/pulse.c
@@ -17,8 +17,6 @@
 
 #include <glib.h>
 #include <glib-object.h>
-#include <sys/types.h>
-#include <unistd.h>
 
 #include <libmatemixer/matemixer-backend.h>
 #include <libmatemixer/matemixer-backend-module.h>
@@ -27,47 +25,59 @@
 #include <pulse/thread-mainloop.h>
 
 #include "pulse.h"
+#include "pulse-connection.h"
 #include "pulse-device.h"
+#include "pulse-stream.h"
+#include "pulse-sink.h"
 
 #define BACKEND_NAME      "PulseAudio"
 #define BACKEND_PRIORITY   0
 
 struct _MateMixerPulsePrivate
 {
-    pa_threaded_mainloop  *mainloop;
-    pa_context            *context;
-    GHashTable            *devices;
+    GHashTable               *devices;
+    gboolean                  lists_loaded;
+    GHashTable               *cards;
+    GHashTable               *sinks;
+    GHashTable               *sink_inputs;
+    GHashTable               *sources;
+    GHashTable               *source_outputs;
+    MateMixerPulseConnection *connection;
 };
 
 /* Support function for dynamic loading of the backend module */
 void  backend_module_init (GTypeModule *module);
 
-const MateMixerBackendModuleInfo *backend_module_get_info (void);
+const MateMixerBackendInfo *backend_module_get_info (void);
 
-static void    mate_mixer_backend_interface_init (MateMixerBackendInterface *iface);
+static void mate_mixer_backend_interface_init (MateMixerBackendInterface *iface);
 
-static void    pulse_card_info_cb (pa_context *c,
-                                   const pa_card_info *info,
-                                   int eol,
-                                   void *userdata);
+static void pulse_card_cb (MateMixerPulseConnection *connection,
+                            const pa_card_info *info,
+                            MateMixerPulse *pulse);
 
-static void    pulse_card_update  (MateMixerPulse *pulse, const pa_card_info *i);
+static void pulse_sink_cb (MateMixerPulseConnection *connection,
+                            const pa_sink_info *info,
+                            MateMixerPulse *pulse);
 
-static void    pulse_state_cb     (pa_context *c, void *userdata);
+static void pulse_sink_input_cb (MateMixerPulseConnection *connection,
+                            const pa_sink_input_info *info,
+                            MateMixerPulse *pulse);
 
-static void    pulse_subscribe_cb (pa_context *c,
-                                   pa_subscription_event_type_t t,
-                                   uint32_t idx,
-                                   void *userdata);
+static void pulse_source_cb (MateMixerPulseConnection *connection,
+                            const pa_source_info *info,
+                            MateMixerPulse *pulse);
 
-static gchar  *pulse_get_app_name (void);
+static void pulse_source_output_cb (MateMixerPulseConnection *connection,
+                            const pa_source_output_info *info,
+                            MateMixerPulse *pulse);
 
 G_DEFINE_DYNAMIC_TYPE_EXTENDED (MateMixerPulse, mate_mixer_pulse,
                                 G_TYPE_OBJECT, 0,
                                 G_IMPLEMENT_INTERFACE_DYNAMIC (MATE_MIXER_TYPE_BACKEND,
                                                                mate_mixer_backend_interface_init))
 
-static MateMixerBackendModuleInfo info;
+static MateMixerBackendInfo info;
 
 void
 backend_module_init (GTypeModule *module)
@@ -77,10 +87,10 @@ backend_module_init (GTypeModule *module)
     info.name         = BACKEND_NAME;
     info.priority     = BACKEND_PRIORITY;
     info.g_type       = MATE_MIXER_TYPE_PULSE;
-    info.backend_type = MATE_MIXER_BACKEND_TYPE_PULSE;
+    info.backend_type = MATE_MIXER_BACKEND_PULSE;
 }
 
-const MateMixerBackendModuleInfo *
+const MateMixerBackendInfo *
 backend_module_get_info (void)
 {
     return &info;
@@ -107,18 +117,70 @@ mate_mixer_pulse_init (MateMixerPulse *pulse)
         g_direct_equal,
         NULL,
         g_object_unref);
+
+    pulse->priv->cards = g_hash_table_new_full (
+        g_direct_hash,
+        g_direct_equal,
+        NULL,
+        g_object_unref);
+    pulse->priv->sinks = g_hash_table_new_full (
+        g_direct_hash,
+        g_direct_equal,
+        NULL,
+        g_object_unref);
+    pulse->priv->sink_inputs = g_hash_table_new_full (
+        g_direct_hash,
+        g_direct_equal,
+        NULL,
+        g_object_unref);
+    pulse->priv->sources = g_hash_table_new_full (
+        g_direct_hash,
+        g_direct_equal,
+        NULL,
+        g_object_unref);
+    pulse->priv->source_outputs = g_hash_table_new_full (
+        g_direct_hash,
+        g_direct_equal,
+        NULL,
+        g_object_unref);
 }
 
 static void
-mate_mixer_pulse_finalize (GObject *object)
+mate_mixer_pulse_dispose (GObject *object)
 {
     MateMixerPulse *pulse;
 
     pulse = MATE_MIXER_PULSE (object);
 
-    g_hash_table_destroy (pulse->priv->devices);
+    if (pulse->priv->devices) {
+        g_hash_table_destroy (pulse->priv->devices);
+        pulse->priv->devices = NULL;
+    }
+
+    if (pulse->priv->cards) {
+        g_hash_table_destroy (pulse->priv->cards);
+        pulse->priv->cards = NULL;
+    }
+    if (pulse->priv->sinks) {
+        g_hash_table_destroy (pulse->priv->sinks);
+        pulse->priv->devices = NULL;
+    }
+    if (pulse->priv->sink_inputs) {
+        g_hash_table_destroy (pulse->priv->sink_inputs);
+        pulse->priv->devices = NULL;
+    }
+    if (pulse->priv->sources) {
+        g_hash_table_destroy (pulse->priv->sources);
+        pulse->priv->devices = NULL;
+    }
+    if (pulse->priv->source_outputs) {
+        g_hash_table_destroy (pulse->priv->source_outputs);
+        pulse->priv->source_outputs = NULL;
+    }
+
+    g_clear_object (&pulse->priv->connection);
 
-    G_OBJECT_CLASS (mate_mixer_pulse_parent_class)->finalize (object);
+    G_OBJECT_CLASS (mate_mixer_pulse_parent_class)->dispose (object);
 }
 
 static void
@@ -127,7 +189,7 @@ mate_mixer_pulse_class_init (MateMixerPulseClass *klass)
     GObjectClass *object_class;
 
     object_class = G_OBJECT_CLASS (klass);
-    object_class->finalize = mate_mixer_pulse_finalize;
+    object_class->dispose = mate_mixer_pulse_dispose;
 
     g_type_class_add_private (object_class, sizeof (MateMixerPulsePrivate));
 }
@@ -141,83 +203,48 @@ mate_mixer_pulse_class_finalize (MateMixerPulseClass *klass)
 gboolean
 mate_mixer_pulse_open (MateMixerBackend *backend)
 {
-    int              ret;
-    gchar           *app_name;
-    MateMixerPulse  *pulse;
+    MateMixerPulse            *pulse;
+    MateMixerPulseConnection  *connection;
 
-    g_return_val_if_fail (MATE_MIXER_IS_BACKEND (backend), FALSE);
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE (backend), FALSE);
 
     pulse = MATE_MIXER_PULSE (backend);
 
-    g_return_val_if_fail (pulse->priv->mainloop == NULL, FALSE);
+    g_return_val_if_fail (pulse->priv->connection == NULL, FALSE);
 
-    pulse->priv->mainloop = pa_threaded_mainloop_new ();
-    if (G_UNLIKELY (pulse->priv->mainloop == NULL)) {
-        g_warning ("Failed to created PulseAudio main loop");
+    connection = mate_mixer_pulse_connection_new (NULL, NULL);
+    if (G_UNLIKELY (connection == NULL)) {
+        g_object_unref (connection);
         return FALSE;
     }
 
-    app_name = pulse_get_app_name ();
-
-    pulse->priv->context = pa_context_new (
-        pa_threaded_mainloop_get_api (pulse->priv->mainloop),
-        app_name);
-
-    g_free (app_name);
-
-    if (G_UNLIKELY (pulse->priv->context == NULL)) {
-        g_warning ("Failed to created PulseAudio context");
-
-        pa_threaded_mainloop_free (pulse->priv->mainloop);
-        pulse->priv->mainloop = NULL;
-        return FALSE;
-    }
-
-    // XXX: investigate PA_CONTEXT_NOFAIL
-    ret = pa_context_connect (pulse->priv->context, NULL, PA_CONTEXT_NOFLAGS, NULL);
-    if (ret < 0) {
-        g_warning ("Failed to connect to PulseAudio server: %s", pa_strerror (ret));
-
-        pa_context_unref (pulse->priv->context);
-        pa_threaded_mainloop_free (pulse->priv->mainloop);
-
-        pulse->priv->context = NULL;
-        pulse->priv->mainloop = NULL;
+    if (!mate_mixer_pulse_connection_connect (connection)) {
+        g_object_unref (connection);
         return FALSE;
     }
 
-    g_debug ("Connected to PulseAudio server");
-
-    pa_threaded_mainloop_lock (pulse->priv->mainloop);
-
-    pa_context_set_state_callback (pulse->priv->context,
-        pulse_state_cb,
-        backend);
-    pa_context_set_subscribe_callback (pulse->priv->context,
-        pulse_subscribe_cb,
-        backend);
-
-    ret = pa_threaded_mainloop_start (pulse->priv->mainloop);
-    if (ret < 0) {
-        g_warning ("Failed to start PulseAudio main loop: %s", pa_strerror (ret));
-
-        pa_threaded_mainloop_unlock (pulse->priv->mainloop);
-
-        pa_context_unref (pulse->priv->context);
-        pa_threaded_mainloop_free (pulse->priv->mainloop);
-
-        pulse->priv->context = NULL;
-        pulse->priv->mainloop = NULL;
-        return FALSE;
-    }
-
-    while (pa_context_get_state (pulse->priv->context) != PA_CONTEXT_READY) {
-        // XXX this will get stuck if connection fails
-        pa_threaded_mainloop_wait (pulse->priv->mainloop);
-    }
-
-    pa_threaded_mainloop_unlock (pulse->priv->mainloop);
-
+    g_signal_connect (connection,
+        "list-item-card",
+        G_CALLBACK (pulse_card_cb),
+        pulse);
+    g_signal_connect (connection,
+        "list-item-sink",
+        G_CALLBACK (pulse_sink_cb),
+        pulse);
+    g_signal_connect (connection,
+        "list-item-sink-input",
+        G_CALLBACK (pulse_sink_input_cb),
+        pulse);
+    g_signal_connect (connection,
+        "list-item-source",
+        G_CALLBACK (pulse_source_cb),
+        pulse);
+    g_signal_connect (connection,
+        "list-item-source-output",
+        G_CALLBACK (pulse_source_output_cb),
+        pulse);
+
+    pulse->priv->connection = connection;
     return TRUE;
 }
 
@@ -226,140 +253,118 @@ mate_mixer_pulse_close (MateMixerBackend *backend)
 {
     MateMixerPulse *pulse;
 
-    g_return_if_fail (MATE_MIXER_IS_BACKEND (backend));
+    g_return_if_fail (MATE_MIXER_IS_PULSE (backend));
 
     pulse = MATE_MIXER_PULSE (backend);
 
-    g_return_if_fail (pulse->priv->mainloop != NULL);
-
-    pa_threaded_mainloop_stop (pulse->priv->mainloop);
+    g_clear_object (&pulse->priv->connection);
+}
 
-    pa_context_unref (pulse->priv->context);
-    pa_threaded_mainloop_free (pulse->priv->mainloop);
+static gboolean
+pulse_load_lists (MateMixerPulse *pulse)
+{
+    /* The Pulse server is queried for initial lists, each of the functions
+     * waits until the list is available and then continues with the next.
+     *
+     * One possible improvement would be to load the lists asynchronously right
+     * after we connect to Pulse and when the application calls one of the
+     * list_* () functions, check if the initial list is already available and
+     * eventually wait until it's available. However, this would be
+     * tricky with the way the Pulse API is currently used and might not
+     * be beneficial at all.
+     */
+
+    // XXX figure out how to handle server reconnects, ideally everything
+    // we know should be ditched and read again asynchronously and
+    // the user should only be notified about actual differences
+    // from the state before we were disconnected
+
+    // mate_mixer_pulse_connection_get_server_info (pulse->priv->connection);
+    mate_mixer_pulse_connection_get_card_list (pulse->priv->connection);
+    mate_mixer_pulse_connection_get_sink_list (pulse->priv->connection);
+    mate_mixer_pulse_connection_get_sink_input_list (pulse->priv->connection);
+    mate_mixer_pulse_connection_get_source_list (pulse->priv->connection);
+    mate_mixer_pulse_connection_get_source_output_list (pulse->priv->connection);
 
-    pulse->priv->context = NULL;
-    pulse->priv->mainloop = NULL;
+    return TRUE;
 }
 
 GList *
 mate_mixer_pulse_list_devices (MateMixerBackend *backend)
 {
-    MateMixerPulse  *pulse;
-    pa_operation    *o;
+    MateMixerPulse *pulse;
+    GList          *list;
 
-    g_return_val_if_fail (MATE_MIXER_IS_BACKEND (backend), NULL);
+    g_return_val_if_fail (MATE_MIXER_IS_PULSE (backend), NULL);
 
     pulse = MATE_MIXER_PULSE (backend);
 
-    pa_threaded_mainloop_lock (pulse->priv->mainloop);
+    if (!pulse->priv->lists_loaded)
+        pulse_load_lists (pulse);
 
-    o = pa_context_get_card_info_list (pulse->priv->context, pulse_card_info_cb, pulse);
-    if (o == NULL) {
-        g_warning ("Failed to read card list: %s",
-            pa_strerror (pa_context_errno (pulse->priv->context)));
-
-        pa_threaded_mainloop_unlock (pulse->priv->mainloop);
-        return NULL;
-    }
+    list = g_hash_table_get_values (pulse->priv->devices);
 
-    while (pa_operation_get_state (o) == PA_OPERATION_RUNNING)
-        pa_threaded_mainloop_wait (pulse->priv->mainloop);
-
-    pa_operation_unref (o);
-    pa_threaded_mainloop_unlock (pulse->priv->mainloop);
+    g_list_foreach (list, (GFunc) g_object_ref, NULL);
+    return list;
+}
 
-    return g_hash_table_get_values (pulse->priv->devices);
+GList *
+mate_mixer_pulse_list_streams (MateMixerBackend *backend)
+{
+    // TODO
+    return NULL;
 }
 
 static void
-pulse_card_info_cb (pa_context *c, const pa_card_info *info, int eol, void *userdata)
+pulse_card_cb (MateMixerPulseConnection *connection,
+                const pa_card_info *info,
+                MateMixerPulse *pulse)
 {
-    MateMixerPulse *pulse;
-
-    pulse = MATE_MIXER_PULSE (userdata);
-
-    if (!eol)
-        pulse_card_update (pulse, info);
-
-    pa_threaded_mainloop_signal (pulse->priv->mainloop, 0);
+    MateMixerPulseDevice *device;
+
+    device = mate_mixer_pulse_device_new (connection, info);
+    if (G_LIKELY (device))
+        g_hash_table_insert (
+            pulse->priv->devices,
+            GINT_TO_POINTER (mate_mixer_pulse_device_get_index (device)),
+            device);
 }
 
 static void
-pulse_card_update (MateMixerPulse *pulse, const pa_card_info *info)
+pulse_sink_cb (MateMixerPulseConnection *connection,
+                            const pa_sink_info *info,
+                            MateMixerPulse *pulse)
 {
-    gpointer item;
-
-    item = g_hash_table_lookup (pulse->priv->devices, GINT_TO_POINTER (info->index));
-    if (item) {
-        /* The card is already known, just update the fields that may
-         * have changed */
-        mate_mixer_pulse_device_update (MATE_MIXER_PULSE_DEVICE (item), info);
-    } else {
-        MateMixerPulseDevice *device = mate_mixer_pulse_device_new (info);
-
-        if (G_UNLIKELY (device == NULL))
-            g_warning ("Failed to process PulseAudio sound device");
-        else
-            g_hash_table_insert (
-                pulse->priv->devices,
-                GINT_TO_POINTER (info->index),
-                device);
-    }
+    MateMixerPulseStream *stream;
+
+    stream = mate_mixer_pulse_sink_new (connection, info);
+    if (G_LIKELY (stream))
+        g_hash_table_insert (
+            pulse->priv->sinks,
+            GINT_TO_POINTER (mate_mixer_pulse_stream_get_index (stream)),
+            stream);
 }
 
 static void
-pulse_state_cb (pa_context *c, void *userdata)
+pulse_sink_input_cb (MateMixerPulseConnection *connection,
+                            const pa_sink_input_info *info,
+                            MateMixerPulse *pulse)
 {
-    MateMixerPulse *pulse;
 
-    pulse = MATE_MIXER_PULSE (userdata);
-
-    // TODO: handle errors
-
-    switch (pa_context_get_state (c)) {
-    case PA_CONTEXT_UNCONNECTED:
-        break;
-    case PA_CONTEXT_CONNECTING:
-        break;
-    case PA_CONTEXT_AUTHORIZING:
-        break;
-    case PA_CONTEXT_SETTING_NAME:
-        break;
-    case PA_CONTEXT_READY:
-        break;
-    case PA_CONTEXT_FAILED:
-        break;
-    case PA_CONTEXT_TERMINATED:
-        break;
-    default:
-        break;
-    }
-
-    pa_threaded_mainloop_signal (pulse->priv->mainloop, FALSE);
 }
 
 static void
-pulse_subscribe_cb (pa_context *c,
-                    pa_subscription_event_type_t t,
-                    uint32_t idx,
-                    void *userdata)
+pulse_source_cb (MateMixerPulseConnection *connection,
+                            const pa_source_info *info,
+                            MateMixerPulse *pulse)
 {
-    // TODO
+
 }
 
-static gchar *
-pulse_get_app_name (void)
+static void
+pulse_source_output_cb (MateMixerPulseConnection *connection,
+                            const pa_source_output_info *info,
+                            MateMixerPulse *pulse)
 {
-    const char *name_app;
-    char        name_buf[256];
-
-    /* Inspired by GStreamer's pulse plugin */
-    name_app = g_get_application_name ();
-    if (name_app != NULL)
-        return g_strdup (name_app);
-
-    if (pa_get_binary_name (name_buf, sizeof (name_buf)) != NULL)
-        return g_strdup (name_buf);
 
-    return g_strdup_printf ("libmatemixer-%lu", (gulong) getpid ());
 }
diff --git a/backends/pulse/pulse.h b/backends/pulse/pulse.h
index 2f8414f..d94a543 100644
--- a/backends/pulse/pulse.h
+++ b/backends/pulse/pulse.h
@@ -54,8 +54,10 @@ struct _MateMixerPulseClass
 
 GType mate_mixer_pulse_get_type (void) G_GNUC_CONST;
 
+/* Interface implementation */
 gboolean      mate_mixer_pulse_open          (MateMixerBackend *backend);
 void          mate_mixer_pulse_close         (MateMixerBackend *backend);
 GList        *mate_mixer_pulse_list_devices  (MateMixerBackend *backend);
+GList        *mate_mixer_pulse_list_streams  (MateMixerBackend *backend);
 
 #endif /* MATEMIXER_PULSE_H */
-- 
cgit v1.2.1