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