mixer-control: remove objects properly when reconnecting to PA

GvcMixerCards are not removed when reconnecting to PA server, which
causes duplicate card entries to appear on PA restart.  Moreover, the
old GvcMixerCard instances have pointers to the old already freed
pa_context, resulting to use-after-free on operations.  Duplicate
entries are also caused by sink/source removal on reconnect not sending
right signals.

Make it clean up all Gvc objects as if we got subscribe remove events
for them all:

Use remove_sink/remove_source to remove sinks/sources so that the right
signals are emitted. Remove cards using remove_card, so that also they
get cleaned up. Remove also any leftover GvcMixerUIDevices.

Move cleanup of streams etc. before pa_context unref, so that we free
the objects referring the pa_context before freeing the context.

This fixes gnome-control-center crashing when PA server is restarted,
and one e.g. tries to do something that ends up in
gvc_mixer_card_change_profile such as selecting output device with port
in different profile.  It also fixes duplicate entries appearing in the
device lists on Pipewire restart (they don't appear with Pulseaudio
since PA device IDs don't change on restart).

It should also fix similar crashes in gnome-shell.
This commit is contained in:
Pauli Virtanen 2023-07-13 17:59:03 +03:00
parent 8e7a5a4c3e
commit f3806ad5df

View File

@ -3119,6 +3119,9 @@ remove_card (GvcMixerControl *control,
g_object_get (G_OBJECT (device), "card", &card, NULL); g_object_get (G_OBJECT (device), "card", &card, NULL);
if (card == NULL)
continue;
if (gvc_mixer_card_get_index (card) == index) { if (gvc_mixer_card_get_index (card) == index) {
g_signal_emit (G_OBJECT (control), g_signal_emit (G_OBJECT (control),
signals[gvc_mixer_ui_device_is_output (device) ? OUTPUT_REMOVED : INPUT_REMOVED], signals[gvc_mixer_ui_device_is_output (device) ? OUTPUT_REMOVED : INPUT_REMOVED],
@ -3434,27 +3437,45 @@ gvc_mixer_new_pa_context (GvcMixerControl *self)
} }
static void static void
remove_all_streams (GvcMixerControl *control, GHashTable *hash_table) remove_all_items (GvcMixerControl *control,
GHashTable *hash_table,
void (*remove_item)(GvcMixerControl *control, guint index))
{ {
GHashTableIter iter; GHashTableIter iter;
gpointer key, value; gpointer key, value;
g_hash_table_iter_init (&iter, hash_table); g_hash_table_iter_init (&iter, hash_table);
while (g_hash_table_iter_next (&iter, &key, &value)) { while (g_hash_table_iter_next (&iter, &key, &value)) {
remove_stream (control, value); if (remove_item) {
remove_item (control, GPOINTER_TO_UINT (key));
g_hash_table_remove (hash_table, key);
g_hash_table_iter_init (&iter, hash_table);
} else {
g_hash_table_iter_remove (&iter); g_hash_table_iter_remove (&iter);
} }
}
} }
static gboolean static gboolean
idle_reconnect (gpointer data) idle_reconnect (gpointer data)
{ {
GvcMixerControl *control = GVC_MIXER_CONTROL (data); GvcMixerControl *control = GVC_MIXER_CONTROL (data);
GHashTableIter iter;
gpointer key, value;
g_return_val_if_fail (control, FALSE); g_return_val_if_fail (control, FALSE);
g_debug ("Reconnect: clean up all objects");
remove_all_items (control, control->priv->sinks, remove_sink);
remove_all_items (control, control->priv->sources, remove_source);
remove_all_items (control, control->priv->sink_inputs, remove_sink_input);
remove_all_items (control, control->priv->source_outputs, remove_source_output);
remove_all_items (control, control->priv->cards, remove_card);
remove_all_items (control, control->priv->ui_inputs, NULL);
remove_all_items (control, control->priv->ui_outputs, NULL);
remove_all_items (control, control->priv->clients, remove_client);
g_debug ("Reconnect: make new connection");
if (control->priv->pa_context) { if (control->priv->pa_context) {
pa_context_unref (control->priv->pa_context); pa_context_unref (control->priv->pa_context);
control->priv->pa_context = NULL; control->priv->pa_context = NULL;
@ -3462,15 +3483,6 @@ idle_reconnect (gpointer data)
gvc_mixer_new_pa_context (control); gvc_mixer_new_pa_context (control);
} }
remove_all_streams (control, control->priv->sinks);
remove_all_streams (control, control->priv->sources);
remove_all_streams (control, control->priv->sink_inputs);
remove_all_streams (control, control->priv->source_outputs);
g_hash_table_iter_init (&iter, control->priv->clients);
while (g_hash_table_iter_next (&iter, &key, &value))
g_hash_table_iter_remove (&iter);
gvc_mixer_control_open (control); /* cannot fail */ gvc_mixer_control_open (control); /* cannot fail */
control->priv->reconnect_id = 0; control->priv->reconnect_id = 0;