From 2f067fd481aa308e828f7725ea80fdfb661c0ade Mon Sep 17 00:00:00 2001 From: Rich Young Date: Thu, 5 Feb 2026 15:52:37 -0500 Subject: [PATCH] fix(engine/linux): adapt windowing and accessibility for GTK4 --- .../linux/fl_accessibility_handler.cc | 11 ++++ .../shell/platform/linux/fl_application.cc | 7 ++- .../platform/linux/fl_display_monitor.cc | 16 +++++- .../platform/linux/fl_platform_handler.cc | 44 +++++++++++++++ .../flutter/shell/platform/linux/fl_view.cc | 55 +++++++++++++++++++ .../shell/platform/linux/fl_view_private.h | 5 +- .../shell/platform/linux/fl_window_monitor.cc | 4 ++ .../platform/linux/fl_window_state_monitor.cc | 4 ++ 8 files changed, 143 insertions(+), 3 deletions(-) diff --git a/engine/src/flutter/shell/platform/linux/fl_accessibility_handler.cc b/engine/src/flutter/shell/platform/linux/fl_accessibility_handler.cc index 5e59795405c..65acbc6f0bf 100644 --- a/engine/src/flutter/shell/platform/linux/fl_accessibility_handler.cc +++ b/engine/src/flutter/shell/platform/linux/fl_accessibility_handler.cc @@ -6,7 +6,9 @@ #include "flutter/shell/platform/linux/fl_accessibility_channel.h" #include "flutter/shell/platform/linux/fl_engine_private.h" +#if !FLUTTER_LINUX_GTK4 #include "flutter/shell/platform/linux/fl_view_private.h" +#endif typedef struct { GWeakRef engine; @@ -22,6 +24,14 @@ static void send_announcement(int64_t view_id, FlTextDirection text_direction, FlAssertiveness assertiveness, gpointer user_data) { +#if FLUTTER_LINUX_GTK4 + (void)view_id; + (void)message; + (void)text_direction; + (void)assertiveness; + (void)user_data; + return; +#else FlAccessibilityHandler* self = FL_ACCESSIBILITY_HANDLER(user_data); FlAccessibilityHandlerPrivate* priv = reinterpret_cast( @@ -41,6 +51,7 @@ static void send_announcement(int64_t view_id, FlViewAccessible* accessible = fl_view_get_accessible(view); fl_view_accessible_send_announcement( accessible, message, assertiveness == FL_ASSERTIVENESS_ASSERTIVE); +#endif } static void fl_accessibility_handler_dispose(GObject* object) { diff --git a/engine/src/flutter/shell/platform/linux/fl_application.cc b/engine/src/flutter/shell/platform/linux/fl_application.cc index 9077f7a0b1f..71e95cbb97b 100644 --- a/engine/src/flutter/shell/platform/linux/fl_application.cc +++ b/engine/src/flutter/shell/platform/linux/fl_application.cc @@ -37,7 +37,8 @@ G_DEFINE_TYPE_WITH_CODE(FlApplication, // Called when the first frame is received. static void first_frame_cb(FlApplication* self, FlView* view) { #if FLUTTER_LINUX_GTK4 - GtkWidget* window = gtk_widget_get_root(GTK_WIDGET(view)); + GtkRoot* root = gtk_widget_get_root(GTK_WIDGET(view)); + GtkWidget* window = root != nullptr ? GTK_WIDGET(root) : nullptr; #else GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(view)); #endif @@ -80,7 +81,11 @@ static GtkWindow* fl_application_create_window(FlApplication* self, if (use_header_bar) { GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); +#if FLUTTER_LINUX_GTK4 + gtk_header_bar_set_show_title_buttons(header_bar, TRUE); +#else gtk_header_bar_set_show_close_button(header_bar, TRUE); +#endif gtk_window_set_titlebar(GTK_WINDOW(window), GTK_WIDGET(header_bar)); } 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 bea40b2cbd1..e6f12f69ace 100644 --- a/engine/src/flutter/shell/platform/linux/fl_display_monitor.cc +++ b/engine/src/flutter/shell/platform/linux/fl_display_monitor.cc @@ -30,13 +30,23 @@ static void notify_display_update(FlDisplayMonitor* self) { return; } +#if FLUTTER_LINUX_GTK4 + GListModel* monitors = gdk_display_get_monitors(self->display); + guint n_monitors = g_list_model_get_n_items(monitors); +#else int n_monitors = gdk_display_get_n_monitors(self->display); +#endif g_autofree FlutterEngineDisplay* displays = g_new0(FlutterEngineDisplay, n_monitors); - for (int i = 0; i < n_monitors; i++) { + for (guint i = 0; i < n_monitors; i++) { FlutterEngineDisplay* display = &displays[i]; +#if FLUTTER_LINUX_GTK4 + GdkMonitor* monitor = + GDK_MONITOR(g_list_model_get_item(monitors, i)); +#else GdkMonitor* monitor = gdk_display_get_monitor(self->display, i); +#endif FlutterEngineDisplayId display_id = GPOINTER_TO_INT( g_hash_table_lookup(self->display_ids_by_monitor, monitor)); if (display_id == 0) { @@ -56,6 +66,10 @@ static void notify_display_update(FlDisplayMonitor* self) { display->width = geometry.width; display->height = geometry.height; display->device_pixel_ratio = gdk_monitor_get_scale_factor(monitor); + +#if FLUTTER_LINUX_GTK4 + g_object_unref(monitor); +#endif } fl_engine_notify_display_update(engine, displays, n_monitors); diff --git a/engine/src/flutter/shell/platform/linux/fl_platform_handler.cc b/engine/src/flutter/shell/platform/linux/fl_platform_handler.cc index 6be22e94363..e3bdd69b8d6 100644 --- a/engine/src/flutter/shell/platform/linux/fl_platform_handler.cc +++ b/engine/src/flutter/shell/platform/linux/fl_platform_handler.cc @@ -34,6 +34,28 @@ struct _FlPlatformHandler { G_DEFINE_TYPE(FlPlatformHandler, fl_platform_handler, G_TYPE_OBJECT) +#if FLUTTER_LINUX_GTK4 +// Called when clipboard text received. +static void clipboard_text_cb(GObject* object, + GAsyncResult* result, + gpointer user_data) { + g_autoptr(FlMethodCall) method_call = FL_METHOD_CALL(user_data); + g_autofree gchar* text = + gdk_clipboard_read_text_finish(GDK_CLIPBOARD(object), result, nullptr); + fl_platform_channel_respond_clipboard_get_data(method_call, text); +} + +// Called when clipboard text received during has_strings. +static void clipboard_text_has_strings_cb(GObject* object, + GAsyncResult* result, + gpointer user_data) { + g_autoptr(FlMethodCall) method_call = FL_METHOD_CALL(user_data); + g_autofree gchar* text = + gdk_clipboard_read_text_finish(GDK_CLIPBOARD(object), result, nullptr); + fl_platform_channel_respond_clipboard_has_strings( + method_call, text != nullptr && strlen(text) > 0); +} +#else // Called when clipboard text received. static void clipboard_text_cb(GtkClipboard* clipboard, const gchar* text, @@ -50,14 +72,21 @@ static void clipboard_text_has_strings_cb(GtkClipboard* clipboard, fl_platform_channel_respond_clipboard_has_strings( method_call, text != nullptr && strlen(text) > 0); } +#endif // Called when Flutter wants to copy to the clipboard. static FlMethodResponse* clipboard_set_data(FlMethodCall* method_call, const gchar* text, gpointer user_data) { +#if FLUTTER_LINUX_GTK4 + GdkClipboard* clipboard = + gdk_display_get_clipboard(gdk_display_get_default()); + gdk_clipboard_set_text(clipboard, text); +#else GtkClipboard* clipboard = gtk_clipboard_get_default(gdk_display_get_default()); gtk_clipboard_set_text(clipboard, text, -1); +#endif return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); } @@ -72,10 +101,17 @@ static FlMethodResponse* clipboard_get_data(FlMethodCall* method_call, nullptr)); } +#if FLUTTER_LINUX_GTK4 + GdkClipboard* clipboard = + gdk_display_get_clipboard(gdk_display_get_default()); + gdk_clipboard_read_text_async(clipboard, nullptr, clipboard_text_cb, + g_object_ref(method_call)); +#else GtkClipboard* clipboard = gtk_clipboard_get_default(gdk_display_get_default()); gtk_clipboard_request_text(clipboard, clipboard_text_cb, g_object_ref(method_call)); +#endif // Will respond later. return nullptr; @@ -85,10 +121,18 @@ static FlMethodResponse* clipboard_get_data(FlMethodCall* method_call, // be pasted, without actually accessing the clipboard content itself. static FlMethodResponse* clipboard_has_strings(FlMethodCall* method_call, gpointer user_data) { +#if FLUTTER_LINUX_GTK4 + GdkClipboard* clipboard = + gdk_display_get_clipboard(gdk_display_get_default()); + gdk_clipboard_read_text_async(clipboard, nullptr, + clipboard_text_has_strings_cb, + g_object_ref(method_call)); +#else GtkClipboard* clipboard = gtk_clipboard_get_default(gdk_display_get_default()); gtk_clipboard_request_text(clipboard, clipboard_text_has_strings_cb, g_object_ref(method_call)); +#endif // Will respond later. return nullptr; diff --git a/engine/src/flutter/shell/platform/linux/fl_view.cc b/engine/src/flutter/shell/platform/linux/fl_view.cc index 2ad5ae7ec8e..7b886c3a787 100644 --- a/engine/src/flutter/shell/platform/linux/fl_view.cc +++ b/engine/src/flutter/shell/platform/linux/fl_view.cc @@ -4,18 +4,24 @@ #include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h" +#if !FLUTTER_LINUX_GTK4 #include +#endif #if FLUTTER_LINUX_GTK4 #include #else #include #endif +#if !FLUTTER_LINUX_GTK4 #include +#endif #include #include "flutter/common/constants.h" +#if !FLUTTER_LINUX_GTK4 #include "flutter/shell/platform/linux/fl_accessible_node.h" +#endif #include "flutter/shell/platform/linux/fl_compositor_opengl.h" #include "flutter/shell/platform/linux/fl_compositor_software.h" #include "flutter/shell/platform/linux/fl_engine_private.h" @@ -25,9 +31,13 @@ #include "flutter/shell/platform/linux/fl_plugin_registrar_private.h" #include "flutter/shell/platform/linux/fl_pointer_manager.h" #include "flutter/shell/platform/linux/fl_scrolling_manager.h" +#if !FLUTTER_LINUX_GTK4 #include "flutter/shell/platform/linux/fl_socket_accessible.h" +#endif #include "flutter/shell/platform/linux/fl_touch_manager.h" +#if !FLUTTER_LINUX_GTK4 #include "flutter/shell/platform/linux/fl_view_accessible.h" +#endif #include "flutter/shell/platform/linux/fl_view_private.h" #include "flutter/shell/platform/linux/fl_window_state_monitor.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h" @@ -78,8 +88,10 @@ struct _FlView { // Manages touch events. FlTouchManager* touch_manager; +#if !FLUTTER_LINUX_GTK4 // Accessible tree from Flutter, exposed as an AtkPlug. FlViewAccessible* view_accessible; +#endif // Signal subscripton for cursor changes. guint cursor_changed_cb_id; @@ -160,8 +172,26 @@ static void init_touch(FlView* self) { } static FlutterPointerDeviceKind get_device_kind(GdkEvent* event) { +#if FLUTTER_LINUX_GTK4 + GdkDevice* device = gdk_event_get_device(event); +#else GdkDevice* device = gdk_event_get_source_device(event); +#endif GdkInputSource source = gdk_device_get_source(device); +#if FLUTTER_LINUX_GTK4 + switch (source) { + case GDK_SOURCE_PEN: + case GDK_SOURCE_TABLET_PAD: + return kFlutterPointerDeviceKindStylus; + case GDK_SOURCE_TOUCHSCREEN: + return kFlutterPointerDeviceKindTouch; + case GDK_SOURCE_TOUCHPAD: + case GDK_SOURCE_TRACKPOINT: + case GDK_SOURCE_KEYBOARD: + case GDK_SOURCE_MOUSE: + return kFlutterPointerDeviceKindMouse; + } +#else switch (source) { case GDK_SOURCE_PEN: case GDK_SOURCE_ERASER: @@ -176,6 +206,8 @@ static FlutterPointerDeviceKind get_device_kind(GdkEvent* event) { case GDK_SOURCE_MOUSE: return kFlutterPointerDeviceKindMouse; } +#endif + return kFlutterPointerDeviceKindMouse; } // Called when the mouse cursor changes. @@ -187,8 +219,12 @@ static void cursor_changed_cb(FlView* self) { if (surface == nullptr) { return; } +#if FLUTTER_LINUX_GTK4 + g_autoptr(GdkCursor) cursor = gdk_cursor_new_from_name(cursor_name, nullptr); +#else g_autoptr(GdkCursor) cursor = gdk_cursor_new_from_name( fl_gtk_surface_get_display(surface), cursor_name); +#endif fl_gtk_surface_set_cursor(surface, cursor); } @@ -260,7 +296,12 @@ static void update_semantics_cb(FlView* self, return; } +#if !FLUTTER_LINUX_GTK4 fl_view_accessible_handle_update_semantics(self->view_accessible, update); +#else + (void)self; + (void)update; +#endif } // Invoked by the engine right before the engine is restarted. @@ -307,8 +348,12 @@ static void fl_view_plugin_registry_iface_init( static void sync_modifier_if_needed(FlView* self, GdkEvent* event) { guint event_time = gdk_event_get_time(event); +#if FLUTTER_LINUX_GTK4 + GdkModifierType event_state = gdk_event_get_modifier_state(event); +#else GdkModifierType event_state = static_cast(0); gdk_event_get_state(event, &event_state); +#endif fl_keyboard_manager_sync_modifier_if_needed( fl_engine_get_keyboard_manager(self->engine), event_state, event_time); } @@ -840,7 +885,9 @@ static void fl_view_dispose(GObject* object) { g_clear_object(&self->zoom_gesture); g_clear_object(&self->rotate_gesture); #endif +#if !FLUTTER_LINUX_GTK4 g_clear_object(&self->view_accessible); +#endif g_clear_object(&self->cancellable); G_OBJECT_CLASS(fl_view_parent_class)->dispose(object); @@ -848,7 +895,9 @@ static void fl_view_dispose(GObject* object) { // Implements GtkWidget::realize. static void fl_view_realize(GtkWidget* widget) { +#if !FLUTTER_LINUX_GTK4 FlView* self = FL_VIEW(widget); +#endif GTK_WIDGET_CLASS(fl_view_parent_class)->realize(widget); @@ -936,16 +985,20 @@ static void fl_view_class_init(FlViewClass* klass) { g_signal_new("first-frame", fl_view_get_type(), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); +#if !FLUTTER_LINUX_GTK4 gtk_widget_class_set_accessible_type(GTK_WIDGET_CLASS(klass), fl_socket_accessible_get_type()); +#endif } // Engine related construction. static void setup_engine(FlView* self) { +#if !FLUTTER_LINUX_GTK4 self->view_accessible = fl_view_accessible_new(self->engine, self->view_id); fl_socket_accessible_embed( FL_SOCKET_ACCESSIBLE(gtk_widget_get_accessible(GTK_WIDGET(self))), atk_plug_get_id(ATK_PLUG(self->view_accessible))); +#endif self->pointer_manager = fl_pointer_manager_new(self->view_id, self->engine); @@ -1144,7 +1197,9 @@ G_MODULE_EXPORT void fl_view_set_background_color(FlView* self, self->background_color = gdk_rgba_copy(color); } +#if !FLUTTER_LINUX_GTK4 FlViewAccessible* fl_view_get_accessible(FlView* self) { g_return_val_if_fail(FL_IS_VIEW(self), nullptr); return self->view_accessible; } +#endif diff --git a/engine/src/flutter/shell/platform/linux/fl_view_private.h b/engine/src/flutter/shell/platform/linux/fl_view_private.h index cbdcef2ba3e..3f630527b01 100644 --- a/engine/src/flutter/shell/platform/linux/fl_view_private.h +++ b/engine/src/flutter/shell/platform/linux/fl_view_private.h @@ -5,11 +5,13 @@ #ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_PRIVATE_H_ #define FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_PRIVATE_H_ -#include "flutter/shell/platform/linux/fl_view_accessible.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h" G_BEGIN_DECLS +#if !FLUTTER_LINUX_GTK4 +#include "flutter/shell/platform/linux/fl_view_accessible.h" + /** * fl_view_get_accessible: * @view: an #FlView. @@ -19,6 +21,7 @@ G_BEGIN_DECLS * Returns: an #FlViewAccessible. */ FlViewAccessible* fl_view_get_accessible(FlView* view); +#endif G_END_DECLS 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 24ae915a647..b3c09bfe4c2 100644 --- a/engine/src/flutter/shell/platform/linux/fl_window_monitor.cc +++ b/engine/src/flutter/shell/platform/linux/fl_window_monitor.cc @@ -27,6 +27,7 @@ struct _FlWindowMonitor { G_DEFINE_TYPE(FlWindowMonitor, fl_window_monitor, G_TYPE_OBJECT) +#if !FLUTTER_LINUX_GTK4 static gboolean configure_event_cb(FlWindowMonitor* self, GdkEventConfigure* event) { flutter::IsolateScope scope(self->isolate); @@ -42,6 +43,7 @@ static gboolean window_state_event_cb(FlWindowMonitor* self, return FALSE; } +#endif static void is_active_notify_cb(FlWindowMonitor* self) { flutter::IsolateScope scope(self->isolate); @@ -102,10 +104,12 @@ G_MODULE_EXPORT FlWindowMonitor* fl_window_monitor_new( self->on_title_notify = on_title_notify; self->on_close = on_close; self->on_destroy = on_destroy; +#if !FLUTTER_LINUX_GTK4 g_signal_connect_swapped(window, "configure-event", G_CALLBACK(configure_event_cb), self); g_signal_connect_swapped(window, "window-state-event", G_CALLBACK(window_state_event_cb), self); +#endif g_signal_connect_swapped(window, "notify::is-active", G_CALLBACK(is_active_notify_cb), self); g_signal_connect_swapped(window, "notify::title", G_CALLBACK(title_notify_cb), diff --git a/engine/src/flutter/shell/platform/linux/fl_window_state_monitor.cc b/engine/src/flutter/shell/platform/linux/fl_window_state_monitor.cc index 3470c4f2715..6318b9e6007 100644 --- a/engine/src/flutter/shell/platform/linux/fl_window_state_monitor.cc +++ b/engine/src/flutter/shell/platform/linux/fl_window_state_monitor.cc @@ -8,6 +8,7 @@ #include "flutter/shell/platform/linux/public/flutter_linux/fl_string_codec.h" +#if !FLUTTER_LINUX_GTK4 static constexpr const char* kFlutterLifecycleChannel = "flutter/lifecycle"; static constexpr const char* kAppLifecycleStateResumed = @@ -16,6 +17,7 @@ static constexpr const char* kAppLifecycleStateInactive = "AppLifecycleState.inactive"; static constexpr const char* kAppLifecycleStateHidden = "AppLifecycleState.hidden"; +#endif struct _FlWindowStateMonitor { GObject parent_instance; @@ -37,6 +39,7 @@ struct _FlWindowStateMonitor { G_DEFINE_TYPE(FlWindowStateMonitor, fl_window_state_monitor, G_TYPE_OBJECT); +#if !FLUTTER_LINUX_GTK4 static void send_lifecycle_state(FlWindowStateMonitor* self, const gchar* lifecycle_state) { g_autoptr(FlValue) value = fl_value_new_string(lifecycle_state); @@ -52,6 +55,7 @@ static void send_lifecycle_state(FlWindowStateMonitor* self, fl_binary_messenger_send_on_channel(self->messenger, kFlutterLifecycleChannel, message, nullptr, nullptr, nullptr); } +#endif #if !FLUTTER_LINUX_GTK4 static gboolean is_hidden(GdkWindowState state) {