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