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