summaryrefslogtreecommitdiff
path: root/backends/pulse/pulse-backend.c
diff options
context:
space:
mode:
authorMichal Ratajsky <[email protected]>2014-07-03 20:48:08 +0200
committerMichal Ratajsky <[email protected]>2014-07-03 20:48:08 +0200
commitb81d9ed11fae7bee59b67b1aee9927e417666875 (patch)
treeb5b2d8406272db2b4457e31f714e1703d6336727 /backends/pulse/pulse-backend.c
parente13f443c1c4c73ef20dd00f1909f8c94c685b07f (diff)
downloadlibmatemixer-b81d9ed11fae7bee59b67b1aee9927e417666875.tar.bz2
libmatemixer-b81d9ed11fae7bee59b67b1aee9927e417666875.tar.xz
PulseAudio and API fixes
Diffstat (limited to 'backends/pulse/pulse-backend.c')
-rw-r--r--backends/pulse/pulse-backend.c403
1 files changed, 262 insertions, 141 deletions
diff --git a/backends/pulse/pulse-backend.c b/backends/pulse/pulse-backend.c
index bdb3675..1794018 100644
--- a/backends/pulse/pulse-backend.c
+++ b/backends/pulse/pulse-backend.c
@@ -145,6 +145,20 @@ static void backend_source_output_removed_cb (PulseConnection
static gboolean backend_try_reconnect (PulseBackend *pulse);
static void backend_remove_connect_source (PulseBackend *pulse);
+
+static void backend_mark_hanging (PulseBackend *pulse);
+static void backend_mark_hanging_hash (GHashTable *hash);
+
+static void backend_remove_hanging (PulseBackend *pulse);
+static void backend_remove_hanging_hash (PulseBackend *pulse,
+ GHashTable *hash);
+
+static void backend_remove_device (PulseBackend *pulse,
+ PulseDevice *device);
+static void backend_remove_stream (PulseBackend *pulse,
+ GHashTable *hash,
+ PulseStream *stream);
+
static void backend_change_state (PulseBackend *backend,
MateMixerState state);
@@ -166,7 +180,7 @@ backend_module_init (GTypeModule *module)
info.name = BACKEND_NAME;
info.priority = BACKEND_PRIORITY;
info.g_type = PULSE_TYPE_BACKEND;
- info.backend_type = MATE_MIXER_BACKEND_PULSE;
+ info.backend_type = MATE_MIXER_BACKEND_PULSEAUDIO;
}
const MateMixerBackendInfo *
@@ -246,6 +260,9 @@ pulse_backend_init (PulseBackend *pulse)
PULSE_TYPE_BACKEND,
PulseBackendPrivate);
+ /* These hash tables store PulseDevice and PulseStream instances, the key
+ * is the PulseAudio index which is not unique across different stream
+ * types, hence the separate hash tables */
pulse->priv->devices =
g_hash_table_new_full (g_direct_hash,
g_direct_equal,
@@ -276,18 +293,8 @@ pulse_backend_init (PulseBackend *pulse)
static void
pulse_backend_dispose (GObject *object)
{
- PulseBackend *pulse;
-
backend_close (MATE_MIXER_BACKEND (object));
- pulse = PULSE_BACKEND (object);
-
- g_clear_pointer (&pulse->priv->devices, g_hash_table_destroy);
- g_clear_pointer (&pulse->priv->sinks, g_hash_table_destroy);
- g_clear_pointer (&pulse->priv->sink_inputs, g_hash_table_destroy);
- g_clear_pointer (&pulse->priv->sources, g_hash_table_destroy);
- g_clear_pointer (&pulse->priv->source_outputs, g_hash_table_destroy);
-
G_OBJECT_CLASS (pulse_backend_parent_class)->dispose (object);
}
@@ -304,6 +311,12 @@ pulse_backend_finalize (GObject *object)
g_free (pulse->priv->app_icon);
g_free (pulse->priv->server_address);
+ g_hash_table_destroy (pulse->priv->devices);
+ g_hash_table_destroy (pulse->priv->sinks);
+ g_hash_table_destroy (pulse->priv->sink_inputs);
+ g_hash_table_destroy (pulse->priv->sources);
+ g_hash_table_destroy (pulse->priv->source_outputs);
+
G_OBJECT_CLASS (pulse_backend_parent_class)->finalize (object);
}
@@ -317,8 +330,10 @@ backend_open (MateMixerBackend *backend)
pulse = PULSE_BACKEND (backend);
- if (G_UNLIKELY (pulse->priv->connection != NULL))
+ if (G_UNLIKELY (pulse->priv->connection != NULL)) {
+ g_warn_if_reached ();
return TRUE;
+ }
connection = pulse_connection_new (pulse->priv->app_name,
pulse->priv->app_id,
@@ -330,7 +345,7 @@ backend_open (MateMixerBackend *backend)
* but it sets up the PulseAudio structures, which might fail in an
* unlikely case */
if (G_UNLIKELY (connection == NULL)) {
- pulse->priv->state = MATE_MIXER_STATE_FAILED;
+ backend_change_state (pulse, MATE_MIXER_STATE_FAILED);
return FALSE;
}
@@ -385,14 +400,15 @@ backend_open (MateMixerBackend *backend)
/* Connect to the PulseAudio server, this might fail either instantly or
* asynchronously, for example when remote connection timeouts */
- if (!pulse_connection_connect (connection)) {
- pulse->priv->state = MATE_MIXER_STATE_FAILED;
+ if (!pulse_connection_connect (connection, FALSE)) {
g_object_unref (connection);
+ backend_change_state (pulse, MATE_MIXER_STATE_FAILED);
return FALSE;
}
pulse->priv->connection = connection;
- pulse->priv->state = MATE_MIXER_STATE_CONNECTING;
+
+ backend_change_state (pulse, MATE_MIXER_STATE_CONNECTING);
return TRUE;
}
@@ -405,18 +421,12 @@ backend_close (MateMixerBackend *backend)
pulse = PULSE_BACKEND (backend);
- /* IDLE is the state in which the backend is already closed */
- if (pulse->priv->state == MATE_MIXER_STATE_IDLE)
- return;
-
backend_remove_connect_source (pulse);
- if (pulse->priv->connection) {
- g_signal_handlers_disconnect_by_data (pulse->priv->connection, pulse);
-
- pulse_connection_disconnect (pulse->priv->connection);
- g_clear_object (&pulse->priv->connection);
- }
+ // XXX disconnect from notifies
+ g_clear_object (&pulse->priv->connection);
+ g_clear_object (&pulse->priv->default_sink);
+ g_clear_object (&pulse->priv->default_source);
g_hash_table_remove_all (pulse->priv->devices);
g_hash_table_remove_all (pulse->priv->sinks);
@@ -424,9 +434,6 @@ backend_close (MateMixerBackend *backend)
g_hash_table_remove_all (pulse->priv->sources);
g_hash_table_remove_all (pulse->priv->source_outputs);
- g_clear_object (&pulse->priv->default_sink);
- g_clear_object (&pulse->priv->default_source);
-
backend_change_state (pulse, MATE_MIXER_STATE_IDLE);
}
@@ -444,18 +451,15 @@ backend_set_data (MateMixerBackend *backend, const MateMixerBackendData *data)
PulseBackend *pulse;
g_return_if_fail (PULSE_IS_BACKEND (backend));
+ g_return_if_fail (data != NULL);
pulse = PULSE_BACKEND (backend);
- g_clear_pointer (&pulse->priv->app_name, g_free);
- g_clear_pointer (&pulse->priv->app_id, g_free);
- g_clear_pointer (&pulse->priv->app_version, g_free);
- g_clear_pointer (&pulse->priv->app_icon, g_free);
- g_clear_pointer (&pulse->priv->server_address, g_free);
-
- /* Allow to unset the details by passing NULL data */
- if (G_UNLIKELY (data == NULL))
- return;
+ g_free (pulse->priv->app_name);
+ g_free (pulse->priv->app_id);
+ g_free (pulse->priv->app_version);
+ g_free (pulse->priv->app_icon);
+ g_free (pulse->priv->server_address);
pulse->priv->app_name = g_strdup (data->app_name);
pulse->priv->app_id = g_strdup (data->app_id);
@@ -520,6 +524,11 @@ backend_set_default_input_stream (MateMixerBackend *backend, MateMixerStream *st
pulse = PULSE_BACKEND (backend);
+ if (G_UNLIKELY (!PULSE_IS_SOURCE (stream))) {
+ g_warn_if_reached ();
+ return FALSE;
+ }
+
return pulse_connection_set_default_source (pulse->priv->connection,
mate_mixer_stream_get_name (stream));
}
@@ -542,6 +551,11 @@ backend_set_default_output_stream (MateMixerBackend *backend, MateMixerStream *s
pulse = PULSE_BACKEND (backend);
+ if (G_UNLIKELY (!PULSE_IS_SINK (stream))) {
+ g_warn_if_reached ();
+ return FALSE;
+ }
+
return pulse_connection_set_default_sink (pulse->priv->connection,
mate_mixer_stream_get_name (stream));
}
@@ -557,13 +571,17 @@ backend_connection_state_cb (PulseConnection *connection,
case PULSE_CONNECTION_DISCONNECTED:
if (pulse->priv->connected_once) {
/* We managed to connect once before, try to reconnect and if it
- * fails immediately, use an idle source;
- * in case the idle source already exists, just let it try again */
+ * fails immediately, use a timeout source.
+ * All current devices and streams are marked as hanging as it is
+ * unknown whether they are still available, stream callbacks will
+ * unmark them and remaining unavailable streams will be removed
+ * when the CONNECTED state is reached. */
+ backend_mark_hanging (pulse);
+ backend_change_state (pulse, MATE_MIXER_STATE_CONNECTING);
- // XXX need to handle cached streams that are gone when reconnected
if (!pulse->priv->connect_source &&
- !pulse_connection_connect (connection)) {
- pulse->priv->connect_source = g_idle_source_new ();
+ !pulse_connection_connect (connection, TRUE)) {
+ pulse->priv->connect_source = g_timeout_source_new (200);
g_source_set_callback (pulse->priv->connect_source,
(GSourceFunc) backend_try_reconnect,
@@ -587,7 +605,10 @@ backend_connection_state_cb (PulseConnection *connection,
break;
case PULSE_CONNECTION_CONNECTED:
- pulse->priv->connected_once = TRUE;
+ if (pulse->priv->connected_once)
+ backend_remove_hanging (pulse);
+ else
+ pulse->priv->connected_once = TRUE;
backend_change_state (pulse, MATE_MIXER_STATE_READY);
break;
@@ -602,13 +623,11 @@ backend_server_info_cb (PulseConnection *connection,
const gchar *name_source = NULL;
const gchar *name_sink = NULL;
- if (pulse->priv->default_source)
+ if (pulse->priv->default_source != NULL)
name_source = mate_mixer_stream_get_name (pulse->priv->default_source);
- if (pulse->priv->default_sink)
- name_sink = mate_mixer_stream_get_name (pulse->priv->default_sink);
if (g_strcmp0 (name_source, info->default_source_name)) {
- if (pulse->priv->default_source)
+ if (pulse->priv->default_source != NULL)
g_clear_object (&pulse->priv->default_source);
if (info->default_source_name != NULL) {
@@ -626,15 +645,18 @@ backend_server_info_cb (PulseConnection *connection,
pulse->priv->default_source = g_object_ref (stream);
g_debug ("Default input stream changed to %s", info->default_source_name);
- g_object_notify (G_OBJECT (pulse), "default-output");
+ g_object_notify (G_OBJECT (pulse), "default-input");
} else
g_debug ("Default input stream %s not yet known",
info->default_source_name);
}
}
+ if (pulse->priv->default_sink != NULL)
+ name_sink = mate_mixer_stream_get_name (pulse->priv->default_sink);
+
if (g_strcmp0 (name_sink, info->default_sink_name)) {
- if (pulse->priv->default_sink)
+ if (pulse->priv->default_sink != NULL)
g_clear_object (&pulse->priv->default_sink);
if (info->default_sink_name != NULL) {
@@ -682,11 +704,18 @@ backend_card_info_cb (PulseConnection *connection,
pulse_device_update (device, info);
}
- g_signal_emit_by_name (G_OBJECT (pulse),
- (p == NULL)
- ? "device-added"
- : "device-changed",
- mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)));
+ if (pulse->priv->connected_once) {
+ /* The object might be hanging if reconnecting is in progress, remove the
+ * hanging flag to prevent it from being removed when connected */
+ if (pulse->priv->state != MATE_MIXER_STATE_READY)
+ g_object_steal_data (G_OBJECT (device), "hanging");
+
+ g_signal_emit_by_name (G_OBJECT (pulse),
+ (p == NULL)
+ ? "device-added"
+ : "device-changed",
+ mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)));
+ }
}
static void
@@ -694,21 +723,13 @@ backend_card_removed_cb (PulseConnection *connection,
guint index,
PulseBackend *pulse)
{
- gpointer p;
- gchar *name;
+ gpointer p;
p = g_hash_table_lookup (pulse->priv->devices, GINT_TO_POINTER (index));
if (G_UNLIKELY (p == NULL))
return;
- name = g_strdup (mate_mixer_device_get_name (MATE_MIXER_DEVICE (p)));
-
- g_hash_table_remove (pulse->priv->devices, GINT_TO_POINTER (index));
- if (G_LIKELY (name != NULL))
- g_signal_emit_by_name (G_OBJECT (pulse),
- "device-removed",
- name);
- g_free (name);
+ backend_remove_device (pulse, PULSE_DEVICE (p));
}
static void
@@ -716,12 +737,18 @@ backend_sink_info_cb (PulseConnection *connection,
const pa_sink_info *info,
PulseBackend *pulse)
{
- gpointer p;
+ PulseDevice *device = NULL;
PulseStream *stream;
+ gpointer p = NULL;
+
+ if (info->card != PA_INVALID_INDEX)
+ p = g_hash_table_lookup (pulse->priv->devices, GINT_TO_POINTER (info->card));
+ if (p)
+ device = PULSE_DEVICE (p);
p = g_hash_table_lookup (pulse->priv->sinks, GINT_TO_POINTER (info->index));
if (p == NULL) {
- stream = pulse_sink_new (connection, info);
+ stream = pulse_sink_new (connection, info, device);
if (G_UNLIKELY (stream == NULL))
return;
@@ -731,14 +758,21 @@ backend_sink_info_cb (PulseConnection *connection,
stream);
} else {
stream = PULSE_STREAM (p);
- pulse_sink_update (stream, info);
+ pulse_sink_update (stream, info, device);
}
- g_signal_emit_by_name (G_OBJECT (pulse),
- (p == NULL)
- ? "stream-added"
- : "stream-changed",
- mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)));
+ if (pulse->priv->connected_once) {
+ /* The object might be hanging if reconnecting is in progress, remove the
+ * hanging flag to prevent it from being removed when connected */
+ if (pulse->priv->state != MATE_MIXER_STATE_READY)
+ g_object_steal_data (G_OBJECT (stream), "hanging");
+
+ g_signal_emit_by_name (G_OBJECT (pulse),
+ (p == NULL)
+ ? "stream-added"
+ : "stream-changed",
+ mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)));
+ }
}
static void
@@ -746,21 +780,13 @@ backend_sink_removed_cb (PulseConnection *connection,
guint index,
PulseBackend *pulse)
{
- gpointer p;
- gchar *name;
+ gpointer p;
p = g_hash_table_lookup (pulse->priv->sinks, GINT_TO_POINTER (index));
if (G_UNLIKELY (p == NULL))
return;
- name = g_strdup (mate_mixer_stream_get_name (MATE_MIXER_STREAM (p)));
-
- g_hash_table_remove (pulse->priv->sinks, GINT_TO_POINTER (index));
- if (G_LIKELY (name != NULL))
- g_signal_emit_by_name (G_OBJECT (pulse),
- "stream-removed",
- name);
- g_free (name);
+ backend_remove_stream (pulse, pulse->priv->sinks, PULSE_STREAM (p));
}
static void
@@ -768,21 +794,22 @@ backend_sink_input_info_cb (PulseConnection *connection,
const pa_sink_input_info *info,
PulseBackend *pulse)
{
+ PulseStream *stream;
gpointer p;
gpointer parent = NULL;
- PulseStream *stream;
- if (info->sink) {
+ if (G_LIKELY (info->sink)) {
parent = g_hash_table_lookup (pulse->priv->sinks, GINT_TO_POINTER (info->sink));
if (G_UNLIKELY (parent == NULL))
- g_debug ("Unknown parent %d of PulseAudio sink input %d",
+ g_debug ("Unknown parent %d of PulseAudio sink input %s",
info->sink,
- info->index);
+ info->name);
}
p = g_hash_table_lookup (pulse->priv->sink_inputs, GINT_TO_POINTER (info->index));
if (p == NULL) {
stream = pulse_sink_input_new (connection, info, parent);
+
if (G_UNLIKELY (stream == NULL))
return;
@@ -794,11 +821,18 @@ backend_sink_input_info_cb (PulseConnection *connection,
pulse_sink_input_update (stream, info, parent);
}
- g_signal_emit_by_name (G_OBJECT (pulse),
- (p == NULL)
- ? "stream-added"
- : "stream-changed",
- mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)));
+ if (pulse->priv->connected_once) {
+ /* The object might be hanging if reconnecting is in progress, remove the
+ * hanging flag to prevent it from being removed when connected */
+ if (pulse->priv->state != MATE_MIXER_STATE_READY)
+ g_object_steal_data (G_OBJECT (stream), "hanging");
+
+ g_signal_emit_by_name (G_OBJECT (pulse),
+ (p == NULL)
+ ? "stream-added"
+ : "stream-changed",
+ mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)));
+ }
}
static void
@@ -806,21 +840,13 @@ backend_sink_input_removed_cb (PulseConnection *connection,
guint index,
PulseBackend *pulse)
{
- gpointer p;
- gchar *name;
+ gpointer p;
p = g_hash_table_lookup (pulse->priv->sink_inputs, GINT_TO_POINTER (index));
if (G_UNLIKELY (p == NULL))
return;
- name = g_strdup (mate_mixer_stream_get_name (MATE_MIXER_STREAM (p)));
-
- g_hash_table_remove (pulse->priv->sink_inputs, GINT_TO_POINTER (index));
- if (G_LIKELY (name != NULL))
- g_signal_emit_by_name (G_OBJECT (pulse),
- "stream-removed",
- name);
- g_free (name);
+ backend_remove_stream (pulse, pulse->priv->sink_inputs, PULSE_STREAM (p));
}
static void
@@ -828,16 +854,22 @@ backend_source_info_cb (PulseConnection *connection,
const pa_source_info *info,
PulseBackend *pulse)
{
- gpointer p;
+ PulseDevice *device = NULL;
PulseStream *stream;
+ gpointer p = NULL;
+
+ /* Skip monitor streams */
+ if (info->monitor_of_sink != PA_INVALID_INDEX)
+ return;
+
+ if (info->card != PA_INVALID_INDEX)
+ p = g_hash_table_lookup (pulse->priv->devices, GINT_TO_POINTER (info->card));
+ if (p)
+ device = PULSE_DEVICE (p);
p = g_hash_table_lookup (pulse->priv->sources, GINT_TO_POINTER (info->index));
if (p == NULL) {
- /* Skip monitor streams */
- if (info->monitor_of_sink != PA_INVALID_INDEX)
- return;
-
- stream = pulse_source_new (connection, info);
+ stream = pulse_source_new (connection, info, device);
if (G_UNLIKELY (stream == NULL))
return;
@@ -847,14 +879,21 @@ backend_source_info_cb (PulseConnection *connection,
stream);
} else {
stream = PULSE_STREAM (p);
- pulse_source_update (stream, info);
+ pulse_source_update (stream, info, device);
}
- g_signal_emit_by_name (G_OBJECT (pulse),
- (p == NULL)
- ? "stream-added"
- : "stream-changed",
- mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)));
+ if (pulse->priv->connected_once) {
+ /* The object might be hanging if reconnecting is in progress, remove the
+ * hanging flag to prevent it from being removed when connected */
+ if (pulse->priv->state != MATE_MIXER_STATE_READY)
+ g_object_steal_data (G_OBJECT (stream), "hanging");
+
+ g_signal_emit_by_name (G_OBJECT (pulse),
+ (p == NULL)
+ ? "stream-added"
+ : "stream-changed",
+ mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)));
+ }
}
static void
@@ -862,21 +901,13 @@ backend_source_removed_cb (PulseConnection *connection,
guint index,
PulseBackend *pulse)
{
- gpointer p;
- gchar *name;
+ gpointer p;
p = g_hash_table_lookup (pulse->priv->sources, GINT_TO_POINTER (index));
if (G_UNLIKELY (p == NULL))
return;
- name = g_strdup (mate_mixer_stream_get_name (MATE_MIXER_STREAM (p)));
-
- g_hash_table_remove (pulse->priv->sources, GINT_TO_POINTER (index));
- if (G_LIKELY (name != NULL))
- g_signal_emit_by_name (G_OBJECT (pulse),
- "stream-removed",
- name);
- g_free (name);
+ backend_remove_stream (pulse, pulse->priv->sources, PULSE_STREAM (p));
}
static void
@@ -884,9 +915,9 @@ backend_source_output_info_cb (PulseConnection *connection,
const pa_source_output_info *info,
PulseBackend *pulse)
{
+ PulseStream *stream;
gpointer p;
gpointer parent = NULL;
- PulseStream *stream;
if (G_LIKELY (info->source)) {
parent = g_hash_table_lookup (pulse->priv->sources, GINT_TO_POINTER (info->source));
@@ -910,11 +941,18 @@ backend_source_output_info_cb (PulseConnection *connection,
pulse_source_output_update (stream, info, parent);
}
- g_signal_emit_by_name (G_OBJECT (pulse),
- (p == NULL)
- ? "stream-added"
- : "stream-changed",
- mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)));
+ if (pulse->priv->connected_once) {
+ /* The object might be hanging if reconnecting is in progress, remove the
+ * hanging flag to prevent it from being removed when connected */
+ if (pulse->priv->state != MATE_MIXER_STATE_READY)
+ g_object_steal_data (G_OBJECT (stream), "hanging");
+
+ g_signal_emit_by_name (G_OBJECT (pulse),
+ (p == NULL)
+ ? "stream-added"
+ : "stream-changed",
+ mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)));
+ }
}
static void
@@ -922,21 +960,13 @@ backend_source_output_removed_cb (PulseConnection *connection,
guint index,
PulseBackend *pulse)
{
- gpointer p;
- gchar *name;
+ gpointer p;
p = g_hash_table_lookup (pulse->priv->source_outputs, GINT_TO_POINTER (index));
if (G_UNLIKELY (p == NULL))
return;
- name = g_strdup (mate_mixer_stream_get_name (MATE_MIXER_STREAM (p)));
-
- g_hash_table_remove (pulse->priv->source_outputs, GINT_TO_POINTER (index));
- if (G_LIKELY (name != NULL))
- g_signal_emit_by_name (G_OBJECT (pulse),
- "stream-removed",
- name);
- g_free (name);
+ backend_remove_stream (pulse, pulse->priv->source_outputs, PULSE_STREAM (p));
}
static gboolean
@@ -945,7 +975,7 @@ backend_try_reconnect (PulseBackend *pulse)
/* When the connect call succeeds, return FALSE to remove the idle source
* and wait for the connection state notifications, otherwise this function
* will be called again */
- return !pulse_connection_connect (pulse->priv->connection);
+ return !pulse_connection_connect (pulse->priv->connection, TRUE);
}
static void
@@ -955,6 +985,97 @@ backend_remove_connect_source (PulseBackend *pulse)
}
static void
+backend_mark_hanging (PulseBackend *pulse)
+{
+ backend_mark_hanging_hash (pulse->priv->devices);
+ backend_mark_hanging_hash (pulse->priv->sinks);
+ backend_mark_hanging_hash (pulse->priv->sink_inputs);
+ backend_mark_hanging_hash (pulse->priv->sources);
+ backend_mark_hanging_hash (pulse->priv->source_outputs);
+}
+
+static void
+backend_mark_hanging_hash (GHashTable *hash)
+{
+ GHashTableIter iter;
+ gpointer value;
+
+ g_hash_table_iter_init (&iter, hash);
+
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ g_object_set_data (G_OBJECT (value), "hanging", GINT_TO_POINTER (1));
+}
+
+static void
+backend_remove_hanging (PulseBackend *pulse)
+{
+ GHashTableIter iter;
+ gpointer value;
+
+ g_hash_table_iter_init (&iter, pulse->priv->devices);
+
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ if (g_object_get_data (G_OBJECT (value), "hanging"))
+ backend_remove_device (pulse, PULSE_DEVICE (value));
+
+ backend_remove_hanging_hash (pulse, pulse->priv->sinks);
+ backend_remove_hanging_hash (pulse, pulse->priv->sink_inputs);
+ backend_remove_hanging_hash (pulse, pulse->priv->sources);
+ backend_remove_hanging_hash (pulse, pulse->priv->source_outputs);
+}
+
+static void
+backend_remove_hanging_hash (PulseBackend *pulse, GHashTable *hash)
+{
+ GHashTableIter iter;
+ gpointer value;
+
+ g_hash_table_iter_init (&iter, hash);
+
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ if (g_object_get_data (G_OBJECT (value), "hanging"))
+ backend_remove_stream (pulse, hash, PULSE_STREAM (value));
+}
+
+static void
+backend_remove_device (PulseBackend *pulse, PulseDevice *device)
+{
+ gchar *name;
+
+ name = g_strdup (mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)));
+
+ g_hash_table_remove (pulse->priv->devices,
+ GINT_TO_POINTER (pulse_device_get_index (device)));
+
+ g_signal_emit_by_name (G_OBJECT (pulse), "device-removed", name);
+ g_free (name);
+}
+
+static void
+backend_remove_stream (PulseBackend *pulse, GHashTable *hash, PulseStream *stream)
+{
+ gchar *name;
+
+ /* Make sure we do not end up with invalid default streams, but this is
+ * very unlikely to happen */
+ if (G_UNLIKELY (MATE_MIXER_STREAM (stream) == pulse->priv->default_sink)) {
+ g_clear_object (&pulse->priv->default_sink);
+ g_object_notify (G_OBJECT (pulse), "default-output");
+ }
+ else if (G_UNLIKELY (MATE_MIXER_STREAM (stream) == pulse->priv->default_source)) {
+ g_clear_object (&pulse->priv->default_source);
+ g_object_notify (G_OBJECT (pulse), "default-input");
+ }
+
+ name = g_strdup (mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)));
+
+ g_hash_table_remove (hash, GINT_TO_POINTER (pulse_stream_get_index (stream)));
+
+ g_signal_emit_by_name (G_OBJECT (pulse), "stream-removed", name);
+ g_free (name);
+}
+
+static void
backend_change_state (PulseBackend *backend, MateMixerState state)
{
if (backend->priv->state == state)