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