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