af9f752052d2911bbc3e402dd1d9afa9deafceb5
[platform/core/uifw/at-spi2-atk.git] / atk-adaptor / accessible-register.c
1 /*
2  * AT-SPI - Assistive Technology Service Provider Interface
3  * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
4  *
5  * Copyright 2008 Novell, Inc.
6  * Copyright 2008, 2009 Codethink Ltd.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include "bridge.h"
29 #include "accessible-register.h"
30
31 /*
32  * This module is responsible for keeping track of all the AtkObjects in
33  * the application, so that they can be accessed remotely and placed in
34  * a client side cache.
35  *
36  * To access an AtkObject remotely we need to provide a D-Bus object 
37  * path for it. The D-Bus object paths used have a standard prefix
38  * (SPI_ATK_OBJECT_PATH_PREFIX). Appended to this prefix is a string
39  * representation of an integer reference. So to access an AtkObject 
40  * remotely we keep a Hashtable that maps the given reference to 
41  * the AtkObject pointer. An object in this hash table is said to be 'registered'.
42  *
43  * The architecture of AT-SPI dbus is such that AtkObjects are not
44  * remotely reference counted. This means that we need to keep track of
45  * object destruction. When an object is destroyed it must be 'deregistered'
46  * To do this lookup we keep a dbus-id attribute on each AtkObject.
47  *
48  * In addition to accessing the objects remotely we must be able to update
49  * the client side cache. This is done using the 'update' signal of the 
50  * org.freedesktop.atspi.Tree interface. The update signal should send out
51  * all of the cacheable data for an Accessible object.
52  *
53  */
54
55 /*
56  * FIXME
57  *
58  * This code seems very brittle.
59  * I would prefer changes to be made to
60  * gail and the ATK interface so that all Accessible
61  * objects are registered with an exporting module.
62  *
63  * This is the same system as Qt has with the QAccessibleBridge
64  * and QAccessibleBridgePlugin. It entails some rather
65  * large structural changes to ATK though:
66  *
67  * Removing infinite spaces (Child access no longer references child).
68  * Removing lazy creation of accessible objects.
69  */
70
71 #define SPI_ATK_OBJECT_PATH_PREFIX  "/org/freedesktop/atspi/accessible"
72 #define SPI_ATK_OBJECT_PATH_DESKTOP "/root"
73
74 #define SPI_ATK_PATH_PREFIX_LENGTH 33
75 #define SPI_ATK_OBJECT_REFERENCE_TEMPLATE SPI_ATK_OBJECT_PATH_PREFIX "/%d"
76
77
78 static GHashTable *ref2ptr = NULL; /* Used for converting a D-Bus path (Reference) to the object pointer */
79
80 static guint counter = 1;
81
82 static GStaticRecMutex registration_mutex = G_STATIC_REC_MUTEX_INIT;
83
84 /*---------------------------------------------------------------------------*/
85
86 static GStaticMutex   recursion_check_guard = G_STATIC_MUTEX_INIT;
87 static gboolean       recursion_check = FALSE;
88
89 static gboolean
90 recursion_check_and_set ()
91 {
92   gboolean ret;
93   g_static_mutex_lock   (&recursion_check_guard);
94   ret = recursion_check;
95   recursion_check = TRUE;
96   g_static_mutex_unlock (&recursion_check_guard);
97   return ret;
98 }
99
100 static void
101 recursion_check_unset ()
102 {
103   g_static_mutex_lock   (&recursion_check_guard);
104   recursion_check = FALSE;
105   g_static_mutex_unlock (&recursion_check_guard);
106 }
107
108 /*---------------------------------------------------------------------------*/
109
110 /*
111  * Each AtkObject must be asssigned a D-Bus path (Reference)
112  *
113  * This function provides an integer reference for a new
114  * AtkObject.
115  */
116 static guint
117 assign_reference(void)
118 {
119   counter++;
120   /* Reference of 0 not allowed as used as direct key in hash table */
121   if (counter == 0)
122     counter++;
123 }
124
125 /*
126  * Returns the reference of the object, or 0 if it is not registered.
127  */
128 static guint
129 object_to_ref (AtkObject *accessible)
130 {
131   return GPOINTER_TO_INT(g_object_get_data (G_OBJECT (accessible), "dbus-id"));
132 }
133
134 /*
135  * Converts the Accessible object reference to its D-Bus object path
136  */
137 static gchar *
138 ref_to_path (guint ref)
139 {
140   return g_strdup_printf(SPI_ATK_OBJECT_REFERENCE_TEMPLATE, ref);
141 }
142
143 /*---------------------------------------------------------------------------*/
144
145 /*
146  * Callback for when a registered AtkObject is destroyed.
147  *
148  * Removes the AtkObject from the reference lookup tables, meaning
149  * it is no longer exposed over D-Bus.
150  */
151 static void
152 deregister_accessible (gpointer data, GObject *accessible)
153 {
154   guint ref;
155   g_assert (ATK_IS_OBJECT (accessible));
156
157   ref = object_to_ref (ATK_OBJECT(accessible));
158   if (ref != 0)
159     {
160       g_hash_table_remove(ref2ptr, GINT_TO_POINTER(ref));
161     }
162 }
163
164 /*
165  * Called to register an AtkObject with AT-SPI and expose it over D-Bus.
166  */
167 static void
168 register_accessible (AtkObject *accessible)
169 {
170   guint ref;
171   g_assert(ATK_IS_OBJECT(accessible));
172
173   ref = assign_reference();
174
175   g_hash_table_insert (ref2ptr, GINT_TO_POINTER(ref), accessible);
176   g_object_set_data (G_OBJECT(accessible), "dbus-id", GINT_TO_POINTER(ref));
177   g_object_weak_ref(G_OBJECT(accessible), deregister_accessible, NULL);
178 }
179
180 /*---------------------------------------------------------------------------*/
181
182 #ifdef SPI_ATK_DEBUG
183 /*
184  * This function checks that the ref-count of an accessible
185  * is greater than 1.
186  *
187  * There is not currently any remote reference counting
188  * in AT-SPI D-Bus so objects that are remotely owned are not
189  * allowed.
190  *
191  * TODO Add debug wrapper
192  */
193 static gboolean
194 non_owned_accessible (AtkObject *accessible)
195 {
196    if ((G_OBJECT (accessible))->ref_count <= 1)
197      {
198        g_warning ("AT-SPI: Child referenced that is not owned by its parent");
199        return TRUE;
200      }
201    else
202      {
203        return FALSE;
204      }
205 }
206 #endif /* SPI_ATK_DEBUG */
207
208 /*---------------------------------------------------------------------------*/
209
210 static gboolean
211 has_manages_descendants (AtkObject *accessible)
212 {
213    AtkStateSet *state;
214    gboolean result = FALSE;
215
216    /* This is dangerous, refing the state set
217     * seems to do wierd things to the tree & cause recursion
218     * by modifying the tree alot.
219     */
220    state = atk_object_ref_state_set (accessible);
221    if (atk_state_set_contains_state (state, ATK_STATE_MANAGES_DESCENDANTS))
222      {
223 #ifdef SPI_ATK_DEBUG
224        g_warning ("AT-SPI: Object with 'Manages descendants' states not currently handled by AT-SPI");
225 #endif
226        result = TRUE;
227      }
228    g_object_unref (state);
229
230    return result;
231 }
232
233 /*
234  * Registers a subtree of accessible objects
235  * rooted at the accessible object provided.
236  *
237  * The leaf nodes do not have their children
238  * registered. A node is considered a leaf
239  * if it has the state "manages-descendants"
240  * or if it has already been registered.
241  */
242 void
243 register_subtree (AtkObject *accessible)
244 {
245   AtkObject *current, *tmp;
246   GQueue    *stack;
247   guint      i;
248   gboolean   recurse;
249
250
251   current = g_object_ref (accessible);
252   if (has_manages_descendants (current))
253     {
254       g_object_unref (current);
255       return;
256     }
257
258   stack = g_queue_new ();
259
260   register_accessible (current);
261   g_queue_push_head (stack, GINT_TO_POINTER (0));
262
263   /*
264    * The index held on the stack is the next child node
265    * that needs processing at the corresponding level in the tree.
266    */
267   while (!g_queue_is_empty (stack))
268     {
269       /* Find the next child node that needs processing */
270
271       i = GPOINTER_TO_INT(g_queue_peek_head (stack));
272       recurse = FALSE;
273
274       while (i < atk_object_get_n_accessible_children (current) &&
275              recurse == FALSE)
276         {
277           tmp = atk_object_ref_accessible_child (current, i);
278
279 #ifdef SPI_ATK_DEBUG
280           non_owned_accessible (tmp);
281 #endif
282
283           if (object_to_ref (tmp))
284             {
285               /* If its already registered, just update */
286               spi_emit_cache_update (tmp, atk_adaptor_app_data->bus);
287             }
288           else if (has_manages_descendants (tmp))
289             {
290               /* If it has manages descendants, just register and update */
291               register_accessible (tmp);
292               spi_emit_cache_update (tmp, atk_adaptor_app_data->bus);
293             }
294           else
295             {
296               recurse = TRUE;
297             }
298
299           if (!recurse)
300             {
301               g_object_unref (G_OBJECT (tmp));
302             }
303
304           i++;
305         }
306
307       if (recurse)
308         {
309           /* Push onto stack */
310           current = tmp;
311           register_accessible (current);
312
313           g_queue_peek_head_link (stack)->data = GINT_TO_POINTER (i);
314           g_queue_push_head (stack, GINT_TO_POINTER (0));
315         }
316       else
317         {
318           /* Pop from stack */
319           spi_emit_cache_update (current, atk_adaptor_app_data->bus);
320           tmp = current;
321           current = atk_object_get_parent (current);
322           g_object_unref (G_OBJECT (tmp));
323           g_queue_pop_head (stack);
324         }
325     }
326     g_queue_free (stack);
327 }
328
329 /*---------------------------------------------------------------------------*/
330
331 /*
332  * Called when an already registered object is updated in such a
333  * way that client side cache needs to be updated.
334  */
335 static void
336 update_accessible (AtkObject *accessible)
337 {
338   guint  ref = 0;
339   g_assert(ATK_IS_OBJECT(accessible));
340
341   ref = object_to_ref (accessible);
342   if (ref)
343     {
344       spi_emit_cache_update (accessible, atk_adaptor_app_data->bus);
345     }
346 }
347
348 /*---------------------------------------------------------------------------*/
349
350 void
351 atk_dbus_foreach_registered(GHFunc func, gpointer data)
352 {
353   g_hash_table_foreach(ref2ptr, func, data);
354 }
355
356 /*
357  * Used to lookup an AtkObject from its D-Bus path.
358  */
359 AtkObject *
360 atk_dbus_path_to_object (const char *path)
361 {
362   guint index;
363   void *data;
364
365   g_assert (path);
366
367   if (strncmp(path, SPI_ATK_OBJECT_PATH_PREFIX, SPI_ATK_PATH_PREFIX_LENGTH) != 0)
368     return NULL;
369
370   path += SPI_ATK_PATH_PREFIX_LENGTH; /* Skip over the prefix */
371
372   if (!g_strcmp0 (SPI_ATK_OBJECT_PATH_DESKTOP, path))
373      return atk_get_root();
374   if (path[0] != '/')
375      return NULL;
376
377   path++;
378   index = atoi (path);
379   data = g_hash_table_lookup (ref2ptr, GINT_TO_POINTER(index));
380   if (data)
381     return ATK_OBJECT (data);
382   else
383     return NULL;
384 }
385
386 /*
387  * Used to lookup a D-Bus path from the AtkObject.
388  */
389 gchar *
390 atk_dbus_object_to_path (AtkObject *accessible)
391 {
392   guint ref;
393
394   ref = object_to_ref (accessible);
395   if (!ref)
396       return NULL;
397   else
398       return ref_to_path (ref);
399 }
400
401 gchar *
402 atk_dbus_desktop_object_path ()
403 {
404   return g_strdup (SPI_ATK_OBJECT_PATH_PREFIX SPI_ATK_OBJECT_PATH_DESKTOP);
405 }
406
407 /*---------------------------------------------------------------------------*/
408
409 /*
410  * Events are not evaluated for non-registered accessibles.
411  *
412  * When a property change is made on a registered accessible
413  * the client side cache should be updated.
414  *
415  * When a parent is changed the subtree is de-registered
416  * if the parent is not attached to the root accessible.
417  */
418 static gboolean
419 tree_update_listener (GSignalInvocationHint *signal_hint,
420                       guint                  n_param_values,
421                       const GValue          *param_values,
422                       gpointer               data)
423 {
424   AtkObject *accessible;
425   AtkPropertyValues *values;
426   const gchar *pname = NULL;
427
428   g_static_rec_mutex_lock (&registration_mutex);
429
430   /* Ensure that only registered accessibles
431    * have their signals processed.
432    */
433   accessible = g_value_get_object (&param_values[0]);
434   g_assert (ATK_IS_OBJECT (accessible));
435
436   if (object_to_ref (accessible))
437     {
438 #ifdef SPI_ATK_DEBUG
439       if (recursion_check_and_set ())
440           g_warning ("AT-SPI: Recursive use of registration module");
441 #endif
442
443       values = (AtkPropertyValues*) g_value_get_pointer (&param_values[1]);
444       pname = values[0].property_name;
445       if (strcmp (pname, "accessible-name") == 0 ||
446           strcmp (pname, "accessible-description") == 0)
447         {
448           update_accessible (accessible);
449         }
450       /* Parent value us updated by child-add signal of parent object */
451
452       recursion_check_unset ();
453     }
454
455   g_static_rec_mutex_unlock (&registration_mutex);
456
457   return TRUE;
458 }
459
460 /*
461  * Events are not evaluated for non registered accessibles.
462  *
463  * When the children of a registered accessible are changed
464  * the subtree, rooted at the child is registered.
465  */
466 static gboolean
467 tree_update_children_listener (GSignalInvocationHint *signal_hint,
468                                guint                  n_param_values,
469                                const GValue          *param_values,
470                                gpointer               data)
471 {
472   AtkObject *accessible;
473   const gchar *detail = NULL;
474   AtkObject *child;
475
476   g_static_rec_mutex_lock (&registration_mutex);
477
478   /* Ensure that only registered accessibles
479    * have their signals processed.
480    */
481   accessible = g_value_get_object (&param_values[0]);
482   g_assert (ATK_IS_OBJECT (accessible));
483
484   if (object_to_ref (accessible))
485     {
486 #ifdef SPI_ATK_DEBUG
487       if (recursion_check_and_set ())
488           g_warning ("AT-SPI: Recursive use of registration module");
489 #endif
490
491       if (signal_hint->detail)
492           detail = g_quark_to_string (signal_hint->detail);
493
494       if (!strcmp (detail, "add"))
495         {
496           gpointer child;
497           int index = g_value_get_uint (param_values + 1);
498           child = g_value_get_pointer (param_values + 2);
499
500           if (!ATK_IS_OBJECT (child))
501             {
502               child = atk_object_ref_accessible_child (accessible, index);
503 #ifdef SPI_ATK_DEBUG
504               non_owned_accessible (child);
505 #endif
506             }
507           register_subtree (child);
508         }
509
510       recursion_check_unset ();
511     }
512
513   g_static_rec_mutex_unlock (&registration_mutex);
514
515   return TRUE;
516 }
517
518 /*
519  * Initializes required global data. The update and removal lists
520  * and the reference lookup tables.
521  *
522  * Initializes all of the required D-Bus interfaces.
523  */
524 void
525 atk_dbus_initialize (AtkObject *root)
526 {
527   if (!ref2ptr)
528     ref2ptr = g_hash_table_new(g_direct_hash, g_direct_equal);
529
530 #ifdef SPI_ATK_DEBUG
531   if (g_thread_supported ())
532       g_message ("AT-SPI: Threads enabled");
533 #endif
534
535   register_subtree (root);
536
537   atk_add_global_event_listener (tree_update_listener, "Gtk:AtkObject:property-change");
538   atk_add_global_event_listener (tree_update_children_listener, "Gtk:AtkObject:children-changed");
539 }
540
541 /*END------------------------------------------------------------------------*/
542