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