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;
31 GDestroyNotify callback_destroyed;
37 G_DEFINE_TYPE (AtspiEventListener, atspi_event_listener, G_TYPE_OBJECT)
40 atspi_event_listener_init (AtspiEventListener *listener)
45 atspi_event_listener_class_init (AtspiEventListenerClass *klass)
50 remove_datum (const AtspiEvent *event, void *user_data)
52 AtspiEventListenerSimpleCB cb = user_data;
59 GDestroyNotify callback_destroyed;
62 static GHashTable *callbacks;
65 callback_ref (void *callback, GDestroyNotify callback_destroyed)
71 callbacks = g_hash_table_new (g_direct_hash, g_direct_equal);
76 info = g_hash_table_lookup (callbacks, callback);
79 info = g_new (CallbackInfo, 1);
82 info->callback = callback;
83 info->callback_destroyed = callback_destroyed;
85 g_hash_table_insert (callbacks, callback, info);
92 callback_unref (gpointer callback)
98 info = g_hash_table_lookup (callbacks, callback);
101 g_warning ("Atspi: Dereferencing invalid callback %p\n", callback);
105 if (info->ref_count == 0)
108 /* TODO: Figure out why this seg faults from Python */
109 if (info->callback_destroyed)
110 (*info->callback_destroyed) (info->callback);
113 g_hash_table_remove (callbacks, callback);
118 * atspi_event_listener_new_simple:
119 * @callback: (scope notified): An #AtspiEventListenerSimpleCB to be called
120 * when an event is fired.
121 * @callback_destroyed: A #GDestroyNotify called when the listener is freed
122 * and data associated with the callback should be freed. Can be NULL.
124 * Returns: (transfer full): A new #AtspiEventListener.
127 atspi_event_listener_new_simple (AtspiEventListenerSimpleCB callback,
128 GDestroyNotify callback_destroyed)
130 AtspiEventListener *listener = g_object_new (ATSPI_TYPE_EVENT_LISTENER, NULL);
131 listener->callback = remove_datum;
132 callback_ref (remove_datum, callback_destroyed);
133 listener->user_data = callback;
134 listener->cb_destroyed = callback_destroyed;
138 static GList *event_listeners = NULL;
141 convert_name_from_dbus (const char *name)
143 gchar *ret = g_malloc (g_utf8_strlen (name, -1) * 2 + 1);
144 const char *p = name;
156 *q++ = tolower (*p++);
166 event_is (AtspiEvent *event, gchar *klass, gchar *major, gchar *minor)
168 return (!strcmp (event->type.klass, klass) &&
169 (major == NULL || !strcmp (event->type.major, major)) &&
170 (minor == NULL || !strcmp (event->type.minor, minor)));
174 cache_process_children_changed (AtspiEvent *event)
176 AtspiAccessible *child;
178 if (!G_VALUE_HOLDS (&event->any_data, ATSPI_TYPE_ACCESSIBLE) ||
179 !event->source->children ||
180 atspi_state_set_contains (event->source->states, ATSPI_STATE_MANAGES_DESCENDANTS))
183 child = g_value_get_object (&event->any_data);
185 if (event_is (event, "object", "children-changed", "add"))
187 if (g_list_find (event->source->children, child))
189 GList *new_list = g_list_insert (event->source->children, g_object_ref (child), event->detail1);
191 event->source->children = new_list;
193 else if (g_list_find (event->source->children, child))
195 event->source->children = g_list_remove (event->source->children, child);
200 cache_process_property_change (AtspiEvent *event)
202 if (event_is (event, "object", "property-change", "accessible-parent"))
204 if (event->source->accessible_parent)
205 g_object_unref (event->source->accessible_parent);
206 if (G_VALUE_HOLDS (&event->any_data, ATSPI_TYPE_ACCESSIBLE))
208 event->source->accessible_parent = g_value_dup_object (&event->any_data);
209 event->source->cached_properties |= ATSPI_CACHE_PARENT;
213 event->source->accessible_parent = NULL;
214 event->source->cached_properties &= ~ATSPI_CACHE_PARENT;
217 else if (event_is (event, "object", "property-change", "accessible-name"))
219 if (event->source->name)
220 g_free (event->source->name);
221 if (G_VALUE_HOLDS_STRING (&event->any_data))
223 event->source->name = g_value_dup_string (&event->any_data);
224 event->source->cached_properties |= ATSPI_CACHE_NAME;
228 event->source->name = NULL;
229 event->source->cached_properties &= ~ATSPI_CACHE_NAME;
232 else if (event_is (event, "object", "property-change", "accessible-description"))
234 if (event->source->description)
235 g_free (event->source->description);
236 if (G_VALUE_HOLDS_STRING (&event->any_data))
238 event->source->description = g_value_dup_string (&event->any_data);
239 event->source->cached_properties |= ATSPI_CACHE_DESCRIPTION;
243 event->source->description = NULL;
244 event->source->cached_properties &= ~ATSPI_CACHE_DESCRIPTION;
250 cache_process_state_changed (AtspiEvent *event)
252 if (event->source->states)
253 atspi_state_set_set_by_name (event->source->states, event->type.minor,
258 demarshal_rect (DBusMessageIter *iter, AtspiRect *rect)
260 dbus_int32_t x, y, width, height;
261 DBusMessageIter iter_struct;
263 dbus_message_iter_recurse (iter, &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, &x);
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, &y);
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, &width);
272 dbus_message_iter_next (&iter_struct);
273 if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32) return FALSE;
274 dbus_message_iter_get_basic (&iter_struct, &height);
278 rect->height = height;
283 strdup_and_adjust_for_dbus (const char *s)
285 gchar *d = g_strdup (s);
296 memmove (p, p + 1, g_utf8_strlen (p, -1));
304 p [1] = toupper (p [1]);
308 d [0] = toupper (d [0]);
313 generate_match_rule (const char *category, const char *name, const char *detail, char **matchrule)
317 *matchrule = g_strdup_printf ("type='signal',interface='org.a11y.atspi.Event.%s'", category);
320 if (name && name [0])
322 gchar *new_str = g_strconcat (*matchrule, ",member='", name, "'", NULL);
326 *matchrule = new_str;
329 if (detail && detail [0])
331 gchar *new_str = g_strconcat (*matchrule, ",arg0='", detail, "'", NULL);
335 *matchrule = new_str;
341 convert_event_type_to_dbus (AtspiEventType *type, char **categoryp, char **namep, char **detailp, char **matchrule)
343 gchar *category = strdup_and_adjust_for_dbus (type->klass);
344 gchar *name = strdup_and_adjust_for_dbus (type->major);
345 gchar *detail = g_strdup (type->minor);
347 generate_match_rule (category, name, detail, matchrule);
349 if (categoryp) *categoryp = category;
350 else g_free (category);
351 if (namep) *namep = name;
352 else if (name) g_free (name);
353 if (detailp) *detailp = detail;
354 else if (detail) g_free (detail);
357 if (category) g_free (category);
358 if (name) g_free (name);
359 if (detail) g_free (detail);
364 convert_event_string_to_dbus (const char *eventType, char **categoryp, char **namep, char **detailp, char **matchrule)
366 gchar *tmp = strdup_and_adjust_for_dbus (eventType);
367 char *category = NULL, *name = NULL, *detail = NULL;
368 char *saveptr = NULL;
371 if (tmp == NULL) return FALSE;
372 category = strtok_r (tmp, ":", &saveptr);
373 if (category) category = g_strdup (category);
374 if (!category) goto oom;
375 name = strtok_r (NULL, ":", &saveptr);
378 name = g_strdup (name);
380 detail = strtok_r (NULL, ":", &saveptr);
381 if (detail) detail = g_strdup (detail);
384 generate_match_rule (category, name, detail, matchrule);
386 if (categoryp) *categoryp = category;
387 else g_free (category);
388 if (namep) *namep = name;
389 else if (name) g_free (name);
390 if (detailp) *detailp = detail;
391 else if (detail) g_free (detail);
395 if (tmp) g_free (tmp);
396 if (category) g_free (category);
397 if (name) g_free (name);
398 if (detail) g_free (detail);
403 listener_entry_free (EventListenerEntry *e)
405 gpointer callback = (e->callback = remove_datum ? e->user_data : e->callback);
406 g_free (e->category);
408 if (e->detail) g_free (e->detail);
409 callback_unref (callback);
414 * atspi_event_listener_register:
415 * @listener: The #AtspiEventListener to register against an event type.
416 * @event_type: a character string indicating the type of events for which
417 * notification is requested. Format is
418 * EventClass:major_type:minor_type:detail
419 * where all subfields other than EventClass are optional.
420 * EventClasses include "object", "window", "mouse",
421 * and toolkit events (e.g. "Gtk", "AWT").
422 * Examples: "focus:", "Gtk:GtkWidget:button_press_event".
424 * Legal object event types:
426 * (property change events)
428 * object:property-change
429 * object:property-change:accessible-name
430 * object:property-change:accessible-description
431 * object:property-change:accessible-parent
432 * object:property-change:accessible-value
433 * object:property-change:accessible-role
434 * object:property-change:accessible-table-caption
435 * object:property-change:accessible-table-column-description
436 * object:property-change:accessible-table-column-header
437 * object:property-change:accessible-table-row-description
438 * object:property-change:accessible-table-row-header
439 * object:property-change:accessible-table-summary
441 * (other object events)
443 * object:state-changed
444 * object:children-changed
445 * object:visible-data-changed
446 * object:selection-changed
447 * object:text-selection-changed
448 * object:text-changed
449 * object:text-caret-moved
450 * object:row-inserted
451 * object:row-reordered
453 * object:column-inserted
454 * object:column-reordered
455 * object:column-deleted
456 * object:model-changed
457 * object:active-descendant-changed
467 * window:desktop-create
468 * window:desktop-destroy
491 * NOTE: this string may be UTF-8, but should not contain byte value 56
492 * (ascii ':'), except as a delimiter, since non-UTF-8 string
493 * delimiting functions are used internally.
494 * In general, listening to
495 * toolkit-specific events is not recommended.
497 * Add an in-process callback function to an existing AtspiEventListener.
499 * Returns: #TRUE if successful, otherwise #FALSE.
502 atspi_event_listener_register (AtspiEventListener *listener,
503 const gchar *event_type)
505 /* TODO: Keep track of which events have been registered, so that we
506 * deregister all of them when the event listener is destroyed */
508 return atspi_event_listener_register_from_callback (listener->callback,
510 listener->cb_destroyed,
515 * atspi_event_listener_register_from_callback:
516 * @callback: (scope notified): the #AtspiEventListenerCB to be registered against
518 * @user_data: (closure): User data to be passed to the callback.
519 * @callback_destroyed: A #GDestroyNotify called when the callback is destroyed.
520 * @event_type: a character string indicating the type of events for which
521 * notification is requested. See #atspi_event_listener_register
522 * for a description of the format.
525 atspi_event_listener_register_from_callback (AtspiEventListenerCB callback,
527 GDestroyNotify callback_destroyed,
528 const gchar *event_type)
530 EventListenerEntry *e;
534 DBusMessage *message, *reply;
541 e = g_new (EventListenerEntry, 1);
542 if (!e) return FALSE;
543 e->callback = callback;
544 e->user_data = user_data;
545 e->callback_destroyed = callback_destroyed;
546 callback_ref (callback == remove_datum ? user_data : callback,
548 if (!convert_event_string_to_dbus (event_type, &e->category, &e->name, &e->detail, &matchrule))
553 new_list = g_list_prepend (event_listeners, e);
556 listener_entry_free (e);
559 event_listeners = new_list;
560 dbus_error_init (&error);
561 dbus_bus_add_match (_atspi_bus(), matchrule, &error);
564 g_warning ("Atspi: Adding match: %s", error.message);
567 dbus_error_init (&error);
568 message = dbus_message_new_method_call (atspi_bus_registry,
570 atspi_interface_registry,
574 dbus_message_append_args (message, DBUS_TYPE_STRING, &event_type, DBUS_TYPE_INVALID);
575 reply = _atspi_dbus_send_with_reply_and_block (message);
577 dbus_message_unref (reply);
583 * atspi_event_listener_register_no_data:
584 * @callback: (scope notified): the #AtspiEventListenerSimpleCB to be
585 * registered against an event type.
586 * @callback_destroyed: A #GDestroyNotify called when the callback is destroyed.
587 * @event_type: a character string indicating the type of events for which
588 * notification is requested. Format is
589 * EventClass:major_type:minor_type:detail
590 * where all subfields other than EventClass are optional.
591 * EventClasses include "object", "window", "mouse",
592 * and toolkit events (e.g. "Gtk", "AWT").
593 * Examples: "focus:", "Gtk:GtkWidget:button_press_event".
595 * Like atspi_event_listener_register, but callback takes no user_data.
598 atspi_event_listener_register_no_data (AtspiEventListenerSimpleCB callback,
599 GDestroyNotify callback_destroyed,
600 const gchar *event_type)
602 return atspi_event_listener_register_from_callback (remove_datum, callback, callback_destroyed, event_type);
606 is_superset (const gchar *super, const gchar *sub)
608 if (!super || !super [0])
610 return (strcmp (super, sub) == 0);
614 * atspi_event_listener_deregister:
615 * @listener: The #AtspiEventListener to deregister.
616 * @event_type: a string specifying the event type for which this
617 * listener is to be deregistered.
619 * deregisters an #AtspiEventListener from the registry, for a specific
622 * Returns: #TRUE if successful, otherwise #FALSE.
625 atspi_event_listener_deregister (AtspiEventListener *listener,
626 const gchar *event_type)
628 atspi_event_listener_deregister_from_callback (listener->callback, listener->user_data, event_type);
632 * atspi_event_listener_deregister_from_callback:
633 * @callback: (scope call): the #AtspiEventListenerCB registered against an
635 * @user_data: (closure): User data that was passed in for this callback.
636 * @event_type: a string specifying the event type for which this
637 * listener is to be deregistered.
639 * deregisters an #AtspiEventListenerCB from the registry, for a specific
642 * Returns: #TRUE if successful, otherwise #FALSE.
645 atspi_event_listener_deregister_from_callback (AtspiEventListenerCB callback,
647 const gchar *event_type)
649 char *category, *name, *detail, *matchrule;
652 if (!convert_event_string_to_dbus (event_type, &category, &name, &detail, &matchrule))
661 for (l = event_listeners; l;)
663 EventListenerEntry *e = l->data;
664 if (e->callback == callback &&
665 e->user_data == user_data &&
666 is_superset (category, e->category) &&
667 is_superset (name, e->name) &&
668 is_superset (detail, e->detail))
670 gboolean need_replace;
672 DBusMessage *message, *reply;
673 need_replace = (l == event_listeners);
674 l = g_list_remove (l, e);
677 dbus_error_init (&error);
678 dbus_bus_remove_match (_atspi_bus(), matchrule, &error);
679 dbus_error_init (&error);
680 message = dbus_message_new_method_call (atspi_bus_registry,
682 atspi_interface_registry,
686 dbus_message_append_args (message, DBUS_TYPE_STRING, &event_type, DBUS_TYPE_INVALID);
687 reply = _atspi_dbus_send_with_reply_and_block (message);
688 dbus_message_unref (reply);
690 listener_entry_free (e);
692 else l = g_list_next (l);
696 if (detail) g_free (detail);
702 * atspi_event_listener_deregister_no_data:
703 * @callback: (scope call): the #AtspiEventListenerSimpleCB registered against
705 * @event_type: a string specifying the event type for which this
706 * listener is to be deregistered.
708 * deregisters an #AtspiEventListenerSimpleCB from the registry, for a specific
711 * Returns: #TRUE if successful, otherwise #FALSE.
714 atspi_event_listener_deregister_no_data (AtspiEventListenerSimpleCB callback,
715 const gchar *event_type)
717 return atspi_event_listener_deregister_from_callback (remove_datum, callback,
722 atspi_event_copy (AtspiEvent *src)
724 AtspiEvent *dst = g_new0 (AtspiEvent, 1);
725 dst->type.klass = g_strdup (src->type.klass);
727 dst->type.major = g_strdup (src->type.major);
729 dst->type.minor = g_strdup (src->type.minor);
730 dst->source = g_object_ref (src->source);
731 dst->detail1 = src->detail1;
732 dst->detail2 = src->detail2;
733 g_value_init (&dst->any_data, G_VALUE_TYPE (&src->any_data));
734 g_value_copy (&src->any_data, &dst->any_data);
739 atspi_event_free (AtspiEvent *event)
741 g_object_unref (event->source);
742 g_free (event->type.klass);
743 if (event->type.major)
744 g_free (event->type.major);
745 if (event->type.minor)
746 g_free (event->type.minor);
747 g_value_unset (&event->any_data);
752 _atspi_send_event (AtspiEvent *e)
754 char *category, *name, *detail;
757 /* Ensure that the value is set to avoid a Python exception */
758 /* TODO: Figure out how to do this without using a private field */
759 if (e->any_data.g_type == 0)
761 g_value_init (&e->any_data, G_TYPE_INT);
762 g_value_set_int (&e->any_data, 0);
765 if (!convert_event_type_to_dbus (&e->type, &category, &name, &detail, NULL))
767 g_warning ("Atspi: Couldn't parse event: %s\n", e->type);
770 for (l = event_listeners; l; l = g_list_next (l))
772 EventListenerEntry *entry = l->data;
773 if (!strcmp (category, entry->category) &&
774 (entry->name == NULL || !strcmp (name, entry->name)) &&
775 (entry->detail == NULL || !strcmp (detail, entry->detail)))
777 entry->callback (atspi_event_copy (e), entry->user_data);
780 if (detail) g_free (detail);
786 atspi_dbus_handle_event (DBusConnection *bus, DBusMessage *message, void *data)
789 const char *category = dbus_message_get_interface (message);
790 const char *member = dbus_message_get_member (message);
791 const char *signature = dbus_message_get_signature (message);
793 DBusMessageIter iter, iter_variant;
794 dbus_message_iter_init (message, &iter);
796 dbus_int32_t detail1, detail2;
799 if (strcmp (signature, "siiv(so)") != 0)
801 g_warning ("Got invalid signature %s for signal %s from interface %s\n", signature, member, category);
805 memset (&e, 0, sizeof (e));
809 category = g_utf8_strrchr (category, -1, '.');
810 if (category == NULL)
813 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
817 dbus_message_iter_get_basic (&iter, &detail);
818 dbus_message_iter_next (&iter);
819 dbus_message_iter_get_basic (&iter, &detail1);
821 dbus_message_iter_next (&iter);
822 dbus_message_iter_get_basic (&iter, &detail2);
824 dbus_message_iter_next (&iter);
826 e.type.klass = convert_name_from_dbus (category);
827 e.type.major = convert_name_from_dbus (member);
828 e.type.minor = convert_name_from_dbus (detail);
830 e.source = _atspi_ref_accessible (dbus_message_get_sender(message), dbus_message_get_path(message));
832 dbus_message_iter_recurse (&iter, &iter_variant);
833 switch (dbus_message_iter_get_arg_type (&iter_variant))
835 case DBUS_TYPE_STRUCT:
838 if (demarshal_rect (&iter_variant, &rect))
840 g_value_init (&e.any_data, ATSPI_TYPE_RECT);
841 g_value_set_boxed (&e.any_data, &rect);
845 AtspiAccessible *accessible;
846 accessible = _atspi_dbus_return_accessible_from_iter (&iter_variant);
847 g_value_init (&e.any_data, ATSPI_TYPE_ACCESSIBLE);
848 g_value_set_instance (&e.any_data, accessible);
852 case DBUS_TYPE_STRING:
854 dbus_message_iter_get_basic (&iter_variant, &p);
855 g_value_init (&e.any_data, G_TYPE_STRING);
856 g_value_set_string (&e.any_data, p);
862 _atspi_send_event (&e);
864 if (event_is (&e, "object", "children-changed", NULL))
866 cache_process_children_changed (&e);
868 else if (event_is (&e, "object", "property-change", NULL))
870 cache_process_property_change (&e);
872 else if (event_is (&e, "object", "state-changed", NULL))
874 cache_process_state_changed (&e);
877 g_free (e.type.klass);
878 g_free (e.type.major);
879 g_free (e.type.minor);
880 g_object_unref (e.source);
881 g_value_unset (&e.any_data);
882 return DBUS_HANDLER_RESULT_HANDLED;
885 G_DEFINE_BOXED_TYPE (AtspiEvent, atspi_event, atspi_event_copy, atspi_event_free)
887 static AtspiEventType *
888 atspi_event_type_copy (AtspiEventType *src)
890 AtspiEventType *dst = g_new0 (AtspiEventType, 1);
891 dst->klass = g_strdup (src->klass);
893 dst->major = g_strdup (src->major);
895 dst->minor = g_strdup (src->minor);
900 atspi_event_type_free (AtspiEventType *type)
902 g_free (type->klass);
904 g_free (type->major);
906 g_free (type->minor);
910 G_DEFINE_BOXED_TYPE (AtspiEventType, atspi_event_type, atspi_event_type_copy, atspi_event_type_free)