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