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/. */
7 #include "ApplicationAccessible.h"
8 #include "mozilla/Likely.h"
9 #include "nsAccessibilityService.h"
12 #include <atk/atkobject.h>
13 #include <atk/atkutil.h>
17 using namespace mozilla
;
18 using namespace mozilla::a11y
;
20 typedef AtkUtil MaiUtil
;
21 typedef AtkUtilClass MaiUtilClass
;
23 #define MAI_VERSION MOZILLA_VERSION
24 #define MAI_NAME "Gecko"
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
)();
34 struct MaiUtilListenerInfo
{
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
;
45 static GHashTable
* sListener_list
= nullptr;
46 static gint sListener_idx
= 1;
49 static guint
add_listener(GSignalEmissionHook listener
,
50 const gchar
* object_type
, const gchar
* signal
,
51 const gchar
* hook_data
, guint gail_listenerid
= 0) {
56 type
= g_type_from_name(object_type
);
58 signal_id
= g_signal_lookup(signal
, type
);
60 MaiUtilListenerInfo
* 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
;
72 g_hash_table_insert(sListener_list
, &(listener_info
->key
), listener_info
);
75 g_warning("Invalid signal type %s\n", signal
);
78 g_warning("Invalid object type %s\n", object_type
);
83 static guint
mai_util_add_global_event_listener(GSignalEmissionHook listener
,
84 const gchar
* event_type
) {
88 split_string
= g_strsplit(event_type
, ":", 3);
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
);
98 rc
= add_listener(listener
, "MaiAtkObject", split_string
[1], event_type
,
101 rc
= add_listener(listener
, split_string
[1], split_string
[2], event_type
);
103 g_strfreev(split_string
);
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
;
114 (MaiUtilListenerInfo
*)g_hash_table_lookup(sListener_list
, &tmp_idx
);
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
);
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
);
127 /* Remove the element from the hash */
128 g_hash_table_remove(sListener_list
, &tmp_idx
);
130 g_warning("Invalid listener hook_id %ld or signal_id %d\n",
131 listener_info
->hook_id
, listener_info
->signal_id
);
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
);
140 g_warning("No listener with the specified listener id %d",
144 g_warning("Invalid listener_id %d", remove_listener
);
148 static AtkKeyEventStruct
* atk_key_event_from_gdk_event_key(GdkEventKey
* key
) {
149 AtkKeyEventStruct
* event
= g_new0(AtkKeyEventStruct
, 1);
152 event
->type
= ATK_KEY_EVENT_PRESS
;
154 case GDK_KEY_RELEASE
:
155 event
->type
= ATK_KEY_EVENT_RELEASE
;
158 g_assert_not_reached();
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
);
170 event
->keycode
= key
->hardware_keycode
;
171 event
->timestamp
= key
->time
;
176 struct MaiKeyEventInfo
{
177 AtkKeyEventStruct
* key_event
;
181 union AtkKeySnoopFuncPointer
{
182 AtkKeySnoopFunc func_ptr
;
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
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
);
199 static GHashTable
* sKey_listener_list
= nullptr;
201 static gint
mai_key_snooper(GtkWidget
* the_widget
, GdkEventKey
* event
,
202 gpointer func_data
) {
203 /* notify each AtkKeySnoopFunc in turn... */
205 MaiKeyEventInfo
* info
= g_new0(MaiKeyEventInfo
, 1);
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
);
217 return (consumed
? 1 : 0);
220 static guint sKey_snooper_id
= 0;
222 static guint
mai_util_add_key_event_listener(AtkKeySnoopFunc listener
,
224 if (MOZ_UNLIKELY(!listener
)) {
228 static guint key
= 0;
230 if (!sKey_listener_list
) {
231 sKey_listener_list
= g_hash_table_new(nullptr, nullptr);
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
);
240 AtkKeySnoopFuncPointer atkKeySnoop
;
241 atkKeySnoop
.func_ptr
= listener
;
243 g_hash_table_insert(sKey_listener_list
, GUINT_TO_POINTER(key
),
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
);
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
);
261 static AtkObject
* mai_util_get_root() {
262 ApplicationAccessible
* app
= ApplicationAcc();
263 if (app
) return app
->GetAtkObject();
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();
273 static const gchar
* mai_util_get_toolkit_name() { return MAI_NAME
; }
275 static const gchar
* mai_util_get_toolkit_version() { return MAI_VERSION
; }
277 static void _listener_info_destroy(gpointer data
) { g_free(data
); }
279 static void window_added(AtkObject
* atk_obj
, guint index
, AtkObject
* child
) {
280 if (!IS_MAI_OBJECT(child
)) return;
282 static guint id
= g_signal_lookup("create", MAI_TYPE_ATK_OBJECT
);
283 g_signal_emit(child
, id
, 0);
286 static void window_removed(AtkObject
* atk_obj
, guint index
, AtkObject
* child
) {
287 if (!IS_MAI_OBJECT(child
)) return;
289 static guint id
= g_signal_lookup("destroy", MAI_TYPE_ATK_OBJECT
);
290 g_signal_emit(child
, id
, 0);
293 static void UtilInterfaceInit(MaiUtilClass
* klass
) {
294 AtkUtilClass
* atk_class
;
297 data
= g_type_class_peek(ATK_TYPE_UTIL
);
298 atk_class
= ATK_UTIL_CLASS(data
);
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
;
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
;
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
,
321 g_signal_connect(root
, "children-changed::remove", (GCallback
)window_removed
,
326 GType
mai_util_get_type() {
327 static GType type
= 0;
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 */
344 g_type_register_static(ATK_TYPE_UTIL
, "MaiUtil", &tinfo
, GTypeFlags(0));