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