a11y: Remove key event listener hash table if no longer required
[profile/ivi/clutter.git] / clutter / cally / cally-util.c
1 /* CALLY - The Clutter Accessibility Implementation Library
2  *
3  * Copyright (C) 2008 Igalia, S.L.
4  *
5  * Author: Alejandro PiƱeiro Iglesias <apinheiro@igalia.com>
6  *
7  * Based on GailUtil from GAIL
8  * Copyright 2001, 2002, 2003 Sun Microsystems Inc.
9  *
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.
14  *
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.
19  *
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.
24  */
25
26 /**
27  * SECTION:cally-util
28  * @Title: CallyUtil
29  * @short_description: #AtkUtil implementation
30  * @see_also: #ClutterActor
31  *
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.
35
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.
39  */
40
41 #ifdef HAVE_CONFIG_H
42 #include "config.h"
43 #endif
44
45 #include <stdlib.h>
46 #include <string.h>
47 #include <clutter/clutter.h>
48
49 #include "cally-util.h"
50 #include "cally-root.h"
51 #include "cally-stage.h"
52
53 /* atkutil.h */
54
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,
59                                                                      gpointer        data);
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);
64
65 /* private */
66 static void                  _listener_info_destroy                  (gpointer data);
67 static guint                 add_listener                            (GSignalEmissionHook  listener,
68                                                                       const gchar         *object_type,
69                                                                       const gchar         *signal,
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,
74                                                                       ClutterEvent *event,
75                                                                       gpointer      user_data);
76 static void                  cally_util_stage_added_cb               (ClutterStageManager *stage_manager,
77                                                                       ClutterStage *stage,
78                                                                       gpointer data);
79 static void                  cally_util_stage_removed_cb             (ClutterStageManager *stage_manager,
80                                                                       ClutterStage *stage,
81                                                                       gpointer data);
82 static gboolean              notify_hf                               (gpointer key,
83                                                                       gpointer value,
84                                                                       gpointer data);
85 static void                  insert_hf                               (gpointer key,
86                                                                       gpointer value,
87                                                                       gpointer data);
88 static AtkKeyEventStruct *  atk_key_event_from_clutter_event_key     (ClutterKeyEvent *event);
89
90
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;
95
96 struct _CallyUtilListenerInfo
97 {
98   gint key;
99   guint signal_id;
100   gulong hook_id;
101 };
102
103 struct _CallyKeyEventInfo
104 {
105   AtkKeySnoopFunc listener;
106   gpointer func_data;
107 };
108
109 static AtkObject* root = NULL;
110 static GHashTable *listener_list = NULL;
111 static GHashTable *key_listener_list = NULL;
112 static gint listener_idx = 1;
113
114
115 G_DEFINE_TYPE (CallyUtil, cally_util, ATK_TYPE_UTIL);
116
117 static void
118 cally_util_class_init (CallyUtilClass *klass)
119 {
120   AtkUtilClass *atk_class;
121   gpointer data;
122
123   data = g_type_class_peek (ATK_TYPE_UTIL);
124   atk_class = ATK_UTIL_CLASS (data);
125
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;
133
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 */
139
140   listener_list = g_hash_table_new_full (g_int_hash, g_int_equal, NULL,
141                                          _listener_info_destroy);
142 }
143
144 static void
145 cally_util_init (CallyUtil *cally_util)
146 {
147   /* instance init: usually not required */
148 }
149
150 /* ------------------------------ ATK UTIL METHODS -------------------------- */
151
152 static AtkObject*
153 cally_util_get_root (void)
154 {
155   if (!root)
156     root = cally_root_new ();
157
158   return root;
159 }
160
161 static const gchar *
162 cally_util_get_toolkit_name (void)
163 {
164   return "clutter";
165 }
166
167 static const gchar *
168 cally_util_get_toolkit_version (void)
169 {
170   return CLUTTER_VERSION_S;
171 }
172
173
174 static guint
175 cally_util_add_global_event_listener (GSignalEmissionHook listener,
176                                       const gchar *event_type)
177 {
178   guint rc = 0;
179   gchar **split_string;
180
181   split_string = g_strsplit (event_type, ":", 3);
182
183   if (g_strv_length (split_string) == 3)
184     rc = add_listener (listener, split_string[1], split_string[2], event_type);
185
186   g_strfreev (split_string);
187
188   return rc;
189 }
190
191 static void
192 cally_util_remove_global_event_listener (guint remove_listener)
193 {
194   if (remove_listener > 0)
195     {
196       CallyUtilListenerInfo *listener_info;
197       gint tmp_idx = remove_listener;
198
199       listener_info = (CallyUtilListenerInfo *)
200         g_hash_table_lookup(listener_list, &tmp_idx);
201
202       if (listener_info != NULL)
203         {
204           /* Hook id of 0 and signal id of 0 are invalid */
205           if (listener_info->hook_id != 0 && listener_info->signal_id != 0)
206             {
207               /* Remove the emission hook */
208               g_signal_remove_emission_hook(listener_info->signal_id,
209                                             listener_info->hook_id);
210
211               /* Remove the element from the hash */
212               g_hash_table_remove(listener_list, &tmp_idx);
213             }
214           else
215             {
216               g_warning("Invalid listener hook_id %ld or signal_id %d\n",
217                         listener_info->hook_id, listener_info->signal_id);
218             }
219         }
220       else
221         {
222           g_warning("No listener with the specified listener id %d", 
223                     remove_listener);
224         }
225     }
226   else
227     {
228       g_warning("Invalid listener_id %d", remove_listener);
229     }
230 }
231
232 static guint
233 cally_util_add_key_event_listener (AtkKeySnoopFunc  listener,
234                                    gpointer         data)
235 {
236   static guint key = 1;
237   CallyKeyEventInfo *event_info = NULL;
238
239   if (!key_listener_list)
240   {
241     key_listener_list = g_hash_table_new_full (NULL, NULL, NULL, g_free);
242
243     cally_util_simulate_snooper_install ();
244   }
245
246   event_info = g_new (CallyKeyEventInfo, 1);
247   event_info->listener = listener;
248   event_info->func_data = data;
249
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 */
252   return key - 1;
253 }
254
255 static void
256 cally_util_remove_key_event_listener (guint remove_listener)
257 {
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);
260   }
261
262   if (g_hash_table_size (key_listener_list) == 0)
263     {
264       g_hash_table_destroy (key_listener_list);
265       key_listener_list = NULL;
266       cally_util_simulate_snooper_remove ();
267     }
268 }
269
270 /* ------------------------------ PRIVATE FUNCTIONS ------------------------- */
271
272 static void
273 _listener_info_destroy (gpointer data)
274 {
275   g_free(data);
276 }
277
278 static guint
279 add_listener (GSignalEmissionHook listener,
280               const gchar         *object_type,
281               const gchar         *signal_name,
282               const gchar         *hook_data)
283 {
284   GType type;
285   guint signal_id;
286   gint  rc = 0;
287
288   type = g_type_from_name (object_type);
289   if (type)
290     {
291       signal_id  = g_signal_lookup (signal_name, type);
292       if (signal_id > 0)
293         {
294           CallyUtilListenerInfo *listener_info;
295
296           rc = listener_idx;
297
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;
305
306           g_hash_table_insert(listener_list, &(listener_info->key), listener_info);
307           listener_idx++;
308         }
309       else
310         {
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);
314         }
315     }
316   else
317     {
318       g_warning("Invalid object type %s\n", object_type);
319     }
320   return rc;
321 }
322
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 */
327 static void
328 cally_util_simulate_snooper_install (void)
329 {
330   ClutterStageManager *stage_manager = NULL;
331   ClutterStage *stage = NULL;
332   GSList *stage_list = NULL;
333   GSList *iter = NULL;
334
335   stage_manager = clutter_stage_manager_get_default ();
336   stage_list = clutter_stage_manager_list_stages (stage_manager);
337
338   for (iter = stage_list; iter != NULL; iter = g_slist_next (iter))
339     {
340       stage = CLUTTER_STAGE (iter->data);
341
342       g_signal_connect (G_OBJECT (stage), "captured-event",
343                         G_CALLBACK (cally_key_snooper), NULL);
344     }
345
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);
350 }
351
352 static void
353 cally_util_simulate_snooper_remove (void)
354 {
355   ClutterStageManager *stage_manager = NULL;
356   ClutterStage *stage = NULL;
357   GSList *stage_list = NULL;
358   GSList *iter = NULL;
359   gint num = 0;
360
361   stage_manager = clutter_stage_manager_get_default ();
362   stage_list = clutter_stage_manager_list_stages (stage_manager);
363
364   for (iter = stage_list; iter != NULL; iter = g_slist_next (iter))
365     {
366       stage = CLUTTER_STAGE (iter->data);
367
368       num += g_signal_handlers_disconnect_by_func (stage, cally_key_snooper, NULL);
369     }
370
371   g_signal_handlers_disconnect_by_func (G_OBJECT (stage_manager),
372                                         G_CALLBACK (cally_util_stage_added_cb),
373                                         cally_key_snooper);
374
375   g_signal_handlers_disconnect_by_func (G_OBJECT (stage_manager),
376                                         G_CALLBACK (cally_util_stage_removed_cb),
377                                         cally_key_snooper);
378
379 #ifdef CALLY_DEBUG
380   g_print ("Number of snooper callbacks disconnected: %i\n", num);
381 #endif
382 }
383
384 static AtkKeyEventStruct *
385 atk_key_event_from_clutter_event_key (ClutterKeyEvent *clutter_event)
386 {
387   AtkKeyEventStruct *atk_event = g_new0 (AtkKeyEventStruct, 1);
388   gunichar key_unichar;
389
390   switch (clutter_event->type)
391     {
392     case CLUTTER_KEY_PRESS:
393       atk_event->type = ATK_KEY_EVENT_PRESS;
394       break;
395     case CLUTTER_KEY_RELEASE:
396       atk_event->type = ATK_KEY_EVENT_RELEASE;
397       break;
398     default:
399       g_assert_not_reached ();
400       return NULL;
401     }
402
403   atk_event->state = clutter_event->modifier_state;
404
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
410   */
411   atk_event->keyval = clutter_event->keyval;
412
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.
418
419      More information: Bug 1952 and 2072
420   */
421
422   key_unichar = clutter_event_get_key_unicode ((ClutterEvent *) clutter_event);
423
424   if (g_unichar_validate (key_unichar) && !g_unichar_iscntrl (key_unichar))
425     {
426       GString *new = NULL;
427
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);
432     }
433   else
434     atk_event->string = NULL;
435
436   atk_event->length = 0;
437
438   atk_event->keycode = clutter_event->hardware_keycode;
439   atk_event->timestamp = clutter_event->time;
440
441 #ifdef CALLY_DEBUG
442
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,
448            atk_event->string);
449 #endif
450
451   return atk_event;
452 }
453
454
455 static gboolean
456 notify_hf (gpointer key, gpointer value, gpointer data)
457 {
458   CallyKeyEventInfo *info = (CallyKeyEventInfo *) value;
459   AtkKeyEventStruct *key_event = (AtkKeyEventStruct *)data;
460
461   return (*(AtkKeySnoopFunc) info->listener) (key_event, info->func_data) ? TRUE : FALSE;
462 }
463
464 static void
465 insert_hf (gpointer key, gpointer value, gpointer data)
466 {
467   GHashTable *new_table = (GHashTable *) data;
468   g_hash_table_insert (new_table, key, value);
469 }
470
471 static gboolean
472 cally_key_snooper (ClutterActor *actor,
473                    ClutterEvent *event,
474                    gpointer      user_data)
475 {
476   AtkKeyEventStruct *key_event = NULL;
477   gint consumed = 0;
478
479   /* filter key events */
480   if ((event->type != CLUTTER_KEY_PRESS) && (event->type != CLUTTER_KEY_RELEASE))
481     {
482       return FALSE;
483     }
484
485   if (key_listener_list)
486     {
487       GHashTable *new_hash = g_hash_table_new (NULL, NULL);
488
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);
494     }
495
496   g_free (key_event->string);
497   g_free (key_event);
498
499   return (consumed ? 1 : 0);
500 }
501
502 static void
503 cally_util_stage_added_cb (ClutterStageManager *stage_manager,
504                            ClutterStage *stage,
505                            gpointer data)
506 {
507   GCallback cally_key_snooper_cb = G_CALLBACK (data);
508
509   g_signal_connect (G_OBJECT (stage), "captured-event", cally_key_snooper_cb, NULL);
510 }
511
512 static void
513 cally_util_stage_removed_cb (ClutterStageManager *stage_manager,
514                              ClutterStage *stage,
515                              gpointer data)
516 {
517   GCallback cally_key_snooper_cb = G_CALLBACK (data);
518
519   g_signal_handlers_disconnect_by_func (stage, cally_key_snooper_cb, NULL);
520 }