]> Pileus Git - ~andy/gtk/blob - gtk/gtkimmodule.c
Use case-insensitive comparison. Fixes XIM module loading on AIX.
[~andy/gtk] / gtk / gtkimmodule.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * Themes added by The Rasterman <raster@redhat.com>
5  * 
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free
18  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20
21 /*
22  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
23  * file for a list of people on the GTK+ Team.  See the ChangeLog
24  * files for a list of changes.  These files are distributed with
25  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
26  */
27 #include <errno.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include <gmodule.h>
33 #include <pango/pango-utils.h>
34 #include "gtkimmodule.h"
35 #include "gtkimcontextsimple.h"
36 #include "gtkrc.h"
37 #include "config.h"
38 #include "gtkintl.h"
39
40 /* Do *not* include "gtkprivate.h" in this file. If you do, the
41  * correct_libdir_prefix() function below will have to move somewhere
42  * else.
43  */
44
45 #define SIMPLE_ID "gtk-im-context-simple"
46
47 typedef struct _GtkIMModule      GtkIMModule;
48 typedef struct _GtkIMModuleClass GtkIMModuleClass;
49
50 #define GTK_TYPE_IM_MODULE          (gtk_im_module_get_type ())
51 #define GTK_IM_MODULE(im_module)    (G_TYPE_CHECK_INSTANCE_CAST ((im_module), GTK_TYPE_IM_MODULE, GtkIMModule))
52 #define GTK_IS_IM_MODULE(im_module) (G_TYPE_CHECK_INSTANCE_TYPE ((im_module), GTK_TYPE_IM_MODULE))
53
54 struct _GtkIMModule
55 {
56   GTypeModule parent_instance;
57   
58   GModule *library;
59
60   void          (*list)   (const GtkIMContextInfo ***contexts,
61                            guint                    *n_contexts);
62   void          (*init)   (GTypeModule              *module);
63   void          (*exit)   (void);
64   GtkIMContext *(*create) (const gchar              *context_id);
65
66   GtkIMContextInfo **contexts;
67   guint n_contexts;
68
69   gchar *path;
70 };
71
72 struct _GtkIMModuleClass 
73 {
74   GTypeModuleClass parent_class;
75 };
76
77 GType gtk_im_module_get_type (void);
78
79 static gint n_loaded_contexts = 0;
80 static GHashTable *contexts_hash = NULL;
81 static GSList *modules_list = NULL;
82
83 static GObjectClass *parent_class = NULL;
84
85 static gboolean
86 gtk_im_module_load (GTypeModule *module)
87 {
88   GtkIMModule *im_module = GTK_IM_MODULE (module);
89   
90   im_module->library = g_module_open (im_module->path, 0);
91   if (!im_module->library)
92     {
93       g_warning (g_module_error());
94       return FALSE;
95     }
96   
97   /* extract symbols from the lib */
98   if (!g_module_symbol (im_module->library, "im_module_init",
99                         (gpointer *)&im_module->init) ||
100       !g_module_symbol (im_module->library, "im_module_exit", 
101                         (gpointer *)&im_module->exit) ||
102       !g_module_symbol (im_module->library, "im_module_list", 
103                         (gpointer *)&im_module->list) ||
104       !g_module_symbol (im_module->library, "im_module_create", 
105                         (gpointer *)&im_module->create))
106     {
107       g_warning (g_module_error());
108       g_module_close (im_module->library);
109       
110       return FALSE;
111     }
112             
113   /* call the theme's init (theme_init) function to let it */
114   /* setup anything it needs to set up. */
115   im_module->init (module);
116
117   return TRUE;
118 }
119
120 static void
121 gtk_im_module_unload (GTypeModule *module)
122 {
123   GtkIMModule *im_module = GTK_IM_MODULE (module);
124   
125   im_module->exit();
126
127   g_module_close (im_module->library);
128   im_module->library = NULL;
129
130   im_module->init = NULL;
131   im_module->exit = NULL;
132   im_module->list = NULL;
133   im_module->create = NULL;
134 }
135
136 /* This only will ever be called if an error occurs during
137  * initialization
138  */
139 static void
140 gtk_im_module_finalize (GObject *object)
141 {
142   GtkIMModule *module = GTK_IM_MODULE (object);
143
144   g_free (module->path);
145
146   parent_class->finalize (object);
147 }
148
149 static void
150 gtk_im_module_class_init (GtkIMModuleClass *class)
151 {
152   GTypeModuleClass *module_class = G_TYPE_MODULE_CLASS (class);
153   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
154
155   parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (class));
156   
157   module_class->load = gtk_im_module_load;
158   module_class->unload = gtk_im_module_unload;
159
160   gobject_class->finalize = gtk_im_module_finalize;
161 }
162
163 GType
164 gtk_im_module_get_type (void)
165 {
166   static GType im_module_type = 0;
167
168   if (!im_module_type)
169     {
170       static const GTypeInfo im_module_info = {
171         sizeof (GtkIMModuleClass),
172         NULL,           /* base_init */
173         NULL,           /* base_finalize */
174         (GClassInitFunc) gtk_im_module_class_init,
175         NULL,           /* class_finalize */
176         NULL,           /* class_data */
177         sizeof (GtkIMModule),
178         0,              /* n_preallocs */
179         NULL,           /* instance_init */
180       };
181
182       im_module_type =
183         g_type_register_static (G_TYPE_TYPE_MODULE, "GtkIMModule",
184                                 &im_module_info, 0);
185     }
186   
187   return im_module_type;
188 }
189
190 static void
191 free_info (GtkIMContextInfo *info)
192 {
193   g_free ((char *)info->context_id);
194   g_free ((char *)info->context_name);
195   g_free ((char *)info->domain);
196   g_free ((char *)info->domain_dirname);
197   g_free ((char *)info->default_locales);
198   g_free (info);
199 }
200
201 static void
202 add_module (GtkIMModule *module, GSList *infos)
203 {
204   GSList *tmp_list = infos;
205   gint i = 0;
206   gint n = g_slist_length (infos);
207   module->contexts = g_new (GtkIMContextInfo *, n);
208
209   while (tmp_list)
210     {
211       GtkIMContextInfo *info = tmp_list->data;
212   
213       if (g_hash_table_lookup (contexts_hash, info->context_id))
214         {
215           free_info (info);     /* Duplicate */
216         }
217       else
218         {
219           g_hash_table_insert (contexts_hash, (char *)info->context_id, module);
220           module->contexts[i++] = tmp_list->data;
221           n_loaded_contexts++;
222         }
223       
224       tmp_list = tmp_list->next;
225     }
226   g_slist_free (infos);
227   module->n_contexts = i;
228
229   modules_list = g_slist_prepend (modules_list, module);
230 }
231
232 #if defined (G_OS_WIN32) && defined (GTK_LIBDIR)
233 /* This is needes on Win32, but not wanted when compiling with MSVC,
234  * as the makefile.msc doesn't define any GTK_LIBDIR value.
235  */
236
237 #define DO_CORRECT_LIBDIR_PREFIX /* Flag to check below whether to call this */
238
239 static void
240 correct_libdir_prefix (gchar **path)
241 {
242   /* GTK_LIBDIR here is supposed to still have the definition from
243    * Makefile.am, i.e. the build-time value. Do *not* include gtkprivate.h
244    * in this file.
245    */
246   if (strncmp (*path, GTK_LIBDIR, strlen (GTK_LIBDIR)) == 0)
247     {
248       /* This is an entry put there by make install on the
249        * packager's system. On Windows a prebuilt GTK+
250        * package can be installed in a random
251        * location. The gtk.immodules file distributed in
252        * such a package contains paths from the package
253        * builder's machine. Replace the path with the real
254        * one on this machine.
255        */
256       extern const gchar *_gtk_get_libdir ();
257       gchar *tem = *path;
258       *path = g_strconcat (_gtk_get_libdir (), tem + strlen (GTK_LIBDIR), NULL);
259       g_free (tem);
260     }
261 }
262 #endif
263
264
265 static void
266 gtk_im_module_init ()
267 {
268   GString *line_buf = g_string_new (NULL);
269   GString *tmp_buf = g_string_new (NULL);
270   gchar *filename = gtk_rc_get_im_module_file();
271   FILE *file;
272   gboolean have_error = FALSE;
273
274   GtkIMModule *module = NULL;
275   GSList *infos = NULL;
276
277   contexts_hash = g_hash_table_new (g_str_hash, g_str_equal);
278
279   file = fopen (filename, "r");
280   if (!file)
281     {
282       /* In case someone wants only the default input method,
283        * we allow no file at all.
284        */
285       g_string_free (line_buf, TRUE);
286       g_string_free (tmp_buf, TRUE);
287       g_free (filename);
288       return;
289     }
290
291   while (!have_error && pango_read_line (file, line_buf))
292     {
293       const char *p;
294       
295       p = line_buf->str;
296
297       if (!pango_skip_space (&p))
298         {
299           /* Blank line marking the end of a module
300            */
301           if (module && *p != '#')
302             {
303               add_module (module, infos);
304               module = NULL;
305               infos = NULL;
306             }
307           
308           continue;
309         }
310
311       if (!module)
312         {
313           /* Read a module location
314            */
315           module = g_object_new (GTK_TYPE_IM_MODULE, NULL);
316
317           if (!pango_scan_string (&p, tmp_buf) ||
318               pango_skip_space (&p))
319             {
320               g_warning ("Error parsing context info in '%s'\n  %s", 
321                          filename, line_buf->str);
322               have_error = TRUE;
323             }
324
325           module->path = g_strdup (tmp_buf->str);
326 #ifdef DO_CORRECT_LIBDIR_PREFIX
327           correct_libdir_prefix (&module->path);
328 #endif
329           g_type_module_set_name (G_TYPE_MODULE (module), module->path);
330         }
331       else
332         {
333           GtkIMContextInfo *info = g_new0 (GtkIMContextInfo, 1);
334           
335           /* Read information about a context type
336            */
337           if (!pango_scan_string (&p, tmp_buf))
338             goto context_error;
339           info->context_id = g_strdup (tmp_buf->str);
340
341           if (!pango_scan_string (&p, tmp_buf))
342             goto context_error;
343           info->context_name = g_strdup (tmp_buf->str);
344
345           if (!pango_scan_string (&p, tmp_buf))
346             goto context_error;
347           info->domain = g_strdup (tmp_buf->str);
348
349           if (!pango_scan_string (&p, tmp_buf))
350             goto context_error;
351           info->domain_dirname = g_strdup (tmp_buf->str);
352 #ifdef DO_CORRECT_LIBDIR_PREFIX
353           correct_libdir_prefix (&info->domain_dirname);
354 #endif
355
356           if (!pango_scan_string (&p, tmp_buf))
357             goto context_error;
358           info->default_locales = g_strdup (tmp_buf->str);
359
360           if (pango_skip_space (&p))
361             goto context_error;
362
363           infos = g_slist_prepend (infos, info);
364           continue;
365
366         context_error:
367           g_warning ("Error parsing context info in '%s'\n  %s", 
368                      filename, line_buf->str);
369           have_error = TRUE;
370         }
371     }
372
373   if (have_error)
374     {
375       GSList *tmp_list = infos;
376       while (tmp_list)
377         {
378           free_info (tmp_list->data);
379           tmp_list = tmp_list->next;
380         }
381       g_slist_free (infos);
382
383       g_object_unref (module);
384     }
385   else if (module)
386     add_module (module, infos);
387
388   fclose (file);
389   g_string_free (line_buf, TRUE);
390   g_string_free (tmp_buf, TRUE);
391   g_free (filename);
392 }
393
394 static gint
395 compare_gtkimcontextinfo_name(const GtkIMContextInfo **a,
396                               const GtkIMContextInfo **b)
397 {
398   return g_utf8_collate ((*a)->context_name, (*b)->context_name);
399 }
400
401 /**
402  * _gtk_im_module_list:
403  * @contexts: location to store an array of pointers to #GtkIMContextInfo
404  *            this array should be freed with g_free() when you are finished.
405  *            The structures it points are statically allocated and should
406  *            not be modified or freed.
407  * @n_contexts: the length of the array stored in @contexts
408  * 
409  * List all available types of input method context
410  **/
411 void
412 _gtk_im_module_list (const GtkIMContextInfo ***contexts,
413                      guint                    *n_contexts)
414 {
415   int n = 0;
416
417   static const GtkIMContextInfo simple_context_info = {
418     SIMPLE_ID,
419     N_("Default"),
420     GETTEXT_PACKAGE,
421 #ifdef GTK_LOCALEDIR
422     GTK_LOCALEDIR,
423 #else
424     "",
425 #endif
426     ""
427   };
428
429   if (!contexts_hash)
430     gtk_im_module_init ();
431
432   if (n_contexts)
433     *n_contexts = (n_loaded_contexts + 1);
434
435   if (contexts)
436     {
437       GSList *tmp_list;
438       int i;
439       
440       *contexts = g_new (const GtkIMContextInfo *, n_loaded_contexts + 1);
441
442       (*contexts)[n++] = &simple_context_info;
443
444       tmp_list = modules_list;
445       while (tmp_list)
446         {
447           GtkIMModule *module = tmp_list->data;
448
449           for (i=0; i<module->n_contexts; i++)
450             (*contexts)[n++] = module->contexts[i];
451           
452           tmp_list = tmp_list->next;
453         }
454
455       /* fisrt element (Default) should always be at top */
456       qsort ((*contexts)+1, n-1, sizeof (GtkIMContextInfo *), (GCompareFunc)compare_gtkimcontextinfo_name);
457     }
458 }
459
460 /**
461  * _gtk_im_module_create:
462  * @context_id: the context ID for the context type to create
463  * 
464  * Create an IM context of a type specified by the string
465  * ID @context_id.
466  * 
467  * Return value: a newly created input context of or @context_id, or
468  * if that could not be created, a newly created GtkIMContextSimple.
469  **/
470 GtkIMContext *
471 _gtk_im_module_create (const gchar *context_id)
472 {
473   GtkIMModule *im_module;
474   GtkIMContext *context = NULL;
475   
476   if (!contexts_hash)
477     gtk_im_module_init ();
478
479   if (strcmp (context_id, SIMPLE_ID) != 0)
480     {
481       im_module = g_hash_table_lookup (contexts_hash, context_id);
482       if (!im_module)
483         {
484           g_warning ("Attempt to load unknown IM context type '%s'", context_id);
485         }
486       else
487         {
488           if (g_type_module_use (G_TYPE_MODULE (im_module)))
489             {
490               context = im_module->create (context_id);
491               g_type_module_unuse (G_TYPE_MODULE (im_module));
492             }
493           
494           if (!context)
495             g_warning ("Loading IM context type '%s' failed", context_id);
496         }
497     }
498   
499   if (!context)
500      return gtk_im_context_simple_new ();
501   else
502     return context;
503 }
504
505 /* Match @locale against @against.
506  * 
507  * 'en_US' against 'en_US'       => 4
508  * 'en_US' against 'en'          => 3
509  * 'en', 'en_UK' against 'en_US' => 2
510  *  all locales, against '*'     => 1
511  */
512 static gint
513 match_locale (const gchar *locale,
514               const gchar *against,
515               gint         against_len)
516 {
517   if (strcmp (against, "*") == 0)
518     return 1;
519
520   if (g_ascii_strcasecmp (locale, against) == 0)
521     return 4;
522
523   if (g_ascii_strncasecmp (locale, against, 2) == 0)
524     return (against_len == 2) ? 3 : 2;
525
526   return 0;
527 }
528
529 /**
530  * _gtk_im_module_get_default_context_id:
531  * @locale: a locale id in the form 'en_US'
532  * 
533  * Return the context_id of the best IM context type
534  * for the given locale ID.
535  * 
536  * Return value: the context ID (will never be %NULL)
537  *    the value is newly allocated and must be freed
538  *    with g_free().
539  **/
540 const gchar *
541 _gtk_im_module_get_default_context_id (const gchar *locale)
542 {
543   GSList *tmp_list;
544   const gchar *context_id = NULL;
545   gint best_goodness = 0;
546   gint i;
547   gchar *tmp_locale, *tmp;
548   const gchar *envvar;
549       
550   if (!contexts_hash)
551     gtk_im_module_init ();
552
553   envvar = g_getenv ("GTK_IM_MODULE");
554   if (envvar &&
555       (strcmp (envvar, SIMPLE_ID) == 0 ||
556        g_hash_table_lookup (contexts_hash, envvar)))
557     return g_strdup (envvar);
558
559   /* Strip the locale code down to the essentials
560    */
561   tmp_locale = g_strdup (locale);
562   tmp = strchr (tmp_locale, '.');
563   if (tmp)
564     *tmp = '\0';
565   tmp = strchr (tmp_locale, '@');
566   if (tmp)
567     *tmp = '\0';
568   
569   tmp_list = modules_list;
570   while (tmp_list)
571     {
572       GtkIMModule *module = tmp_list->data;
573      
574       for (i=0; i<module->n_contexts; i++)
575         {
576           const gchar *p = module->contexts[i]->default_locales;
577           while (p)
578             {
579               const gchar *q = strchr (p, ':');
580               gint goodness = match_locale (tmp_locale, p, q ? q - p : strlen (p));
581
582               if (goodness > best_goodness)
583                 {
584                   context_id = module->contexts[i]->context_id;
585                   best_goodness = goodness;
586                 }
587
588               p = q ? q + 1 : NULL;
589             }
590         }
591       
592       tmp_list = tmp_list->next;
593     }
594
595   g_free (tmp_locale);
596   
597   return g_strdup (context_id ? context_id : SIMPLE_ID);
598 }