2002-12-16 Michael Meeks <michael@ximian.com>
[platform/core/uifw/at-spi2-atk.git] / cspi / spi_main.c
1 /*
2  * AT-SPI - Assistive Technology Service Provider Interface
3  * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
4  *
5  * Copyright 2001, 2002 Sun Microsystems Inc.,
6  * Copyright 2001, 2002 Ximian, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 /*
25  *
26  * Basic SPI initialization and event loop function prototypes
27  *
28  */
29 #include <string.h>
30 #include <stdlib.h>
31 #include <cspi/spi-private.h>
32
33 #undef DEBUG_OBJECTS
34
35 static CORBA_Environment ev = { 0 };
36 static Accessibility_Registry registry = CORBA_OBJECT_NIL;
37 static GHashTable *live_refs = NULL;
38
39 static guint
40 cspi_object_hash (gconstpointer key)
41 {
42   CORBA_Object object = (CORBA_Object) key;
43
44   return CORBA_Object_hash (object, 0, &ev);
45 }
46
47 static gboolean
48 cspi_object_equal (gconstpointer a, gconstpointer b)
49 {
50   CORBA_Object objecta = (CORBA_Object) a;
51   CORBA_Object objectb = (CORBA_Object) b;
52
53   return CORBA_Object_is_equivalent (objecta, objectb, &ev);
54 }
55
56 static void
57 cspi_object_release (gpointer value)
58 {
59   Accessible *a = (Accessible *) value;
60
61 #ifdef DEBUG_OBJECTS
62   g_print ("releasing %p => %p\n", a, a->objref);
63 #endif
64
65   if (!a->on_loan)
66     {
67       cspi_release_unref (a->objref);
68     }
69
70   memset (a, 0xaa, sizeof (Accessible));
71   a->ref_count = -1;
72
73 #ifndef DEBUG_OBJECTS
74   free (a);
75 #endif
76 }
77
78 SPIBoolean
79 cspi_accessible_is_a (Accessible *accessible,
80                       const char *interface_name)
81 {
82   SPIBoolean        retval;
83   Bonobo_Unknown unknown;
84
85   if (accessible == NULL)
86     {
87       return FALSE;
88     }
89
90   unknown = Bonobo_Unknown_queryInterface (CSPI_OBJREF (accessible),
91                                            interface_name, cspi_ev ());
92
93   if (ev._major != CORBA_NO_EXCEPTION)
94     {
95       g_error ("Exception '%s' checking if is '%s'",
96                cspi_exception_get_text (),
97                interface_name);
98     }
99
100   if (unknown != CORBA_OBJECT_NIL)
101     {
102       retval = TRUE;
103       cspi_release_unref (unknown);
104     }
105   else
106     {
107       retval = FALSE;
108     }
109
110   return retval;
111 }
112
113 static GHashTable *
114 cspi_get_live_refs (void)
115 {
116   if (!live_refs) 
117     {
118       live_refs = g_hash_table_new_full (cspi_object_hash,
119                                          cspi_object_equal,
120                                          NULL,
121                                          cspi_object_release);
122     }
123   return live_refs;
124 }
125
126 CORBA_Environment *
127 cspi_ev (void)
128 {
129   CORBA_exception_init (&ev);
130   return &ev;
131 }
132
133 Accessibility_Registry
134 cspi_registry (void)
135 {
136   if (!cspi_ping (registry))
137     {
138       registry = cspi_init ();
139     }
140   return registry;
141 }
142
143 SPIBoolean
144 cspi_exception (void)
145 {
146   SPIBoolean retval;
147
148   if (ev._major != CORBA_NO_EXCEPTION)
149     {
150       CORBA_exception_free (&ev);
151       retval = TRUE;
152     }
153   else
154     {
155       retval = FALSE;
156     }
157
158   return retval;
159 }
160
161 /*
162  *   This method swallows the corba_object BonoboUnknown
163  * reference, and returns an Accessible associated with it.
164  * If the reference is loaned, it means it is only valid
165  * between a borrow / return pair.
166  */
167 static Accessible *
168 cspi_object_get_ref (CORBA_Object corba_object, gboolean on_loan)
169 {
170   Accessible *ref;
171
172   if (corba_object == CORBA_OBJECT_NIL)
173     {
174       ref = NULL;
175     }
176   else if (!cspi_check_ev ("pre method check: add"))
177     {
178       ref = NULL;
179     }
180   else
181     {
182       if ((ref = g_hash_table_lookup (cspi_get_live_refs (), corba_object)))
183         {
184           g_assert (ref->ref_count > 0);
185           ref->ref_count++;
186           if (!on_loan)
187             {
188               if (ref->on_loan) /* Convert to a permanant ref */
189                 {
190                   ref->on_loan = FALSE;
191                 }
192               else
193                 {
194                   cspi_release_unref (corba_object);
195                 }
196             }
197 #ifdef DEBUG_OBJECTS
198           g_print ("returning cached %p => %p\n", ref, ref->objref);
199 #endif
200         }
201       else
202         {
203           ref = malloc (sizeof (Accessible));
204           ref->objref = corba_object;
205           ref->ref_count = 1;
206           ref->on_loan = on_loan;
207 #ifdef DEBUG_OBJECTS
208           g_print ("allocated %p => %p\n", ref, corba_object);
209 #endif
210           g_hash_table_insert (cspi_get_live_refs (), ref->objref, ref);
211         }
212     }
213
214   return ref;
215 }
216
217 Accessible *
218 cspi_object_add (CORBA_Object corba_object)
219 {
220   return cspi_object_get_ref (corba_object, FALSE);
221 }
222
223 Accessible *
224 cspi_object_borrow (CORBA_Object corba_object)
225 {
226   return cspi_object_get_ref (corba_object, TRUE);
227 }
228
229 void
230 cspi_object_return (Accessible *accessible)
231 {
232   g_return_if_fail (accessible != NULL);
233
234   if (!accessible->on_loan ||
235       accessible->ref_count == 1)
236     {
237       cspi_object_unref (accessible);
238     }
239   else /* Convert to a permanant ref */
240     {
241       accessible->on_loan = FALSE;
242       accessible->objref = cspi_dup_ref (accessible->objref);
243       accessible->ref_count--;
244     }
245 }
246
247 Accessible *
248 cspi_object_take (CORBA_Object corba_object)
249 {
250   Accessible *accessible;
251   accessible = cspi_object_borrow (corba_object);
252
253   cspi_object_ref (accessible);
254   /* 
255    * if the remote object is dead, 
256    * cspi_object_return will throw an exception. 
257    * FIXME: what clears that exception context ever ?
258    */
259   cspi_object_return (accessible);
260   if (cspi_exception ()) 
261     {
262       cspi_object_unref (accessible);
263       accessible = NULL;
264     }
265   return accessible;
266 }
267
268 void
269 cspi_object_ref (Accessible *accessible)
270 {
271   g_return_if_fail (accessible != NULL);
272
273   accessible->ref_count++;
274 }
275
276 void
277 cspi_object_unref (Accessible *accessible)
278 {
279   if (accessible == NULL)
280     {
281       return;
282     }
283
284   if (--accessible->ref_count == 0)
285     {
286       g_hash_table_remove (cspi_get_live_refs (), accessible->objref);
287     }
288 }
289
290 static void
291 cspi_cleanup (void)
292 {
293   GHashTable *refs;
294
295   refs = live_refs;
296   live_refs = NULL;
297   if (refs)
298     {
299       g_hash_table_destroy (refs);
300     }
301
302   if (registry != CORBA_OBJECT_NIL)
303     {
304       cspi_release_unref (registry);
305       registry = CORBA_OBJECT_NIL;
306     }
307 }
308
309 static gboolean SPI_inited = FALSE;
310
311 /**
312  * SPI_init:
313  *
314  * Connects to the accessibility registry and initializes the SPI.
315  *
316  * Returns: 0 on success, otherwise an integer error code.
317  **/
318 int
319 SPI_init (void)
320 {
321   if (SPI_inited)
322     {
323       return 1;
324     }
325
326   SPI_inited = TRUE;
327
328   CORBA_exception_init (&ev);
329
330   registry = cspi_init ();
331
332   g_atexit (cspi_cleanup);
333   
334   return 0;
335 }
336
337 /**
338  * SPI_event_main:
339  *
340  * Starts/enters the main event loop for the SPI services.
341  *
342  * (NOTE: This method does not return control, it is exited via a call to
343  *  SPI_event_quit () from within an event handler).
344  *
345  **/
346 void
347 SPI_event_main (void)
348 {
349   cspi_main ();
350 }
351
352 /**
353  * SPI_event_quit:
354  *
355  * Quits the last main event loop for the SPI services,
356  * see SPI_event_main
357  **/
358 void
359 SPI_event_quit (void)
360 {
361   cspi_main_quit ();
362 }
363
364 /**
365  * SPI_eventIsReady:
366  *
367  * Checks to see if an SPI event is waiting in the event queue.
368  * Used by clients that don't wish to use SPI_event_main().
369  *
370  * Not Yet Implemented.
371  *
372  * Returns: #TRUE if an event is waiting, otherwise #FALSE.
373  *
374  **/
375 SPIBoolean
376 SPI_eventIsReady ()
377 {
378   return FALSE;
379 }
380
381 /**
382  * SPI_nextEvent:
383  * @waitForEvent: a #SPIBoolean indicating whether to block or not.
384  *
385  * Gets the next event in the SPI event queue; blocks if no event
386  * is pending and @waitForEvent is #TRUE.
387  * Used by clients that don't wish to use SPI_event_main().
388  *
389  * Not Yet Implemented.
390  *
391  * Returns: the next #AccessibleEvent in the SPI event queue.
392  **/
393 AccessibleEvent *
394 SPI_nextEvent (SPIBoolean waitForEvent)
395 {
396   return NULL;
397 }
398
399 static void
400 report_leaked_ref (gpointer key, gpointer val, gpointer user_data)
401 {
402   char *name, *role;
403   Accessible *a = (Accessible *) val;
404   
405   name = Accessible_getName (a);
406   if (cspi_exception ())
407     {
408       name = NULL;
409     }
410
411   role = Accessible_getRoleName (a);
412   if (cspi_exception ())
413     {
414       role = NULL;
415     }
416
417   fprintf (stderr, "leaked %d references to object %s, role %s %p\n",
418            a->ref_count, name ? name : "<?>", role ? role : "<?>", a);
419
420   SPI_freeString (name);
421 }
422
423
424 /**
425  * SPI_exit:
426  *
427  * Disconnects from the Accessibility Registry and releases 
428  * any floating resources. Call only once at exit.
429  *
430  * Returns: 0 if there were no leaks, otherwise non zero.
431  **/
432 int
433 SPI_exit (void)
434 {
435   int leaked;
436
437   if (!SPI_inited)
438     {
439       return 0;
440     }
441
442   SPI_inited = FALSE;
443
444   if (live_refs)
445     {
446       leaked = g_hash_table_size (live_refs);
447 #define PRINT_LEAKS
448 #ifdef PRINT_LEAKS
449       g_hash_table_foreach (live_refs, report_leaked_ref, NULL);
450 #endif
451     }
452   else
453     {
454       leaked = 0;
455     }
456
457 #ifdef DEBUG_OBJECTS
458   if (leaked)
459     {
460       fprintf (stderr, "Leaked %d SPI handles\n", leaked);
461     }
462 #endif
463
464   cspi_cleanup ();
465
466   fprintf (stderr, "bye-bye!\n");
467
468   return leaked;
469 }
470
471 /**
472  * SPI_freeString:
473  * @s: a character string returned from another at-spi call.
474  *
475  * Free a character string returned from an at-spi call.  Clients of
476  * at-spi should use this function instead of free () or g_free().
477  * A NULL string @s will be silently ignored.
478  * This API should not be used to free strings
479  * from other libraries or allocated by the client.
480  **/
481 void
482 SPI_freeString (char *s)
483 {
484   if (s)
485     {
486       CORBA_free (s);
487     }
488 }