Only initialize the cache when an AT is running
[platform/core/uifw/at-spi2-atk.git] / atk-adaptor / accessible-cache.c
1 /*
2  * AT-SPI - Assistive Technology Service Provider Interface
3  * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
4  *
5  * Copyright 2009, 2010 Codethink Ltd.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #include <atk/atk.h>
24 #include <string.h>
25
26 #include "accessible-cache.h"
27 #include "accessible-register.h"
28 #include "bridge.h"
29
30 SpiCache *spi_global_cache = NULL;
31
32 static gboolean
33 child_added_listener (GSignalInvocationHint * signal_hint,
34                       guint n_param_values,
35                       const GValue * param_values, gpointer data);
36
37 static void
38 toplevel_added_listener (AtkObject * accessible,
39                          guint index, AtkObject * child);
40
41 static void
42 remove_object (GObject * source, GObject * gobj, gpointer data);
43
44 static void
45 add_object (SpiCache * cache, GObject * gobj);
46
47 static void
48 add_subtree (SpiCache *cache, AtkObject * accessible);
49
50 static gboolean
51 add_pending_items (gpointer data);
52
53 /*---------------------------------------------------------------------------*/
54
55 static void
56 spi_cache_finalize (GObject * object);
57
58 static void
59 spi_cache_dispose (GObject * object);
60
61 /*---------------------------------------------------------------------------*/
62
63 enum
64 {
65   OBJECT_ADDED,
66   OBJECT_REMOVED,
67   LAST_SIGNAL
68 };
69 static guint cache_signals[LAST_SIGNAL] = { 0 };
70
71 /*---------------------------------------------------------------------------*/
72
73 G_DEFINE_TYPE (SpiCache, spi_cache, G_TYPE_OBJECT)
74
75 static void spi_cache_class_init (SpiCacheClass * klass)
76 {
77   GObjectClass *object_class = (GObjectClass *) klass;
78
79   spi_cache_parent_class = g_type_class_ref (G_TYPE_OBJECT);
80
81   object_class->finalize = spi_cache_finalize;
82   object_class->dispose = spi_cache_dispose;
83
84   cache_signals [OBJECT_ADDED] = \
85       g_signal_new ("object-added",
86                     SPI_CACHE_TYPE,
87                     G_SIGNAL_ACTION,
88                     0,
89                     NULL,
90                     NULL,
91                     g_cclosure_marshal_VOID__OBJECT,
92                     G_TYPE_NONE,
93                     1,
94                     G_TYPE_OBJECT);
95
96   cache_signals [OBJECT_REMOVED] = \
97       g_signal_new ("object-removed",
98                     SPI_CACHE_TYPE,
99                     G_SIGNAL_ACTION,
100                     0,
101                     NULL,
102                     NULL,
103                     g_cclosure_marshal_VOID__OBJECT,
104                     G_TYPE_NONE,
105                     1,
106                     G_TYPE_OBJECT);
107 }
108
109 static void
110 spi_cache_init (SpiCache * cache)
111 {
112 g_print("dbg: init cache\n");
113   cache->objects = g_hash_table_new (g_direct_hash, g_direct_equal);
114   cache->add_traversal = g_queue_new ();
115
116 #ifdef SPI_ATK_DEBUG
117   if (g_thread_supported ())
118     g_message ("AT-SPI: Threads enabled");
119
120   g_debug ("AT-SPI: Initial Atk tree regisration");
121 #endif
122
123   g_signal_connect (spi_global_register,
124                     "object-deregistered",
125                     (GCallback) remove_object, cache);
126
127   add_subtree (cache, spi_global_app_data->root);
128
129   cache->child_added_listener = atk_add_global_event_listener (child_added_listener,
130                                                                "Gtk:AtkObject:children-changed"); 
131
132   g_signal_connect (G_OBJECT (spi_global_app_data->root),
133                     "children-changed::add",
134                     (GCallback) toplevel_added_listener, NULL);
135 }
136
137 static void
138 spi_cache_finalize (GObject * object)
139 {
140   SpiCache *cache = SPI_CACHE (object);
141
142   while (!g_queue_is_empty (cache->add_traversal))
143     g_object_unref (G_OBJECT (g_queue_pop_head (cache->add_traversal)));
144   g_queue_free (cache->add_traversal);
145   g_hash_table_unref (cache->objects);
146
147   g_signal_handlers_disconnect_by_func (spi_global_register,
148                                         (GCallback) remove_object, cache);
149
150   g_signal_handlers_disconnect_by_func (G_OBJECT (spi_global_app_data->root),
151                                         (GCallback) toplevel_added_listener, NULL);
152
153   atk_remove_global_event_listener (cache->child_added_listener);
154
155   G_OBJECT_CLASS (spi_cache_parent_class)->finalize (object);
156 }
157
158 static void
159 spi_cache_dispose (GObject * object)
160 {
161   SpiCache *cache = SPI_CACHE (object);
162
163   G_OBJECT_CLASS (spi_cache_parent_class)->dispose (object);
164 }
165
166 /*---------------------------------------------------------------------------*/
167
168 static void
169 remove_object (GObject * source, GObject * gobj, gpointer data)
170 {
171   SpiCache *cache = SPI_CACHE (data);
172   
173   if (spi_cache_in (cache, gobj))
174     {
175 #ifdef SPI_ATK_DEBUG
176   g_debug ("CACHE REM - %s - %d - %s\n", atk_object_get_name (ATK_OBJECT (gobj)),
177             atk_object_get_role (ATK_OBJECT (gobj)),
178             spi_register_object_to_path (spi_global_register, gobj));
179 #endif
180       g_signal_emit (cache, cache_signals [OBJECT_REMOVED], 0, gobj);
181       g_hash_table_remove (cache->objects, gobj);
182     }
183   else
184     g_queue_remove (cache->add_traversal, gobj);
185 }
186
187 static void
188 add_object (SpiCache * cache, GObject * gobj)
189 {
190   g_return_if_fail (G_IS_OBJECT (gobj));
191
192   g_hash_table_insert (cache->objects, gobj, NULL);
193
194 #ifdef SPI_ATK_DEBUG
195   g_debug ("CACHE ADD - %s - %d - %s\n", atk_object_get_name (ATK_OBJECT (gobj)),
196             atk_object_get_role (ATK_OBJECT (gobj)),
197             spi_register_object_to_path (spi_global_register, gobj));
198 #endif
199
200   g_signal_emit (cache, cache_signals [OBJECT_ADDED], 0, gobj);
201 }
202
203 /*---------------------------------------------------------------------------*/
204
205 static GStaticRecMutex cache_mutex        = G_STATIC_REC_MUTEX_INIT;
206 static GStaticMutex recursion_check_guard = G_STATIC_MUTEX_INIT;
207
208 static gboolean recursion_check = FALSE;
209
210 static gboolean
211 recursion_check_and_set ()
212 {
213   gboolean ret;
214   g_static_mutex_lock (&recursion_check_guard);
215   ret = recursion_check;
216   recursion_check = TRUE;
217   g_static_mutex_unlock (&recursion_check_guard);
218   return ret;
219 }
220
221 static void
222 recursion_check_unset ()
223 {
224   g_static_mutex_lock (&recursion_check_guard);
225   recursion_check = FALSE;
226   g_static_mutex_unlock (&recursion_check_guard);
227 }
228
229 /*---------------------------------------------------------------------------*/
230
231 static void
232 append_children (AtkObject * accessible, GQueue * traversal)
233 {
234   AtkObject *current;
235   guint i;
236   gint count = atk_object_get_n_accessible_children (accessible);
237
238   if (count < 0)
239     count = 0;
240   for (i = 0; i < count; i++)
241     {
242       current = atk_object_ref_accessible_child (accessible, i);
243       if (current)
244         {
245           g_queue_push_tail (traversal, current);
246         }
247     }
248 }
249
250 /*
251  * Adds a subtree of accessible objects
252  * to the cache at the accessible object provided.
253  *
254  * The leaf nodes do not have their children
255  * registered. A node is considered a leaf
256  * if it has the state "manages-descendants"
257  * or if it has already been registered.
258  */
259 static void
260 add_subtree (SpiCache *cache, AtkObject * accessible)
261 {
262   g_return_if_fail (ATK_IS_OBJECT (accessible));
263
264   g_object_ref (accessible);
265   g_queue_push_tail (cache->add_traversal, accessible);
266   add_pending_items (cache);
267 }
268
269 static gboolean
270 add_pending_items (gpointer data)
271 {
272   SpiCache *cache = SPI_CACHE (data);
273   AtkObject *current;
274   GQueue *to_add;
275
276   to_add = g_queue_new ();
277
278   while (!g_queue_is_empty (cache->add_traversal))
279     {
280       AtkStateSet *set;
281       
282       current = g_queue_pop_head (cache->add_traversal);
283       set = atk_object_ref_state_set (current);
284
285       if (set && !atk_state_set_contains_state (set, ATK_STATE_TRANSIENT))
286         {
287           g_queue_push_tail (to_add, current);
288           if (!spi_cache_in (cache, G_OBJECT (current)) &&
289               !atk_state_set_contains_state  (set, ATK_STATE_MANAGES_DESCENDANTS) &&
290               !atk_state_set_contains_state  (set, ATK_STATE_DEFUNCT))
291             {
292               append_children (current, cache->add_traversal);
293             }
294         }
295
296       if (set)
297         g_object_unref (set);
298     }
299
300   while (!g_queue_is_empty (to_add))
301     {
302       current = g_queue_pop_head (to_add);
303
304       /* Make sure object is registerd so we are notified if it goes away */
305       g_free (spi_register_object_to_path (spi_global_register,
306               G_OBJECT (current)));
307
308       add_object (cache, G_OBJECT(current));
309       g_object_unref (G_OBJECT (current));
310     }
311
312   g_queue_free (to_add);
313   cache->add_pending_idle = 0;
314   return FALSE;
315 }
316
317 /*---------------------------------------------------------------------------*/
318
319 static gboolean
320 child_added_listener (GSignalInvocationHint * signal_hint,
321                       guint n_param_values,
322                       const GValue * param_values, gpointer data)
323 {
324   SpiCache *cache = spi_global_cache;
325   AtkObject *accessible;
326
327   const gchar *detail = NULL;
328
329   g_static_rec_mutex_lock (&cache_mutex);
330
331   /* 
332    * Ensure that only accessibles already in the cache
333    * have their signals processed.
334    */
335   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
336   g_return_val_if_fail (ATK_IS_OBJECT (accessible), TRUE);
337
338   if (spi_cache_in (cache, G_OBJECT(accessible)))
339     {
340 #ifdef SPI_ATK_DEBUG
341       if (recursion_check_and_set ())
342         g_warning ("AT-SPI: Recursive use of cache module");
343
344       g_debug ("AT-SPI: Tree update listener");
345 #endif
346       if (signal_hint->detail)
347         detail = g_quark_to_string (signal_hint->detail);
348
349       if (detail && !strncmp (detail, "add", 3))
350         {
351           gpointer child;
352           int index = g_value_get_uint (param_values + 1);
353           child = g_value_get_pointer (param_values + 2);
354           if (!child)
355             {
356               g_static_rec_mutex_unlock (&cache_mutex);
357               return;
358             }
359
360           g_object_ref (child);
361           g_queue_push_tail (cache->add_traversal, child);
362
363           if (cache->add_pending_idle == 0)
364             cache->add_pending_idle = g_idle_add (add_pending_items, cache);
365         }
366 #ifdef SPI_ATK_DEBUG
367       recursion_check_unset ();
368 #endif
369     }
370
371   g_static_rec_mutex_unlock (&cache_mutex);
372
373   return TRUE;
374 }
375
376 /*---------------------------------------------------------------------------*/
377
378 static void
379 toplevel_added_listener (AtkObject * accessible,
380                          guint index, AtkObject * child)
381 {
382   SpiCache *cache = spi_global_cache;
383
384   g_static_rec_mutex_lock (&cache_mutex);
385
386   g_return_if_fail (ATK_IS_OBJECT (accessible));
387
388   if (spi_cache_in (cache, G_OBJECT(accessible)))
389     {
390 #ifdef SPI_ATK_DEBUG
391       if (recursion_check_and_set ())
392         g_warning ("AT-SPI: Recursive use of registration module");
393
394       g_debug ("AT-SPI: Toplevel added listener");
395 #endif
396       if (!ATK_IS_OBJECT (child))
397         {
398           child = atk_object_ref_accessible_child (accessible, index);
399         }
400       else
401         g_object_ref (child);
402
403       g_queue_push_tail (cache->add_traversal, child);
404
405       if (cache->add_pending_idle == 0)
406         cache->add_pending_idle = g_idle_add (add_pending_items, cache);
407 #ifdef SPI_ATK_DEBUG
408       recursion_check_unset ();
409 #endif
410     }
411
412   g_static_rec_mutex_unlock (&cache_mutex);
413 }
414
415 /*---------------------------------------------------------------------------*/
416
417 void
418 spi_cache_foreach (SpiCache * cache, GHFunc func, gpointer data)
419 {
420   g_hash_table_foreach (cache->objects, func, data);
421 }
422
423 gboolean
424 spi_cache_in (SpiCache * cache, GObject * object)
425 {
426   if (!cache)
427     return FALSE;
428
429   if (g_hash_table_lookup_extended (cache->objects,
430                                     object,
431                                     NULL,
432                                     NULL))
433     return TRUE;
434   else
435     return FALSE;
436 }
437
438 #ifdef SPI_ATK_DEBUG
439 void
440 spi_cache_print_info (GObject * obj)
441 {
442   char * path = spi_register_object_to_path (spi_global_register, obj);
443  
444   if (spi_cache_in (spi_global_cache, obj))
445       g_printf ("%s IC\n", path);
446   else
447       g_printf ("%s NC\n", path);
448
449   if (path)
450       g_free (path);
451 }
452 #endif
453
454 /*END------------------------------------------------------------------------*/