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