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