Started fixing IDL docs.
[platform/core/uifw/at-spi2-atk.git] / libspi / deviceeventcontroller.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 /* deviceeventcontroler.c: implement the DeviceEventController interface */
24
25 #include <config.h>
26
27 #ifdef SPI_DEBUG
28 #  include <stdio.h>
29 #endif
30
31 #include <X11/Xlib.h>
32 #include <X11/extensions/XTest.h>
33 #include <gdk/gdkx.h> /* TODO: hide dependency (wrap in single porting file) */
34 #include <gdk/gdkwindow.h>
35
36 #include <libspi/deviceeventcontroller.h>
37
38 /* Our parent Gtk object type */
39 #define PARENT_TYPE BONOBO_TYPE_OBJECT
40
41 /* A pointer to our parent object class */
42 static GObjectClass *spi_device_event_controller_parent_class;
43
44 static gboolean kbd_registered = FALSE;
45
46 static Display *display;
47
48 static Window root_window;
49
50 typedef enum {
51         SPI_DEVICE_TYPE_KBD,
52         SPI_DEVICE_TYPE_MOUSE,
53         SPI_DEVICE_TYPE_LAST_DEFINED
54 } SpiDeviceTypeCategory;
55
56 struct _DEControllerListener {
57         CORBA_Object          object;
58         SpiDeviceTypeCategory type;
59 };
60
61 typedef struct _DEControllerListener DEControllerListener;
62
63 struct _DEControllerKeyListener {
64         DEControllerListener listener;
65         Accessibility_KeySet *keys;
66         Accessibility_ControllerEventMask *mask;
67         Accessibility_KeyEventTypeSeq *typeseq;
68         gboolean is_system_global;      
69 };
70
71 typedef struct _DEControllerKeyListener DEControllerKeyListener;
72
73 static gboolean _controller_register_with_devices (SpiDeviceEventController *controller);
74 static gboolean _controller_grab_keyboard (SpiDeviceEventController *controller);
75
76 static void controller_register_device_listener (SpiDeviceEventController *controller,
77                                                  DEControllerListener *l,
78                                                  CORBA_Environment *ev);
79
80 /*
81  * Private methods
82  */
83
84 static gint
85 _compare_corba_objects (gconstpointer p1, gconstpointer p2)
86 {
87   CORBA_Environment ev;
88   gint retval;
89   retval = !CORBA_Object_is_equivalent ((CORBA_Object) p1, (CORBA_Object) p2, &ev);
90
91 #ifdef SPI_DEBUG
92   fprintf (stderr, "comparing %p to %p; result %d\n",
93            p1, p2,
94            retval);
95 #endif
96   return retval;  
97 }
98
99 static gint
100 _compare_listeners (gconstpointer p1, gconstpointer p2)
101 {
102   DEControllerListener *l1 = (DEControllerListener *) p1;       
103   DEControllerListener *l2 = (DEControllerListener *) p2;       
104   return _compare_corba_objects (l1->object, l2->object);
105 }
106
107 static gint
108 _eventmask_compare_value (gconstpointer p1, gconstpointer p2)
109 {
110     long d;
111     if (!p1 || !p2)
112         return (gint) (p1?1:(p2?-1:0));
113     else
114         d = ((long)((Accessibility_ControllerEventMask*)p2)->value) -
115                 ((long)((Accessibility_ControllerEventMask*)p1)->value);
116     return (gint) d;
117 }
118
119 static DEControllerKeyListener *
120 dec_key_listener_new (CORBA_Object l,
121                       const Accessibility_KeySet *keys,
122                       const Accessibility_ControllerEventMask *mask,
123                       const Accessibility_KeyEventTypeSeq *typeseq,
124                       const CORBA_boolean is_system_global,
125                       CORBA_Environment *ev)
126 {
127   DEControllerKeyListener *key_listener = g_new0 (DEControllerKeyListener, 1);
128   key_listener->listener.object = CORBA_Object_duplicate (l, ev);
129   key_listener->listener.type = SPI_DEVICE_TYPE_KBD;
130   key_listener->keys = ORBit_copy_value (keys, TC_Accessibility_KeySet);
131   key_listener->mask = ORBit_copy_value (mask, TC_Accessibility_ControllerEventMask);
132   key_listener->is_system_global = is_system_global;
133
134   g_print ("new listener, with mask %x, is_global %d, keys %p\n",
135            (unsigned int) key_listener->mask->value,
136            (int) key_listener->is_system_global,
137            (void *) key_listener->keys);
138   
139   return key_listener;  
140 }
141
142 static void
143 controller_register_device_listener (SpiDeviceEventController *controller,
144                                      DEControllerListener *listener,
145                                      CORBA_Environment *ev)
146 {
147   Accessibility_ControllerEventMask *mask_ptr = NULL;
148   DEControllerKeyListener *key_listener;
149   
150   switch (listener->type) {
151   case SPI_DEVICE_TYPE_KBD:
152       key_listener = (DEControllerKeyListener *) listener;        
153       controller->key_listeners = g_list_append (controller->key_listeners, key_listener);
154       
155       mask_ptr = (Accessibility_ControllerEventMask *)
156               g_list_find_custom (controller->keymask_list, (gpointer) key_listener->mask,
157                                   _eventmask_compare_value);
158       if (mask_ptr)
159               ++(mask_ptr->refcount);
160       else
161       {
162               if (key_listener->mask->refcount != (CORBA_unsigned_short) 1)
163                       fprintf (stderr, "mask initial refcount is not 1!\n");
164               if (key_listener->mask->value > (CORBA_unsigned_long) 2048)
165                       fprintf (stderr, "mask value looks invalid (%lu)\n",
166                                (unsigned long) key_listener->mask->value);
167               else
168                       fprintf (stderr, "appending mask with val=%lu\n",
169                                (unsigned long) key_listener->mask->value);
170               mask_ptr = Accessibility_ControllerEventMask__alloc();
171               mask_ptr->value = key_listener->mask->value;
172               mask_ptr->refcount = (CORBA_unsigned_short) 1;
173               controller->keymask_list = g_list_append (controller->keymask_list,
174                                                         (gpointer) mask_ptr);
175       }
176       break;
177   case SPI_DEVICE_TYPE_MOUSE:
178 /*    controller->mouse_listeners = g_list_append (controller->mouse_listeners,
179                                                    CORBA_Object_duplicate (l, ev));*/
180
181 /* possibly this interface should NOT be used for mouse events ? */
182       break;
183   }
184 }
185
186 static void
187 controller_deregister_device_listener (SpiDeviceEventController *controller,
188                                        DEControllerListener *listener,
189                                        CORBA_Environment *ev)
190 {
191   Accessibility_ControllerEventMask *mask_ptr;
192   DEControllerKeyListener *key_listener;
193   GList *list_ptr;
194   switch (listener->type) {
195   case SPI_DEVICE_TYPE_KBD:
196       key_listener = (DEControllerKeyListener *) listener;
197       list_ptr = g_list_find_custom (controller->key_listeners, listener, _compare_listeners);
198       /* TODO: need a different custom compare func */
199       if (list_ptr)
200           controller->key_listeners = g_list_remove (controller->key_listeners, list_ptr);
201       list_ptr = (GList *)
202                   g_list_find_custom (controller->keymask_list, (gpointer) key_listener->mask,
203                                      _eventmask_compare_value);
204       if (list_ptr)
205         {
206           mask_ptr = (Accessibility_ControllerEventMask *) list_ptr->data;
207           if (mask_ptr)
208               --mask_ptr->refcount;
209           if (!mask_ptr->refcount)
210             {
211               controller->keymask_list =
212                       g_list_remove (controller->keymask_list, mask_ptr);
213               ;  /* TODO: release any key grabs that are in place for this key mask */
214             }
215         }
216       break;
217   case SPI_DEVICE_TYPE_MOUSE:
218 /*    controller->mouse_listeners = g_list_append (controller->mouse_listeners,
219                                                    CORBA_Object_duplicate (l, ev));*/
220
221 /* possibly this interface should NOT be used for mouse events ? */
222       break;
223   }
224 }
225
226 static gboolean
227 _controller_register_with_devices (SpiDeviceEventController *controller)
228 {
229   gboolean retval = FALSE;
230
231   /* calls to device-specific implementations and routines go here */
232   /* register with: keyboard hardware code handler */
233   /* register with: (translated) keystroke handler */
234 #ifdef SPI_DEBUG
235   fprintf (stderr, "About to request events on window %ld of display %p\n",
236            (unsigned long) GDK_ROOT_WINDOW(), GDK_DISPLAY());
237 #endif
238   /* We must open a new connection to the server to avoid clashing with the GDK event loop */
239   display = XOpenDisplay (g_getenv ("DISPLAY"));
240   root_window = DefaultRootWindow (display);            
241   XSelectInput (display,
242                 root_window,
243                 KeyPressMask | KeyReleaseMask);
244   /* register with: mouse hardware device handler? */
245   /* register with: mouse event handler */
246   return retval;
247 }
248
249
250 static gboolean
251 key_event_matches_listener (Accessibility_KeyStroke *key_event,
252                             DEControllerKeyListener *listener,
253                             CORBA_boolean is_system_global)
254 {
255   g_print ("mask=%x, listener mask= %x\n", (unsigned int) key_event->modifiers,
256            (unsigned int) (listener->mask->value & 0xFFFF));    
257   if ((key_event->modifiers == (CORBA_unsigned_short) (listener->mask->value && 0xFFFF)) &&
258       ((listener->keys == NULL) || (1)) && /* in keyset seq */
259       (1) && /* in event type seq */
260       (is_system_global == listener->is_system_global))
261     {
262       return TRUE;
263     }
264   else
265     return FALSE;
266 }
267
268 static gboolean
269 notify_keylisteners (GList *key_listeners,
270                      Accessibility_KeyStroke *key_event,
271                      CORBA_boolean is_system_global,
272                      CORBA_Environment *ev)
273 {
274   int i, n_listeners = g_list_length (key_listeners);
275   gboolean is_consumed = FALSE;
276
277   for (i=0; i<n_listeners && !is_consumed; ++i)
278     {
279       Accessibility_KeystrokeListener ls;
280       DEControllerKeyListener *key_listener = (DEControllerKeyListener *)
281             g_list_nth_data (key_listeners, i);
282       ls = (Accessibility_KeystrokeListener) key_listener->listener.object;
283       if (key_event_matches_listener (key_event, key_listener, is_system_global))
284         {
285           if (!CORBA_Object_is_nil(ls, ev))
286             {
287               is_consumed = Accessibility_KeystrokeListener_keyEvent (ls, key_event, ev);
288             }           
289         }
290       else g_print ("no match for listener %d\n", i);
291     }
292   return is_consumed;
293 }
294
295 static gboolean
296 _check_key_event (SpiDeviceEventController *controller)
297 {
298         static gboolean initialized = FALSE;
299         XEvent *x_event = g_new0 (XEvent, 1);
300         XKeyEvent *x_key_event;
301         KeySym keysym;
302         gboolean is_consumed = FALSE;
303         Accessibility_KeyStroke key_event;
304         static CORBA_Environment ev;
305
306         if (!initialized)
307         {
308           initialized = TRUE;
309           CORBA_exception_init (&ev);
310         }
311
312         while (XPending(display))
313           {
314             XNextEvent (display, x_event);
315             if (XFilterEvent (x_event, None)) continue;   
316             if (x_event->type == KeyPress)
317               {
318                 x_key_event = (XKeyEvent *)x_event;
319                 keysym = XLookupKeysym (x_key_event, 0);
320                 key_event.keyID = (CORBA_long)(keysym);
321                 key_event.keycode = (CORBA_short) x_key_event->keycode;
322                 key_event.type = Accessibility_KEY_PRESSED;
323                 key_event.modifiers = (CORBA_unsigned_short)(x_key_event->state);
324 #ifdef SPI_KEYEVENT_DEBUG
325             fprintf (stderr,
326                      "Key %lu pressed (%c), modifiers %d\n",
327                      (unsigned long) keysym,
328                      keysym ? (int) keysym : '*',
329                      (int) x_key_event->state);
330 #endif
331 #ifdef SPI_DEBUG
332             fprintf (stderr, "%s%c",
333                      (x_key_event->state & Mod1Mask)?"Alt-":"",
334                      ((x_key_event->state & ShiftMask)^(x_key_event->state & LockMask))?
335                      g_ascii_toupper (keysym) : g_ascii_tolower (keysym));
336 #endif /* SPI_DEBUG */
337               }
338             else
339             {
340 #ifdef SPI_KEYEVENT_DEBUG
341                     fprintf (stderr, "other event, type %d\n", (int) x_event->type);
342 #endif
343             }
344             /* relay to listeners, and decide whether to consume it or not */
345             is_consumed = notify_keylisteners (controller->key_listeners, &key_event, CORBA_TRUE, &ev);
346
347             if (is_consumed)
348             {
349               XAllowEvents (display, AsyncKeyboard, CurrentTime);
350             }
351             else
352             {
353               XAllowEvents (display, ReplayKeyboard, CurrentTime);
354             }
355           }
356         XUngrabKey (display, AnyKey, AnyModifier, root_window);
357         return _controller_grab_keyboard (controller);
358 }
359
360 static gboolean
361 _controller_grab_keyboard (SpiDeviceEventController *controller)
362 {
363         GList *maskList = controller->keymask_list;
364         int i;
365         int last_mask;
366         last_mask = g_list_length (maskList);
367
368 /*
369  * masks known to work with default RH 7.1: 
370  * 0 (no mods), LockMask, Mod1Mask, Mod2Mask, ShiftMask,
371  * ShiftMask|LockMask, Mod1Mask|LockMask, Mod2Mask|LockMask,
372  * ShiftMask|Mod1Mask, ShiftMask|Mod2Mask, Mod1Mask|Mod2Mask,
373  * ShiftMask|LockMask|Mod1Mask, ShiftMask|LockMask|Mod2Mask,
374  *
375  * ControlMask grabs are broken, must be in use already
376  */
377         
378         for (i=0; i < last_mask; ++i)
379         {
380                 Accessibility_ControllerEventMask *mask
381                         = (Accessibility_ControllerEventMask *)g_list_nth_data (maskList, i);
382                 unsigned long maskVal = 0xFFFFFFFF;
383                 if (mask) maskVal = (unsigned long) mask->value;
384 #ifdef SPI_KEYEVENT_DEBUG
385                 fprintf (stderr, "mask=%lx\n", maskVal);
386 #endif
387                 if (!(maskVal & ControlMask))
388                 {
389                         XGrabKey (display,
390                                   AnyKey,
391                                   maskVal,
392                                   root_window,
393                                   True,
394                                   GrabModeAsync,
395                                   GrabModeAsync);
396                         /* TODO: check call for errors and return FALSE if error occurs */
397                 } else {
398                         return FALSE; /* can't do control key yet */
399                 }
400         }
401         return TRUE;
402 }
403
404 /*
405  * Implemented GObject::finalize
406  */
407 static void
408 spi_device_event_controller_object_finalize (GObject *object)
409 {
410
411 #ifdef SPI_DEBUG
412         fprintf(stderr, "spi_device_event_controller_object_finalize called\n");
413 #endif
414         spi_device_event_controller_parent_class->finalize (object);
415 }
416
417 /*
418  * CORBA Accessibility::DeviceEventController::registerKeystrokeListener
419  *     method implementation
420  */
421 static void
422 impl_register_keystroke_listener (PortableServer_Servant     servant,
423                                   const Accessibility_KeystrokeListener l,
424                                   const Accessibility_KeySet *keys,
425                                   const Accessibility_ControllerEventMask *mask,
426                                   const Accessibility_KeyEventTypeSeq *type,
427                                   const CORBA_boolean is_system_global,
428                                   CORBA_Environment         *ev)
429 {
430         SpiDeviceEventController *controller = SPI_DEVICE_EVENT_CONTROLLER (
431                 bonobo_object_from_servant (servant));
432         DEControllerKeyListener *dec_listener;
433 #ifdef SPI_DEBUG
434         fprintf (stderr, "registering keystroke listener %p with maskVal %lu\n",
435                  (void *) l, (unsigned long) mask->value);
436 #endif
437         dec_listener = dec_key_listener_new (l, keys, mask, type, is_system_global, ev);
438         controller_register_device_listener (controller, (DEControllerListener *) dec_listener, ev);
439 }
440
441 /*
442  * CORBA Accessibility::DeviceEventController::deregisterKeystrokeListener
443  *     method implementation
444  */
445 static void
446 impl_deregister_keystroke_listener (PortableServer_Servant     servant,
447                                     const Accessibility_KeystrokeListener l,
448                                     const Accessibility_KeySet *keys,
449                                     const Accessibility_ControllerEventMask *mask,
450                                     const Accessibility_KeyEventTypeSeq *type,
451                                     const CORBA_boolean is_system_global,
452                                     CORBA_Environment         *ev)
453 {
454         SpiDeviceEventController *controller = SPI_DEVICE_EVENT_CONTROLLER (
455                 bonobo_object_from_servant (servant));
456         DEControllerKeyListener *key_listener = dec_key_listener_new (l,
457                                                                       keys,
458                                                                       mask,
459                                                                       type,
460                                                                       is_system_global,
461                                                                       ev);
462 #ifdef SPI_DEBUG
463         fprintf (stderr, "deregistering keystroke listener %p with maskVal %lu\n",
464                  (void *) l, (unsigned long) mask->value);
465 #endif
466         controller_deregister_device_listener(controller,
467                                               (DEControllerListener *) key_listener,
468                                               ev);
469 }
470
471 /*
472  * CORBA Accessibility::DeviceEventController::registerMouseListener
473  *     method implementation
474  */
475 /*
476 static void
477 impl_register_mouse_listener (PortableServer_Servant     servant,
478                               const Accessibility_MouseListener *l,
479                               CORBA_Environment         *ev)
480 {
481         SpiDeviceEventController *controller = SPI_DEVICE_EVENT_CONTROLLER (
482                 bonobo_object_from_servant (servant));
483 #ifdef SPI_DEBUG
484         fprintf (stderr, "registering mouse listener %p\n", l);
485 #endif
486         controller_register_device_listener(controller, DEVICE_TYPE_MOUSE, l, keys, mask, ev);
487 }
488 */
489
490 static KeyCode
491 keycode_for_keysym (long keysym)
492 {
493   return XKeysymToKeycode (display, (KeySym) keysym);
494 }
495
496 /*
497  * CORBA Accessibility::DeviceEventController::registerKeystrokeListener
498  *     method implementation
499  */
500 static void
501 impl_generate_key_event (PortableServer_Servant     servant,
502                          const CORBA_long           keycode,
503                          const Accessibility_KeySynthType synth_type,
504                          CORBA_Environment          *ev)
505 {
506         long key_synth_code;
507 #ifdef SPI_DEBUG
508         fprintf (stderr, "synthesizing keystroke %ld\n", (long) keycode);
509 #endif
510         /* TODO: hide/wrap/remove X dependency */
511
512         /* TODO: be accessX-savvy so that keyrelease occurs after sufficient timeout */
513         
514         /*
515          * TODO: when initializing, query for XTest extension before using,
516          * and fall back to XSendEvent() if XTest is not available.
517          */
518         
519         switch (synth_type)
520         {
521         case Accessibility_KEY_PRESS:
522                 XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) keycode, True, CurrentTime);
523                 break;
524         case Accessibility_KEY_PRESSRELEASE:
525                 XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) keycode, True, CurrentTime);
526         case Accessibility_KEY_RELEASE:
527                 XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) keycode, False, CurrentTime);
528                 break;
529         case Accessibility_KEY_SYM:
530                 key_synth_code = keycode_for_keysym (keycode);
531                 XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) key_synth_code, True, CurrentTime);
532                 XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) key_synth_code, False, CurrentTime);
533                 break;
534         }
535 }
536
537 /* Accessibility::DeviceEventController::generateMouseEvent */
538 static void
539 impl_generate_mouse_event (PortableServer_Servant servant,
540                            const CORBA_long       x,
541                            const CORBA_long       y,
542                            const CORBA_char      *eventName,
543                            CORBA_Environment     *ev)
544 {
545 #ifdef SPI_DEBUG
546   fprintf (stderr, "generating mouse %s event at %ld, %ld\n", eventName, x, y);
547 #endif
548 }
549
550 /* Accessibility::DeviceEventController::notifyListenersSync */
551 static CORBA_boolean
552 impl_notify_listeners_sync(PortableServer_Servant     servant,
553                            const Accessibility_DeviceEvent *event,
554                            CORBA_Environment         *ev)
555 {
556   SpiDeviceEventController *controller = SPI_DEVICE_EVENT_CONTROLLER (
557                                          bonobo_object_from_servant (servant));
558   Accessibility_KeyStroke *key_event = (Accessibility_KeyStroke *) event;       
559 #ifdef SPI_DEBUG
560   g_print ("notifying listeners synchronously: controller %x, event id %d\n",
561            (void *) controller, (int) event->eventID);
562 #endif
563   return (notify_keylisteners (controller->key_listeners, key_event, CORBA_FALSE, ev) ?
564           CORBA_TRUE : CORBA_FALSE); 
565 }
566
567 /* Accessibility::DeviceEventController::notifyListenersAsync */
568 static void
569 impl_notify_listeners_async (PortableServer_Servant     servant,
570                              const Accessibility_DeviceEvent *event,
571                              CORBA_Environment         *ev)
572 {
573   SpiDeviceEventController *controller = SPI_DEVICE_EVENT_CONTROLLER(
574                                          bonobo_object_from_servant (servant));
575   Accessibility_KeyStroke *key_event = (Accessibility_KeyStroke *) event;       
576 #ifdef SPI_DEBUG
577   fprintf (stderr, "notifying listeners asynchronously\n");
578 #endif
579   notify_keylisteners (controller->key_listeners, key_event, CORBA_FALSE, ev); 
580 }
581
582 static void
583 spi_device_event_controller_class_init (SpiDeviceEventControllerClass *klass)
584 {
585         GObjectClass * object_class = (GObjectClass *) klass;
586         POA_Accessibility_DeviceEventController__epv *epv = &klass->epv;
587         spi_device_event_controller_parent_class = g_type_class_peek_parent (klass);
588
589         object_class->finalize = spi_device_event_controller_object_finalize;
590
591         epv->registerKeystrokeListener = impl_register_keystroke_listener;
592         epv->deregisterKeystrokeListener = impl_deregister_keystroke_listener;
593 /*        epv->registerMouseListener = impl_register_mouse_listener; */
594         epv->generateKeyEvent = impl_generate_key_event;
595         epv->generateMouseEvent = impl_generate_mouse_event;
596         epv->notifyListenersSync = impl_notify_listeners_sync;
597         epv->notifyListenersAsync = impl_notify_listeners_async;
598         klass->check_key_event = _check_key_event;
599 }
600
601 static void
602 spi_device_event_controller_init (SpiDeviceEventController *device_event_controller)
603 {
604   device_event_controller->key_listeners = NULL;
605   device_event_controller->mouse_listeners = NULL;
606   device_event_controller->keymask_list = NULL;
607   kbd_registered = _controller_register_with_devices (device_event_controller);
608 }
609
610 gboolean
611 spi_device_event_controller_check_key_event (SpiDeviceEventController *controller)
612 {
613         SpiDeviceEventControllerClass *klass = SPI_DEVICE_EVENT_CONTROLLER_GET_CLASS (controller);
614         if (klass->check_key_event)
615                 return (klass->check_key_event) (controller);
616         return FALSE;
617 }
618
619 SpiDeviceEventController *
620 spi_device_event_controller_new (void *registryp)
621 {
622   BonoboObject *registry = (BonoboObject *) registryp;  
623   SpiDeviceEventController *retval = g_object_new (
624           SPI_DEVICE_EVENT_CONTROLLER_TYPE, NULL);
625   retval->registry = registry;
626   bonobo_object_ref (registry);
627   return retval;
628 }
629
630 BONOBO_TYPE_FUNC_FULL (SpiDeviceEventController,
631                        Accessibility_DeviceEventController,
632                        PARENT_TYPE,
633                        spi_device_event_controller);
634