Revised recent patch, cleaned up internal APIs.
[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_Accessible_unref (a, &ev);
115   Accessibility_Registry_notifyEvent (BONOBO_OBJREF (registry),
116                                       &e, &ev);
117   Accessibility_Desktop_unref (e.source, &ev);
118   CORBA_exception_free (&ev);
119 }
120
121
122
123 static void
124 desktop_remove_application (SpiDesktop *desktop,
125                             guint index, gpointer data)
126 {
127   BonoboObject *registry = BONOBO_OBJECT (data);
128   Accessibility_Event e;
129   Accessibility_Accessible a;
130   CORBA_Environment ev;
131   
132   CORBA_exception_init (&ev);
133
134   e.type = "object:children-changed:remove";
135   e.source = BONOBO_OBJREF (desktop);
136   e.detail1 = index;
137   e.detail2 = 0;
138   a = Accessibility_Accessible_getChildAtIndex (BONOBO_OBJREF (desktop), 
139                                                 index, &ev);
140   /* FIXME
141   spi_init_any_object (&e.any_data, a);
142   */
143   spi_init_any_nil (&e.any_data);
144   Accessibility_Accessible_unref (a, &ev);
145   Accessibility_Registry_notifyEvent (BONOBO_OBJREF (registry),
146                                       &e, &ev);
147   Accessibility_Desktop_unref (e.source, &ev);
148   CORBA_exception_free (&ev);
149 }
150
151
152 static void
153 spi_registry_object_finalize (GObject *object)
154 {
155   DBG (1, g_warning ("spi_registry_object_finalize called\n"));
156
157   /* TODO: unref deviceeventcontroller, which disconnects key listener */
158   G_OBJECT_CLASS (spi_registry_parent_class)->finalize (object);
159 }
160
161 static long
162 _get_unique_id (void)
163 {
164   static long id = 0;
165
166   return ++id;
167 }
168
169 /**
170  * registerApplication:
171  * @application: a reference to the requesting @Application
172  * return values: void
173  *
174  * Register a new application with the accessibility broker.
175  *
176  **/
177 static void
178 impl_accessibility_registry_register_application (PortableServer_Servant servant,
179                                                   const Accessibility_Application application,
180                                                   CORBA_Environment * ev)
181 {
182   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
183
184 #ifdef SPI_DEBUG
185   fprintf (stderr, "registering app %p\n", application);
186 #endif
187   spi_desktop_add_application (registry->desktop, application);
188
189   Accessibility_Application__set_id (application, _get_unique_id (), ev);
190
191   /*
192    * TODO: change the implementation below to a WM-aware one;
193    * e.g. don't add all apps to the SpiDesktop
194    */
195 }
196
197 #ifdef USE_A_HASH_IN_FUTURE
198 static gint
199 compare_corba_objects (gconstpointer p1, gconstpointer p2)
200 {
201   CORBA_Environment ev;
202   gint retval;
203
204 #ifdef SPI_DEBUG
205   fprintf (stderr, "comparing %p to %p\n",
206            p1, p2);
207 #endif
208   
209   retval = !CORBA_Object_is_equivalent ((CORBA_Object) p1, (CORBA_Object) p2, &ev);
210   return retval;  
211 }
212 #endif
213
214 static void
215 register_with_toolkits (SpiRegistry *spi_registry_bonobo_object, EventTypeStruct *etype, CORBA_Environment *ev)
216 {
217   gint n_desktops;
218   gint n_apps;
219   gint i, j;
220   Accessibility_Desktop desktop;
221   Accessibility_Application app;
222   Accessibility_Registry registry;
223   registry  = BONOBO_OBJREF (spi_registry_bonobo_object);
224
225   /* for each app in each desktop, call ...Application_registerToolkitEventListener */
226
227   n_desktops = Accessibility_Registry_getDesktopCount (registry, ev);
228
229   for (i=0; i<n_desktops; ++i)
230     {
231       desktop = Accessibility_Registry_getDesktop (registry, i, ev);
232       n_apps = Accessibility_Desktop__get_childCount (desktop, ev);
233       for (j=0; j<n_apps; ++j)
234         {
235           app = (Accessibility_Application) Accessibility_Desktop_getChildAtIndex (desktop,
236                                                                                    j,
237                                                                                    ev);
238           Accessibility_Application_registerToolkitEventListener (app,
239                                                                   registry,
240                                                                   CORBA_string_dup (etype->event_name),
241                                                                   ev);
242         }
243     }
244 }
245
246 #ifdef USE_A_HASH_IN_FUTURE
247
248 static gint
249 compare_listener_quarks (gconstpointer p1, gconstpointer p2)
250 {
251         return (((SpiListenerStruct *)p2)->event_type_quark !=
252                 ((SpiListenerStruct *)p1)->event_type_quark);
253 }
254
255 static gint
256 compare_listener_corbaref (gconstpointer p1, gconstpointer p2)
257 {
258   return compare_corba_objects (((SpiListenerStruct *)p2)->listener,
259                                 ((SpiListenerStruct *)p1)->listener);
260 }
261 #endif
262
263 static void
264 parse_event_type (EventTypeStruct *etype, const char *event_name)
265 {
266   gchar **split_string;
267   gchar *s;
268
269   split_string = g_strsplit (event_name, ":", 4);
270   etype->event_name = event_name;
271
272   if (!g_ascii_strncasecmp (event_name, "focus:", 6))
273     {
274       etype->type_cat = ETYPE_FOCUS;
275     }
276   else if (!g_ascii_strncasecmp (event_name, "mouse:", 6))
277     {
278       etype->type_cat = ETYPE_MOUSE;
279     }
280   else if (!g_ascii_strncasecmp (event_name, "object:", 7))
281     {
282       etype->type_cat = ETYPE_OBJECT;
283     }
284   else if (!g_ascii_strncasecmp (event_name, "window:", 7))
285     {
286       etype->type_cat = ETYPE_WINDOW;
287     }
288   else if (!g_ascii_strncasecmp (event_name, "keyboard:", 9))
289     {
290       etype->type_cat = ETYPE_KEYBOARD;
291     }
292   else
293     {
294       etype->type_cat = ETYPE_TOOLKIT;
295     }
296
297   if (split_string[1])
298     {
299       etype->major = g_quark_from_string (split_string[1]);
300       if (split_string[2])
301         {
302           etype->minor = g_quark_from_string (s = g_strconcat (split_string[1], split_string[2], NULL));
303           g_free (s);
304           if (split_string[3])
305             {
306               etype->detail = g_quark_from_string (split_string[3]);
307             }
308           else
309             {
310               etype->detail = g_quark_from_static_string ("");
311             }
312         }
313       else
314         {
315           etype->minor = etype->major;
316           etype->detail = g_quark_from_static_string (""); //etype->major;
317         }
318     }
319   else
320     {
321       etype->major = g_quark_from_static_string ("");
322       etype->minor = etype->major;
323       etype->detail = etype->major;
324     }
325
326   g_strfreev (split_string);
327 }
328
329 /**
330  * deregisterApplication:
331  * @application: a reference to the @Application
332  * to be deregistered.
333  * return values: void
334  *
335  * De-register an application previously registered with the broker.
336  *
337  **/
338 static void
339 impl_accessibility_registry_deregister_application (PortableServer_Servant servant,
340                                                     const Accessibility_Application application,
341                                                     CORBA_Environment * ev)
342 {
343   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
344
345   spi_desktop_remove_application (registry->desktop, application);
346
347 #ifdef SPI_DEBUG
348   fprintf (stderr, "de-registered app %p\n", application);
349 #endif
350 }
351
352 static GList **
353 get_listener_list (SpiRegistry      *registry,
354                    EventTypeCategory cat)
355 {
356   GList **ret;
357   
358   switch (cat)
359     {
360       case ETYPE_OBJECT:
361       case ETYPE_PROPERTY:
362       case ETYPE_FOCUS:
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       case ETYPE_KEYBOARD:
373       default:
374         ret = NULL;
375         break;
376     }
377   return ret;
378 }
379
380 /*
381  * CORBA Accessibility::Registry::registerGlobalEventListener method implementation
382  */
383 static void
384 impl_accessibility_registry_register_global_event_listener (
385         PortableServer_Servant      servant,
386         Accessibility_EventListener listener,
387         const CORBA_char           *event_name,
388         CORBA_Environment          *ev)
389 {
390   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant)); 
391   SpiListenerStruct *ls = spi_listener_struct_new (listener, ev);
392   EventTypeStruct etype;
393   GList          **list;
394
395 #ifdef SPI_LISTENER_DEBUG
396   fprintf (stderr, "registering for events of type %s\n", event_name);
397 #endif
398
399   /* parse, check major event type and add listener accordingly */
400   parse_event_type (&etype, event_name);
401   ls->event_type_quark = etype.minor;
402   ls->event_type_cat = etype.type_cat;
403
404   list = get_listener_list (registry, etype.type_cat);
405
406   if (list)
407     {
408       *list = g_list_prepend (*list, ls);
409
410       if (etype.type_cat == ETYPE_TOOLKIT)
411         {
412           register_with_toolkits (registry, &etype, ev);
413         }
414     }
415   else
416     {
417       spi_listener_struct_free (ls, ev);
418     }
419 }
420
421 static SpiReEntrantContinue
422 remove_listener_cb (GList * const *list, gpointer user_data)
423 {
424   SpiListenerStruct *ls = (SpiListenerStruct *) (*list)->data;
425   CORBA_Environment  ev;
426   Accessibility_EventListener listener = user_data;
427
428   CORBA_exception_init (&ev);
429         
430   if (CORBA_Object_is_equivalent (ls->listener, listener, &ev))
431     {
432        spi_re_entrant_list_delete_link (list);
433        spi_listener_struct_free (ls, &ev);
434     }
435
436   CORBA_exception_free (&ev);
437
438   return SPI_RE_ENTRANT_CONTINUE;
439 }
440
441 /*
442  * CORBA Accessibility::Registry::deregisterGlobalEventListenerAll method implementation
443  */
444 static void
445 impl_accessibility_registry_deregister_global_event_listener_all (
446         PortableServer_Servant      servant,
447         Accessibility_EventListener listener,
448         CORBA_Environment          *ev)
449 {
450   int i;
451   GList **lists[3];
452   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
453
454   lists[0] = &registry->object_listeners;
455   lists[1] = &registry->window_listeners;
456   lists[2] = &registry->toolkit_listeners;
457
458   for (i = 0; i < sizeof (lists) / sizeof (lists[0]); i++)
459     {
460       spi_re_entrant_list_foreach (lists [i], remove_listener_cb, listener);
461     }
462 }
463
464
465 /*
466  * CORBA Accessibility::Registry::deregisterGlobalEventListener method implementation
467  */
468 static void
469 impl_accessibility_registry_deregister_global_event_listener (
470         PortableServer_Servant      servant,
471         Accessibility_EventListener listener,
472         const CORBA_char           *event_name,
473         CORBA_Environment          *ev)
474 {
475   SpiRegistry    *registry;
476   EventTypeStruct etype;
477
478   registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
479
480   parse_event_type (&etype, (char *) event_name);
481
482   spi_re_entrant_list_foreach (get_listener_list (registry, etype.type_cat),
483                                 remove_listener_cb, listener);
484 }
485
486
487 /**
488  * getDesktopCount:
489  * return values: a short integer indicating the current number of
490  * @Desktops.
491  *
492  * Get the current number of desktops.
493  *
494  **/
495 static short
496 impl_accessibility_registry_get_desktop_count (PortableServer_Servant servant,
497                                                CORBA_Environment * ev)
498 {
499   /* TODO: implement support for multiple virtual desktops */
500   CORBA_short n_desktops;
501   n_desktops = (CORBA_short) 1;
502   return n_desktops;
503 }
504
505
506 /**
507  * getDesktop:
508  * @n: the index of the requested @Desktop.
509  * return values: a reference to the requested @Desktop.
510  *
511  * Get the nth accessible desktop.
512  *
513  **/
514 static Accessibility_Desktop
515 impl_accessibility_registry_get_desktop (PortableServer_Servant servant,
516                                          const CORBA_short n,
517                                          CORBA_Environment * ev)
518 {
519   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
520
521   /* TODO: implement support for multiple virtual desktops */
522   if (n == 0)
523     {
524       return (Accessibility_Desktop)
525         bonobo_object_dup_ref (BONOBO_OBJREF (registry->desktop), ev);
526     }
527   else
528     {
529       return (Accessibility_Desktop) CORBA_OBJECT_NIL;
530     }
531 }
532
533
534 /**
535  * getDesktopList:
536  * return values: a sequence containing references to
537  * the @Desktops.
538  *
539  * Get a list of accessible desktops.
540  *
541  **/
542 static Accessibility_DesktopSeq *
543 impl_accessibility_registry_get_desktop_list (PortableServer_Servant servant,
544                                               CORBA_Environment * ev)
545 {
546   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
547   Accessibility_DesktopSeq *desktops;
548
549   desktops = Accessibility_DesktopSeq__alloc ();
550   desktops->_length = desktops->_maximum = 1;
551   desktops->_buffer = Accessibility_DesktopSeq_allocbuf (desktops->_length);
552   desktops->_buffer [0] = bonobo_object_dup_ref (BONOBO_OBJREF (registry->desktop), ev);
553
554   return desktops;
555 }
556
557
558 static Accessibility_DeviceEventController
559 impl_accessibility_registry_get_device_event_controller (PortableServer_Servant servant,
560                                                          CORBA_Environment     *ev)
561 {
562   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
563
564   if (!registry->de_controller)
565     {
566       registry->de_controller = spi_device_event_controller_new (registry);
567     }
568
569   return bonobo_object_dup_ref (BONOBO_OBJREF (registry->de_controller), ev);
570 }
571
572 typedef struct {
573   CORBA_Environment  *ev;
574   Bonobo_Unknown      source;
575   EventTypeStruct     etype;
576   Accessibility_Event e_out;
577 } NotifyContext;
578
579 static SpiReEntrantContinue
580 notify_listeners_cb (GList * const *list, gpointer user_data)
581 {
582   SpiListenerStruct *ls;
583   NotifyContext     *ctx = user_data;
584 #ifdef SPI_DEBUG
585   CORBA_string       s;
586 #endif
587
588   ls = (*list)->data;
589
590 #ifdef SPI_LISTENER_DEBUG
591   fprintf (stderr, "event quarks: %lx %lx %lx\n", ls->event_type_quark, ctx->etype.major, ctx->etype.minor);
592   fprintf (stderr, "event name: %s\n", ctx->etype.event_name);
593 #endif
594
595   if ((ls->event_type_quark == ctx->etype.major) ||
596       (ls->event_type_quark == ctx->etype.minor))
597     {
598 #ifdef SPI_DEBUG
599       fprintf (stderr, "notifying listener %d\n", 0);
600 /* g_list_index (list, l->data)); */
601       s = Accessibility_Accessible__get_name (ctx->source, ctx->ev);
602       fprintf (stderr, "event source name %s\n", s);
603       CORBA_free (s);
604 #endif
605       
606       ctx->e_out.source = CORBA_Object_duplicate (ctx->source, ctx->ev);
607       if (BONOBO_EX (ctx->ev))
608         {
609           return SPI_RE_ENTRANT_CONTINUE;
610         }
611       
612       if ((*list) && (*list)->data == ls)
613         {
614           Accessibility_EventListener_notifyEvent (
615             (Accessibility_EventListener) ls->listener, &ctx->e_out, ctx->ev);
616           if (ctx->ev->_major != CORBA_NO_EXCEPTION)
617             {
618               DBG (1, g_warning ("Accessibility app error: exception during "
619                         "event notification: %s\n",
620                         CORBA_exception_id (ctx->ev)));
621               if (ctx->ev->_major == CORBA_SYSTEM_EXCEPTION)
622                       CORBA_exception_init (ctx->ev);
623               /* clear system exception on notify, it means listener is dead but
624                * that's no concern of the event source :-) */
625             }
626         }
627       else /* dup re-entered */
628         {
629           CORBA_Object_release (ctx->e_out.source, ctx->ev);
630         }
631     }  
632
633   return SPI_RE_ENTRANT_CONTINUE;
634 }
635
636 static void
637 impl_registry_notify_event (PortableServer_Servant     servant,
638                             const Accessibility_Event *e,
639                             CORBA_Environment         *ev)
640 {
641   SpiRegistry  *registry;
642   GList       **list;
643   NotifyContext ctx;
644
645   registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
646
647   parse_event_type (&ctx.etype, e->type);
648
649   list = get_listener_list (registry, ctx.etype.type_cat);
650
651   if (list && *list)
652     {
653       ctx.ev = ev;
654       ctx.e_out = *e;
655       CORBA_any__copy (&ctx.e_out.any_data, &e->any_data);
656       ctx.source = e->source;
657       spi_re_entrant_list_foreach (list, notify_listeners_cb, &ctx);
658     }
659 }
660
661 static void
662 spi_registry_class_init (SpiRegistryClass *klass)
663 {
664   GObjectClass * object_class = (GObjectClass *) klass;
665   POA_Accessibility_Registry__epv *epv = &klass->epv;
666
667   spi_registry_parent_class = g_type_class_ref (SPI_LISTENER_TYPE);
668   
669   object_class->finalize = spi_registry_object_finalize;
670
671   klass->parent_class.epv.notifyEvent   = impl_registry_notify_event;
672   
673   epv->registerApplication              = impl_accessibility_registry_register_application;
674   epv->deregisterApplication            = impl_accessibility_registry_deregister_application;
675   epv->registerGlobalEventListener      = impl_accessibility_registry_register_global_event_listener;
676   epv->deregisterGlobalEventListener    = impl_accessibility_registry_deregister_global_event_listener;
677   epv->deregisterGlobalEventListenerAll = impl_accessibility_registry_deregister_global_event_listener_all;
678   epv->getDeviceEventController         = impl_accessibility_registry_get_device_event_controller;
679   epv->getDesktopCount                  = impl_accessibility_registry_get_desktop_count;
680   epv->getDesktop                       = impl_accessibility_registry_get_desktop;
681   epv->getDesktopList                   = impl_accessibility_registry_get_desktop_list;
682 }
683
684 static void
685 spi_registry_init (SpiRegistry *registry)
686 {
687   spi_registry_set_debug (g_getenv ("AT_SPI_DEBUG"));
688   registry->object_listeners = NULL;
689   registry->window_listeners = NULL;
690   registry->toolkit_listeners = NULL;
691   registry->desktop = spi_desktop_new ();
692   /* Register callback notification for application addition and removal */
693   g_signal_connect (G_OBJECT (registry->desktop),
694                     "application_added",
695                     G_CALLBACK (desktop_add_application),
696                     registry);
697
698   g_signal_connect (G_OBJECT (registry->desktop),
699                     "application_removed",
700                     G_CALLBACK (desktop_remove_application),
701                     registry);
702
703   registry->de_controller = spi_device_event_controller_new (registry);
704 }
705
706 BONOBO_TYPE_FUNC_FULL (SpiRegistry,
707                        Accessibility_Registry,
708                        PARENT_TYPE,
709                        spi_registry);
710
711 SpiRegistry *
712 spi_registry_new (void)
713 {
714   SpiRegistry *retval = g_object_new (SPI_REGISTRY_TYPE, NULL);
715   bonobo_object_set_immortal (BONOBO_OBJECT (retval), TRUE);
716   return retval;
717 }