Re-register keystroke listeners if the registry goes away and returns
[platform/upstream/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 #AtspiEventListenerSimpleCB 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   DBusMessage *message, *reply;
560   GPtrArray *matchrule_array;
561   gint i;
562
563   if (!callback)
564     {
565       return FALSE;
566     }
567
568   if (!event_type)
569   {
570     g_warning ("called atspi_event_listener_register_from_callback with a NULL event_type");
571     return FALSE;
572   }
573
574   e = g_new (EventListenerEntry, 1);
575   e->event_type = g_strdup (event_type);
576   e->callback = callback;
577   e->user_data = user_data;
578   e->callback_destroyed = callback_destroyed;
579   callback_ref (callback == remove_datum ? (gpointer)user_data : (gpointer)callback,
580                 callback_destroyed);
581   if (!convert_event_type_to_dbus (event_type, &e->category, &e->name, &e->detail, &matchrule_array))
582   {
583     g_free (e);
584     return FALSE;
585   }
586   event_listeners = g_list_prepend (event_listeners, e);
587   for (i = 0; i < matchrule_array->len; i++)
588   {
589     char *matchrule = g_ptr_array_index (matchrule_array, i);
590     dbus_error_init (&d_error);
591     dbus_bus_add_match (_atspi_bus(), matchrule, &d_error);
592     if (dbus_error_is_set (&d_error))
593       {
594         g_warning ("Atspi: Adding match: %s", d_error.message);
595         dbus_error_free (&d_error);
596         /* TODO: Set error */
597       }
598
599     g_free (matchrule);
600   }
601   g_ptr_array_free (matchrule_array, TRUE);
602
603   notify_event_registered (e);
604   return TRUE;
605 }
606
607 void
608 _atspi_reregister_event_listeners ()
609 {
610   GList *l;
611   EventListenerEntry *e;
612
613   for (l = event_listeners; l; l = l->next)
614     {
615       e = l->data;
616       notify_event_registered (e);
617     }
618 }
619
620 /**
621  * atspi_event_listener_register_no_data:
622  * @callback: (scope notified): the #AtspiEventListenerSimpleCB to be
623  *            registered against an event type.
624  * @callback_destroyed: A #GDestroyNotify called when the callback is destroyed.
625  * @event_type: a character string indicating the type of events for which
626  *            notification is requested.  Format is
627  *            EventClass:major_type:minor_type:detail
628  *            where all subfields other than EventClass are optional.
629  *            EventClasses include "object", "window", "mouse",
630  *            and toolkit events (e.g. "Gtk", "AWT").
631  *            Examples: "focus:", "Gtk:GtkWidget:button_press_event".
632  *
633  * Registers an #AtspiEventListenetSimpleCB. The method is similar to 
634  * #atspi_event_listener_register, but @callback takes no user_data.
635  *
636  * Returns: #TRUE if successfull, otherwise #FALSE.
637  **/
638 gboolean
639 atspi_event_listener_register_no_data (AtspiEventListenerSimpleCB callback,
640                                  GDestroyNotify callback_destroyed,
641                                  const gchar              *event_type,
642                                  GError **error)
643 {
644   return atspi_event_listener_register_from_callback (remove_datum, callback,
645                                                       callback_destroyed,
646                                                       event_type, error);
647 }
648
649 static gboolean
650 is_superset (const gchar *super, const gchar *sub)
651 {
652   if (!super || !super [0])
653     return TRUE;
654   return (strcmp (super, sub) == 0);
655 }
656
657 /**
658  * atspi_event_listener_deregister:
659  * @listener: The #AtspiEventListener to deregister.
660  * @event_type: a string specifying the event type for which this
661  *             listener is to be deregistered.
662  *
663  * Deregisters an #AtspiEventListener from the registry, for a specific
664  *             event type.
665  *
666  * Returns: #TRUE if successful, otherwise #FALSE.
667  **/
668 gboolean
669 atspi_event_listener_deregister (AtspiEventListener *listener,
670                                                const gchar              *event_type,
671                                                GError **error)
672 {
673   return atspi_event_listener_deregister_from_callback (listener->callback,
674                                                         listener->user_data,
675                                                         event_type, error);
676 }
677
678 /**
679  * atspi_event_listener_deregister_from_callback:
680  * @callback: (scope call): the #AtspiEventListenerCB registered against an
681  *            event type.
682  * @user_data: (closure): User data that was passed in for this callback.
683  * @event_type: a string specifying the event type for which this
684  *             listener is to be deregistered.
685  *
686  * Deregisters an #AtspiEventListenerCB from the registry, for a specific
687  *             event type.
688  *
689  * Returns: #TRUE if successful, otherwise #FALSE.
690  **/
691 gboolean
692 atspi_event_listener_deregister_from_callback (AtspiEventListenerCB callback,
693                                                void *user_data,
694                                                const gchar              *event_type,
695                                                GError **error)
696 {
697   char *category, *name, *detail;
698   GPtrArray *matchrule_array;
699   gint i;
700   GList *l;
701
702   if (!convert_event_type_to_dbus (event_type, &category, &name, &detail, &matchrule_array))
703   {
704     return FALSE;
705   }
706   if (!callback)
707     {
708       return FALSE;
709     }
710
711   for (l = event_listeners; l;)
712   {
713     EventListenerEntry *e = l->data;
714     if (e->callback == callback &&
715         e->user_data == user_data &&
716         is_superset (category, e->category) &&
717         is_superset (name, e->name) &&
718         is_superset (detail, e->detail))
719     {
720       gboolean need_replace;
721       DBusMessage *message, *reply;
722       need_replace = (l == event_listeners);
723       l = g_list_remove (l, e);
724       if (need_replace)
725         event_listeners = l;
726       for (i = 0; i < matchrule_array->len; i++)
727       {
728         char *matchrule = g_ptr_array_index (matchrule_array, i);
729         dbus_bus_remove_match (_atspi_bus(), matchrule, NULL);
730       }
731       message = dbus_message_new_method_call (atspi_bus_registry,
732             atspi_path_registry,
733             atspi_interface_registry,
734             "DeregisterEvent");
735       if (!message)
736       return FALSE;
737       dbus_message_append_args (message, DBUS_TYPE_STRING, &event_type, DBUS_TYPE_INVALID);
738       reply = _atspi_dbus_send_with_reply_and_block (message, error);
739       if (reply)
740         dbus_message_unref (reply);
741
742       listener_entry_free (e);
743     }
744     else l = g_list_next (l);
745   }
746   g_free (category);
747   g_free (name);
748   if (detail) g_free (detail);
749   for (i = 0; i < matchrule_array->len; i++)
750     g_free (g_ptr_array_index (matchrule_array, i));
751   g_ptr_array_free (matchrule_array, TRUE);
752   return TRUE;
753 }
754
755 /**
756  * atspi_event_listener_deregister_no_data:
757  * @callback: (scope call): the #AtspiEventListenerSimpleCB registered against
758  *            an event type.
759  * @event_type: a string specifying the event type for which this
760  *             listener is to be deregistered.
761  *
762  * deregisters an #AtspiEventListenerSimpleCB from the registry, for a specific
763  *             event type.
764  *
765  * Returns: #TRUE if successful, otherwise #FALSE.
766  **/
767 gboolean
768 atspi_event_listener_deregister_no_data (AtspiEventListenerSimpleCB callback,
769                                    const gchar              *event_type,
770                                    GError **error)
771 {
772   return atspi_event_listener_deregister_from_callback (remove_datum, callback,
773                                                         event_type,
774                                                         error);
775 }
776
777 static AtspiEvent *
778 atspi_event_copy (AtspiEvent *src)
779 {
780   AtspiEvent *dst = g_new0 (AtspiEvent, 1);
781   dst->type = g_strdup (src->type);
782   dst->source = g_object_ref (src->source);
783   dst->detail1 = src->detail1;
784   dst->detail2 = src->detail2;
785   g_value_init (&dst->any_data, G_VALUE_TYPE (&src->any_data));
786   g_value_copy (&src->any_data, &dst->any_data);
787   return dst;
788 }
789
790 static void
791 atspi_event_free (AtspiEvent *event)
792 {
793   g_object_unref (event->source);
794   g_free (event->type);
795   g_value_unset (&event->any_data);
796   g_free (event);
797 }
798
799 static gboolean
800 detail_matches_listener (const char *event_detail, const char *listener_detail)
801 {
802   if (!listener_detail)
803     return TRUE;
804
805   return !(listener_detail [strcspn (listener_detail, ":")] == '\0'
806                ? strncmp (listener_detail, event_detail,
807                           strcspn (event_detail, ":"))
808                : strcmp (listener_detail, event_detail));
809 }
810
811 void
812 _atspi_send_event (AtspiEvent *e)
813 {
814   char *category, *name, *detail;
815   GList *l;
816   GList *called_listeners = NULL;
817
818   /* Ensure that the value is set to avoid a Python exception */
819   /* TODO: Figure out how to do this without using a private field */
820   if (e->any_data.g_type == 0)
821   {
822     g_value_init (&e->any_data, G_TYPE_INT);
823     g_value_set_int (&e->any_data, 0);
824   }
825
826   if (!convert_event_type_to_dbus (e->type, &category, &name, &detail, NULL))
827   {
828     g_warning ("Atspi: Couldn't parse event: %s\n", e->type);
829     return;
830   }
831   for (l = event_listeners; l; l = g_list_next (l))
832   {
833     EventListenerEntry *entry = l->data;
834     if (!strcmp (category, entry->category) &&
835         (entry->name == NULL || !strcmp (name, entry->name)) &&
836         detail_matches_listener (detail, entry->detail))
837     {
838       GList *l2;
839       for (l2 = called_listeners; l2; l2 = l2->next)
840       {
841         EventListenerEntry *e2 = l2->data;
842         if (entry->callback == e2->callback && entry->user_data == e2->user_data)
843           break;
844       }
845       if (!l2)
846       {
847         entry->callback (atspi_event_copy (e), entry->user_data);
848         called_listeners = g_list_prepend (called_listeners, entry);
849       }
850     }
851   }
852   if (detail) g_free (detail);
853   g_free (name);
854   g_free (category);
855   g_list_free (called_listeners);
856 }
857
858 DBusHandlerResult
859 _atspi_dbus_handle_event (DBusConnection *bus, DBusMessage *message, void *data)
860 {
861   char *detail = NULL;
862   const char *category = dbus_message_get_interface (message);
863   const char *member = dbus_message_get_member (message);
864   const char *signature = dbus_message_get_signature (message);
865   gchar *name;
866   gchar *converted_type;
867   DBusMessageIter iter, iter_variant;
868   dbus_message_iter_init (message, &iter);
869   AtspiEvent e;
870   dbus_int32_t detail1, detail2;
871   char *p;
872
873   if (strcmp (signature, "siiv(so)") != 0)
874   {
875     g_warning ("Got invalid signature %s for signal %s from interface %s\n", signature, member, category);
876     return DBUS_HANDLER_RESULT_HANDLED;
877   }
878
879   memset (&e, 0, sizeof (e));
880
881   if (category)
882   {
883     category = g_utf8_strrchr (category, -1, '.');
884     if (category == NULL)
885     {
886       // TODO: Error
887       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
888     }
889     category++;
890   }
891   dbus_message_iter_get_basic (&iter, &detail);
892   dbus_message_iter_next (&iter);
893   dbus_message_iter_get_basic (&iter, &detail1);
894   e.detail1 = detail1;
895   dbus_message_iter_next (&iter);
896   dbus_message_iter_get_basic (&iter, &detail2);
897   e.detail2 = detail2;
898   dbus_message_iter_next (&iter);
899
900   converted_type = convert_name_from_dbus (category, FALSE);
901   name = convert_name_from_dbus (member, FALSE);
902   detail = convert_name_from_dbus (detail, TRUE);
903
904   if (strcasecmp  (category, name) != 0)
905   {
906     p = g_strconcat (converted_type, ":", name, NULL);
907     g_free (converted_type);
908     converted_type = p;
909   }
910   else if (detail [0] == '\0')
911   {
912     p = g_strconcat (converted_type, ":",  NULL);
913     g_free (converted_type);
914     converted_type = p;
915   }
916
917   if (detail[0] != '\0')
918   {
919     p = g_strconcat (converted_type, ":", detail, NULL);
920     g_free (converted_type);
921     converted_type = p;
922   }
923   e.type = converted_type;
924   e.source = _atspi_ref_accessible (dbus_message_get_sender(message), dbus_message_get_path(message));
925
926   dbus_message_iter_recurse (&iter, &iter_variant);
927   switch (dbus_message_iter_get_arg_type (&iter_variant))
928   {
929     case DBUS_TYPE_STRUCT:
930     {
931       AtspiRect rect;
932       if (demarshal_rect (&iter_variant, &rect))
933       {
934         g_value_init (&e.any_data, ATSPI_TYPE_RECT);
935         g_value_set_boxed (&e.any_data, &rect);
936       }
937       else
938       {
939         AtspiAccessible *accessible;
940         accessible = _atspi_dbus_return_accessible_from_iter (&iter_variant);
941         g_value_init (&e.any_data, ATSPI_TYPE_ACCESSIBLE);
942         g_value_set_instance (&e.any_data, accessible);
943         if (accessible)
944           g_object_unref (accessible);  /* value now owns it */
945       }
946       break;
947     }
948     case DBUS_TYPE_STRING:
949     {
950       dbus_message_iter_get_basic (&iter_variant, &p);
951       g_value_init (&e.any_data, G_TYPE_STRING);
952       g_value_set_string (&e.any_data, p);
953       break;
954     }
955   default:
956     break;
957   }
958
959   if (!strncmp (e.type, "object:children-changed", 23))
960   {
961     cache_process_children_changed (&e);
962   }
963   else if (!strncmp (e.type, "object:property-change", 22))
964   {
965     cache_process_property_change (&e);
966   }
967   else if (!strncmp (e.type, "object:state-changed", 20))
968   {
969     cache_process_state_changed (&e);
970   }
971   else if (!strncmp (e.type, "focus", 5))
972   {
973     /* BGO#663992 - TODO: figure out the real problem */
974     e.source->cached_properties &= ~(ATSPI_CACHE_STATES);
975   }
976
977   _atspi_send_event (&e);
978
979   g_free (converted_type);
980   g_free (name);
981   g_free (detail);
982   g_object_unref (e.source);
983   g_value_unset (&e.any_data);
984   return DBUS_HANDLER_RESULT_HANDLED;
985 }
986
987 G_DEFINE_BOXED_TYPE (AtspiEvent, atspi_event, atspi_event_copy, atspi_event_free)