UtilInterface.cpp (11595B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ApplicationAccessible.h" 8 #include "mozilla/Likely.h" 9 #include "nsAccessibilityService.h" 10 #include "nsMai.h" 11 12 #include <atk/atkobject.h> 13 #include <atk/atkutil.h> 14 #include <gtk/gtk.h> 15 #include <string.h> 16 17 using namespace mozilla; 18 using namespace mozilla::a11y; 19 20 typedef AtkUtil MaiUtil; 21 typedef AtkUtilClass MaiUtilClass; 22 23 #define MAI_VERSION MOZILLA_VERSION 24 #define MAI_NAME "Gecko" 25 26 extern "C" { 27 static guint (*gail_add_global_event_listener)(GSignalEmissionHook listener, 28 const gchar* event_type); 29 static void (*gail_remove_global_event_listener)(guint remove_listener); 30 static void (*gail_remove_key_event_listener)(guint remove_listener); 31 static AtkObject* (*gail_get_root)(); 32 } 33 34 struct MaiUtilListenerInfo { 35 gint key; 36 guint signal_id; 37 gulong hook_id; 38 // For window create/destory/minimize/maximize/restore/activate/deactivate 39 // events, we'll chain gail_util's add/remove_global_event_listener. 40 // So we store the listenerid returned by gail's add_global_event_listener 41 // in this structure to call gail's remove_global_event_listener later. 42 guint gail_listenerid; 43 }; 44 45 static GHashTable* sListener_list = nullptr; 46 static gint sListener_idx = 1; 47 48 extern "C" { 49 static guint add_listener(GSignalEmissionHook listener, 50 const gchar* object_type, const gchar* signal, 51 const gchar* hook_data, guint gail_listenerid = 0) { 52 GType type; 53 guint signal_id; 54 gint rc = 0; 55 56 type = g_type_from_name(object_type); 57 if (type) { 58 signal_id = g_signal_lookup(signal, type); 59 if (signal_id > 0) { 60 MaiUtilListenerInfo* listener_info; 61 62 rc = sListener_idx; 63 64 listener_info = 65 (MaiUtilListenerInfo*)g_malloc(sizeof(MaiUtilListenerInfo)); 66 listener_info->key = sListener_idx; 67 listener_info->hook_id = g_signal_add_emission_hook( 68 signal_id, 0, listener, g_strdup(hook_data), (GDestroyNotify)g_free); 69 listener_info->signal_id = signal_id; 70 listener_info->gail_listenerid = gail_listenerid; 71 72 g_hash_table_insert(sListener_list, &(listener_info->key), listener_info); 73 sListener_idx++; 74 } else { 75 g_warning("Invalid signal type %s\n", signal); 76 } 77 } else { 78 g_warning("Invalid object type %s\n", object_type); 79 } 80 return rc; 81 } 82 83 static guint mai_util_add_global_event_listener(GSignalEmissionHook listener, 84 const gchar* event_type) { 85 guint rc = 0; 86 gchar** split_string; 87 88 split_string = g_strsplit(event_type, ":", 3); 89 90 if (split_string) { 91 if (!strcmp("window", split_string[0])) { 92 guint gail_listenerid = 0; 93 if (gail_add_global_event_listener) { 94 // call gail's function to track gtk native window events 95 gail_listenerid = gail_add_global_event_listener(listener, event_type); 96 } 97 98 rc = add_listener(listener, "MaiAtkObject", split_string[1], event_type, 99 gail_listenerid); 100 } else { 101 rc = add_listener(listener, split_string[1], split_string[2], event_type); 102 } 103 g_strfreev(split_string); 104 } 105 return rc; 106 } 107 108 static void mai_util_remove_global_event_listener(guint remove_listener) { 109 if (remove_listener > 0) { 110 MaiUtilListenerInfo* listener_info; 111 gint tmp_idx = remove_listener; 112 113 listener_info = 114 (MaiUtilListenerInfo*)g_hash_table_lookup(sListener_list, &tmp_idx); 115 116 if (listener_info != nullptr) { 117 if (gail_remove_global_event_listener && listener_info->gail_listenerid) { 118 gail_remove_global_event_listener(listener_info->gail_listenerid); 119 } 120 121 /* Hook id of 0 and signal id of 0 are invalid */ 122 if (listener_info->hook_id != 0 && listener_info->signal_id != 0) { 123 /* Remove the emission hook */ 124 g_signal_remove_emission_hook(listener_info->signal_id, 125 listener_info->hook_id); 126 127 /* Remove the element from the hash */ 128 g_hash_table_remove(sListener_list, &tmp_idx); 129 } else { 130 g_warning("Invalid listener hook_id %ld or signal_id %d\n", 131 listener_info->hook_id, listener_info->signal_id); 132 } 133 } else { 134 // atk-bridge is initialized with gail (e.g. yelp) 135 // try gail_remove_global_event_listener 136 if (gail_remove_global_event_listener) { 137 return gail_remove_global_event_listener(remove_listener); 138 } 139 140 g_warning("No listener with the specified listener id %d", 141 remove_listener); 142 } 143 } else { 144 g_warning("Invalid listener_id %d", remove_listener); 145 } 146 } 147 148 static AtkKeyEventStruct* atk_key_event_from_gdk_event_key(GdkEventKey* key) { 149 AtkKeyEventStruct* event = g_new0(AtkKeyEventStruct, 1); 150 switch (key->type) { 151 case GDK_KEY_PRESS: 152 event->type = ATK_KEY_EVENT_PRESS; 153 break; 154 case GDK_KEY_RELEASE: 155 event->type = ATK_KEY_EVENT_RELEASE; 156 break; 157 default: 158 g_assert_not_reached(); 159 return nullptr; 160 } 161 event->state = key->state; 162 event->keyval = key->keyval; 163 event->length = key->length; 164 if (key->string && key->string[0] && 165 g_unichar_isgraph(g_utf8_get_char(key->string))) { 166 event->string = key->string; 167 } else if (key->type == GDK_KEY_PRESS || key->type == GDK_KEY_RELEASE) { 168 event->string = gdk_keyval_name(key->keyval); 169 } 170 event->keycode = key->hardware_keycode; 171 event->timestamp = key->time; 172 173 return event; 174 } 175 176 struct MaiKeyEventInfo { 177 AtkKeyEventStruct* key_event; 178 gpointer func_data; 179 }; 180 181 union AtkKeySnoopFuncPointer { 182 AtkKeySnoopFunc func_ptr; 183 gpointer data; 184 }; 185 186 static gboolean notify_hf(gpointer key, gpointer value, gpointer data) { 187 MaiKeyEventInfo* info = (MaiKeyEventInfo*)data; 188 AtkKeySnoopFuncPointer atkKeySnoop; 189 atkKeySnoop.data = value; 190 return (atkKeySnoop.func_ptr)(info->key_event, info->func_data) ? TRUE 191 : FALSE; 192 } 193 194 static void insert_hf(gpointer key, gpointer value, gpointer data) { 195 GHashTable* new_table = (GHashTable*)data; 196 g_hash_table_insert(new_table, key, value); 197 } 198 199 static GHashTable* sKey_listener_list = nullptr; 200 201 static gint mai_key_snooper(GtkWidget* the_widget, GdkEventKey* event, 202 gpointer func_data) { 203 /* notify each AtkKeySnoopFunc in turn... */ 204 205 MaiKeyEventInfo* info = g_new0(MaiKeyEventInfo, 1); 206 gint consumed = 0; 207 if (sKey_listener_list) { 208 GHashTable* new_hash = g_hash_table_new(nullptr, nullptr); 209 g_hash_table_foreach(sKey_listener_list, insert_hf, new_hash); 210 info->key_event = atk_key_event_from_gdk_event_key(event); 211 info->func_data = func_data; 212 consumed = g_hash_table_foreach_steal(new_hash, notify_hf, info); 213 g_hash_table_destroy(new_hash); 214 g_free(info->key_event); 215 } 216 g_free(info); 217 return (consumed ? 1 : 0); 218 } 219 220 static guint sKey_snooper_id = 0; 221 222 static guint mai_util_add_key_event_listener(AtkKeySnoopFunc listener, 223 gpointer data) { 224 if (MOZ_UNLIKELY(!listener)) { 225 return 0; 226 } 227 228 static guint key = 0; 229 230 if (!sKey_listener_list) { 231 sKey_listener_list = g_hash_table_new(nullptr, nullptr); 232 } 233 234 // If we have no registered event listeners then we need to (re)install the 235 // key event snooper. 236 if (g_hash_table_size(sKey_listener_list) == 0) { 237 sKey_snooper_id = gtk_key_snooper_install(mai_key_snooper, data); 238 } 239 240 AtkKeySnoopFuncPointer atkKeySnoop; 241 atkKeySnoop.func_ptr = listener; 242 key++; 243 g_hash_table_insert(sKey_listener_list, GUINT_TO_POINTER(key), 244 atkKeySnoop.data); 245 return key; 246 } 247 248 static void mai_util_remove_key_event_listener(guint remove_listener) { 249 if (!sKey_listener_list) { 250 // atk-bridge is initialized with gail (e.g. yelp) 251 // try gail_remove_key_event_listener 252 return gail_remove_key_event_listener(remove_listener); 253 } 254 255 g_hash_table_remove(sKey_listener_list, GUINT_TO_POINTER(remove_listener)); 256 if (g_hash_table_size(sKey_listener_list) == 0) { 257 gtk_key_snooper_remove(sKey_snooper_id); 258 } 259 } 260 261 static AtkObject* mai_util_get_root() { 262 ApplicationAccessible* app = ApplicationAcc(); 263 if (app) return app->GetAtkObject(); 264 265 // We've shutdown, try to use gail instead 266 // (to avoid assert in spi_atk_tidy_windows()) 267 // XXX tbsaunde then why didn't we replace the gail atk_util impl? 268 if (gail_get_root) return gail_get_root(); 269 270 return nullptr; 271 } 272 273 static const gchar* mai_util_get_toolkit_name() { return MAI_NAME; } 274 275 static const gchar* mai_util_get_toolkit_version() { return MAI_VERSION; } 276 277 static void _listener_info_destroy(gpointer data) { g_free(data); } 278 279 static void window_added(AtkObject* atk_obj, guint index, AtkObject* child) { 280 if (!IS_MAI_OBJECT(child)) return; 281 282 static guint id = g_signal_lookup("create", MAI_TYPE_ATK_OBJECT); 283 g_signal_emit(child, id, 0); 284 } 285 286 static void window_removed(AtkObject* atk_obj, guint index, AtkObject* child) { 287 if (!IS_MAI_OBJECT(child)) return; 288 289 static guint id = g_signal_lookup("destroy", MAI_TYPE_ATK_OBJECT); 290 g_signal_emit(child, id, 0); 291 } 292 293 static void UtilInterfaceInit(MaiUtilClass* klass) { 294 AtkUtilClass* atk_class; 295 gpointer data; 296 297 data = g_type_class_peek(ATK_TYPE_UTIL); 298 atk_class = ATK_UTIL_CLASS(data); 299 300 // save gail function pointer 301 gail_add_global_event_listener = atk_class->add_global_event_listener; 302 gail_remove_global_event_listener = atk_class->remove_global_event_listener; 303 gail_remove_key_event_listener = atk_class->remove_key_event_listener; 304 gail_get_root = atk_class->get_root; 305 306 atk_class->add_global_event_listener = mai_util_add_global_event_listener; 307 atk_class->remove_global_event_listener = 308 mai_util_remove_global_event_listener; 309 atk_class->add_key_event_listener = mai_util_add_key_event_listener; 310 atk_class->remove_key_event_listener = mai_util_remove_key_event_listener; 311 atk_class->get_root = mai_util_get_root; 312 atk_class->get_toolkit_name = mai_util_get_toolkit_name; 313 atk_class->get_toolkit_version = mai_util_get_toolkit_version; 314 315 sListener_list = g_hash_table_new_full(g_int_hash, g_int_equal, nullptr, 316 _listener_info_destroy); 317 // Keep track of added/removed windows. 318 AtkObject* root = atk_get_root(); 319 g_signal_connect(root, "children-changed::add", (GCallback)window_added, 320 nullptr); 321 g_signal_connect(root, "children-changed::remove", (GCallback)window_removed, 322 nullptr); 323 } 324 } 325 326 GType mai_util_get_type() { 327 static GType type = 0; 328 329 if (!type) { 330 static const GTypeInfo tinfo = { 331 sizeof(MaiUtilClass), 332 (GBaseInitFunc) nullptr, /* base init */ 333 (GBaseFinalizeFunc) nullptr, /* base finalize */ 334 (GClassInitFunc)UtilInterfaceInit, /* class init */ 335 (GClassFinalizeFunc) nullptr, /* class finalize */ 336 nullptr, /* class data */ 337 sizeof(MaiUtil), /* instance size */ 338 0, /* nb preallocs */ 339 (GInstanceInitFunc) nullptr, /* instance init */ 340 nullptr /* value table */ 341 }; 342 343 type = 344 g_type_register_static(ATK_TYPE_UTIL, "MaiUtil", &tinfo, GTypeFlags(0)); 345 } 346 return type; 347 }