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