2009-08-12 Mark Doffman <mark.doffman@codethink.co.uk>
[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 reference_counter = 0;
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   reference_counter++;
120   /* Reference of 0 not allowed as used as direct key in hash table */
121   if (reference_counter == 0)
122     reference_counter++;
123   return reference_counter;
124 }
125
126 /*
127  * Returns the reference of the object, or 0 if it is not registered.
128  */
129 static guint
130 object_to_ref (AtkObject *accessible)
131 {
132   return GPOINTER_TO_INT(g_object_get_data (G_OBJECT (accessible), "dbus-id"));
133 }
134
135 /*
136  * Converts the Accessible object reference to its D-Bus object path
137  */
138 gchar *
139 atk_dbus_ref_to_path (guint ref)
140 {
141   return g_strdup_printf(SPI_ATK_OBJECT_REFERENCE_TEMPLATE, ref);
142 }
143
144 /*---------------------------------------------------------------------------*/
145
146 /*
147  * Callback for when a registered AtkObject is destroyed.
148  *
149  * Removes the AtkObject from the reference lookup tables, meaning
150  * it is no longer exposed over D-Bus.
151  */
152 static void
153 deregister_accessible (gpointer data, GObject *accessible)
154 {
155   guint ref;
156   g_return_if_fail (ATK_IS_OBJECT (accessible));
157
158   ref = object_to_ref (ATK_OBJECT(accessible));
159   if (ref != 0)
160     {
161       spi_emit_cache_removal (ref, atk_adaptor_app_data->bus);
162       g_hash_table_remove(ref2ptr, GINT_TO_POINTER(ref));
163     }
164 }
165
166 /*
167  * Called to register an AtkObject with AT-SPI and expose it over D-Bus.
168  */
169 static void
170 register_accessible (AtkObject *accessible)
171 {
172   guint ref;
173   g_return_if_fail (ATK_IS_OBJECT(accessible));
174
175   ref = assign_reference();
176
177   g_hash_table_insert (ref2ptr, GINT_TO_POINTER(ref), accessible);
178   g_object_set_data (G_OBJECT(accessible), "dbus-id", GINT_TO_POINTER(ref));
179   g_object_weak_ref(G_OBJECT(accessible), deregister_accessible, NULL);
180 }
181
182 /*---------------------------------------------------------------------------*/
183
184 #ifdef SPI_ATK_DEBUG
185 /*
186  * This function checks that the ref-count of an accessible
187  * is greater than 1.
188  *
189  * There is not currently any remote reference counting
190  * in AT-SPI D-Bus so objects that are remotely owned are not
191  * allowed.
192  *
193  * TODO Add debug wrapper
194  */
195 static gboolean
196 non_owned_accessible (AtkObject *accessible)
197 {
198    if ((G_OBJECT (accessible))->ref_count <= 1)
199      {
200        g_warning ("AT-SPI: Child referenced that is not owned by its parent");
201        return TRUE;
202      }
203    else
204      {
205        return FALSE;
206      }
207 }
208 #endif /* SPI_ATK_DEBUG */
209
210 /*---------------------------------------------------------------------------*/
211
212 static gboolean
213 has_manages_descendants (AtkObject *accessible)
214 {
215    AtkStateSet *state;
216    gboolean result = FALSE;
217
218    /* This is dangerous, refing the state set
219     * seems to do wierd things to the tree & cause recursion
220     * by modifying the tree alot.
221     */
222    state = atk_object_ref_state_set (accessible);
223    if (atk_state_set_contains_state (state, ATK_STATE_MANAGES_DESCENDANTS))
224      {
225 #ifdef SPI_ATK_DEBUG
226        g_warning ("AT-SPI: Object with 'Manages descendants' states not currently handled by AT-SPI");
227 #endif
228        result = TRUE;
229      }
230    g_object_unref (state);
231
232    return result;
233 }
234
235 static void
236 append_children (AtkObject *accessible, GQueue *traversal)
237 {
238   AtkObject *current;
239   guint i;
240   gint count = atk_object_get_n_accessible_children (accessible);
241
242   if (count < 0) count = 0;
243   for (i =0; i < count; i++)
244     {
245       current = atk_object_ref_accessible_child (accessible, i);
246       if (current)
247         {
248 #ifdef SPI_ATK_DEBUG
249           non_owned_accessible (current);
250 #endif
251           if (!has_manages_descendants (current))
252               g_queue_push_tail (traversal, current);
253           else
254               g_object_unref (G_OBJECT (current));
255         }
256     }
257 }
258
259 /*
260  * Registers a subtree of accessible objects
261  * rooted at the accessible object provided.
262  *
263  * The leaf nodes do not have their children
264  * registered. A node is considered a leaf
265  * if it has the state "manages-descendants"
266  * or if it has already been registered.
267  */
268 void
269 register_subtree (AtkObject *accessible)
270 {
271   AtkObject *current;
272   GQueue    *traversal;
273   GQueue    *emit_update;
274
275   g_return_if_fail (ATK_IS_OBJECT (accessible));
276
277   traversal = g_queue_new ();
278   emit_update = g_queue_new ();
279
280   g_object_ref (accessible);
281   g_queue_push_tail (traversal, accessible);
282
283   while (!g_queue_is_empty (traversal))
284     {
285       current = g_queue_pop_head (traversal);
286       g_queue_push_tail (emit_update, current);
287       if (!object_to_ref (current))
288         {
289           register_accessible (current);
290 #ifdef SPI_ATK_DEBUG
291           g_debug ("REG  - %s - %d - %s", atk_object_get_name     (current),
292                                           atk_object_get_role     (current),
293                                           atk_dbus_object_to_path (current));
294 #endif
295           append_children (current, traversal);
296         }
297     }
298
299   while (!g_queue_is_empty (emit_update))
300     {
301       current = g_queue_pop_head (emit_update);
302       spi_emit_cache_update (current, atk_adaptor_app_data->bus);
303       g_object_unref (G_OBJECT (current));
304     }
305
306   g_queue_free (traversal);
307   g_queue_free (emit_update);
308 }
309
310 /*---------------------------------------------------------------------------*/
311
312 /*
313  * Called when an already registered object is updated in such a
314  * way that client side cache needs to be updated.
315  */
316 static void
317 update_accessible (AtkObject *accessible)
318 {
319   guint  ref = 0;
320   g_return_if_fail (ATK_IS_OBJECT(accessible));
321
322   ref = object_to_ref (accessible);
323   if (ref)
324     {
325       spi_emit_cache_update (accessible, atk_adaptor_app_data->bus);
326     }
327 }
328
329 /*---------------------------------------------------------------------------*/
330
331 void
332 atk_dbus_foreach_registered(GHFunc func, gpointer data)
333 {
334   g_hash_table_foreach(ref2ptr, func, data);
335 }
336
337 /*
338  * Used to lookup an AtkObject from its D-Bus path.
339  */
340 AtkObject *
341 atk_dbus_path_to_object (const char *path)
342 {
343   guint index;
344   void *data;
345
346   g_return_val_if_fail (path, NULL);
347
348   if (strncmp(path, SPI_ATK_OBJECT_PATH_PREFIX, SPI_ATK_PATH_PREFIX_LENGTH) != 0)
349     return NULL;
350
351   path += SPI_ATK_PATH_PREFIX_LENGTH; /* Skip over the prefix */
352
353   if (!g_strcmp0 (SPI_ATK_OBJECT_PATH_DESKTOP, path))
354      return atk_get_root();
355   if (path[0] != '/')
356      return NULL;
357
358   path++;
359   index = atoi (path);
360   data = g_hash_table_lookup (ref2ptr, GINT_TO_POINTER(index));
361   if (data)
362     return ATK_OBJECT (data);
363   else
364     return NULL;
365 }
366
367 /*
368  * TODO WARNING HACK This function is dangerous.
369  * It should only be called before sending an event on an
370  * object that has not already been registered.
371  */
372 gchar *
373 atk_dbus_object_attempt_registration (AtkObject *accessible)
374 {
375   guint ref;
376
377   ref = object_to_ref (accessible);
378   if (!ref)
379     {
380       /* See if the object is attached to the main tree */
381       AtkObject *current, *prev = NULL;
382       guint cref = 0;
383
384       /* This should iterate until it hits a NULL or registered parent */
385       prev = accessible;
386       current = atk_object_get_parent (accessible);
387       if (current)
388           cref = object_to_ref (current);
389       while (current && !cref)
390         {
391           prev = current;
392           current = atk_object_get_parent (current);
393           if (current)
394               cref = object_to_ref (current);
395         }
396
397       /* A registered parent, with non-registered child, has been found */
398       if (current)
399         {
400           register_subtree (prev);
401         }
402
403       /* The object SHOULD be registered now. If it isn't - I give up */
404       ref = object_to_ref (accessible);
405       if (ref)
406         {
407           return atk_dbus_ref_to_path (ref);
408         }
409       else
410         {
411 #ifdef SPI_ATK_DEBUG
412           g_debug ("AT-SPI: Could not register a non-attached accessible object");
413 #endif
414           return NULL;
415         }
416     }
417   else
418     {
419       return atk_dbus_ref_to_path (ref);
420     }
421 }
422
423
424 /*
425  * Used to lookup a D-Bus path from the AtkObject.
426  */
427 gchar *
428 atk_dbus_object_to_path (AtkObject *accessible)
429 {
430   guint ref;
431
432   ref = object_to_ref (accessible);
433   if (!ref)
434       return NULL;
435   else
436       return atk_dbus_ref_to_path (ref);
437 }
438
439 gchar *
440 atk_dbus_desktop_object_path ()
441 {
442   return g_strdup (SPI_ATK_OBJECT_PATH_PREFIX SPI_ATK_OBJECT_PATH_DESKTOP);
443 }
444
445 /*---------------------------------------------------------------------------*/
446
447 typedef gboolean (*TreeUpdateAction) (GSignalInvocationHint *signal_hint,
448                                       guint                  n_param_values,
449                                       const GValue          *param_values,
450                                       gpointer               data,
451                                       AtkObject             *accessible);
452
453 /*
454  * Events are not evaluated for non-registered accessibles.
455  *
456  * When a property change is made on a registered accessible
457  * the client side cache should be updated.
458  *
459  * When a parent is changed the subtree is de-registered
460  * if the parent is not attached to the root accessible.
461  */
462 /* TODO Turn this function into a macro? */
463 static gboolean
464 tree_update_wrapper (GSignalInvocationHint *signal_hint,
465                      guint                  n_param_values,
466                      const GValue          *param_values,
467                      gpointer               data,
468                      TreeUpdateAction       action)
469 {
470   AtkObject *accessible;
471
472   g_static_rec_mutex_lock (&registration_mutex);
473
474   /* Ensure that only registered accessibles
475    * have their signals processed.
476    */
477   accessible = ATK_OBJECT(g_value_get_object (&param_values[0]));
478   g_return_val_if_fail (ATK_IS_OBJECT (accessible), TRUE);
479
480   if (object_to_ref (accessible))
481     {
482 #ifdef SPI_ATK_DEBUG
483       if (recursion_check_and_set ())
484           g_warning ("AT-SPI: Recursive use of registration module");
485
486       g_debug ("AT-SPI: Tree update listener");
487 #endif
488       action (signal_hint, n_param_values, param_values, data, accessible);
489
490       recursion_check_unset ();
491     }
492
493   g_static_rec_mutex_unlock (&registration_mutex);
494
495   return TRUE;
496 }
497
498 static gboolean
499 tree_update_state_action (GSignalInvocationHint *signal_hint,
500                           guint                  n_param_values,
501                           const GValue          *param_values,
502                           gpointer               data,
503                           AtkObject             *accessible)
504 {
505       update_accessible (accessible);
506   return TRUE;
507 }
508
509 static gboolean
510 tree_update_state_listener (GSignalInvocationHint *signal_hint,
511                             guint                  n_param_values,
512                             const GValue          *param_values,
513                             gpointer               data)
514 {
515       tree_update_wrapper (signal_hint, n_param_values, param_values, data, tree_update_state_action);
516   return TRUE;
517 }
518
519 static gboolean
520 tree_update_property_action (GSignalInvocationHint *signal_hint,
521                              guint                  n_param_values,
522                              const GValue          *param_values,
523                              gpointer               data,
524                              AtkObject             *accessible)
525 {
526       AtkPropertyValues *values;
527       const gchar *pname = NULL;
528
529       values = (AtkPropertyValues*) g_value_get_pointer (&param_values[1]);
530       pname = values[0].property_name;
531       if (strcmp (pname, "accessible-name") == 0 ||
532           strcmp (pname, "accessible-description") == 0 ||
533           strcmp (pname, "accessible-parent") == 0)
534         {
535           update_accessible (accessible);
536         }
537       /* Parent value us updated by child-add signal of parent object */
538       return TRUE;
539 }
540
541 static gboolean
542 tree_update_property_listener (GSignalInvocationHint *signal_hint,
543                                guint                  n_param_values,
544                                const GValue          *param_values,
545                                gpointer               data)
546 {
547   tree_update_wrapper (signal_hint, n_param_values, param_values, data, tree_update_property_action);
548   return TRUE;
549 }
550
551 static gboolean
552 tree_update_children_action (GSignalInvocationHint *signal_hint,
553                              guint                  n_param_values,
554                              const GValue          *param_values,
555                              gpointer               data,
556                              AtkObject             *accessible)
557 {
558       const gchar *detail = NULL;
559       AtkObject *child;
560
561       if (has_manages_descendants (accessible)) return TRUE;
562       if (signal_hint->detail)
563           detail = g_quark_to_string (signal_hint->detail);
564
565       if (!strcmp (detail, "add"))
566         {
567           gpointer child;
568           int index = g_value_get_uint (param_values + 1);
569           child = g_value_get_pointer (param_values + 2);
570
571           if (!ATK_IS_OBJECT (child))
572             {
573               child = atk_object_ref_accessible_child (accessible, index);
574 #ifdef SPI_ATK_DEBUG
575               non_owned_accessible (child);
576 #endif
577             }
578           register_subtree (child);
579           update_accessible (accessible);
580         }
581       return TRUE;
582 }
583
584 static gboolean
585 tree_update_children_listener (GSignalInvocationHint *signal_hint,
586                                guint                  n_param_values,
587                                const GValue          *param_values,
588                                gpointer               data)
589 {
590       tree_update_wrapper (signal_hint, n_param_values, param_values, data, tree_update_children_action);
591       return TRUE;
592 }
593
594 /*---------------------------------------------------------------------------*/
595
596 static void
597 spi_atk_register_toplevel_added (AtkObject *accessible,
598                                  guint     index,
599                                  AtkObject *child)
600 {
601   g_static_rec_mutex_lock (&registration_mutex);
602
603   g_return_if_fail (ATK_IS_OBJECT (accessible));
604
605   if (object_to_ref (accessible))
606     {
607 #ifdef SPI_ATK_DEBUG
608       if (recursion_check_and_set ())
609           g_warning ("AT-SPI: Recursive use of registration module");
610
611       g_debug ("AT-SPI: Toplevel added listener");
612 #endif
613       if (!ATK_IS_OBJECT (child))
614         {
615           child = atk_object_ref_accessible_child (accessible, index);
616 #ifdef SPI_ATK_DEBUG
617           non_owned_accessible (child);
618 #endif
619         }
620       register_subtree (child);
621       update_accessible (accessible);
622
623       recursion_check_unset ();
624     }
625
626   g_static_rec_mutex_unlock (&registration_mutex);
627 }
628
629 static void
630 spi_atk_register_toplevel_removed (AtkObject *accessible,
631                                    guint     index,
632                                    AtkObject *child)
633 {
634   g_static_rec_mutex_lock (&registration_mutex);
635
636   g_return_if_fail (ATK_IS_OBJECT (accessible));
637
638   if (object_to_ref (accessible))
639     {
640 #ifdef SPI_ATK_DEBUG
641       if (recursion_check_and_set ())
642           g_warning ("AT-SPI: Recursive use of registration module");
643
644       g_debug ("AT-SPI: Toplevel removed listener");
645 #endif
646       update_accessible (accessible);
647       recursion_check_unset ();
648     }
649
650   g_static_rec_mutex_unlock (&registration_mutex);
651 }
652
653 /*
654  * Initializes required global data. The update and removal lists
655  * and the reference lookup tables.
656  *
657  * Initializes all of the required D-Bus interfaces.
658  */
659 void
660 atk_dbus_initialize (AtkObject *root)
661 {
662   if (!ref2ptr)
663     ref2ptr = g_hash_table_new(g_direct_hash, g_direct_equal);
664
665 #ifdef SPI_ATK_DEBUG
666   if (g_thread_supported ())
667       g_message ("AT-SPI: Threads enabled");
668
669   g_debug ("AT-SPI: Initial Atk tree regisration");
670 #endif
671
672   register_subtree (root);
673
674   atk_add_global_event_listener (tree_update_property_listener, "Gtk:AtkObject:property-change");
675   atk_add_global_event_listener (tree_update_children_listener, "Gtk:AtkObject:children-changed");
676   atk_add_global_event_listener (tree_update_state_listener, "Gtk:AtkObject:state-change");
677
678   g_signal_connect (root,
679                     "children-changed::add",
680                     (GCallback) spi_atk_register_toplevel_added,
681                     NULL);
682   g_signal_connect (root,
683                     "children-changed::remove",
684                     (GCallback) spi_atk_register_toplevel_removed,
685                     NULL);
686 }
687
688 /*END------------------------------------------------------------------------*/