1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/accessible/src/atk/UtilInterface.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,404 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "ApplicationAccessibleWrap.h" 1.11 +#include "mozilla/Likely.h" 1.12 +#include "nsAccessibilityService.h" 1.13 +#include "nsMai.h" 1.14 + 1.15 +#include <atk/atk.h> 1.16 +#include <gtk/gtk.h> 1.17 +#include <string.h> 1.18 + 1.19 +using namespace mozilla; 1.20 +using namespace mozilla::a11y; 1.21 + 1.22 +typedef AtkUtil MaiUtil; 1.23 +typedef AtkUtilClass MaiUtilClass; 1.24 + 1.25 +#define MAI_VERSION MOZILLA_VERSION 1.26 +#define MAI_NAME "Gecko" 1.27 + 1.28 +extern "C" { 1.29 +static guint (*gail_add_global_event_listener)(GSignalEmissionHook listener, 1.30 + const gchar* event_type); 1.31 +static void (*gail_remove_global_event_listener) (guint remove_listener); 1.32 +static void (*gail_remove_key_event_listener) (guint remove_listener); 1.33 +static AtkObject* (*gail_get_root)(); 1.34 +} 1.35 + 1.36 +struct MaiUtilListenerInfo 1.37 +{ 1.38 + gint key; 1.39 + guint signal_id; 1.40 + gulong hook_id; 1.41 + // For window create/destory/minimize/maximize/restore/activate/deactivate 1.42 + // events, we'll chain gail_util's add/remove_global_event_listener. 1.43 + // So we store the listenerid returned by gail's add_global_event_listener 1.44 + // in this structure to call gail's remove_global_event_listener later. 1.45 + guint gail_listenerid; 1.46 +}; 1.47 + 1.48 +static GHashTable* sListener_list = nullptr; 1.49 +static gint sListener_idx = 1; 1.50 + 1.51 +extern "C" { 1.52 +static guint 1.53 +add_listener (GSignalEmissionHook listener, 1.54 + const gchar *object_type, 1.55 + const gchar *signal, 1.56 + const gchar *hook_data, 1.57 + guint gail_listenerid = 0) 1.58 +{ 1.59 + GType type; 1.60 + guint signal_id; 1.61 + gint rc = 0; 1.62 + 1.63 + type = g_type_from_name(object_type); 1.64 + if (type) { 1.65 + signal_id = g_signal_lookup(signal, type); 1.66 + if (signal_id > 0) { 1.67 + MaiUtilListenerInfo *listener_info; 1.68 + 1.69 + rc = sListener_idx; 1.70 + 1.71 + listener_info = (MaiUtilListenerInfo *) 1.72 + g_malloc(sizeof(MaiUtilListenerInfo)); 1.73 + listener_info->key = sListener_idx; 1.74 + listener_info->hook_id = 1.75 + g_signal_add_emission_hook(signal_id, 0, listener, 1.76 + g_strdup(hook_data), 1.77 + (GDestroyNotify)g_free); 1.78 + listener_info->signal_id = signal_id; 1.79 + listener_info->gail_listenerid = gail_listenerid; 1.80 + 1.81 + g_hash_table_insert(sListener_list, &(listener_info->key), 1.82 + listener_info); 1.83 + sListener_idx++; 1.84 + } 1.85 + else { 1.86 + g_warning("Invalid signal type %s\n", signal); 1.87 + } 1.88 + } 1.89 + else { 1.90 + g_warning("Invalid object type %s\n", object_type); 1.91 + } 1.92 + return rc; 1.93 +} 1.94 + 1.95 +static guint 1.96 +mai_util_add_global_event_listener(GSignalEmissionHook listener, 1.97 + const gchar *event_type) 1.98 +{ 1.99 + guint rc = 0; 1.100 + gchar **split_string; 1.101 + 1.102 + split_string = g_strsplit (event_type, ":", 3); 1.103 + 1.104 + if (split_string) { 1.105 + if (!strcmp ("window", split_string[0])) { 1.106 + guint gail_listenerid = 0; 1.107 + if (gail_add_global_event_listener) { 1.108 + // call gail's function to track gtk native window events 1.109 + gail_listenerid = 1.110 + gail_add_global_event_listener(listener, event_type); 1.111 + } 1.112 + 1.113 + rc = add_listener (listener, "MaiAtkObject", split_string[1], 1.114 + event_type, gail_listenerid); 1.115 + } 1.116 + else { 1.117 + rc = add_listener (listener, split_string[1], split_string[2], 1.118 + event_type); 1.119 + } 1.120 + g_strfreev(split_string); 1.121 + } 1.122 + return rc; 1.123 +} 1.124 + 1.125 +static void 1.126 +mai_util_remove_global_event_listener(guint remove_listener) 1.127 +{ 1.128 + if (remove_listener > 0) { 1.129 + MaiUtilListenerInfo *listener_info; 1.130 + gint tmp_idx = remove_listener; 1.131 + 1.132 + listener_info = (MaiUtilListenerInfo *) 1.133 + g_hash_table_lookup(sListener_list, &tmp_idx); 1.134 + 1.135 + if (listener_info != nullptr) { 1.136 + if (gail_remove_global_event_listener && 1.137 + listener_info->gail_listenerid) { 1.138 + gail_remove_global_event_listener(listener_info->gail_listenerid); 1.139 + } 1.140 + 1.141 + /* Hook id of 0 and signal id of 0 are invalid */ 1.142 + if (listener_info->hook_id != 0 && listener_info->signal_id != 0) { 1.143 + /* Remove the emission hook */ 1.144 + g_signal_remove_emission_hook(listener_info->signal_id, 1.145 + listener_info->hook_id); 1.146 + 1.147 + /* Remove the element from the hash */ 1.148 + g_hash_table_remove(sListener_list, &tmp_idx); 1.149 + } 1.150 + else { 1.151 + g_warning("Invalid listener hook_id %ld or signal_id %d\n", 1.152 + listener_info->hook_id, listener_info->signal_id); 1.153 + } 1.154 + } 1.155 + else { 1.156 + // atk-bridge is initialized with gail (e.g. yelp) 1.157 + // try gail_remove_global_event_listener 1.158 + if (gail_remove_global_event_listener) { 1.159 + return gail_remove_global_event_listener(remove_listener); 1.160 + } 1.161 + 1.162 + g_warning("No listener with the specified listener id %d", 1.163 + remove_listener); 1.164 + } 1.165 + } 1.166 + else { 1.167 + g_warning("Invalid listener_id %d", remove_listener); 1.168 + } 1.169 +} 1.170 + 1.171 +static AtkKeyEventStruct * 1.172 +atk_key_event_from_gdk_event_key (GdkEventKey *key) 1.173 +{ 1.174 + AtkKeyEventStruct *event = g_new0(AtkKeyEventStruct, 1); 1.175 + switch (key->type) { 1.176 + case GDK_KEY_PRESS: 1.177 + event->type = ATK_KEY_EVENT_PRESS; 1.178 + break; 1.179 + case GDK_KEY_RELEASE: 1.180 + event->type = ATK_KEY_EVENT_RELEASE; 1.181 + break; 1.182 + default: 1.183 + g_assert_not_reached (); 1.184 + return nullptr; 1.185 + } 1.186 + event->state = key->state; 1.187 + event->keyval = key->keyval; 1.188 + event->length = key->length; 1.189 + if (key->string && key->string [0] && 1.190 + (key->state & GDK_CONTROL_MASK || 1.191 + g_unichar_isgraph (g_utf8_get_char (key->string)))) { 1.192 + event->string = key->string; 1.193 + } 1.194 + else if (key->type == GDK_KEY_PRESS || 1.195 + key->type == GDK_KEY_RELEASE) { 1.196 + event->string = gdk_keyval_name (key->keyval); 1.197 + } 1.198 + event->keycode = key->hardware_keycode; 1.199 + event->timestamp = key->time; 1.200 + 1.201 + return event; 1.202 +} 1.203 + 1.204 +struct MaiKeyEventInfo 1.205 +{ 1.206 + AtkKeyEventStruct *key_event; 1.207 + gpointer func_data; 1.208 +}; 1.209 + 1.210 +union AtkKeySnoopFuncPointer 1.211 +{ 1.212 + AtkKeySnoopFunc func_ptr; 1.213 + gpointer data; 1.214 +}; 1.215 + 1.216 +static gboolean 1.217 +notify_hf(gpointer key, gpointer value, gpointer data) 1.218 +{ 1.219 + MaiKeyEventInfo *info = (MaiKeyEventInfo *)data; 1.220 + AtkKeySnoopFuncPointer atkKeySnoop; 1.221 + atkKeySnoop.data = value; 1.222 + return (atkKeySnoop.func_ptr)(info->key_event, info->func_data) ? TRUE : FALSE; 1.223 +} 1.224 + 1.225 +static void 1.226 +insert_hf(gpointer key, gpointer value, gpointer data) 1.227 +{ 1.228 + GHashTable *new_table = (GHashTable *) data; 1.229 + g_hash_table_insert (new_table, key, value); 1.230 +} 1.231 + 1.232 +static GHashTable* sKey_listener_list = nullptr; 1.233 + 1.234 +static gint 1.235 +mai_key_snooper(GtkWidget *the_widget, GdkEventKey *event, gpointer func_data) 1.236 +{ 1.237 + /* notify each AtkKeySnoopFunc in turn... */ 1.238 + 1.239 + MaiKeyEventInfo *info = g_new0(MaiKeyEventInfo, 1); 1.240 + gint consumed = 0; 1.241 + if (sKey_listener_list) { 1.242 + GHashTable *new_hash = g_hash_table_new(nullptr, nullptr); 1.243 + g_hash_table_foreach (sKey_listener_list, insert_hf, new_hash); 1.244 + info->key_event = atk_key_event_from_gdk_event_key (event); 1.245 + info->func_data = func_data; 1.246 + consumed = g_hash_table_foreach_steal (new_hash, notify_hf, info); 1.247 + g_hash_table_destroy (new_hash); 1.248 + g_free(info->key_event); 1.249 + } 1.250 + g_free(info); 1.251 + return (consumed ? 1 : 0); 1.252 +} 1.253 + 1.254 +static guint sKey_snooper_id = 0; 1.255 + 1.256 +static guint 1.257 +mai_util_add_key_event_listener (AtkKeySnoopFunc listener, 1.258 + gpointer data) 1.259 +{ 1.260 + if (MOZ_UNLIKELY(!listener)) 1.261 + return 0; 1.262 + 1.263 + static guint key=0; 1.264 + 1.265 + if (!sKey_listener_list) { 1.266 + sKey_listener_list = g_hash_table_new(nullptr, nullptr); 1.267 + sKey_snooper_id = gtk_key_snooper_install(mai_key_snooper, data); 1.268 + } 1.269 + AtkKeySnoopFuncPointer atkKeySnoop; 1.270 + atkKeySnoop.func_ptr = listener; 1.271 + g_hash_table_insert(sKey_listener_list, GUINT_TO_POINTER (key++), 1.272 + atkKeySnoop.data); 1.273 + return key; 1.274 +} 1.275 + 1.276 +static void 1.277 +mai_util_remove_key_event_listener (guint remove_listener) 1.278 +{ 1.279 + if (!sKey_listener_list) { 1.280 + // atk-bridge is initialized with gail (e.g. yelp) 1.281 + // try gail_remove_key_event_listener 1.282 + return gail_remove_key_event_listener(remove_listener); 1.283 + } 1.284 + 1.285 + g_hash_table_remove(sKey_listener_list, GUINT_TO_POINTER (remove_listener)); 1.286 + if (g_hash_table_size(sKey_listener_list) == 0) { 1.287 + gtk_key_snooper_remove(sKey_snooper_id); 1.288 + } 1.289 +} 1.290 + 1.291 +static AtkObject* 1.292 +mai_util_get_root() 1.293 +{ 1.294 + ApplicationAccessible* app = ApplicationAcc(); 1.295 + if (app) 1.296 + return app->GetAtkObject(); 1.297 + 1.298 + // We've shutdown, try to use gail instead 1.299 + // (to avoid assert in spi_atk_tidy_windows()) 1.300 + // XXX tbsaunde then why didn't we replace the gail atk_util impl? 1.301 + if (gail_get_root) 1.302 + return gail_get_root(); 1.303 + 1.304 + return nullptr; 1.305 +} 1.306 + 1.307 +static const gchar* 1.308 +mai_util_get_toolkit_name() 1.309 +{ 1.310 + return MAI_NAME; 1.311 +} 1.312 + 1.313 +static const gchar* 1.314 +mai_util_get_toolkit_version() 1.315 +{ 1.316 + return MAI_VERSION; 1.317 +} 1.318 + 1.319 +static void 1.320 +_listener_info_destroy(gpointer data) 1.321 +{ 1.322 + g_free(data); 1.323 +} 1.324 + 1.325 +static void 1.326 +window_added (AtkObject *atk_obj, 1.327 + guint index, 1.328 + AtkObject *child) 1.329 +{ 1.330 + if (!IS_MAI_OBJECT(child)) 1.331 + return; 1.332 + 1.333 + static guint id = g_signal_lookup ("create", MAI_TYPE_ATK_OBJECT); 1.334 + g_signal_emit (child, id, 0); 1.335 +} 1.336 + 1.337 +static void 1.338 +window_removed (AtkObject *atk_obj, 1.339 + guint index, 1.340 + AtkObject *child) 1.341 +{ 1.342 + if (!IS_MAI_OBJECT(child)) 1.343 + return; 1.344 + 1.345 + static guint id = g_signal_lookup ("destroy", MAI_TYPE_ATK_OBJECT); 1.346 + g_signal_emit (child, id, 0); 1.347 +} 1.348 + 1.349 + static void 1.350 +UtilInterfaceInit(MaiUtilClass* klass) 1.351 +{ 1.352 + AtkUtilClass *atk_class; 1.353 + gpointer data; 1.354 + 1.355 + data = g_type_class_peek(ATK_TYPE_UTIL); 1.356 + atk_class = ATK_UTIL_CLASS(data); 1.357 + 1.358 + // save gail function pointer 1.359 + gail_add_global_event_listener = atk_class->add_global_event_listener; 1.360 + gail_remove_global_event_listener = atk_class->remove_global_event_listener; 1.361 + gail_remove_key_event_listener = atk_class->remove_key_event_listener; 1.362 + gail_get_root = atk_class->get_root; 1.363 + 1.364 + atk_class->add_global_event_listener = 1.365 + mai_util_add_global_event_listener; 1.366 + atk_class->remove_global_event_listener = 1.367 + mai_util_remove_global_event_listener; 1.368 + atk_class->add_key_event_listener = mai_util_add_key_event_listener; 1.369 + atk_class->remove_key_event_listener = mai_util_remove_key_event_listener; 1.370 + atk_class->get_root = mai_util_get_root; 1.371 + atk_class->get_toolkit_name = mai_util_get_toolkit_name; 1.372 + atk_class->get_toolkit_version = mai_util_get_toolkit_version; 1.373 + 1.374 + sListener_list = g_hash_table_new_full(g_int_hash, g_int_equal, nullptr, 1.375 + _listener_info_destroy); 1.376 + // Keep track of added/removed windows. 1.377 + AtkObject *root = atk_get_root (); 1.378 + g_signal_connect (root, "children-changed::add", (GCallback) window_added, nullptr); 1.379 + g_signal_connect (root, "children-changed::remove", (GCallback) window_removed, nullptr); 1.380 +} 1.381 +} 1.382 + 1.383 +GType 1.384 +mai_util_get_type() 1.385 +{ 1.386 + static GType type = 0; 1.387 + 1.388 + if (!type) { 1.389 + static const GTypeInfo tinfo = { 1.390 + sizeof(MaiUtilClass), 1.391 + (GBaseInitFunc) nullptr, /* base init */ 1.392 + (GBaseFinalizeFunc) nullptr, /* base finalize */ 1.393 + (GClassInitFunc) UtilInterfaceInit, /* class init */ 1.394 + (GClassFinalizeFunc) nullptr, /* class finalize */ 1.395 + nullptr, /* class data */ 1.396 + sizeof(MaiUtil), /* instance size */ 1.397 + 0, /* nb preallocs */ 1.398 + (GInstanceInitFunc) nullptr, /* instance init */ 1.399 + nullptr /* value table */ 1.400 + }; 1.401 + 1.402 + type = g_type_register_static(ATK_TYPE_UTIL, 1.403 + "MaiUtil", &tinfo, GTypeFlags(0)); 1.404 + } 1.405 + return type; 1.406 +} 1.407 +