From 33235f42ae8701326742d102d4e699e862a954ed Mon Sep 17 00:00:00 2001 From: Rich Young Date: Thu, 5 Feb 2026 19:03:35 -0500 Subject: [PATCH] fix(engine/linux): add GTK4 close-request and monitor updates --- .../platform/linux/fl_display_monitor.cc | 50 +++++++++++++++++++ .../flutter/shell/platform/linux/fl_view.cc | 27 +++++++--- .../shell/platform/linux/fl_window_monitor.cc | 28 +++++++++++ 3 files changed, 99 insertions(+), 6 deletions(-) diff --git a/engine/src/flutter/shell/platform/linux/fl_display_monitor.cc b/engine/src/flutter/shell/platform/linux/fl_display_monitor.cc index e6f12f69ace..75395d51b9e 100644 --- a/engine/src/flutter/shell/platform/linux/fl_display_monitor.cc +++ b/engine/src/flutter/shell/platform/linux/fl_display_monitor.cc @@ -84,6 +84,49 @@ static void monitor_removed_cb(FlDisplayMonitor* self, GdkMonitor* monitor) { notify_display_update(self); } +#if FLUTTER_LINUX_GTK4 +static void prune_display_ids_for_current_monitors(FlDisplayMonitor* self) { + GListModel* monitors = gdk_display_get_monitors(self->display); + guint n_monitors = g_list_model_get_n_items(monitors); + + GHashTable* current = g_hash_table_new(g_direct_hash, g_direct_equal); + for (guint i = 0; i < n_monitors; i++) { + GdkMonitor* monitor = GDK_MONITOR(g_list_model_get_item(monitors, i)); + g_hash_table_add(current, monitor); + } + + GHashTableIter iter; + gpointer key = nullptr; + g_hash_table_iter_init(&iter, self->display_ids_by_monitor); + while (g_hash_table_iter_next(&iter, &key, nullptr)) { + if (!g_hash_table_contains(current, key)) { + g_hash_table_iter_remove(&iter); + } + } + + GHashTableIter current_iter; + g_hash_table_iter_init(¤t_iter, current); + while (g_hash_table_iter_next(¤t_iter, &key, nullptr)) { + g_object_unref(G_OBJECT(key)); + } + g_hash_table_unref(current); +} + +static void monitors_changed_cb(GListModel* list, + guint position, + guint removed, + guint added, + gpointer user_data) { + (void)list; + (void)position; + (void)removed; + (void)added; + FlDisplayMonitor* self = FL_DISPLAY_MONITOR(user_data); + prune_display_ids_for_current_monitors(self); + notify_display_update(self); +} +#endif + static void fl_display_monitor_dispose(GObject* object) { FlDisplayMonitor* self = FL_DISPLAY_MONITOR(object); @@ -117,12 +160,19 @@ FlDisplayMonitor* fl_display_monitor_new(FlEngine* engine, void fl_display_monitor_start(FlDisplayMonitor* self) { g_return_if_fail(FL_IS_DISPLAY_MONITOR(self)); +#if FLUTTER_LINUX_GTK4 + GListModel* monitors = gdk_display_get_monitors(self->display); + g_signal_connect_object(monitors, "items-changed", + G_CALLBACK(monitors_changed_cb), self, + static_cast(0)); +#else g_signal_connect_object(self->display, "monitor-added", G_CALLBACK(monitor_added_cb), self, G_CONNECT_SWAPPED); g_signal_connect_object(self->display, "monitor-removed", G_CALLBACK(monitor_removed_cb), self, G_CONNECT_SWAPPED); +#endif notify_display_update(self); } diff --git a/engine/src/flutter/shell/platform/linux/fl_view.cc b/engine/src/flutter/shell/platform/linux/fl_view.cc index 4a7954422c2..ca32cbbff14 100644 --- a/engine/src/flutter/shell/platform/linux/fl_view.cc +++ b/engine/src/flutter/shell/platform/linux/fl_view.cc @@ -153,13 +153,23 @@ static FlGdkSurface* fl_view_get_toplevel_surface(FlView* self) { return toplevel != nullptr ? fl_gtk_widget_get_surface(toplevel) : nullptr; } -// Signal handler for GtkWidget::delete-event +// Signal handler for GtkWidget::delete-event (GTK3 only) static gboolean window_delete_event_cb(FlView* self) { fl_engine_request_app_exit(self->engine); // Stop the event from propagating. return TRUE; } +#if FLUTTER_LINUX_GTK4 +// Signal handler for GtkWindow::close-request. +static gboolean window_close_request_cb(GtkWindow* window, FlView* self) { + (void)window; + fl_engine_request_app_exit(self->engine); + // Stop the event from propagating. + return TRUE; +} +#endif + static void init_scrolling(FlView* self) { g_clear_object(&self->scrolling_manager); self->scrolling_manager = @@ -754,8 +764,13 @@ static void realize_cb(FlView* self) { GTK_WINDOW(toplevel_window)); // Handle requests by the user to close the application. +#if FLUTTER_LINUX_GTK4 + g_signal_connect(toplevel_window, "close-request", + G_CALLBACK(window_close_request_cb), self); +#else g_signal_connect_swapped(toplevel_window, "delete-event", G_CALLBACK(window_delete_event_cb), self); +#endif // Flutter engine will need to make the context current from raster thread // during initialization. @@ -767,6 +782,11 @@ static void realize_cb(FlView* self) { return; } +#if FLUTTER_LINUX_GTK4 + fl_text_input_handler_set_widget( + fl_engine_get_text_input_handler(self->engine), GTK_WIDGET(self)); +#endif + setup_cursor(self); handle_geometry_changed(self); @@ -1014,11 +1034,6 @@ static void setup_engine(FlView* self) { init_scrolling(self); init_touch(self); -#if FLUTTER_LINUX_GTK4 - fl_text_input_handler_set_widget( - fl_engine_get_text_input_handler(self->engine), - GTK_WIDGET(self)); -#endif self->on_pre_engine_restart_cb_id = g_signal_connect_swapped(self->engine, "on-pre-engine-restart", diff --git a/engine/src/flutter/shell/platform/linux/fl_window_monitor.cc b/engine/src/flutter/shell/platform/linux/fl_window_monitor.cc index b3c09bfe4c2..e6bf522977c 100644 --- a/engine/src/flutter/shell/platform/linux/fl_window_monitor.cc +++ b/engine/src/flutter/shell/platform/linux/fl_window_monitor.cc @@ -55,6 +55,13 @@ static void title_notify_cb(FlWindowMonitor* self) { self->on_title_notify(); } +#if FLUTTER_LINUX_GTK4 +static gboolean close_request_cb(FlWindowMonitor* self) { + flutter::IsolateScope scope(self->isolate); + self->on_close(); + return TRUE; +} +#else static gboolean delete_event_cb(FlWindowMonitor* self, GdkEvent* event) { flutter::IsolateScope scope(self->isolate); self->on_close(); @@ -62,6 +69,18 @@ static gboolean delete_event_cb(FlWindowMonitor* self, GdkEvent* event) { // Stop default behaviour of destroying the window. return TRUE; } +#endif + +#if FLUTTER_LINUX_GTK4 +static gboolean close_request_cb(GtkWindow* window, FlWindowMonitor* self) { + (void)window; + flutter::IsolateScope scope(self->isolate); + self->on_close(); + + // Stop default behaviour of destroying the window. + return TRUE; +} +#endif static void destroy_cb(FlWindowMonitor* self) { flutter::IsolateScope scope(self->isolate); @@ -114,8 +133,17 @@ G_MODULE_EXPORT FlWindowMonitor* fl_window_monitor_new( G_CALLBACK(is_active_notify_cb), self); g_signal_connect_swapped(window, "notify::title", G_CALLBACK(title_notify_cb), self); +#if FLUTTER_LINUX_GTK4 + g_signal_connect(window, "close-request", G_CALLBACK(close_request_cb), self); +#else +#if FLUTTER_LINUX_GTK4 + g_signal_connect_swapped(window, "close-request", + G_CALLBACK(close_request_cb), self); +#else g_signal_connect_swapped(window, "delete-event", G_CALLBACK(delete_event_cb), self); +#endif +#endif g_signal_connect_swapped(window, "destroy", G_CALLBACK(destroy_cb), self); return self;