Imported version 2.7.91
[platform/core/uifw/at-spi2-core.git] / atspi / atspi-event-listener.c
1 /*
2  * AT-SPI - Assistive Technology Service Provider Interface
3  * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
4  *
5  * Copyright 2002 Ximian Inc.
6  * Copyright 2002 Sun Microsystems, Inc.
7  * Copyright 2010, 2011 Novell, Inc.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public
20  * License along with this library; if not, write to the
21  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22  * Boston, MA 02111-1307, USA.
23  */
24
25 #include "atspi-private.h"
26 #include <string.h>
27 #include <ctype.h>
28
29 typedef struct
30 {
31   AtspiEventListenerCB callback;
32   void *user_data;
33   GDestroyNotify callback_destroyed;
34   char *event_type;
35   char *category;
36   char *name;
37   char *detail;
38 } EventListenerEntry;
39
40 G_DEFINE_TYPE (AtspiEventListener, atspi_event_listener, G_TYPE_OBJECT)
41
42 void
43 atspi_event_listener_init (AtspiEventListener *listener)
44 {
45 }
46
47 void
48 atspi_event_listener_class_init (AtspiEventListenerClass *klass)
49 {
50 }
51
52 static void
53 remove_datum (const AtspiEvent *event, void *user_data)
54 {
55   AtspiEventListenerSimpleCB cb = user_data;
56   cb (event);
57 }
58
59 typedef struct
60 {
61   gpointer callback;
62   GDestroyNotify callback_destroyed;
63   gint ref_count;
64 } CallbackInfo;
65 static GHashTable *callbacks;
66
67 void
68 callback_ref (void *callback, GDestroyNotify callback_destroyed)
69 {
70   CallbackInfo *info;
71
72   if (!callbacks)
73   {
74     callbacks = g_hash_table_new (g_direct_hash, g_direct_equal);
75     if (!callbacks)
76       return;
77   }
78
79   info = g_hash_table_lookup (callbacks, callback);
80   if (!info)
81   {
82     info = g_new (CallbackInfo, 1);
83     info->callback = callback;
84     info->callback_destroyed = callback_destroyed;
85     info->ref_count = 1;
86     g_hash_table_insert (callbacks, callback, info);
87   }
88   else
89     info->ref_count++;
90 }
91
92 void
93 callback_unref (gpointer callback)
94 {
95   CallbackInfo *info;
96
97   if (!callbacks)
98     return;
99   info = g_hash_table_lookup (callbacks, callback);
100   if (!info)
101   {
102     g_warning ("Atspi: Dereferencing invalid callback %p\n", callback);
103     return;
104   }
105   info->ref_count--;
106   if (info->ref_count == 0)
107   {
108 #if 0
109     /* TODO: Figure out why this seg faults from Python */
110     if (info->callback_destroyed)
111       (*info->callback_destroyed) (info->callback);
112 #endif
113     g_free (info);
114     g_hash_table_remove (callbacks, callback);
115   }
116 }
117
118 /**
119  * atspi_event_listener_new:
120  * @callback: (scope notified): An #AtspiEventListenerCB to be called
121  * when an event is fired.
122  * @user_data: (closure): data to pass to the callback.
123  * @callback_destroyed: A #GDestroyNotify called when the listener is freed
124  * and data associated with the callback should be freed.  Can be NULL.
125  *
126  * Creates a new #AtspiEventListener associated with a specified @callback.
127  *
128  * Returns: (transfer full): A new #AtspiEventListener.
129  */
130 AtspiEventListener *
131 atspi_event_listener_new (AtspiEventListenerCB callback,
132                                  gpointer user_data,
133                                  GDestroyNotify callback_destroyed)
134 {
135   AtspiEventListener *listener = g_object_new (ATSPI_TYPE_EVENT_LISTENER, NULL);
136   listener->callback = callback;
137   callback_ref (callback, callback_destroyed);
138   listener->user_data = user_data;
139   listener->cb_destroyed = callback_destroyed;
140   return listener;
141 }
142
143 /**
144  * atspi_event_listener_new_simple:
145  * @callback: (scope notified): An #AtspiEventListenerSimpleCB to be called
146  * when an event is fired.
147  * @callback_destroyed: A #GDestroyNotify called when the listener is freed
148  * and data associated with the callback should be freed.  Can be NULL.
149  *
150  * Creates a new #AtspiEventListener associated with a specified @callback.
151  * Returns: (transfer full): A new #AtspiEventListener.
152  **/
153 AtspiEventListener *
154 atspi_event_listener_new_simple (AtspiEventListenerSimpleCB callback,
155                                  GDestroyNotify callback_destroyed)
156 {
157   AtspiEventListener *listener = g_object_new (ATSPI_TYPE_EVENT_LISTENER, NULL);
158   listener->callback = remove_datum;
159   callback_ref (remove_datum, callback_destroyed);
160   listener->user_data = callback;
161   listener->cb_destroyed = callback_destroyed;
162   return listener;
163 }
164
165 static GList *event_listeners = NULL;
166
167 static gchar *
168 convert_name_from_dbus (const char *name, gboolean path_hack)
169 {
170   gchar *ret = g_malloc (g_utf8_strlen (name, -1) * 2 + 1);
171   const char *p = name;
172   gchar *q = ret;
173
174   if (!ret)
175     return NULL;
176
177   while (*p)
178   {
179     if (isupper (*p))
180     {
181       if (q > ret)
182         *q++ = '-';
183       *q++ = tolower (*p++);
184     }
185     else if (path_hack && *p == '/')
186     {
187       *q++ = ':';
188       p++;
189     }
190     else
191       *q++ = *p++;
192   }
193   *q = '\0';
194   return ret;
195 }
196
197 static void
198 cache_process_children_changed (AtspiEvent *event)
199 {
200   AtspiAccessible *child;
201
202   if (!G_VALUE_HOLDS (&event->any_data, ATSPI_TYPE_ACCESSIBLE) ||
203       !(event->source->cached_properties & ATSPI_CACHE_CHILDREN) ||
204       atspi_state_set_contains (event->source->states, ATSPI_STATE_MANAGES_DESCENDANTS))
205     return;
206
207   child = g_value_get_object (&event->any_data);
208
209   if (!strncmp (event->type, "object:children-changed:add", 27))
210   {
211     if (g_list_find (event->source->children, child))
212       return;
213     event->source->children = g_list_insert (event->source->children,
214                                              g_object_ref (child),
215                                              event->detail1);
216   }
217   else if (g_list_find (event->source->children, child))
218   {
219     event->source->children = g_list_remove (event->source->children, child);
220     if (child == child->parent.app->root)
221       g_object_run_dispose (G_OBJECT (child->parent.app));
222     g_object_unref (child);
223   }
224 }
225
226 static void
227 cache_process_property_change (AtspiEvent *event)
228 {
229   if (!strcmp (event->type, "object:property-change:accessible-parent"))
230   {
231     if (event->source->accessible_parent)
232       g_object_unref (event->source->accessible_parent);
233     if (G_VALUE_HOLDS (&event->any_data, ATSPI_TYPE_ACCESSIBLE))
234     {
235       event->source->accessible_parent = g_value_dup_object (&event->any_data);
236       _atspi_accessible_add_cache (event->source, ATSPI_CACHE_PARENT);
237     }
238     else
239     {
240       event->source->accessible_parent = NULL;
241       event->source->cached_properties &= ~ATSPI_CACHE_PARENT;
242     }
243   }
244   else if (!strcmp (event->type, "object:property-change:accessible-name"))
245   {
246     if (event->source->name)
247       g_free (event->source->name);
248     if (G_VALUE_HOLDS_STRING (&event->any_data))
249     {
250       event->source->name = g_value_dup_string (&event->any_data);
251       _atspi_accessible_add_cache (event->source, ATSPI_CACHE_NAME);
252     }
253     else
254     {
255       event->source->name = NULL;
256       event->source->cached_properties &= ~ATSPI_CACHE_NAME;
257     }
258   }
259   else if (!strcmp (event->type, "object:property-change:accessible-description"))
260   {
261     if (event->source->description)
262       g_free (event->source->description);
263     if (G_VALUE_HOLDS_STRING (&event->any_data))
264     {
265       event->source->description = g_value_dup_string (&event->any_data);
266       _atspi_accessible_add_cache (event->source, ATSPI_CACHE_DESCRIPTION);
267     }
268     else
269     {
270       event->source->description = NULL;
271       event->source->cached_properties &= ~ATSPI_CACHE_DESCRIPTION;
272     }
273   }
274   else if (!strcmp (event->type, "object:property-change:accessible-role"))
275   {
276     if (G_VALUE_HOLDS_INT (&event->any_data))
277     {
278       event->source->role = g_value_get_int (&event->any_data);
279       _atspi_accessible_add_cache (event->source, ATSPI_CACHE_ROLE);
280     }
281     else
282     {
283       event->source->cached_properties &= ~ATSPI_CACHE_ROLE;
284     }
285   }
286 }
287
288 static void
289 cache_process_state_changed (AtspiEvent *event)
290 {
291   if (event->source->states)
292     atspi_state_set_set_by_name (event->source->states, event->type + 21,
293                                  event->detail1);
294 }
295
296 static dbus_bool_t
297 demarshal_rect (DBusMessageIter *iter, AtspiRect *rect)
298 {
299   dbus_int32_t x, y, width, height;
300   DBusMessageIter iter_struct;
301
302   dbus_message_iter_recurse (iter, &iter_struct);
303   if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32) return FALSE;
304   dbus_message_iter_get_basic (&iter_struct, &x);
305   dbus_message_iter_next (&iter_struct);
306   if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32) return FALSE;
307   dbus_message_iter_get_basic (&iter_struct, &y);
308   dbus_message_iter_next (&iter_struct);
309   if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32) return FALSE;
310   dbus_message_iter_get_basic (&iter_struct, &width);
311   dbus_message_iter_next (&iter_struct);
312   if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32) return FALSE;
313   dbus_message_iter_get_basic (&iter_struct, &height);
314   rect->x = x;
315   rect->y = y;
316   rect->width = width;
317   rect->height = height;
318   return TRUE;
319 }
320
321 static gchar *
322 strdup_and_adjust_for_dbus (const char *s)
323 {
324   gchar *d = g_strdup (s);
325   gchar *p;
326   int parts = 0;
327
328   if (!d)
329     return NULL;
330
331   for (p = d; *p; p++)
332   {
333     if (*p == '-')
334     {
335       memmove (p, p + 1, g_utf8_strlen (p, -1));
336       *p = toupper (*p);
337     }
338     else if (*p == ':')
339     {
340       parts++;
341       if (parts == 2)
342         break;
343       p [1] = toupper (p [1]);
344     }
345   }
346
347   d [0] = toupper (d [0]);
348   return d;
349 }
350
351 static gboolean
352 convert_event_type_to_dbus (const char *eventType, char **categoryp, char **namep, char **detailp, GPtrArray **matchrule_array)
353 {
354   gchar *tmp = strdup_and_adjust_for_dbus (eventType);
355   char *category = NULL, *name = NULL, *detail = NULL;
356   char *saveptr = NULL;
357
358   if (tmp == NULL) return FALSE;
359   category = strtok_r (tmp, ":", &saveptr);
360   if (category) category = g_strdup (category);
361   name = strtok_r (NULL, ":", &saveptr);
362   if (name)
363   {
364     name = g_strdup (name);
365     detail = strtok_r (NULL, ":", &saveptr);
366     if (detail) detail = g_strdup (detail);
367   }
368   if (matchrule_array)
369   {
370     gchar *matchrule;
371     matchrule = g_strdup_printf ("type='signal',interface='org.a11y.atspi.Event.%s'", category);
372     if (name && name [0])
373     {
374       gchar *new_str = g_strconcat (matchrule, ",member='", name, "'", NULL);
375       g_free (matchrule);
376       matchrule = new_str;
377     }
378     (*matchrule_array) = g_ptr_array_new ();
379     if (detail && detail [0])
380     {
381       gchar *new_str = g_strconcat (matchrule, ",arg0='", detail, "'", NULL);
382       g_ptr_array_add (*matchrule_array, new_str);
383       new_str = g_strconcat (matchrule, ",arg0path='", detail, "/'", NULL);
384       g_ptr_array_add (*matchrule_array, new_str);
385       g_free (matchrule);
386     }
387     else
388       g_ptr_array_add (*matchrule_array, matchrule);
389   }
390   if (categoryp) *categoryp = category;
391   else g_free (category);
392   if (namep) *namep = name;
393   else if (name) g_free (name);
394   if (detailp) *detailp = detail;
395   else if (detail) g_free (detail);
396   g_free (tmp);
397   return TRUE;
398 }
399
400 static void
401 listener_entry_free (EventListenerEntry *e)
402 {
403   gpointer callback = (e->callback == remove_datum ? (gpointer)e->user_data : (gpointer)e->callback);
404   g_free (e->event_type);
405   g_free (e->category);
406   g_free (e->name);
407   if (e->detail) g_free (e->detail);
408   callback_unref (callback);
409   g_free (e);
410 }
411
412 /**
413  * atspi_event_listener_register:
414  * @listener: The #AtspiEventListener to register against an event type.
415  * @event_type: a character string indicating the type of events for which
416  *            notification is requested.  Format is
417  *            EventClass:major_type:minor_type:detail
418  *            where all subfields other than EventClass are optional.
419  *            EventClasses include "object", "window", "mouse",
420  *            and toolkit events (e.g. "Gtk", "AWT").
421  *            Examples: "focus:", "Gtk:GtkWidget:button_press_event".
422  *
423  * Adds an in-process callback function to an existing #AtspiEventListener.
424  *
425  * Legal object event types:
426  *
427  *    (property change events)
428  *
429  *            object:property-change
430  *            object:property-change:accessible-name
431  *            object:property-change:accessible-description
432  *            object:property-change:accessible-parent
433  *            object:property-change:accessible-value
434  *            object:property-change:accessible-role
435  *            object:property-change:accessible-table-caption
436  *            object:property-change:accessible-table-column-description
437  *            object:property-change:accessible-table-column-header
438  *            object:property-change:accessible-table-row-description
439  *            object:property-change:accessible-table-row-header
440  *            object:property-change:accessible-table-summary
441  *
442  *    (other object events)
443  *
444  *            object:state-changed 
445  *            object:children-changed
446  *            object:visible-data-changed
447  *            object:selection-changed
448  *            object:text-selection-changed
449  *            object:text-changed
450  *            object:text-caret-moved
451  *            object:row-inserted
452  *            object:row-reordered
453  *            object:row-deleted
454  *            object:column-inserted
455  *            object:column-reordered
456  *            object:column-deleted
457  *            object:model-changed
458  *            object:active-descendant-changed
459  *
460  *  (window events)
461  *
462  *            window:minimize
463  *            window:maximize
464  *            window:restore
465  *            window:close
466  *            window:create
467  *            window:reparent
468  *            window:desktop-create
469  *            window:desktop-destroy
470  *            window:activate
471  *            window:deactivate
472  *            window:raise
473  *            window:lower
474  *            window:move
475  *            window:resize
476  *            window:shade
477  *            window:unshade
478  *            window:restyle
479  *
480  *  (other events)
481  *
482  *            focus:
483  *            mouse:abs
484  *            mouse:rel
485  *            mouse:b1p
486  *            mouse:b1r
487  *            mouse:b2p
488  *            mouse:b2r
489  *            mouse:b3p
490  *            mouse:b3r
491  *
492  * NOTE: this character string may be UTF-8, but should not contain byte 
493  * value 56
494  *            (ascii ':'), except as a delimiter, since non-UTF-8 string
495  *            delimiting functions are used internally.
496  *            In general, listening to
497  *            toolkit-specific events is not recommended.
498  *
499  *
500  * Returns: #TRUE if successful, otherwise #FALSE.
501  **/
502 gboolean
503 atspi_event_listener_register (AtspiEventListener *listener,
504                                              const gchar              *event_type,
505                                              GError **error)
506 {
507   /* TODO: Keep track of which events have been registered, so that we
508  * deregister all of them when the event listener is destroyed */
509
510   return atspi_event_listener_register_from_callback (listener->callback,
511                                                       listener->user_data,
512                                                       listener->cb_destroyed,
513                                                       event_type, error);
514 }
515
516 static gboolean
517 notify_event_registered (EventListenerEntry *e)
518 {
519   DBusMessage *message, *reply;
520
521   message = dbus_message_new_method_call (atspi_bus_registry,
522         atspi_path_registry,
523         atspi_interface_registry,
524         "RegisterEvent");
525   if (!message)
526     return FALSE;
527   dbus_message_append_args (message, DBUS_TYPE_STRING, &e->event_type, DBUS_TYPE_INVALID);
528   reply = _atspi_dbus_send_with_reply_and_block (message, NULL);
529
530   if (reply)
531     dbus_message_unref (reply);
532   return TRUE;
533 }
534
535 /**
536  * atspi_event_listener_register_from_callback:
537  * @callback: (scope notified): the #AtspiEventListenerCB to be registered 
538  * against an event type.
539  * @user_data: (closure): User data to be passed to the callback.
540  * @callback_destroyed: A #GDestroyNotify called when the callback is destroyed.
541  * @event_type: a character string indicating the type of events for which
542  *            notification is requested.  See #atspi_event_listener_register
543  * for a description of the format.
544  * 
545  * Registers an #AtspiEventListenerCB against an @event_type.
546  *
547  * Returns: #TRUE if successfull, otherwise #FALSE.
548  *
549  **/
550 gboolean
551 atspi_event_listener_register_from_callback (AtspiEventListenerCB callback,
552                                              void *user_data,
553                                              GDestroyNotify callback_destroyed,
554                                              const gchar              *event_type,
555                                              GError **error)
556 {
557   EventListenerEntry *e;
558   DBusError d_error;
559   GPtrArray *matchrule_array;
560   gint i;
561
562   if (!callback)
563     {
564       return FALSE;
565     }
566
567   if (!event_type)
568   {
569     g_warning ("called atspi_event_listener_register_from_callback with a NULL event_type");
570     return FALSE;
571   }
572
573   e = g_new (EventListenerEntry, 1);
574   e->event_type = g_strdup (event_type);
575   e->callback = callback;
576   e->user_data = user_data;
577   e->callback_destroyed = callback_destroyed;
578   callback_ref (callback == remove_datum ? (gpointer)user_data : (gpointer)callback,
579                 callback_destroyed);
580   if (!convert_event_type_to_dbus (event_type, &e->category, &e->name, &e->detail, &matchrule_array))
581   {
582     g_free (e);
583     return FALSE;
584   }
585   event_listeners = g_list_prepend (event_listeners, e);
586   for (i = 0; i < matchrule_array->len; i++)
587   {
588     char *matchrule = g_ptr_array_index (matchrule_array, i);
589     dbus_error_init (&d_error);
590     dbus_bus_add_match (_atspi_bus(), matchrule, &d_error);
591     if (dbus_error_is_set (&d_error))
592       {
593         g_warning ("Atspi: Adding match: %s", d_error.message);
594         dbus_error_free (&d_error);
595         /* TODO: Set error */
596       }
597
598     g_free (matchrule);
599   }
600   g_ptr_array_free (matchrule_array, TRUE);
601
602   notify_event_registered (e);
603   return TRUE;
604 }
605
606 void
607 _atspi_reregister_event_listeners ()
608 {
609   GList *l;
610   EventListenerEntry *e;
611
612   for (l = event_listeners; l; l = l->next)
613     {
614       e = l->data;
615       notify_event_registered (e);
616     }
617 }
618
619 /**
620  * atspi_event_listener_register_no_data:
621  * @callback: (scope notified): the #AtspiEventListenerSimpleCB to be
622  *            registered against an event type.
623  * @callback_destroyed: A #GDestroyNotify called when the callback is destroyed.
624  * @event_type: a character string indicating the type of events for which
625  *            notification is requested.  Format is
626  *            EventClass:major_type:minor_type:detail
627  *            where all subfields other than EventClass are optional.
628  *            EventClasses include "object", "window", "mouse",
629  *            and toolkit events (e.g. "Gtk", "AWT").
630  *            Examples: "focus:", "Gtk:GtkWidget:button_press_event".
631  *
632  * Registers an #AtspiEventListenetSimpleCB. The method is similar to 
633  * #atspi_event_listener_register, but @callback takes no user_data.
634  *
635  * Returns: #TRUE if successfull, otherwise #FALSE.
636  **/
637 gboolean
638 atspi_event_listener_register_no_data (AtspiEventListenerSimpleCB callback,
639                                  GDestroyNotify callback_destroyed,
640                                  const gchar              *event_type,
641                                  GError **error)
642 {
643   return atspi_event_listener_register_from_callback (remove_datum, callback,
644                                                       callback_destroyed,
645                                                       event_type, error);
646 }
647
648 static gboolean
649 is_superset (const gchar *super, const gchar *sub)
650 {
651   if (!super || !super [0])
652     return TRUE;
653   return (strcmp (super, sub) == 0);
654 }
655
656 /**
657  * atspi_event_listener_deregister:
658  * @listener: The #AtspiEventListener to deregister.
659  * @event_type: a string specifying the event type for which this
660  *             listener is to be deregistered.
661  *
662  * Deregisters an #AtspiEventListener from the registry, for a specific
663  *             event type.
664  *
665  * Returns: #TRUE if successful, otherwise #FALSE.
666  **/
667 gboolean
668 atspi_event_listener_deregister (AtspiEventListener *listener,
669                                                const gchar              *event_type,
670                                                GError **error)
671 {
672   return atspi_event_listener_deregister_from_callback (listener->callback,
673                                                         listener->user_data,
674                                                         event_type, error);
675 }
676
677 /**
678  * atspi_event_listener_deregister_from_callback:
679  * @callback: (scope call): the #AtspiEventListenerCB registered against an
680  *            event type.
681  * @user_data: (closure): User data that was passed in for this callback.
682  * @event_type: a string specifying the event type for which this
683  *             listener is to be deregistered.
684  *
685  * Deregisters an #AtspiEventListenerCB from the registry, for a specific
686  *             event type.
687  *
688  * Returns: #TRUE if successful, otherwise #FALSE.
689  **/
690 gboolean
691 atspi_event_listener_deregister_from_callback (AtspiEventListenerCB callback,
692                                                void *user_data,
693                                                const gchar              *event_type,
694                                                GError **error)
695 {
696   char *category, *name, *detail;
697   GPtrArray *matchrule_array;
698   gint i;
699   GList *l;
700
701   if (!convert_event_type_to_dbus (event_type, &category, &name, &detail, &matchrule_array))
702   {
703     return FALSE;
704   }
705   if (!callback)
706     {
707       return FALSE;
708     }
709
710   for (l = event_listeners; l;)
711   {
712     EventListenerEntry *e = l->data;
713     if (e->callback == callback &&
714         e->user_data == user_data &&
715         is_superset (category, e->category) &&
716         is_superset (name, e->name) &&
717         is_superset (detail, e->detail))
718     {
719       gboolean need_replace;
720       DBusMessage *message, *reply;
721       need_replace = (l == event_listeners);
722       l = g_list_remove (l, e);
723       if (need_replace)
724         event_listeners = l;
725       for (i = 0; i < matchrule_array->len; i++)
726       {
727         char *matchrule = g_ptr_array_index (matchrule_array, i);
728         dbus_bus_remove_match (_atspi_bus(), matchrule, NULL);
729       }
730       message = dbus_message_new_method_call (atspi_bus_registry,
731             atspi_path_registry,
732             atspi_interface_registry,
733             "DeregisterEvent");
734       if (!message)
735       return FALSE;
736       dbus_message_append_args (message, DBUS_TYPE_STRING, &event_type, DBUS_TYPE_INVALID);
737       reply = _atspi_dbus_send_with_reply_and_block (message, error);
738       if (reply)
739         dbus_message_unref (reply);
740
741       listener_entry_free (e);
742     }
743     else l = g_list_next (l);
744   }
745   g_free (category);
746   g_free (name);
747   if (detail) g_free (detail);
748   for (i = 0; i < matchrule_array->len; i++)
749     g_free (g_ptr_array_index (matchrule_array, i));
750   g_ptr_array_free (matchrule_array, TRUE);
751   return TRUE;
752 }
753
754 /**
755  * atspi_event_listener_deregister_no_data:
756  * @callback: (scope call): the #AtspiEventListenerSimpleCB registered against
757  *            an event type.
758  * @event_type: a string specifying the event type for which this
759  *             listener is to be deregistered.
760  *
761  * deregisters an #AtspiEventListenerSimpleCB from the registry, for a specific
762  *             event type.
763  *
764  * Returns: #TRUE if successful, otherwise #FALSE.
765  **/
766 gboolean
767 atspi_event_listener_deregister_no_data (AtspiEventListenerSimpleCB callback,
768                                    const gchar              *event_type,
769                                    GError **error)
770 {
771   return atspi_event_listener_deregister_from_callback (remove_datum, callback,
772                                                         event_type,
773                                                         error);
774 }
775
776 static AtspiEvent *
777 atspi_event_copy (AtspiEvent *src)
778 {
779   AtspiEvent *dst = g_new0 (AtspiEvent, 1);
780   dst->type = g_strdup (src->type);
781   dst->source = g_object_ref (src->source);
782   dst->detail1 = src->detail1;
783   dst->detail2 = src->detail2;
784   g_value_init (&dst->any_data, G_VALUE_TYPE (&src->any_data));
785   g_value_copy (&src->any_data, &dst->any_data);
786   return dst;
787 }
788
789 static void
790 atspi_event_free (AtspiEvent *event)
791 {
792   g_object_unref (event->source);
793   g_free (event->type);
794   g_value_unset (&event->any_data);
795   g_free (event);
796 }
797
798 static gboolean
799 detail_matches_listener (const char *event_detail, const char *listener_detail)
800 {
801   if (!listener_detail)
802     return TRUE;
803
804   if (!event_detail)
805     return (listener_detail ? FALSE : TRUE);
806
807   return !(listener_detail [strcspn (listener_detail, ":")] == '\0'
808                ? strncmp (listener_detail, event_detail,
809                           strcspn (event_detail, ":"))
810                : strcmp (listener_detail, event_detail));
811 }
812
813 void
814 _atspi_send_event (AtspiEvent *e)
815 {
816   char *category, *name, *detail;
817   GList *l;
818   GList *called_listeners = NULL;
819
820   /* Ensure that the value is set to avoid a Python exception */
821   /* TODO: Figure out how to do this without using a private field */
822   if (e->any_data.g_type == 0)
823   {
824     g_value_init (&e->any_data, G_TYPE_INT);
825     g_value_set_int (&e->any_data, 0);
826   }
827
828   if (!convert_event_type_to_dbus (e->type, &category, &name, &detail, NULL))
829   {
830     g_warning ("Atspi: Couldn't parse event: %s\n", e->type);
831     return;
832   }
833   for (l = event_listeners; l; l = g_list_next (l))
834   {
835     EventListenerEntry *entry = l->data;
836     if (!strcmp (category, entry->category) &&
837         (entry->name == NULL || !strcmp (name, entry->name)) &&
838         detail_matches_listener (detail, entry->detail))
839     {
840       GList *l2;
841       for (l2 = called_listeners; l2; l2 = l2->next)
842       {
843         EventListenerEntry *e2 = l2->data;
844         if (entry->callback == e2->callback && entry->user_data == e2->user_data)
845           break;
846       }
847       if (!l2)
848       {
849         entry->callback (atspi_event_copy (e), entry->user_data);
850         called_listeners = g_list_prepend (called_listeners, entry);
851       }
852     }
853   }
854   if (detail) g_free (detail);
855   g_free (name);
856   g_free (category);
857   g_list_free (called_listeners);
858 }
859
860 DBusHandlerResult
861 _atspi_dbus_handle_event (DBusConnection *bus, DBusMessage *message, void *data)
862 {
863   char *detail = NULL;
864   const char *category = dbus_message_get_interface (message);
865   const char *member = dbus_message_get_member (message);
866   const char *signature = dbus_message_get_signature (message);
867   gchar *name;
868   gchar *converted_type;
869   DBusMessageIter iter, iter_variant;
870   dbus_message_iter_init (message, &iter);
871   AtspiEvent e;
872   dbus_int32_t detail1, detail2;
873   char *p;
874
875   if (strcmp (signature, "siiv(so)") != 0)
876   {
877     g_warning ("Got invalid signature %s for signal %s from interface %s\n", signature, member, category);
878     return DBUS_HANDLER_RESULT_HANDLED;
879   }
880
881   memset (&e, 0, sizeof (e));
882
883   if (category)
884   {
885     category = g_utf8_strrchr (category, -1, '.');
886     if (category == NULL)
887     {
888       // TODO: Error
889       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
890     }
891     category++;
892   }
893   dbus_message_iter_get_basic (&iter, &detail);
894   dbus_message_iter_next (&iter);
895   dbus_message_iter_get_basic (&iter, &detail1);
896   e.detail1 = detail1;
897   dbus_message_iter_next (&iter);
898   dbus_message_iter_get_basic (&iter, &detail2);
899   e.detail2 = detail2;
900   dbus_message_iter_next (&iter);
901
902   converted_type = convert_name_from_dbus (category, FALSE);
903   name = convert_name_from_dbus (member, FALSE);
904   detail = convert_name_from_dbus (detail, TRUE);
905
906   if (strcasecmp  (category, name) != 0)
907   {
908     p = g_strconcat (converted_type, ":", name, NULL);
909     g_free (converted_type);
910     converted_type = p;
911   }
912   else if (detail [0] == '\0')
913   {
914     p = g_strconcat (converted_type, ":",  NULL);
915     g_free (converted_type);
916     converted_type = p;
917   }
918
919   if (detail[0] != '\0')
920   {
921     p = g_strconcat (converted_type, ":", detail, NULL);
922     g_free (converted_type);
923     converted_type = p;
924   }
925   e.type = converted_type;
926   e.source = _atspi_ref_accessible (dbus_message_get_sender(message), dbus_message_get_path(message));
927
928   if (NULL == e.source)
929   {
930     g_free (converted_type);
931     g_free (name);
932     g_free (detail);
933     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
934   }
935
936   dbus_message_iter_recurse (&iter, &iter_variant);
937   switch (dbus_message_iter_get_arg_type (&iter_variant))
938   {
939     case DBUS_TYPE_STRUCT:
940     {
941       AtspiRect rect;
942       if (demarshal_rect (&iter_variant, &rect))
943       {
944         g_value_init (&e.any_data, ATSPI_TYPE_RECT);
945         g_value_set_boxed (&e.any_data, &rect);
946       }
947       else
948       {
949         AtspiAccessible *accessible;
950         accessible = _atspi_dbus_return_accessible_from_iter (&iter_variant);
951         g_value_init (&e.any_data, ATSPI_TYPE_ACCESSIBLE);
952         g_value_set_instance (&e.any_data, accessible);
953         if (accessible)
954           g_object_unref (accessible);  /* value now owns it */
955       }
956       break;
957     }
958     case DBUS_TYPE_STRING:
959     {
960       dbus_message_iter_get_basic (&iter_variant, &p);
961       g_value_init (&e.any_data, G_TYPE_STRING);
962       g_value_set_string (&e.any_data, p);
963       break;
964     }
965   default:
966     break;
967   }
968
969   if (!strncmp (e.type, "object:children-changed", 23))
970   {
971     cache_process_children_changed (&e);
972   }
973   else if (!strncmp (e.type, "object:property-change", 22))
974   {
975     cache_process_property_change (&e);
976   }
977   else if (!strncmp (e.type, "object:state-changed", 20))
978   {
979     cache_process_state_changed (&e);
980   }
981   else if (!strncmp (e.type, "focus", 5))
982   {
983     /* BGO#663992 - TODO: figure out the real problem */
984     e.source->cached_properties &= ~(ATSPI_CACHE_STATES);
985   }
986
987   _atspi_send_event (&e);
988
989   g_free (converted_type);
990   g_free (name);
991   g_free (detail);
992   g_object_unref (e.source);
993   g_value_unset (&e.any_data);
994   return DBUS_HANDLER_RESULT_HANDLED;
995 }
996
997 G_DEFINE_BOXED_TYPE (AtspiEvent, atspi_event, atspi_event_copy, atspi_event_free)