a11y: atk_add_key_event_listener listener_id should not return 0 as a valid value
[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       cally_util_simulate_snooper_remove ();
265     }
266 }
267
268 /* ------------------------------ PRIVATE FUNCTIONS ------------------------- */
269
270 static void
271 _listener_info_destroy (gpointer data)
272 {
273   g_free(data);
274 }
275
276 static guint
277 add_listener (GSignalEmissionHook listener,
278               const gchar         *object_type,
279               const gchar         *signal_name,
280               const gchar         *hook_data)
281 {
282   GType type;
283   guint signal_id;
284   gint  rc = 0;
285
286   type = g_type_from_name (object_type);
287   if (type)
288     {
289       signal_id  = g_signal_lookup (signal_name, type);
290       if (signal_id > 0)
291         {
292           CallyUtilListenerInfo *listener_info;
293
294           rc = listener_idx;
295
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;
303
304           g_hash_table_insert(listener_list, &(listener_info->key), listener_info);
305           listener_idx++;
306         }
307       else
308         {
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);
312         }
313     }
314   else
315     {
316       g_warning("Invalid object type %s\n", object_type);
317     }
318   return rc;
319 }
320
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 */
325 static void
326 cally_util_simulate_snooper_install (void)
327 {
328   ClutterStageManager *stage_manager = NULL;
329   ClutterStage *stage = NULL;
330   GSList *stage_list = NULL;
331   GSList *iter = NULL;
332
333   stage_manager = clutter_stage_manager_get_default ();
334   stage_list = clutter_stage_manager_list_stages (stage_manager);
335
336   for (iter = stage_list; iter != NULL; iter = g_slist_next (iter))
337     {
338       stage = CLUTTER_STAGE (iter->data);
339
340       g_signal_connect (G_OBJECT (stage), "captured-event",
341                         G_CALLBACK (cally_key_snooper), NULL);
342     }
343
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);
348 }
349
350 static void
351 cally_util_simulate_snooper_remove (void)
352 {
353   ClutterStageManager *stage_manager = NULL;
354   ClutterStage *stage = NULL;
355   GSList *stage_list = NULL;
356   GSList *iter = NULL;
357   gint num = 0;
358
359   stage_manager = clutter_stage_manager_get_default ();
360   stage_list = clutter_stage_manager_list_stages (stage_manager);
361
362   for (iter = stage_list; iter != NULL; iter = g_slist_next (iter))
363     {
364       stage = CLUTTER_STAGE (iter->data);
365
366       num += g_signal_handlers_disconnect_by_func (stage, cally_key_snooper, NULL);
367     }
368
369   g_signal_handlers_disconnect_by_func (G_OBJECT (stage_manager),
370                                         G_CALLBACK (cally_util_stage_added_cb),
371                                         cally_key_snooper);
372
373   g_signal_handlers_disconnect_by_func (G_OBJECT (stage_manager),
374                                         G_CALLBACK (cally_util_stage_removed_cb),
375                                         cally_key_snooper);
376
377 #ifdef CALLY_DEBUG
378   g_print ("Number of snooper callbacks disconnected: %i\n", num);
379 #endif
380 }
381
382 static AtkKeyEventStruct *
383 atk_key_event_from_clutter_event_key (ClutterKeyEvent *clutter_event)
384 {
385   AtkKeyEventStruct *atk_event = g_new0 (AtkKeyEventStruct, 1);
386   gunichar key_unichar;
387
388   switch (clutter_event->type)
389     {
390     case CLUTTER_KEY_PRESS:
391       atk_event->type = ATK_KEY_EVENT_PRESS;
392       break;
393     case CLUTTER_KEY_RELEASE:
394       atk_event->type = ATK_KEY_EVENT_RELEASE;
395       break;
396     default:
397       g_assert_not_reached ();
398       return NULL;
399     }
400
401   atk_event->state = clutter_event->modifier_state;
402
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
408   */
409   atk_event->keyval = clutter_event->keyval;
410
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.
416
417      More information: Bug 1952 and 2072
418   */
419
420   key_unichar = clutter_event_get_key_unicode ((ClutterEvent *) clutter_event);
421
422   if (g_unichar_validate (key_unichar) && !g_unichar_iscntrl (key_unichar))
423     {
424       GString *new = NULL;
425
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);
430     }
431   else
432     atk_event->string = NULL;
433
434   atk_event->length = 0;
435
436   atk_event->keycode = clutter_event->hardware_keycode;
437   atk_event->timestamp = clutter_event->time;
438
439 #ifdef CALLY_DEBUG
440
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,
446            atk_event->string);
447 #endif
448
449   return atk_event;
450 }
451
452
453 static gboolean
454 notify_hf (gpointer key, gpointer value, gpointer data)
455 {
456   CallyKeyEventInfo *info = (CallyKeyEventInfo *) value;
457   AtkKeyEventStruct *key_event = (AtkKeyEventStruct *)data;
458
459   return (*(AtkKeySnoopFunc) info->listener) (key_event, info->func_data) ? TRUE : FALSE;
460 }
461
462 static void
463 insert_hf (gpointer key, gpointer value, gpointer data)
464 {
465   GHashTable *new_table = (GHashTable *) data;
466   g_hash_table_insert (new_table, key, value);
467 }
468
469 static gboolean
470 cally_key_snooper (ClutterActor *actor,
471                    ClutterEvent *event,
472                    gpointer      user_data)
473 {
474   AtkKeyEventStruct *key_event = NULL;
475   gint consumed = 0;
476
477   /* filter key events */
478   if ((event->type != CLUTTER_KEY_PRESS) && (event->type != CLUTTER_KEY_RELEASE))
479     {
480       return FALSE;
481     }
482
483   if (key_listener_list)
484     {
485       GHashTable *new_hash = g_hash_table_new (NULL, NULL);
486
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);
492     }
493
494   g_free (key_event->string);
495   g_free (key_event);
496
497   return (consumed ? 1 : 0);
498 }
499
500 static void
501 cally_util_stage_added_cb (ClutterStageManager *stage_manager,
502                            ClutterStage *stage,
503                            gpointer data)
504 {
505   GCallback cally_key_snooper_cb = G_CALLBACK (data);
506
507   g_signal_connect (G_OBJECT (stage), "captured-event", cally_key_snooper_cb, NULL);
508 }
509
510 static void
511 cally_util_stage_removed_cb (ClutterStageManager *stage_manager,
512                              ClutterStage *stage,
513                              gpointer data)
514 {
515   GCallback cally_key_snooper_cb = G_CALLBACK (data);
516
517   g_signal_handlers_disconnect_by_func (stage, cally_key_snooper_cb, NULL);
518 }