shortcutmanager: Track propagation phase of added controllers

GtkShortcutManager allows adding controllers to it. For the default
implementation, they get added to one of two models, based on the
propagation phase (either GTK_PHASE_CAPTURE or GTK_PHASE_BUBBLE).

However, when a controller is removed, its presence in the manager gets
checked against the current propagation phase of the controller, which
may have changed from when it was added. This can lead to crashes if the
controller was not disposed properly since it still has a reference in
one of the two models of the GtkShortcutManager.

To fix this, add a callback for `notify::propagation-phase`, which
removes the controller from all possible models and readds it again with
its current phase. This callback is only disconnected permanently when
the controller is manually removed with
`gtk_shortcut_manager_default_remove_controller()`.

Closes #6246
This commit is contained in:
Matthijs Velsink
2024-04-07 14:37:55 +02:00
committed by Matthias Clasen
parent 49e145ba70
commit 137be79931

View File

@@ -82,8 +82,8 @@ gtk_shortcut_manager_get_model (GtkShortcutManager *self,
}
static void
gtk_shortcut_manager_default_add_controller (GtkShortcutManager *self,
GtkShortcutController *controller)
gtk_shortcut_manager_add_controller (GtkShortcutManager *self,
GtkShortcutController *controller)
{
GtkFlattenListModel *model;
GtkPropagationPhase phase;
@@ -98,13 +98,12 @@ gtk_shortcut_manager_default_add_controller (GtkShortcutManager *self,
}
static void
gtk_shortcut_manager_default_remove_controller (GtkShortcutManager *self,
GtkShortcutController *controller)
gtk_shortcut_manager_remove_controller_for_phase (GtkShortcutManager *self,
GtkShortcutController *controller,
GtkPropagationPhase phase)
{
GtkFlattenListModel *model;
GtkPropagationPhase phase;
phase = gtk_event_controller_get_propagation_phase (GTK_EVENT_CONTROLLER (controller));
model = gtk_shortcut_manager_get_model (self, phase);
if (model)
{
@@ -117,6 +116,41 @@ gtk_shortcut_manager_default_remove_controller (GtkShortcutManager *self,
}
}
static void
propagation_phase_changed (GtkShortcutController *controller,
GParamSpec *pspec,
GtkShortcutManager *self)
{
/* Remove from all models and readd */
gtk_shortcut_manager_remove_controller_for_phase (self, controller, GTK_PHASE_CAPTURE);
gtk_shortcut_manager_remove_controller_for_phase (self, controller, GTK_PHASE_BUBBLE);
gtk_shortcut_manager_add_controller (self, controller);
}
static void
gtk_shortcut_manager_default_add_controller (GtkShortcutManager *self,
GtkShortcutController *controller)
{
gtk_shortcut_manager_add_controller (self, controller);
g_signal_connect_object (controller, "notify::propagation-phase",
G_CALLBACK (propagation_phase_changed), self, G_CONNECT_DEFAULT);
}
static void
gtk_shortcut_manager_default_remove_controller (GtkShortcutManager *self,
GtkShortcutController *controller)
{
GtkPropagationPhase phase;
phase = gtk_event_controller_get_propagation_phase (GTK_EVENT_CONTROLLER (controller));
gtk_shortcut_manager_remove_controller_for_phase (self, controller, phase);
g_signal_handlers_disconnect_by_func (controller, propagation_phase_changed, self);
}
static void
gtk_shortcut_manager_default_init (GtkShortcutManagerInterface *iface)
{