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