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