Some work on event support; many bug 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   char *category;
32   char *name;
33   char *detail;
34 } EventListenerEntry;
35
36 /*
37  * Misc. helpers.
38  */
39
40 /*
41  * Standard event dispatcher
42  */
43
44 static guint listener_id = 0;
45 static GList *event_listeners = NULL;
46
47 static gchar *
48 convert_name_from_dbus (const char *name)
49 {
50   gchar *ret = g_malloc (g_utf8_strlen (name, -1) * 2 + 1);
51   const char *p = name;
52   gchar *q = ret;
53
54   if (!ret)
55     return NULL;
56
57   while (*p)
58   {
59     if (isupper (*p))
60     {
61       if (q > ret)
62         *q++ = '-';
63       *q++ = tolower (*p++);
64     }
65     else
66       *q++ = *p++;
67   }
68   *q = '\0';
69   return ret;
70 }
71
72 static void
73 cache_process_children_changed (AtspiEvent *event)
74 {
75   if (event->v_type != EVENT_DATA_OBJECT ||
76       !event->source->children ||
77       atspi_state_set_contains (event->source->states, ATSPI_STATE_MANAGES_DESCENDANTS))
78     return;
79
80   if (!strncmp (event->type, "object:children-changed:add", 27))
81   {
82     GList *new_list = g_list_insert (event->source->children, g_object_ref (event->v.accessible), event->detail1);
83     if (new_list)
84       event->source->children = new_list;
85   }
86   else if (g_list_find (event->source->children, event->v.accessible))
87   {
88     event->source->children = g_list_remove (event->source->children, event->v.accessible);
89   }
90 }
91
92 static void
93 cache_process_property_change (AtspiEvent *event)
94 {
95   if (!strcmp (event->type, "object:property-change:accessible-parent"))
96   {
97     if (event->source->accessible_parent)
98       g_object_unref (event->source->accessible_parent);
99     if (event->v_type == EVENT_DATA_OBJECT)
100     {
101       event->source->accessible_parent = g_object_ref (event->v.accessible);
102       event->source->cached_properties |= ATSPI_CACHE_PARENT;
103     }
104     else
105     {
106       event->source->accessible_parent = NULL;
107       event->source->cached_properties &= ~ATSPI_CACHE_PARENT;
108     }
109   }
110   else if (!strcmp (event->type, "object:property-change:accessible-name"))
111   {
112     if (event->source->name)
113       g_free (event->source->name);
114     if (event->v_type == EVENT_DATA_STRING)
115     {
116       event->source->name = g_strdup (event->v.text);
117       event->source->cached_properties |= ATSPI_CACHE_NAME;
118     }
119     else
120     {
121       event->source->name = NULL;
122       event->source->cached_properties &= ~ATSPI_CACHE_NAME;
123     }
124   }
125   else if (!strcmp (event->type, "object:property-change:accessible-description"))
126   {
127     if (event->source->description)
128       g_free (event->source->description);
129     if (event->v_type == EVENT_DATA_STRING)
130     {
131       event->source->description = g_strdup (event->v.text);
132       event->source->cached_properties |= ATSPI_CACHE_DESCRIPTION;
133     }
134     else
135     {
136       event->source->description = NULL;
137       event->source->cached_properties &= ~ATSPI_CACHE_DESCRIPTION;
138     }
139   }
140 }
141
142 static void
143 cache_process_state_changed (AtspiEvent *event)
144 {
145   atspi_state_set_set_by_name (event->source->states, event->type + 21, event->detail1);
146 }
147
148 static dbus_bool_t
149 demarshal_rect (DBusMessageIter *iter, AtspiRect *rect)
150 {
151   dbus_int32_t x, y, width, height;
152   DBusMessageIter iter_struct;
153
154   dbus_message_iter_recurse (iter, &iter_struct);
155   if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32) return FALSE;
156   dbus_message_iter_get_basic (&iter_struct, &x);
157   dbus_message_iter_next (&iter_struct);
158   if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32) return FALSE;
159   dbus_message_iter_get_basic (&iter_struct, &y);
160   dbus_message_iter_next (&iter_struct);
161   if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32) return FALSE;
162   dbus_message_iter_get_basic (&iter_struct, &width);
163   dbus_message_iter_next (&iter_struct);
164   if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32) return FALSE;
165   dbus_message_iter_get_basic (&iter_struct, &height);
166   rect->x = x;
167   rect->y = y;
168   rect->width = width;
169   rect->height = height;
170   return TRUE;
171 }
172
173 static gchar *
174 strdup_and_adjust_for_dbus (const char *s)
175 {
176   gchar *d = g_strdup (s);
177   gchar *p;
178
179   if (!d)
180     return NULL;
181
182   for (p = d; *p; p++)
183   {
184     if (*p == '-')
185     {
186       memmove (p, p + 1, g_utf8_strlen (p, -1));
187       *p = toupper (*p);
188     }
189     else if (*p == ':')
190     {
191       p [1] = toupper (p [1]);
192     }
193   }
194
195   d [0] = toupper (d [0]);
196   return d;
197 }
198
199 static gboolean
200 convert_event_type_to_dbus (const char *eventType, char **categoryp, char **namep, char **detailp, char **matchrule)
201 {
202   gchar *tmp = strdup_and_adjust_for_dbus (eventType);
203   char *category = NULL, *name = NULL, *detail = NULL;
204   char *saveptr = NULL;
205   char *p;
206
207   if (tmp == NULL) return FALSE;
208   category = strtok_r (tmp, ":", &saveptr);
209   if (category) category = g_strdup (category);
210   if (!category) goto oom;
211   name = strtok_r (NULL, ":", &saveptr);
212   if (name)
213   {
214     name = g_strdup (name);
215     if (!name) goto oom;
216     detail = strtok_r (NULL, ":", &saveptr);
217     if (detail) detail = g_strdup (detail);
218   }
219   if (matchrule)
220   {
221     *matchrule = g_strdup_printf ("type='signal',interface='org.a11y.atspi.Event.%s'", category);
222     if (!*matchrule) goto oom;
223     if (name && name [0])
224     {
225       gchar *new_str = g_strconcat (*matchrule, ",member='", name, "'", NULL);
226       if (new_str)
227       {
228         g_free (*matchrule);
229         *matchrule = new_str;
230       }
231     }
232     if (detail && detail [0])
233     {
234       gchar *new_str = g_strconcat (*matchrule, ",arg0='", detail, "'", NULL);
235       if (new_str)
236       {
237         g_free (*matchrule);
238         *matchrule = new_str;
239       }
240     }
241   }
242   if (categoryp) *categoryp = category;
243   else g_free (category);
244   if (namep) *namep = name;
245   else if (name) g_free (name);
246   if (detailp) *detailp = detail;
247   else if (detail) g_free (detail);
248   g_free (tmp);
249   return TRUE;
250 oom:
251   if (tmp) g_free (tmp);
252   if (category) g_free (category);
253   if (name) g_free (name);
254   if (detail) g_free (detail);
255   return FALSE;
256 }
257
258 static void
259 listener_entry_free (EventListenerEntry *e)
260 {
261   g_free (e->category);
262   g_free (e->name);
263   if (e->detail) g_free (e->detail);
264   g_free (e);
265 }
266
267 /**
268  * atspi_event_listener_register:
269  * @callback: (scope call): the #AtspiEventListenerCB to be registered against
270  *            an event type.
271  * @user_data: (closure): User data to be passed to the callback.
272  * @event_type: a character string indicating the type of events for which
273  *            notification is requested.  Format is
274  *            EventClass:major_type:minor_type:detail
275  *            where all subfields other than EventClass are optional.
276  *            EventClasses include "object", "window", "mouse",
277  *            and toolkit events (e.g. "Gtk", "AWT").
278  *            Examples: "focus:", "Gtk:GtkWidget:button_press_event".
279  *
280  * Legal object event types:
281  *
282  *    (property change events)
283  *
284  *            object:property-change
285  *            object:property-change:accessible-name
286  *            object:property-change:accessible-description
287  *            object:property-change:accessible-parent
288  *            object:property-change:accessible-value
289  *            object:property-change:accessible-role
290  *            object:property-change:accessible-table-caption
291  *            object:property-change:accessible-table-column-description
292  *            object:property-change:accessible-table-column-header
293  *            object:property-change:accessible-table-row-description
294  *            object:property-change:accessible-table-row-header
295  *            object:property-change:accessible-table-summary
296  *
297  *    (other object events)
298  *
299  *            object:state-changed 
300  *            object:children-changed
301  *            object:visible-data-changed
302  *            object:selection-changed
303  *            object:text-selection-changed
304  *            object:text-changed
305  *            object:text-caret-moved
306  *            object:row-inserted
307  *            object:row-reordered
308  *            object:row-deleted
309  *            object:column-inserted
310  *            object:column-reordered
311  *            object:column-deleted
312  *            object:model-changed
313  *            object:active-descendant-changed
314  *
315  *  (window events)
316  *
317  *            window:minimize
318  *            window:maximize
319  *            window:restore
320  *            window:close
321  *            window:create
322  *            window:reparent
323  *            window:desktop-create
324  *            window:desktop-destroy
325  *            window:activate
326  *            window:deactivate
327  *            window:raise
328  *            window:lower
329  *            window:move
330  *            window:resize
331  *            window:shade
332  *            window:unshade
333  *            window:restyle
334  *
335  *  (other events)
336  *
337  *            focus:
338  *            mouse:abs
339  *            mouse:rel
340  *            mouse:b1p
341  *            mouse:b1r
342  *            mouse:b2p
343  *            mouse:b2r
344  *            mouse:b3p
345  *            mouse:b3r
346  *
347  * NOTE: this string may be UTF-8, but should not contain byte value 56
348  *            (ascii ':'), except as a delimiter, since non-UTF-8 string
349  *            delimiting functions are used internally.
350  *            In general, listening to
351  *            toolkit-specific events is not recommended.
352  *
353  * Add an in-process callback function to an existing AtspiEventListener.
354  *
355  * Returns: #TRUE if successful, otherwise #FALSE.
356  **/
357 gboolean
358 atspi_event_listener_register (AtspiEventListenerCB callback,
359                                  void *user_data,
360                                  const gchar              *event_type)
361 {
362   EventListenerEntry *e;
363   char *matchrule;
364   DBusError error;
365   GList *new_list;
366   DBusMessage *message, *reply;
367
368   if (!callback)
369     {
370       return FALSE;
371     }
372
373   e = g_new (EventListenerEntry, 1);
374   if (!e) return FALSE;
375   e->callback = callback;
376   e->user_data = user_data;
377   if (!convert_event_type_to_dbus (event_type, &e->category, &e->name, &e->detail, &matchrule))
378   {
379     g_free (e);
380     return FALSE;
381   }
382   new_list = g_list_prepend (event_listeners, e);
383   if (!new_list)
384   {
385     listener_entry_free (e);
386     return FALSE;
387   }
388   event_listeners = new_list;
389   dbus_error_init (&error);
390   dbus_bus_add_match (_atspi_bus(), matchrule, &error);
391   if (error.message)
392   {
393     g_warning ("Atspi: Adding match: %s", error.message);
394   }
395
396   dbus_error_init (&error);
397   message = dbus_message_new_method_call (atspi_bus_registry,
398         atspi_path_registry,
399         atspi_interface_registry,
400         "RegisterEvent");
401   if (!message)
402     return;
403   dbus_message_append_args (message, DBUS_TYPE_STRING, &event_type, DBUS_TYPE_INVALID);
404   reply = _atspi_dbus_send_with_reply_and_block (message);
405   dbus_message_unref (reply);
406
407   return TRUE;
408 }
409
410 static gboolean
411 is_superset (const gchar *super, const gchar *sub)
412 {
413   if (!super || !super [0])
414     return TRUE;
415   return (strcmp (super, sub) == 0);
416 }
417
418 /**
419  * atspi_event_listener_deregister:
420  * @callback: (scope call): the #AtspiEventListenerCB registered against an
421  *            event type.
422  * @user_data: (closure): User data that was passed in for this callback.
423  * @event_type: a string specifying the event type for which this
424  *             listener is to be deregistered.
425  *
426  * deregisters an #AtspiEventListenerCB from the registry, for a specific
427  *             event type.
428  *
429  * Returns: #TRUE if successful, otherwise #FALSE.
430  **/
431 gboolean
432 atspi_event_listener_deregister (AtspiEventListenerCB callback,
433                                    void *user_data,
434                                    const gchar              *event_type)
435 {
436   char *category, *name, *detail, *matchrule;
437   GList *l;
438
439   if (!convert_event_type_to_dbus (event_type, &category, &name, &detail, &matchrule))
440   {
441     return FALSE;
442   }
443   if (!callback)
444     {
445       return FALSE;
446     }
447
448   for (l = event_listeners; l;)
449   {
450     EventListenerEntry *e = l->data;
451     if (e->callback == callback &&
452         e->user_data == user_data &&
453         is_superset (category, e->category) &&
454         is_superset (name, e->name) &&
455         is_superset (detail, e->detail))
456     {
457       DBusError error;
458       DBusMessage *message, *reply;
459       l = g_list_remove (l, e);
460       dbus_error_init (&error);
461       dbus_bus_remove_match (_atspi_bus(), matchrule, &error);
462       dbus_error_init (&error);
463       message = dbus_message_new_method_call (atspi_bus_registry,
464             atspi_path_registry,
465             atspi_interface_registry,
466             "RegisterEvent");
467       if (!message)
468       return;
469       dbus_message_append_args (message, DBUS_TYPE_STRING, &event_type, DBUS_TYPE_INVALID);
470       reply = _atspi_dbus_send_with_reply_and_block (message);
471       dbus_message_unref (reply);
472
473       listener_entry_free (e);
474     }
475     else l = g_list_next (l);
476   }
477   g_free (category);
478   g_free (name);
479   if (detail) g_free (detail);
480   g_free (matchrule);
481   return TRUE;
482 }
483
484 void
485 _atspi_send_event (AtspiEvent *e)
486 {
487   char *category, *name, *detail;
488   GList *l;
489
490   if (!convert_event_type_to_dbus (e->type, &category, &name, &detail, NULL))
491   {
492     g_warning ("Atspi: Couldn't parse event: %s\n", e->type);
493     return;
494   }
495   for (l = event_listeners; l; l = g_list_next (l))
496   {
497     EventListenerEntry *entry = l->data;
498     if (!strcmp (category, entry->category) &&
499         (entry->name == NULL || !strcmp (name, entry->name)) &&
500         (entry->detail == NULL || !strcmp (detail, entry->detail)))
501     {
502         entry->callback (entry->user_data, e);
503     }
504   }
505   if (detail) g_free (detail);
506   g_free (name);
507   g_free (category);
508 }
509
510 DBusHandlerResult
511 atspi_dbus_handle_event (DBusConnection *bus, DBusMessage *message, void *data)
512 {
513   char *detail = NULL;
514   const char *category = dbus_message_get_interface (message);
515   const char *member = dbus_message_get_member (message);
516   gchar *name;
517   gchar *converted_type;
518   DBusMessageIter iter, iter_variant;
519   dbus_message_iter_init (message, &iter);
520   AtspiEvent e;
521   dbus_int32_t detail1, detail2;
522   char *p;
523
524   if (category)
525   {
526     category = g_utf8_strrchr (category, -1, '.');
527     if (category == NULL)
528     {
529       // TODO: Error
530       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
531     }
532     category++;
533   }
534   g_return_val_if_fail (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_STRING, DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
535   dbus_message_iter_get_basic (&iter, &detail);
536   dbus_message_iter_next (&iter);
537   /* TODO: Return error indicating invalid arguments  in next line */
538   g_return_val_if_fail (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_INT32, DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
539   dbus_message_iter_get_basic (&iter, &detail1);
540   e.detail1 = detail1;
541   dbus_message_iter_next (&iter);
542   g_return_val_if_fail (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_INT32, DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
543   dbus_message_iter_get_basic (&iter, &detail2);
544   e.detail2 = detail2;
545   dbus_message_iter_next (&iter);
546
547   converted_type = convert_name_from_dbus (category);
548   name = convert_name_from_dbus (member);
549   detail = convert_name_from_dbus (detail);
550
551   if (strcasecmp  (category, name) != 0)
552   {
553     p = g_strconcat (converted_type, ":", name, NULL);
554     if (p)
555     {
556       g_free (converted_type);
557       converted_type = p;
558     }
559   }
560   if (detail[0] != '\0')
561   {
562     p = g_strconcat (converted_type, ":", detail, NULL);
563     if (p)
564     {
565       g_free (converted_type);
566       converted_type = p;
567     }
568   }
569   e.type = converted_type;
570   e.source = _atspi_ref_accessible (dbus_message_get_sender(message), dbus_message_get_path(message));
571   dbus_message_iter_recurse (&iter, &iter_variant);
572   switch (dbus_message_iter_get_arg_type (&iter_variant))
573   {
574     case DBUS_TYPE_STRUCT:
575     {
576       if (demarshal_rect (&iter_variant, &e.v.rect))
577       {
578         e.v_type = EVENT_DATA_RECT;
579       }
580       else
581       {
582         e.v_type = EVENT_DATA_OBJECT;
583         e.v.accessible = _atspi_dbus_return_accessible_from_iter (&iter_variant);
584       }
585       break;
586     }
587     case DBUS_TYPE_STRING:
588     {
589       dbus_message_iter_get_basic (&iter_variant, &p);
590       e.v_type = EVENT_DATA_STRING;
591       e.v.text = g_strdup (p);
592       break;
593     }
594   default:
595     break;
596   }
597   _atspi_send_event (&e);
598
599   if (!strcmp (e.type, "children-changed"))
600   {
601     cache_process_children_changed (&e);
602   }
603   else if (!strcmp (e.type, "property-change"))
604   {
605     cache_process_property_change (&e);
606   }
607   else if (!strcmp (e.type, "state-changed"))
608   {
609     cache_process_state_changed (&e);
610   }
611
612   g_free (converted_type);
613   g_free (name);
614   g_free (detail);
615   g_object_unref (e.source);
616   if (e.v_type == EVENT_DATA_OBJECT)
617     g_object_unref (e.v.accessible);
618   return DBUS_HANDLER_RESULT_HANDLED;
619 }
620