Various fixes
[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  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 #include "atspi-private.h"
25 #include <string.h>
26
27 typedef struct
28 {
29   AtspiEventListenerCB callback;
30   void *user_data;
31   GDestroyNotify callback_destroyed;
32   char *category;
33   char *name;
34   char *detail;
35 } EventListenerEntry;
36
37 /*
38  * Misc. helpers.
39  */
40
41 /*
42  * Standard event dispatcher
43  */
44
45 static guint listener_id = 0;
46 static GList *event_listeners = NULL;
47
48 static gchar *
49 convert_name_from_dbus (const char *name)
50 {
51   gchar *ret = g_malloc (g_utf8_strlen (name, -1) * 2 + 1);
52   const char *p = name;
53   gchar *q = ret;
54
55   if (!ret)
56     return NULL;
57
58   while (*p)
59   {
60     if (isupper (*p))
61     {
62       if (q > ret)
63         *q++ = '-';
64       *q++ = tolower (*p++);
65     }
66     else
67       *q++ = *p++;
68   }
69   *q = '\0';
70   return ret;
71 }
72
73 static void
74 cache_process_children_changed (AtspiEvent *event)
75 {
76   AtspiAccessible *child;
77
78   if (!G_VALUE_HOLDS (&event->any_data, ATSPI_TYPE_ACCESSIBLE) ||
79       !event->source->children ||
80       atspi_state_set_contains (event->source->states, ATSPI_STATE_MANAGES_DESCENDANTS))
81     return;
82
83   child = g_value_get_object (&event->any_data);
84
85   if (!strncmp (event->type, "object:children-changed:add", 27))
86   {
87     GList *new_list = g_list_insert (event->source->children, g_object_ref (child), event->detail1);
88     if (new_list)
89       event->source->children = new_list;
90   }
91   else if (g_list_find (event->source->children, child))
92   {
93     event->source->children = g_list_remove (event->source->children, child);
94   }
95 }
96
97 static void
98 cache_process_property_change (AtspiEvent *event)
99 {
100   if (!strcmp (event->type, "object:property-change:accessible-parent"))
101   {
102     if (event->source->accessible_parent)
103       g_object_unref (event->source->accessible_parent);
104     if (G_VALUE_HOLDS (&event->any_data, ATSPI_TYPE_ACCESSIBLE))
105     {
106       event->source->accessible_parent = g_value_dup_object (&event->any_data);
107       event->source->cached_properties |= ATSPI_CACHE_PARENT;
108     }
109     else
110     {
111       event->source->accessible_parent = NULL;
112       event->source->cached_properties &= ~ATSPI_CACHE_PARENT;
113     }
114   }
115   else if (!strcmp (event->type, "object:property-change:accessible-name"))
116   {
117     if (event->source->name)
118       g_free (event->source->name);
119     if (G_VALUE_HOLDS_STRING (&event->any_data))
120     {
121       event->source->name = g_value_dup_string (&event->any_data);
122       event->source->cached_properties |= ATSPI_CACHE_NAME;
123     }
124     else
125     {
126       event->source->name = NULL;
127       event->source->cached_properties &= ~ATSPI_CACHE_NAME;
128     }
129   }
130   else if (!strcmp (event->type, "object:property-change:accessible-description"))
131   {
132     if (event->source->description)
133       g_free (event->source->description);
134     if (G_VALUE_HOLDS_STRING (&event->any_data))
135     {
136       event->source->description = g_value_dup_string (&event->any_data);
137       event->source->cached_properties |= ATSPI_CACHE_DESCRIPTION;
138     }
139     else
140     {
141       event->source->description = NULL;
142       event->source->cached_properties &= ~ATSPI_CACHE_DESCRIPTION;
143     }
144   }
145 }
146
147 static void
148 cache_process_state_changed (AtspiEvent *event)
149 {
150   atspi_state_set_set_by_name (event->source->states, event->type + 21, event->detail1);
151 }
152
153 static dbus_bool_t
154 demarshal_rect (DBusMessageIter *iter, AtspiRect *rect)
155 {
156   dbus_int32_t x, y, width, height;
157   DBusMessageIter iter_struct;
158
159   dbus_message_iter_recurse (iter, &iter_struct);
160   if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32) return FALSE;
161   dbus_message_iter_get_basic (&iter_struct, &x);
162   dbus_message_iter_next (&iter_struct);
163   if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32) return FALSE;
164   dbus_message_iter_get_basic (&iter_struct, &y);
165   dbus_message_iter_next (&iter_struct);
166   if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32) return FALSE;
167   dbus_message_iter_get_basic (&iter_struct, &width);
168   dbus_message_iter_next (&iter_struct);
169   if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32) return FALSE;
170   dbus_message_iter_get_basic (&iter_struct, &height);
171   rect->x = x;
172   rect->y = y;
173   rect->width = width;
174   rect->height = height;
175   return TRUE;
176 }
177
178 static gchar *
179 strdup_and_adjust_for_dbus (const char *s)
180 {
181   gchar *d = g_strdup (s);
182   gchar *p;
183
184   if (!d)
185     return NULL;
186
187   for (p = d; *p; p++)
188   {
189     if (*p == '-')
190     {
191       memmove (p, p + 1, g_utf8_strlen (p, -1));
192       *p = toupper (*p);
193     }
194     else if (*p == ':')
195     {
196       p [1] = toupper (p [1]);
197     }
198   }
199
200   d [0] = toupper (d [0]);
201   return d;
202 }
203
204 static gboolean
205 convert_event_type_to_dbus (const char *eventType, char **categoryp, char **namep, char **detailp, char **matchrule)
206 {
207   gchar *tmp = strdup_and_adjust_for_dbus (eventType);
208   char *category = NULL, *name = NULL, *detail = NULL;
209   char *saveptr = NULL;
210   char *p;
211
212   if (tmp == NULL) return FALSE;
213   category = strtok_r (tmp, ":", &saveptr);
214   if (category) category = g_strdup (category);
215   if (!category) goto oom;
216   name = strtok_r (NULL, ":", &saveptr);
217   if (name)
218   {
219     name = g_strdup (name);
220     if (!name) goto oom;
221     detail = strtok_r (NULL, ":", &saveptr);
222     if (detail) detail = g_strdup (detail);
223   }
224   if (matchrule)
225   {
226     *matchrule = g_strdup_printf ("type='signal',interface='org.a11y.atspi.Event.%s'", category);
227     if (!*matchrule) goto oom;
228     if (name && name [0])
229     {
230       gchar *new_str = g_strconcat (*matchrule, ",member='", name, "'", NULL);
231       if (new_str)
232       {
233         g_free (*matchrule);
234         *matchrule = new_str;
235       }
236     }
237     if (detail && detail [0])
238     {
239       gchar *new_str = g_strconcat (*matchrule, ",arg0='", detail, "'", NULL);
240       if (new_str)
241       {
242         g_free (*matchrule);
243         *matchrule = new_str;
244       }
245     }
246   }
247   if (categoryp) *categoryp = category;
248   else g_free (category);
249   if (namep) *namep = name;
250   else if (name) g_free (name);
251   if (detailp) *detailp = detail;
252   else if (detail) g_free (detail);
253   g_free (tmp);
254   return TRUE;
255 oom:
256   if (tmp) g_free (tmp);
257   if (category) g_free (category);
258   if (name) g_free (name);
259   if (detail) g_free (detail);
260   return FALSE;
261 }
262
263 static void
264 listener_entry_free (EventListenerEntry *e)
265 {
266   g_free (e->category);
267   g_free (e->name);
268   if (e->detail) g_free (e->detail);
269   if (e->callback_destroyed)
270     (*e->callback_destroyed) (e->callback);
271   g_free (e);
272 }
273
274 /**
275  * atspi_event_listener_register:
276  * @callback: (scope notified): the #AtspiEventListenerCB to be registered against
277  *            an event type.
278  * @user_data: (closure): User data to be passed to the callback.
279  * @callback_destroyed: A #GDestroyNotify called when the callback is destroyed.
280  * @event_type: a character string indicating the type of events for which
281  *            notification is requested.  Format is
282  *            EventClass:major_type:minor_type:detail
283  *            where all subfields other than EventClass are optional.
284  *            EventClasses include "object", "window", "mouse",
285  *            and toolkit events (e.g. "Gtk", "AWT").
286  *            Examples: "focus:", "Gtk:GtkWidget:button_press_event".
287  *
288  * Legal object event types:
289  *
290  *    (property change events)
291  *
292  *            object:property-change
293  *            object:property-change:accessible-name
294  *            object:property-change:accessible-description
295  *            object:property-change:accessible-parent
296  *            object:property-change:accessible-value
297  *            object:property-change:accessible-role
298  *            object:property-change:accessible-table-caption
299  *            object:property-change:accessible-table-column-description
300  *            object:property-change:accessible-table-column-header
301  *            object:property-change:accessible-table-row-description
302  *            object:property-change:accessible-table-row-header
303  *            object:property-change:accessible-table-summary
304  *
305  *    (other object events)
306  *
307  *            object:state-changed 
308  *            object:children-changed
309  *            object:visible-data-changed
310  *            object:selection-changed
311  *            object:text-selection-changed
312  *            object:text-changed
313  *            object:text-caret-moved
314  *            object:row-inserted
315  *            object:row-reordered
316  *            object:row-deleted
317  *            object:column-inserted
318  *            object:column-reordered
319  *            object:column-deleted
320  *            object:model-changed
321  *            object:active-descendant-changed
322  *
323  *  (window events)
324  *
325  *            window:minimize
326  *            window:maximize
327  *            window:restore
328  *            window:close
329  *            window:create
330  *            window:reparent
331  *            window:desktop-create
332  *            window:desktop-destroy
333  *            window:activate
334  *            window:deactivate
335  *            window:raise
336  *            window:lower
337  *            window:move
338  *            window:resize
339  *            window:shade
340  *            window:unshade
341  *            window:restyle
342  *
343  *  (other events)
344  *
345  *            focus:
346  *            mouse:abs
347  *            mouse:rel
348  *            mouse:b1p
349  *            mouse:b1r
350  *            mouse:b2p
351  *            mouse:b2r
352  *            mouse:b3p
353  *            mouse:b3r
354  *
355  * NOTE: this string may be UTF-8, but should not contain byte value 56
356  *            (ascii ':'), except as a delimiter, since non-UTF-8 string
357  *            delimiting functions are used internally.
358  *            In general, listening to
359  *            toolkit-specific events is not recommended.
360  *
361  * Add an in-process callback function to an existing AtspiEventListener.
362  *
363  * Returns: #TRUE if successful, otherwise #FALSE.
364  **/
365 gboolean
366 atspi_event_listener_register (AtspiEventListenerCB callback,
367                                  void *user_data,
368                                  GDestroyNotify callback_destroyed,
369                                  const gchar              *event_type)
370 {
371   EventListenerEntry *e;
372   char *matchrule;
373   DBusError error;
374   GList *new_list;
375   DBusMessage *message, *reply;
376
377   if (!callback)
378     {
379       return FALSE;
380     }
381
382   e = g_new (EventListenerEntry, 1);
383   if (!e) return FALSE;
384   e->callback = callback;
385   e->user_data = user_data;
386   e->callback_destroyed = callback_destroyed;
387   if (!convert_event_type_to_dbus (event_type, &e->category, &e->name, &e->detail, &matchrule))
388   {
389     g_free (e);
390     return FALSE;
391   }
392   new_list = g_list_prepend (event_listeners, e);
393   if (!new_list)
394   {
395     listener_entry_free (e);
396     return FALSE;
397   }
398   event_listeners = new_list;
399   dbus_error_init (&error);
400   dbus_bus_add_match (_atspi_bus(), matchrule, &error);
401   if (error.message)
402   {
403     g_warning ("Atspi: Adding match: %s", error.message);
404   }
405
406   dbus_error_init (&error);
407   message = dbus_message_new_method_call (atspi_bus_registry,
408         atspi_path_registry,
409         atspi_interface_registry,
410         "RegisterEvent");
411   if (!message)
412     return;
413   dbus_message_append_args (message, DBUS_TYPE_STRING, &event_type, DBUS_TYPE_INVALID);
414   reply = _atspi_dbus_send_with_reply_and_block (message);
415   dbus_message_unref (reply);
416
417   return TRUE;
418 }
419
420 void
421 remove_datum (const AtspiEvent *event, void *user_data)
422 {
423   AtspiEventListenerSimpleCB cb = user_data;
424   cb (event);
425 }
426
427 /**
428  * atspi_event_listener_register_no_data:
429  * @callback: (scope notified): the #AtspiEventListenerSimpleCB to be
430  *            registered against an event type.
431  * @callback_destroyed: A #GDestroyNotify called when the callback is destroyed.
432  * @event_type: a character string indicating the type of events for which
433  *            notification is requested.  Format is
434  *            EventClass:major_type:minor_type:detail
435  *            where all subfields other than EventClass are optional.
436  *            EventClasses include "object", "window", "mouse",
437  *            and toolkit events (e.g. "Gtk", "AWT").
438  *            Examples: "focus:", "Gtk:GtkWidget:button_press_event".
439  *
440  * Like atspi_event_listener_register, but callback takes no user_data.
441  **/
442 gboolean
443 atspi_event_listener_register_no_data (AtspiEventListenerSimpleCB callback,
444                                  GDestroyNotify callback_destroyed,
445                                  const gchar              *event_type)
446 {
447   return atspi_event_listener_register (remove_datum, callback, callback_destroyed, event_type);
448 }
449
450 static gboolean
451 is_superset (const gchar *super, const gchar *sub)
452 {
453   if (!super || !super [0])
454     return TRUE;
455   return (strcmp (super, sub) == 0);
456 }
457
458 /**
459  * atspi_event_listener_deregister:
460  * @callback: (scope call): the #AtspiEventListenerCB registered against an
461  *            event type.
462  * @user_data: (closure): User data that was passed in for this callback.
463  * @event_type: a string specifying the event type for which this
464  *             listener is to be deregistered.
465  *
466  * deregisters an #AtspiEventListenerCB from the registry, for a specific
467  *             event type.
468  *
469  * Returns: #TRUE if successful, otherwise #FALSE.
470  **/
471 gboolean
472 atspi_event_listener_deregister (AtspiEventListenerCB callback,
473                                    void *user_data,
474                                    const gchar              *event_type)
475 {
476   char *category, *name, *detail, *matchrule;
477   GList *l;
478
479   if (!convert_event_type_to_dbus (event_type, &category, &name, &detail, &matchrule))
480   {
481     return FALSE;
482   }
483   if (!callback)
484     {
485       return FALSE;
486     }
487
488   for (l = event_listeners; l;)
489   {
490     EventListenerEntry *e = l->data;
491     if (e->callback == callback &&
492         e->user_data == user_data &&
493         is_superset (category, e->category) &&
494         is_superset (name, e->name) &&
495         is_superset (detail, e->detail))
496     {
497       DBusError error;
498       DBusMessage *message, *reply;
499       l = g_list_remove (l, e);
500       dbus_error_init (&error);
501       dbus_bus_remove_match (_atspi_bus(), matchrule, &error);
502       dbus_error_init (&error);
503       message = dbus_message_new_method_call (atspi_bus_registry,
504             atspi_path_registry,
505             atspi_interface_registry,
506             "RegisterEvent");
507       if (!message)
508       return;
509       dbus_message_append_args (message, DBUS_TYPE_STRING, &event_type, DBUS_TYPE_INVALID);
510       reply = _atspi_dbus_send_with_reply_and_block (message);
511       dbus_message_unref (reply);
512
513       listener_entry_free (e);
514     }
515     else l = g_list_next (l);
516   }
517   g_free (category);
518   g_free (name);
519   if (detail) g_free (detail);
520   g_free (matchrule);
521   return TRUE;
522 }
523
524 /**
525  * atspi_event_listener_deregister_no_data:
526  * @callback: (scope call): the #AtspiEventListenerSimpleCB registered against
527  *            an event type.
528  * @event_type: a string specifying the event type for which this
529  *             listener is to be deregistered.
530  *
531  * deregisters an #AtspiEventListenerSimpleCB from the registry, for a specific
532  *             event type.
533  *
534  * Returns: #TRUE if successful, otherwise #FALSE.
535  **/
536 gboolean
537 atspi_event_listener_deregister_no_data (AtspiEventListenerSimpleCB callback,
538                                    const gchar              *event_type)
539 {
540   return atspi_event_listener_deregister (remove_datum, callback, event_type);
541 }
542
543 void
544 _atspi_send_event (AtspiEvent *e)
545 {
546   char *category, *name, *detail;
547   GList *l;
548
549   /* Ensure that the value is set to avoid a Python exception */
550   /* TODO: Figure out how to do this without using a private field */
551   if (e->any_data.g_type == 0)
552     g_value_set_int (&e->any_data, 0);
553
554   if (!convert_event_type_to_dbus (e->type, &category, &name, &detail, NULL))
555   {
556     g_warning ("Atspi: Couldn't parse event: %s\n", e->type);
557     return;
558   }
559   for (l = event_listeners; l; l = g_list_next (l))
560   {
561     EventListenerEntry *entry = l->data;
562     if (!strcmp (category, entry->category) &&
563         (entry->name == NULL || !strcmp (name, entry->name)) &&
564         (entry->detail == NULL || !strcmp (detail, entry->detail)))
565     {
566         entry->callback (e, entry->user_data);
567     }
568   }
569   if (detail) g_free (detail);
570   g_free (name);
571   g_free (category);
572 }
573
574 DBusHandlerResult
575 atspi_dbus_handle_event (DBusConnection *bus, DBusMessage *message, void *data)
576 {
577   char *detail = NULL;
578   const char *category = dbus_message_get_interface (message);
579   const char *member = dbus_message_get_member (message);
580   gchar *name;
581   gchar *converted_type;
582   DBusMessageIter iter, iter_variant;
583   dbus_message_iter_init (message, &iter);
584   AtspiEvent e;
585   dbus_int32_t detail1, detail2;
586   char *p;
587
588   memset (&e, 0, sizeof (e));
589
590   if (category)
591   {
592     category = g_utf8_strrchr (category, -1, '.');
593     if (category == NULL)
594     {
595       // TODO: Error
596       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
597     }
598     category++;
599   }
600   g_return_val_if_fail (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_STRING, DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
601   dbus_message_iter_get_basic (&iter, &detail);
602   dbus_message_iter_next (&iter);
603   /* TODO: Return error indicating invalid arguments  in next line */
604   g_return_val_if_fail (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_INT32, DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
605   dbus_message_iter_get_basic (&iter, &detail1);
606   e.detail1 = detail1;
607   dbus_message_iter_next (&iter);
608   g_return_val_if_fail (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_INT32, DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
609   dbus_message_iter_get_basic (&iter, &detail2);
610   e.detail2 = detail2;
611   dbus_message_iter_next (&iter);
612
613   converted_type = convert_name_from_dbus (category);
614   name = convert_name_from_dbus (member);
615   detail = convert_name_from_dbus (detail);
616
617   if (strcasecmp  (category, name) != 0)
618   {
619     p = g_strconcat (converted_type, ":", name, NULL);
620     if (p)
621     {
622       g_free (converted_type);
623       converted_type = p;
624     }
625   }
626   if (detail[0] != '\0')
627   {
628     p = g_strconcat (converted_type, ":", detail, NULL);
629     if (p)
630     {
631       g_free (converted_type);
632       converted_type = p;
633     }
634   }
635   e.type = converted_type;
636   e.source = _atspi_ref_accessible (dbus_message_get_sender(message), dbus_message_get_path(message));
637
638   dbus_message_iter_recurse (&iter, &iter_variant);
639   switch (dbus_message_iter_get_arg_type (&iter_variant))
640   {
641     case DBUS_TYPE_STRUCT:
642     {
643       AtspiRect rect;
644       if (demarshal_rect (&iter_variant, &rect))
645       {
646         g_value_init (&e.any_data, ATSPI_TYPE_RECT);
647         g_value_set_boxed (&e.any_data, &rect);
648       }
649       else
650       {
651         AtspiAccessible *accessible;
652         accessible = _atspi_dbus_return_accessible_from_iter (&iter_variant);
653         g_value_init (&e.any_data, ATSPI_TYPE_ACCESSIBLE);
654         g_value_set_instance (&e.any_data, accessible);
655       }
656       break;
657     }
658     case DBUS_TYPE_STRING:
659     {
660       dbus_message_iter_get_basic (&iter_variant, &p);
661       g_value_set_string (&e.any_data, p);
662       break;
663     }
664   default:
665     break;
666   }
667   _atspi_send_event (&e);
668
669   if (!strcmp (e.type, "children-changed"))
670   {
671     cache_process_children_changed (&e);
672   }
673   else if (!strcmp (e.type, "property-change"))
674   {
675     cache_process_property_change (&e);
676   }
677   else if (!strcmp (e.type, "state-changed"))
678   {
679     cache_process_state_changed (&e);
680   }
681
682   g_free (converted_type);
683   g_free (name);
684   g_free (detail);
685   g_object_unref (e.source);
686   g_value_unset (&e.any_data);
687   return DBUS_HANDLER_RESULT_HANDLED;
688 }
689
690 static AtspiEvent *
691 atspi_event_copy (AtspiEvent *src)
692 {
693   AtspiEvent *dst = g_new0 (AtspiEvent, 1);
694   dst->type = g_strdup (src->type);
695   dst->detail1 = src->detail1;
696   dst->detail2 = src->detail2;
697   g_value_copy (&dst->any_data, &src->any_data);
698 }
699
700 static void
701 atspi_event_free (AtspiEvent *event)
702 {
703   g_object_unref (event->source);
704   g_free (event->type);
705   g_value_unset (&event->any_data);
706   g_free (event);
707 }
708
709 G_DEFINE_BOXED_TYPE (AtspiEvent, atspi_event, atspi_event_copy, atspi_event_free)