Box some types, add missing header file, and other 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, 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);
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, ATSPI_TYPE_ACCESSIBLE))
105     {
106       event->source->accessible_parent = g_value_dup_object (&event->any);
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))
120     {
121       event->source->name = g_value_dup_string (&event->any);
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))
135     {
136       event->source->description = g_value_dup_string (&event->any);
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_freed: 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_freed: 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   if (!convert_event_type_to_dbus (e->type, &category, &name, &detail, NULL))
550   {
551     g_warning ("Atspi: Couldn't parse event: %s\n", e->type);
552     return;
553   }
554   for (l = event_listeners; l; l = g_list_next (l))
555   {
556     EventListenerEntry *entry = l->data;
557     if (!strcmp (category, entry->category) &&
558         (entry->name == NULL || !strcmp (name, entry->name)) &&
559         (entry->detail == NULL || !strcmp (detail, entry->detail)))
560     {
561         entry->callback (entry->user_data, e);
562     }
563   }
564   if (detail) g_free (detail);
565   g_free (name);
566   g_free (category);
567 }
568
569 DBusHandlerResult
570 atspi_dbus_handle_event (DBusConnection *bus, DBusMessage *message, void *data)
571 {
572   char *detail = NULL;
573   const char *category = dbus_message_get_interface (message);
574   const char *member = dbus_message_get_member (message);
575   gchar *name;
576   gchar *converted_type;
577   DBusMessageIter iter, iter_variant;
578   dbus_message_iter_init (message, &iter);
579   AtspiEvent e;
580   dbus_int32_t detail1, detail2;
581   char *p;
582
583   if (category)
584   {
585     category = g_utf8_strrchr (category, -1, '.');
586     if (category == NULL)
587     {
588       // TODO: Error
589       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
590     }
591     category++;
592   }
593   g_return_val_if_fail (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_STRING, DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
594   dbus_message_iter_get_basic (&iter, &detail);
595   dbus_message_iter_next (&iter);
596   /* TODO: Return error indicating invalid arguments  in next line */
597   g_return_val_if_fail (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_INT32, DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
598   dbus_message_iter_get_basic (&iter, &detail1);
599   e.detail1 = detail1;
600   dbus_message_iter_next (&iter);
601   g_return_val_if_fail (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_INT32, DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
602   dbus_message_iter_get_basic (&iter, &detail2);
603   e.detail2 = detail2;
604   dbus_message_iter_next (&iter);
605
606   converted_type = convert_name_from_dbus (category);
607   name = convert_name_from_dbus (member);
608   detail = convert_name_from_dbus (detail);
609
610   if (strcasecmp  (category, name) != 0)
611   {
612     p = g_strconcat (converted_type, ":", name, NULL);
613     if (p)
614     {
615       g_free (converted_type);
616       converted_type = p;
617     }
618   }
619   if (detail[0] != '\0')
620   {
621     p = g_strconcat (converted_type, ":", detail, NULL);
622     if (p)
623     {
624       g_free (converted_type);
625       converted_type = p;
626     }
627   }
628   e.type = converted_type;
629   e.source = _atspi_ref_accessible (dbus_message_get_sender(message), dbus_message_get_path(message));
630   dbus_message_iter_recurse (&iter, &iter_variant);
631   switch (dbus_message_iter_get_arg_type (&iter_variant))
632   {
633     case DBUS_TYPE_STRUCT:
634     {
635       AtspiRect rect;
636       if (demarshal_rect (&iter_variant, &rect))
637       {
638         g_value_init (&e.any, ATSPI_TYPE_RECT);
639         g_value_set_instance (&e.any, &rect);
640       }
641       else
642       {
643         AtspiAccessible *accessible;
644         accessible = _atspi_dbus_return_accessible_from_iter (&iter_variant);
645         g_value_init (&e.any, ATSPI_TYPE_ACCESSIBLE);
646         g_value_set_instance (&e.any, accessible);
647       }
648       break;
649     }
650     case DBUS_TYPE_STRING:
651     {
652       dbus_message_iter_get_basic (&iter_variant, &p);
653       g_value_set_string (&e.any, p);
654       break;
655     }
656   default:
657     break;
658   }
659   _atspi_send_event (&e);
660
661   if (!strcmp (e.type, "children-changed"))
662   {
663     cache_process_children_changed (&e);
664   }
665   else if (!strcmp (e.type, "property-change"))
666   {
667     cache_process_property_change (&e);
668   }
669   else if (!strcmp (e.type, "state-changed"))
670   {
671     cache_process_state_changed (&e);
672   }
673
674   g_free (converted_type);
675   g_free (name);
676   g_free (detail);
677   g_object_unref (e.source);
678   g_value_unset (&e.any);
679   return DBUS_HANDLER_RESULT_HANDLED;
680 }
681
682 static AtspiEvent *
683 atspi_event_copy (AtspiEvent *src)
684 {
685   AtspiEvent *dst = g_new0 (AtspiEvent, 1);
686   dst->type = g_strdup (src->type);
687   dst->detail1 = src->detail1;
688   dst->detail2 = src->detail2;
689   g_value_copy (&dst->any, &src->any);
690 }
691
692 static void
693 atspi_event_free (AtspiEvent *event)
694 {
695   g_object_unref (event->source);
696   g_free (event->type);
697   g_value_unset (&event->any);
698   g_free (event);
699 }
700
701 G_DEFINE_BOXED_TYPE (AtspiEvent, atspi_event, atspi_event_copy, atspi_event_free)