diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index a76e2b9f70e1b..016f10fd428c4 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -42351,6 +42351,8 @@ ORIGIN: ../../../flutter/shell/platform/linux/fl_settings_plugin_test.cc + ../.. ORIGIN: ../../../flutter/shell/platform/linux/fl_settings_portal.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_settings_portal.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_settings_portal_test.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_socket_accessible.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_socket_accessible.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_standard_message_codec.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_standard_message_codec_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_standard_method_codec.cc + ../../../flutter/LICENSE @@ -45264,6 +45266,8 @@ FILE: ../../../flutter/shell/platform/linux/fl_settings_plugin_test.cc FILE: ../../../flutter/shell/platform/linux/fl_settings_portal.cc FILE: ../../../flutter/shell/platform/linux/fl_settings_portal.h FILE: ../../../flutter/shell/platform/linux/fl_settings_portal_test.cc +FILE: ../../../flutter/shell/platform/linux/fl_socket_accessible.cc +FILE: ../../../flutter/shell/platform/linux/fl_socket_accessible.h FILE: ../../../flutter/shell/platform/linux/fl_standard_message_codec.cc FILE: ../../../flutter/shell/platform/linux/fl_standard_message_codec_test.cc FILE: ../../../flutter/shell/platform/linux/fl_standard_method_codec.cc diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index c28c7b558bfc7..8e1b8fc86e18c 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -132,6 +132,7 @@ source_set("flutter_linux_sources") { "fl_settings.cc", "fl_settings_plugin.cc", "fl_settings_portal.cc", + "fl_socket_accessible.cc", "fl_standard_message_codec.cc", "fl_standard_method_codec.cc", "fl_string_codec.cc", diff --git a/shell/platform/linux/fl_socket_accessible.cc b/shell/platform/linux/fl_socket_accessible.cc new file mode 100644 index 0000000000000..9b2d06c1320f0 --- /dev/null +++ b/shell/platform/linux/fl_socket_accessible.cc @@ -0,0 +1,57 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/linux/fl_socket_accessible.h" + +// This is a copy of GtkSocketAccessible, which requires GTK 3.24.30 + +struct _FlSocketAccessible { + GtkContainerAccessible parent; + AtkObject* accessible_socket; +}; + +G_DEFINE_TYPE(FlSocketAccessible, + fl_socket_accessible, + GTK_TYPE_CONTAINER_ACCESSIBLE) + +static AtkObject* fl_socket_accessible_ref_child(AtkObject* object, int i) { + FlSocketAccessible* self = FL_SOCKET_ACCESSIBLE(object); + return i == 0 ? ATK_OBJECT(g_object_ref(self->accessible_socket)) : nullptr; +} + +static int fl_socket_accessible_get_n_children(AtkObject* object) { + return 1; +} + +static void fl_socket_accessible_finalize(GObject* object) { + FlSocketAccessible* self = FL_SOCKET_ACCESSIBLE(object); + + g_clear_object(&self->accessible_socket); + + G_OBJECT_CLASS(fl_socket_accessible_parent_class)->finalize(object); +} + +static void fl_socket_accessible_initialize(AtkObject* object, gpointer data) { + FlSocketAccessible* self = FL_SOCKET_ACCESSIBLE(object); + + ATK_OBJECT_CLASS(fl_socket_accessible_parent_class)->initialize(object, data); + + self->accessible_socket = atk_socket_new(); +} + +static void fl_socket_accessible_class_init(FlSocketAccessibleClass* klass) { + GObjectClass* object_class = G_OBJECT_CLASS(klass); + object_class->finalize = fl_socket_accessible_finalize; + + AtkObjectClass* atk_class = ATK_OBJECT_CLASS(klass); + atk_class->initialize = fl_socket_accessible_initialize; + atk_class->get_n_children = fl_socket_accessible_get_n_children; + atk_class->ref_child = fl_socket_accessible_ref_child; +} + +static void fl_socket_accessible_init(FlSocketAccessible* self) {} + +void fl_socket_accessible_embed(FlSocketAccessible* self, gchar* id) { + atk_socket_embed(ATK_SOCKET(self->accessible_socket), id); +} diff --git a/shell/platform/linux/fl_socket_accessible.h b/shell/platform/linux/fl_socket_accessible.h new file mode 100644 index 0000000000000..a3eb6ed10d85b --- /dev/null +++ b/shell/platform/linux/fl_socket_accessible.h @@ -0,0 +1,22 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_SOCKET_ACCESSIBLE_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_SOCKET_ACCESSIBLE_H_ + +#include + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlSocketAccessible, + fl_socket_accessible, + FL, + SOCKET_ACCESSIBLE, + GtkContainerAccessible); + +void fl_socket_accessible_embed(FlSocketAccessible* self, gchar* id); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_SOCKET_ACCESSIBLE_H_ diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc index 0d1fb4709f703..8dc057e6fa027 100644 --- a/shell/platform/linux/fl_view.cc +++ b/shell/platform/linux/fl_view.cc @@ -6,8 +6,12 @@ #include "flutter/shell/platform/linux/fl_view_private.h" +#include +#include + #include +#include "flutter/shell/platform/linux/fl_accessible_node.h" #include "flutter/shell/platform/linux/fl_backing_store_provider.h" #include "flutter/shell/platform/linux/fl_engine_private.h" #include "flutter/shell/platform/linux/fl_key_event.h" @@ -19,6 +23,7 @@ #include "flutter/shell/platform/linux/fl_renderer_gdk.h" #include "flutter/shell/platform/linux/fl_scrolling_manager.h" #include "flutter/shell/platform/linux/fl_scrolling_view_delegate.h" +#include "flutter/shell/platform/linux/fl_socket_accessible.h" #include "flutter/shell/platform/linux/fl_text_input_plugin.h" #include "flutter/shell/platform/linux/fl_text_input_view_delegate.h" #include "flutter/shell/platform/linux/fl_view_accessible.h" @@ -64,6 +69,9 @@ struct _FlView { gulong keymap_keys_changed_cb_id; // Signal connection ID for // keymap-keys-changed gulong window_state_cb_id; // Signal connection ID for window-state-changed + + // Accessible tree from Flutter, exposed as an AtkPlug. + FlViewAccessible* view_accessible; }; enum { kPropFlutterProject = 1, kPropLast }; @@ -231,9 +239,7 @@ static void update_semantics_cb(FlEngine* engine, gpointer user_data) { FlView* self = FL_VIEW(user_data); - AtkObject* accessible = gtk_widget_get_accessible(GTK_WIDGET(self)); - fl_view_accessible_handle_update_semantics(FL_VIEW_ACCESSIBLE(accessible), - update); + fl_view_accessible_handle_update_semantics(self->view_accessible, update); } // Invoked by the engine right before the engine is restarted. @@ -586,6 +592,11 @@ static void realize_cb(FlView* self) { } handle_geometry_changed(self); + + self->view_accessible = fl_view_accessible_new(self->engine); + fl_socket_accessible_embed( + FL_SOCKET_ACCESSIBLE(gtk_widget_get_accessible(GTK_WIDGET(self))), + atk_plug_get_id(ATK_PLUG(self->view_accessible))); } static gboolean render_cb(FlView* self, GdkGLContext* context) { @@ -691,6 +702,7 @@ static void fl_view_dispose(GObject* object) { } g_clear_object(&self->mouse_cursor_plugin); g_clear_object(&self->platform_plugin); + g_clear_object(&self->view_accessible); G_OBJECT_CLASS(fl_view_parent_class)->dispose(object); } @@ -733,7 +745,7 @@ static void fl_view_class_init(FlViewClass* klass) { G_PARAM_STATIC_STRINGS))); gtk_widget_class_set_accessible_type(GTK_WIDGET_CLASS(klass), - fl_view_accessible_get_type()); + fl_socket_accessible_get_type()); } static void fl_view_init(FlView* self) { diff --git a/shell/platform/linux/fl_view_accessible.cc b/shell/platform/linux/fl_view_accessible.cc index 7ddefe0b0bbfb..77ba12aec1282 100644 --- a/shell/platform/linux/fl_view_accessible.cc +++ b/shell/platform/linux/fl_view_accessible.cc @@ -8,45 +8,31 @@ #include "flutter/shell/platform/linux/public/flutter_linux/fl_value.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h" +static constexpr int32_t kRootSemanticsNodeId = 0; + struct _FlViewAccessible { - GtkContainerAccessible parent_instance; + AtkPlug parent_instance; FlEngine* engine; // Semantics nodes keyed by ID GHashTable* semantics_nodes_by_id; + + // Flag to track when root node is created. + gboolean root_node_created; }; enum { kProp0, kPropEngine, kPropLast }; -G_DEFINE_TYPE(FlViewAccessible, - fl_view_accessible, - GTK_TYPE_CONTAINER_ACCESSIBLE) - -static void init_engine(FlViewAccessible* self, FlEngine* engine) { - g_assert(self->engine == nullptr); - self->engine = engine; - g_object_add_weak_pointer(G_OBJECT(self), - reinterpret_cast(&self->engine)); -} - -static FlEngine* get_engine(FlViewAccessible* self) { - if (self->engine == nullptr) { - FlView* view = FL_VIEW(gtk_accessible_get_widget(GTK_ACCESSIBLE(self))); - init_engine(self, fl_view_get_engine(view)); - } - return self->engine; -} +G_DEFINE_TYPE(FlViewAccessible, fl_view_accessible, ATK_TYPE_PLUG) static FlAccessibleNode* create_node(FlViewAccessible* self, FlutterSemanticsNode2* semantics) { - FlEngine* engine = get_engine(self); - if (semantics->flags & kFlutterSemanticsFlagIsTextField) { - return fl_accessible_text_field_new(engine, semantics->id); + return fl_accessible_text_field_new(self->engine, semantics->id); } - return fl_accessible_node_new(engine, semantics->id); + return fl_accessible_node_new(self->engine, semantics->id); } static FlAccessibleNode* lookup_node(FlViewAccessible* self, int32_t id) { @@ -64,14 +50,19 @@ static FlAccessibleNode* get_node(FlViewAccessible* self, } node = create_node(self, semantics); - if (semantics->id == 0) { + if (semantics->id == kRootSemanticsNodeId) { fl_accessible_node_set_parent(node, ATK_OBJECT(self), 0); - g_signal_emit_by_name(self, "children-changed::add", 0, node, nullptr); } g_hash_table_insert(self->semantics_nodes_by_id, GINT_TO_POINTER(semantics->id), reinterpret_cast(node)); + // Update when root node is created. + if (!self->root_node_created && semantics->id == kRootSemanticsNodeId) { + g_signal_emit_by_name(self, "children-changed::add", 0, node, nullptr); + self->root_node_created = true; + } + return node; } @@ -104,20 +95,11 @@ static AtkRole fl_view_accessible_get_role(AtkObject* accessible) { return ATK_ROLE_PANEL; } -// Implements GObject::set_property -static void fl_view_accessible_set_property(GObject* object, - guint prop_id, - const GValue* value, - GParamSpec* pspec) { - FlViewAccessible* self = FL_VIEW_ACCESSIBLE(object); - switch (prop_id) { - case kPropEngine: - init_engine(self, FL_ENGINE(g_value_get_object(value))); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); - break; - } +// Implements AtkObject::ref_state_set +static AtkStateSet* fl_view_accessible_ref_state_set(AtkObject* accessible) { + FlViewAccessible* self = FL_VIEW_ACCESSIBLE(accessible); + FlAccessibleNode* node = lookup_node(self, 0); + return node != nullptr ? atk_object_ref_state_set(ATK_OBJECT(node)) : nullptr; } static void fl_view_accessible_dispose(GObject* object) { @@ -138,16 +120,9 @@ static void fl_view_accessible_class_init(FlViewAccessibleClass* klass) { ATK_OBJECT_CLASS(klass)->get_n_children = fl_view_accessible_get_n_children; ATK_OBJECT_CLASS(klass)->ref_child = fl_view_accessible_ref_child; ATK_OBJECT_CLASS(klass)->get_role = fl_view_accessible_get_role; + ATK_OBJECT_CLASS(klass)->ref_state_set = fl_view_accessible_ref_state_set; G_OBJECT_CLASS(klass)->dispose = fl_view_accessible_dispose; - G_OBJECT_CLASS(klass)->set_property = fl_view_accessible_set_property; - - g_object_class_install_property( - G_OBJECT_CLASS(klass), kPropEngine, - g_param_spec_object( - "engine", "engine", "Flutter engine", fl_engine_get_type(), - static_cast(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS))); } static void fl_view_accessible_init(FlViewAccessible* self) { @@ -155,13 +130,21 @@ static void fl_view_accessible_init(FlViewAccessible* self) { g_direct_hash, g_direct_equal, nullptr, g_object_unref); } +FlViewAccessible* fl_view_accessible_new(FlEngine* engine) { + FlViewAccessible* self = + FL_VIEW_ACCESSIBLE(g_object_new(fl_view_accessible_get_type(), nullptr)); + self->engine = engine; + g_object_add_weak_pointer(G_OBJECT(self), + reinterpret_cast(&self->engine)); + return self; +} + void fl_view_accessible_handle_update_semantics( FlViewAccessible* self, const FlutterSemanticsUpdate2* update) { g_autoptr(GHashTable) pending_children = g_hash_table_new_full(g_direct_hash, g_direct_equal, nullptr, reinterpret_cast(fl_value_unref)); - for (size_t i = 0; i < update->node_count; i++) { FlutterSemanticsNode2* node = update->nodes[i]; FlAccessibleNode* atk_node = get_node(self, node); diff --git a/shell/platform/linux/fl_view_accessible.h b/shell/platform/linux/fl_view_accessible.h index 0c45d7b2b85a6..220338d274bb1 100644 --- a/shell/platform/linux/fl_view_accessible.h +++ b/shell/platform/linux/fl_view_accessible.h @@ -9,17 +9,24 @@ #error "Only can be included directly." #endif -#include +#include #include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h" G_BEGIN_DECLS +// ATK g_autoptr macros weren't added until 2.37. Add them manually. +// https://gitlab.gnome.org/GNOME/atk/-/issues/10 +#if !ATK_CHECK_VERSION(2, 37, 0) +G_DEFINE_AUTOPTR_CLEANUP_FUNC(AtkPlug, g_object_unref) +#endif + G_DECLARE_FINAL_TYPE(FlViewAccessible, fl_view_accessible, FL, VIEW_ACCESSIBLE, - GtkContainerAccessible) + AtkPlug) /** * FlViewAccessible: @@ -28,6 +35,16 @@ G_DECLARE_FINAL_TYPE(FlViewAccessible, * #FlView. */ +/** + * fl_view_accessible_new: + * + * Creates a new accessibility object that exposes Flutter accessibility + * information to ATK. + * + * Returns: a new #FlViewAccessible. + */ +FlViewAccessible* fl_view_accessible_new(FlEngine* engine); + /** * fl_view_accessible_handle_update_semantics: * @accessible: an #FlViewAccessible. diff --git a/shell/platform/linux/fl_view_accessible_test.cc b/shell/platform/linux/fl_view_accessible_test.cc index be4495e7ebf2c..0b6d17ab49a21 100644 --- a/shell/platform/linux/fl_view_accessible_test.cc +++ b/shell/platform/linux/fl_view_accessible_test.cc @@ -12,8 +12,7 @@ TEST(FlViewAccessibleTest, BuildTree) { g_autoptr(FlEngine) engine = make_mock_engine(); - g_autoptr(FlViewAccessible) accessible = FL_VIEW_ACCESSIBLE( - g_object_new(fl_view_accessible_get_type(), "engine", engine, nullptr)); + g_autoptr(FlViewAccessible) accessible = fl_view_accessible_new(engine); int32_t children[] = {111, 222}; FlutterSemanticsNode2 root_node = { @@ -49,8 +48,7 @@ TEST(FlViewAccessibleTest, BuildTree) { TEST(FlViewAccessibleTest, AddRemoveChildren) { g_autoptr(FlEngine) engine = make_mock_engine(); - g_autoptr(FlViewAccessible) accessible = FL_VIEW_ACCESSIBLE( - g_object_new(fl_view_accessible_get_type(), "engine", engine, nullptr)); + g_autoptr(FlViewAccessible) accessible = fl_view_accessible_new(engine); FlutterSemanticsNode2 root_node = { .id = 0,