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