gtk2/gtk/a11y/gtkatspiroot.c
Emmanuele Bassi 9ce790032d a11y: Convenience API for referencing ATSPI root node
We turn the root node into a reference fairly often, so it's worth it to
have a utility function that does this for us.
2020-10-12 16:19:32 +01:00

590 lines
19 KiB
C

/* 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 "gtkatspicontextprivate.h"
#include "gtkatspiprivate.h"
#include "gtkatspiutilsprivate.h"
#include "gtkdebug.h"
#include "gtkwindow.h"
#include "a11y/atspi/atspi-accessible.h"
#include "a11y/atspi/atspi-application.h"
#include <locale.h>
#include <glib/gi18n-lib.h>
#include <gio/gio.h>
#define ATSPI_VERSION "2.1"
#define ATSPI_PATH_PREFIX "/org/a11y/atspi"
#define ATSPI_ROOT_PATH ATSPI_PATH_PREFIX "/accessible/root"
#define ATSPI_CACHE_PATH ATSPI_PATH_PREFIX "/cache"
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;
GtkAtSpiCache *cache;
GListModel *toplevels;
};
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->cache);
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)
{
if (g_strcmp0 (method_name, "GetLocale") == 0)
{
guint lctype;
const char *locale;
int types[] = {
LC_MESSAGES, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME
};
g_variant_get (parameters, "(u)", &lctype);
if (lctype >= G_N_ELEMENTS (types))
{
g_dbus_method_invocation_return_error (invocation,
G_IO_ERROR,
G_IO_ERROR_INVALID_ARGUMENT,
"Not a known locale facet: %u", lctype);
return;
}
locale = setlocale (types[lctype], NULL);
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", locale));
}
}
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)
{
GtkAtSpiRoot *self = user_data;
if (g_strcmp0 (method_name, "GetRole") == 0)
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(u)", ATSPI_ROLE_APPLICATION));
else if (g_strcmp0 (method_name, "GetRoleName") == 0)
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "application"));
else if (g_strcmp0 (method_name, "GetLocalizedRoleName") == 0)
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", C_("accessibility", "application")));
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"));
g_variant_builder_add (&builder, "u", 0);
g_variant_builder_add (&builder, "u", 0);
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");
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))",
self->desktop_name,
self->desktop_path));
}
else if (g_strcmp0 (method_name, "GetChildAtIndex") == 0)
{
int idx, real_idx = 0;
g_variant_get (parameters, "(i)", &idx);
GtkWidget *window = NULL;
guint n_toplevels = g_list_model_get_n_items (self->toplevels);
for (guint i = 0; i < n_toplevels; i++)
{
window = g_list_model_get_item (self->toplevels, i);
g_object_unref (window);
if (!gtk_widget_get_visible (window))
continue;
if (real_idx == idx)
break;
real_idx += 1;
}
if (window == NULL)
return;
GtkATContext *context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (window));
const char *name = g_dbus_connection_get_unique_name (self->connection);
const char *path = gtk_at_spi_context_get_context_path (GTK_AT_SPI_CONTEXT (context));
g_dbus_method_invocation_return_value (invocation, g_variant_new ("((so))", name, path));
}
else if (g_strcmp0 (method_name, "GetChildren") == 0)
{
GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(so)"));
guint n_toplevels = g_list_model_get_n_items (self->toplevels);
for (guint i = 0; i < n_toplevels; i++)
{
GtkWidget *window = g_list_model_get_item (self->toplevels, i);
g_object_unref (window);
if (!gtk_widget_get_visible (window))
continue;
GtkATContext *context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (window));
const char *name = g_dbus_connection_get_unique_name (self->connection);
const char *path = gtk_at_spi_context_get_context_path (GTK_AT_SPI_CONTEXT (context));
g_variant_builder_add (&builder, "(so)", name, path);
}
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a(so))", &builder));
}
else if (g_strcmp0 (method_name, "GetIndexInParent") == 0)
{
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", -1));
}
}
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)
{
guint n_toplevels = g_list_model_get_n_items (self->toplevels);
int n_children = 0;
for (guint i = 0; i < n_toplevels; i++)
{
GtkWidget *window = g_list_model_get_item (self->toplevels, i);
if (gtk_widget_get_visible (window))
n_children += 1;
g_object_unref (window);
}
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));
}
/* Register the cache object */
self->cache = gtk_at_spi_cache_new (self->connection, ATSPI_CACHE_PATH);
/* Monitor the top levels */
self->toplevels = gtk_window_get_toplevels ();
}
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,
(GDBusInterfaceInfo *) &atspi_application_interface,
&root_application_vtable,
self,
NULL,
NULL);
g_dbus_connection_register_object (self->connection,
self->root_path,
(GDBusInterfaceInfo *) &atspi_accessible_interface,
&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;
}
GtkAtSpiCache *
gtk_at_spi_root_get_cache (GtkAtSpiRoot *self)
{
g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), NULL);
return self->cache;
}
/*< private >
* gtk_at_spi_root_to_ref:
* @self: a #GtkAtSpiRoot
*
* Returns an ATSPI object reference for the #GtkAtSpiRoot node.
*
* Returns: (transfer floating): a #GVariant with the root reference
*/
GVariant *
gtk_at_spi_root_to_ref (GtkAtSpiRoot *self)
{
g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), NULL);
if (self->desktop_path == NULL)
return gtk_at_spi_null_ref ();
return g_variant_new ("(so)", self->desktop_name, self->desktop_path);
}