[gtk/a11y/atspi: 5/28] a11y: Register the accessible root object




commit bd43e9cfc5d756b6caacf468e1542e47945de7d8
Author: Emmanuele Bassi <ebassi gnome org>
Date:   Fri Oct 9 12:32:09 2020 +0100

    a11y: Register the accessible root object
    
    When we create the first AT-SPI context we also need to register the
    accessible root on the accessibility bus. The accessible root object is
    the main entry point of an accessible application, and it holds the
    global state to present to the ATs that connect to the bus.

 gtk/a11y/gtkatspicontext.c     | 291 +++++++++++++++++++++++++++
 gtk/a11y/gtkatspiroot.c        | 432 +++++++++++++++++++++++++++++++++++++++++
 gtk/a11y/gtkatspirootprivate.h |  37 ++++
 gtk/a11y/meson.build           |   1 +
 4 files changed, 761 insertions(+)
---
diff --git a/gtk/a11y/gtkatspicontext.c b/gtk/a11y/gtkatspicontext.c
index 104458020b..d2c582d018 100644
--- a/gtk/a11y/gtkatspicontext.c
+++ b/gtk/a11y/gtkatspicontext.c
@@ -22,18 +22,48 @@
 
 #include "gtkatspicontextprivate.h"
 
+#include "gtkatspirootprivate.h"
+#include "gtkdebug.h"
+#include "gtkwindow.h"
+
+#include <gio/gio.h>
+
 #if defined(GDK_WINDOWING_WAYLAND)
 # include <gdk/wayland/gdkwaylanddisplay.h>
 #endif
 #if defined(GDK_WINDOWING_X11)
 # include <gdk/x11/gdkx11display.h>
+# include <gdk/x11/gdkx11property.h>
 #endif
 
 struct _GtkAtSpiContext
 {
   GtkATContext parent_instance;
+
+  /* The root object, used as a entry point */
+  GtkAtSpiRoot *root;
+
+  /* The address for the ATSPI accessibility bus */
+  char *bus_address;
+
+  /* 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;
+};
+
+enum
+{
+  PROP_BUS_ADDRESS = 1,
+
+  N_PROPS
 };
 
+static GParamSpec *obj_props[N_PROPS];
+
 G_DEFINE_TYPE (GtkAtSpiContext, gtk_at_spi_context, GTK_TYPE_AT_CONTEXT)
 
 static void
@@ -47,9 +77,113 @@ gtk_at_spi_context_state_change (GtkATContext                *self,
 {
 }
 
+static void
+gtk_at_spi_context_finalize (GObject *gobject)
+{
+  GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject);
+
+  g_free (self->bus_address);
+  g_free (self->context_path);
+
+  G_OBJECT_CLASS (gtk_at_spi_context_parent_class)->finalize (gobject);
+}
+
+static void
+gtk_at_spi_context_set_property (GObject      *gobject,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_BUS_ADDRESS:
+      self->bus_address = g_value_dup_string (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+static void
+gtk_at_spi_context_get_property (GObject    *gobject,
+                                 guint       prop_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+  GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_BUS_ADDRESS:
+      g_value_set_string (value, self->bus_address);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
 static void
 gtk_at_spi_context_constructed (GObject *gobject)
 {
+  GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject);
+  GdkDisplay *display;
+
+  g_assert (self->bus_address);
+
+  /* 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 unique per-display connection
+   */
+  display = gtk_at_context_get_display (GTK_AT_CONTEXT (self));
+  self->root =
+    g_object_get_data (G_OBJECT (display), "-gtk-atspi-root");
+
+  if (self->root == NULL)
+    {
+      self->root = gtk_at_spi_root_new (self->bus_address);
+      self->connection = gtk_at_spi_root_get_connection (self->root);
+
+      g_object_set_data_full (G_OBJECT (display), "-gtk-atspi-root",
+                              self->root,
+                              g_object_unref);
+    }
+
+  /* We use the application's object path to build the path of each
+   * accessible object exposed on the accessibility bus; the path is
+   * also used to access the object cache
+   */
+  GApplication *application = g_application_get_default ();
+  char *base_path = NULL;
+
+  if (application != NULL)
+    {
+      const char *app_path = g_application_get_dbus_object_path (application);
+      base_path = g_strconcat (app_path, "/a11y", NULL);
+    }
+  else
+    {
+      char *uuid = g_uuid_string_random ();
+      base_path = g_strconcat ("/org/gtk/application/", uuid, "/a11y", NULL);
+      g_free (uuid);
+    }
+
+  /* We use a unique id to ensure that we don't have conflicting
+   * objects on the bus
+   */
+  char *uuid = g_uuid_string_random ();
+
+  self->context_path = g_strconcat (base_path, "/", uuid, NULL);
+
+  GTK_NOTE (A11Y, g_message ("ATSPI context path: %s", self->context_path));
+
+  g_free (base_path);
+  g_free (uuid);
+
   G_OBJECT_CLASS (gtk_at_spi_context_parent_class)->constructed (gobject);
 }
 
@@ -60,8 +194,20 @@ gtk_at_spi_context_class_init (GtkAtSpiContextClass *klass)
   GtkATContextClass *context_class = GTK_AT_CONTEXT_CLASS (klass);
 
   gobject_class->constructed = gtk_at_spi_context_constructed;
+  gobject_class->set_property = gtk_at_spi_context_set_property;
+  gobject_class->get_property = gtk_at_spi_context_get_property;
+  gobject_class->finalize = gtk_at_spi_context_finalize;
 
   context_class->state_change = gtk_at_spi_context_state_change;
+
+  obj_props[PROP_BUS_ADDRESS] =
+    g_param_spec_string ("bus-address", NULL, NULL,
+                         NULL,
+                         G_PARAM_CONSTRUCT_ONLY |
+                         G_PARAM_READWRITE |
+                         G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (gobject_class, N_PROPS, obj_props);
 }
 
 static void
@@ -69,6 +215,144 @@ gtk_at_spi_context_init (GtkAtSpiContext *self)
 {
 }
 
+#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)
+    {
+      g_critical ("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)
+    {
+      g_critical ("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"));
+      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);
+
+          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);
+
+          g_object_set_data_full (G_OBJECT (display), "-gtk-atspi-bus-address",
+                                  addr,
+                                  g_free);
+
+          bus_address = addr;
+        }
+    }
+#endif
+
+out:
+  return bus_address;
+}
+
 GtkATContext *
 gtk_at_spi_create_context (GtkAccessibleRole  accessible_role,
                            GtkAccessible     *accessible,
@@ -77,12 +361,18 @@ gtk_at_spi_create_context (GtkAccessibleRole  accessible_role,
   g_return_val_if_fail (GTK_IS_ACCESSIBLE (accessible), NULL);
   g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);
 
+  const char *bus_address = get_bus_address (display);
+
+  if (bus_address == NULL)
+    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,
+                         "bus-address", bus_address,
                          NULL);
 #endif
 #if defined(GDK_WINDOWING_X11)
@@ -91,6 +381,7 @@ gtk_at_spi_create_context (GtkAccessibleRole  accessible_role,
                          "accessible-role", accessible_role,
                          "accessible", accessible,
                          "display", display,
+                         "bus-address", bus_address,
                          NULL);
 #endif
 
diff --git a/gtk/a11y/gtkatspiroot.c b/gtk/a11y/gtkatspiroot.c
new file mode 100644
index 0000000000..5d7e5feef7
--- /dev/null
+++ b/gtk/a11y/gtkatspiroot.c
@@ -0,0 +1,432 @@
+/* gtkatspiroot.c: AT-SPI root object
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkatspirootprivate.h"
+
+#include "gtkdebug.h"
+#include "gtkwindow.h"
+
+#include "a11y/atspi/atspi-accessible.h"
+#include "a11y/atspi/atspi-application.h"
+
+#include <locale.h>
+
+#include <gio/gio.h>
+
+#define ATSPI_VERSION   "2.1"
+#define ATSPI_ROOT_PATH "/org/a11y/atspi/accessible/root"
+
+struct _GtkAtSpiRoot
+{
+  GObject parent_instance;
+
+  char *bus_address;
+  GDBusConnection *connection;
+
+  const char *root_path;
+
+  const char *toolkit_name;
+  const char *version;
+  const char *atspi_version;
+
+  char *desktop_name;
+  char *desktop_path;
+
+  gint32 application_id;
+};
+
+enum
+{
+  PROP_BUS_ADDRESS = 1,
+
+  N_PROPS
+};
+
+static GParamSpec *obj_props[N_PROPS];
+
+G_DEFINE_TYPE (GtkAtSpiRoot, gtk_at_spi_root, G_TYPE_OBJECT)
+
+static void
+gtk_at_spi_root_finalize (GObject *gobject)
+{
+  GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject);
+
+  g_free (self->bus_address);
+  g_free (self->desktop_name);
+  g_free (self->desktop_path);
+
+  G_OBJECT_CLASS (gtk_at_spi_root_parent_class)->dispose (gobject);
+}
+
+static void
+gtk_at_spi_root_dispose (GObject *gobject)
+{
+  GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject);
+
+  g_clear_object (&self->connection);
+
+  G_OBJECT_CLASS (gtk_at_spi_root_parent_class)->dispose (gobject);
+}
+
+static void
+gtk_at_spi_root_set_property (GObject      *gobject,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+  GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_BUS_ADDRESS:
+      self->bus_address = g_value_dup_string (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+static void
+gtk_at_spi_root_get_property (GObject    *gobject,
+                              guint       prop_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_BUS_ADDRESS:
+      g_value_set_string (value, self->bus_address);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+
+static void
+handle_application_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)
+{
+}
+
+static GVariant *
+handle_application_get_property (GDBusConnection       *connection,
+                                 const gchar           *sender,
+                                 const gchar           *object_path,
+                                 const gchar           *interface_name,
+                                 const gchar           *property_name,
+                                 GError               **error,
+                                 gpointer               user_data)
+{
+  GtkAtSpiRoot *self = user_data;
+  GVariant *res = NULL;
+
+  if (g_strcmp0 (property_name, "Id") == 0)
+    res = g_variant_new_int32 (self->application_id);
+  else if (g_strcmp0 (property_name, "ToolkitName") == 0)
+    res = g_variant_new_string (self->toolkit_name);
+  else if (g_strcmp0 (property_name, "Version") == 0)
+    res = g_variant_new_string (self->version);
+  else if (g_strcmp0 (property_name, "AtspiVersion") == 0)
+    res = g_variant_new_string (self->atspi_version);
+  else
+    g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                 "Unknown property '%s'", property_name);
+
+  return res;
+}
+
+static gboolean
+handle_application_set_property (GDBusConnection       *connection,
+                                 const gchar           *sender,
+                                 const gchar           *object_path,
+                                 const gchar           *interface_name,
+                                 const gchar           *property_name,
+                                 GVariant              *value,
+                                 GError               **error,
+                                 gpointer               user_data)
+{
+  GtkAtSpiRoot *self = user_data;
+
+  if (g_strcmp0 (property_name, "Id") == 0)
+    {
+      g_variant_get (value, "i", &(self->application_id));
+    }
+  else
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                   "Invalid property '%s'", property_name);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+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)
+{
+  g_printerr ("[Accessible] Method '%s' on interface '%s' for object '%s' from '%s'\n",
+              method_name, interface_name, object_path, sender);
+
+}
+
+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)
+{
+  GtkAtSpiRoot *self = user_data;
+  GVariant *res = NULL;
+
+  if (g_strcmp0 (property_name, "Name") == 0)
+    res = g_variant_new_string (g_get_prgname ());
+  else if (g_strcmp0 (property_name, "Description") == 0)
+    res = g_variant_new_string (g_get_application_name ());
+  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 = g_variant_new ("(so)", self->desktop_name, self->desktop_path);
+  else if (g_strcmp0 (property_name, "ChildCount") == 0)
+    {
+      int n_children = 0;
+
+      if (g_strcmp0 (object_path, ATSPI_ROOT_PATH) == 0)
+        {
+          GList *windows = gtk_window_list_toplevels ();
+
+          /* We are only interested in the visible top levels */
+          for (GList *l = windows; l != NULL; l = l->next)
+            {
+              if (gtk_widget_is_visible (l->data))
+                n_children += 1;
+            }
+
+          g_list_free (windows);
+        }
+
+      res = g_variant_new_int32 (n_children);
+    }
+  else
+    g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                 "Unknown property '%s'", property_name);
+
+  return res;
+}
+
+static const GDBusInterfaceVTable root_application_vtable = {
+  handle_application_method,
+  handle_application_get_property,
+  handle_application_set_property,
+};
+
+static const GDBusInterfaceVTable root_accessible_vtable = {
+  handle_accessible_method,
+  handle_accessible_get_property,
+  NULL,
+};
+
+static void
+on_registration_reply (GObject      *gobject,
+                       GAsyncResult *result,
+                       gpointer      user_data)
+{
+  GtkAtSpiRoot *self = user_data;
+
+  GError *error = NULL;
+  GVariant *reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (gobject), result, &error);
+
+  if (error != NULL)
+    {
+      g_critical ("Unable to register the application: %s", error->message);
+      g_error_free (error);
+      return;
+    }
+
+  if (reply != NULL)
+    {
+      g_variant_get (reply, "((so))",
+                     &self->desktop_name,
+                     &self->desktop_path);
+      g_variant_unref (reply);
+
+      GTK_NOTE (A11Y, g_message ("Connected to the a11y registry at (%s, %s)",
+                                 self->desktop_name,
+                                 self->desktop_path));
+    }
+}
+
+static void
+gtk_at_spi_root_register (GtkAtSpiRoot *self)
+{
+  /* Register the root element; every application has a single root, so we only
+   * need to do this once.
+   *
+   * The root element is used to advertise our existence on the accessibility
+   * bus, and it's the entry point to the accessible objects tree.
+   *
+   * The announcement is split into two phases:
+   *
+   *  1. we register the org.a11y.atspi.Application and org.a11y.atspi.Accessible
+   *     interfaces at the well-known object path
+   *  2. we invoke the org.a11y.atspi.Socket.Embed method with the connection's
+   *     unique name and the object path
+   *  3. the ATSPI registry daemon will set the org.a11y.atspi.Application.Id
+   *     property on the given object path
+   *  4. the registration concludes when the Embed method returns us the desktop
+   *     name and object path
+   */
+  self->toolkit_name = "GTK";
+  self->version = PACKAGE_VERSION;
+  self->atspi_version = ATSPI_VERSION;
+  self->root_path = ATSPI_ROOT_PATH;
+
+  g_dbus_connection_register_object (self->connection,
+                                     self->root_path,
+                                     atspi_application_interface_info (),
+                                     &root_application_vtable,
+                                     self,
+                                     NULL,
+                                     NULL);
+  g_dbus_connection_register_object (self->connection,
+                                     self->root_path,
+                                     atspi_accessible_interface_info (),
+                                     &root_accessible_vtable,
+                                     self,
+                                     NULL,
+                                     NULL);
+
+  GTK_NOTE (A11Y, g_message ("Registering (%s, %s) on the a11y bus",
+                             g_dbus_connection_get_unique_name (self->connection),
+                             self->root_path));
+
+  g_dbus_connection_call (self->connection,
+                          "org.a11y.atspi.Registry",
+                          ATSPI_ROOT_PATH,
+                          "org.a11y.atspi.Socket",
+                          "Embed",
+                          g_variant_new ("((so))",
+                            g_dbus_connection_get_unique_name (self->connection),
+                            self->root_path
+                          ),
+                          G_VARIANT_TYPE ("((so))"),
+                          G_DBUS_CALL_FLAGS_NONE, -1,
+                          NULL,
+                          on_registration_reply,
+                          self);
+}
+
+static void
+gtk_at_spi_root_constructed (GObject *gobject)
+{
+  GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject);
+
+  GError *error = NULL;
+
+  /* The accessibility bus is a fully managed bus */
+  self->connection =
+    g_dbus_connection_new_for_address_sync (self->bus_address,
+                                            G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+                                            G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
+                                            NULL, NULL,
+                                            &error);
+
+  if (error != NULL)
+    {
+      g_critical ("Unable to connect to the accessibility bus at '%s': %s",
+                  self->bus_address,
+                  error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+  gtk_at_spi_root_register (self);
+
+out:
+  G_OBJECT_CLASS (gtk_at_spi_root_parent_class)->constructed (gobject);
+}
+
+static void
+gtk_at_spi_root_class_init (GtkAtSpiRootClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->constructed = gtk_at_spi_root_constructed;
+  gobject_class->set_property = gtk_at_spi_root_set_property;
+  gobject_class->get_property = gtk_at_spi_root_get_property;
+  gobject_class->dispose = gtk_at_spi_root_dispose;
+  gobject_class->finalize = gtk_at_spi_root_finalize;
+
+  obj_props[PROP_BUS_ADDRESS] =
+    g_param_spec_string ("bus-address", NULL, NULL,
+                         NULL,
+                         G_PARAM_CONSTRUCT_ONLY |
+                         G_PARAM_READWRITE |
+                         G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (gobject_class, N_PROPS, obj_props);
+}
+
+static void
+gtk_at_spi_root_init (GtkAtSpiRoot *self)
+{
+}
+
+GtkAtSpiRoot *
+gtk_at_spi_root_new (const char *bus_address)
+{
+  g_return_val_if_fail (bus_address != NULL, NULL);
+
+  return g_object_new (GTK_TYPE_AT_SPI_ROOT,
+                       "bus-address", bus_address,
+                       NULL);
+}
+
+GDBusConnection *
+gtk_at_spi_root_get_connection (GtkAtSpiRoot *self)
+{
+  g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), NULL);
+
+  return self->connection;
+}
diff --git a/gtk/a11y/gtkatspirootprivate.h b/gtk/a11y/gtkatspirootprivate.h
new file mode 100644
index 0000000000..ff30b24670
--- /dev/null
+++ b/gtk/a11y/gtkatspirootprivate.h
@@ -0,0 +1,37 @@
+/* gtkatspirootprivate.h: AT-SPI root object
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_AT_SPI_ROOT (gtk_at_spi_root_get_type())
+
+G_DECLARE_FINAL_TYPE (GtkAtSpiRoot, gtk_at_spi_root, GTK, AT_SPI_ROOT, GObject)
+
+GtkAtSpiRoot *
+gtk_at_spi_root_new (const char *bus_address);
+
+GDBusConnection *
+gtk_at_spi_root_get_connection (GtkAtSpiRoot *self);
+
+G_END_DECLS
diff --git a/gtk/a11y/meson.build b/gtk/a11y/meson.build
index 22d1956027..d9c6cdc97c 100644
--- a/gtk/a11y/meson.build
+++ b/gtk/a11y/meson.build
@@ -10,5 +10,6 @@ if gtk_a11y_backends.contains('atspi')
 
   gtk_a11y_src += files([
     'gtkatspicontext.c',
+    'gtkatspiroot.c',
   ])
 endif


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]