From d92c237a1708e5352533486b116d64da3f28b532 Mon Sep 17 00:00:00 2001 From: Jehan Date: Mon, 15 Dec 2025 23:42:59 +0100 Subject: [PATCH] =?UTF-8?q?Issue=20#13709:=20wait=20we=20get=20our=20first?= =?UTF-8?q?=20surface=20focus=20before=20listing=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … input devices. Per Carlos' advice on gtk#7534, I wait for us to get a focus, since the pad devices are only created at that point. Note that this is a Wayland-only issue, but since it doesn't matter too much that input devices are not initialized before we have a focused GUI anyway, let's make this simpler. At the earliest, the splash focus can announce a focus, but since it is possible to start GIMP without the splash, display shells will also possibly announce the first focus (there will always be a display shell focusing at some point for any GUI GIMP!). --- app/core/gimp.c | 30 +++++ app/core/gimp.h | 6 + app/display/gimpdisplayshell-tool-events.c | 9 +- app/gui/splash.c | 21 +++ app/widgets/gimpdevicemanager.c | 112 ++++++++++------ app/widgets/gimpdevices.c | 147 ++++++++++++--------- 6 files changed, 217 insertions(+), 108 deletions(-) diff --git a/app/core/gimp.c b/app/core/gimp.c index e59145209b..039bf2bc1b 100644 --- a/app/core/gimp.c +++ b/app/core/gimp.c @@ -97,6 +97,7 @@ enum CLIPBOARD_CHANGED, FILTER_HISTORY_CHANGED, IMAGE_OPENED, + FOCUSED_ONCE, LAST_SIGNAL }; @@ -205,6 +206,13 @@ gimp_class_init (GimpClass *klass) NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_FILE); + gimp_signals[FOCUSED_ONCE] = + g_signal_new ("focused-once", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + object_class->constructed = gimp_constructed; object_class->set_property = gimp_set_property; object_class->get_property = gimp_get_property; @@ -293,6 +301,8 @@ gimp_init (Gimp *gimp) gimp->templates = gimp_list_new (GIMP_TYPE_TEMPLATE, TRUE); gimp_object_set_static_name (GIMP_OBJECT (gimp->templates), "templates"); + + gimp->focused_once = FALSE; } static void @@ -917,6 +927,26 @@ gimp_exit (Gimp *gimp, gimp, NULL); } +void +gimp_set_focused_once (Gimp *gimp) +{ + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + if (! gimp->focused_once) + { + gimp->focused_once = TRUE; + g_signal_emit (gimp, gimp_signals[FOCUSED_ONCE], 0); + } +} + +gboolean +gimp_has_focused_once (Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE); + + return gimp->focused_once; +} + GList * gimp_get_image_iter (Gimp *gimp) { diff --git a/app/core/gimp.h b/app/core/gimp.h index b81b32f2f7..514abc76d2 100644 --- a/app/core/gimp.h +++ b/app/core/gimp.h @@ -133,6 +133,9 @@ struct _Gimp /* the context used by the interface */ GimpContext *user_context; + + /* GUI focus occured. See gtk#7534. */ + gboolean focused_once; }; struct _GimpClass @@ -189,6 +192,9 @@ gboolean gimp_is_restored (Gimp *gimp); void gimp_exit (Gimp *gimp, gboolean force); +void gimp_set_focused_once (Gimp *gimp); +gboolean gimp_has_focused_once (Gimp *gimp); + GList * gimp_get_image_iter (Gimp *gimp); GList * gimp_get_display_iter (Gimp *gimp); GList * gimp_get_image_windows (Gimp *gimp); diff --git a/app/display/gimpdisplayshell-tool-events.c b/app/display/gimpdisplayshell-tool-events.c index 62805d66d0..58447b5d80 100644 --- a/app/display/gimpdisplayshell-tool-events.c +++ b/app/display/gimpdisplayshell-tool-events.c @@ -258,8 +258,13 @@ gimp_display_shell_events (GtkWidget *widget, { GdkEventFocus *fevent = (GdkEventFocus *) event; - if (fevent->in && shell->display->config->activate_on_focus) - set_display = TRUE; + if (fevent->in) + { + gimp_set_focused_once (gimp); + + if (shell->display->config->activate_on_focus) + set_display = TRUE; + } } break; diff --git a/app/gui/splash.c b/app/gui/splash.c index 4d5ec1aaea..a2e98f801c 100644 --- a/app/gui/splash.c +++ b/app/gui/splash.c @@ -101,6 +101,10 @@ static gboolean splash_key_event (GtkWidget *window, GimpSplash *splash); static gboolean splash_unset_alt (GimpSplash *splash); +static gboolean splash_window_focus (GtkWidget *window, + GtkDirectionType direction, + Gimp *gimp); + static void splash_rectangle_union (GdkRectangle *dest, PangoRectangle *pango_rect, gint offset_x, @@ -293,6 +297,10 @@ splash_create (Gimp *gimp, gtk_box_pack_end (GTK_BOX (vbox), splash->progress, FALSE, FALSE, 0); gtk_widget_show (splash->progress); + g_signal_connect (splash->window, "focus", + G_CALLBACK (splash_window_focus), + gimp); + gtk_widget_show (splash->window); #ifdef G_OS_WIN32 @@ -485,6 +493,19 @@ splash_unset_alt (GimpSplash *splash) return G_SOURCE_REMOVE; } +static gboolean +splash_window_focus (GtkWidget *window, + GtkDirectionType direction, + Gimp *gimp) +{ + g_signal_handlers_disconnect_by_func (splash->window, + G_CALLBACK (splash_window_focus), + gimp); + gimp_set_focused_once (gimp); + + return FALSE; +} + /* area returns the union of the previous and new ink rectangles */ static void splash_position_layouts (GimpSplash *splash, diff --git a/app/widgets/gimpdevicemanager.c b/app/widgets/gimpdevicemanager.c index 1d24ee245f..41622a2f26 100644 --- a/app/widgets/gimpdevicemanager.c +++ b/app/widgets/gimpdevicemanager.c @@ -77,6 +77,8 @@ static void gimp_device_manager_get_property (GObject *object, GValue *value, GParamSpec *pspec); +static void gimp_device_manager_focused_once (GimpDeviceManager *manager); + static void gimp_device_manager_display_opened (GdkDisplayManager *disp_manager, GdkDisplay *display, GimpDeviceManager *manager); @@ -168,54 +170,17 @@ gimp_device_manager_constructed (GObject *object) { GimpDeviceManager *manager = GIMP_DEVICE_MANAGER (object); GimpDeviceManagerPrivate *private = GET_PRIVATE (object); - GdkDisplayManager *disp_manager; - GSList *displays; - GSList *list; - GdkDisplay *display; - GdkSeat *seat; - GdkDevice *pointer; - GimpDeviceInfo *device_info; - GimpContext *user_context; G_OBJECT_CLASS (parent_class)->constructed (object); gimp_assert (GIMP_IS_GIMP (private->gimp)); - disp_manager = gdk_display_manager_get (); - - displays = gdk_display_manager_list_displays (disp_manager); - - /* present displays in the order in which they were opened */ - displays = g_slist_reverse (displays); - - for (list = displays; list; list = g_slist_next (list)) - { - gimp_device_manager_display_opened (disp_manager, list->data, manager); - } - - g_slist_free (displays); - - g_signal_connect (disp_manager, "display-opened", - G_CALLBACK (gimp_device_manager_display_opened), - manager); - - display = gdk_display_get_default (); - seat = gdk_display_get_default_seat (display); - - pointer = gdk_seat_get_pointer (seat); - - device_info = gimp_device_info_get_by_device (pointer); - gimp_device_manager_set_current_device (manager, device_info); - - g_signal_connect_object (private->gimp->config, "notify::devices-share-tool", - G_CALLBACK (gimp_device_manager_config_notify), - manager, 0); - - user_context = gimp_get_user_context (private->gimp); - - g_signal_connect_object (user_context, "tool-changed", - G_CALLBACK (gimp_device_manager_tool_changed), - manager, 0); + if (gimp_has_focused_once (private->gimp)) + gimp_device_manager_focused_once (manager); + else + g_signal_connect_object (private->gimp, "focused-once", + G_CALLBACK (gimp_device_manager_focused_once), + manager, G_CONNECT_SWAPPED); } static void @@ -397,6 +362,59 @@ gimp_device_manager_reconfigure_pads (GimpDeviceManager *manager) /* private functions */ +static void +gimp_device_manager_focused_once (GimpDeviceManager *manager) +{ + GimpDeviceManagerPrivate *private = GET_PRIVATE (manager); + GdkDisplayManager *disp_manager; + GSList *displays; + GSList *list; + GdkDisplay *display; + GdkSeat *seat; + GdkDevice *pointer; + GimpDeviceInfo *device_info; + GimpContext *user_context; + + g_signal_handlers_disconnect_by_func (private->gimp, + G_CALLBACK (gimp_device_manager_focused_once), + manager); + + disp_manager = gdk_display_manager_get (); + displays = gdk_display_manager_list_displays (disp_manager); + + /* present displays in the order in which they were opened */ + displays = g_slist_reverse (displays); + + for (list = displays; list; list = g_slist_next (list)) + { + gimp_device_manager_display_opened (disp_manager, list->data, manager); + } + + g_slist_free (displays); + + g_signal_connect (disp_manager, "display-opened", + G_CALLBACK (gimp_device_manager_display_opened), + manager); + + display = gdk_display_get_default (); + seat = gdk_display_get_default_seat (display); + + pointer = gdk_seat_get_pointer (seat); + + device_info = gimp_device_info_get_by_device (pointer); + gimp_device_manager_set_current_device (manager, device_info); + + g_signal_connect_object (private->gimp->config, "notify::devices-share-tool", + G_CALLBACK (gimp_device_manager_config_notify), + manager, 0); + + user_context = gimp_get_user_context (private->gimp); + + g_signal_connect_object (user_context, "tool-changed", + G_CALLBACK (gimp_device_manager_tool_changed), + manager, 0); +} + static void gimp_device_manager_display_opened (GdkDisplayManager *disp_manager, GdkDisplay *display, @@ -427,6 +445,14 @@ gimp_device_manager_display_opened (GdkDisplayManager *disp_manager, device = gdk_seat_get_pointer (seat); gimp_device_manager_device_added (seat, device, manager); + /* XXX The whole reason why we created the "focused-once" signal on + * Gimp object is because on Wayland, GDK returns a NULL GdkDevice for + * tablet pads until a surface got in focus at least once (see + * gtk#7534). So we wait for this one-time focus before querying + * devices. It makes things a bit more bothersome, but is acceptable + * since anyway we can't do anything with input devices before we have + * a GUI. + */ devices = gdk_seat_get_slaves (seat, GDK_SEAT_CAPABILITY_ALL); /* create device info structures for present devices */ diff --git a/app/widgets/gimpdevices.c b/app/widgets/gimpdevices.c index e6c98ffac0..23c285685f 100644 --- a/app/widgets/gimpdevices.c +++ b/app/widgets/gimpdevices.c @@ -50,6 +50,9 @@ static gboolean devicerc_deleted = FALSE; +static void gimp_devices_restore_on_focused_once (Gimp *gimp); + + /* public functions */ void @@ -87,69 +90,12 @@ gimp_devices_exit (Gimp *gimp) void gimp_devices_restore (Gimp *gimp) { - GimpDeviceManager *manager; - GList *list; - GFile *file; - GError *error = NULL; - - g_return_if_fail (GIMP_IS_GIMP (gimp)); - - manager = gimp_devices_get_manager (gimp); - - g_return_if_fail (GIMP_IS_DEVICE_MANAGER (manager)); - - for (list = GIMP_LIST (manager)->queue->head; - list; - list = g_list_next (list)) - { - GimpDeviceInfo *device_info = list->data; - - gimp_device_info_save_tool (device_info); - gimp_device_info_set_default_tool (device_info); - } - - file = gimp_directory_file ("devicerc", NULL); - - if (gimp->be_verbose) - g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file)); - - if (! gimp_config_deserialize_file (GIMP_CONFIG (manager), - file, - gimp, - &error)) - { - if (error->code != GIMP_CONFIG_ERROR_OPEN_ENOENT) - gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message); - - g_error_free (error); - /* don't bail out here */ - } - - g_object_unref (file); - - for (list = GIMP_LIST (manager)->queue->head; - list; - list = g_list_next (list)) - { - GimpDeviceInfo *device_info = list->data; - - if (! GIMP_TOOL_PRESET (device_info)->tool_options) - { - gimp_device_info_save_tool (device_info); - - g_printerr ("%s: set default tool on loaded GimpDeviceInfo without tool options: %s\n", - G_STRFUNC, gimp_object_get_name (device_info)); - } - } - - if (! GIMP_GUI_CONFIG (gimp->config)->devices_share_tool) - { - GimpDeviceInfo *current_device; - - current_device = gimp_device_manager_get_current_device (manager); - - gimp_device_info_restore_tool (current_device); - } + if (gimp_has_focused_once (gimp)) + gimp_devices_restore_on_focused_once (gimp); + else + g_signal_connect (gimp, "focused-once", + G_CALLBACK (gimp_devices_restore_on_focused_once), + NULL); } void @@ -395,3 +341,78 @@ gimp_devices_check_change (Gimp *gimp, return FALSE; } + + +/* Private functions */ + +static void +gimp_devices_restore_on_focused_once (Gimp *gimp) +{ + GimpDeviceManager *manager; + GList *list; + GFile *file; + GError *error = NULL; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + g_signal_handlers_disconnect_by_func (gimp, + G_CALLBACK (gimp_devices_restore_on_focused_once), + NULL); + + manager = gimp_devices_get_manager (gimp); + + g_return_if_fail (GIMP_IS_DEVICE_MANAGER (manager)); + + for (list = GIMP_LIST (manager)->queue->head; + list; + list = g_list_next (list)) + { + GimpDeviceInfo *device_info = list->data; + + gimp_device_info_save_tool (device_info); + gimp_device_info_set_default_tool (device_info); + } + + file = gimp_directory_file ("devicerc", NULL); + + if (gimp->be_verbose) + g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file)); + + if (! gimp_config_deserialize_file (GIMP_CONFIG (manager), + file, + gimp, + &error)) + { + if (error->code != GIMP_CONFIG_ERROR_OPEN_ENOENT) + gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message); + + g_error_free (error); + /* don't bail out here */ + } + + g_object_unref (file); + + for (list = GIMP_LIST (manager)->queue->head; + list; + list = g_list_next (list)) + { + GimpDeviceInfo *device_info = list->data; + + if (! GIMP_TOOL_PRESET (device_info)->tool_options) + { + gimp_device_info_save_tool (device_info); + + g_printerr ("%s: set default tool on loaded GimpDeviceInfo without tool options: %s\n", + G_STRFUNC, gimp_object_get_name (device_info)); + } + } + + if (! GIMP_GUI_CONFIG (gimp->config)->devices_share_tool) + { + GimpDeviceInfo *current_device; + + current_device = gimp_device_manager_get_current_device (manager); + + gimp_device_info_restore_tool (current_device); + } +}