fix(engine/linux): adapt windowing and accessibility for GTK4

This commit is contained in:
Rich Young 2026-02-05 15:52:37 -05:00
parent ec5a872f41
commit 2f067fd481
8 changed files with 143 additions and 3 deletions

View File

@ -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<FlAccessibilityHandlerPrivate*>(
@ -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) {

View File

@ -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));
}

View File

@ -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);

View File

@ -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;

View File

@ -4,18 +4,24 @@
#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h"
#if !FLUTTER_LINUX_GTK4
#include <atk/atk.h>
#endif
#if FLUTTER_LINUX_GTK4
#include <gdk/wayland/gdkwayland.h>
#else
#include <gdk/gdkwayland.h>
#endif
#if !FLUTTER_LINUX_GTK4
#include <gtk/gtk-a11y.h>
#endif
#include <cstring>
#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<GdkModifierType>(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

View File

@ -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

View File

@ -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),

View File

@ -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) {