2 * AT-SPI - Assistive Technology Service Provider Interface
3 * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
5 * Copyright 2002 Ximian Inc.
6 * Copyright 2002 Sun Microsystems, Inc.
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.
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.
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.
24 #include "atspi-private.h"
29 AtspiEventListenerCB callback;
41 * Standard event dispatcher
44 static guint listener_id = 0;
45 static GList *event_listeners = NULL;
48 convert_name_from_dbus (const char *name)
50 gchar *ret = g_malloc (g_utf8_strlen (name, -1) * 2 + 1);
63 *q++ = tolower (*p++);
73 cache_process_children_changed (AtspiEvent *event)
75 if (event->v_type != EVENT_DATA_OBJECT ||
76 !event->source->children ||
77 atspi_state_set_contains (event->source->states, ATSPI_STATE_MANAGES_DESCENDANTS))
80 if (!strncmp (event->type, "object:children-changed:add", 27))
82 GList *new_list = g_list_insert (event->source->children, g_object_ref (event->v.accessible), event->detail1);
84 event->source->children = new_list;
86 else if (g_list_find (event->source->children, event->v.accessible))
88 event->source->children = g_list_remove (event->source->children, event->v.accessible);
93 cache_process_property_change (AtspiEvent *event)
95 if (!strcmp (event->type, "object:property-change:accessible-parent"))
97 if (event->source->accessible_parent)
98 g_object_unref (event->source->accessible_parent);
99 if (event->v_type == EVENT_DATA_OBJECT)
101 event->source->accessible_parent = g_object_ref (event->v.accessible);
102 event->source->cached_properties |= ATSPI_CACHE_PARENT;
106 event->source->accessible_parent = NULL;
107 event->source->cached_properties &= ~ATSPI_CACHE_PARENT;
110 else if (!strcmp (event->type, "object:property-change:accessible-name"))
112 if (event->source->name)
113 g_free (event->source->name);
114 if (event->v_type == EVENT_DATA_STRING)
116 event->source->name = g_strdup (event->v.text);
117 event->source->cached_properties |= ATSPI_CACHE_NAME;
121 event->source->name = NULL;
122 event->source->cached_properties &= ~ATSPI_CACHE_NAME;
125 else if (!strcmp (event->type, "object:property-change:accessible-description"))
127 if (event->source->description)
128 g_free (event->source->description);
129 if (event->v_type == EVENT_DATA_STRING)
131 event->source->description = g_strdup (event->v.text);
132 event->source->cached_properties |= ATSPI_CACHE_DESCRIPTION;
136 event->source->description = NULL;
137 event->source->cached_properties &= ~ATSPI_CACHE_DESCRIPTION;
143 cache_process_state_changed (AtspiEvent *event)
145 atspi_state_set_set_by_name (event->source->states, event->type + 21, event->detail1);
149 demarshal_rect (DBusMessageIter *iter, AtspiRect *rect)
151 dbus_int32_t x, y, width, height;
152 DBusMessageIter iter_struct;
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);
169 rect->height = height;
174 strdup_and_adjust_for_dbus (const char *s)
176 gchar *d = g_strdup (s);
186 memmove (p, p + 1, g_utf8_strlen (p, -1));
191 p [1] = toupper (p [1]);
195 d [0] = toupper (d [0]);
200 convert_event_type_to_dbus (const char *eventType, char **categoryp, char **namep, char **detailp, char **matchrule)
202 gchar *tmp = strdup_and_adjust_for_dbus (eventType);
203 char *category = NULL, *name = NULL, *detail = NULL;
204 char *saveptr = NULL;
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);
214 name = g_strdup (name);
216 detail = strtok_r (NULL, ":", &saveptr);
217 if (detail) detail = g_strdup (detail);
221 *matchrule = g_strdup_printf ("type='signal',interface='org.a11y.atspi.Event.%s'", category);
222 if (!*matchrule) goto oom;
223 if (name && name [0])
225 gchar *new_str = g_strconcat (*matchrule, ",member='", name, "'", NULL);
229 *matchrule = new_str;
232 if (detail && detail [0])
234 gchar *new_str = g_strconcat (*matchrule, ",arg0='", detail, "'", NULL);
238 *matchrule = new_str;
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);
251 if (tmp) g_free (tmp);
252 if (category) g_free (category);
253 if (name) g_free (name);
254 if (detail) g_free (detail);
259 listener_entry_free (EventListenerEntry *e)
261 g_free (e->category);
263 if (e->detail) g_free (e->detail);
268 * atspi_event_listener_register:
269 * @callback: (scope call): the #AtspiEventListenerCB to be registered against
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".
280 * Legal object event types:
282 * (property change events)
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
297 * (other object events)
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
309 * object:column-inserted
310 * object:column-reordered
311 * object:column-deleted
312 * object:model-changed
313 * object:active-descendant-changed
323 * window:desktop-create
324 * window:desktop-destroy
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.
353 * Add an in-process callback function to an existing AtspiEventListener.
355 * Returns: #TRUE if successful, otherwise #FALSE.
358 atspi_event_listener_register (AtspiEventListenerCB callback,
360 const gchar *event_type)
362 EventListenerEntry *e;
366 DBusMessage *message, *reply;
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))
382 new_list = g_list_prepend (event_listeners, e);
385 listener_entry_free (e);
388 event_listeners = new_list;
389 dbus_error_init (&error);
390 dbus_bus_add_match (_atspi_bus(), matchrule, &error);
393 g_warning ("Atspi: Adding match: %s", error.message);
396 dbus_error_init (&error);
397 message = dbus_message_new_method_call (atspi_bus_registry,
399 atspi_interface_registry,
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);
411 is_superset (const gchar *super, const gchar *sub)
413 if (!super || !super [0])
415 return (strcmp (super, sub) == 0);
419 * atspi_event_listener_deregister:
420 * @callback: (scope call): the #AtspiEventListenerCB registered against an
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.
426 * deregisters an #AtspiEventListenerCB from the registry, for a specific
429 * Returns: #TRUE if successful, otherwise #FALSE.
432 atspi_event_listener_deregister (AtspiEventListenerCB callback,
434 const gchar *event_type)
436 char *category, *name, *detail, *matchrule;
439 if (!convert_event_type_to_dbus (event_type, &category, &name, &detail, &matchrule))
448 for (l = event_listeners; l;)
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))
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,
465 atspi_interface_registry,
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);
473 listener_entry_free (e);
475 else l = g_list_next (l);
479 if (detail) g_free (detail);
485 _atspi_send_event (AtspiEvent *e)
487 char *category, *name, *detail;
490 if (!convert_event_type_to_dbus (e->type, &category, &name, &detail, NULL))
492 g_warning ("Atspi: Couldn't parse event: %s\n", e->type);
495 for (l = event_listeners; l; l = g_list_next (l))
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)))
502 entry->callback (entry->user_data, e);
505 if (detail) g_free (detail);
511 atspi_dbus_handle_event (DBusConnection *bus, DBusMessage *message, void *data)
514 const char *category = dbus_message_get_interface (message);
515 const char *member = dbus_message_get_member (message);
517 gchar *converted_type;
518 DBusMessageIter iter, iter_variant;
519 dbus_message_iter_init (message, &iter);
521 dbus_int32_t detail1, detail2;
526 category = g_utf8_strrchr (category, -1, '.');
527 if (category == NULL)
530 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
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);
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);
545 dbus_message_iter_next (&iter);
547 converted_type = convert_name_from_dbus (category);
548 name = convert_name_from_dbus (member);
549 detail = convert_name_from_dbus (detail);
551 if (strcasecmp (category, name) != 0)
553 p = g_strconcat (converted_type, ":", name, NULL);
556 g_free (converted_type);
560 if (detail[0] != '\0')
562 p = g_strconcat (converted_type, ":", detail, NULL);
565 g_free (converted_type);
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))
574 case DBUS_TYPE_STRUCT:
576 if (demarshal_rect (&iter_variant, &e.v.rect))
578 e.v_type = EVENT_DATA_RECT;
582 e.v_type = EVENT_DATA_OBJECT;
583 e.v.accessible = _atspi_dbus_return_accessible_from_iter (&iter_variant);
587 case DBUS_TYPE_STRING:
589 dbus_message_iter_get_basic (&iter_variant, &p);
590 e.v_type = EVENT_DATA_STRING;
591 e.v.text = g_strdup (p);
597 _atspi_send_event (&e);
599 if (!strcmp (e.type, "children-changed"))
601 cache_process_children_changed (&e);
603 else if (!strcmp (e.type, "property-change"))
605 cache_process_property_change (&e);
607 else if (!strcmp (e.type, "state-changed"))
609 cache_process_state_changed (&e);
612 g_free (converted_type);
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;