412 lines
12 KiB
C++
412 lines
12 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "ApplicationAccessibleWrap.h"
|
|
#include "mozilla/Likely.h"
|
|
#include "nsAccessibilityService.h"
|
|
#include "nsMai.h"
|
|
|
|
#include <atk/atk.h>
|
|
#include <gtk/gtk.h>
|
|
#include <string.h>
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::a11y;
|
|
|
|
typedef AtkUtil MaiUtil;
|
|
typedef AtkUtilClass MaiUtilClass;
|
|
|
|
#define MAI_VERSION MOZILLA_VERSION
|
|
#define MAI_NAME "Gecko"
|
|
|
|
extern "C" {
|
|
static guint (*gail_add_global_event_listener)(GSignalEmissionHook listener,
|
|
const gchar* event_type);
|
|
static void (*gail_remove_global_event_listener) (guint remove_listener);
|
|
static void (*gail_remove_key_event_listener) (guint remove_listener);
|
|
static AtkObject* (*gail_get_root)();
|
|
}
|
|
|
|
struct MaiUtilListenerInfo
|
|
{
|
|
gint key;
|
|
guint signal_id;
|
|
gulong hook_id;
|
|
// For window create/destory/minimize/maximize/restore/activate/deactivate
|
|
// events, we'll chain gail_util's add/remove_global_event_listener.
|
|
// So we store the listenerid returned by gail's add_global_event_listener
|
|
// in this structure to call gail's remove_global_event_listener later.
|
|
guint gail_listenerid;
|
|
};
|
|
|
|
static GHashTable* sListener_list = nullptr;
|
|
static gint sListener_idx = 1;
|
|
|
|
extern "C" {
|
|
static guint
|
|
add_listener (GSignalEmissionHook listener,
|
|
const gchar *object_type,
|
|
const gchar *signal,
|
|
const gchar *hook_data,
|
|
guint gail_listenerid = 0)
|
|
{
|
|
GType type;
|
|
guint signal_id;
|
|
gint rc = 0;
|
|
|
|
type = g_type_from_name(object_type);
|
|
if (type) {
|
|
signal_id = g_signal_lookup(signal, type);
|
|
if (signal_id > 0) {
|
|
MaiUtilListenerInfo *listener_info;
|
|
|
|
rc = sListener_idx;
|
|
|
|
listener_info = (MaiUtilListenerInfo *)
|
|
g_malloc(sizeof(MaiUtilListenerInfo));
|
|
listener_info->key = sListener_idx;
|
|
listener_info->hook_id =
|
|
g_signal_add_emission_hook(signal_id, 0, listener,
|
|
g_strdup(hook_data),
|
|
(GDestroyNotify)g_free);
|
|
listener_info->signal_id = signal_id;
|
|
listener_info->gail_listenerid = gail_listenerid;
|
|
|
|
g_hash_table_insert(sListener_list, &(listener_info->key),
|
|
listener_info);
|
|
sListener_idx++;
|
|
}
|
|
else {
|
|
g_warning("Invalid signal type %s\n", signal);
|
|
}
|
|
}
|
|
else {
|
|
g_warning("Invalid object type %s\n", object_type);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static guint
|
|
mai_util_add_global_event_listener(GSignalEmissionHook listener,
|
|
const gchar *event_type)
|
|
{
|
|
guint rc = 0;
|
|
gchar **split_string;
|
|
|
|
split_string = g_strsplit (event_type, ":", 3);
|
|
|
|
if (split_string) {
|
|
if (!strcmp ("window", split_string[0])) {
|
|
guint gail_listenerid = 0;
|
|
if (gail_add_global_event_listener) {
|
|
// call gail's function to track gtk native window events
|
|
gail_listenerid =
|
|
gail_add_global_event_listener(listener, event_type);
|
|
}
|
|
|
|
rc = add_listener (listener, "MaiAtkObject", split_string[1],
|
|
event_type, gail_listenerid);
|
|
}
|
|
else {
|
|
rc = add_listener (listener, split_string[1], split_string[2],
|
|
event_type);
|
|
}
|
|
g_strfreev(split_string);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static void
|
|
mai_util_remove_global_event_listener(guint remove_listener)
|
|
{
|
|
if (remove_listener > 0) {
|
|
MaiUtilListenerInfo *listener_info;
|
|
gint tmp_idx = remove_listener;
|
|
|
|
listener_info = (MaiUtilListenerInfo *)
|
|
g_hash_table_lookup(sListener_list, &tmp_idx);
|
|
|
|
if (listener_info != nullptr) {
|
|
if (gail_remove_global_event_listener &&
|
|
listener_info->gail_listenerid) {
|
|
gail_remove_global_event_listener(listener_info->gail_listenerid);
|
|
}
|
|
|
|
/* Hook id of 0 and signal id of 0 are invalid */
|
|
if (listener_info->hook_id != 0 && listener_info->signal_id != 0) {
|
|
/* Remove the emission hook */
|
|
g_signal_remove_emission_hook(listener_info->signal_id,
|
|
listener_info->hook_id);
|
|
|
|
/* Remove the element from the hash */
|
|
g_hash_table_remove(sListener_list, &tmp_idx);
|
|
}
|
|
else {
|
|
g_warning("Invalid listener hook_id %ld or signal_id %d\n",
|
|
listener_info->hook_id, listener_info->signal_id);
|
|
}
|
|
}
|
|
else {
|
|
// atk-bridge is initialized with gail (e.g. yelp)
|
|
// try gail_remove_global_event_listener
|
|
if (gail_remove_global_event_listener) {
|
|
return gail_remove_global_event_listener(remove_listener);
|
|
}
|
|
|
|
g_warning("No listener with the specified listener id %d",
|
|
remove_listener);
|
|
}
|
|
}
|
|
else {
|
|
g_warning("Invalid listener_id %d", remove_listener);
|
|
}
|
|
}
|
|
|
|
static AtkKeyEventStruct *
|
|
atk_key_event_from_gdk_event_key (GdkEventKey *key)
|
|
{
|
|
AtkKeyEventStruct *event = g_new0(AtkKeyEventStruct, 1);
|
|
switch (key->type) {
|
|
case GDK_KEY_PRESS:
|
|
event->type = ATK_KEY_EVENT_PRESS;
|
|
break;
|
|
case GDK_KEY_RELEASE:
|
|
event->type = ATK_KEY_EVENT_RELEASE;
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
return nullptr;
|
|
}
|
|
event->state = key->state;
|
|
event->keyval = key->keyval;
|
|
event->length = key->length;
|
|
if (key->string && key->string [0] &&
|
|
(key->state & GDK_CONTROL_MASK ||
|
|
g_unichar_isgraph (g_utf8_get_char (key->string)))) {
|
|
event->string = key->string;
|
|
}
|
|
else if (key->type == GDK_KEY_PRESS ||
|
|
key->type == GDK_KEY_RELEASE) {
|
|
event->string = gdk_keyval_name (key->keyval);
|
|
}
|
|
event->keycode = key->hardware_keycode;
|
|
event->timestamp = key->time;
|
|
|
|
return event;
|
|
}
|
|
|
|
struct MaiKeyEventInfo
|
|
{
|
|
AtkKeyEventStruct *key_event;
|
|
gpointer func_data;
|
|
};
|
|
|
|
union AtkKeySnoopFuncPointer
|
|
{
|
|
AtkKeySnoopFunc func_ptr;
|
|
gpointer data;
|
|
};
|
|
|
|
static gboolean
|
|
notify_hf(gpointer key, gpointer value, gpointer data)
|
|
{
|
|
MaiKeyEventInfo *info = (MaiKeyEventInfo *)data;
|
|
AtkKeySnoopFuncPointer atkKeySnoop;
|
|
atkKeySnoop.data = value;
|
|
return (atkKeySnoop.func_ptr)(info->key_event, info->func_data) ? TRUE : FALSE;
|
|
}
|
|
|
|
static void
|
|
insert_hf(gpointer key, gpointer value, gpointer data)
|
|
{
|
|
GHashTable *new_table = (GHashTable *) data;
|
|
g_hash_table_insert (new_table, key, value);
|
|
}
|
|
|
|
static GHashTable* sKey_listener_list = nullptr;
|
|
|
|
static gint
|
|
mai_key_snooper(GtkWidget *the_widget, GdkEventKey *event, gpointer func_data)
|
|
{
|
|
/* notify each AtkKeySnoopFunc in turn... */
|
|
|
|
MaiKeyEventInfo *info = g_new0(MaiKeyEventInfo, 1);
|
|
gint consumed = 0;
|
|
if (sKey_listener_list) {
|
|
GHashTable *new_hash = g_hash_table_new(nullptr, nullptr);
|
|
g_hash_table_foreach (sKey_listener_list, insert_hf, new_hash);
|
|
info->key_event = atk_key_event_from_gdk_event_key (event);
|
|
info->func_data = func_data;
|
|
consumed = g_hash_table_foreach_steal (new_hash, notify_hf, info);
|
|
g_hash_table_destroy (new_hash);
|
|
g_free(info->key_event);
|
|
}
|
|
g_free(info);
|
|
return (consumed ? 1 : 0);
|
|
}
|
|
|
|
static guint sKey_snooper_id = 0;
|
|
|
|
static guint
|
|
mai_util_add_key_event_listener(AtkKeySnoopFunc listener, gpointer data)
|
|
{
|
|
if (MOZ_UNLIKELY(!listener)) {
|
|
return 0;
|
|
}
|
|
|
|
static guint key = 0;
|
|
|
|
if (!sKey_listener_list) {
|
|
sKey_listener_list = g_hash_table_new(nullptr, nullptr);
|
|
}
|
|
|
|
// If we have no registered event listeners then we need to (re)install the
|
|
// key event snooper.
|
|
if (g_hash_table_size(sKey_listener_list) == 0) {
|
|
sKey_snooper_id = gtk_key_snooper_install(mai_key_snooper, data);
|
|
}
|
|
|
|
AtkKeySnoopFuncPointer atkKeySnoop;
|
|
atkKeySnoop.func_ptr = listener;
|
|
key++;
|
|
g_hash_table_insert(sKey_listener_list, GUINT_TO_POINTER(key),
|
|
atkKeySnoop.data);
|
|
return key;
|
|
}
|
|
|
|
static void
|
|
mai_util_remove_key_event_listener (guint remove_listener)
|
|
{
|
|
if (!sKey_listener_list) {
|
|
// atk-bridge is initialized with gail (e.g. yelp)
|
|
// try gail_remove_key_event_listener
|
|
return gail_remove_key_event_listener(remove_listener);
|
|
}
|
|
|
|
g_hash_table_remove(sKey_listener_list, GUINT_TO_POINTER (remove_listener));
|
|
if (g_hash_table_size(sKey_listener_list) == 0) {
|
|
gtk_key_snooper_remove(sKey_snooper_id);
|
|
}
|
|
}
|
|
|
|
static AtkObject*
|
|
mai_util_get_root()
|
|
{
|
|
ApplicationAccessible* app = ApplicationAcc();
|
|
if (app)
|
|
return app->GetAtkObject();
|
|
|
|
// We've shutdown, try to use gail instead
|
|
// (to avoid assert in spi_atk_tidy_windows())
|
|
// XXX tbsaunde then why didn't we replace the gail atk_util impl?
|
|
if (gail_get_root)
|
|
return gail_get_root();
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static const gchar*
|
|
mai_util_get_toolkit_name()
|
|
{
|
|
return MAI_NAME;
|
|
}
|
|
|
|
static const gchar*
|
|
mai_util_get_toolkit_version()
|
|
{
|
|
return MAI_VERSION;
|
|
}
|
|
|
|
static void
|
|
_listener_info_destroy(gpointer data)
|
|
{
|
|
g_free(data);
|
|
}
|
|
|
|
static void
|
|
window_added (AtkObject *atk_obj,
|
|
guint index,
|
|
AtkObject *child)
|
|
{
|
|
if (!IS_MAI_OBJECT(child))
|
|
return;
|
|
|
|
static guint id = g_signal_lookup ("create", MAI_TYPE_ATK_OBJECT);
|
|
g_signal_emit (child, id, 0);
|
|
}
|
|
|
|
static void
|
|
window_removed (AtkObject *atk_obj,
|
|
guint index,
|
|
AtkObject *child)
|
|
{
|
|
if (!IS_MAI_OBJECT(child))
|
|
return;
|
|
|
|
static guint id = g_signal_lookup ("destroy", MAI_TYPE_ATK_OBJECT);
|
|
g_signal_emit (child, id, 0);
|
|
}
|
|
|
|
static void
|
|
UtilInterfaceInit(MaiUtilClass* klass)
|
|
{
|
|
AtkUtilClass *atk_class;
|
|
gpointer data;
|
|
|
|
data = g_type_class_peek(ATK_TYPE_UTIL);
|
|
atk_class = ATK_UTIL_CLASS(data);
|
|
|
|
// save gail function pointer
|
|
gail_add_global_event_listener = atk_class->add_global_event_listener;
|
|
gail_remove_global_event_listener = atk_class->remove_global_event_listener;
|
|
gail_remove_key_event_listener = atk_class->remove_key_event_listener;
|
|
gail_get_root = atk_class->get_root;
|
|
|
|
atk_class->add_global_event_listener =
|
|
mai_util_add_global_event_listener;
|
|
atk_class->remove_global_event_listener =
|
|
mai_util_remove_global_event_listener;
|
|
atk_class->add_key_event_listener = mai_util_add_key_event_listener;
|
|
atk_class->remove_key_event_listener = mai_util_remove_key_event_listener;
|
|
atk_class->get_root = mai_util_get_root;
|
|
atk_class->get_toolkit_name = mai_util_get_toolkit_name;
|
|
atk_class->get_toolkit_version = mai_util_get_toolkit_version;
|
|
|
|
sListener_list = g_hash_table_new_full(g_int_hash, g_int_equal, nullptr,
|
|
_listener_info_destroy);
|
|
// Keep track of added/removed windows.
|
|
AtkObject *root = atk_get_root ();
|
|
g_signal_connect (root, "children-changed::add", (GCallback) window_added, nullptr);
|
|
g_signal_connect (root, "children-changed::remove", (GCallback) window_removed, nullptr);
|
|
}
|
|
}
|
|
|
|
GType
|
|
mai_util_get_type()
|
|
{
|
|
static GType type = 0;
|
|
|
|
if (!type) {
|
|
static const GTypeInfo tinfo = {
|
|
sizeof(MaiUtilClass),
|
|
(GBaseInitFunc) nullptr, /* base init */
|
|
(GBaseFinalizeFunc) nullptr, /* base finalize */
|
|
(GClassInitFunc) UtilInterfaceInit, /* class init */
|
|
(GClassFinalizeFunc) nullptr, /* class finalize */
|
|
nullptr, /* class data */
|
|
sizeof(MaiUtil), /* instance size */
|
|
0, /* nb preallocs */
|
|
(GInstanceInitFunc) nullptr, /* instance init */
|
|
nullptr /* value table */
|
|
};
|
|
|
|
type = g_type_register_static(ATK_TYPE_UTIL,
|
|
"MaiUtil", &tinfo, GTypeFlags(0));
|
|
}
|
|
return type;
|
|
}
|
|
|