1 /* CALLY - The Clutter Accessibility Implementation Library
3 * Copyright (C) 2008 Igalia, S.L.
5 * Author: Alejandro PiƱeiro Iglesias <apinheiro@igalia.com>
7 * Based on GailUtil from GAIL
8 * Copyright 2001, 2002, 2003 Sun Microsystems Inc.
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the
22 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 * Boston, MA 02111-1307, USA.
29 * @short_description: #AtkUtil implementation
30 * @see_also: #ClutterActor
32 * #CallyUtil implements #AtkUtil abstract methods. Although it
33 * includes the name "Util" it is in fact one of the most important
34 * interfaces to be implemented in any ATK toolkit implementation.
36 * For instance, it defines atk_get_root(), the method that returns
37 * the root object in the hierarchy. Without it, you don't have
38 * available any accessible object.
47 #include <clutter/clutter.h>
49 #include "cally-util.h"
50 #include "cally-root.h"
51 #include "cally-stage.h"
55 static guint cally_util_add_global_event_listener (GSignalEmissionHook listener,
56 const gchar* event_type);
57 static void cally_util_remove_global_event_listener (guint remove_listener);
58 static guint cally_util_add_key_event_listener (AtkKeySnoopFunc listener,
60 static void cally_util_remove_key_event_listener (guint remove_listener);
61 static AtkObject* cally_util_get_root (void);
62 static const gchar * cally_util_get_toolkit_name (void);
63 static const gchar * cally_util_get_toolkit_version (void);
66 static void _listener_info_destroy (gpointer data);
67 static guint add_listener (GSignalEmissionHook listener,
68 const gchar *object_type,
70 const gchar *hook_data);
71 static void cally_util_simulate_snooper_install (void);
72 static void cally_util_simulate_snooper_remove (void);
73 static gboolean cally_key_snooper (ClutterActor *actor,
76 static void cally_util_stage_added_cb (ClutterStageManager *stage_manager,
79 static void cally_util_stage_removed_cb (ClutterStageManager *stage_manager,
82 static gboolean notify_hf (gpointer key,
85 static void insert_hf (gpointer key,
88 static AtkKeyEventStruct * atk_key_event_from_clutter_event_key (ClutterKeyEvent *event);
91 /* This is just a copy of the Gail one, a shared library or place to
92 define it could be a good idea. */
93 typedef struct _CallyUtilListenerInfo CallyUtilListenerInfo;
94 typedef struct _CallyKeyEventInfo CallyKeyEventInfo;
96 struct _CallyUtilListenerInfo
103 struct _CallyKeyEventInfo
105 AtkKeySnoopFunc listener;
109 static AtkObject* root = NULL;
110 static GHashTable *listener_list = NULL;
111 static GHashTable *key_listener_list = NULL;
112 static gint listener_idx = 1;
115 G_DEFINE_TYPE (CallyUtil, cally_util, ATK_TYPE_UTIL);
118 cally_util_class_init (CallyUtilClass *klass)
120 AtkUtilClass *atk_class;
123 data = g_type_class_peek (ATK_TYPE_UTIL);
124 atk_class = ATK_UTIL_CLASS (data);
126 atk_class->add_global_event_listener = cally_util_add_global_event_listener;
127 atk_class->remove_global_event_listener = cally_util_remove_global_event_listener;
128 atk_class->add_key_event_listener = cally_util_add_key_event_listener;
129 atk_class->remove_key_event_listener = cally_util_remove_key_event_listener;
130 atk_class->get_root = cally_util_get_root;
131 atk_class->get_toolkit_name = cally_util_get_toolkit_name;
132 atk_class->get_toolkit_version = cally_util_get_toolkit_version;
134 /* FIXME: Instead of create this on the class, I think that would
135 worth to implement CallyUtil as a singleton instance, so the
136 class methods will access this instance. This will be a good
137 future enhancement, meanwhile, just using the same *working*
138 implementation used on GailUtil */
140 listener_list = g_hash_table_new_full (g_int_hash, g_int_equal, NULL,
141 _listener_info_destroy);
145 cally_util_init (CallyUtil *cally_util)
147 /* instance init: usually not required */
150 /* ------------------------------ ATK UTIL METHODS -------------------------- */
153 cally_util_get_root (void)
156 root = cally_root_new ();
162 cally_util_get_toolkit_name (void)
168 cally_util_get_toolkit_version (void)
170 return CLUTTER_VERSION_S;
175 cally_util_add_global_event_listener (GSignalEmissionHook listener,
176 const gchar *event_type)
179 gchar **split_string;
181 split_string = g_strsplit (event_type, ":", 3);
183 if (g_strv_length (split_string) == 3)
184 rc = add_listener (listener, split_string[1], split_string[2], event_type);
186 g_strfreev (split_string);
192 cally_util_remove_global_event_listener (guint remove_listener)
194 if (remove_listener > 0)
196 CallyUtilListenerInfo *listener_info;
197 gint tmp_idx = remove_listener;
199 listener_info = (CallyUtilListenerInfo *)
200 g_hash_table_lookup(listener_list, &tmp_idx);
202 if (listener_info != NULL)
204 /* Hook id of 0 and signal id of 0 are invalid */
205 if (listener_info->hook_id != 0 && listener_info->signal_id != 0)
207 /* Remove the emission hook */
208 g_signal_remove_emission_hook(listener_info->signal_id,
209 listener_info->hook_id);
211 /* Remove the element from the hash */
212 g_hash_table_remove(listener_list, &tmp_idx);
216 g_warning("Invalid listener hook_id %ld or signal_id %d\n",
217 listener_info->hook_id, listener_info->signal_id);
222 g_warning("No listener with the specified listener id %d",
228 g_warning("Invalid listener_id %d", remove_listener);
233 cally_util_add_key_event_listener (AtkKeySnoopFunc listener,
236 static guint key = 1;
237 CallyKeyEventInfo *event_info = NULL;
239 if (!key_listener_list)
241 key_listener_list = g_hash_table_new_full (NULL, NULL, NULL, g_free);
243 cally_util_simulate_snooper_install ();
246 event_info = g_new (CallyKeyEventInfo, 1);
247 event_info->listener = listener;
248 event_info->func_data = data;
250 g_hash_table_insert (key_listener_list, GUINT_TO_POINTER (key++), event_info);
251 /* XXX: we don't check to see if n_listeners > MAXUINT */
256 cally_util_remove_key_event_listener (guint remove_listener)
258 if (!g_hash_table_remove (key_listener_list, GUINT_TO_POINTER (remove_listener))) {
259 g_warning ("Not able to remove listener with id %i", remove_listener);
262 if (g_hash_table_size (key_listener_list) == 0)
264 g_hash_table_destroy (key_listener_list);
265 key_listener_list = NULL;
266 cally_util_simulate_snooper_remove ();
270 /* ------------------------------ PRIVATE FUNCTIONS ------------------------- */
273 _listener_info_destroy (gpointer data)
279 add_listener (GSignalEmissionHook listener,
280 const gchar *object_type,
281 const gchar *signal_name,
282 const gchar *hook_data)
288 type = g_type_from_name (object_type);
291 signal_id = g_signal_lookup (signal_name, type);
294 CallyUtilListenerInfo *listener_info;
298 listener_info = g_new (CallyUtilListenerInfo, 1);
299 listener_info->key = listener_idx;
300 listener_info->hook_id =
301 g_signal_add_emission_hook (signal_id, 0, listener,
302 g_strdup (hook_data),
303 (GDestroyNotify) g_free);
304 listener_info->signal_id = signal_id;
306 g_hash_table_insert(listener_list, &(listener_info->key), listener_info);
311 /* This is mainly because some "window:xxx" methods not
312 implemented on CallyStage */
313 g_debug ("Signal type %s not supported\n", signal_name);
318 g_warning("Invalid object type %s\n", object_type);
323 /* Trying to emulate gtk_key_snooper install (a kind of wrapper). This
324 could be implemented without it, but I will maintain it in this
325 way, so if in the future clutter implements it natively it would be
326 easier the transition */
328 cally_util_simulate_snooper_install (void)
330 ClutterStageManager *stage_manager = NULL;
331 ClutterStage *stage = NULL;
332 GSList *stage_list = NULL;
335 stage_manager = clutter_stage_manager_get_default ();
336 stage_list = clutter_stage_manager_list_stages (stage_manager);
338 for (iter = stage_list; iter != NULL; iter = g_slist_next (iter))
340 stage = CLUTTER_STAGE (iter->data);
342 g_signal_connect (G_OBJECT (stage), "captured-event",
343 G_CALLBACK (cally_key_snooper), NULL);
346 g_signal_connect (G_OBJECT (stage_manager), "stage-added",
347 G_CALLBACK (cally_util_stage_added_cb), cally_key_snooper);
348 g_signal_connect (G_OBJECT (stage_manager), "stage-removed",
349 G_CALLBACK (cally_util_stage_removed_cb), cally_key_snooper);
353 cally_util_simulate_snooper_remove (void)
355 ClutterStageManager *stage_manager = NULL;
356 ClutterStage *stage = NULL;
357 GSList *stage_list = NULL;
361 stage_manager = clutter_stage_manager_get_default ();
362 stage_list = clutter_stage_manager_list_stages (stage_manager);
364 for (iter = stage_list; iter != NULL; iter = g_slist_next (iter))
366 stage = CLUTTER_STAGE (iter->data);
368 num += g_signal_handlers_disconnect_by_func (stage, cally_key_snooper, NULL);
371 g_signal_handlers_disconnect_by_func (G_OBJECT (stage_manager),
372 G_CALLBACK (cally_util_stage_added_cb),
375 g_signal_handlers_disconnect_by_func (G_OBJECT (stage_manager),
376 G_CALLBACK (cally_util_stage_removed_cb),
380 g_print ("Number of snooper callbacks disconnected: %i\n", num);
384 static AtkKeyEventStruct *
385 atk_key_event_from_clutter_event_key (ClutterKeyEvent *clutter_event)
387 AtkKeyEventStruct *atk_event = g_new0 (AtkKeyEventStruct, 1);
388 gunichar key_unichar;
390 switch (clutter_event->type)
392 case CLUTTER_KEY_PRESS:
393 atk_event->type = ATK_KEY_EVENT_PRESS;
395 case CLUTTER_KEY_RELEASE:
396 atk_event->type = ATK_KEY_EVENT_RELEASE;
399 g_assert_not_reached ();
403 atk_event->state = clutter_event->modifier_state;
405 /* We emit the clutter keyval. This is not exactly the one expected
406 by AtkKeyEventStruct, as it expects a Gdk-like event, with the
407 modifiers applied. But to avoid a dependency to gdk, we delegate
408 that on the AT application.
409 More information: Bug 1952 and bug 2072
411 atk_event->keyval = clutter_event->keyval;
413 /* It is expected to store a key defining string here (ie "Space" in
414 case you press a space). Anyway, there are no function on clutter
415 to obtain that, and we want to avoid a gdk dependency here, so we
416 delegate on the AT application to obtain that string using the
417 rest of the data on the ATK event struct.
419 More information: Bug 1952 and 2072
422 key_unichar = clutter_event_get_key_unicode ((ClutterEvent *) clutter_event);
424 if (g_unichar_validate (key_unichar) && !g_unichar_iscntrl (key_unichar))
428 new = g_string_new ("");
429 new = g_string_insert_unichar (new, 0, key_unichar);
430 atk_event->string = new->str;
431 g_string_free (new, FALSE);
434 atk_event->string = NULL;
436 atk_event->length = 0;
438 atk_event->keycode = clutter_event->hardware_keycode;
439 atk_event->timestamp = clutter_event->time;
443 g_debug ("CallyKeyEvent:\tsym 0x%x\n\t\tmods %x\n\t\tcode %u\n\t\ttime %lx \n\t\tstring %s\n",
444 (unsigned int) atk_event->keyval,
445 (unsigned int) atk_event->state,
446 (unsigned int) atk_event->keycode,
447 (unsigned long int) atk_event->timestamp,
456 notify_hf (gpointer key, gpointer value, gpointer data)
458 CallyKeyEventInfo *info = (CallyKeyEventInfo *) value;
459 AtkKeyEventStruct *key_event = (AtkKeyEventStruct *)data;
461 return (*(AtkKeySnoopFunc) info->listener) (key_event, info->func_data) ? TRUE : FALSE;
465 insert_hf (gpointer key, gpointer value, gpointer data)
467 GHashTable *new_table = (GHashTable *) data;
468 g_hash_table_insert (new_table, key, value);
472 cally_key_snooper (ClutterActor *actor,
476 AtkKeyEventStruct *key_event = NULL;
479 /* filter key events */
480 if ((event->type != CLUTTER_KEY_PRESS) && (event->type != CLUTTER_KEY_RELEASE))
485 if (key_listener_list)
487 GHashTable *new_hash = g_hash_table_new (NULL, NULL);
489 g_hash_table_foreach (key_listener_list, insert_hf, new_hash);
490 key_event = atk_key_event_from_clutter_event_key ((ClutterKeyEvent *)event);
491 /* func data is inside the hash table */
492 consumed = g_hash_table_foreach_steal (new_hash, notify_hf, key_event);
493 g_hash_table_destroy (new_hash);
496 g_free (key_event->string);
499 return (consumed ? 1 : 0);
503 cally_util_stage_added_cb (ClutterStageManager *stage_manager,
507 GCallback cally_key_snooper_cb = G_CALLBACK (data);
509 g_signal_connect (G_OBJECT (stage), "captured-event", cally_key_snooper_cb, NULL);
513 cally_util_stage_removed_cb (ClutterStageManager *stage_manager,
517 GCallback cally_key_snooper_cb = G_CALLBACK (data);
519 g_signal_handlers_disconnect_by_func (stage, cally_key_snooper_cb, NULL);