Bug 1793677 [wpt PR 36271] - Run sysdiagnose at the end of Azure Pipelines jobs on...
[gecko.git] / accessible / atk / UtilInterface.cpp
blob8389e09f802ec9992c06d3f86411e34b6e658a33
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"
10 #include "nsMai.h"
12 #include <atk/atkobject.h>
13 #include <atk/atkutil.h>
14 #include <gtk/gtk.h>
15 #include <string.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"
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)();
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;
45 static GHashTable* sListener_list = nullptr;
46 static gint sListener_idx = 1;
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;
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;
62 rc = sListener_idx;
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;
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);
77 } else {
78 g_warning("Invalid object type %s\n", object_type);
80 return rc;
83 static guint mai_util_add_global_event_listener(GSignalEmissionHook listener,
84 const gchar* event_type) {
85 guint rc = 0;
86 gchar** split_string;
88 split_string = g_strsplit(event_type, ":", 3);
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);
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);
103 g_strfreev(split_string);
105 return rc;
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;
113 listener_info =
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);
129 } else {
130 g_warning("Invalid listener hook_id %ld or signal_id %d\n",
131 listener_info->hook_id, listener_info->signal_id);
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);
140 g_warning("No listener with the specified listener id %d",
141 remove_listener);
143 } else {
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);
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;
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;
173 return event;
176 struct MaiKeyEventInfo {
177 AtkKeyEventStruct* key_event;
178 gpointer func_data;
181 union AtkKeySnoopFuncPointer {
182 AtkKeySnoopFunc func_ptr;
183 gpointer data;
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;
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);
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);
216 g_free(info);
217 return (consumed ? 1 : 0);
220 static guint sKey_snooper_id = 0;
222 static guint mai_util_add_key_event_listener(AtkKeySnoopFunc listener,
223 gpointer data) {
224 if (MOZ_UNLIKELY(!listener)) {
225 return 0;
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;
242 key++;
243 g_hash_table_insert(sKey_listener_list, GUINT_TO_POINTER(key),
244 atkKeySnoop.data);
245 return 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();
270 return nullptr;
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;
295 gpointer data;
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,
320 nullptr);
321 g_signal_connect(root, "children-changed::remove", (GCallback)window_removed,
322 nullptr);
326 GType mai_util_get_type() {
327 static GType type = 0;
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 */
343 type =
344 g_type_register_static(ATK_TYPE_UTIL, "MaiUtil", &tinfo, GTypeFlags(0));
346 return type;