Update cache in response to role change notifications
[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     event->source->children = g_list_insert (event->source->children,
213                                              g_object_ref (child),
214                                              event->detail1);
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   else if (!strcmp (event->type, "object:property-change:accessible-role"))
274   {
275     if (G_VALUE_HOLDS_INT (&event->any_data))
276     {
277       event->source->role = g_value_get_int (&event->any_data);
278       _atspi_accessible_add_cache (event->source, ATSPI_CACHE_ROLE);
279     }
280     else
281     {
282       event->source->cached_properties &= ~ATSPI_CACHE_ROLE;
283     }
284   }
285 }
286
287 static void
288 cache_process_state_changed (AtspiEvent *event)
289 {
290   if (event->source->states)
291     atspi_state_set_set_by_name (event->source->states, event->type + 21,
292                                  event->detail1);
293 }
294
295 static dbus_bool_t
296 demarshal_rect (DBusMessageIter *iter, AtspiRect *rect)
297 {
298   dbus_int32_t x, y, width, height;
299   DBusMessageIter iter_struct;
300
301   dbus_message_iter_recurse (iter, &iter_struct);
302   if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32) return FALSE;
303   dbus_message_iter_get_basic (&iter_struct, &x);
304   dbus_message_iter_next (&iter_struct);
305   if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32) return FALSE;
306   dbus_message_iter_get_basic (&iter_struct, &y);
307   dbus_message_iter_next (&iter_struct);
308   if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32) return FALSE;
309   dbus_message_iter_get_basic (&iter_struct, &width);
310   dbus_message_iter_next (&iter_struct);
311   if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32) return FALSE;
312   dbus_message_iter_get_basic (&iter_struct, &height);
313   rect->x = x;
314   rect->y = y;
315   rect->width = width;
316   rect->height = height;
317   return TRUE;
318 }
319
320 static gchar *
321 strdup_and_adjust_for_dbus (const char *s)
322 {
323   gchar *d = g_strdup (s);
324   gchar *p;
325   int parts = 0;
326
327   if (!d)
328     return NULL;
329
330   for (p = d; *p; p++)
331   {
332     if (*p == '-')
333     {
334       memmove (p, p + 1, g_utf8_strlen (p, -1));
335       *p = toupper (*p);
336     }
337     else if (*p == ':')
338     {
339       parts++;
340       if (parts == 2)
341         break;
342       p [1] = toupper (p [1]);
343     }
344   }
345
346   d [0] = toupper (d [0]);
347   return d;
348 }
349
350 static gboolean
351 convert_event_type_to_dbus (const char *eventType, char **categoryp, char **namep, char **detailp, GPtrArray **matchrule_array)
352 {
353   gchar *tmp = strdup_and_adjust_for_dbus (eventType);
354   char *category = NULL, *name = NULL, *detail = NULL;
355   char *saveptr = NULL;
356
357   if (tmp == NULL) return FALSE;
358   category = strtok_r (tmp, ":", &saveptr);
359   if (category) category = g_strdup (category);
360   name = strtok_r (NULL, ":", &saveptr);
361   if (name)
362   {
363     name = g_strdup (name);
364     detail = strtok_r (NULL, ":", &saveptr);
365     if (detail) detail = g_strdup (detail);
366   }
367   if (matchrule_array)
368   {
369     gchar *matchrule;
370     matchrule = g_strdup_printf ("type='signal',interface='org.a11y.atspi.Event.%s'", category);
371     if (name && name [0])
372     {
373       gchar *new_str = g_strconcat (matchrule, ",member='", name, "'", NULL);
374       g_free (matchrule);
375       matchrule = new_str;
376     }
377     (*matchrule_array) = g_ptr_array_new ();
378     if (detail && detail [0])
379     {
380       gchar *new_str = g_strconcat (matchrule, ",arg0='", detail, "'", NULL);
381       g_ptr_array_add (*matchrule_array, new_str);
382       new_str = g_strconcat (matchrule, ",arg0path='", detail, "/'", NULL);
383       g_ptr_array_add (*matchrule_array, new_str);
384       g_free (matchrule);
385     }
386     else
387       g_ptr_array_add (*matchrule_array, matchrule);
388   }
389   if (categoryp) *categoryp = category;
390   else g_free (category);
391   if (namep) *namep = name;
392   else if (name) g_free (name);
393   if (detailp) *detailp = detail;
394   else if (detail) g_free (detail);
395   g_free (tmp);
396   return TRUE;
397 }
398
399 static void
400 listener_entry_free (EventListenerEntry *e)
401 {
402   gpointer callback = (e->callback == remove_datum ? (gpointer)e->user_data : (gpointer)e->callback);
403   g_free (e->category);
404   g_free (e->name);
405   if (e->detail) g_free (e->detail);
406   callback_unref (callback);
407   g_free (e);
408 }
409
410 /**
411  * atspi_event_listener_register:
412  * @listener: The #AtspiEventListener to register against an event type.
413  * @event_type: a character string indicating the type of events for which
414  *            notification is requested.  Format is
415  *            EventClass:major_type:minor_type:detail
416  *            where all subfields other than EventClass are optional.
417  *            EventClasses include "object", "window", "mouse",
418  *            and toolkit events (e.g. "Gtk", "AWT").
419  *            Examples: "focus:", "Gtk:GtkWidget:button_press_event".
420  *
421  * Adds an in-process callback function to an existing #AtspiEventListener.
422  *
423  * Legal object event types:
424  *
425  *    (property change events)
426  *
427  *            object:property-change
428  *            object:property-change:accessible-name
429  *            object:property-change:accessible-description
430  *            object:property-change:accessible-parent
431  *            object:property-change:accessible-value
432  *            object:property-change:accessible-role
433  *            object:property-change:accessible-table-caption
434  *            object:property-change:accessible-table-column-description
435  *            object:property-change:accessible-table-column-header
436  *            object:property-change:accessible-table-row-description
437  *            object:property-change:accessible-table-row-header
438  *            object:property-change:accessible-table-summary
439  *
440  *    (other object events)
441  *
442  *            object:state-changed 
443  *            object:children-changed
444  *            object:visible-data-changed
445  *            object:selection-changed
446  *            object:text-selection-changed
447  *            object:text-changed
448  *            object:text-caret-moved
449  *            object:row-inserted
450  *            object:row-reordered
451  *            object:row-deleted
452  *            object:column-inserted
453  *            object:column-reordered
454  *            object:column-deleted
455  *            object:model-changed
456  *            object:active-descendant-changed
457  *
458  *  (window events)
459  *
460  *            window:minimize
461  *            window:maximize
462  *            window:restore
463  *            window:close
464  *            window:create
465  *            window:reparent
466  *            window:desktop-create
467  *            window:desktop-destroy
468  *            window:activate
469  *            window:deactivate
470  *            window:raise
471  *            window:lower
472  *            window:move
473  *            window:resize
474  *            window:shade
475  *            window:unshade
476  *            window:restyle
477  *
478  *  (other events)
479  *
480  *            focus:
481  *            mouse:abs
482  *            mouse:rel
483  *            mouse:b1p
484  *            mouse:b1r
485  *            mouse:b2p
486  *            mouse:b2r
487  *            mouse:b3p
488  *            mouse:b3r
489  *
490  * NOTE: this character string may be UTF-8, but should not contain byte 
491  * value 56
492  *            (ascii ':'), except as a delimiter, since non-UTF-8 string
493  *            delimiting functions are used internally.
494  *            In general, listening to
495  *            toolkit-specific events is not recommended.
496  *
497  *
498  * Returns: #TRUE if successful, otherwise #FALSE.
499  **/
500 gboolean
501 atspi_event_listener_register (AtspiEventListener *listener,
502                                              const gchar              *event_type,
503                                              GError **error)
504 {
505   /* TODO: Keep track of which events have been registered, so that we
506  * deregister all of them when the event listener is destroyed */
507
508   return atspi_event_listener_register_from_callback (listener->callback,
509                                                       listener->user_data,
510                                                       listener->cb_destroyed,
511                                                       event_type, error);
512 }
513
514 /**
515  * atspi_event_listener_register_from_callback:
516  * @callback: (scope notified): the #AtspiEventListenerCB to be registered 
517  * against an event type.
518  * @user_data: (closure): User data to be passed to the callback.
519  * @callback_destroyed: A #GDestroyNotify called when the callback is destroyed.
520  * @event_type: a character string indicating the type of events for which
521  *            notification is requested.  See #atspi_event_listener_register
522  * for a description of the format.
523  * 
524  * Registers an #AtspiEventListenerCB against an @event_type.
525  *
526  * Returns: #TRUE if successfull, otherwise #FALSE.
527  *
528  **/
529 gboolean
530 atspi_event_listener_register_from_callback (AtspiEventListenerCB callback,
531                                              void *user_data,
532                                              GDestroyNotify callback_destroyed,
533                                              const gchar              *event_type,
534                                              GError **error)
535 {
536   EventListenerEntry *e;
537   DBusError d_error;
538   DBusMessage *message, *reply;
539   GPtrArray *matchrule_array;
540   gint i;
541
542   if (!callback)
543     {
544       return FALSE;
545     }
546
547   if (!event_type)
548   {
549     g_warning ("called atspi_event_listener_register_from_callback with a NULL event_type");
550     return FALSE;
551   }
552
553   e = g_new (EventListenerEntry, 1);
554   e->callback = callback;
555   e->user_data = user_data;
556   e->callback_destroyed = callback_destroyed;
557   callback_ref (callback == remove_datum ? (gpointer)user_data : (gpointer)callback,
558                 callback_destroyed);
559   if (!convert_event_type_to_dbus (event_type, &e->category, &e->name, &e->detail, &matchrule_array))
560   {
561     g_free (e);
562     return FALSE;
563   }
564   event_listeners = g_list_prepend (event_listeners, e);
565   for (i = 0; i < matchrule_array->len; i++)
566   {
567     char *matchrule = g_ptr_array_index (matchrule_array, i);
568     dbus_error_init (&d_error);
569     dbus_bus_add_match (_atspi_bus(), matchrule, &d_error);
570     if (dbus_error_is_set (&d_error))
571       {
572         g_warning ("Atspi: Adding match: %s", d_error.message);
573         dbus_error_free (&d_error);
574         /* TODO: Set error */
575       }
576
577     g_free (matchrule);
578   }
579   g_ptr_array_free (matchrule_array, TRUE);
580
581   message = dbus_message_new_method_call (atspi_bus_registry,
582         atspi_path_registry,
583         atspi_interface_registry,
584         "RegisterEvent");
585   if (!message)
586     return FALSE;
587   dbus_message_append_args (message, DBUS_TYPE_STRING, &event_type, DBUS_TYPE_INVALID);
588   reply = _atspi_dbus_send_with_reply_and_block (message, error);
589   if (reply)
590     dbus_message_unref (reply);
591
592   return TRUE;
593 }
594
595 /**
596  * atspi_event_listener_register_no_data:
597  * @callback: (scope notified): the #AtspiEventListenerSimpleCB to be
598  *            registered against an event type.
599  * @callback_destroyed: A #GDestroyNotify called when the callback is destroyed.
600  * @event_type: a character string indicating the type of events for which
601  *            notification is requested.  Format is
602  *            EventClass:major_type:minor_type:detail
603  *            where all subfields other than EventClass are optional.
604  *            EventClasses include "object", "window", "mouse",
605  *            and toolkit events (e.g. "Gtk", "AWT").
606  *            Examples: "focus:", "Gtk:GtkWidget:button_press_event".
607  *
608  * Registers an #AtspiEventListenetSimpleCB. The method is similar to 
609  * #atspi_event_listener_register, but @callback takes no user_data.
610  *
611  * Returns: #TRUE if successfull, otherwise #FALSE.
612  **/
613 gboolean
614 atspi_event_listener_register_no_data (AtspiEventListenerSimpleCB callback,
615                                  GDestroyNotify callback_destroyed,
616                                  const gchar              *event_type,
617                                  GError **error)
618 {
619   return atspi_event_listener_register_from_callback (remove_datum, callback,
620                                                       callback_destroyed,
621                                                       event_type, error);
622 }
623
624 static gboolean
625 is_superset (const gchar *super, const gchar *sub)
626 {
627   if (!super || !super [0])
628     return TRUE;
629   return (strcmp (super, sub) == 0);
630 }
631
632 /**
633  * atspi_event_listener_deregister:
634  * @listener: The #AtspiEventListener to deregister.
635  * @event_type: a string specifying the event type for which this
636  *             listener is to be deregistered.
637  *
638  * Deregisters an #AtspiEventListener from the registry, for a specific
639  *             event type.
640  *
641  * Returns: #TRUE if successful, otherwise #FALSE.
642  **/
643 gboolean
644 atspi_event_listener_deregister (AtspiEventListener *listener,
645                                                const gchar              *event_type,
646                                                GError **error)
647 {
648   return atspi_event_listener_deregister_from_callback (listener->callback,
649                                                         listener->user_data,
650                                                         event_type, error);
651 }
652
653 /**
654  * atspi_event_listener_deregister_from_callback:
655  * @callback: (scope call): the #AtspiEventListenerCB registered against an
656  *            event type.
657  * @user_data: (closure): User data that was passed in for this callback.
658  * @event_type: a string specifying the event type for which this
659  *             listener is to be deregistered.
660  *
661  * Deregisters an #AtspiEventListenerCB from the registry, for a specific
662  *             event type.
663  *
664  * Returns: #TRUE if successful, otherwise #FALSE.
665  **/
666 gboolean
667 atspi_event_listener_deregister_from_callback (AtspiEventListenerCB callback,
668                                                void *user_data,
669                                                const gchar              *event_type,
670                                                GError **error)
671 {
672   char *category, *name, *detail;
673   GPtrArray *matchrule_array;
674   gint i;
675   GList *l;
676
677   if (!convert_event_type_to_dbus (event_type, &category, &name, &detail, &matchrule_array))
678   {
679     return FALSE;
680   }
681   if (!callback)
682     {
683       return FALSE;
684     }
685
686   for (l = event_listeners; l;)
687   {
688     EventListenerEntry *e = l->data;
689     if (e->callback == callback &&
690         e->user_data == user_data &&
691         is_superset (category, e->category) &&
692         is_superset (name, e->name) &&
693         is_superset (detail, e->detail))
694     {
695       gboolean need_replace;
696       DBusMessage *message, *reply;
697       need_replace = (l == event_listeners);
698       l = g_list_remove (l, e);
699       if (need_replace)
700         event_listeners = l;
701       for (i = 0; i < matchrule_array->len; i++)
702       {
703         char *matchrule = g_ptr_array_index (matchrule_array, i);
704         dbus_bus_remove_match (_atspi_bus(), matchrule, NULL);
705       }
706       message = dbus_message_new_method_call (atspi_bus_registry,
707             atspi_path_registry,
708             atspi_interface_registry,
709             "DeregisterEvent");
710       if (!message)
711       return FALSE;
712       dbus_message_append_args (message, DBUS_TYPE_STRING, &event_type, DBUS_TYPE_INVALID);
713       reply = _atspi_dbus_send_with_reply_and_block (message, error);
714       if (reply)
715         dbus_message_unref (reply);
716
717       listener_entry_free (e);
718     }
719     else l = g_list_next (l);
720   }
721   g_free (category);
722   g_free (name);
723   if (detail) g_free (detail);
724   for (i = 0; i < matchrule_array->len; i++)
725     g_free (g_ptr_array_index (matchrule_array, i));
726   g_ptr_array_free (matchrule_array, TRUE);
727   return TRUE;
728 }
729
730 /**
731  * atspi_event_listener_deregister_no_data:
732  * @callback: (scope call): the #AtspiEventListenerSimpleCB registered against
733  *            an event type.
734  * @event_type: a string specifying the event type for which this
735  *             listener is to be deregistered.
736  *
737  * deregisters an #AtspiEventListenerSimpleCB from the registry, for a specific
738  *             event type.
739  *
740  * Returns: #TRUE if successful, otherwise #FALSE.
741  **/
742 gboolean
743 atspi_event_listener_deregister_no_data (AtspiEventListenerSimpleCB callback,
744                                    const gchar              *event_type,
745                                    GError **error)
746 {
747   return atspi_event_listener_deregister_from_callback (remove_datum, callback,
748                                                         event_type,
749                                                         error);
750 }
751
752 static AtspiEvent *
753 atspi_event_copy (AtspiEvent *src)
754 {
755   AtspiEvent *dst = g_new0 (AtspiEvent, 1);
756   dst->type = g_strdup (src->type);
757   dst->source = g_object_ref (src->source);
758   dst->detail1 = src->detail1;
759   dst->detail2 = src->detail2;
760   g_value_init (&dst->any_data, G_VALUE_TYPE (&src->any_data));
761   g_value_copy (&src->any_data, &dst->any_data);
762   return dst;
763 }
764
765 static void
766 atspi_event_free (AtspiEvent *event)
767 {
768   g_object_unref (event->source);
769   g_free (event->type);
770   g_value_unset (&event->any_data);
771   g_free (event);
772 }
773
774 static gboolean
775 detail_matches_listener (const char *event_detail, const char *listener_detail)
776 {
777   if (!listener_detail)
778     return TRUE;
779
780   return !(listener_detail [strcspn (listener_detail, ":")] == '\0'
781                ? strncmp (listener_detail, event_detail,
782                           strcspn (event_detail, ":"))
783                : strcmp (listener_detail, event_detail));
784 }
785
786 void
787 _atspi_send_event (AtspiEvent *e)
788 {
789   char *category, *name, *detail;
790   GList *l;
791
792   /* Ensure that the value is set to avoid a Python exception */
793   /* TODO: Figure out how to do this without using a private field */
794   if (e->any_data.g_type == 0)
795   {
796     g_value_init (&e->any_data, G_TYPE_INT);
797     g_value_set_int (&e->any_data, 0);
798   }
799
800   if (!convert_event_type_to_dbus (e->type, &category, &name, &detail, NULL))
801   {
802     g_warning ("Atspi: Couldn't parse event: %s\n", e->type);
803     return;
804   }
805   for (l = event_listeners; l; l = g_list_next (l))
806   {
807     EventListenerEntry *entry = l->data;
808     if (!strcmp (category, entry->category) &&
809         (entry->name == NULL || !strcmp (name, entry->name)) &&
810         detail_matches_listener (detail, entry->detail))
811     {
812         entry->callback (atspi_event_copy (e), entry->user_data);
813     }
814   }
815   if (detail) g_free (detail);
816   g_free (name);
817   g_free (category);
818 }
819
820 DBusHandlerResult
821 _atspi_dbus_handle_event (DBusConnection *bus, DBusMessage *message, void *data)
822 {
823   char *detail = NULL;
824   const char *category = dbus_message_get_interface (message);
825   const char *member = dbus_message_get_member (message);
826   const char *signature = dbus_message_get_signature (message);
827   gchar *name;
828   gchar *converted_type;
829   DBusMessageIter iter, iter_variant;
830   dbus_message_iter_init (message, &iter);
831   AtspiEvent e;
832   dbus_int32_t detail1, detail2;
833   char *p;
834
835   if (strcmp (signature, "siiv(so)") != 0)
836   {
837     g_warning ("Got invalid signature %s for signal %s from interface %s\n", signature, member, category);
838     return DBUS_HANDLER_RESULT_HANDLED;
839   }
840
841   memset (&e, 0, sizeof (e));
842
843   if (category)
844   {
845     category = g_utf8_strrchr (category, -1, '.');
846     if (category == NULL)
847     {
848       // TODO: Error
849       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
850     }
851     category++;
852   }
853   dbus_message_iter_get_basic (&iter, &detail);
854   dbus_message_iter_next (&iter);
855   dbus_message_iter_get_basic (&iter, &detail1);
856   e.detail1 = detail1;
857   dbus_message_iter_next (&iter);
858   dbus_message_iter_get_basic (&iter, &detail2);
859   e.detail2 = detail2;
860   dbus_message_iter_next (&iter);
861
862   converted_type = convert_name_from_dbus (category, FALSE);
863   name = convert_name_from_dbus (member, FALSE);
864   detail = convert_name_from_dbus (detail, TRUE);
865
866   if (strcasecmp  (category, name) != 0)
867   {
868     p = g_strconcat (converted_type, ":", name, NULL);
869     g_free (converted_type);
870     converted_type = p;
871   }
872   else if (detail [0] == '\0')
873   {
874     p = g_strconcat (converted_type, ":",  NULL);
875     g_free (converted_type);
876     converted_type = p;
877   }
878
879   if (detail[0] != '\0')
880   {
881     p = g_strconcat (converted_type, ":", detail, NULL);
882     g_free (converted_type);
883     converted_type = p;
884   }
885   e.type = converted_type;
886   e.source = _atspi_ref_accessible (dbus_message_get_sender(message), dbus_message_get_path(message));
887
888   dbus_message_iter_recurse (&iter, &iter_variant);
889   switch (dbus_message_iter_get_arg_type (&iter_variant))
890   {
891     case DBUS_TYPE_STRUCT:
892     {
893       AtspiRect rect;
894       if (demarshal_rect (&iter_variant, &rect))
895       {
896         g_value_init (&e.any_data, ATSPI_TYPE_RECT);
897         g_value_set_boxed (&e.any_data, &rect);
898       }
899       else
900       {
901         AtspiAccessible *accessible;
902         accessible = _atspi_dbus_return_accessible_from_iter (&iter_variant);
903         g_value_init (&e.any_data, ATSPI_TYPE_ACCESSIBLE);
904         g_value_set_instance (&e.any_data, accessible);
905         if (accessible)
906           g_object_unref (accessible);  /* value now owns it */
907       }
908       break;
909     }
910     case DBUS_TYPE_STRING:
911     {
912       dbus_message_iter_get_basic (&iter_variant, &p);
913       g_value_init (&e.any_data, G_TYPE_STRING);
914       g_value_set_string (&e.any_data, p);
915       break;
916     }
917   default:
918     break;
919   }
920
921   if (!strncmp (e.type, "object:children-changed", 23))
922   {
923     cache_process_children_changed (&e);
924   }
925   else if (!strncmp (e.type, "object:property-change", 22))
926   {
927     cache_process_property_change (&e);
928   }
929   else if (!strncmp (e.type, "object:state-changed", 20))
930   {
931     cache_process_state_changed (&e);
932   }
933   else if (!strncmp (e.type, "focus", 5))
934   {
935     /* BGO#663992 - TODO: figure out the real problem */
936     e.source->cached_properties &= ~(ATSPI_CACHE_STATES);
937   }
938
939   _atspi_send_event (&e);
940
941   g_free (converted_type);
942   g_free (name);
943   g_free (detail);
944   g_object_unref (e.source);
945   g_value_unset (&e.any_data);
946   return DBUS_HANDLER_RESULT_HANDLED;
947 }
948
949 G_DEFINE_BOXED_TYPE (AtspiEvent, atspi_event, atspi_event_copy, atspi_event_free)