/* gtkatspicontext.c: AT-SPI GtkATContext implementation * * Copyright 2020 GNOME Foundation * * SPDX-License-Identifier: LGPL-2.1-or-later * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, see . */ #include "config.h" #include "gtkatspicontextprivate.h" #include "gtkaccessibleprivate.h" #include "gtkatspiactionprivate.h" #include "gtkatspieditabletextprivate.h" #include "gtkatspiprivate.h" #include "gtkatspirootprivate.h" #include "gtkatspiselectionprivate.h" #include "gtkatspitextprivate.h" #include "gtkatspiutilsprivate.h" #include "gtkatspivalueprivate.h" #include "gtkatspicomponentprivate.h" #include "a11y/atspi/atspi-accessible.h" #include "a11y/atspi/atspi-action.h" #include "a11y/atspi/atspi-editabletext.h" #include "a11y/atspi/atspi-text.h" #include "a11y/atspi/atspi-value.h" #include "a11y/atspi/atspi-selection.h" #include "a11y/atspi/atspi-component.h" #include "gtkdebug.h" #include "gtkeditable.h" #include "gtkentryprivate.h" #include "gtkroot.h" #include "gtkstack.h" #include "gtktextview.h" #include "gtktypebuiltins.h" #include "gtkwindow.h" #include #include #if defined(GDK_WINDOWING_WAYLAND) # include #endif #if defined(GDK_WINDOWING_X11) # include # include #endif /* We create a GtkAtSpiContext object for (almost) every widget. * * Each context implements a number of Atspi interfaces on a D-Bus * object. The context objects are connected into a tree by the * Parent property and GetChildAtIndex method of the Accessible * interface. * * The tree is an almost perfect mirror image of the widget tree, * with a few notable exceptions: * * - We don't create contexts for the GtkText widgets inside * entry wrappers, since the Text functionality is represented * on the entry contexts. * * - We insert non-widget backed context objects for each page * of a stack. The main purpose of these extra context is to * hold the TAB_PANEL role and be the target of the CONTROLS * relation with their corresponding tabs (in the stack * switcher or notebook). */ struct _GtkAtSpiContext { GtkATContext parent_instance; /* The root object, used as a entry point */ GtkAtSpiRoot *root; /* The object path of the ATContext on the bus */ char *context_path; /* Just a pointer; the connection is owned by the GtkAtSpiRoot * associated to the GtkATContext */ GDBusConnection *connection; /* Accerciser refuses to work unless we implement a GetInterface * call that returns a list of all implemented interfaces. We * collect the answer here. */ GVariant *interfaces; guint registration_ids[20]; guint n_registered_objects; }; G_DEFINE_TYPE (GtkAtSpiContext, gtk_at_spi_context, GTK_TYPE_AT_CONTEXT) /* {{{ State handling */ static void set_atspi_state (guint64 *states, AtspiStateType state) { *states |= (G_GUINT64_CONSTANT (1) << state); } static void unset_atspi_state (guint64 *states, AtspiStateType state) { *states &= ~(G_GUINT64_CONSTANT (1) << state); } static void collect_states (GtkAtSpiContext *self, GVariantBuilder *builder) { GtkATContext *ctx = GTK_AT_CONTEXT (self); GtkAccessibleValue *value; GtkAccessible *accessible; guint64 states = 0; accessible = gtk_at_context_get_accessible (ctx); set_atspi_state (&states, ATSPI_STATE_VISIBLE); if (ctx->accessible_role == GTK_ACCESSIBLE_ROLE_WINDOW) { set_atspi_state (&states, ATSPI_STATE_SHOWING); if (gtk_accessible_get_platform_state (accessible, GTK_ACCESSIBLE_PLATFORM_STATE_ACTIVE)) set_atspi_state (&states, ATSPI_STATE_ACTIVE); } if (ctx->accessible_role == GTK_ACCESSIBLE_ROLE_TEXT_BOX || ctx->accessible_role == GTK_ACCESSIBLE_ROLE_SEARCH_BOX || ctx->accessible_role == GTK_ACCESSIBLE_ROLE_SPIN_BUTTON) set_atspi_state (&states, ATSPI_STATE_EDITABLE); if (gtk_at_context_has_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_READ_ONLY)) { value = gtk_at_context_get_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_READ_ONLY); if (gtk_boolean_accessible_value_get (value)) { set_atspi_state (&states, ATSPI_STATE_READ_ONLY); unset_atspi_state (&states, ATSPI_STATE_EDITABLE); } } if (gtk_accessible_get_platform_state (accessible, GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE)) set_atspi_state (&states, ATSPI_STATE_FOCUSABLE); if (gtk_accessible_get_platform_state (accessible, GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED)) set_atspi_state (&states, ATSPI_STATE_FOCUSED); if (gtk_at_context_has_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_ORIENTATION)) { value = gtk_at_context_get_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_ORIENTATION); if (gtk_orientation_accessible_value_get (value) == GTK_ORIENTATION_HORIZONTAL) set_atspi_state (&states, ATSPI_STATE_HORIZONTAL); else set_atspi_state (&states, ATSPI_STATE_VERTICAL); } if (gtk_at_context_has_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_MODAL)) { value = gtk_at_context_get_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_MODAL); if (gtk_boolean_accessible_value_get (value)) set_atspi_state (&states, ATSPI_STATE_MODAL); } if (gtk_at_context_has_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_MULTI_LINE)) { value = gtk_at_context_get_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_MULTI_LINE); if (gtk_boolean_accessible_value_get (value)) set_atspi_state (&states, ATSPI_STATE_MULTI_LINE); } if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_BUSY)) { value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_BUSY); if (gtk_boolean_accessible_value_get (value)) set_atspi_state (&states, ATSPI_STATE_BUSY); } if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_CHECKED)) { value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_CHECKED); switch (gtk_tristate_accessible_value_get (value)) { case GTK_ACCESSIBLE_TRISTATE_TRUE: set_atspi_state (&states, ATSPI_STATE_CHECKED); break; case GTK_ACCESSIBLE_TRISTATE_MIXED: set_atspi_state (&states, ATSPI_STATE_INDETERMINATE); break; case GTK_ACCESSIBLE_TRISTATE_FALSE: default: break; } } if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_DISABLED)) { value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_DISABLED); if (!gtk_boolean_accessible_value_get (value)) set_atspi_state (&states, ATSPI_STATE_SENSITIVE); } else set_atspi_state (&states, ATSPI_STATE_SENSITIVE); if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_EXPANDED)) { value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_EXPANDED); if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN) { set_atspi_state (&states, ATSPI_STATE_EXPANDABLE); if (gtk_boolean_accessible_value_get (value)) set_atspi_state (&states, ATSPI_STATE_EXPANDED); } } if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_INVALID)) { value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_INVALID); switch (gtk_invalid_accessible_value_get (value)) { case GTK_ACCESSIBLE_INVALID_TRUE: case GTK_ACCESSIBLE_INVALID_GRAMMAR: case GTK_ACCESSIBLE_INVALID_SPELLING: set_atspi_state (&states, ATSPI_STATE_INVALID); break; case GTK_ACCESSIBLE_INVALID_FALSE: default: break; } } if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_PRESSED)) { value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_PRESSED); switch (gtk_tristate_accessible_value_get (value)) { case GTK_ACCESSIBLE_TRISTATE_TRUE: set_atspi_state (&states, ATSPI_STATE_PRESSED); break; case GTK_ACCESSIBLE_TRISTATE_MIXED: set_atspi_state (&states, ATSPI_STATE_INDETERMINATE); break; case GTK_ACCESSIBLE_TRISTATE_FALSE: default: break; } } if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_SELECTED)) { value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_SELECTED); if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN) { set_atspi_state (&states, ATSPI_STATE_SELECTABLE); if (gtk_boolean_accessible_value_get (value)) set_atspi_state (&states, ATSPI_STATE_SELECTED); } } g_variant_builder_add (builder, "u", (guint32) (states & 0xffffffff)); g_variant_builder_add (builder, "u", (guint32) (states >> 32)); } /* }}} */ /* {{{ Relation handling */ static void collect_relations (GtkAtSpiContext *self, GVariantBuilder *builder) { GtkATContext *ctx = GTK_AT_CONTEXT (self); struct { GtkAccessibleRelation r; AtspiRelationType s; } map[] = { { GTK_ACCESSIBLE_RELATION_LABELLED_BY, ATSPI_RELATION_LABELLED_BY }, { GTK_ACCESSIBLE_RELATION_CONTROLS, ATSPI_RELATION_CONTROLLER_FOR }, { GTK_ACCESSIBLE_RELATION_DESCRIBED_BY, ATSPI_RELATION_DESCRIBED_BY }, { GTK_ACCESSIBLE_RELATION_FLOW_TO, ATSPI_RELATION_FLOWS_TO}, }; GtkAccessibleValue *value; GList *list, *l; GtkATContext *target_ctx; int i; for (i = 0; i < G_N_ELEMENTS (map); i++) { if (!gtk_at_context_has_accessible_relation (ctx, map[i].r)) continue; GVariantBuilder b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(so)")); value = gtk_at_context_get_accessible_relation (ctx, map[i].r); list = gtk_reference_list_accessible_value_get (value); for (l = list; l; l = l->next) { target_ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (l->data)); /* Realize the ATContext of the target, so we can ask for its ref */ gtk_at_context_realize (target_ctx); g_variant_builder_add (&b, "@(so)", gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (target_ctx))); } g_variant_builder_add (builder, "(ua(so))", map[i].s, &b); } } /* }}} */ /* {{{ Accessible implementation */ static int get_index_in_parent (GtkWidget *widget) { GtkWidget *parent = gtk_widget_get_parent (widget); GtkWidget *child; int idx; if (parent == NULL) return -1; idx = 0; for (child = gtk_widget_get_first_child (parent); child; child = gtk_widget_get_next_sibling (child)) { if (child == widget) return idx; if (!gtk_accessible_should_present (GTK_ACCESSIBLE (child))) continue; idx++; } return -1; } static int get_index_in_toplevels (GtkWidget *widget) { GListModel *toplevels = gtk_window_get_toplevels (); guint n_toplevels = g_list_model_get_n_items (toplevels); GtkWidget *window; int idx; idx = 0; for (guint i = 0; i < n_toplevels; i++) { window = g_list_model_get_item (toplevels, i); g_object_unref (window); if (window == widget) return idx; if (!gtk_accessible_should_present (GTK_ACCESSIBLE (window))) continue; idx += 1; } return -1; } static GVariant * get_parent_context_ref (GtkAccessible *accessible) { GVariant *res = NULL; if (GTK_IS_WIDGET (accessible)) { GtkWidget *widget = GTK_WIDGET (accessible); GtkWidget *parent = gtk_widget_get_parent (widget); if (parent == NULL) { GtkATContext *context = gtk_accessible_get_at_context (accessible); GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (context); res = gtk_at_spi_root_to_ref (self->root); } else if (GTK_IS_STACK (parent)) { GtkStackPage *page = gtk_stack_get_page (GTK_STACK (parent), widget); GtkATContext *parent_context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (page)); if (parent_context != NULL) { gtk_at_context_realize (parent_context); res = gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (parent_context)); } } else { GtkATContext *parent_context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (parent)); if (parent_context != NULL) { /* XXX: This realize() is needed otherwise opening a GtkPopover will * emit a warning when getting the context's reference */ gtk_at_context_realize (parent_context); res = gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (parent_context)); } } } else if (GTK_IS_STACK_PAGE (accessible)) { GtkWidget *parent = gtk_widget_get_parent (gtk_stack_page_get_child (GTK_STACK_PAGE (accessible))); GtkATContext *parent_context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (parent)); if (parent_context != NULL) res = gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (parent_context)); } if (res == NULL) res = gtk_at_spi_null_ref (); return res; } static void handle_accessible_method (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { GtkAtSpiContext *self = user_data; GTK_NOTE (A11Y, g_message ("handling %s on %s", method_name, object_path)); if (g_strcmp0 (method_name, "GetRole") == 0) { guint atspi_role = gtk_atspi_role_for_context (GTK_AT_CONTEXT (self)); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(u)", atspi_role)); } else if (g_strcmp0 (method_name, "GetRoleName") == 0) { GtkAccessibleRole role = gtk_at_context_get_accessible_role (GTK_AT_CONTEXT (self)); const char *name = gtk_accessible_role_to_name (role, NULL); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", name)); } else if (g_strcmp0 (method_name, "GetLocalizedRoleName") == 0) { GtkAccessibleRole role = gtk_at_context_get_accessible_role (GTK_AT_CONTEXT (self)); const char *name = gtk_accessible_role_to_name (role, GETTEXT_PACKAGE); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", name)); } else if (g_strcmp0 (method_name, "GetState") == 0) { GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(au)")); g_variant_builder_open (&builder, G_VARIANT_TYPE ("au")); collect_states (self, &builder); g_variant_builder_close (&builder); g_dbus_method_invocation_return_value (invocation, g_variant_builder_end (&builder)); } else if (g_strcmp0 (method_name, "GetAttributes") == 0) { GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(a{ss})")); g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{ss}")); g_variant_builder_add (&builder, "{ss}", "toolkit", "GTK"); if (gtk_at_context_has_accessible_property (GTK_AT_CONTEXT (self), GTK_ACCESSIBLE_PROPERTY_PLACEHOLDER)) { GtkAccessibleValue *value; value = gtk_at_context_get_accessible_property (GTK_AT_CONTEXT (self), GTK_ACCESSIBLE_PROPERTY_PLACEHOLDER); g_variant_builder_add (&builder, "{ss}", "placeholder-text", gtk_string_accessible_value_get (value)); } g_variant_builder_close (&builder); g_dbus_method_invocation_return_value (invocation, g_variant_builder_end (&builder)); } else if (g_strcmp0 (method_name, "GetApplication") == 0) { g_dbus_method_invocation_return_value (invocation, g_variant_new ("(@(so))", gtk_at_spi_root_to_ref (self->root))); } else if (g_strcmp0 (method_name, "GetChildAtIndex") == 0) { GtkATContext *context = NULL; GtkAccessible *accessible; int idx, real_idx = 0; g_variant_get (parameters, "(i)", &idx); accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self)); if (GTK_IS_STACK_PAGE (accessible)) { if (idx == 0) { GtkWidget *child; child = gtk_stack_page_get_child (GTK_STACK_PAGE (accessible)); if (gtk_accessible_should_present (GTK_ACCESSIBLE (child))) context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child)); } } else if (GTK_IS_WIDGET (accessible)) { GtkWidget *widget = GTK_WIDGET (accessible); GtkWidget *child; real_idx = 0; for (child = gtk_widget_get_first_child (widget); child; child = gtk_widget_get_next_sibling (child)) { if (!gtk_accessible_should_present (GTK_ACCESSIBLE (child))) continue; if (real_idx == idx) break; real_idx += 1; } if (child) { if (GTK_IS_STACK (accessible)) context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (gtk_stack_get_page (GTK_STACK (accessible), child))); else context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child)); } } if (context == NULL) { g_dbus_method_invocation_return_error (invocation, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "No child with index %d", idx); return; } /* Realize the child ATContext in order to get its ref */ gtk_at_context_realize (context); GVariant *ref = gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (context)); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(@(so))", ref)); } else if (g_strcmp0 (method_name, "GetChildren") == 0) { GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(so)")); GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self)); if (GTK_IS_WIDGET (accessible)) { GtkWidget *widget = GTK_WIDGET (accessible); GtkWidget *child; for (child = gtk_widget_get_first_child (widget); child; child = gtk_widget_get_next_sibling (child)) { if (!gtk_accessible_should_present (GTK_ACCESSIBLE (child))) continue; GtkATContext *context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child)); /* Realize the child ATContext in order to get its ref */ gtk_at_context_realize (context); GVariant *ref = gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (context)); if (ref != NULL) g_variant_builder_add (&builder, "@(so)", ref); } } else if (GTK_IS_STACK_PAGE (accessible)) { GtkWidget *child = gtk_stack_page_get_child (GTK_STACK_PAGE (accessible)); if (gtk_accessible_should_present (GTK_ACCESSIBLE (child))) { GtkATContext *context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child)); /* Realize the child ATContext in order to get its ref */ gtk_at_context_realize (context); GVariant *ref = gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (context)); if (ref != NULL) g_variant_builder_add (&builder, "@(so)", ref); } } g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a(so))", &builder)); } else if (g_strcmp0 (method_name, "GetIndexInParent") == 0) { int idx = gtk_at_spi_context_get_index_in_parent (self); if (idx == -1) g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Not found"); else g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", idx)); } else if (g_strcmp0 (method_name, "GetRelationSet") == 0) { GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(ua(so))")); collect_relations (self, &builder); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a(ua(so)))", &builder)); } else if (g_strcmp0 (method_name, "GetInterfaces") == 0) { g_dbus_method_invocation_return_value (invocation, g_variant_new ("(@as)", self->interfaces)); } } static GVariant * handle_accessible_get_property (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GError **error, gpointer user_data) { GtkAtSpiContext *self = user_data; GVariant *res = NULL; GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self)); GTK_NOTE (A11Y, g_message ("handling GetProperty %s on %s", property_name, object_path)); if (g_strcmp0 (property_name, "Name") == 0) { char *label = gtk_at_context_get_name (GTK_AT_CONTEXT (self)); res = g_variant_new_string (label ? label : ""); g_free (label); } else if (g_strcmp0 (property_name, "Description") == 0) { char *label = gtk_at_context_get_description (GTK_AT_CONTEXT (self)); res = g_variant_new_string (label ? label : ""); g_free (label); } else if (g_strcmp0 (property_name, "Locale") == 0) res = g_variant_new_string (setlocale (LC_MESSAGES, NULL)); else if (g_strcmp0 (property_name, "AccessibleId") == 0) res = g_variant_new_string (""); else if (g_strcmp0 (property_name, "Parent") == 0) res = get_parent_context_ref (accessible); else if (g_strcmp0 (property_name, "ChildCount") == 0) res = g_variant_new_int32 (gtk_at_spi_context_get_child_count (self)); else g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Unknown property '%s'", property_name); return res; } static const GDBusInterfaceVTable accessible_vtable = { handle_accessible_method, handle_accessible_get_property, NULL, }; /* }}} */ /* {{{ Change notification */ static void emit_text_changed (GtkAtSpiContext *self, const char *kind, int start, int end, const char *text) { if (self->connection == NULL) return; g_dbus_connection_emit_signal (self->connection, NULL, self->context_path, "org.a11y.atspi.Event.Object", "TextChanged", g_variant_new ("(siiva{sv})", kind, start, end, g_variant_new_string (text), NULL), NULL); } static void emit_text_selection_changed (GtkAtSpiContext *self, const char *kind, int cursor_position) { if (self->connection == NULL) return; if (strcmp (kind, "text-caret-moved") == 0) g_dbus_connection_emit_signal (self->connection, NULL, self->context_path, "org.a11y.atspi.Event.Object", "TextCaretMoved", g_variant_new ("(siiva{sv})", "", cursor_position, 0, g_variant_new_string (""), NULL), NULL); else g_dbus_connection_emit_signal (self->connection, NULL, self->context_path, "org.a11y.atspi.Event.Object", "TextSelectionChanged", g_variant_new ("(siiva{sv})", "", 0, 0, g_variant_new_string (""), NULL), NULL); } static void emit_selection_changed (GtkAtSpiContext *self, const char *kind) { if (self->connection == NULL) return; g_dbus_connection_emit_signal (self->connection, NULL, self->context_path, "org.a11y.atspi.Event.Object", "SelectionChanged", g_variant_new ("(siiva{sv})", "", 0, 0, g_variant_new_string (""), NULL), NULL); } static void emit_state_changed (GtkAtSpiContext *self, const char *name, gboolean enabled) { if (self->connection == NULL) return; g_dbus_connection_emit_signal (self->connection, NULL, self->context_path, "org.a11y.atspi.Event.Object", "StateChanged", g_variant_new ("(siiva{sv})", name, enabled, 0, g_variant_new_string ("0"), NULL), NULL); } static void emit_defunct (GtkAtSpiContext *self) { if (self->connection == NULL) return; g_dbus_connection_emit_signal (self->connection, NULL, self->context_path, "org.a11y.atspi.Event.Object", "StateChanged", g_variant_new ("(siiva{sv})", "defunct", TRUE, 0, g_variant_new_string ("0"), NULL), NULL); } static void emit_property_changed (GtkAtSpiContext *self, const char *name, GVariant *value) { if (self->connection == NULL) return; g_dbus_connection_emit_signal (self->connection, NULL, self->context_path, "org.a11y.atspi.Event.Object", "PropertyChange", g_variant_new ("(siiva{sv})", name, 0, 0, value, NULL), NULL); } static void emit_bounds_changed (GtkAtSpiContext *self, int x, int y, int width, int height) { if (self->connection == NULL) return; g_dbus_connection_emit_signal (self->connection, NULL, self->context_path, "org.a11y.atspi.Event.Object", "BoundsChanged", g_variant_new ("(siiva{sv})", "", 0, 0, g_variant_new ("(iiii)", x, y, width, height), NULL), NULL); } static void emit_children_changed (GtkAtSpiContext *self, GtkAtSpiContext *child_context, int idx, GtkAccessibleChildState state) { /* If we don't have a connection on either contexts, we cannot emit a signal */ if (self->connection == NULL || child_context->connection == NULL) return; GVariant *context_ref = gtk_at_spi_context_to_ref (self); GVariant *child_ref = gtk_at_spi_context_to_ref (child_context); gtk_at_spi_emit_children_changed (self->connection, self->context_path, state, idx, child_ref, context_ref); } static void emit_focus (GtkAtSpiContext *self, gboolean focus_in) { if (self->connection == NULL) return; if (focus_in) g_dbus_connection_emit_signal (self->connection, NULL, self->context_path, "org.a11y.atspi.Event.Focus", "Focus", g_variant_new ("(siiva{sv})", "", 0, 0, g_variant_new_string ("0"), NULL), NULL); } static void emit_window_event (GtkAtSpiContext *self, const char *event_type) { if (self->connection == NULL) return; g_dbus_connection_emit_signal (self->connection, NULL, self->context_path, "org.a11y.atspi.Event.Window", event_type, g_variant_new ("(siiva{sv})", "", 0, 0, g_variant_new_string("0"), NULL), NULL); } static void gtk_at_spi_context_state_change (GtkATContext *ctx, GtkAccessibleStateChange changed_states, GtkAccessiblePropertyChange changed_properties, GtkAccessibleRelationChange changed_relations, GtkAccessibleAttributeSet *states, GtkAccessibleAttributeSet *properties, GtkAccessibleAttributeSet *relations) { GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (ctx); GtkAccessible *accessible = gtk_at_context_get_accessible (ctx); GtkAccessibleValue *value; if (GTK_IS_WIDGET (accessible) && !gtk_widget_get_realized (GTK_WIDGET (accessible))) return; if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_HIDDEN) { GtkWidget *parent; GtkATContext *context; GtkAccessibleChildChange change; value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_HIDDEN); if (gtk_boolean_accessible_value_get (value)) change = GTK_ACCESSIBLE_CHILD_CHANGE_REMOVED; else change = GTK_ACCESSIBLE_CHILD_CHANGE_ADDED; if (GTK_IS_ROOT (accessible)) { gtk_at_spi_root_child_changed (self->root, change, accessible); emit_state_changed (self, "showing", gtk_boolean_accessible_value_get (value)); } else { if (GTK_IS_WIDGET (accessible)) parent = gtk_widget_get_parent (GTK_WIDGET (accessible)); else if (GTK_IS_STACK_PAGE (accessible)) parent = gtk_widget_get_parent (gtk_stack_page_get_child (GTK_STACK_PAGE (accessible))); else g_assert_not_reached (); context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (parent)); gtk_at_context_child_changed (context, change, accessible); } } if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_BUSY) { value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_BUSY); emit_state_changed (self, "busy", gtk_boolean_accessible_value_get (value)); } if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_CHECKED) { value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_CHECKED); switch (gtk_tristate_accessible_value_get (value)) { case GTK_ACCESSIBLE_TRISTATE_TRUE: emit_state_changed (self, "checked", TRUE); emit_state_changed (self, "indeterminate", FALSE); break; case GTK_ACCESSIBLE_TRISTATE_MIXED: emit_state_changed (self, "checked", FALSE); emit_state_changed (self, "indeterminate", TRUE); break; case GTK_ACCESSIBLE_TRISTATE_FALSE: emit_state_changed (self, "checked", FALSE); emit_state_changed (self, "indeterminate", FALSE); break; default: break; } } if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_DISABLED) { value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_DISABLED); emit_state_changed (self, "sensitive", !gtk_boolean_accessible_value_get (value)); } if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_EXPANDED) { value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_EXPANDED); if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN) { emit_state_changed (self, "expandable", TRUE); emit_state_changed (self, "expanded",gtk_boolean_accessible_value_get (value)); } else emit_state_changed (self, "expandable", FALSE); } if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_INVALID) { value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_INVALID); switch (gtk_invalid_accessible_value_get (value)) { case GTK_ACCESSIBLE_INVALID_TRUE: case GTK_ACCESSIBLE_INVALID_GRAMMAR: case GTK_ACCESSIBLE_INVALID_SPELLING: emit_state_changed (self, "invalid", TRUE); break; case GTK_ACCESSIBLE_INVALID_FALSE: emit_state_changed (self, "invalid", FALSE); break; default: break; } } if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_PRESSED) { value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_PRESSED); switch (gtk_tristate_accessible_value_get (value)) { case GTK_ACCESSIBLE_TRISTATE_TRUE: emit_state_changed (self, "pressed", TRUE); emit_state_changed (self, "indeterminate", FALSE); break; case GTK_ACCESSIBLE_TRISTATE_MIXED: emit_state_changed (self, "pressed", FALSE); emit_state_changed (self, "indeterminate", TRUE); break; case GTK_ACCESSIBLE_TRISTATE_FALSE: emit_state_changed (self, "pressed", FALSE); emit_state_changed (self, "indeterminate", FALSE); break; default: break; } } if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_SELECTED) { value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_SELECTED); if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN) { emit_state_changed (self, "selectable", TRUE); emit_state_changed (self, "selected",gtk_boolean_accessible_value_get (value)); } else emit_state_changed (self, "selectable", FALSE); } if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_READ_ONLY) { gboolean readonly; value = gtk_accessible_attribute_set_get_value (properties, GTK_ACCESSIBLE_PROPERTY_READ_ONLY); readonly = gtk_boolean_accessible_value_get (value); emit_state_changed (self, "read-only", readonly); if (ctx->accessible_role == GTK_ACCESSIBLE_ROLE_TEXT_BOX) emit_state_changed (self, "editable", !readonly); } if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_ORIENTATION) { value = gtk_accessible_attribute_set_get_value (properties, GTK_ACCESSIBLE_PROPERTY_ORIENTATION); if (gtk_orientation_accessible_value_get (value) == GTK_ORIENTATION_HORIZONTAL) { emit_state_changed (self, "horizontal", TRUE); emit_state_changed (self, "vertical", FALSE); } else { emit_state_changed (self, "horizontal", FALSE); emit_state_changed (self, "vertical", TRUE); } } if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_MODAL) { value = gtk_accessible_attribute_set_get_value (properties, GTK_ACCESSIBLE_PROPERTY_MODAL); emit_state_changed (self, "modal", gtk_boolean_accessible_value_get (value)); } if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_MULTI_LINE) { value = gtk_accessible_attribute_set_get_value (properties, GTK_ACCESSIBLE_PROPERTY_MULTI_LINE); emit_state_changed (self, "multi-line", gtk_boolean_accessible_value_get (value)); } if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_LABEL) { char *label = gtk_at_context_get_name (GTK_AT_CONTEXT (self)); GVariant *v = g_variant_new_take_string (label); emit_property_changed (self, "accessible-name", v); } if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_DESCRIPTION) { char *label = gtk_at_context_get_description (GTK_AT_CONTEXT (self)); GVariant *v = g_variant_new_take_string (label); emit_property_changed (self, "accessible-description", v); } } static void gtk_at_spi_context_platform_change (GtkATContext *ctx, GtkAccessiblePlatformChange changed_platform) { GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (ctx); GtkAccessible *accessible = gtk_at_context_get_accessible (ctx); GtkWidget *widget; if (!GTK_IS_WIDGET (accessible)) return; widget = GTK_WIDGET (accessible); if (!gtk_widget_get_realized (widget)) return; if (changed_platform & GTK_ACCESSIBLE_PLATFORM_CHANGE_FOCUSABLE) { gboolean state = gtk_accessible_get_platform_state (GTK_ACCESSIBLE (widget), GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE); emit_state_changed (self, "focusable", state); } if (changed_platform & GTK_ACCESSIBLE_PLATFORM_CHANGE_FOCUSED) { gboolean state = gtk_accessible_get_platform_state (GTK_ACCESSIBLE (widget), GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED); emit_state_changed (self, "focused", state); emit_focus (self, state); } if (changed_platform & GTK_ACCESSIBLE_PLATFORM_CHANGE_ACTIVE) { gboolean state = gtk_accessible_get_platform_state (GTK_ACCESSIBLE (widget), GTK_ACCESSIBLE_PLATFORM_STATE_ACTIVE); emit_state_changed (self, "active", state); /* Orca tracks the window:activate and window:deactivate events on top * levels to decide whether to track other AT-SPI events */ if (gtk_accessible_get_accessible_role (accessible) == GTK_ACCESSIBLE_ROLE_WINDOW) { if (state) emit_window_event (self, "activate"); else emit_window_event (self, "deactivate"); } } } static void gtk_at_spi_context_bounds_change (GtkATContext *ctx) { GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (ctx); GtkAccessible *accessible = gtk_at_context_get_accessible (ctx); GtkWidget *widget; GtkWidget *parent; double x, y; int width, height; if (!GTK_IS_WIDGET (accessible)) return; widget = GTK_WIDGET (accessible); if (!gtk_widget_get_realized (widget)) return; parent = gtk_widget_get_parent (widget); if (parent) gtk_widget_translate_coordinates (widget, parent, 0., 0., &x, &y); else x = y = 0.; width = gtk_widget_get_width (widget); height = gtk_widget_get_height (widget); emit_bounds_changed (self, (int)x, (int)y, width, height); } static void gtk_at_spi_context_child_change (GtkATContext *ctx, GtkAccessibleChildChange change, GtkAccessible *child) { GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (ctx); GtkAccessible *accessible = gtk_at_context_get_accessible (ctx); GtkATContext *child_context = gtk_accessible_get_at_context (child); GtkWidget *parent_widget; GtkWidget *child_widget; int idx = 0; if (!GTK_IS_WIDGET (accessible)) return; if (child_context == NULL) return; /* handle the stack page special case */ if (GTK_IS_WIDGET (child) && GTK_IS_STACK (gtk_widget_get_parent (GTK_WIDGET (child)))) { GtkWidget *stack; GtkStackPage *page; GListModel *pages; stack = gtk_widget_get_parent (GTK_WIDGET (child)); page = gtk_stack_get_page (GTK_STACK (stack), GTK_WIDGET (child)); pages = G_LIST_MODEL (gtk_stack_get_pages (GTK_STACK (stack))); idx = 0; for (guint i = 0; i < g_list_model_get_n_items (pages); i++) { GtkStackPage *item = g_list_model_get_item (pages, i); g_object_unref (item); if (!gtk_accessible_should_present (GTK_ACCESSIBLE (item))) continue; if (item == page) break; idx++; } g_object_unref (pages); if (change & GTK_ACCESSIBLE_CHILD_CHANGE_ADDED) { emit_children_changed (GTK_AT_SPI_CONTEXT (gtk_accessible_get_at_context (GTK_ACCESSIBLE (stack))), GTK_AT_SPI_CONTEXT (gtk_accessible_get_at_context (GTK_ACCESSIBLE (page))), idx, GTK_ACCESSIBLE_CHILD_STATE_ADDED); emit_children_changed (GTK_AT_SPI_CONTEXT (gtk_accessible_get_at_context (GTK_ACCESSIBLE (page))), GTK_AT_SPI_CONTEXT (gtk_accessible_get_at_context (child)), 0, GTK_ACCESSIBLE_CHILD_STATE_ADDED); } if (change & GTK_ACCESSIBLE_CHILD_CHANGE_REMOVED) { emit_children_changed (GTK_AT_SPI_CONTEXT (gtk_accessible_get_at_context (GTK_ACCESSIBLE (page))), GTK_AT_SPI_CONTEXT (gtk_accessible_get_at_context (child)), 0, GTK_ACCESSIBLE_CHILD_STATE_REMOVED); emit_children_changed (GTK_AT_SPI_CONTEXT (gtk_accessible_get_at_context (GTK_ACCESSIBLE (stack))), GTK_AT_SPI_CONTEXT (gtk_accessible_get_at_context (GTK_ACCESSIBLE (page))), idx, GTK_ACCESSIBLE_CHILD_STATE_REMOVED); } return; } parent_widget = GTK_WIDGET (accessible); if (GTK_IS_STACK_PAGE (child)) child_widget = gtk_stack_page_get_child (GTK_STACK_PAGE (child)); else child_widget = GTK_WIDGET (child); if (gtk_widget_get_parent (child_widget) != parent_widget) { idx = 0; } else { for (GtkWidget *iter = gtk_widget_get_first_child (parent_widget); iter != NULL; iter = gtk_widget_get_next_sibling (iter)) { if (!gtk_accessible_should_present (GTK_ACCESSIBLE (iter))) continue; if (iter == child_widget) break; idx += 1; } } if (change & GTK_ACCESSIBLE_CHILD_CHANGE_ADDED) emit_children_changed (self, GTK_AT_SPI_CONTEXT (child_context), idx, GTK_ACCESSIBLE_CHILD_STATE_ADDED); else if (change & GTK_ACCESSIBLE_CHILD_CHANGE_REMOVED) emit_children_changed (self, GTK_AT_SPI_CONTEXT (child_context), idx, GTK_ACCESSIBLE_CHILD_STATE_REMOVED); } /* }}} */ /* {{{ D-Bus Registration */ static void gtk_at_spi_context_register_object (GtkAtSpiContext *self) { GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self)); GVariantBuilder interfaces = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_STRING_ARRAY); const GDBusInterfaceVTable *vtable; g_variant_builder_add (&interfaces, "s", atspi_accessible_interface.name); self->registration_ids[self->n_registered_objects] = g_dbus_connection_register_object (self->connection, self->context_path, (GDBusInterfaceInfo *) &atspi_accessible_interface, &accessible_vtable, self, NULL, NULL); self->n_registered_objects++; vtable = gtk_atspi_get_component_vtable (accessible); if (vtable) { g_variant_builder_add (&interfaces, "s", atspi_component_interface.name); self->registration_ids[self->n_registered_objects] = g_dbus_connection_register_object (self->connection, self->context_path, (GDBusInterfaceInfo *) &atspi_component_interface, vtable, self, NULL, NULL); self->n_registered_objects++; } vtable = gtk_atspi_get_text_vtable (accessible); if (vtable) { g_variant_builder_add (&interfaces, "s", atspi_text_interface.name); self->registration_ids[self->n_registered_objects] = g_dbus_connection_register_object (self->connection, self->context_path, (GDBusInterfaceInfo *) &atspi_text_interface, vtable, self, NULL, NULL); self->n_registered_objects++; } vtable = gtk_atspi_get_editable_text_vtable (accessible); if (vtable) { g_variant_builder_add (&interfaces, "s", atspi_editable_text_interface.name); self->registration_ids[self->n_registered_objects] = g_dbus_connection_register_object (self->connection, self->context_path, (GDBusInterfaceInfo *) &atspi_editable_text_interface, vtable, self, NULL, NULL); self->n_registered_objects++; } vtable = gtk_atspi_get_value_vtable (accessible); if (vtable) { g_variant_builder_add (&interfaces, "s", atspi_value_interface.name); self->registration_ids[self->n_registered_objects] = g_dbus_connection_register_object (self->connection, self->context_path, (GDBusInterfaceInfo *) &atspi_value_interface, vtable, self, NULL, NULL); self->n_registered_objects++; } /* Calling gtk_accessible_get_accessible_role() in here will recurse, * so pass the role in explicitly. */ vtable = gtk_atspi_get_selection_vtable (accessible, GTK_AT_CONTEXT (self)->accessible_role); if (vtable) { g_variant_builder_add (&interfaces, "s", atspi_selection_interface.name); self->registration_ids[self->n_registered_objects] = g_dbus_connection_register_object (self->connection, self->context_path, (GDBusInterfaceInfo *) &atspi_selection_interface, vtable, self, NULL, NULL); self->n_registered_objects++; } vtable = gtk_atspi_get_action_vtable (accessible); if (vtable) { g_variant_builder_add (&interfaces, "s", atspi_action_interface.name); self->registration_ids[self->n_registered_objects] = g_dbus_connection_register_object (self->connection, self->context_path, (GDBusInterfaceInfo *) &atspi_action_interface, vtable, self, NULL, NULL); self->n_registered_objects++; } self->interfaces = g_variant_ref_sink (g_variant_builder_end (&interfaces)); GTK_NOTE (A11Y, g_message ("Registered %d interfaces on object path '%s'", self->n_registered_objects, self->context_path)); } static void gtk_at_spi_context_unregister_object (GtkAtSpiContext *self) { while (self->n_registered_objects > 0) { self->n_registered_objects--; g_dbus_connection_unregister_object (self->connection, self->registration_ids[self->n_registered_objects]); self->registration_ids[self->n_registered_objects] = 0; } g_clear_pointer (&self->interfaces, g_variant_unref); } /* }}} */ /* {{{ GObject boilerplate */ static void gtk_at_spi_context_finalize (GObject *gobject) { GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject); gtk_at_spi_context_unregister_object (self); g_clear_object (&self->root); g_free (self->context_path); G_OBJECT_CLASS (gtk_at_spi_context_parent_class)->finalize (gobject); } static void gtk_at_spi_context_constructed (GObject *gobject) { GtkAtSpiContext *self G_GNUC_UNUSED = GTK_AT_SPI_CONTEXT (gobject); G_OBJECT_CLASS (gtk_at_spi_context_parent_class)->constructed (gobject); } static const char *get_bus_address (GdkDisplay *display); static void gtk_at_spi_context_realize (GtkATContext *context) { GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (context); GdkDisplay *display = gtk_at_context_get_display (context); /* Every GTK application has a single root AT-SPI object, which * handles all the global state, including the cache of accessible * objects. We use the GdkDisplay to store it, so it's guaranteed * to be a unique per-display connection */ self->root = g_object_get_data (G_OBJECT (display), "-gtk-atspi-root"); if (self->root == NULL) { self->root = gtk_at_spi_root_new (get_bus_address (display)); g_object_set_data_full (G_OBJECT (display), "-gtk-atspi-root", g_object_ref (self->root), g_object_unref); } else { g_object_ref (self->root); } /* UUIDs use '-' as the separator, but that's not a valid character * for a DBus object path */ char *uuid = g_uuid_string_random (); size_t len = strlen (uuid); for (size_t i = 0; i < len; i++) { if (uuid[i] == '-') uuid[i] = '_'; } self->context_path = g_strconcat (gtk_at_spi_root_get_base_path (self->root), "/", uuid, NULL); g_free (uuid); self->connection = gtk_at_spi_root_get_connection (self->root); if (self->connection == NULL) return; GtkAccessible *accessible = gtk_at_context_get_accessible (context); #ifdef G_ENABLE_DEBUG if (GTK_DEBUG_CHECK (A11Y)) { GtkAccessibleRole role = gtk_at_context_get_accessible_role (context); char *role_name = g_enum_to_string (GTK_TYPE_ACCESSIBLE_ROLE, role); g_message ("Realizing ATSPI context “%s” for accessible “%s”, with role: “%s”", self->context_path, G_OBJECT_TYPE_NAME (accessible), role_name); g_free (role_name); } #endif gtk_atspi_connect_text_signals (accessible, (GtkAtspiTextChangedCallback *)emit_text_changed, (GtkAtspiTextSelectionCallback *)emit_text_selection_changed, self); gtk_atspi_connect_selection_signals (accessible, (GtkAtspiSelectionCallback *)emit_selection_changed, self); gtk_at_spi_context_register_object (self); gtk_at_spi_root_queue_register (self->root, self); } static void gtk_at_spi_context_unrealize (GtkATContext *context) { GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (context); GtkAccessible *accessible = gtk_at_context_get_accessible (context); GTK_NOTE (A11Y, g_message ("Unrealizing ATSPI context at '%s' for accessible '%s'", self->context_path, G_OBJECT_TYPE_NAME (accessible))); /* Notify ATs that the accessible object is going away */ emit_defunct (self); gtk_at_spi_root_unregister (self->root, self); gtk_atspi_disconnect_text_signals (accessible); gtk_atspi_disconnect_selection_signals (accessible); gtk_at_spi_context_unregister_object (self); g_clear_pointer (&self->context_path, g_free); g_clear_object (&self->root); } static void gtk_at_spi_context_class_init (GtkAtSpiContextClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GtkATContextClass *context_class = GTK_AT_CONTEXT_CLASS (klass); gobject_class->constructed = gtk_at_spi_context_constructed; gobject_class->finalize = gtk_at_spi_context_finalize; context_class->realize = gtk_at_spi_context_realize; context_class->unrealize = gtk_at_spi_context_unrealize; context_class->state_change = gtk_at_spi_context_state_change; context_class->platform_change = gtk_at_spi_context_platform_change; context_class->bounds_change = gtk_at_spi_context_bounds_change; context_class->child_change = gtk_at_spi_context_child_change; } static void gtk_at_spi_context_init (GtkAtSpiContext *self) { } /* }}} */ /* {{{ Bus address discovery */ #ifdef GDK_WINDOWING_X11 static char * get_bus_address_x11 (GdkDisplay *display) { GTK_NOTE (A11Y, g_message ("Acquiring a11y bus via X11...")); Display *xdisplay = gdk_x11_display_get_xdisplay (display); Atom type_return; int format_return; gulong nitems_return; gulong bytes_after_return; guchar *data = NULL; char *address = NULL; gdk_x11_display_error_trap_push (display); XGetWindowProperty (xdisplay, DefaultRootWindow (xdisplay), gdk_x11_get_xatom_by_name_for_display (display, "AT_SPI_BUS"), 0L, BUFSIZ, False, (Atom) 31, &type_return, &format_return, &nitems_return, &bytes_after_return, &data); gdk_x11_display_error_trap_pop_ignored (display); address = g_strdup ((char *) data); XFree (data); return address; } #endif #if defined(GDK_WINDOWING_WAYLAND) || defined(GDK_WINDOWING_X11) static char * get_bus_address_dbus (GdkDisplay *display) { GTK_NOTE (A11Y, g_message ("Acquiring a11y bus via DBus...")); GError *error = NULL; GDBusConnection *connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); if (error != NULL) { GTK_NOTE (A11Y, g_message ("Unable to acquire session bus: %s", error->message)); g_error_free (error); return NULL; } GVariant *res = g_dbus_connection_call_sync (connection, "org.a11y.Bus", "/org/a11y/bus", "org.a11y.Bus", "GetAddress", NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (error != NULL) { GTK_NOTE (A11Y, g_message ("Unable to acquire the address of the accessibility bus: %s", error->message)); g_error_free (error); } char *address = NULL; if (res != NULL) { g_variant_get (res, "(s)", &address); g_variant_unref (res); } g_object_unref (connection); return address; } #endif static const char * get_bus_address (GdkDisplay *display) { const char *bus_address; bus_address = g_object_get_data (G_OBJECT (display), "-gtk-atspi-bus-address"); if (bus_address != NULL) return bus_address; /* The bus address environment variable takes precedence; this is the * mechanism used by Flatpak to handle the accessibility bus portal * between the sandbox and the outside world */ bus_address = g_getenv ("AT_SPI_BUS_ADDRESS"); if (bus_address != NULL && *bus_address != '\0') { GTK_NOTE (A11Y, g_message ("Using ATSPI bus address from environment: %s", bus_address)); g_object_set_data_full (G_OBJECT (display), "-gtk-atspi-bus-address", g_strdup (bus_address), g_free); goto out; } #if defined(GDK_WINDOWING_WAYLAND) if (bus_address == NULL) { if (GDK_IS_WAYLAND_DISPLAY (display)) { char *addr = get_bus_address_dbus (display); GTK_NOTE (A11Y, g_message ("Using ATSPI bus address from D-Bus: %s", addr)); g_object_set_data_full (G_OBJECT (display), "-gtk-atspi-bus-address", addr, g_free); bus_address = addr; } } #endif #if defined(GDK_WINDOWING_X11) if (bus_address == NULL) { if (GDK_IS_X11_DISPLAY (display)) { char *addr = get_bus_address_dbus (display); if (addr == NULL) { addr = get_bus_address_x11 (display); GTK_NOTE (A11Y, g_message ("Using ATSPI bus address from X11: %s", addr)); } else { GTK_NOTE (A11Y, g_message ("Using ATSPI bus address from D-Bus: %s", addr)); } g_object_set_data_full (G_OBJECT (display), "-gtk-atspi-bus-address", addr, g_free); bus_address = addr; } } #endif out: return bus_address; } /* }}} */ /* {{{ API */ GtkATContext * gtk_at_spi_create_context (GtkAccessibleRole accessible_role, GtkAccessible *accessible, GdkDisplay *display) { const char *bus_address; g_return_val_if_fail (GTK_IS_ACCESSIBLE (accessible), NULL); g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL); bus_address = get_bus_address (display); if (bus_address == NULL) bus_address = ""; if (*bus_address == '\0') return NULL; #if defined(GDK_WINDOWING_WAYLAND) if (GDK_IS_WAYLAND_DISPLAY (display)) return g_object_new (GTK_TYPE_AT_SPI_CONTEXT, "accessible-role", accessible_role, "accessible", accessible, "display", display, NULL); #endif #if defined(GDK_WINDOWING_X11) if (GDK_IS_X11_DISPLAY (display)) return g_object_new (GTK_TYPE_AT_SPI_CONTEXT, "accessible-role", accessible_role, "accessible", accessible, "display", display, NULL); #endif return NULL; } const char * gtk_at_spi_context_get_context_path (GtkAtSpiContext *self) { g_return_val_if_fail (GTK_IS_AT_SPI_CONTEXT (self), NULL); return self->context_path; } /*< private > * gtk_at_spi_context_to_ref: * @self: a `GtkAtSpiContext` * * Returns an ATSPI object reference for the `GtkAtSpiContext`. * * Returns: (transfer floating): a `GVariant` with the reference */ GVariant * gtk_at_spi_context_to_ref (GtkAtSpiContext *self) { g_return_val_if_fail (GTK_IS_AT_SPI_CONTEXT (self), NULL); if (self->context_path == NULL) return gtk_at_spi_null_ref (); const char *name = g_dbus_connection_get_unique_name (self->connection); return g_variant_new ("(so)", name, self->context_path); } GVariant * gtk_at_spi_context_get_interfaces (GtkAtSpiContext *self) { g_return_val_if_fail (GTK_IS_AT_SPI_CONTEXT (self), NULL); return self->interfaces; } GVariant * gtk_at_spi_context_get_states (GtkAtSpiContext *self) { GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("au")); collect_states (self, &builder); return g_variant_builder_end (&builder); } GVariant * gtk_at_spi_context_get_parent_ref (GtkAtSpiContext *self) { g_return_val_if_fail (GTK_IS_AT_SPI_CONTEXT (self), NULL); GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self)); return get_parent_context_ref (accessible); } GtkAtSpiRoot * gtk_at_spi_context_get_root (GtkAtSpiContext *self) { g_return_val_if_fail (GTK_IS_AT_SPI_CONTEXT (self), NULL); return self->root; } int gtk_at_spi_context_get_index_in_parent (GtkAtSpiContext *self) { g_return_val_if_fail (GTK_IS_AT_SPI_CONTEXT (self), -1); GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self)); int idx; if (GTK_IS_ROOT (accessible)) idx = get_index_in_toplevels (GTK_WIDGET (accessible)); else if (GTK_IS_STACK_PAGE (accessible)) idx = get_index_in_parent (gtk_stack_page_get_child (GTK_STACK_PAGE (accessible))); else if (GTK_IS_STACK (gtk_widget_get_parent (GTK_WIDGET (accessible)))) idx = 1; else idx = get_index_in_parent (GTK_WIDGET (accessible)); return idx; } int gtk_at_spi_context_get_child_count (GtkAtSpiContext *self) { g_return_val_if_fail (GTK_IS_AT_SPI_CONTEXT (self), -1); GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self)); int n_children = -1; if (GTK_IS_WIDGET (accessible)) { GtkWidget *child; n_children = 0; for (child = gtk_widget_get_first_child (GTK_WIDGET (accessible)); child; child = gtk_widget_get_next_sibling (child)) { if (!gtk_accessible_should_present (GTK_ACCESSIBLE (child))) continue; n_children++; } } else if (GTK_IS_STACK_PAGE (accessible)) { n_children = 1; } return n_children; } /* }}} */ /* vim:set foldmethod=marker expandtab: */