michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ApplicationAccessibleWrap.h" michael@0: #include "mozilla/Likely.h" michael@0: #include "nsAccessibilityService.h" michael@0: #include "nsMai.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::a11y; michael@0: michael@0: typedef AtkUtil MaiUtil; michael@0: typedef AtkUtilClass MaiUtilClass; michael@0: michael@0: #define MAI_VERSION MOZILLA_VERSION michael@0: #define MAI_NAME "Gecko" michael@0: michael@0: extern "C" { michael@0: static guint (*gail_add_global_event_listener)(GSignalEmissionHook listener, michael@0: const gchar* event_type); michael@0: static void (*gail_remove_global_event_listener) (guint remove_listener); michael@0: static void (*gail_remove_key_event_listener) (guint remove_listener); michael@0: static AtkObject* (*gail_get_root)(); michael@0: } michael@0: michael@0: struct MaiUtilListenerInfo michael@0: { michael@0: gint key; michael@0: guint signal_id; michael@0: gulong hook_id; michael@0: // For window create/destory/minimize/maximize/restore/activate/deactivate michael@0: // events, we'll chain gail_util's add/remove_global_event_listener. michael@0: // So we store the listenerid returned by gail's add_global_event_listener michael@0: // in this structure to call gail's remove_global_event_listener later. michael@0: guint gail_listenerid; michael@0: }; michael@0: michael@0: static GHashTable* sListener_list = nullptr; michael@0: static gint sListener_idx = 1; michael@0: michael@0: extern "C" { michael@0: static guint michael@0: add_listener (GSignalEmissionHook listener, michael@0: const gchar *object_type, michael@0: const gchar *signal, michael@0: const gchar *hook_data, michael@0: guint gail_listenerid = 0) michael@0: { michael@0: GType type; michael@0: guint signal_id; michael@0: gint rc = 0; michael@0: michael@0: type = g_type_from_name(object_type); michael@0: if (type) { michael@0: signal_id = g_signal_lookup(signal, type); michael@0: if (signal_id > 0) { michael@0: MaiUtilListenerInfo *listener_info; michael@0: michael@0: rc = sListener_idx; michael@0: michael@0: listener_info = (MaiUtilListenerInfo *) michael@0: g_malloc(sizeof(MaiUtilListenerInfo)); michael@0: listener_info->key = sListener_idx; michael@0: listener_info->hook_id = michael@0: g_signal_add_emission_hook(signal_id, 0, listener, michael@0: g_strdup(hook_data), michael@0: (GDestroyNotify)g_free); michael@0: listener_info->signal_id = signal_id; michael@0: listener_info->gail_listenerid = gail_listenerid; michael@0: michael@0: g_hash_table_insert(sListener_list, &(listener_info->key), michael@0: listener_info); michael@0: sListener_idx++; michael@0: } michael@0: else { michael@0: g_warning("Invalid signal type %s\n", signal); michael@0: } michael@0: } michael@0: else { michael@0: g_warning("Invalid object type %s\n", object_type); michael@0: } michael@0: return rc; michael@0: } michael@0: michael@0: static guint michael@0: mai_util_add_global_event_listener(GSignalEmissionHook listener, michael@0: const gchar *event_type) michael@0: { michael@0: guint rc = 0; michael@0: gchar **split_string; michael@0: michael@0: split_string = g_strsplit (event_type, ":", 3); michael@0: michael@0: if (split_string) { michael@0: if (!strcmp ("window", split_string[0])) { michael@0: guint gail_listenerid = 0; michael@0: if (gail_add_global_event_listener) { michael@0: // call gail's function to track gtk native window events michael@0: gail_listenerid = michael@0: gail_add_global_event_listener(listener, event_type); michael@0: } michael@0: michael@0: rc = add_listener (listener, "MaiAtkObject", split_string[1], michael@0: event_type, gail_listenerid); michael@0: } michael@0: else { michael@0: rc = add_listener (listener, split_string[1], split_string[2], michael@0: event_type); michael@0: } michael@0: g_strfreev(split_string); michael@0: } michael@0: return rc; michael@0: } michael@0: michael@0: static void michael@0: mai_util_remove_global_event_listener(guint remove_listener) michael@0: { michael@0: if (remove_listener > 0) { michael@0: MaiUtilListenerInfo *listener_info; michael@0: gint tmp_idx = remove_listener; michael@0: michael@0: listener_info = (MaiUtilListenerInfo *) michael@0: g_hash_table_lookup(sListener_list, &tmp_idx); michael@0: michael@0: if (listener_info != nullptr) { michael@0: if (gail_remove_global_event_listener && michael@0: listener_info->gail_listenerid) { michael@0: gail_remove_global_event_listener(listener_info->gail_listenerid); michael@0: } michael@0: michael@0: /* Hook id of 0 and signal id of 0 are invalid */ michael@0: if (listener_info->hook_id != 0 && listener_info->signal_id != 0) { michael@0: /* Remove the emission hook */ michael@0: g_signal_remove_emission_hook(listener_info->signal_id, michael@0: listener_info->hook_id); michael@0: michael@0: /* Remove the element from the hash */ michael@0: g_hash_table_remove(sListener_list, &tmp_idx); michael@0: } michael@0: else { michael@0: g_warning("Invalid listener hook_id %ld or signal_id %d\n", michael@0: listener_info->hook_id, listener_info->signal_id); michael@0: } michael@0: } michael@0: else { michael@0: // atk-bridge is initialized with gail (e.g. yelp) michael@0: // try gail_remove_global_event_listener michael@0: if (gail_remove_global_event_listener) { michael@0: return gail_remove_global_event_listener(remove_listener); michael@0: } michael@0: michael@0: g_warning("No listener with the specified listener id %d", michael@0: remove_listener); michael@0: } michael@0: } michael@0: else { michael@0: g_warning("Invalid listener_id %d", remove_listener); michael@0: } michael@0: } michael@0: michael@0: static AtkKeyEventStruct * michael@0: atk_key_event_from_gdk_event_key (GdkEventKey *key) michael@0: { michael@0: AtkKeyEventStruct *event = g_new0(AtkKeyEventStruct, 1); michael@0: switch (key->type) { michael@0: case GDK_KEY_PRESS: michael@0: event->type = ATK_KEY_EVENT_PRESS; michael@0: break; michael@0: case GDK_KEY_RELEASE: michael@0: event->type = ATK_KEY_EVENT_RELEASE; michael@0: break; michael@0: default: michael@0: g_assert_not_reached (); michael@0: return nullptr; michael@0: } michael@0: event->state = key->state; michael@0: event->keyval = key->keyval; michael@0: event->length = key->length; michael@0: if (key->string && key->string [0] && michael@0: (key->state & GDK_CONTROL_MASK || michael@0: g_unichar_isgraph (g_utf8_get_char (key->string)))) { michael@0: event->string = key->string; michael@0: } michael@0: else if (key->type == GDK_KEY_PRESS || michael@0: key->type == GDK_KEY_RELEASE) { michael@0: event->string = gdk_keyval_name (key->keyval); michael@0: } michael@0: event->keycode = key->hardware_keycode; michael@0: event->timestamp = key->time; michael@0: michael@0: return event; michael@0: } michael@0: michael@0: struct MaiKeyEventInfo michael@0: { michael@0: AtkKeyEventStruct *key_event; michael@0: gpointer func_data; michael@0: }; michael@0: michael@0: union AtkKeySnoopFuncPointer michael@0: { michael@0: AtkKeySnoopFunc func_ptr; michael@0: gpointer data; michael@0: }; michael@0: michael@0: static gboolean michael@0: notify_hf(gpointer key, gpointer value, gpointer data) michael@0: { michael@0: MaiKeyEventInfo *info = (MaiKeyEventInfo *)data; michael@0: AtkKeySnoopFuncPointer atkKeySnoop; michael@0: atkKeySnoop.data = value; michael@0: return (atkKeySnoop.func_ptr)(info->key_event, info->func_data) ? TRUE : FALSE; michael@0: } michael@0: michael@0: static void michael@0: insert_hf(gpointer key, gpointer value, gpointer data) michael@0: { michael@0: GHashTable *new_table = (GHashTable *) data; michael@0: g_hash_table_insert (new_table, key, value); michael@0: } michael@0: michael@0: static GHashTable* sKey_listener_list = nullptr; michael@0: michael@0: static gint michael@0: mai_key_snooper(GtkWidget *the_widget, GdkEventKey *event, gpointer func_data) michael@0: { michael@0: /* notify each AtkKeySnoopFunc in turn... */ michael@0: michael@0: MaiKeyEventInfo *info = g_new0(MaiKeyEventInfo, 1); michael@0: gint consumed = 0; michael@0: if (sKey_listener_list) { michael@0: GHashTable *new_hash = g_hash_table_new(nullptr, nullptr); michael@0: g_hash_table_foreach (sKey_listener_list, insert_hf, new_hash); michael@0: info->key_event = atk_key_event_from_gdk_event_key (event); michael@0: info->func_data = func_data; michael@0: consumed = g_hash_table_foreach_steal (new_hash, notify_hf, info); michael@0: g_hash_table_destroy (new_hash); michael@0: g_free(info->key_event); michael@0: } michael@0: g_free(info); michael@0: return (consumed ? 1 : 0); michael@0: } michael@0: michael@0: static guint sKey_snooper_id = 0; michael@0: michael@0: static guint michael@0: mai_util_add_key_event_listener (AtkKeySnoopFunc listener, michael@0: gpointer data) michael@0: { michael@0: if (MOZ_UNLIKELY(!listener)) michael@0: return 0; michael@0: michael@0: static guint key=0; michael@0: michael@0: if (!sKey_listener_list) { michael@0: sKey_listener_list = g_hash_table_new(nullptr, nullptr); michael@0: sKey_snooper_id = gtk_key_snooper_install(mai_key_snooper, data); michael@0: } michael@0: AtkKeySnoopFuncPointer atkKeySnoop; michael@0: atkKeySnoop.func_ptr = listener; michael@0: g_hash_table_insert(sKey_listener_list, GUINT_TO_POINTER (key++), michael@0: atkKeySnoop.data); michael@0: return key; michael@0: } michael@0: michael@0: static void michael@0: mai_util_remove_key_event_listener (guint remove_listener) michael@0: { michael@0: if (!sKey_listener_list) { michael@0: // atk-bridge is initialized with gail (e.g. yelp) michael@0: // try gail_remove_key_event_listener michael@0: return gail_remove_key_event_listener(remove_listener); michael@0: } michael@0: michael@0: g_hash_table_remove(sKey_listener_list, GUINT_TO_POINTER (remove_listener)); michael@0: if (g_hash_table_size(sKey_listener_list) == 0) { michael@0: gtk_key_snooper_remove(sKey_snooper_id); michael@0: } michael@0: } michael@0: michael@0: static AtkObject* michael@0: mai_util_get_root() michael@0: { michael@0: ApplicationAccessible* app = ApplicationAcc(); michael@0: if (app) michael@0: return app->GetAtkObject(); michael@0: michael@0: // We've shutdown, try to use gail instead michael@0: // (to avoid assert in spi_atk_tidy_windows()) michael@0: // XXX tbsaunde then why didn't we replace the gail atk_util impl? michael@0: if (gail_get_root) michael@0: return gail_get_root(); michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: static const gchar* michael@0: mai_util_get_toolkit_name() michael@0: { michael@0: return MAI_NAME; michael@0: } michael@0: michael@0: static const gchar* michael@0: mai_util_get_toolkit_version() michael@0: { michael@0: return MAI_VERSION; michael@0: } michael@0: michael@0: static void michael@0: _listener_info_destroy(gpointer data) michael@0: { michael@0: g_free(data); michael@0: } michael@0: michael@0: static void michael@0: window_added (AtkObject *atk_obj, michael@0: guint index, michael@0: AtkObject *child) michael@0: { michael@0: if (!IS_MAI_OBJECT(child)) michael@0: return; michael@0: michael@0: static guint id = g_signal_lookup ("create", MAI_TYPE_ATK_OBJECT); michael@0: g_signal_emit (child, id, 0); michael@0: } michael@0: michael@0: static void michael@0: window_removed (AtkObject *atk_obj, michael@0: guint index, michael@0: AtkObject *child) michael@0: { michael@0: if (!IS_MAI_OBJECT(child)) michael@0: return; michael@0: michael@0: static guint id = g_signal_lookup ("destroy", MAI_TYPE_ATK_OBJECT); michael@0: g_signal_emit (child, id, 0); michael@0: } michael@0: michael@0: static void michael@0: UtilInterfaceInit(MaiUtilClass* klass) michael@0: { michael@0: AtkUtilClass *atk_class; michael@0: gpointer data; michael@0: michael@0: data = g_type_class_peek(ATK_TYPE_UTIL); michael@0: atk_class = ATK_UTIL_CLASS(data); michael@0: michael@0: // save gail function pointer michael@0: gail_add_global_event_listener = atk_class->add_global_event_listener; michael@0: gail_remove_global_event_listener = atk_class->remove_global_event_listener; michael@0: gail_remove_key_event_listener = atk_class->remove_key_event_listener; michael@0: gail_get_root = atk_class->get_root; michael@0: michael@0: atk_class->add_global_event_listener = michael@0: mai_util_add_global_event_listener; michael@0: atk_class->remove_global_event_listener = michael@0: mai_util_remove_global_event_listener; michael@0: atk_class->add_key_event_listener = mai_util_add_key_event_listener; michael@0: atk_class->remove_key_event_listener = mai_util_remove_key_event_listener; michael@0: atk_class->get_root = mai_util_get_root; michael@0: atk_class->get_toolkit_name = mai_util_get_toolkit_name; michael@0: atk_class->get_toolkit_version = mai_util_get_toolkit_version; michael@0: michael@0: sListener_list = g_hash_table_new_full(g_int_hash, g_int_equal, nullptr, michael@0: _listener_info_destroy); michael@0: // Keep track of added/removed windows. michael@0: AtkObject *root = atk_get_root (); michael@0: g_signal_connect (root, "children-changed::add", (GCallback) window_added, nullptr); michael@0: g_signal_connect (root, "children-changed::remove", (GCallback) window_removed, nullptr); michael@0: } michael@0: } michael@0: michael@0: GType michael@0: mai_util_get_type() michael@0: { michael@0: static GType type = 0; michael@0: michael@0: if (!type) { michael@0: static const GTypeInfo tinfo = { michael@0: sizeof(MaiUtilClass), michael@0: (GBaseInitFunc) nullptr, /* base init */ michael@0: (GBaseFinalizeFunc) nullptr, /* base finalize */ michael@0: (GClassInitFunc) UtilInterfaceInit, /* class init */ michael@0: (GClassFinalizeFunc) nullptr, /* class finalize */ michael@0: nullptr, /* class data */ michael@0: sizeof(MaiUtil), /* instance size */ michael@0: 0, /* nb preallocs */ michael@0: (GInstanceInitFunc) nullptr, /* instance init */ michael@0: nullptr /* value table */ michael@0: }; michael@0: michael@0: type = g_type_register_static(ATK_TYPE_UTIL, michael@0: "MaiUtil", &tinfo, GTypeFlags(0)); michael@0: } michael@0: return type; michael@0: } michael@0: