Issue #11957: ignore events on all display shell when one is being created.

This fixes a never-ending flickering blocking the GUI, until a manual
focus event (such as moving a window in front) breaks the infinite loop
of handling focus events.
This commit is contained in:
Jehan 2024-08-25 23:50:26 +02:00
parent b728ecb9f2
commit c5db158f58
5 changed files with 101 additions and 10 deletions

View File

@ -306,3 +306,39 @@ gimp_displays_unset_busy (Gimp *gimp)
gimp_display_shell_unset_override_cursor (shell);
}
}
/**
* gimp_displays_accept_focus_events:
* @gimp:
*
* When this returns %FALSE, we should ignore focus events. In
* particular, I had weird cases in multi-window mode, before a new
* image window was actually displayed on screen, there seemed to be
* infinite loops of focus events switching the active display between
* the current active one and the one being created, with flickering
* window title and the new display never actually displayed until we
* broke the loop by manually getting out of focus (see #11957).
* This is why when any of the display is not fully drawn yet (which
* usually means it is being created), we temporarily ignore focus
* events on all displays.
*/
gboolean
gimp_displays_accept_focus_events (Gimp *gimp)
{
GList *list;
g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
for (list = gimp_get_display_iter (gimp);
list;
list = g_list_next (list))
{
GimpDisplay *display = list->data;
GimpDisplayShell *shell = gimp_display_get_shell (display);
if (! gimp_display_shell_is_drawn (shell))
return FALSE;
}
return TRUE;
}

View File

@ -19,18 +19,19 @@
#define __GIMP_DISPLAY_FOREACH_H__
gboolean gimp_displays_dirty (Gimp *gimp);
GimpContainer * gimp_displays_get_dirty_images (Gimp *gimp);
void gimp_displays_delete (Gimp *gimp);
void gimp_displays_close (Gimp *gimp);
void gimp_displays_reconnect (Gimp *gimp,
GimpImage *old,
GimpImage *new);
gboolean gimp_displays_dirty (Gimp *gimp);
GimpContainer * gimp_displays_get_dirty_images (Gimp *gimp);
void gimp_displays_delete (Gimp *gimp);
void gimp_displays_close (Gimp *gimp);
void gimp_displays_reconnect (Gimp *gimp,
GimpImage *old,
GimpImage *new);
gint gimp_displays_get_num_visible (Gimp *gimp);
gint gimp_displays_get_num_visible (Gimp *gimp);
void gimp_displays_set_busy (Gimp *gimp);
void gimp_displays_unset_busy (Gimp *gimp);
void gimp_displays_set_busy (Gimp *gimp);
void gimp_displays_unset_busy (Gimp *gimp);
gboolean gimp_displays_accept_focus_events (Gimp *gimp);
#endif /* __GIMP_DISPLAY_FOREACH_H__ */

View File

@ -63,6 +63,7 @@
#include "gimpcanvas.h"
#include "gimpdisplay.h"
#include "gimpdisplay-foreach.h"
#include "gimpdisplayshell.h"
#include "gimpdisplayshell-autoscroll.h"
#include "gimpdisplayshell-cursor.h"
@ -173,6 +174,15 @@ gimp_display_shell_events (GtkWidget *widget,
gimp = gimp_display_get_gimp (shell->display);
/* When a display is being created, we may have some weird cases where
* we get never-ending focus events fighting for which image window
* should have focus, in an infinite loop. See #11957.
* Just ignore focus events in these cases, and in particular, don't
* set the display from such focus events.
*/
if (! gimp_displays_accept_focus_events (gimp))
return TRUE;
switch (event->type)
{
case GDK_KEY_PRESS:

View File

@ -187,6 +187,9 @@ static void gimp_display_shell_transform_overlay (GimpDisplayShell *shell,
GtkWidget *child,
gdouble *x,
gdouble *y);
static gboolean gimp_display_shell_draw (GimpDisplayShell *shell,
cairo_t *cr,
gpointer *data);
G_DEFINE_TYPE_WITH_CODE (GimpDisplayShell, gimp_display_shell,
@ -373,6 +376,7 @@ gimp_display_shell_init (GimpDisplayShell *shell)
shell->equidistance_side_vertical = GIMP_ARRANGE_HFILL;
shell->near_layer_vertical1 = NULL;
shell->near_layer_vertical2 = NULL;
shell->drawn = FALSE;
env = g_getenv ("GIMP_DISPLAY_RENDER_BUF_SIZE");
@ -429,6 +433,10 @@ gimp_display_shell_init (GimpDisplayShell *shell)
G_CALLBACK (gimp_display_shell_events),
shell);
g_signal_connect (shell, "draw",
G_CALLBACK (gimp_display_shell_draw),
NULL);
gimp_help_connect (GTK_WIDGET (shell), NULL, gimp_standard_help_func,
GIMP_HELP_IMAGE_WINDOW, NULL, NULL);
}
@ -1333,6 +1341,19 @@ gimp_display_shell_transform_overlay (GimpDisplayShell *shell,
}
}
static gboolean
gimp_display_shell_draw (GimpDisplayShell *shell,
cairo_t *cr,
gpointer *data)
{
g_signal_handlers_disconnect_by_func (G_OBJECT (shell),
G_CALLBACK (gimp_display_shell_draw),
data);
shell->drawn = TRUE;
return FALSE;
}
/* public functions */
@ -2269,3 +2290,22 @@ gimp_display_shell_set_mask (GimpDisplayShell *shell,
gimp_display_shell_expose_full (shell);
gimp_display_shell_render_invalidate_full (shell);
}
/**
* gimp_display_shell_is_drawn:
* @shell:
*
* This should be run when you need to verify that the shell or its
* display were actually drawn. gtk_widget_is_visible(),
* gtk_widget_get_mapped() or gtk_widget_get_realized() are not enough
* because they may all return TRUE before the window is actually on
* screen.
*
* Returns: whether @shell was actually drawn on screen, i.e. the "draw"
* signal has run.
*/
gboolean
gimp_display_shell_is_drawn (GimpDisplayShell *shell)
{
return shell->drawn;
}

View File

@ -253,6 +253,8 @@ struct _GimpDisplayShell
GimpAlignmentType equidistance_side_vertical;
GimpLayer *near_layer_vertical1;
GimpLayer *near_layer_vertical2;
gboolean drawn;
};
struct _GimpDisplayShellClass
@ -359,5 +361,7 @@ void gimp_display_shell_set_mask (GimpDisplayShell *shell,
GeglColor *color,
gboolean inverted);
gboolean gimp_display_shell_is_drawn (GimpDisplayShell *shell);
#endif /* __GIMP_DISPLAY_SHELL_H__ */