Merge branch 'master' into mgorse
[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 "tree-adaptor.h"
30 #include "accessible-register.h"
31
32 /*
33  * This module is responsible for keeping track of all the AtkObjects in
34  * the application, so that they can be accessed remotely and placed in
35  * a client side cache.
36  *
37  * To access an AtkObject remotely we need to provide a D-Bus object 
38  * path for it. The D-Bus object paths used have a standard prefix
39  * (SPI_ATK_OBJECT_PATH_PREFIX). Appended to this prefix is a string
40  * representation of an integer reference. So to access an AtkObject 
41  * remotely we keep a Hashtable that maps the given reference to 
42  * the AtkObject pointer. An object in this hash table is said to be 'registered'.
43  *
44  * The architecture of AT-SPI dbus is such that AtkObjects are not
45  * remotely reference counted. This means that we need to keep track of
46  * object destruction. When an object is destroyed it must be 'deregistered'
47  * To do this lookup we keep a dbus-id attribute on each AtkObject.
48  *
49  * In addition to accessing the objects remotely we must be able to update
50  * the client side cache. This is done using the 'update' signal of the 
51  * org.freedesktop.atspi.Tree interface. The update signal should send out
52  * all of the cacheable data for an Accessible object.
53  *
54  */
55
56 /*
57  * FIXME
58  *
59  * This code seems very brittle.
60  * I would prefer changes to be made to
61  * gail and the ATK interface so that all Accessible
62  * objects are registered with an exporting module.
63  *
64  * This is the same system as Qt has with the QAccessibleBridge
65  * and QAccessibleBridgePlugin. It entails some rather
66  * large structural changes to ATK though:
67  *
68  * Removing infinite spaces (Child access no longer references child).
69  * Removing lazy creation of accessible objects.
70  */
71
72 #define SPI_ATK_OBJECT_PATH_PREFIX  "/org/freedesktop/atspi/accessible"
73 #define SPI_ATK_OBJECT_PATH_DESKTOP "/desktop"
74
75 #define SPI_ATK_PATH_PREFIX_LENGTH 33
76 #define SPI_ATK_OBJECT_REFERENCE_TEMPLATE SPI_ATK_OBJECT_PATH_PREFIX "/%d"
77
78
79 static GHashTable *ref2ptr = NULL; /* Used for converting a D-Bus path (Reference) to the object pointer */
80 static GHashTable *objects_with_subrefs = NULL;
81 static GHashTable *leased_refs = NULL;
82 static int leased_refs_count;
83
84 static guint reference_counter = 0;
85
86 static GStaticRecMutex registration_mutex = G_STATIC_REC_MUTEX_INIT;
87
88 /*---------------------------------------------------------------------------*/
89
90 static GStaticMutex   recursion_check_guard = G_STATIC_MUTEX_INIT;
91 static gboolean       recursion_check = FALSE;
92 static int last_gc_time;
93
94 static void deregister_sub_accessible (gpointer key, gpointer obj_data, gpointer iter);
95
96 static void deregister_sub_hyperlink (gpointer key, gpointer obj_data, gpointer iter);
97
98 static gboolean
99 recursion_check_and_set ()
100 {
101   gboolean ret;
102   g_static_mutex_lock   (&recursion_check_guard);
103   ret = recursion_check;
104   recursion_check = TRUE;
105   g_static_mutex_unlock (&recursion_check_guard);
106   return ret;
107 }
108
109 static void
110 recursion_check_unset ()
111 {
112   g_static_mutex_lock   (&recursion_check_guard);
113   recursion_check = FALSE;
114   g_static_mutex_unlock (&recursion_check_guard);
115 }
116
117 /*---------------------------------------------------------------------------*/
118
119 /*
120  * Each AtkObject must be asssigned a D-Bus path (Reference)
121  *
122  * This function provides an integer reference for a new
123  * AtkObject.
124  */
125 static guint
126 assign_reference(void)
127 {
128   reference_counter++;
129   /* Reference of 0 not allowed as used as direct key in hash table */
130   if (reference_counter == 0)
131     reference_counter++;
132   /* TODO: If we've wrapped, ensure that two objects don't have the same ref */
133   return reference_counter;
134 }
135
136 /*
137  * Returns the reference of the object, or 0 if it is not registered.
138  */
139 static guint
140 gobject_to_ref (GObject *gobj)
141 {
142   return GPOINTER_TO_INT(g_object_get_data (gobj, "dbus-id"));
143 }
144
145 static guint
146 object_to_ref (AtkObject *accessible)
147 {
148   return gobject_to_ref (G_OBJECT (accessible));
149 }
150
151 static guint
152 hyperlink_to_ref (AtkHyperlink *link)
153 {
154   return gobject_to_ref (G_OBJECT (link));
155 }
156
157 /*
158  * Converts the Accessible object reference to its D-Bus object path
159  */
160 gchar *
161 atk_dbus_ref_to_path (guint ref)
162 {
163   return g_strdup_printf(SPI_ATK_OBJECT_REFERENCE_TEMPLATE, ref);
164 }
165
166 /*---------------------------------------------------------------------------*/
167
168 /*
169  * Callback for when a registered AtkObject is destroyed.
170  *
171  * Removes the AtkObject from the reference lookup tables, meaning
172  * it is no longer exposed over D-Bus.
173  */
174 static void
175 deregister_object (gpointer data, GObject *gobj)
176 {
177   guint ref;
178   GHashTable *subrefs_atk;
179   GHashTable *subrefs_hyperlink;
180   g_return_if_fail (ATK_IS_OBJECT (gobj) || ATK_IS_HYPERLINK (gobj));
181
182   subrefs_atk = (GHashTable *) g_object_get_data (gobj, "dbus-subrefs-atk");
183   subrefs_hyperlink = (GHashTable *) g_object_get_data (gobj, "dbus-subrefs-hyperlink");
184
185   if (subrefs_atk)
186   {
187     g_hash_table_foreach (subrefs_atk, deregister_sub_accessible, data);
188     g_hash_table_unref (subrefs_atk);
189   }
190   
191   if (subrefs_hyperlink)
192   {
193     g_hash_table_foreach (subrefs_hyperlink, deregister_sub_hyperlink, data);
194     g_hash_table_unref (subrefs_hyperlink);
195   }
196   
197   if (subrefs_atk || subrefs_hyperlink)
198     g_hash_table_remove (objects_with_subrefs, gobj);
199
200   if (ATK_IS_OBJECT (gobj))
201   {
202     ref = object_to_ref (ATK_OBJECT (gobj));
203     if (ref != 0)
204       {
205         spi_emit_cache_removal (ref, atk_adaptor_app_data->bus);
206         g_hash_table_remove(ref2ptr, GINT_TO_POINTER(ref));
207       }
208     }
209   }
210
211 static void
212 deregister_sub_accessible (gpointer key, gpointer obj_data, gpointer iter)
213 {
214   GObject *obj = G_OBJECT (obj_data);
215   deregister_object (NULL, obj);
216   g_hash_table_remove (leased_refs, obj);
217   g_object_unref (obj);
218 }
219
220 static void
221 deregister_sub_hyperlink (gpointer key, gpointer obj_data, gpointer iter)
222 {
223   guint ref;
224   GObject *ghyperlink = G_OBJECT (obj_data);
225
226   g_return_if_fail (ATK_IS_HYPERLINK (ghyperlink));
227
228   ref = gobject_to_ref (ghyperlink);
229   if (ref != 0)
230     {
231       g_hash_table_remove(ref2ptr, GINT_TO_POINTER(ref));
232     }
233   g_object_unref (ghyperlink);
234 }
235
236 static void
237 register_gobject (GObject *gobj, GObject *container)
238 {
239   guint ref;
240   g_return_if_fail (G_IS_OBJECT(gobj));
241
242   ref = assign_reference();
243
244   g_hash_table_insert (ref2ptr, GINT_TO_POINTER(ref), gobj);
245   g_object_set_data (G_OBJECT(gobj), "dbus-id", GINT_TO_POINTER(ref));
246   g_object_weak_ref(G_OBJECT(gobj), deregister_object, NULL);
247
248   if (container)
249   {
250     GHashTable *subrefs = (GHashTable *) g_object_get_data (G_OBJECT (container), "dbus-subrefs-atk");
251     if (!subrefs)
252     {
253       subrefs = g_hash_table_new(g_direct_hash, g_direct_equal);
254       g_object_set_data (G_OBJECT (container), "dbus-subrefs-atk", subrefs);
255     }
256     g_hash_table_insert (subrefs, GINT_TO_POINTER(ref), gobj);
257     g_hash_table_insert (objects_with_subrefs, gobj, subrefs);
258   }
259
260   if (ATK_IS_HYPERLINK (gobj))
261     g_object_ref (gobj);
262   else if (ATK_IS_OBJECT (gobj))
263   {
264     AtkObject *accessible = ATK_OBJECT (gobj);
265     AtkStateSet *state = atk_object_ref_state_set (accessible);
266     if (atk_state_set_contains_state (state, ATK_STATE_TRANSIENT))
267     {
268       g_object_ref (gobj);
269     }
270     g_object_unref (state);
271   }
272 }
273
274 /*
275  * Called to register an AtkObject with AT-SPI and expose it over D-Bus.
276  */
277 static void
278 register_accessible (AtkObject *accessible, AtkObject *container)
279 {
280   g_return_if_fail (ATK_IS_OBJECT(accessible));
281
282   register_gobject (G_OBJECT (accessible), G_OBJECT (container));
283 }
284
285 static void
286 register_hyperlink (AtkHyperlink *hyperlink, AtkObject *container)
287 {
288   guint ref;
289   g_return_if_fail (ATK_IS_HYPERLINK (hyperlink));
290   g_return_if_fail (container);
291
292   ref = assign_reference();
293
294   g_hash_table_insert (ref2ptr, GINT_TO_POINTER(ref), hyperlink);
295   g_object_set_data (G_OBJECT(hyperlink), "dbus-id", GINT_TO_POINTER(ref));
296   g_object_ref (G_OBJECT (hyperlink));
297
298   GHashTable *subrefs = (GHashTable *) g_object_get_data (G_OBJECT (container), "dbus-subrefs-hyperlink");
299   if (!subrefs)
300   {
301     subrefs = g_hash_table_new(g_direct_hash, g_direct_equal);
302     g_object_set_data (G_OBJECT (container), "dbus-subrefs-hyperlink", GINT_TO_POINTER(ref));
303   }
304   g_hash_table_insert (subrefs, GINT_TO_POINTER(ref), hyperlink);
305 }
306
307 /*---------------------------------------------------------------------------*/
308
309 #ifdef SPI_ATK_DEBUG
310 /*
311  * This function checks that the ref-count of an accessible
312  * is greater than 1.
313  *
314  * There is not currently any remote reference counting
315  * in AT-SPI D-Bus so objects that are remotely owned are not
316  * allowed.
317  *
318  * TODO Add debug wrapper
319  */
320 static gboolean
321 non_owned_accessible (AtkObject *accessible)
322 {
323    if ((G_OBJECT (accessible))->ref_count <= 1)
324      {
325        g_warning ("AT-SPI: Child referenced that is not owned by its parent");
326        return TRUE;
327      }
328    else
329      {
330        return FALSE;
331      }
332 }
333 #endif /* SPI_ATK_DEBUG */
334
335 /*---------------------------------------------------------------------------*/
336
337 /* TRUE if we should not keep this object / tell the AT about it
338  * Currently true if TRANSIENT and not SHOWING
339  */
340 static gboolean
341 object_is_moot (AtkObject *accessible)
342 {
343    AtkStateSet *state;
344    gboolean result = FALSE;
345
346    /* This is dangerous, refing the state set
347     * seems to do wierd things to the tree & cause recursion
348     * by modifying the tree alot.
349     */
350    state = atk_object_ref_state_set (accessible);
351    if ( atk_state_set_contains_state (state, ATK_STATE_TRANSIENT) &&
352        !atk_state_set_contains_state (state, ATK_STATE_SHOWING))
353      {
354        result = TRUE;
355      }
356    g_object_unref (state);
357
358    return result;
359 }
360
361 static void
362 append_children (AtkObject *accessible, GQueue *traversal)
363 {
364   AtkObject *current;
365   guint i;
366   gint count = atk_object_get_n_accessible_children (accessible);
367
368   if (count < 0) count = 0;
369   for (i =0; i < count; i++)
370     {
371       current = atk_object_ref_accessible_child (accessible, i);
372       if (current)
373         {
374 #ifdef SPI_ATK_DEBUG
375           non_owned_accessible (current);
376 #endif
377           g_queue_push_tail (traversal, current);
378         }
379     }
380 }
381
382 /*
383  * Registers a subtree of accessible objects
384  * rooted at the accessible object provided.
385  *
386  * The leaf nodes do not have their children
387  * registered. A node is considered a leaf
388  * if it has the state "manages-descendants"
389  * or if it has already been registered.
390  */
391 void
392 register_subtree (AtkObject *accessible)
393 {
394   AtkObject *current;
395   GQueue    *traversal;
396   GQueue    *emit_update;
397
398   g_return_if_fail (ATK_IS_OBJECT (accessible));
399
400   traversal = g_queue_new ();
401   emit_update = g_queue_new ();
402
403   g_object_ref (accessible);
404   g_queue_push_tail (traversal, accessible);
405
406   while (!g_queue_is_empty (traversal))
407     {
408       current = g_queue_pop_head (traversal);
409       g_queue_push_tail (emit_update, current);
410       if (!object_to_ref (current))
411         {
412           register_accessible (current, NULL);
413 #ifdef SPI_ATK_DEBUG
414           g_debug ("REG  - %s - %d - %s", atk_object_get_name     (current),
415                                           atk_object_get_role     (current),
416                                           atk_dbus_object_to_path (current));
417 #endif
418           append_children (current, traversal);
419         }
420     }
421
422   while (!g_queue_is_empty (emit_update))
423     {
424       current = g_queue_pop_head (emit_update);
425       spi_emit_cache_update (current, atk_adaptor_app_data->bus);
426       g_object_unref (G_OBJECT (current));
427     }
428
429   g_queue_free (traversal);
430   g_queue_free (emit_update);
431 }
432
433 /*---------------------------------------------------------------------------*/
434
435 /*
436  * Called when an already registered object is updated in such a
437  * way that client side cache needs to be updated.
438  */
439 static void
440 update_accessible (AtkObject *accessible)
441 {
442   guint  ref = 0;
443   g_return_if_fail (ATK_IS_OBJECT(accessible));
444
445   ref = object_to_ref (accessible);
446   if (ref)
447     {
448       spi_emit_cache_update (accessible, atk_adaptor_app_data->bus);
449     }
450 }
451
452 /*---------------------------------------------------------------------------*/
453
454 void
455 atk_dbus_foreach_registered(GHFunc func, gpointer data)
456 {
457   g_hash_table_foreach(ref2ptr, func, data);
458 }
459
460 /*
461  * Used to lookup an AtkObject from its D-Bus path.
462  */
463 GObject *
464 atk_dbus_path_to_gobject (const char *path)
465 {
466   guint index;
467   void *data;
468
469   g_return_val_if_fail (path, NULL);
470
471   if (strncmp(path, SPI_ATK_OBJECT_PATH_PREFIX, SPI_ATK_PATH_PREFIX_LENGTH) != 0)
472     return NULL;
473
474   path += SPI_ATK_PATH_PREFIX_LENGTH; /* Skip over the prefix */
475
476   if (!g_strcmp0 (SPI_ATK_OBJECT_PATH_DESKTOP, path))
477      return G_OBJECT (atk_get_root());
478   if (path[0] != '/')
479      return NULL;
480
481   path++;
482   index = atoi (path);
483   data = g_hash_table_lookup (ref2ptr, GINT_TO_POINTER(index));
484   if (data)
485   {
486     GObject *gobj = G_OBJECT (data);
487     g_object_set_data (gobj, "last-ref-time", (gpointer) time (NULL));
488     return gobj;
489   }
490   else
491     return NULL;
492 }
493
494 AtkObject *
495 atk_dbus_path_to_object (const char *path)
496 {
497   return ATK_OBJECT (atk_dbus_path_to_gobject (path));
498 }
499
500 /*
501  * TODO WARNING HACK This function is dangerous.
502  * It should only be called before sending an event on an
503  * object that has not already been registered.
504  */
505 gchar *
506 atk_dbus_object_attempt_registration (AtkObject *accessible)
507 {
508   guint ref;
509
510   ref = object_to_ref (accessible);
511   if (!ref)
512     {
513       /* See if the object is attached to the main tree */
514       AtkObject *current, *prev = NULL;
515       guint cref = 0;
516
517       /* This should iterate until it hits a NULL or registered parent */
518       prev = accessible;
519       current = atk_object_get_parent (accessible);
520       if (current)
521           cref = object_to_ref (current);
522       while (current && !cref)
523         {
524           prev = current;
525           current = atk_object_get_parent (current);
526           if (current)
527               cref = object_to_ref (current);
528         }
529
530       /* A registered parent, with non-registered child, has been found */
531       if (current)
532         {
533           register_subtree (prev);
534         }
535
536       /* The object SHOULD be registered now. If it isn't - I give up */
537       ref = object_to_ref (accessible);
538       if (ref)
539         {
540           return atk_dbus_ref_to_path (ref);
541         }
542       else
543         {
544 #ifdef SPI_ATK_DEBUG
545           g_debug ("AT-SPI: Could not register a non-attached accessible object");
546 #endif
547           return NULL;
548         }
549     }
550   else
551     {
552       return atk_dbus_ref_to_path (ref);
553     }
554 }
555
556
557 /*
558  * Used to lookup a D-Bus path from the AtkObject.
559  */
560 static gchar *
561 atk_dbus_gobject_to_path_internal (GObject *gobj, gboolean do_register, GObject *container)
562 {
563   guint ref;
564
565   ref = gobject_to_ref (gobj);
566   if (!ref && do_register)
567   {
568     register_gobject (gobj, container);
569     ref = gobject_to_ref (gobj);
570   }
571
572   if (!ref)
573       return NULL;
574   else
575       return atk_dbus_ref_to_path (ref);
576 }
577
578 gchar *
579 atk_dbus_object_to_path (AtkObject *accessible, gboolean do_register)
580 {
581   AtkObject *container = (accessible && do_register? atk_object_get_parent (accessible): NULL);
582   return atk_dbus_gobject_to_path_internal (G_OBJECT (accessible), do_register, G_OBJECT (container));
583 }
584
585 gchar *
586 atk_dbus_sub_object_to_path (GObject *gobj, GObject *container)
587 {
588   return atk_dbus_gobject_to_path_internal (gobj, TRUE, container);
589 }
590
591 gchar *
592 atk_dbus_hyperlink_to_path (AtkHyperlink *hyperlink, AtkObject *container)
593 {
594   guint ref;
595
596   ref = gobject_to_ref (G_OBJECT (hyperlink));
597   if (!ref && container)
598   {
599     register_hyperlink (hyperlink, container);
600     ref = hyperlink_to_ref (hyperlink);
601   }
602
603   if (!ref)
604       return NULL;
605   else
606       return atk_dbus_ref_to_path (ref);
607 }
608
609 gchar *
610 atk_dbus_desktop_object_path ()
611 {
612   return g_strdup (SPI_ATK_OBJECT_PATH_PREFIX SPI_ATK_OBJECT_PATH_DESKTOP);
613 }
614
615 /*---------------------------------------------------------------------------*/
616
617 typedef gboolean (*TreeUpdateAction) (GSignalInvocationHint *signal_hint,
618                                       guint                  n_param_values,
619                                       const GValue          *param_values,
620                                       gpointer               data,
621                                       AtkObject             *accessible);
622
623 /*
624  * Events are not evaluated for non-registered accessibles.
625  *
626  * When a property change is made on a registered accessible
627  * the client side cache should be updated.
628  *
629  * When a parent is changed the subtree is de-registered
630  * if the parent is not attached to the root accessible.
631  */
632 /* TODO Turn this function into a macro? */
633 static gboolean
634 tree_update_wrapper (GSignalInvocationHint *signal_hint,
635                      guint                  n_param_values,
636                      const GValue          *param_values,
637                      gpointer               data,
638                      TreeUpdateAction       action)
639 {
640   AtkObject *accessible;
641
642   g_static_rec_mutex_lock (&registration_mutex);
643
644   /* Ensure that only registered accessibles
645    * have their signals processed.
646    */
647   accessible = ATK_OBJECT(g_value_get_object (&param_values[0]));
648   g_return_val_if_fail (ATK_IS_OBJECT (accessible), TRUE);
649
650   if (object_to_ref (accessible))
651     {
652 #ifdef SPI_ATK_DEBUG
653       if (recursion_check_and_set ())
654           g_warning ("AT-SPI: Recursive use of registration module");
655
656       g_debug ("AT-SPI: Tree update listener");
657 #endif
658       action (signal_hint, n_param_values, param_values, data, accessible);
659
660       recursion_check_unset ();
661     }
662
663   g_static_rec_mutex_unlock (&registration_mutex);
664
665   return TRUE;
666 }
667
668 static gboolean
669 maybe_expire_lease (gpointer key, gpointer obj_data, gpointer iter)
670 {
671   time_t secs = time (NULL) - (time_t)obj_data;
672
673   if (secs < 30)
674     return FALSE;
675   deregister_sub_accessible (key, obj_data, iter);
676   return TRUE;
677 }
678
679 static void
680 expire_old_leases_in (gpointer key, gpointer obj_data, gpointer iter)
681 {
682   g_hash_table_foreach_remove ((GHashTable *)obj_data, maybe_expire_lease, NULL);
683 }
684
685 static void
686 expire_old_leases ()
687 {
688   g_hash_table_foreach (objects_with_subrefs, expire_old_leases_in, NULL);
689 }
690
691 static gboolean
692 tree_update_state_action (GSignalInvocationHint *signal_hint,
693                           guint                  n_param_values,
694                           const GValue          *param_values,
695                           gpointer               data,
696                           AtkObject             *accessible)
697 {
698   const gchar *name;
699   gboolean state;
700
701   if (n_param_values < 3)
702   {
703     g_warning ("at-spi: Not enough params in state-changed signal");
704                                 return TRUE;
705   }
706
707   name = g_value_get_string (param_values + 1);
708   state = g_value_get_boolean (param_values + 2);
709   if (!strcmp (name, "visible"))
710   {
711     AtkStateSet *set = atk_object_ref_state_set (accessible);
712     if (atk_state_set_contains_state (set, ATK_STATE_TRANSIENT))
713     {
714       if (state == 0)
715       {
716         g_hash_table_insert (leased_refs, accessible, (gpointer) time (NULL));
717         leased_refs_count++;
718         /* todo: Set to a high number: 5 for dbg. */
719         if (leased_refs_count > 5)
720           expire_old_leases ();
721       }
722       else
723       {
724         g_hash_table_remove (leased_refs, accessible);
725         leased_refs_count--;
726       }
727     }
728     g_object_unref (set);
729   }
730
731       update_accessible (accessible);
732   return TRUE;
733 }
734
735 static gboolean
736 tree_update_state_listener (GSignalInvocationHint *signal_hint,
737                             guint                  n_param_values,
738                             const GValue          *param_values,
739                             gpointer               data)
740 {
741       tree_update_wrapper (signal_hint, n_param_values, param_values, data, tree_update_state_action);
742   return TRUE;
743 }
744
745 static gboolean
746 tree_update_property_action (GSignalInvocationHint *signal_hint,
747                              guint                  n_param_values,
748                              const GValue          *param_values,
749                              gpointer               data,
750                              AtkObject             *accessible)
751 {
752       AtkPropertyValues *values;
753       const gchar *pname = NULL;
754
755       values = (AtkPropertyValues*) g_value_get_pointer (&param_values[1]);
756       pname = values[0].property_name;
757       if (strcmp (pname, "accessible-name") == 0 ||
758           strcmp (pname, "accessible-description") == 0 ||
759           strcmp (pname, "accessible-parent") == 0 ||
760           strcmp (pname, "accessible-role") == 0)
761         {
762           update_accessible (accessible);
763         }
764       /* Parent value us updated by child-add signal of parent object */
765       return TRUE;
766 }
767
768 static gboolean
769 tree_update_property_listener (GSignalInvocationHint *signal_hint,
770                                guint                  n_param_values,
771                                const GValue          *param_values,
772                                gpointer               data)
773 {
774   tree_update_wrapper (signal_hint, n_param_values, param_values, data, tree_update_property_action);
775   return TRUE;
776 }
777
778 static gboolean
779 tree_update_children_action (GSignalInvocationHint *signal_hint,
780                              guint                  n_param_values,
781                              const GValue          *param_values,
782                              gpointer               data,
783                              AtkObject             *accessible)
784 {
785       const gchar *detail = NULL;
786       AtkObject *child;
787
788       if (signal_hint->detail)
789           detail = g_quark_to_string (signal_hint->detail);
790
791       if (!strcmp (detail, "add"))
792         {
793           gpointer child;
794           int index = g_value_get_uint (param_values + 1);
795           child = g_value_get_pointer (param_values + 2);
796
797           if (!ATK_IS_OBJECT (child))
798             {
799               child = atk_object_ref_accessible_child (accessible, index);
800 #ifdef SPI_ATK_DEBUG
801               non_owned_accessible (child);
802 #endif
803             }
804           register_subtree (child);
805           update_accessible (accessible);
806         }
807       return TRUE;
808 }
809
810 static gboolean
811 tree_update_children_listener (GSignalInvocationHint *signal_hint,
812                                guint                  n_param_values,
813                                const GValue          *param_values,
814                                gpointer               data)
815 {
816       tree_update_wrapper (signal_hint, n_param_values, param_values, data, tree_update_children_action);
817       return TRUE;
818 }
819
820 /*---------------------------------------------------------------------------*/
821
822 static void
823 spi_atk_register_toplevel_added (AtkObject *accessible,
824                                  guint     index,
825                                  AtkObject *child)
826 {
827   g_static_rec_mutex_lock (&registration_mutex);
828
829   g_return_if_fail (ATK_IS_OBJECT (accessible));
830
831   if (object_to_ref (accessible))
832     {
833 #ifdef SPI_ATK_DEBUG
834       if (recursion_check_and_set ())
835           g_warning ("AT-SPI: Recursive use of registration module");
836
837       g_debug ("AT-SPI: Toplevel added listener");
838 #endif
839       if (!ATK_IS_OBJECT (child))
840         {
841           child = atk_object_ref_accessible_child (accessible, index);
842 #ifdef SPI_ATK_DEBUG
843           non_owned_accessible (child);
844 #endif
845         }
846       register_subtree (child);
847       update_accessible (accessible);
848
849       recursion_check_unset ();
850     }
851
852   g_static_rec_mutex_unlock (&registration_mutex);
853 }
854
855 static void
856 spi_atk_register_toplevel_removed (AtkObject *accessible,
857                                    guint     index,
858                                    AtkObject *child)
859 {
860   g_static_rec_mutex_lock (&registration_mutex);
861
862   g_return_if_fail (ATK_IS_OBJECT (accessible));
863
864   if (object_to_ref (accessible))
865     {
866 #ifdef SPI_ATK_DEBUG
867       if (recursion_check_and_set ())
868           g_warning ("AT-SPI: Recursive use of registration module");
869
870       g_debug ("AT-SPI: Toplevel removed listener");
871 #endif
872       update_accessible (accessible);
873       recursion_check_unset ();
874     }
875
876   g_static_rec_mutex_unlock (&registration_mutex);
877 }
878
879 /*
880  * Initializes required global data. The update and removal lists
881  * and the reference lookup tables.
882  *
883  * Initializes all of the required D-Bus interfaces.
884  */
885 void
886 atk_dbus_initialize (AtkObject *root)
887 {
888   if (!ref2ptr)
889     ref2ptr = g_hash_table_new(g_direct_hash, g_direct_equal);
890
891   if (!objects_with_subrefs)
892     objects_with_subrefs = g_hash_table_new(g_direct_hash, g_direct_equal);
893
894   if (!leased_refs)
895     leased_refs = g_hash_table_new(g_direct_hash, g_direct_equal);
896
897 #ifdef SPI_ATK_DEBUG
898   if (g_thread_supported ())
899       g_message ("AT-SPI: Threads enabled");
900
901   g_debug ("AT-SPI: Initial Atk tree regisration");
902 #endif
903
904   register_subtree (root);
905
906   atk_add_global_event_listener (tree_update_property_listener, "Gtk:AtkObject:property-change");
907   atk_add_global_event_listener (tree_update_children_listener, "Gtk:AtkObject:children-changed");
908   atk_add_global_event_listener (tree_update_state_listener, "Gtk:AtkObject:state-change");
909
910   g_signal_connect (root,
911                     "children-changed::add",
912                     (GCallback) spi_atk_register_toplevel_added,
913                     NULL);
914   g_signal_connect (root,
915                     "children-changed::remove",
916                     (GCallback) spi_atk_register_toplevel_removed,
917                     NULL);
918 }
919
920 /*END------------------------------------------------------------------------*/