2003-01-27 Padraig O'Briain <padraig.obriain@sun.com
[platform/core/uifw/at-spi2-atk.git] / registryd / registry.c
1 /*
2  * AT-SPI - Assistive Technology Service Provider Interface
3  * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
4  *
5  * Copyright 2001, 2002 Sun Microsystems Inc.,
6  * Copyright 2001, 2002 Ximian, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 /* registry.c: the main accessibility service registry implementation */
25
26 #undef SPI_LISTENER_DEBUG
27 #undef SPI_DEBUG
28
29 #include <config.h>
30 #ifdef SPI_DEBUG
31 #  include <stdio.h>
32 #endif
33
34 #include <bonobo/bonobo-exception.h>
35 #include "../libspi/spi-private.h"
36 #include "registry.h"
37
38 /* Our parent GObject type  */
39 #define PARENT_TYPE SPI_LISTENER_TYPE
40
41 /* A pointer to our parent object class */
42 static SpiListenerClass *spi_registry_parent_class;
43
44 int _dbg = 0;
45
46 typedef enum {
47   ETYPE_FOCUS,
48   ETYPE_OBJECT,
49   ETYPE_PROPERTY,
50   ETYPE_WINDOW,
51   ETYPE_TOOLKIT,
52   ETYPE_KEYBOARD,
53   ETYPE_MOUSE,
54   ETYPE_LAST_DEFINED
55 } EventTypeCategory;
56
57 typedef struct {
58   const char *event_name;
59   EventTypeCategory type_cat;
60   GQuark major;  /* from string segment[1] */
61   GQuark minor;  /* from string segment[1]+segment[2] */
62   GQuark detail; /* from string segment[3] (not concatenated) */
63 } EventTypeStruct;
64
65 typedef struct {
66   Accessibility_EventListener listener;
67   GQuark            event_type_quark;
68   EventTypeCategory event_type_cat;
69 } SpiListenerStruct;
70
71 static void
72 spi_registry_set_debug (const char *debug_flag_string)
73 {
74   if (debug_flag_string) 
75     _dbg = (int) g_ascii_strtod (debug_flag_string, NULL);
76 }
77
78 SpiListenerStruct *
79 spi_listener_struct_new (Accessibility_EventListener listener, CORBA_Environment *ev)
80 {
81   SpiListenerStruct *retval = g_malloc (sizeof (SpiListenerStruct));
82   retval->listener = bonobo_object_dup_ref (listener, ev);
83   return retval;
84 }
85
86
87 void
88 spi_listener_struct_free (SpiListenerStruct *ls, CORBA_Environment *ev)
89 {
90   bonobo_object_release_unref (ls->listener, ev);
91   g_free (ls);
92 }
93
94 static void
95 desktop_add_application (SpiDesktop *desktop,
96                          guint index, gpointer data)
97 {
98   BonoboObject *registry = BONOBO_OBJECT (data);
99   Accessibility_Event e;
100   CORBA_Environment ev;
101   Accessibility_Accessible a;
102   
103   CORBA_exception_init (&ev);
104   e.type = "object:children-changed:add";
105   e.source = BONOBO_OBJREF (desktop);
106   e.detail1 = index;
107   e.detail2 = 0;
108   a = Accessibility_Accessible_getChildAtIndex (BONOBO_OBJREF (desktop), 
109                                                 index, &ev);
110   /* FIXME
111   spi_init_any_object (&e.any_data, a);
112   */
113   spi_init_any_nil (&e.any_data);
114   Accessibility_Registry_notifyEvent (BONOBO_OBJREF (registry),
115                                       &e, &ev);
116   bonobo_object_release_unref (a, &ev);
117   CORBA_exception_free (&ev);
118 }
119
120
121
122 static void
123 desktop_remove_application (SpiDesktop *desktop,
124                             guint index, gpointer data)
125 {
126   BonoboObject *registry = BONOBO_OBJECT (data);
127   Accessibility_Event e;
128   Accessibility_Accessible a;
129   CORBA_Environment ev;
130   
131   CORBA_exception_init (&ev);
132
133   e.type = "object:children-changed:remove";
134   e.source = BONOBO_OBJREF (desktop);
135   e.detail1 = index;
136   e.detail2 = 0;
137   a = Accessibility_Accessible_getChildAtIndex (BONOBO_OBJREF (desktop), 
138                                                 index, &ev);
139   /* FIXME
140   spi_init_any_object (&e.any_data, a);
141   */
142   spi_init_any_nil (&e.any_data);
143   Accessibility_Accessible_unref (a, &ev);
144   Accessibility_Registry_notifyEvent (BONOBO_OBJREF (registry),
145                                       &e, &ev);
146   Accessibility_Desktop_unref (e.source, &ev);
147   CORBA_exception_free (&ev);
148 }
149
150
151 static void
152 spi_registry_object_finalize (GObject *object)
153 {
154   DBG (1, g_warning ("spi_registry_object_finalize called\n"));
155
156   /* TODO: unref deviceeventcontroller, which disconnects key listener */
157   G_OBJECT_CLASS (spi_registry_parent_class)->finalize (object);
158 }
159
160 static long
161 _get_unique_id (void)
162 {
163   static long id = 0;
164
165   return ++id;
166 }
167
168 /**
169  * registerApplication:
170  * @application: a reference to the requesting @Application
171  * return values: void
172  *
173  * Register a new application with the accessibility broker.
174  *
175  **/
176 static void
177 impl_accessibility_registry_register_application (PortableServer_Servant servant,
178                                                   const Accessibility_Application application,
179                                                   CORBA_Environment * ev)
180 {
181   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
182
183 #ifdef SPI_DEBUG
184   fprintf (stderr, "registering app %p\n", application);
185 #endif
186   spi_desktop_add_application (registry->desktop, application);
187
188   Accessibility_Application__set_id (application, _get_unique_id (), ev);
189
190   /*
191    * TODO: change the implementation below to a WM-aware one;
192    * e.g. don't add all apps to the SpiDesktop
193    */
194 }
195
196 #ifdef USE_A_HASH_IN_FUTURE
197 static gint
198 compare_corba_objects (gconstpointer p1, gconstpointer p2)
199 {
200   CORBA_Environment ev;
201   gint retval;
202
203 #ifdef SPI_DEBUG
204   fprintf (stderr, "comparing %p to %p\n",
205            p1, p2);
206 #endif
207   
208   retval = !CORBA_Object_is_equivalent ((CORBA_Object) p1, (CORBA_Object) p2, &ev);
209   return retval;  
210 }
211 #endif
212
213 static void
214 register_with_toolkits (SpiRegistry *spi_registry_bonobo_object, EventTypeStruct *etype, CORBA_Environment *ev)
215 {
216   gint n_desktops;
217   gint n_apps;
218   gint i, j;
219   Accessibility_Desktop desktop;
220   Accessibility_Application app;
221   Accessibility_Registry registry;
222   registry  = BONOBO_OBJREF (spi_registry_bonobo_object);
223
224   /* for each app in each desktop, call ...Application_registerToolkitEventListener */
225
226   n_desktops = Accessibility_Registry_getDesktopCount (registry, ev);
227
228   for (i=0; i<n_desktops; ++i)
229     {
230       desktop = Accessibility_Registry_getDesktop (registry, i, ev);
231       n_apps = Accessibility_Desktop__get_childCount (desktop, ev);
232       for (j=0; j<n_apps; ++j)
233         {
234           app = (Accessibility_Application) Accessibility_Desktop_getChildAtIndex (desktop,
235                                                                                    j,
236                                                                                    ev);
237           Accessibility_Application_registerToolkitEventListener (app,
238                                                                   registry,
239                                                                   CORBA_string_dup (etype->event_name),
240                                                                   ev);
241         }
242     }
243 }
244
245 #ifdef USE_A_HASH_IN_FUTURE
246
247 static gint
248 compare_listener_quarks (gconstpointer p1, gconstpointer p2)
249 {
250         return (((SpiListenerStruct *)p2)->event_type_quark !=
251                 ((SpiListenerStruct *)p1)->event_type_quark);
252 }
253
254 static gint
255 compare_listener_corbaref (gconstpointer p1, gconstpointer p2)
256 {
257   return compare_corba_objects (((SpiListenerStruct *)p2)->listener,
258                                 ((SpiListenerStruct *)p1)->listener);
259 }
260 #endif
261
262 static void
263 parse_event_type (EventTypeStruct *etype, const char *event_name)
264 {
265   gchar **split_string;
266   gchar *s;
267
268   split_string = g_strsplit (event_name, ":", 4);
269   etype->event_name = event_name;
270
271   if (!g_ascii_strncasecmp (event_name, "focus:", 6))
272     {
273       etype->type_cat = ETYPE_FOCUS;
274     }
275   else if (!g_ascii_strncasecmp (event_name, "mouse:", 6))
276     {
277       etype->type_cat = ETYPE_MOUSE;
278     }
279   else if (!g_ascii_strncasecmp (event_name, "object:", 7))
280     {
281       etype->type_cat = ETYPE_OBJECT;
282     }
283   else if (!g_ascii_strncasecmp (event_name, "window:", 7))
284     {
285       etype->type_cat = ETYPE_WINDOW;
286     }
287   else if (!g_ascii_strncasecmp (event_name, "keyboard:", 9))
288     {
289       etype->type_cat = ETYPE_KEYBOARD;
290     }
291   else
292     {
293       etype->type_cat = ETYPE_TOOLKIT;
294     }
295
296   if (split_string[1])
297     {
298       etype->major = g_quark_from_string (split_string[1]);
299       if (split_string[2])
300         {
301           etype->minor = g_quark_from_string (s = g_strconcat (split_string[1], split_string[2], NULL));
302           g_free (s);
303           if (split_string[3])
304             {
305               etype->detail = g_quark_from_string (split_string[3]);
306             }
307           else
308             {
309               etype->detail = g_quark_from_static_string ("");
310             }
311         }
312       else
313         {
314           etype->minor = etype->major;
315           etype->detail = g_quark_from_static_string (""); //etype->major;
316         }
317     }
318   else
319     {
320       etype->major = g_quark_from_static_string ("");
321       etype->minor = etype->major;
322       etype->detail = etype->major;
323     }
324
325   g_strfreev (split_string);
326 }
327
328 /**
329  * deregisterApplication:
330  * @application: a reference to the @Application
331  * to be deregistered.
332  * return values: void
333  *
334  * De-register an application previously registered with the broker.
335  *
336  **/
337 static void
338 impl_accessibility_registry_deregister_application (PortableServer_Servant servant,
339                                                     const Accessibility_Application application,
340                                                     CORBA_Environment * ev)
341 {
342   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
343
344   spi_desktop_remove_application (registry->desktop, application);
345
346 #ifdef SPI_DEBUG
347   fprintf (stderr, "de-registered app %p\n", application);
348 #endif
349 }
350
351 static GList **
352 get_listener_list (SpiRegistry      *registry,
353                    EventTypeCategory cat)
354 {
355   GList **ret;
356   
357   switch (cat)
358     {
359       case ETYPE_OBJECT:
360       case ETYPE_PROPERTY:
361       case ETYPE_FOCUS:
362       case ETYPE_KEYBOARD:
363         ret = &registry->object_listeners;
364         break;
365       case ETYPE_WINDOW:
366         ret = &registry->window_listeners;
367         break;
368       case ETYPE_MOUSE:
369       case ETYPE_TOOLKIT:
370         ret = &registry->toolkit_listeners;
371         break;
372       default:
373         ret = NULL;
374         break;
375     }
376   return ret;
377 }
378
379 /*
380  * CORBA Accessibility::Registry::registerGlobalEventListener method implementation
381  */
382 static void
383 impl_accessibility_registry_register_global_event_listener (
384         PortableServer_Servant      servant,
385         Accessibility_EventListener listener,
386         const CORBA_char           *event_name,
387         CORBA_Environment          *ev)
388 {
389   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant)); 
390   SpiListenerStruct *ls = spi_listener_struct_new (listener, ev);
391   EventTypeStruct etype;
392   GList          **list;
393
394 #ifdef SPI_LISTENER_DEBUG
395   fprintf (stderr, "registering for events of type %s\n", event_name);
396 #endif
397
398   /* parse, check major event type and add listener accordingly */
399   parse_event_type (&etype, event_name);
400   ls->event_type_quark = etype.minor;
401   ls->event_type_cat = etype.type_cat;
402
403   list = get_listener_list (registry, etype.type_cat);
404
405   if (list)
406     {
407       *list = g_list_prepend (*list, ls);
408
409       if (etype.type_cat == ETYPE_TOOLKIT)
410         {
411           register_with_toolkits (registry, &etype, ev);
412         }
413     }
414   else
415     {
416       spi_listener_struct_free (ls, ev);
417     }
418 }
419
420 static SpiReEntrantContinue
421 remove_listener_cb (GList * const *list, gpointer user_data)
422 {
423   SpiListenerStruct *ls = (SpiListenerStruct *) (*list)->data;
424   CORBA_Environment  ev;
425   Accessibility_EventListener listener = user_data;
426
427   CORBA_exception_init (&ev);
428         
429   if (CORBA_Object_is_equivalent (ls->listener, listener, &ev))
430     {
431        spi_re_entrant_list_delete_link (list);
432        spi_listener_struct_free (ls, &ev);
433     }
434
435   CORBA_exception_free (&ev);
436
437   return SPI_RE_ENTRANT_CONTINUE;
438 }
439
440 /*
441  * CORBA Accessibility::Registry::deregisterGlobalEventListenerAll method implementation
442  */
443 static void
444 impl_accessibility_registry_deregister_global_event_listener_all (
445         PortableServer_Servant      servant,
446         Accessibility_EventListener listener,
447         CORBA_Environment          *ev)
448 {
449   int i;
450   GList **lists[3];
451   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
452
453   lists[0] = &registry->object_listeners;
454   lists[1] = &registry->window_listeners;
455   lists[2] = &registry->toolkit_listeners;
456
457   for (i = 0; i < sizeof (lists) / sizeof (lists[0]); i++)
458     {
459       spi_re_entrant_list_foreach (lists [i], remove_listener_cb, listener);
460     }
461 }
462
463
464 /*
465  * CORBA Accessibility::Registry::deregisterGlobalEventListener method implementation
466  */
467 static void
468 impl_accessibility_registry_deregister_global_event_listener (
469         PortableServer_Servant      servant,
470         Accessibility_EventListener listener,
471         const CORBA_char           *event_name,
472         CORBA_Environment          *ev)
473 {
474   SpiRegistry    *registry;
475   EventTypeStruct etype;
476
477   registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
478
479   parse_event_type (&etype, (char *) event_name);
480
481   spi_re_entrant_list_foreach (get_listener_list (registry, etype.type_cat),
482                                 remove_listener_cb, listener);
483 }
484
485
486 /**
487  * getDesktopCount:
488  * return values: a short integer indicating the current number of
489  * @Desktops.
490  *
491  * Get the current number of desktops.
492  *
493  **/
494 static short
495 impl_accessibility_registry_get_desktop_count (PortableServer_Servant servant,
496                                                CORBA_Environment * ev)
497 {
498   /* TODO: implement support for multiple virtual desktops */
499   CORBA_short n_desktops;
500   n_desktops = (CORBA_short) 1;
501   return n_desktops;
502 }
503
504
505 /**
506  * getDesktop:
507  * @n: the index of the requested @Desktop.
508  * return values: a reference to the requested @Desktop.
509  *
510  * Get the nth accessible desktop.
511  *
512  **/
513 static Accessibility_Desktop
514 impl_accessibility_registry_get_desktop (PortableServer_Servant servant,
515                                          const CORBA_short n,
516                                          CORBA_Environment * ev)
517 {
518   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
519
520   /* TODO: implement support for multiple virtual desktops */
521   if (n == 0)
522     {
523       return (Accessibility_Desktop)
524         bonobo_object_dup_ref (BONOBO_OBJREF (registry->desktop), ev);
525     }
526   else
527     {
528       return (Accessibility_Desktop) CORBA_OBJECT_NIL;
529     }
530 }
531
532
533 /**
534  * getDesktopList:
535  * return values: a sequence containing references to
536  * the @Desktops.
537  *
538  * Get a list of accessible desktops.
539  *
540  **/
541 static Accessibility_DesktopSeq *
542 impl_accessibility_registry_get_desktop_list (PortableServer_Servant servant,
543                                               CORBA_Environment * ev)
544 {
545   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
546   Accessibility_DesktopSeq *desktops;
547
548   desktops = Accessibility_DesktopSeq__alloc ();
549   desktops->_length = desktops->_maximum = 1;
550   desktops->_buffer = Accessibility_DesktopSeq_allocbuf (desktops->_length);
551   desktops->_buffer [0] = bonobo_object_dup_ref (BONOBO_OBJREF (registry->desktop), ev);
552
553   return desktops;
554 }
555
556
557 static Accessibility_DeviceEventController
558 impl_accessibility_registry_get_device_event_controller (PortableServer_Servant servant,
559                                                          CORBA_Environment     *ev)
560 {
561   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
562
563   if (!registry->de_controller)
564     {
565       registry->de_controller = spi_device_event_controller_new (registry);
566     }
567
568   return bonobo_object_dup_ref (BONOBO_OBJREF (registry->de_controller), ev);
569 }
570
571 typedef struct {
572   CORBA_Environment  *ev;
573   Bonobo_Unknown      source;
574   EventTypeStruct     etype;
575   Accessibility_Event e_out;
576 } NotifyContext;
577
578 static SpiReEntrantContinue
579 notify_listeners_cb (GList * const *list, gpointer user_data)
580 {
581   SpiListenerStruct *ls;
582   NotifyContext     *ctx = user_data;
583
584   ls = (*list)->data;
585
586 #ifdef SPI_LISTENER_DEBUG
587   fprintf (stderr, "event quarks: %lx %lx %lx\n", ls->event_type_quark, ctx->etype.major, ctx->etype.minor);
588   fprintf (stderr, "event name: %s\n", ctx->etype.event_name);
589 #endif
590   if ((ls->event_type_quark == ctx->etype.major) ||
591       (ls->event_type_quark == ctx->etype.minor))
592     {
593 #ifdef SPI_DEBUG
594       CORBA_string s;
595       fprintf (stderr, "notifying listener %d\n", 0);
596 /* g_list_index (list, l->data)); */
597       s = Accessibility_Accessible__get_name (ctx->source, ctx->ev);
598       fprintf (stderr, "event source name %s\n", s);
599       CORBA_free (s);
600       if (BONOBO_EX (ctx->ev))
601         {
602           CORBA_exception_free (ctx->ev);
603           return SPI_RE_ENTRANT_CONTINUE;
604         }
605 #endif
606       
607       ctx->e_out.source = ctx->source;
608       
609       if ((*list) && (*list)->data == ls)
610         {
611           Accessibility_EventListener_notifyEvent (
612             (Accessibility_EventListener) ls->listener, &ctx->e_out, ctx->ev);
613           if (ctx->ev->_major != CORBA_NO_EXCEPTION)
614             {
615               DBG (1, g_warning ("Accessibility app error: exception during "
616                         "event notification: %s\n",
617                         CORBA_exception_id (ctx->ev)));
618               CORBA_exception_free (ctx->ev);
619               /* FIXME: check that this item is removed from the list
620                * on system exception by a 'broken' listener */
621             }
622         }
623     }  
624
625   return SPI_RE_ENTRANT_CONTINUE;
626 }
627
628 static void
629 impl_registry_notify_event (PortableServer_Servant     servant,
630                             const Accessibility_Event *e,
631                             CORBA_Environment         *ev)
632 {
633   SpiRegistry  *registry;
634   GList       **list;
635   NotifyContext ctx;
636
637   registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
638
639   parse_event_type (&ctx.etype, e->type);
640
641   list = get_listener_list (registry, ctx.etype.type_cat);
642
643   if (list && *list)
644     {
645       ctx.ev = ev;
646       ctx.e_out = *e;
647       ctx.source = e->source;
648       spi_re_entrant_list_foreach (list, notify_listeners_cb, &ctx);
649     }
650 }
651
652 static void
653 spi_registry_class_init (SpiRegistryClass *klass)
654 {
655   GObjectClass * object_class = (GObjectClass *) klass;
656   POA_Accessibility_Registry__epv *epv = &klass->epv;
657
658   spi_registry_parent_class = g_type_class_ref (SPI_LISTENER_TYPE);
659   
660   object_class->finalize = spi_registry_object_finalize;
661
662   klass->parent_class.epv.notifyEvent   = impl_registry_notify_event;
663   
664   epv->registerApplication              = impl_accessibility_registry_register_application;
665   epv->deregisterApplication            = impl_accessibility_registry_deregister_application;
666   epv->registerGlobalEventListener      = impl_accessibility_registry_register_global_event_listener;
667   epv->deregisterGlobalEventListener    = impl_accessibility_registry_deregister_global_event_listener;
668   epv->deregisterGlobalEventListenerAll = impl_accessibility_registry_deregister_global_event_listener_all;
669   epv->getDeviceEventController         = impl_accessibility_registry_get_device_event_controller;
670   epv->getDesktopCount                  = impl_accessibility_registry_get_desktop_count;
671   epv->getDesktop                       = impl_accessibility_registry_get_desktop;
672   epv->getDesktopList                   = impl_accessibility_registry_get_desktop_list;
673 }
674
675 static void
676 spi_registry_init (SpiRegistry *registry)
677 {
678   spi_registry_set_debug (g_getenv ("AT_SPI_DEBUG"));
679   /*
680    * TODO: fixme, this module makes the foolish assumptions that
681    * registryd uses the same display as the apps, and that the
682    * DISPLAY environment variable is set.
683    */
684   gdk_init (NULL, NULL);
685
686   registry->object_listeners = NULL;
687   registry->window_listeners = NULL;
688   registry->toolkit_listeners = NULL;
689   registry->desktop = spi_desktop_new ();
690   /* Register callback notification for application addition and removal */
691   g_signal_connect (G_OBJECT (registry->desktop),
692                     "application_added",
693                     G_CALLBACK (desktop_add_application),
694                     registry);
695
696   g_signal_connect (G_OBJECT (registry->desktop),
697                     "application_removed",
698                     G_CALLBACK (desktop_remove_application),
699                     registry);
700
701   registry->de_controller = spi_device_event_controller_new (registry);
702 }
703
704 BONOBO_TYPE_FUNC_FULL (SpiRegistry,
705                        Accessibility_Registry,
706                        PARENT_TYPE,
707                        spi_registry);
708
709 SpiRegistry *
710 spi_registry_new (void)
711 {
712   SpiRegistry *retval = g_object_new (SPI_REGISTRY_TYPE, NULL);
713   bonobo_object_set_immortal (BONOBO_OBJECT (retval), TRUE);
714   return retval;
715 }