2002-01-18 Michael Meeks <michael@ximian.com>
[platform/core/uifw/at-spi2-atk.git] / atk-bridge / bridge.c
1 /*
2  * AT-SPI - Assistive Technology Service Provider Interface
3  * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
4  *
5  * Copyright 2001 Sun Microsystems Inc.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <stdarg.h>
26 #include <libbonobo.h>
27 #include <orbit/orbit.h>
28 #include <atk/atk.h>
29 #include <atk/atkobject.h>
30 #include <atk/atknoopobject.h>
31 #include <libspi/Accessibility.h>
32 #include "accessible.h"
33 #include "application.h"
34
35 #undef SPI_BRIDGE_DEBUG
36
37 static CORBA_Environment ev;
38 static Accessibility_Registry registry;
39 static SpiApplication *this_app = NULL;
40
41 static void     spi_atk_bridge_exit_func               (void);
42 static void     spi_atk_register_event_listeners       (void);
43 static gboolean spi_atk_bridge_idle_init               (gpointer               user_data);
44 static void     spi_atk_bridge_focus_tracker           (AtkObject             *object);
45 static gboolean spi_atk_bridge_property_event_listener (GSignalInvocationHint *signal_hint,
46                                                         guint                  n_param_values,
47                                                         const GValue          *param_values,
48                                                         gpointer               data);
49 static gboolean spi_atk_bridge_signal_listener         (GSignalInvocationHint *signal_hint,
50                                                         guint                  n_param_values,
51                                                         const GValue          *param_values,
52                                                         gpointer               data);
53 static gint     spi_atk_bridge_key_listener            (AtkKeyEventStruct     *event,
54                                                         gpointer               data);
55
56 /*
57  *   These exported symbols are hooked by gnome-program
58  * to provide automatic module initialization and shutdown.
59  */
60 extern void gnome_accessibility_module_init     (void);
61 extern void gnome_accessibility_module_shutdown (void);
62
63 static int     atk_bridge_initialized = FALSE;
64 static guint   atk_bridge_focus_tracker_id = 0;
65 static guint   atk_bridge_key_event_listener_id = 0;
66 static guint   idle_init_id = 0;
67 static GArray *listener_ids = NULL;
68
69 int
70 gtk_module_init (gint *argc, gchar **argv[])
71 {
72   CORBA_Environment ev;
73
74   if (atk_bridge_initialized)
75     {
76       return 0;
77     }
78   atk_bridge_initialized = TRUE;
79
80   if (!bonobo_init (argc, *argv))
81     {
82       g_error ("Could not initialize Bonobo");
83     }
84
85   CORBA_exception_init(&ev);
86
87   registry = bonobo_activation_activate_from_id (
88           "OAFIID:Accessibility_Registry:proto0.1", 0, NULL, &ev);
89   
90   if (ev._major != CORBA_NO_EXCEPTION)
91     {
92       g_error ("Accessibility app error: exception during "
93                "registry activation from id: %s\n",
94                CORBA_exception_id (&ev));
95       CORBA_exception_free (&ev);
96     }
97
98   if (CORBA_Object_is_nil (registry, &ev))
99     {
100       g_error ("Could not locate registry");
101     }
102
103   bonobo_activate ();
104
105   /* Create the accessible application server object */
106
107   this_app = spi_application_new (atk_get_root ());
108
109   fprintf (stderr, "About to register application\n");
110
111   Accessibility_Registry_registerApplication (registry,
112                                               BONOBO_OBJREF (this_app),
113                                               &ev);
114
115   g_atexit (spi_atk_bridge_exit_func);
116
117   idle_init_id = g_idle_add (spi_atk_bridge_idle_init, NULL);
118
119   return 0;
120 }
121
122 static gboolean
123 spi_atk_bridge_idle_init (gpointer user_data)
124 {
125   idle_init_id = 0;
126
127   spi_atk_register_event_listeners ();
128
129   fprintf (stderr, "Application registered & listening\n");
130
131   return FALSE;
132 }
133
134 static void
135 add_signal_listener (const char *signal_name)
136 {
137   guint id;
138
139   id = atk_add_global_event_listener (
140     spi_atk_bridge_signal_listener, signal_name);
141
142   g_array_append_val (listener_ids, id);
143 }
144
145 static void
146 spi_atk_register_event_listeners (void)
147 {
148   /*
149    * kludge to make sure the Atk interface types are registered, otherwise
150    * the AtkText signal handlers below won't get registered
151    */
152   guint      id;
153   GObject   *ao = g_object_new (ATK_TYPE_OBJECT, NULL);
154   AtkObject *bo = atk_no_op_object_new (ao);
155   
156   /* Register for focus event notifications, and register app with central registry  */
157
158   listener_ids = g_array_sized_new (FALSE, TRUE, sizeof (guint), 16);
159
160   atk_bridge_focus_tracker_id = atk_add_focus_tracker (spi_atk_bridge_focus_tracker);
161
162   id = atk_add_global_event_listener (spi_atk_bridge_property_event_listener,
163                                       "Gtk:AtkObject:property-change");
164   g_array_append_val (listener_ids, id);
165
166   add_signal_listener ("Gtk:AtkObject:children-changed");
167   add_signal_listener ("Gtk:AtkObject:visible-data-changed");
168   add_signal_listener ("Gtk:AtkSelection:selection-changed");
169   add_signal_listener ("Gtk:AtkText:text-selection-changed");
170   add_signal_listener ("Gtk:AtkText:text-changed");
171   add_signal_listener ("Gtk:AtkText:text-caret-moved");
172   add_signal_listener ("Gtk:AtkTable:row-inserted");
173   add_signal_listener ("Gtk:AtkTable:row-reordered");
174   add_signal_listener ("Gtk:AtkTable:row-deleted");
175   add_signal_listener ("Gtk:AtkTable:column-inserted");
176   add_signal_listener ("Gtk:AtkTable:column-reordered");
177   add_signal_listener ("Gtk:AtkTable:column-deleted");
178   add_signal_listener ("Gtk:AtkTable:model-changed");
179 /*
180  * May add the following listeners to implement preemptive key listening for GTK+
181  *
182  * atk_add_global_event_listener (spi_atk_bridge_widgetkey_listener, "Gtk:GtkWidget:key-press-event");
183  * atk_add_global_event_listener (spi_atk_bridge_widgetkey_listener, "Gtk:GtkWidget:key-release-event");
184  */
185   atk_bridge_key_event_listener_id = atk_add_key_event_listener (
186     spi_atk_bridge_key_listener, NULL);
187   
188   g_object_unref (G_OBJECT (bo));
189   g_object_unref (ao);
190 }
191
192 static void
193 deregister_application (BonoboObject *app)
194 {
195   Accessibility_Registry_deregisterApplication (
196           registry, BONOBO_OBJREF (app), &ev);
197
198   registry = bonobo_object_release_unref (registry, &ev);
199   
200   app = bonobo_object_unref (app);
201 }
202
203 static void
204 spi_atk_bridge_exit_func (void)
205 {
206   BonoboObject *app = (BonoboObject *) this_app;
207
208   fprintf (stderr, "exiting bridge\n");
209
210   if (!app)
211     {
212       return;
213     }
214   this_app = NULL;
215
216   /*
217    *  FIXME: this may be incorrect for apps that do their own bonobo
218    *  shutdown, until we can explicitly shutdown to get the ordering
219    *  right.
220    */
221   if (!bonobo_is_initialized ())
222     {
223       fprintf (stderr, "Re-initializing bonobo\n");
224       g_assert (bonobo_init (0, NULL));
225       g_assert (bonobo_activate ());
226     }
227   
228   deregister_application (app);
229
230   fprintf (stderr, "bridge exit func complete.\n");
231
232   if (g_getenv ("AT_BRIDGE_SHUTDOWN"))
233     {
234       g_assert (!bonobo_debug_shutdown ());
235     }
236 }
237
238 void
239 gnome_accessibility_module_init (void)
240 {
241   gtk_module_init (NULL, NULL);
242
243   g_print("Atk Accessibilty bridge initialized\n");
244 }
245
246 void
247 gnome_accessibility_module_shutdown (void)
248 {
249   BonoboObject *app = (BonoboObject *) this_app;
250
251   if (!atk_bridge_initialized)
252     {
253       return;
254     }
255   atk_bridge_initialized = FALSE;
256   this_app = NULL;
257
258   g_print("Atk Accessibilty bridge shutdown\n");
259
260   if (idle_init_id)
261     {
262       g_source_remove (idle_init_id);
263       idle_init_id = 0;
264     }
265   else
266     {
267       int     i;
268       GArray *ids = listener_ids;
269
270       listener_ids = NULL;
271       atk_remove_focus_tracker (atk_bridge_focus_tracker_id);
272       
273       for (i = 0; ids && i < ids->len; i++)
274         {
275           atk_remove_global_event_listener (g_array_index (ids, guint, i));
276         }
277
278       atk_remove_key_event_listener (atk_bridge_key_event_listener_id);
279     }
280
281   deregister_application (app);
282 }
283
284 static void
285 spi_atk_bridge_focus_tracker (AtkObject *object)
286 {
287   SpiAccessible *source;
288   Accessibility_Event e;
289
290   source = spi_accessible_new (object);
291
292   e.type = "focus:";
293   e.source = BONOBO_OBJREF (source);
294   e.detail1 = 0;
295   e.detail2 = 0;
296
297   Accessibility_Registry_notifyEvent (registry, &e, &ev);
298
299   CORBA_exception_free (&ev);
300 }
301
302 static void
303 spi_atk_emit_eventv (GObject      *gobject,
304                      unsigned long detail1,
305                      unsigned long detail2,
306                      const char   *format, ...)
307 {
308   va_list             args;
309   Accessibility_Event e;
310   SpiAccessible      *source;
311   AtkObject          *aobject;
312 #ifdef SPI_BRIDGE_DEBUG
313   CORBA_string s;
314 #endif
315   
316   va_start (args, format);
317   
318   if (ATK_IS_IMPLEMENTOR (gobject))
319     {
320       aobject = atk_implementor_ref_accessible (ATK_IMPLEMENTOR (gobject));
321       source  = spi_accessible_new (aobject);
322       g_object_unref (G_OBJECT (aobject));
323     }
324   else if (ATK_IS_OBJECT (gobject))
325     {
326       aobject = ATK_OBJECT (gobject);
327       source  = spi_accessible_new (aobject);
328     }
329   else
330     {
331       aobject = NULL;
332       source  = NULL;
333       g_error ("received property-change event from non-AtkImplementor");
334     }
335
336   if (source != NULL)
337     {
338       e.type = g_strdup_vprintf (format, args);
339       e.source = BONOBO_OBJREF (source);
340       e.detail1 = detail1;
341       e.detail2 = detail2;
342
343 #ifdef SPI_BRIDGE_DEBUG
344       s = Accessibility_Accessible__get_name (BONOBO_OBJREF (source), &ev);
345       g_warning ("Emitting event '%s' (%lu, %lu) on %s",
346                  e.type, e.detail1, e.detail2, s);
347       CORBA_free (s);
348 #endif
349
350       Accessibility_Registry_notifyEvent (registry, &e, &ev);
351
352       CORBA_exception_free (&ev);
353
354       g_free (e.type);
355     }
356
357   va_end (args);
358 }
359
360 static gboolean
361 spi_atk_bridge_property_event_listener (GSignalInvocationHint *signal_hint,
362                                         guint n_param_values,
363                                         const GValue *param_values,
364                                         gpointer data)
365 {
366   AtkPropertyValues *values;
367   GObject *gobject;
368
369 #ifdef SPI_BRIDGE_DEBUG
370   GSignalQuery signal_query;
371   const gchar *name;
372   gchar *s, *s2;
373   
374   g_signal_query (signal_hint->signal_id, &signal_query);
375   name = signal_query.signal_name;
376
377   s2 = g_type_name (G_OBJECT_TYPE (g_value_get_object (param_values + 0)));
378   s = atk_object_get_name (ATK_OBJECT (g_value_get_object (param_values + 0)));
379   fprintf (stderr, "Received (property) signal %s:%s from object %s (gail %s)\n",
380            g_type_name (signal_query.itype), name, s, s2);
381 #endif
382
383   gobject = g_value_get_object (param_values + 0);
384   values = (AtkPropertyValues*) g_value_get_pointer (param_values + 1);
385
386   spi_atk_emit_eventv (gobject, 0, 0, "object:property-change:%s", values->property_name);
387
388   return TRUE;
389 }
390
391 #if THIS_WILL_EVER_BE_USED
392 static gboolean
393 spi_atk_bridge_state_event_listener (GSignalInvocationHint *signal_hint,
394                                      guint n_param_values,
395                                      const GValue *param_values,
396                                      gpointer data)
397 {
398   GObject *gobject;
399   AtkPropertyValues *values;
400 #ifdef SPI_BRIDGE_DEBUG
401   GSignalQuery signal_query;
402   const gchar *name;
403   
404   g_signal_query (signal_hint->signal_id, &signal_query);
405   name = signal_query.signal_name;
406   fprintf (stderr, "Received (state) signal %s:%s\n",
407            g_type_name (signal_query.itype), name);
408 #endif
409
410   gobject = g_value_get_object (param_values + 0);
411   values = (AtkPropertyValues*) g_value_get_pointer (param_values + 1);
412
413   spi_atk_emit_eventv (gobject, 
414                        (unsigned long) values->old_value.data[0].v_ulong,
415                        (unsigned long) values->new_value.data[0].v_ulong,
416                        "object:%s:?", values->property_name);
417   
418   return TRUE;
419 }
420 #endif
421
422 static void
423 spi_init_keystroke_from_atk_key_event (Accessibility_DeviceEvent  *keystroke,
424                                        AtkKeyEventStruct          *event)
425 {
426 #ifdef SPI_DEBUG
427   if (event)
428     {
429       g_print ("event %c (%d)\n", (int) event->keyval, (int) event->keycode);
430     }
431   else
432 #endif
433   if (!event)
434     {
435       g_print ("WARNING: NULL key event!");
436     }
437   
438   keystroke->id        = (CORBA_long) event->keyval;
439   keystroke->hw_code   = (CORBA_short) event->keycode;
440   keystroke->timestamp = (CORBA_unsigned_long) event->timestamp;
441   keystroke->modifiers = (CORBA_unsigned_short) (event->state & 0xFFFF);
442   if (event->string)
443     {
444       keystroke->event_string = CORBA_string_dup (event->string);
445       keystroke->is_text = CORBA_TRUE;
446     }
447   else
448     {
449       keystroke->event_string = CORBA_string_dup ("");
450       keystroke->is_text = CORBA_FALSE;
451     }
452   switch (event->type)
453     {
454     case (ATK_KEY_EVENT_PRESS):
455       keystroke->type = Accessibility_KEY_PRESSED;
456       break;
457     case (ATK_KEY_EVENT_RELEASE):
458       keystroke->type = Accessibility_KEY_RELEASED;
459       break;
460     default:
461       keystroke->type = 0;
462       break;
463     }
464 #if 0  
465   g_print ("key_event type %d; val=%d code=%d modifiers=%x name=%s is_text=%d, time=%lx\n",
466            (int) keystroke->type, (int) keystroke->id, (int) keystroke->hw_code,
467            (int) keystroke->modifiers,
468            keystroke->event_string, (int) keystroke->is_text, (unsigned long) keystroke->timestamp);
469 #endif
470 }
471
472 static gint
473 spi_atk_bridge_key_listener (AtkKeyEventStruct *event, gpointer data)
474 {
475   CORBA_boolean             result;
476   Accessibility_DeviceEvent key_event;
477   Accessibility_DeviceEventController controller =
478     Accessibility_Registry_getDeviceEventController (registry, &ev);
479
480   if (BONOBO_EX (&ev))
481     {
482       g_warning ("failure: no deviceeventcontroller found\n");
483       CORBA_exception_free (&ev);
484       result = FALSE;
485     }
486   else
487     {
488
489       spi_init_keystroke_from_atk_key_event (&key_event, event);
490
491       result = Accessibility_DeviceEventController_notifyListenersSync (
492         controller, &key_event, &ev);
493
494       CORBA_exception_free (&ev);
495     }
496
497   return result;
498 }
499
500 static gboolean
501 spi_atk_bridge_signal_listener (GSignalInvocationHint *signal_hint,
502                                 guint n_param_values,
503                                 const GValue *param_values,
504                                 gpointer data)
505 {
506   GObject *gobject;
507   GSignalQuery signal_query;
508   const gchar *name;
509   gint detail1 = 0, detail2 = 0;
510 #ifdef SPI_BRIDGE_DEBUG
511   gchar *s, *s2;
512 #endif
513   
514   g_signal_query (signal_hint->signal_id, &signal_query);
515
516   name = signal_query.signal_name;
517
518 #ifdef SPI_BRIDGE_DEBUG
519   s2 = g_type_name (G_OBJECT_TYPE (g_value_get_object (param_values + 0)));
520   s = atk_object_get_name (ATK_OBJECT (g_value_get_object (param_values + 0)));
521   fprintf (stderr, "Received signal %s:%s from object %s (gail %s)\n",
522            g_type_name (signal_query.itype), name, s, s2);
523 #endif
524
525   gobject = g_value_get_object (param_values + 0);
526   if (G_VALUE_TYPE (param_values + 1) == G_TYPE_INT)
527     detail1 = g_value_get_int (param_values + 1);
528   if (G_VALUE_TYPE (param_values + 2) == G_TYPE_INT)
529     detail2 = g_value_get_int (param_values + 2);
530   
531   spi_atk_emit_eventv (gobject, detail1, detail2, "object:%s", name);
532
533   return TRUE;
534 }