2001-11-13 Michael Meeks <michael@ximian.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 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 /*
24  * registry.c: the main accessibility service registry implementation
25  */
26
27 #ifdef SPI_DEBUG
28 #include <stdio.h>
29 #endif
30 #include <config.h>
31 #include <bonobo/Bonobo.h>
32
33 /*
34  * This pulls the CORBA definitions for the "Accessibility::Registry" server
35  */
36 #include <libspi/Accessibility.h>
37
38 /*
39  * We'd like to replace the dependance on X-isms with a wrapper layer,
40  * to the extent that it can't be done with pure GDK.
41  * Anyone want to help?
42  */
43 #include <X11/Xlib.h>
44 #include <gdk/gdkx.h>
45
46 /*
47  * This pulls the definition for the BonoboObject (GType)
48  */
49 #include "registry.h"
50
51 /*
52  * Our parent GObject type
53  */
54 #define PARENT_TYPE SPI_LISTENER_TYPE
55
56 /*
57  * A pointer to our parent object class
58  */
59 static SpiListenerClass *spi_registry_parent_class;
60
61 typedef enum {
62   ETYPE_FOCUS,
63   ETYPE_OBJECT,
64   ETYPE_PROPERTY,
65   ETYPE_WINDOW,
66   ETYPE_TOOLKIT,
67   ETYPE_LAST_DEFINED
68 } EventTypeCategory;
69
70 typedef struct {
71   char *event_name;
72   EventTypeCategory type_cat;
73   char * major;
74   char * minor;
75   char * detail;
76   guint hash;
77 } EventTypeStruct;
78
79 typedef struct {
80   Accessibility_EventListener listener;
81   guint event_type_hash;
82   EventTypeCategory event_type_cat;
83 } SpiListenerStruct;
84
85 /* static function prototypes */
86 static void _registry_notify_listeners ( GList *listeners,
87                                         const Accessibility_Event *e,
88                                         CORBA_Environment *ev);
89
90 static long _get_unique_id();
91
92 static gboolean _device_event_controller_hook (gpointer source);
93
94 /*
95  * Implemented GObject::finalize
96  */
97 static void
98 spi_registry_object_finalize (GObject *object)
99 {
100 /*        SpiRegistry *registry = SPI_REGISTRY (object); */
101         GObjectClass *object_class = G_OBJECT_GET_CLASS( object);
102
103         printf("spi_registry_object_finalize called\n");
104
105         object_class->finalize (object);
106 }
107
108 /**
109  * registerApplication:
110  * @application: a reference to the requesting @Application
111  * return values: void
112  *
113  * Register a new application with the accessibility broker.
114  *
115  **/
116 static void
117 impl_accessibility_registry_register_application (PortableServer_Servant servant,
118                                                   const Accessibility_Application application,
119                                                   CORBA_Environment * ev)
120 {
121   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
122
123 #ifdef SPI_DEBUG
124   fprintf (stderr, "registering app %p\n", application);
125 #endif
126   registry->desktop->applications = g_list_append (registry->desktop->applications,
127                                                    bonobo_object_dup_ref (application, ev));
128
129   /* TODO: create unique string here (with libuuid call ?) and hash ? */
130   Accessibility_Application__set_id (application, _get_unique_id(), ev);
131
132   /*
133    * TODO: change the implementation below to a WM-aware one;
134    * e.g. don't add all apps to the SpiDesktop
135    */
136 }
137
138 static gint
139 compare_corba_objects (gconstpointer p1, gconstpointer p2)
140 {
141   CORBA_Environment ev;
142   gint retval;
143
144 #ifdef SPI_DEBUG
145   fprintf (stderr, "comparing %p to %p\n",
146            p1, p2);
147 #endif
148   
149   retval = !CORBA_Object_is_equivalent ((CORBA_Object) p1, (CORBA_Object) p2, &ev);
150   return retval;  
151 }
152
153 static void
154 register_with_toolkits (SpiRegistry *spi_registry_bonobo_object, EventTypeStruct *etype, CORBA_Environment *ev)
155 {
156   gint n_desktops;
157   gint n_apps;
158   gint i, j;
159   Accessibility_Desktop desktop;
160   Accessibility_Application app;
161   Accessibility_Registry registry;
162   registry  = BONOBO_OBJREF (spi_registry_bonobo_object);
163
164   /* for each app in each desktop, call ...Application_registerToolkitEventListener */
165
166   n_desktops = Accessibility_Registry_getDesktopCount (registry, ev);
167
168   for (i=0; i<n_desktops; ++i)
169     {
170       desktop = Accessibility_Registry_getDesktop (registry, i, ev);
171       n_apps = Accessibility_Desktop__get_childCount (desktop, ev);
172       for (j=0; j<n_apps; ++j)
173         {
174           app = (Accessibility_Application) Accessibility_Desktop_getChildAtIndex (desktop,
175                                                                                    j,
176                                                                                    ev);
177           Accessibility_Application_registerToolkitEventListener (app,
178                                                                   registry,
179                                                                   CORBA_string_dup (etype->event_name),
180                                                                   
181                                                                   ev);
182         }
183     }
184 }
185
186 static gint
187 compare_listener_hash (gconstpointer p1, gconstpointer p2)
188 {
189   return (((SpiListenerStruct *)p2)->event_type_hash - ((SpiListenerStruct *)p1)->event_type_hash);
190 }
191
192 static gint
193 compare_listener_corbaref (gconstpointer p1, gconstpointer p2)
194 {
195   return compare_corba_objects (((SpiListenerStruct *)p2)->listener,
196                                 ((SpiListenerStruct *)p1)->listener);
197 }
198
199 static void
200 parse_event_type (EventTypeStruct *etype, char *event_name)
201 {
202   guint nbytes = 0;
203   gchar **split_string;
204
205   split_string = g_strsplit(event_name, ":", 4);
206   etype->event_name = g_strndup(event_name, 255);
207
208   if (!g_ascii_strncasecmp (event_name, "focus:", 6))
209     {
210       etype->type_cat = ETYPE_FOCUS;
211     }
212   else if (!g_ascii_strncasecmp (event_name, "object:", 7))
213     {
214       etype->type_cat = ETYPE_OBJECT;
215     }
216   else if (!g_ascii_strncasecmp (event_name, "window:", 7))
217     {
218       etype->type_cat = ETYPE_WINDOW;
219     }
220   else
221     {
222       etype->type_cat = ETYPE_TOOLKIT;
223     }
224
225   if (split_string[1])
226     {
227       etype->major = split_string[1];
228       if (split_string[2])
229         {
230           etype->minor = split_string[2];
231           if (split_string[3])
232             {
233               etype->detail = split_string[3];
234               etype->hash = g_str_hash ( g_strconcat (split_string[1], split_string[2], split_string[3], NULL));
235             }
236           else
237             {
238               etype->detail = g_strdup ("");
239               etype->hash = g_str_hash ( g_strconcat (split_string[1], split_string[2], NULL));
240             }
241         }
242       else
243         {
244           etype->minor = g_strdup ("");
245           etype->hash = g_str_hash ( split_string[1]);
246         }
247     }
248   else
249     {
250       etype->major = g_strdup ("");
251       etype->minor = g_strdup ("");
252       etype->detail = g_strdup ("");
253       etype->hash = g_str_hash ("");
254     }
255
256   /* TODO: don't forget to free the strings from caller when done ! */
257 }
258
259 /**
260  * deregisterApplication:
261  * @application: a reference to the @Application
262  * to be deregistered.
263  * return values: void
264  *
265  * De-register an application previously registered with the broker.
266  *
267  **/
268 static void
269 impl_accessibility_registry_deregister_application (PortableServer_Servant servant,
270                                                     const Accessibility_Application application,
271                                                     CORBA_Environment * ev)
272 {
273   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
274   GList *list = g_list_find_custom (registry->desktop->applications, &application, compare_corba_objects);
275
276 #ifdef SPI_DEBUG
277   gint i;
278 #endif
279
280   if (list)
281     {
282 #ifdef SPI_DEBUG
283       fprintf (stderr, "deregistering application %p\n", application);
284 #endif
285       registry->desktop->applications = g_list_delete_link (registry->desktop->applications, list);
286 #ifdef SPI_DEBUG
287       fprintf (stderr, "there are now %d apps registered.\n", g_list_length (registry->desktop->applications));
288       for (i = 0; i < g_list_length (registry->desktop->applications); ++i) {
289           fprintf (stderr, "getting application %d\n", i);
290           fprintf (stderr, "object address %p\n",
291                g_list_nth_data (registry->desktop->applications, i));
292       }
293 #endif      
294     }
295   else
296     fprintf (stderr, "could not deregister application\n");
297 }
298
299 /*
300  * CORBA Accessibility::Registry::registerGlobalEventListener method implementation
301  */
302 static void
303 impl_accessibility_registry_register_global_event_listener (
304                                              PortableServer_Servant  servant,
305                                              Accessibility_EventListener listener,
306                                              const CORBA_char *event_name,
307                                              CORBA_Environment      *ev)
308 {
309   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
310   SpiListenerStruct *ls = g_malloc (sizeof (SpiListenerStruct));
311   EventTypeStruct etype;
312   gboolean is_toolkit_specific = TRUE;
313
314   fprintf(stderr, "registering for events of type %s\n", event_name);
315
316   /* parse, check major event type and add listener accordingly */
317   parse_event_type (&etype, event_name);
318   ls->event_type_hash = etype.hash;
319   ls->event_type_cat = etype.type_cat;
320
321   switch (etype.type_cat)
322     {
323     case (ETYPE_FOCUS) :
324     case (ETYPE_OBJECT) :
325     case (ETYPE_PROPERTY) :
326       ls->listener = CORBA_Object_duplicate (listener, ev);
327       registry->object_listeners =
328         g_list_append (registry->object_listeners, ls);
329       break;
330     case (ETYPE_WINDOW) :
331       /* Support for Window Manager Events is not yet implemented */
332       break;
333     case (ETYPE_TOOLKIT) :
334       ls->listener = CORBA_Object_duplicate (listener, ev);
335       registry->toolkit_listeners =
336         g_list_append (registry->toolkit_listeners, ls);
337       register_with_toolkits (registry, &etype, ev);
338       break;
339     default:
340       break;
341     }
342 }
343
344 /*
345  * CORBA Accessibility::Registry::deregisterGlobalEventListenerAll method implementation
346  */
347 static void
348 impl_accessibility_registry_deregister_global_event_listener_all (
349                                                     PortableServer_Servant  servant,
350                                                     Accessibility_EventListener listener,
351                                                     CORBA_Environment      *ev)
352 {
353   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
354   SpiListenerStruct *ls = g_malloc (sizeof (SpiListenerStruct));
355   GList *list;
356   ls->listener = listener;  
357   list = g_list_find_custom (registry->object_listeners, ls,
358                              compare_listener_corbaref);
359
360   /*
361    * TODO : de-register with toolkit if the last instance of a listener
362    *        to a particular toolkit event type has been deregistered.
363    */
364
365   while (list)
366     {
367       fprintf (stderr, "deregistering listener\n");
368       registry->object_listeners = g_list_delete_link (registry->object_listeners, list);
369       list = g_list_find_custom (registry->object_listeners, ls, compare_listener_corbaref);
370     }
371   list = g_list_find_custom (registry->toolkit_listeners, ls, compare_listener_corbaref);
372   while (list)
373     {
374       fprintf (stderr, "deregistering listener\n");
375       registry->toolkit_listeners = g_list_delete_link (registry->toolkit_listeners, list);
376       list = g_list_find_custom (registry->toolkit_listeners, ls, compare_listener_corbaref);
377     }
378 }
379
380 /*
381  * CORBA Accessibility::Registry::deregisterGlobalEventListener method implementation
382  */
383 static void
384 impl_accessibility_registry_deregister_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;
392   EventTypeStruct etype;
393   GList *list;
394   GList **listeners;
395
396   parse_event_type (&etype, event_name);
397   switch (etype.type_cat)
398     {
399     case (ETYPE_OBJECT) :
400     case (ETYPE_PROPERTY) :
401     case (ETYPE_FOCUS) :
402       listeners = &registry->object_listeners;
403       break;
404     case (ETYPE_WINDOW) :
405       /* Support for Window Manager Events is not yet implemented */
406       break;
407     case (ETYPE_TOOLKIT) :
408       listeners = &registry->toolkit_listeners;
409       break;
410     default:
411       break;
412     }
413
414   ls.event_type_hash = etype.hash;
415   list = g_list_find_custom (*listeners, &ls, compare_listener_hash);
416
417   while (list)
418     {
419       fprintf (stderr, "deregistering listener\n");
420       *listeners = g_list_delete_link (*listeners, list);
421       list = g_list_find_custom (*listeners, &ls, compare_listener_hash);
422     }
423 }
424
425
426 /**
427  * getDesktopCount:
428  * return values: a short integer indicating the current number of
429  * @Desktops.
430  *
431  * Get the current number of desktops.
432  *
433  **/
434 static short
435 impl_accessibility_registry_get_desktop_count (PortableServer_Servant servant,
436                                                CORBA_Environment * ev)
437 {
438   /* TODO: implement support for multiple virtual desktops */
439   CORBA_short n_desktops;
440   n_desktops = (CORBA_short) 1;
441   return n_desktops;
442 }
443
444 /**
445  * getDesktop:
446  * @n: the index of the requested @Desktop.
447  * return values: a reference to the requested @Desktop.
448  *
449  * Get the nth accessible desktop.
450  *
451  **/
452 static Accessibility_Desktop
453 impl_accessibility_registry_get_desktop (PortableServer_Servant servant,
454                                          const CORBA_short n,
455                                          CORBA_Environment * ev)
456 {
457   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
458
459   /* TODO: implement support for multiple virtual desktops */
460   if (n == 0)
461     {
462       return (Accessibility_Desktop)
463         CORBA_Object_duplicate (BONOBO_OBJREF (registry->desktop), ev);
464     }
465   else
466     {
467       return (Accessibility_Desktop) CORBA_OBJECT_NIL;
468     }
469 }
470
471 /**
472  * getDesktopList:
473  * return values: a sequence containing references to
474  * the @Desktops.
475  *
476  * Get a list of accessible desktops.
477  *
478  **/
479 static Accessibility_DesktopSeq *
480 impl_accessibility_registry_get_desktop_list (PortableServer_Servant servant,
481                                               CORBA_Environment * ev)
482 {
483   /* TODO: implement support for multiple virtual desktops */
484   return (Accessibility_DesktopSeq *) NULL;
485 }
486
487 static Accessibility_DeviceEventController
488 impl_accessibility_registry_get_device_event_controller (PortableServer_Servant servant,
489                                                          CORBA_Environment * ev)
490 {
491   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
492   if (!registry->device_event_controller)
493     registry->device_event_controller = g_object_new (SPI_DEVICE_EVENT_CONTROLLER_TYPE, NULL);
494   return CORBA_Object_duplicate (BONOBO_OBJREF (registry->device_event_controller), ev);
495 }
496
497 static void
498 impl_registry_notify_event (PortableServer_Servant servant,
499                             const Accessibility_Event *e,
500                             CORBA_Environment *ev)
501 {
502   SpiRegistry *registry = SPI_REGISTRY (bonobo_object_from_servant (servant));
503   EventTypeStruct etype;
504
505   parse_event_type (&etype, e->type);
506
507   switch (etype.type_cat)
508     {
509     case (ETYPE_OBJECT) :
510     case (ETYPE_PROPERTY) :
511     case (ETYPE_FOCUS) :
512       _registry_notify_listeners (registry->object_listeners, e, ev); 
513       break;
514     case (ETYPE_WINDOW) :
515       _registry_notify_listeners (registry->window_listeners, e, ev);
516       break;
517     case (ETYPE_TOOLKIT) :
518       _registry_notify_listeners (registry->toolkit_listeners, e, ev); 
519       break;
520     default:
521       break;
522     }
523   /* Accessibility_Accessible_unref (e->source, ev);*/ /* This should be here! */
524 }
525
526 static long
527 _get_unique_id ()
528 {
529   static long id = 0;
530   return ++id;
531 }
532
533 static void
534 _registry_notify_listeners ( GList *listeners,
535                             const Accessibility_Event *e,
536                             CORBA_Environment *ev)
537 {
538   int n;
539   int len;
540   SpiListenerStruct *ls;
541   EventTypeStruct etype;
542   guint minor_hash;
543   parse_event_type (&etype, e->type);
544   minor_hash = g_str_hash (g_strconcat (etype.major, etype.minor, NULL));
545   len = g_list_length (listeners);
546
547   for (n=0; n<len; ++n)
548     {
549       ls =  (SpiListenerStruct *) g_list_nth_data (listeners, n);
550 #ifdef SPI_SPI_LISTENER_DEBUG
551       fprintf(stderr, "event hashes: %lx %lx %lx\n", ls->event_type_hash, etype.hash, minor_hash);
552       fprintf(stderr, "event name: %s\n", etype.event_name);
553 #endif
554       if ((ls->event_type_hash == etype.hash) || (ls->event_type_hash == minor_hash))
555         {
556 #ifdef SPI_DEBUG
557           fprintf(stderr, "notifying listener #%d\n", n);
558           fprintf(stderr, "event source name %s\n", Accessibility_Accessible__get_name(e->source, ev));
559 #endif
560           e->source = CORBA_Object_duplicate (e->source, ev);
561           Accessibility_Accessible_ref ( e->source, ev);
562           Accessibility_EventListener_notifyEvent ((Accessibility_EventListener) ls->listener,
563                                                    e,
564                                                    ev);
565           if (ev->_major != CORBA_NO_EXCEPTION) {
566                 fprintf(stderr,
567                 ("Accessibility app error: exception during event notification: %s\n"),
568                         CORBA_exception_id(ev));
569                 exit(-1);
570           }
571         }
572     }
573 }
574
575 static gboolean _device_event_controller_hook (gpointer p)
576 {
577     SpiRegistry *registry = (SpiRegistry *)p;
578     SpiDeviceEventController *controller = registry->device_event_controller;
579     if (controller)
580         spi_device_event_controller_check_key_event (controller);
581     return TRUE;
582 }
583
584 static void
585 spi_registry_class_init (SpiRegistryClass *klass)
586 {
587         GObjectClass * object_class = (GObjectClass *) klass;
588         POA_Accessibility_Registry__epv *epv = &klass->epv;
589
590         spi_registry_parent_class = g_type_class_ref (SPI_LISTENER_TYPE);
591
592         object_class->finalize = spi_registry_object_finalize;
593
594         epv->registerApplication = impl_accessibility_registry_register_application;
595         epv->deregisterApplication = impl_accessibility_registry_deregister_application;
596         epv->registerGlobalEventListener = impl_accessibility_registry_register_global_event_listener;
597         epv->deregisterGlobalEventListener = impl_accessibility_registry_deregister_global_event_listener;
598         epv->deregisterGlobalEventListenerAll = impl_accessibility_registry_deregister_global_event_listener_all;
599         epv->getDeviceEventController = impl_accessibility_registry_get_device_event_controller;
600         epv->getDesktopCount = impl_accessibility_registry_get_desktop_count;
601         epv->getDesktop = impl_accessibility_registry_get_desktop;
602         epv->getDesktopList = impl_accessibility_registry_get_desktop_list;
603
604         ((SpiListenerClass *) klass)->epv.notifyEvent = impl_registry_notify_event;
605 }
606
607 static void
608 spi_registry_init (SpiRegistry *registry)
609 {
610   registry->object_listeners = NULL;
611   registry->window_listeners = NULL;
612   registry->toolkit_listeners = NULL;
613   registry->applications = NULL;
614   registry->desktop = spi_desktop_new();
615   registry->device_event_controller = NULL;
616   registry->kbd_event_hook = _device_event_controller_hook;
617 }
618
619 GType
620 spi_registry_get_type (void)
621 {
622         static GType type = 0;
623
624         if (!type) {
625                 static const GTypeInfo tinfo = {
626                         sizeof (SpiRegistryClass),
627                         (GBaseInitFunc) NULL,
628                         (GBaseFinalizeFunc) NULL,
629                         (GClassInitFunc) spi_registry_class_init,
630                         (GClassFinalizeFunc) NULL,
631                         NULL, /* class data */
632                         sizeof (SpiRegistry),
633                         0, /* n preallocs */
634                         (GInstanceInitFunc) spi_registry_init,
635                         NULL /* value table */
636                 };
637                 /*
638                  *   Here we use bonobo_type_unique instead of
639                  * gtk_type_unique, this auto-generates a load of
640                  * CORBA structures for us. All derived types must
641                  * use bonobo_type_unique.
642                  */
643                 type = bonobo_type_unique (
644                         PARENT_TYPE,
645                         POA_Accessibility_Registry__init,
646                         NULL,
647                         G_STRUCT_OFFSET (SpiRegistryClass, epv),
648                         &tinfo,
649                         "SpiRegistry");
650         }
651
652         return type;
653 }
654
655 SpiRegistry *
656 spi_registry_new (void)
657 {
658     SpiRegistry *retval =
659                SPI_REGISTRY (g_object_new (spi_registry_get_type (), NULL));
660     return retval;
661 }