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 cally_util_simulate_snooper_remove ();
268 /* ------------------------------ PRIVATE FUNCTIONS ------------------------- */
271 _listener_info_destroy (gpointer data)
277 add_listener (GSignalEmissionHook listener,
278 const gchar *object_type,
279 const gchar *signal_name,
280 const gchar *hook_data)
286 type = g_type_from_name (object_type);
289 signal_id = g_signal_lookup (signal_name, type);
292 CallyUtilListenerInfo *listener_info;
296 listener_info = g_new (CallyUtilListenerInfo, 1);
297 listener_info->key = listener_idx;
298 listener_info->hook_id =
299 g_signal_add_emission_hook (signal_id, 0, listener,
300 g_strdup (hook_data),
301 (GDestroyNotify) g_free);
302 listener_info->signal_id = signal_id;
304 g_hash_table_insert(listener_list, &(listener_info->key), listener_info);
309 /* This is mainly because some "window:xxx" methods not
310 implemented on CallyStage */
311 g_debug ("Signal type %s not supported\n", signal_name);
316 g_warning("Invalid object type %s\n", object_type);
321 /* Trying to emulate gtk_key_snooper install (a kind of wrapper). This
322 could be implemented without it, but I will maintain it in this
323 way, so if in the future clutter implements it natively it would be
324 easier the transition */
326 cally_util_simulate_snooper_install (void)
328 ClutterStageManager *stage_manager = NULL;
329 ClutterStage *stage = NULL;
330 GSList *stage_list = NULL;
333 stage_manager = clutter_stage_manager_get_default ();
334 stage_list = clutter_stage_manager_list_stages (stage_manager);
336 for (iter = stage_list; iter != NULL; iter = g_slist_next (iter))
338 stage = CLUTTER_STAGE (iter->data);
340 g_signal_connect (G_OBJECT (stage), "captured-event",
341 G_CALLBACK (cally_key_snooper), NULL);
344 g_signal_connect (G_OBJECT (stage_manager), "stage-added",
345 G_CALLBACK (cally_util_stage_added_cb), cally_key_snooper);
346 g_signal_connect (G_OBJECT (stage_manager), "stage-removed",
347 G_CALLBACK (cally_util_stage_removed_cb), cally_key_snooper);
351 cally_util_simulate_snooper_remove (void)
353 ClutterStageManager *stage_manager = NULL;
354 ClutterStage *stage = NULL;
355 GSList *stage_list = NULL;
359 stage_manager = clutter_stage_manager_get_default ();
360 stage_list = clutter_stage_manager_list_stages (stage_manager);
362 for (iter = stage_list; iter != NULL; iter = g_slist_next (iter))
364 stage = CLUTTER_STAGE (iter->data);
366 num += g_signal_handlers_disconnect_by_func (stage, cally_key_snooper, NULL);
369 g_signal_handlers_disconnect_by_func (G_OBJECT (stage_manager),
370 G_CALLBACK (cally_util_stage_added_cb),
373 g_signal_handlers_disconnect_by_func (G_OBJECT (stage_manager),
374 G_CALLBACK (cally_util_stage_removed_cb),
378 g_print ("Number of snooper callbacks disconnected: %i\n", num);
382 static AtkKeyEventStruct *
383 atk_key_event_from_clutter_event_key (ClutterKeyEvent *clutter_event)
385 AtkKeyEventStruct *atk_event = g_new0 (AtkKeyEventStruct, 1);
386 gunichar key_unichar;
388 switch (clutter_event->type)
390 case CLUTTER_KEY_PRESS:
391 atk_event->type = ATK_KEY_EVENT_PRESS;
393 case CLUTTER_KEY_RELEASE:
394 atk_event->type = ATK_KEY_EVENT_RELEASE;
397 g_assert_not_reached ();
401 atk_event->state = clutter_event->modifier_state;
403 /* We emit the clutter keyval. This is not exactly the one expected
404 by AtkKeyEventStruct, as it expects a Gdk-like event, with the
405 modifiers applied. But to avoid a dependency to gdk, we delegate
406 that on the AT application.
407 More information: Bug 1952 and bug 2072
409 atk_event->keyval = clutter_event->keyval;
411 /* It is expected to store a key defining string here (ie "Space" in
412 case you press a space). Anyway, there are no function on clutter
413 to obtain that, and we want to avoid a gdk dependency here, so we
414 delegate on the AT application to obtain that string using the
415 rest of the data on the ATK event struct.
417 More information: Bug 1952 and 2072
420 key_unichar = clutter_event_get_key_unicode ((ClutterEvent *) clutter_event);
422 if (g_unichar_validate (key_unichar) && !g_unichar_iscntrl (key_unichar))
426 new = g_string_new ("");
427 new = g_string_insert_unichar (new, 0, key_unichar);
428 atk_event->string = new->str;
429 g_string_free (new, FALSE);
432 atk_event->string = NULL;
434 atk_event->length = 0;
436 atk_event->keycode = clutter_event->hardware_keycode;
437 atk_event->timestamp = clutter_event->time;
441 g_debug ("CallyKeyEvent:\tsym 0x%x\n\t\tmods %x\n\t\tcode %u\n\t\ttime %lx \n\t\tstring %s\n",
442 (unsigned int) atk_event->keyval,
443 (unsigned int) atk_event->state,
444 (unsigned int) atk_event->keycode,
445 (unsigned long int) atk_event->timestamp,
454 notify_hf (gpointer key, gpointer value, gpointer data)
456 CallyKeyEventInfo *info = (CallyKeyEventInfo *) value;
457 AtkKeyEventStruct *key_event = (AtkKeyEventStruct *)data;
459 return (*(AtkKeySnoopFunc) info->listener) (key_event, info->func_data) ? TRUE : FALSE;
463 insert_hf (gpointer key, gpointer value, gpointer data)
465 GHashTable *new_table = (GHashTable *) data;
466 g_hash_table_insert (new_table, key, value);
470 cally_key_snooper (ClutterActor *actor,
474 AtkKeyEventStruct *key_event = NULL;
477 /* filter key events */
478 if ((event->type != CLUTTER_KEY_PRESS) && (event->type != CLUTTER_KEY_RELEASE))
483 if (key_listener_list)
485 GHashTable *new_hash = g_hash_table_new (NULL, NULL);
487 g_hash_table_foreach (key_listener_list, insert_hf, new_hash);
488 key_event = atk_key_event_from_clutter_event_key ((ClutterKeyEvent *)event);
489 /* func data is inside the hash table */
490 consumed = g_hash_table_foreach_steal (new_hash, notify_hf, key_event);
491 g_hash_table_destroy (new_hash);
494 g_free (key_event->string);
497 return (consumed ? 1 : 0);
501 cally_util_stage_added_cb (ClutterStageManager *stage_manager,
505 GCallback cally_key_snooper_cb = G_CALLBACK (data);
507 g_signal_connect (G_OBJECT (stage), "captured-event", cally_key_snooper_cb, NULL);
511 cally_util_stage_removed_cb (ClutterStageManager *stage_manager,
515 GCallback cally_key_snooper_cb = G_CALLBACK (data);
517 g_signal_handlers_disconnect_by_func (stage, cally_key_snooper_cb, NULL);