/* 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 "gtkatspicacheprivate.h"
#include "gtkatspicontextprivate.h"
#include "gtkaccessibleprivate.h"
#include "gtkatspiprivate.h"
#include "gtkatspiutilsprivate.h"
#include "gtkdebug.h"
#include "gtkwindow.h"
#include "gtkprivate.h"
#include "gdkprivate.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"
#define ATSPI_REGISTRY_PATH ATSPI_PATH_PREFIX "/registry"
struct _GtkAtSpiRoot
{
GObject parent_instance;
char *bus_address;
GDBusConnection *connection;
char *base_path;
const char *root_path;
const char *toolkit_name;
const char *version;
const char *atspi_version;
char *desktop_name;
char *desktop_path;
gint32 application_id;
guint register_id;
GList *queued_contexts;
GtkAtSpiCache *cache;
GListModel *toplevels;
/* HashTable<str, uint> */
GHashTable *event_listeners;
bool can_use_event_listeners;
};
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_clear_handle_id (&self->register_id, g_source_remove);
g_clear_pointer (&self->event_listeners, g_hash_table_unref);
g_free (self->bus_address);
g_free (self->base_path);
g_free (self->desktop_name);
g_free (self->desktop_path);
G_OBJECT_CLASS (gtk_at_spi_root_parent_class)->finalize (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_clear_pointer (&self->queued_contexts, g_list_free);
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));
}
else if (g_strcmp0 (method_name, "GetApplicationBusAddress" ) == 0 )
{
GtkAtSpiRoot *root = user_data;
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)" , root->bus_address));
}
}
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));
g_object_unref (context);
}
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_object_unref (context);
}
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 ));
}
else if (g_strcmp0 (method_name, "GetRelationSet" ) == 0 )
{
GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(ua(so))" ));
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a(ua(so)))" , &builder));
}
else if (g_strcmp0 (method_name, "GetInterfaces" ) == 0 )
{
GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("as" ));
g_variant_builder_add (&builder, "s" , atspi_accessible_interface.name);
g_variant_builder_add (&builder, "s" , atspi_application_interface.name);
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(as)" , &builder));
}
}
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 () ? g_get_prgname () : "Unnamed" );
else if (g_strcmp0 (property_name, "Description" ) == 0 )
res = g_variant_new_string (g_get_application_name () ? g_get_application_name () : "No description" );
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 )
{
const char *id = NULL;
GApplication *application = g_application_get_default ();
if (application)
id = g_application_get_application_id (application);
res = g_variant_new_string (id ? id : "" );
}
else if (g_strcmp0 (property_name, "Parent" ) == 0 )
res = gtk_at_spi_root_get_parent_ref (self);
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,
};
void
gtk_at_spi_root_child_changed (GtkAtSpiRoot *self,
GtkAccessibleChildChange change,
GtkAccessible *child)
{
guint n, i;
int idx = 0 ;
GVariant *window_ref;
GtkAccessibleChildState state;
if (!self->toplevels)
return ;
for (i = 0 , n = g_list_model_get_n_items (self->toplevels); i < n; i++)
{
GtkAccessible *item = g_list_model_get_item (self->toplevels, i);
g_object_unref (item);
if (item == child)
break ;
if (!gtk_accessible_should_present (item))
continue ;
idx++;
}
if (child == NULL)
{
window_ref = gtk_at_spi_null_ref ();
}
else
{
GtkATContext *context = gtk_accessible_get_at_context (child);
window_ref = gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (context));
g_object_unref (context);
}
switch (change)
{
case GTK_ACCESSIBLE_CHILD_CHANGE_ADDED:
state = GTK_ACCESSIBLE_CHILD_STATE_ADDED;
break ;
case GTK_ACCESSIBLE_CHILD_CHANGE_REMOVED:
state = GTK_ACCESSIBLE_CHILD_STATE_REMOVED;
break ;
default :
g_assert_not_reached ();
}
gtk_at_spi_emit_children_changed (self->connection,
self->root_path,
state,
idx,
window_ref);
}
static void
on_event_listener_registered (GDBusConnection *connection,
const char *sender_name,
const char *object_path,
const char *interface_name,
const char *signal_name,
GVariant *parameters,
gpointer user_data)
{
GtkAtSpiRoot *self = user_data;
if (g_strcmp0 (object_path, ATSPI_REGISTRY_PATH) == 0 &&
g_strcmp0 (interface_name, "org.a11y.atspi.Registry" ) == 0 &&
g_strcmp0 (signal_name, "EventListenerRegistered" ) == 0 )
{
const char *sender = NULL;
const char *event_name = NULL;
unsigned int *count;
if (self->event_listeners == NULL)
self->event_listeners = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free,
g_free);
g_variant_get (parameters, "(&s&sas)" , &sender, &event_name, NULL);
count = g_hash_table_lookup (self->event_listeners, sender);
if (count == NULL)
{
GTK_DEBUG (A11Y, "Registering event listener (%s, %s) on the a11y bus" ,
sender,
event_name[0 ] != 0 ? event_name : "(none)" );
count = g_new (unsigned int , 1 );
*count = 1 ;
g_hash_table_insert (self->event_listeners, g_strdup (sender), count);
}
else if (*count == G_MAXUINT)
{
GTK_DEBUG (A11Y, "Reference count for event listener %s reached saturation" , sender);
}
else
{
GTK_DEBUG (A11Y, "Incrementing refcount for event listener %s" , sender);
*count += 1 ;
}
}
}
static void
on_event_listener_deregistered (GDBusConnection *connection,
const char *sender_name,
const char *object_path,
const char *interface_name,
const char *signal_name,
GVariant *parameters,
gpointer user_data)
{
GtkAtSpiRoot *self = user_data;
if (g_strcmp0 (object_path, ATSPI_REGISTRY_PATH) == 0 &&
g_strcmp0 (interface_name, "org.a11y.atspi.Registry" ) == 0 &&
g_strcmp0 (signal_name, "EventListenerDeregistered" ) == 0 )
{
const char *sender = NULL;
const char *event = NULL;
unsigned int *count;
g_variant_get (parameters, "(&s&s)" , &sender, &event);
if (G_UNLIKELY (self->event_listeners == NULL))
{
GTK_DEBUG (A11Y,
"Received org.a11y.atspi.Registry::EventListenerDeregistered for "
"sender (%s, %s) without a corresponding EventListenerRegistered "
"signal." ,
sender, event[0 ] != '\0' ? event : "(no event)" );
return ;
}
count = g_hash_table_lookup (self->event_listeners, sender);
if (G_UNLIKELY (count == NULL))
{
GTK_DEBUG (A11Y,
"Received org.a11y.atspi.Registry::EventListenerDeregistered for "
"sender (%s, %s) without a corresponding EventListenerRegistered "
"signal." ,
sender, event[0 ] != '\0' ? event : "(no event)" );
}
else if (*count > 1 )
{
GTK_DEBUG (A11Y, "Decreasing refcount for listener %s" , sender);
*count -= 1 ;
}
else
{
GTK_DEBUG (A11Y, "Deregistering event listener %s on the a11y bus" , sender);
g_hash_table_remove (self->event_listeners, sender);
}
}
}
static bool
check_flatpak_portal_version (unsigned int minimum_version)
{
static uint32_t flatpak_portal_version = 0 ;
static size_t initialized = 0 ;
if (g_once_init_enter (&initialized))
{
GDBusConnection *session_bus;
GError *error = NULL;
GVariant *child;
GVariant *res;
session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
if (error != NULL)
{
g_warning ("Unable to retrieve the session bus: %s" ,
error->message);
g_clear_error (&error);
g_once_init_leave (&initialized, 1 );
return false ;
}
res =
g_dbus_connection_call_sync (session_bus,
"org.freedesktop.portal.Flatpak" ,
"/org/freedesktop/portal/Flatpak" ,
"org.freedesktop.DBus.Properties" ,
"Get" ,
g_variant_new ("(ss)" , "org.freedesktop.portal.Flatpak" , "version" ),
G_VARIANT_TYPE ("(v)" ),
G_DBUS_CALL_FLAGS_NONE,
-1 ,
NULL,
&error);
if (error != NULL)
{
g_warning ("Unable to retrieve the Flatpak portal version: %s" ,
error->message);
g_clear_error (&error);
g_once_init_leave (&initialized, 1 );
return false ;
}
g_variant_get (res, "(v)" , &child);
g_variant_unref (res);
flatpak_portal_version = g_variant_get_uint32 (child);
g_variant_unref (child);
g_once_init_leave (&initialized, 1 );
}
GTK_DEBUG (A11Y, "Flatpak portal version: %u (required: %u)" , flatpak_portal_version, minimum_version);
return flatpak_portal_version >= minimum_version;
}
static void
on_registered_events_reply (GObject *gobject,
GAsyncResult *result,
gpointer data)
{
GError *error = NULL;
GVariant *reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (gobject), result, &error);
if (error != NULL)
{
g_critical ("Unable to get the list of registered event listeners: %s" , error->message);
g_error_free (error);
return ;
}
GtkAtSpiRoot *self = data;
GVariant *listeners = g_variant_get_child_value (reply, 0 );
GVariantIter *iter;
const char *sender, *event_name;
if (self->event_listeners == NULL)
self->event_listeners = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
g_variant_get (listeners, "a(ss)" , &iter);
while (g_variant_iter_loop (iter, "(&s&s)" , &sender, &event_name))
{
unsigned int *count;
GTK_DEBUG (A11Y, "Registering event listener (%s, %s) on the a11y bus" ,
sender,
event_name[0 ] != 0 ? event_name : "(none)" );
count = g_hash_table_lookup (self->event_listeners, sender);
if (count == NULL)
{
count = g_new (unsigned int , 1 );
*count = 1 ;
g_hash_table_insert (self->event_listeners, g_strdup (sender), count);
}
else if (*count == G_MAXUINT)
{
g_critical ("Reference count for event listener %s reached saturation" , sender);
}
else
*count += 1 ;
}
g_variant_iter_free (iter);
g_variant_unref (listeners);
g_variant_unref (reply);
}
typedef struct {
GtkAtSpiRoot *root;
GtkAtSpiRootRegisterFunc register_func;
} RegistrationData;
static void
on_registration_reply (GObject *gobject,
GAsyncResult *result,
gpointer user_data)
{
RegistrationData *data = user_data;
GtkAtSpiRoot *self = data->root;
GError *error = NULL;
GVariant *reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (gobject), result, &error);
self->register_id = 0 ;
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_DEBUG (A11Y, "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, self);
/* Drain the list of queued GtkAtSpiContexts, and add them to the cache */
if (self->queued_contexts != NULL)
{
self->queued_contexts = g_list_reverse (self->queued_contexts);
for (GList *l = self->queued_contexts; l != NULL; l = l->next)
{
if (data->register_func != NULL)
data->register_func (self, l->data);
gtk_at_spi_cache_add_context (self->cache, l->data);
}
g_clear_pointer (&self->queued_contexts, g_list_free);
}
self->toplevels = gtk_window_get_toplevels ();
g_free (data);
/* Check if we're running inside a sandbox.
*
* Flatpak applications need to have the D - Bus proxy set up inside the
* sandbox to allow event registration signals to propagate , so we
* check if the version of the Flatpak portal is recent enough .
*/
if (gdk_running_in_sandbox () &&
!check_flatpak_portal_version (7 ))
{
GTK_DEBUG (A11Y, "Sandboxed does not allow event listener registration" );
self->can_use_event_listeners = false ;
return ;
}
/* Subscribe to notifications on the registered event listeners */
g_dbus_connection_signal_subscribe (self->connection,
"org.a11y.atspi.Registry" ,
"org.a11y.atspi.Registry" ,
"EventListenerRegistered" ,
ATSPI_REGISTRY_PATH,
NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
on_event_listener_registered,
self,
NULL);
g_dbus_connection_signal_subscribe (self->connection,
"org.a11y.atspi.Registry" ,
"org.a11y.atspi.Registry" ,
"EventListenerDeregistered" ,
ATSPI_REGISTRY_PATH,
NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
on_event_listener_deregistered,
self,
NULL);
/* Get the list of ATs listening to events, in case they were started
* before the application ; we want to delay the D - Bus traffic as much
* as possible until we know something is listening on the accessibility
* bus
*/
g_dbus_connection_call (self->connection,
"org.a11y.atspi.Registry" ,
ATSPI_REGISTRY_PATH,
"org.a11y.atspi.Registry" ,
"GetRegisteredEvents" ,
g_variant_new ("()" ),
G_VARIANT_TYPE ("(a(ss))" ),
G_DBUS_CALL_FLAGS_NONE, -1 ,
NULL,
on_registered_events_reply,
self);
self->can_use_event_listeners = true ;
}
static gboolean
root_register (gpointer user_data)
{
RegistrationData *data = user_data;
GtkAtSpiRoot *self = data->root;
const char *unique_name;
/* 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;
unique_name = g_dbus_connection_get_unique_name (self->connection);
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_DEBUG (A11Y, "Registering (%s, %s) on the a11y bus" ,
unique_name,
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))" , unique_name, self->root_path),
G_VARIANT_TYPE ("((so))" ),
G_DBUS_CALL_FLAGS_NONE, -1 ,
NULL,
on_registration_reply,
data);
return G_SOURCE_REMOVE;
}
/*< private >
* gtk_at_spi_root_queue_register :
* @ self : a ` GtkAtSpiRoot `
* @ context : the AtSpi context to register
* @ func : the function to call when the root has been registered
*
* Queues the registration of the root object on the AT - SPI bus .
*/
void
gtk_at_spi_root_queue_register (GtkAtSpiRoot *self,
GtkAtSpiContext *context,
GtkAtSpiRootRegisterFunc func)
{
/* The cache is available if the root has finished registering itself; if we
* are still waiting for the registration to finish , add the context to a queue
*/
if (self->cache != NULL)
{
if (func != NULL)
func (self, context);
gtk_at_spi_cache_add_context (self->cache, context);
return ;
}
else
{
if (g_list_find (self->queued_contexts, context) == NULL)
self->queued_contexts = g_list_prepend (self->queued_contexts, context);
}
/* Ignore multiple registration requests while one is already in flight */
if (self->register_id != 0 )
return ;
RegistrationData *data = g_new (RegistrationData, 1 );
data->root = self;
data->register_func = func;
self->register_id = g_idle_add (root_register, data);
gdk_source_set_static_name_by_id (self->register_id, "[gtk] ATSPI root registration" );
}
void
gtk_at_spi_root_unregister (GtkAtSpiRoot *self,
GtkAtSpiContext *context)
{
if (self->queued_contexts != NULL)
self->queued_contexts = g_list_remove (self->queued_contexts, context);
if (self->cache != NULL)
gtk_at_spi_cache_remove_context (self->cache, context);
}
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;
}
/* 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 ();
if (application != NULL && g_application_get_is_registered (application))
{
const char *app_path = g_application_get_dbus_object_path (application);
/* No need to validate the path */
self->base_path = g_strconcat (app_path, "/a11y" , NULL);
}
if (self->base_path == NULL)
{
const char *program_name = g_get_prgname ();
char *base_name = NULL;
if (program_name == NULL || *program_name == 0 )
base_name = g_strdup ("unknown" );
else if (*program_name == '/' )
base_name = g_path_get_basename (program_name);
else
base_name = g_strdup (program_name);
self->base_path = g_strconcat ("/org/gtk/application/" ,
base_name,
"/a11y" ,
NULL);
g_free (base_name);
/* Turn potentially invalid program names into something that can be
* used as a DBus path
*/
size_t len = strlen (self->base_path);
for (size_t i = 0 ; i < len; i++)
{
char c = self->base_path[i];
if (c == '/' )
continue ;
if ((c >= '0' && c <= '9' ) ||
(c >= 'A' && c <= 'Z' ) ||
(c >= 'a' && c <= 'z' ) ||
(c == '_' ))
continue ;
self->base_path[i] = '_' ;
}
}
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_NAME);
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);
return g_variant_new ("(so)" ,
g_dbus_connection_get_unique_name (self->connection),
self->root_path);
}
/*< private >
* gtk_at_spi_root_get_parent_ref :
* @ self : a ` GtkAtSpiRoot `
*
* Returns an AT - SPI object reference for the parent of the root ,
* which is the desktop accessible .
*
* Returns : a ` GVariant ` with the parent reference
*/
GVariant *
gtk_at_spi_root_get_parent_ref (GtkAtSpiRoot *self)
{
g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), gtk_at_spi_null_ref ());
if (self->desktop_name != NULL && self->desktop_path != NULL)
return g_variant_new ("(so)" , self->desktop_name, self->desktop_path);
return gtk_at_spi_null_ref ();
}
const char *
gtk_at_spi_root_get_base_path (GtkAtSpiRoot *self)
{
g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), NULL);
return self->base_path;
}
gboolean
gtk_at_spi_root_has_event_listeners (GtkAtSpiRoot *self)
{
g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), FALSE );
/* If we can't rely on event listeners, we default to being chatty */
if (!self->can_use_event_listeners)
return TRUE ;
return self->event_listeners != NULL &&
g_hash_table_size (self->event_listeners) != 0 ;
}
Messung V0.5 in Prozent C=99 H=92 G=95
¤ Dauer der Verarbeitung: 0.17 Sekunden
(vorverarbeitet am 2026-07-03)
¤
*© Formatika GbR, Deutschland