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 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Sun Microsystems, Inc.
20 : * Portions created by the Initial Developer are Copyright (C) 2002
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Bolian Yin (bolian.yin@sun.com)
25 : * Ginn Chen (ginn.chen@sun.com)
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either the GNU General Public License Version 2 or later (the "GPL"), or
29 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : #include "nsApplicationAccessibleWrap.h"
42 :
43 : #include "nsCOMPtr.h"
44 : #include "nsMai.h"
45 : #include "prlink.h"
46 : #include "prenv.h"
47 : #include "nsIGConfService.h"
48 : #include "nsIServiceManager.h"
49 : #include "nsAutoPtr.h"
50 : #include "nsAccessibilityService.h"
51 : #include "AtkSocketAccessible.h"
52 :
53 : #include <gtk/gtk.h>
54 : #include <atk/atk.h>
55 : #ifdef MOZ_ENABLE_DBUS
56 : #include <dbus/dbus.h>
57 : #endif
58 :
59 : using namespace mozilla;
60 : using namespace mozilla::a11y;
61 :
62 : typedef GType (* AtkGetTypeType) (void);
63 : GType g_atk_hyperlink_impl_type = G_TYPE_INVALID;
64 : static bool sATKChecked = false;
65 : static PRLibrary *sATKLib = nsnull;
66 : static const char sATKLibName[] = "libatk-1.0.so.0";
67 : static const char sATKHyperlinkImplGetTypeSymbol[] =
68 : "atk_hyperlink_impl_get_type";
69 :
70 : /* gail function pointer */
71 : static guint (* gail_add_global_event_listener) (GSignalEmissionHook listener,
72 : const gchar *event_type);
73 : static void (* gail_remove_global_event_listener) (guint remove_listener);
74 : static void (* gail_remove_key_event_listener) (guint remove_listener);
75 : static AtkObject * (*gail_get_root) (void);
76 :
77 : /* maiutil */
78 :
79 : static guint mai_util_add_global_event_listener(GSignalEmissionHook listener,
80 : const gchar *event_type);
81 : static void mai_util_remove_global_event_listener(guint remove_listener);
82 : static guint mai_util_add_key_event_listener(AtkKeySnoopFunc listener,
83 : gpointer data);
84 : static void mai_util_remove_key_event_listener(guint remove_listener);
85 : static AtkObject *mai_util_get_root(void);
86 : static G_CONST_RETURN gchar *mai_util_get_toolkit_name(void);
87 : static G_CONST_RETURN gchar *mai_util_get_toolkit_version(void);
88 :
89 :
90 : /* Misc */
91 :
92 : static void _listener_info_destroy(gpointer data);
93 : static guint add_listener (GSignalEmissionHook listener,
94 : const gchar *object_type,
95 : const gchar *signal,
96 : const gchar *hook_data,
97 : guint gail_listenerid = 0);
98 : static AtkKeyEventStruct *atk_key_event_from_gdk_event_key(GdkEventKey *key);
99 : static gboolean notify_hf(gpointer key, gpointer value, gpointer data);
100 : static void insert_hf(gpointer key, gpointer value, gpointer data);
101 : static gint mai_key_snooper(GtkWidget *the_widget, GdkEventKey *event,
102 : gpointer func_data);
103 :
104 : static GHashTable* sListener_list = NULL;
105 : static gint sListener_idx = 1;
106 :
107 : #define MAI_TYPE_UTIL (mai_util_get_type ())
108 : #define MAI_UTIL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
109 : MAI_TYPE_UTIL, MaiUtil))
110 : #define MAI_UTIL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), \
111 : MAI_TYPE_UTIL, MaiUtilClass))
112 : #define MAI_IS_UTIL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
113 : MAI_TYPE_UTIL))
114 : #define MAI_IS_UTIL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \
115 : MAI_TYPE_UTIL))
116 : #define MAI_UTIL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), \
117 : MAI_TYPE_UTIL, MaiUtilClass))
118 :
119 : static GHashTable* sKey_listener_list = NULL;
120 : static guint sKey_snooper_id = 0;
121 : static bool sToplevel_event_hook_added = false;
122 : static gulong sToplevel_show_hook = 0;
123 : static gulong sToplevel_hide_hook = 0;
124 :
125 : G_BEGIN_DECLS
126 : typedef void (*GnomeAccessibilityInit) (void);
127 : typedef void (*GnomeAccessibilityShutdown) (void);
128 : G_END_DECLS
129 :
130 : struct MaiUtil
131 : {
132 : AtkUtil parent;
133 : GList *listener_list;
134 : };
135 :
136 : struct MaiKeyEventInfo
137 : {
138 : AtkKeyEventStruct *key_event;
139 : gpointer func_data;
140 : };
141 :
142 : union AtkKeySnoopFuncPointer
143 : {
144 : AtkKeySnoopFunc func_ptr;
145 : gpointer data;
146 : };
147 :
148 : struct GnomeAccessibilityModule
149 : {
150 : const char *libName;
151 : PRLibrary *lib;
152 : const char *initName;
153 : GnomeAccessibilityInit init;
154 : const char *shutdownName;
155 : GnomeAccessibilityShutdown shutdown;
156 : };
157 :
158 : struct MaiUtilClass
159 : {
160 : AtkUtilClass parent_class;
161 : };
162 :
163 : GType mai_util_get_type (void);
164 : static void mai_util_class_init(MaiUtilClass *klass);
165 :
166 : /* supporting */
167 : PRLogModuleInfo *gMaiLog = NULL;
168 :
169 : #define MAI_VERSION MOZILLA_VERSION
170 : #define MAI_NAME "Gecko"
171 :
172 : struct MaiUtilListenerInfo
173 : {
174 : gint key;
175 : guint signal_id;
176 : gulong hook_id;
177 : // For window create/destory/minimize/maximize/restore/activate/deactivate
178 : // events, we'll chain gail_util's add/remove_global_event_listener.
179 : // So we store the listenerid returned by gail's add_global_event_listener
180 : // in this structure to call gail's remove_global_event_listener later.
181 : guint gail_listenerid;
182 : };
183 :
184 : static GnomeAccessibilityModule sAtkBridge = {
185 : #ifdef AIX
186 : "libatk-bridge.a(libatk-bridge.so.0)", NULL,
187 : #else
188 : "libatk-bridge.so", NULL,
189 : #endif
190 : "gnome_accessibility_module_init", NULL,
191 : "gnome_accessibility_module_shutdown", NULL
192 : };
193 :
194 : static GnomeAccessibilityModule sGail = {
195 : "libgail.so", NULL,
196 : "gnome_accessibility_module_init", NULL,
197 : "gnome_accessibility_module_shutdown", NULL
198 : };
199 :
200 : GType
201 0 : mai_util_get_type(void)
202 : {
203 : static GType type = 0;
204 :
205 0 : if (!type) {
206 : static const GTypeInfo tinfo = {
207 : sizeof(MaiUtilClass),
208 : (GBaseInitFunc) NULL, /* base init */
209 : (GBaseFinalizeFunc) NULL, /* base finalize */
210 : (GClassInitFunc) mai_util_class_init, /* class init */
211 : (GClassFinalizeFunc) NULL, /* class finalize */
212 : NULL, /* class data */
213 : sizeof(MaiUtil), /* instance size */
214 : 0, /* nb preallocs */
215 : (GInstanceInitFunc) NULL, /* instance init */
216 : NULL /* value table */
217 : };
218 :
219 : type = g_type_register_static(ATK_TYPE_UTIL,
220 0 : "MaiUtil", &tinfo, GTypeFlags(0));
221 : }
222 0 : return type;
223 : }
224 :
225 : static void
226 0 : window_added (AtkObject *atk_obj,
227 : guint index,
228 : AtkObject *child)
229 : {
230 0 : if (!IS_MAI_OBJECT(child))
231 0 : return;
232 :
233 0 : static guint id = g_signal_lookup ("create", MAI_TYPE_ATK_OBJECT);
234 0 : g_signal_emit (child, id, 0);
235 : }
236 :
237 : static void
238 0 : window_removed (AtkObject *atk_obj,
239 : guint index,
240 : AtkObject *child)
241 : {
242 0 : if (!IS_MAI_OBJECT(child))
243 0 : return;
244 :
245 0 : static guint id = g_signal_lookup ("destroy", MAI_TYPE_ATK_OBJECT);
246 0 : g_signal_emit (child, id, 0);
247 : }
248 :
249 : /* intialize the the atk interface (function pointers) with MAI implementation.
250 : * When atk bridge get loaded, these interface can be used.
251 : */
252 : static void
253 0 : mai_util_class_init(MaiUtilClass *klass)
254 : {
255 : AtkUtilClass *atk_class;
256 : gpointer data;
257 :
258 0 : data = g_type_class_peek(ATK_TYPE_UTIL);
259 0 : atk_class = ATK_UTIL_CLASS(data);
260 :
261 : // save gail function pointer
262 0 : gail_add_global_event_listener = atk_class->add_global_event_listener;
263 0 : gail_remove_global_event_listener = atk_class->remove_global_event_listener;
264 0 : gail_remove_key_event_listener = atk_class->remove_key_event_listener;
265 0 : gail_get_root = atk_class->get_root;
266 :
267 : atk_class->add_global_event_listener =
268 0 : mai_util_add_global_event_listener;
269 : atk_class->remove_global_event_listener =
270 0 : mai_util_remove_global_event_listener;
271 0 : atk_class->add_key_event_listener = mai_util_add_key_event_listener;
272 0 : atk_class->remove_key_event_listener = mai_util_remove_key_event_listener;
273 0 : atk_class->get_root = mai_util_get_root;
274 0 : atk_class->get_toolkit_name = mai_util_get_toolkit_name;
275 0 : atk_class->get_toolkit_version = mai_util_get_toolkit_version;
276 :
277 : sListener_list = g_hash_table_new_full(g_int_hash, g_int_equal, NULL,
278 0 : _listener_info_destroy);
279 : // Keep track of added/removed windows.
280 0 : AtkObject *root = atk_get_root ();
281 0 : g_signal_connect (root, "children-changed::add", (GCallback) window_added, NULL);
282 0 : g_signal_connect (root, "children-changed::remove", (GCallback) window_removed, NULL);
283 0 : }
284 :
285 : static guint
286 0 : mai_util_add_global_event_listener(GSignalEmissionHook listener,
287 : const gchar *event_type)
288 : {
289 0 : guint rc = 0;
290 : gchar **split_string;
291 :
292 0 : split_string = g_strsplit (event_type, ":", 3);
293 :
294 0 : if (split_string) {
295 0 : if (!strcmp ("window", split_string[0])) {
296 0 : guint gail_listenerid = 0;
297 0 : if (gail_add_global_event_listener) {
298 : // call gail's function to track gtk native window events
299 : gail_listenerid =
300 0 : gail_add_global_event_listener(listener, event_type);
301 : }
302 :
303 0 : rc = add_listener (listener, "MaiAtkObject", split_string[1],
304 0 : event_type, gail_listenerid);
305 : }
306 : else {
307 0 : rc = add_listener (listener, split_string[1], split_string[2],
308 0 : event_type);
309 : }
310 0 : g_strfreev(split_string);
311 : }
312 0 : return rc;
313 : }
314 :
315 : static void
316 0 : mai_util_remove_global_event_listener(guint remove_listener)
317 : {
318 0 : if (remove_listener > 0) {
319 : MaiUtilListenerInfo *listener_info;
320 0 : gint tmp_idx = remove_listener;
321 :
322 : listener_info = (MaiUtilListenerInfo *)
323 0 : g_hash_table_lookup(sListener_list, &tmp_idx);
324 :
325 0 : if (listener_info != NULL) {
326 0 : if (gail_remove_global_event_listener &&
327 : listener_info->gail_listenerid) {
328 0 : gail_remove_global_event_listener(listener_info->gail_listenerid);
329 : }
330 :
331 : /* Hook id of 0 and signal id of 0 are invalid */
332 0 : if (listener_info->hook_id != 0 && listener_info->signal_id != 0) {
333 : /* Remove the emission hook */
334 : g_signal_remove_emission_hook(listener_info->signal_id,
335 0 : listener_info->hook_id);
336 :
337 : /* Remove the element from the hash */
338 0 : g_hash_table_remove(sListener_list, &tmp_idx);
339 : }
340 : else {
341 0 : g_warning("Invalid listener hook_id %ld or signal_id %d\n",
342 0 : listener_info->hook_id, listener_info->signal_id);
343 : }
344 : }
345 : else {
346 : // atk-bridge is initialized with gail (e.g. yelp)
347 : // try gail_remove_global_event_listener
348 0 : if (gail_remove_global_event_listener) {
349 0 : return gail_remove_global_event_listener(remove_listener);
350 : }
351 :
352 : g_warning("No listener with the specified listener id %d",
353 0 : remove_listener);
354 : }
355 : }
356 : else {
357 0 : g_warning("Invalid listener_id %d", remove_listener);
358 : }
359 : }
360 :
361 : static AtkKeyEventStruct *
362 0 : atk_key_event_from_gdk_event_key (GdkEventKey *key)
363 : {
364 0 : AtkKeyEventStruct *event = g_new0(AtkKeyEventStruct, 1);
365 0 : switch (key->type) {
366 : case GDK_KEY_PRESS:
367 0 : event->type = ATK_KEY_EVENT_PRESS;
368 0 : break;
369 : case GDK_KEY_RELEASE:
370 0 : event->type = ATK_KEY_EVENT_RELEASE;
371 0 : break;
372 : default:
373 0 : g_assert_not_reached ();
374 : return NULL;
375 : }
376 0 : event->state = key->state;
377 0 : event->keyval = key->keyval;
378 0 : event->length = key->length;
379 0 : if (key->string && key->string [0] &&
380 : (key->state & GDK_CONTROL_MASK ||
381 0 : g_unichar_isgraph (g_utf8_get_char (key->string)))) {
382 0 : event->string = key->string;
383 : }
384 0 : else if (key->type == GDK_KEY_PRESS ||
385 : key->type == GDK_KEY_RELEASE) {
386 0 : event->string = gdk_keyval_name (key->keyval);
387 : }
388 0 : event->keycode = key->hardware_keycode;
389 0 : event->timestamp = key->time;
390 :
391 0 : MAI_LOG_DEBUG(("MaiKey:\tsym %u\n\tmods %x\n\tcode %u\n\ttime %lx\n",
392 : (unsigned int) event->keyval,
393 : (unsigned int) event->state,
394 : (unsigned int) event->keycode,
395 : (unsigned long int) event->timestamp));
396 0 : return event;
397 : }
398 :
399 : static gboolean
400 0 : notify_hf(gpointer key, gpointer value, gpointer data)
401 : {
402 0 : MaiKeyEventInfo *info = (MaiKeyEventInfo *)data;
403 : AtkKeySnoopFuncPointer atkKeySnoop;
404 0 : atkKeySnoop.data = value;
405 0 : return (atkKeySnoop.func_ptr)(info->key_event, info->func_data) ? TRUE : FALSE;
406 : }
407 :
408 : static void
409 0 : insert_hf(gpointer key, gpointer value, gpointer data)
410 : {
411 0 : GHashTable *new_table = (GHashTable *) data;
412 0 : g_hash_table_insert (new_table, key, value);
413 0 : }
414 :
415 : static gint
416 0 : mai_key_snooper(GtkWidget *the_widget, GdkEventKey *event, gpointer func_data)
417 : {
418 : /* notify each AtkKeySnoopFunc in turn... */
419 :
420 0 : MaiKeyEventInfo *info = g_new0(MaiKeyEventInfo, 1);
421 0 : gint consumed = 0;
422 0 : if (sKey_listener_list) {
423 0 : GHashTable *new_hash = g_hash_table_new(NULL, NULL);
424 0 : g_hash_table_foreach (sKey_listener_list, insert_hf, new_hash);
425 0 : info->key_event = atk_key_event_from_gdk_event_key (event);
426 0 : info->func_data = func_data;
427 0 : consumed = g_hash_table_foreach_steal (new_hash, notify_hf, info);
428 0 : g_hash_table_destroy (new_hash);
429 0 : g_free(info->key_event);
430 : }
431 0 : g_free(info);
432 0 : return (consumed ? 1 : 0);
433 : }
434 :
435 : static guint
436 0 : mai_util_add_key_event_listener (AtkKeySnoopFunc listener,
437 : gpointer data)
438 : {
439 0 : NS_ENSURE_TRUE(listener, 0);
440 :
441 : static guint key=0;
442 :
443 0 : if (!sKey_listener_list) {
444 0 : sKey_listener_list = g_hash_table_new(NULL, NULL);
445 0 : sKey_snooper_id = gtk_key_snooper_install(mai_key_snooper, data);
446 : }
447 : AtkKeySnoopFuncPointer atkKeySnoop;
448 0 : atkKeySnoop.func_ptr = listener;
449 : g_hash_table_insert(sKey_listener_list, GUINT_TO_POINTER (key++),
450 0 : atkKeySnoop.data);
451 0 : return key;
452 : }
453 :
454 : static void
455 0 : mai_util_remove_key_event_listener (guint remove_listener)
456 : {
457 0 : if (!sKey_listener_list) {
458 : // atk-bridge is initialized with gail (e.g. yelp)
459 : // try gail_remove_key_event_listener
460 0 : return gail_remove_key_event_listener(remove_listener);
461 : }
462 :
463 0 : g_hash_table_remove(sKey_listener_list, GUINT_TO_POINTER (remove_listener));
464 0 : if (g_hash_table_size(sKey_listener_list) == 0) {
465 0 : gtk_key_snooper_remove(sKey_snooper_id);
466 : }
467 : }
468 :
469 : AtkObject *
470 0 : mai_util_get_root(void)
471 : {
472 0 : if (nsAccessibilityService::IsShutdown()) {
473 : // We've shutdown, try to use gail instead
474 : // (to avoid assert in spi_atk_tidy_windows())
475 0 : if (gail_get_root)
476 0 : return gail_get_root();
477 :
478 0 : return nsnull;
479 : }
480 :
481 : nsApplicationAccessible *applicationAcc =
482 0 : nsAccessNode::GetApplicationAccessible();
483 :
484 0 : if (applicationAcc)
485 0 : return applicationAcc->GetAtkObject();
486 :
487 0 : return nsnull;
488 : }
489 :
490 : G_CONST_RETURN gchar *
491 0 : mai_util_get_toolkit_name(void)
492 : {
493 0 : return MAI_NAME;
494 : }
495 :
496 : G_CONST_RETURN gchar *
497 0 : mai_util_get_toolkit_version(void)
498 : {
499 0 : return MAI_VERSION;
500 : }
501 :
502 : void
503 0 : _listener_info_destroy(gpointer data)
504 : {
505 0 : g_free(data);
506 0 : }
507 :
508 : guint
509 0 : add_listener (GSignalEmissionHook listener,
510 : const gchar *object_type,
511 : const gchar *signal,
512 : const gchar *hook_data,
513 : guint gail_listenerid)
514 : {
515 : GType type;
516 : guint signal_id;
517 0 : gint rc = 0;
518 :
519 0 : type = g_type_from_name(object_type);
520 0 : if (type) {
521 0 : signal_id = g_signal_lookup(signal, type);
522 0 : if (signal_id > 0) {
523 : MaiUtilListenerInfo *listener_info;
524 :
525 0 : rc = sListener_idx;
526 :
527 : listener_info = (MaiUtilListenerInfo *)
528 0 : g_malloc(sizeof(MaiUtilListenerInfo));
529 0 : listener_info->key = sListener_idx;
530 : listener_info->hook_id =
531 : g_signal_add_emission_hook(signal_id, 0, listener,
532 0 : g_strdup(hook_data),
533 0 : (GDestroyNotify)g_free);
534 0 : listener_info->signal_id = signal_id;
535 0 : listener_info->gail_listenerid = gail_listenerid;
536 :
537 : g_hash_table_insert(sListener_list, &(listener_info->key),
538 0 : listener_info);
539 0 : sListener_idx++;
540 : }
541 : else {
542 0 : g_warning("Invalid signal type %s\n", signal);
543 : }
544 : }
545 : else {
546 0 : g_warning("Invalid object type %s\n", object_type);
547 : }
548 0 : return rc;
549 : }
550 :
551 : static nsresult LoadGtkModule(GnomeAccessibilityModule& aModule);
552 :
553 : // nsApplicationAccessibleWrap
554 :
555 0 : nsApplicationAccessibleWrap::nsApplicationAccessibleWrap():
556 0 : nsApplicationAccessible()
557 : {
558 0 : MAI_LOG_DEBUG(("======Create AppRootAcc=%p\n", (void*)this));
559 0 : }
560 :
561 0 : nsApplicationAccessibleWrap::~nsApplicationAccessibleWrap()
562 : {
563 0 : MAI_LOG_DEBUG(("======Destory AppRootAcc=%p\n", (void*)this));
564 0 : nsAccessibleWrap::ShutdownAtkObject();
565 0 : }
566 :
567 : static gboolean
568 0 : toplevel_event_watcher(GSignalInvocationHint* ihint,
569 : guint n_param_values,
570 : const GValue* param_values,
571 : gpointer data)
572 : {
573 : static GQuark sQuark_gecko_acc_obj = 0;
574 :
575 0 : if (!sQuark_gecko_acc_obj)
576 0 : sQuark_gecko_acc_obj = g_quark_from_static_string("GeckoAccObj");
577 :
578 0 : if (nsAccessibilityService::IsShutdown())
579 0 : return TRUE;
580 :
581 0 : GObject* object = reinterpret_cast<GObject*>(g_value_get_object(param_values));
582 0 : if (!GTK_IS_WINDOW(object))
583 0 : return TRUE;
584 :
585 0 : AtkObject* child = gtk_widget_get_accessible(GTK_WIDGET(object));
586 :
587 : // GTK native dialog
588 0 : if (!IS_MAI_OBJECT(child) &&
589 0 : (atk_object_get_role(child) == ATK_ROLE_DIALOG)) {
590 :
591 0 : if (data == reinterpret_cast<gpointer>(nsIAccessibleEvent::EVENT_SHOW)) {
592 :
593 : // Attach the dialog accessible to app accessible tree
594 0 : nsAccessible* windowAcc = GetAccService()->AddNativeRootAccessible(child);
595 0 : g_object_set_qdata(G_OBJECT(child), sQuark_gecko_acc_obj,
596 0 : reinterpret_cast<gpointer>(windowAcc));
597 :
598 : } else {
599 :
600 : // Deattach the dialog accessible
601 : nsAccessible* windowAcc =
602 : reinterpret_cast<nsAccessible*>
603 0 : (g_object_get_qdata(G_OBJECT(child), sQuark_gecko_acc_obj));
604 0 : if (windowAcc) {
605 0 : GetAccService()->RemoveNativeRootAccessible(windowAcc);
606 0 : g_object_set_qdata(G_OBJECT(child), sQuark_gecko_acc_obj, NULL);
607 : }
608 :
609 : }
610 : }
611 :
612 0 : return TRUE;
613 : }
614 :
615 : bool
616 0 : nsApplicationAccessibleWrap::Init()
617 : {
618 0 : if (ShouldA11yBeEnabled()) {
619 : // load and initialize gail library
620 0 : nsresult rv = LoadGtkModule(sGail);
621 0 : if (NS_SUCCEEDED(rv)) {
622 0 : (*sGail.init)();
623 : }
624 : else {
625 0 : MAI_LOG_DEBUG(("Fail to load lib: %s\n", sGail.libName));
626 : }
627 :
628 0 : MAI_LOG_DEBUG(("Mozilla Atk Implementation initializing\n"));
629 : // Initialize the MAI Utility class
630 : // it will overwrite gail_util
631 0 : g_type_class_unref(g_type_class_ref(MAI_TYPE_UTIL));
632 :
633 : // Init atk-bridge now
634 0 : PR_SetEnv("NO_AT_BRIDGE=0");
635 :
636 : // load and initialize atk-bridge library
637 0 : rv = LoadGtkModule(sAtkBridge);
638 0 : if (NS_SUCCEEDED(rv)) {
639 : // init atk-bridge
640 0 : (*sAtkBridge.init)();
641 : }
642 : else
643 0 : MAI_LOG_DEBUG(("Fail to load lib: %s\n", sAtkBridge.libName));
644 :
645 0 : if (!sToplevel_event_hook_added) {
646 0 : sToplevel_event_hook_added = true;
647 : sToplevel_show_hook =
648 : g_signal_add_emission_hook(g_signal_lookup("show", GTK_TYPE_WINDOW),
649 : 0, toplevel_event_watcher,
650 0 : reinterpret_cast<gpointer>(nsIAccessibleEvent::EVENT_SHOW), NULL);
651 : sToplevel_hide_hook =
652 : g_signal_add_emission_hook(g_signal_lookup("hide", GTK_TYPE_WINDOW),
653 : 0, toplevel_event_watcher,
654 0 : reinterpret_cast<gpointer>(nsIAccessibleEvent::EVENT_HIDE), NULL);
655 : }
656 : }
657 :
658 0 : return nsApplicationAccessible::Init();
659 : }
660 :
661 : void
662 0 : nsApplicationAccessibleWrap::Unload()
663 : {
664 0 : if (sToplevel_event_hook_added) {
665 0 : sToplevel_event_hook_added = false;
666 : g_signal_remove_emission_hook(g_signal_lookup("show", GTK_TYPE_WINDOW),
667 0 : sToplevel_show_hook);
668 : g_signal_remove_emission_hook(g_signal_lookup("hide", GTK_TYPE_WINDOW),
669 0 : sToplevel_hide_hook);
670 : }
671 :
672 0 : if (sAtkBridge.lib) {
673 : // Do not shutdown/unload atk-bridge,
674 : // an exit function registered will take care of it
675 : // if (sAtkBridge.shutdown)
676 : // (*sAtkBridge.shutdown)();
677 : // PR_UnloadLibrary(sAtkBridge.lib);
678 0 : sAtkBridge.lib = NULL;
679 0 : sAtkBridge.init = NULL;
680 0 : sAtkBridge.shutdown = NULL;
681 : }
682 0 : if (sGail.lib) {
683 : // Do not shutdown gail because
684 : // 1) Maybe it's not init-ed by us. e.g. GtkEmbed
685 : // 2) We need it to avoid assert in spi_atk_tidy_windows
686 : // if (sGail.shutdown)
687 : // (*sGail.shutdown)();
688 : // PR_UnloadLibrary(sGail.lib);
689 0 : sGail.lib = NULL;
690 0 : sGail.init = NULL;
691 0 : sGail.shutdown = NULL;
692 : }
693 : // if (sATKLib) {
694 : // PR_UnloadLibrary(sATKLib);
695 : // sATKLib = nsnull;
696 : // }
697 0 : }
698 :
699 : NS_IMETHODIMP
700 0 : nsApplicationAccessibleWrap::GetName(nsAString& aName)
701 : {
702 : // ATK doesn't provide a way to obtain an application name (for example,
703 : // Firefox or Thunderbird) like IA2 does. Thus let's return an application
704 : // name as accessible name that was used to get a branding name (for example,
705 : // Minefield aka nightly Firefox or Daily aka nightly Thunderbird).
706 0 : return GetAppName(aName);
707 : }
708 :
709 : NS_IMETHODIMP
710 0 : nsApplicationAccessibleWrap::GetNativeInterface(void **aOutAccessible)
711 : {
712 0 : *aOutAccessible = nsnull;
713 :
714 0 : if (!mAtkObject) {
715 : mAtkObject =
716 : reinterpret_cast<AtkObject *>
717 0 : (g_object_new(MAI_TYPE_ATK_OBJECT, NULL));
718 0 : NS_ENSURE_TRUE(mAtkObject, NS_ERROR_OUT_OF_MEMORY);
719 :
720 0 : atk_object_initialize(mAtkObject, this);
721 0 : mAtkObject->role = ATK_ROLE_INVALID;
722 0 : mAtkObject->layer = ATK_LAYER_INVALID;
723 : }
724 :
725 0 : *aOutAccessible = mAtkObject;
726 0 : return NS_OK;
727 : }
728 :
729 : struct AtkRootAccessibleAddedEvent {
730 : AtkObject *app_accessible;
731 : AtkObject *root_accessible;
732 : PRUint32 index;
733 : };
734 :
735 0 : gboolean fireRootAccessibleAddedCB(gpointer data)
736 : {
737 0 : AtkRootAccessibleAddedEvent* eventData = (AtkRootAccessibleAddedEvent*)data;
738 : g_signal_emit_by_name(eventData->app_accessible, "children_changed::add",
739 0 : eventData->index, eventData->root_accessible, NULL);
740 0 : g_object_unref(eventData->app_accessible);
741 0 : g_object_unref(eventData->root_accessible);
742 0 : free(data);
743 :
744 0 : return FALSE;
745 : }
746 :
747 : bool
748 0 : nsApplicationAccessibleWrap::AppendChild(nsAccessible *aChild)
749 : {
750 0 : if (!nsApplicationAccessible::AppendChild(aChild))
751 0 : return false;
752 :
753 0 : AtkObject *atkAccessible = nsAccessibleWrap::GetAtkObject(aChild);
754 0 : atk_object_set_parent(atkAccessible, mAtkObject);
755 :
756 0 : PRUint32 count = mChildren.Length();
757 :
758 : // Emit children_changed::add in a timeout
759 : // to make sure aRootAccWrap is fully initialized.
760 : AtkRootAccessibleAddedEvent* eventData = (AtkRootAccessibleAddedEvent*)
761 0 : malloc(sizeof(AtkRootAccessibleAddedEvent));
762 0 : if (eventData) {
763 0 : eventData->app_accessible = mAtkObject;
764 0 : eventData->root_accessible = atkAccessible;
765 0 : eventData->index = count -1;
766 0 : g_object_ref(mAtkObject);
767 0 : g_object_ref(atkAccessible);
768 0 : g_timeout_add(0, fireRootAccessibleAddedCB, eventData);
769 : }
770 :
771 0 : return true;
772 : }
773 :
774 : bool
775 0 : nsApplicationAccessibleWrap::RemoveChild(nsAccessible* aChild)
776 : {
777 0 : PRInt32 index = aChild->IndexInParent();
778 :
779 0 : AtkObject *atkAccessible = nsAccessibleWrap::GetAtkObject(aChild);
780 0 : atk_object_set_parent(atkAccessible, NULL);
781 : g_signal_emit_by_name(mAtkObject, "children_changed::remove", index,
782 0 : atkAccessible, NULL);
783 :
784 0 : return nsApplicationAccessible::RemoveChild(aChild);
785 : }
786 :
787 : void
788 0 : nsApplicationAccessibleWrap::PreCreate()
789 : {
790 0 : if (!sATKChecked) {
791 0 : sATKLib = PR_LoadLibrary(sATKLibName);
792 0 : if (sATKLib) {
793 0 : AtkGetTypeType pfn_atk_hyperlink_impl_get_type = (AtkGetTypeType) PR_FindFunctionSymbol(sATKLib, sATKHyperlinkImplGetTypeSymbol);
794 0 : if (pfn_atk_hyperlink_impl_get_type)
795 0 : g_atk_hyperlink_impl_type = pfn_atk_hyperlink_impl_get_type();
796 :
797 : AtkGetTypeType pfn_atk_socket_get_type;
798 : pfn_atk_socket_get_type = (AtkGetTypeType)
799 : PR_FindFunctionSymbol(sATKLib,
800 0 : AtkSocketAccessible::sATKSocketGetTypeSymbol);
801 0 : if (pfn_atk_socket_get_type) {
802 : AtkSocketAccessible::g_atk_socket_type =
803 0 : pfn_atk_socket_get_type();
804 : AtkSocketAccessible::g_atk_socket_embed = (AtkSocketEmbedType)
805 : PR_FindFunctionSymbol(sATKLib,
806 : AtkSocketAccessible
807 0 : ::sATKSocketEmbedSymbol);
808 : AtkSocketAccessible::gCanEmbed =
809 : AtkSocketAccessible::g_atk_socket_type != G_TYPE_INVALID &&
810 0 : AtkSocketAccessible::g_atk_socket_embed;
811 : }
812 : }
813 0 : sATKChecked = true;
814 : }
815 0 : }
816 :
817 : static nsresult
818 0 : LoadGtkModule(GnomeAccessibilityModule& aModule)
819 : {
820 0 : NS_ENSURE_ARG(aModule.libName);
821 :
822 0 : if (!(aModule.lib = PR_LoadLibrary(aModule.libName))) {
823 :
824 0 : MAI_LOG_DEBUG(("Fail to load lib: %s in default path\n", aModule.libName));
825 :
826 : //try to load the module with "gtk-2.0/modules" appended
827 0 : char *curLibPath = PR_GetLibraryPath();
828 0 : nsCAutoString libPath(curLibPath);
829 : #if defined(LINUX) && defined(__x86_64__)
830 : libPath.Append(":/usr/lib64:/usr/lib");
831 : #else
832 0 : libPath.Append(":/usr/lib");
833 : #endif
834 0 : MAI_LOG_DEBUG(("Current Lib path=%s\n", libPath.get()));
835 0 : PR_FreeLibraryName(curLibPath);
836 :
837 0 : PRInt16 loc1 = 0, loc2 = 0;
838 0 : PRInt16 subLen = 0;
839 0 : while (loc2 >= 0) {
840 0 : loc2 = libPath.FindChar(':', loc1);
841 0 : if (loc2 < 0)
842 0 : subLen = libPath.Length() - loc1;
843 : else
844 0 : subLen = loc2 - loc1;
845 0 : nsCAutoString sub(Substring(libPath, loc1, subLen));
846 0 : sub.Append("/gtk-2.0/modules/");
847 0 : sub.Append(aModule.libName);
848 0 : aModule.lib = PR_LoadLibrary(sub.get());
849 0 : if (aModule.lib) {
850 0 : MAI_LOG_DEBUG(("Ok, load %s from %s\n", aModule.libName, sub.get()));
851 : break;
852 : }
853 0 : loc1 = loc2+1;
854 : }
855 0 : if (!aModule.lib) {
856 0 : MAI_LOG_DEBUG(("Fail to load %s\n", aModule.libName));
857 0 : return NS_ERROR_FAILURE;
858 : }
859 : }
860 :
861 : //we have loaded the library, try to get the function ptrs
862 0 : if (!(aModule.init = PR_FindFunctionSymbol(aModule.lib,
863 0 : aModule.initName)) ||
864 : !(aModule.shutdown = PR_FindFunctionSymbol(aModule.lib,
865 0 : aModule.shutdownName))) {
866 :
867 : //fail, :(
868 0 : MAI_LOG_DEBUG(("Fail to find symbol %s in %s",
869 : aModule.init ? aModule.shutdownName : aModule.initName,
870 : aModule.libName));
871 0 : PR_UnloadLibrary(aModule.lib);
872 0 : aModule.lib = NULL;
873 0 : return NS_ERROR_FAILURE;
874 : }
875 0 : return NS_OK;
876 : }
877 :
878 : namespace mozilla {
879 : namespace a11y {
880 :
881 : static const char sAccEnv [] = "GNOME_ACCESSIBILITY";
882 : #ifdef MOZ_ENABLE_DBUS
883 : static DBusPendingCall *sPendingCall = nsnull;
884 : #endif
885 :
886 : void
887 0 : PreInit()
888 : {
889 : #ifdef MOZ_ENABLE_DBUS
890 : static bool sChecked = FALSE;
891 0 : if (sChecked)
892 0 : return;
893 :
894 0 : sChecked = TRUE;
895 :
896 : // dbus is only checked if GNOME_ACCESSIBILITY is unset
897 : // also make sure that a session bus address is available to prevent dbus from
898 : // starting a new one. Dbus confuses the test harness when it creates a new
899 : // process (see bug 693343)
900 0 : if (PR_GetEnv(sAccEnv) || !PR_GetEnv("DBUS_SESSION_BUS_ADDRESS"))
901 0 : return;
902 :
903 0 : DBusConnection* bus = dbus_bus_get(DBUS_BUS_SESSION, nsnull);
904 0 : if (!bus)
905 0 : return;
906 :
907 0 : dbus_connection_set_exit_on_disconnect(bus, FALSE);
908 :
909 : static const char* iface = "org.a11y.Status";
910 : static const char* member = "IsEnabled";
911 : DBusMessage *message;
912 : message = dbus_message_new_method_call("org.a11y.Bus", "/org/a11y/bus",
913 : "org.freedesktop.DBus.Properties",
914 0 : "Get");
915 0 : if (!message)
916 0 : goto dbus_done;
917 :
918 : dbus_message_append_args(message, DBUS_TYPE_STRING, &iface,
919 0 : DBUS_TYPE_STRING, &member, DBUS_TYPE_INVALID);
920 0 : dbus_connection_send_with_reply(bus, message, &sPendingCall, 1000);
921 0 : dbus_message_unref(message);
922 :
923 : dbus_done:
924 0 : dbus_connection_unref(bus);
925 : #endif
926 : }
927 :
928 : bool
929 0 : ShouldA11yBeEnabled()
930 : {
931 : static bool sChecked = false, sShouldEnable = false;
932 0 : if (sChecked)
933 0 : return sShouldEnable;
934 :
935 0 : sChecked = true;
936 :
937 : // check if accessibility enabled/disabled by environment variable
938 0 : const char* envValue = PR_GetEnv(sAccEnv);
939 0 : if (envValue)
940 0 : return sShouldEnable = !!atoi(envValue);
941 :
942 : #ifdef MOZ_ENABLE_DBUS
943 0 : PreInit();
944 0 : bool dbusSuccess = false;
945 0 : DBusMessage *reply = nsnull;
946 0 : if (!sPendingCall)
947 0 : goto dbus_done;
948 :
949 0 : dbus_pending_call_block(sPendingCall);
950 0 : reply = dbus_pending_call_steal_reply(sPendingCall);
951 0 : dbus_pending_call_unref(sPendingCall);
952 0 : sPendingCall = nsnull;
953 0 : if (!reply ||
954 0 : dbus_message_get_type(reply) != DBUS_MESSAGE_TYPE_METHOD_RETURN ||
955 0 : strcmp(dbus_message_get_signature (reply), DBUS_TYPE_VARIANT_AS_STRING))
956 0 : goto dbus_done;
957 :
958 : DBusMessageIter iter, iter_variant, iter_struct;
959 : dbus_bool_t dResult;
960 0 : dbus_message_iter_init(reply, &iter);
961 0 : dbus_message_iter_recurse (&iter, &iter_variant);
962 0 : switch (dbus_message_iter_get_arg_type(&iter_variant)) {
963 : case DBUS_TYPE_STRUCT:
964 : // at-spi2-core 2.2.0-2.2.1 had a bug where it returned a struct
965 0 : dbus_message_iter_recurse(&iter_variant, &iter_struct);
966 0 : if (dbus_message_iter_get_arg_type(&iter_struct) == DBUS_TYPE_BOOLEAN) {
967 0 : dbus_message_iter_get_basic(&iter_struct, &dResult);
968 0 : sShouldEnable = dResult;
969 0 : dbusSuccess = true;
970 : }
971 :
972 0 : break;
973 : case DBUS_TYPE_BOOLEAN:
974 0 : dbus_message_iter_get_basic(&iter_variant, &dResult);
975 0 : sShouldEnable = dResult;
976 0 : dbusSuccess = true;
977 0 : break;
978 : default:
979 0 : break;
980 : }
981 :
982 : dbus_done:
983 0 : if (reply)
984 0 : dbus_message_unref(reply);
985 :
986 0 : if (dbusSuccess)
987 0 : return sShouldEnable;
988 : #endif
989 :
990 : //check gconf-2 setting
991 : static const char sGconfAccessibilityKey[] =
992 : "/desktop/gnome/interface/accessibility";
993 0 : nsresult rv = NS_OK;
994 : nsCOMPtr<nsIGConfService> gconf =
995 0 : do_GetService(NS_GCONFSERVICE_CONTRACTID, &rv);
996 0 : if (NS_SUCCEEDED(rv) && gconf)
997 0 : gconf->GetBool(NS_LITERAL_CSTRING(sGconfAccessibilityKey), &sShouldEnable);
998 :
999 0 : return sShouldEnable;
1000 : }
1001 : } // namespace a11y
1002 : } // namespace mozilla
1003 :
|