]> Pileus Git - ~andy/gtk/blob - gtk/gtkimmodule.c
Allow fallback for input method modules
[~andy/gtk] / gtk / gtkimmodule.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free
16  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 /*
20  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
21  * file for a list of people on the GTK+ Team.  See the ChangeLog
22  * files for a list of changes.  These files are distributed with
23  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
24  */
25
26 #include "config.h"
27
28 #include <errno.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32
33 #include <glib/gstdio.h>
34 #include <gmodule.h>
35 #include "gtkimmodule.h"
36 #include "gtkimcontextsimple.h"
37 #include "gtksettings.h"
38 #include "gtkprivate.h"
39 #include "gtkintl.h"
40
41 #define SIMPLE_ID "gtk-im-context-simple"
42
43 /**
44  * GtkIMContextInfo:
45  * @context_id: The unique identification string of the input method.
46  * @context_name: The human-readable name of the input method.
47  * @domain: Translation domain to be used with dgettext()
48  * @domain_dirname: Name of locale directory for use with bindtextdomain()
49  * @default_locales: A colon-separated list of locales where this input method
50  *   should be the default. The asterisk "*" sets the default for all locales.
51  *
52  * Bookkeeping information about a loadable input method.
53  */
54
55 typedef struct _GtkIMModule      GtkIMModule;
56 typedef struct _GtkIMModuleClass GtkIMModuleClass;
57
58 #define GTK_TYPE_IM_MODULE          (gtk_im_module_get_type ())
59 #define GTK_IM_MODULE(im_module)    (G_TYPE_CHECK_INSTANCE_CAST ((im_module), GTK_TYPE_IM_MODULE, GtkIMModule))
60 #define GTK_IS_IM_MODULE(im_module) (G_TYPE_CHECK_INSTANCE_TYPE ((im_module), GTK_TYPE_IM_MODULE))
61
62 struct _GtkIMModule
63 {
64   GTypeModule parent_instance;
65   
66   gboolean builtin;
67
68   GModule *library;
69
70   void          (*list)   (const GtkIMContextInfo ***contexts,
71                            guint                    *n_contexts);
72   void          (*init)   (GTypeModule              *module);
73   void          (*exit)   (void);
74   GtkIMContext *(*create) (const gchar              *context_id);
75
76   GtkIMContextInfo **contexts;
77   guint n_contexts;
78
79   gchar *path;
80 };
81
82 struct _GtkIMModuleClass 
83 {
84   GTypeModuleClass parent_class;
85 };
86
87 static GType gtk_im_module_get_type (void);
88
89 static gint n_loaded_contexts = 0;
90 static GHashTable *contexts_hash = NULL;
91 static GSList *modules_list = NULL;
92
93 static GObjectClass *parent_class = NULL;
94
95 static gboolean
96 gtk_im_module_load (GTypeModule *module)
97 {
98   GtkIMModule *im_module = GTK_IM_MODULE (module);
99   
100   if (!im_module->builtin)
101     {
102       im_module->library = g_module_open (im_module->path, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
103       if (!im_module->library)
104         {
105           g_warning ("%s", g_module_error());
106           return FALSE;
107         }
108   
109       /* extract symbols from the lib */
110       if (!g_module_symbol (im_module->library, "im_module_init",
111                             (gpointer *)&im_module->init) ||
112           !g_module_symbol (im_module->library, "im_module_exit", 
113                             (gpointer *)&im_module->exit) ||
114           !g_module_symbol (im_module->library, "im_module_list", 
115                             (gpointer *)&im_module->list) ||
116           !g_module_symbol (im_module->library, "im_module_create", 
117                             (gpointer *)&im_module->create))
118         {
119           g_warning ("%s", g_module_error());
120           g_module_close (im_module->library);
121           
122           return FALSE;
123         }
124     }
125             
126   /* call the module's init function to let it */
127   /* setup anything it needs to set up. */
128   im_module->init (module);
129
130   return TRUE;
131 }
132
133 static void
134 gtk_im_module_unload (GTypeModule *module)
135 {
136   GtkIMModule *im_module = GTK_IM_MODULE (module);
137   
138   im_module->exit();
139
140   if (!im_module->builtin)
141     {
142       g_module_close (im_module->library);
143       im_module->library = NULL;
144
145       im_module->init = NULL;
146       im_module->exit = NULL;
147       im_module->list = NULL;
148       im_module->create = NULL;
149     }
150 }
151
152 /* This only will ever be called if an error occurs during
153  * initialization
154  */
155 static void
156 gtk_im_module_finalize (GObject *object)
157 {
158   GtkIMModule *module = GTK_IM_MODULE (object);
159
160   g_free (module->path);
161
162   parent_class->finalize (object);
163 }
164
165 G_DEFINE_TYPE (GtkIMModule, gtk_im_module, G_TYPE_TYPE_MODULE)
166
167 static void
168 gtk_im_module_class_init (GtkIMModuleClass *class)
169 {
170   GTypeModuleClass *module_class = G_TYPE_MODULE_CLASS (class);
171   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
172
173   parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (class));
174   
175   module_class->load = gtk_im_module_load;
176   module_class->unload = gtk_im_module_unload;
177
178   gobject_class->finalize = gtk_im_module_finalize;
179 }
180
181 static void 
182 gtk_im_module_init (GtkIMModule* object)
183 {
184 }
185
186 static void
187 free_info (GtkIMContextInfo *info)
188 {
189   g_free ((char *)info->context_id);
190   g_free ((char *)info->context_name);
191   g_free ((char *)info->domain);
192   g_free ((char *)info->domain_dirname);
193   g_free ((char *)info->default_locales);
194   g_free (info);
195 }
196
197 static void
198 add_module (GtkIMModule *module, GSList *infos)
199 {
200   GSList *tmp_list = infos;
201   gint i = 0;
202   gint n = g_slist_length (infos);
203   module->contexts = g_new (GtkIMContextInfo *, n);
204
205   while (tmp_list)
206     {
207       GtkIMContextInfo *info = tmp_list->data;
208   
209       if (g_hash_table_lookup (contexts_hash, info->context_id))
210         {
211           free_info (info);     /* Duplicate */
212         }
213       else
214         {
215           g_hash_table_insert (contexts_hash, (char *)info->context_id, module);
216           module->contexts[i++] = tmp_list->data;
217           n_loaded_contexts++;
218         }
219       
220       tmp_list = tmp_list->next;
221     }
222   g_slist_free (infos);
223   module->n_contexts = i;
224
225   modules_list = g_slist_prepend (modules_list, module);
226 }
227
228 #ifdef G_OS_WIN32
229
230 static void
231 correct_libdir_prefix (gchar **path)
232 {
233   /* GTK_LIBDIR is the build-time libdir */
234   if (strncmp (*path, GTK_LIBDIR, strlen (GTK_LIBDIR)) == 0)
235     {
236       /* This is an entry put there by make install on the
237        * packager's system. On Windows a prebuilt GTK+
238        * package can be installed in a random
239        * location. The gtk.immodules file distributed in
240        * such a package contains paths from the package
241        * builder's machine. Replace the path with the real
242        * one on this machine.
243        */
244       gchar *tem = *path;
245       *path = g_strconcat (_gtk_get_libdir (), tem + strlen (GTK_LIBDIR), NULL);
246       g_free (tem);
247     }
248 }
249
250 static void
251 correct_localedir_prefix (gchar **path)
252 {
253   /* See above */
254   if (strncmp (*path, GTK_LOCALEDIR, strlen (GTK_LOCALEDIR)) == 0)
255     {
256       gchar *tem = *path;
257       *path = g_strconcat (_gtk_get_localedir (), tem + strlen (GTK_LOCALEDIR), NULL);
258       g_free (tem);
259     }
260 }
261 #endif
262
263
264 G_GNUC_UNUSED static GtkIMModule *
265 add_builtin_module (const gchar             *module_name,
266                     const GtkIMContextInfo **contexts,
267                     int                      n_contexts)
268 {
269   GtkIMModule *module = g_object_new (GTK_TYPE_IM_MODULE, NULL);
270   GSList *infos = NULL;
271   int i;
272
273   for (i = 0; i < n_contexts; i++)
274     {
275       GtkIMContextInfo *info = g_new (GtkIMContextInfo, 1);
276       info->context_id = g_strdup (contexts[i]->context_id);
277       info->context_name = g_strdup (contexts[i]->context_name);
278       info->domain = g_strdup (contexts[i]->domain);
279       info->domain_dirname = g_strdup (contexts[i]->domain_dirname);
280 #ifdef G_OS_WIN32
281       correct_localedir_prefix ((char **) &info->domain_dirname);
282 #endif
283       info->default_locales = g_strdup (contexts[i]->default_locales);
284       infos = g_slist_prepend (infos, info);
285     }
286
287   module->builtin = TRUE;
288   g_type_module_set_name (G_TYPE_MODULE (module), module_name);
289   add_module (module, infos);
290
291   return module;
292 }
293
294 static void
295 gtk_im_module_initialize (void)
296 {
297   GString *line_buf = g_string_new (NULL);
298   GString *tmp_buf = g_string_new (NULL);
299   gchar *filename = gtk_rc_get_im_module_file();
300   FILE *file;
301   gboolean have_error = FALSE;
302
303   GtkIMModule *module = NULL;
304   GSList *infos = NULL;
305
306   contexts_hash = g_hash_table_new (g_str_hash, g_str_equal);
307
308 #define do_builtin(m)                                                   \
309   {                                                                     \
310     const GtkIMContextInfo **contexts;                                  \
311     int n_contexts;                                                     \
312     extern void _gtk_immodule_ ## m ## _list (const GtkIMContextInfo ***contexts, \
313                                               guint                    *n_contexts); \
314     extern void _gtk_immodule_ ## m ## _init (GTypeModule *module);     \
315     extern void _gtk_immodule_ ## m ## _exit (void);                    \
316     extern GtkIMContext *_gtk_immodule_ ## m ## _create (const gchar *context_id); \
317                                                                         \
318     _gtk_immodule_ ## m ## _list (&contexts, &n_contexts);              \
319     module = add_builtin_module (#m, contexts, n_contexts);             \
320     module->init = _gtk_immodule_ ## m ## _init;                        \
321     module->exit = _gtk_immodule_ ## m ## _exit;                        \
322     module->create = _gtk_immodule_ ## m ## _create;                    \
323     module = NULL;                                                      \
324   }
325
326 #ifdef INCLUDE_IM_am_et
327   do_builtin (am_et);
328 #endif
329 #ifdef INCLUDE_IM_cedilla
330   do_builtin (cedilla);
331 #endif
332 #ifdef INCLUDE_IM_cyrillic_translit
333   do_builtin (cyrillic_translit);
334 #endif
335 #ifdef INCLUDE_IM_ime
336   do_builtin (ime);
337 #endif
338 #ifdef INCLUDE_IM_inuktitut
339   do_builtin (inuktitut);
340 #endif
341 #ifdef INCLUDE_IM_ipa
342   do_builtin (ipa);
343 #endif
344 #ifdef INCLUDE_IM_multipress
345   do_builtin (multipress);
346 #endif
347 #ifdef INCLUDE_IM_thai
348   do_builtin (thai);
349 #endif
350 #ifdef INCLUDE_IM_ti_er
351   do_builtin (ti_er);
352 #endif
353 #ifdef INCLUDE_IM_ti_et
354   do_builtin (ti_et);
355 #endif
356 #ifdef INCLUDE_IM_viqr
357   do_builtin (viqr);
358 #endif
359 #ifdef INCLUDE_IM_xim
360   do_builtin (xim);
361 #endif
362
363 #undef do_builtin
364
365   file = g_fopen (filename, "r");
366   if (!file)
367     {
368       /* In case someone wants only the default input method,
369        * we allow no file at all.
370        */
371       g_string_free (line_buf, TRUE);
372       g_string_free (tmp_buf, TRUE);
373       g_free (filename);
374       return;
375     }
376
377   while (!have_error && pango_read_line (file, line_buf))
378     {
379       const char *p;
380       
381       p = line_buf->str;
382
383       if (!pango_skip_space (&p))
384         {
385           /* Blank line marking the end of a module
386            */
387           if (module && *p != '#')
388             {
389               add_module (module, infos);
390               module = NULL;
391               infos = NULL;
392             }
393           
394           continue;
395         }
396
397       if (!module)
398         {
399           /* Read a module location
400            */
401           module = g_object_new (GTK_TYPE_IM_MODULE, NULL);
402
403           if (!pango_scan_string (&p, tmp_buf) ||
404               pango_skip_space (&p))
405             {
406               g_warning ("Error parsing context info in '%s'\n  %s", 
407                          filename, line_buf->str);
408               have_error = TRUE;
409             }
410
411           module->path = g_strdup (tmp_buf->str);
412 #ifdef G_OS_WIN32
413           correct_libdir_prefix (&module->path);
414 #endif
415           g_type_module_set_name (G_TYPE_MODULE (module), module->path);
416         }
417       else
418         {
419           GtkIMContextInfo *info = g_new0 (GtkIMContextInfo, 1);
420           
421           /* Read information about a context type
422            */
423           if (!pango_scan_string (&p, tmp_buf))
424             goto context_error;
425           info->context_id = g_strdup (tmp_buf->str);
426
427           if (!pango_scan_string (&p, tmp_buf))
428             goto context_error;
429           info->context_name = g_strdup (tmp_buf->str);
430
431           if (!pango_scan_string (&p, tmp_buf))
432             goto context_error;
433           info->domain = g_strdup (tmp_buf->str);
434
435           if (!pango_scan_string (&p, tmp_buf))
436             goto context_error;
437           info->domain_dirname = g_strdup (tmp_buf->str);
438 #ifdef G_OS_WIN32
439           correct_localedir_prefix ((char **) &info->domain_dirname);
440 #endif
441
442           if (!pango_scan_string (&p, tmp_buf))
443             goto context_error;
444           info->default_locales = g_strdup (tmp_buf->str);
445
446           if (pango_skip_space (&p))
447             goto context_error;
448
449           infos = g_slist_prepend (infos, info);
450           continue;
451
452         context_error:
453           g_warning ("Error parsing context info in '%s'\n  %s", 
454                      filename, line_buf->str);
455           have_error = TRUE;
456         }
457     }
458
459   if (have_error)
460     {
461       GSList *tmp_list = infos;
462       while (tmp_list)
463         {
464           free_info (tmp_list->data);
465           tmp_list = tmp_list->next;
466         }
467       g_slist_free (infos);
468
469       g_object_unref (module);
470     }
471   else if (module)
472     add_module (module, infos);
473
474   fclose (file);
475   g_string_free (line_buf, TRUE);
476   g_string_free (tmp_buf, TRUE);
477   g_free (filename);
478 }
479
480 static gint
481 compare_gtkimcontextinfo_name(const GtkIMContextInfo **a,
482                               const GtkIMContextInfo **b)
483 {
484   return g_utf8_collate ((*a)->context_name, (*b)->context_name);
485 }
486
487 /**
488  * _gtk_im_module_list:
489  * @contexts: location to store an array of pointers to #GtkIMContextInfo
490  *            this array should be freed with g_free() when you are finished.
491  *            The structures it points are statically allocated and should
492  *            not be modified or freed.
493  * @n_contexts: the length of the array stored in @contexts
494  * 
495  * List all available types of input method context
496  */
497 void
498 _gtk_im_module_list (const GtkIMContextInfo ***contexts,
499                      guint                    *n_contexts)
500 {
501   int n = 0;
502
503   static
504 #ifndef G_OS_WIN32
505           const
506 #endif
507                 GtkIMContextInfo simple_context_info = {
508     SIMPLE_ID,
509     N_("Simple"),
510     GETTEXT_PACKAGE,
511 #ifdef GTK_LOCALEDIR
512     GTK_LOCALEDIR,
513 #else
514     "",
515 #endif
516     ""
517   };
518
519 #ifdef G_OS_WIN32
520   static gboolean beenhere = FALSE;
521 #endif
522
523   if (!contexts_hash)
524     gtk_im_module_initialize ();
525
526 #ifdef G_OS_WIN32
527   if (!beenhere)
528     {
529       beenhere = TRUE;
530       /* correct_localedir_prefix() requires its parameter to be a
531        * malloced string
532        */
533       simple_context_info.domain_dirname = g_strdup (simple_context_info.domain_dirname);
534       correct_localedir_prefix ((char **) &simple_context_info.domain_dirname);
535     }
536 #endif
537
538   if (n_contexts)
539     *n_contexts = (n_loaded_contexts + 1);
540
541   if (contexts)
542     {
543       GSList *tmp_list;
544       int i;
545       
546       *contexts = g_new (const GtkIMContextInfo *, n_loaded_contexts + 1);
547
548       (*contexts)[n++] = &simple_context_info;
549
550       tmp_list = modules_list;
551       while (tmp_list)
552         {
553           GtkIMModule *module = tmp_list->data;
554
555           for (i=0; i<module->n_contexts; i++)
556             (*contexts)[n++] = module->contexts[i];
557           
558           tmp_list = tmp_list->next;
559         }
560
561       /* fisrt element (Default) should always be at top */
562       qsort ((*contexts)+1, n-1, sizeof (GtkIMContextInfo *), (GCompareFunc)compare_gtkimcontextinfo_name);
563     }
564 }
565
566 /**
567  * _gtk_im_module_create:
568  * @context_id: the context ID for the context type to create
569  * 
570  * Create an IM context of a type specified by the string
571  * ID @context_id.
572  * 
573  * Return value: a newly created input context of or @context_id, or
574  *     if that could not be created, a newly created GtkIMContextSimple.
575  */
576 GtkIMContext *
577 _gtk_im_module_create (const gchar *context_id)
578 {
579   GtkIMModule *im_module;
580   GtkIMContext *context = NULL;
581   
582   if (!contexts_hash)
583     gtk_im_module_initialize ();
584
585   if (strcmp (context_id, SIMPLE_ID) != 0)
586     {
587       im_module = g_hash_table_lookup (contexts_hash, context_id);
588       if (!im_module)
589         {
590           g_warning ("Attempt to load unknown IM context type '%s'", context_id);
591         }
592       else
593         {
594           if (g_type_module_use (G_TYPE_MODULE (im_module)))
595             {
596               context = im_module->create (context_id);
597               g_type_module_unuse (G_TYPE_MODULE (im_module));
598             }
599           
600           if (!context)
601             g_warning ("Loading IM context type '%s' failed", context_id);
602         }
603     }
604   
605   if (!context)
606      return gtk_im_context_simple_new ();
607   else
608     return context;
609 }
610
611 /* Match @locale against @against.
612  * 
613  * 'en_US' against 'en_US'       => 4
614  * 'en_US' against 'en'          => 3
615  * 'en', 'en_UK' against 'en_US' => 2
616  *  all locales, against '*'     => 1
617  */
618 static gint
619 match_locale (const gchar *locale,
620               const gchar *against,
621               gint         against_len)
622 {
623   if (strcmp (against, "*") == 0)
624     return 1;
625
626   if (g_ascii_strcasecmp (locale, against) == 0)
627     return 4;
628
629   if (g_ascii_strncasecmp (locale, against, 2) == 0)
630     return (against_len == 2) ? 3 : 2;
631
632   return 0;
633 }
634
635 static const gchar *
636 lookup_immodule (gchar **immodules_list)
637 {
638   while (immodules_list && *immodules_list)
639     {
640       if (g_strcmp0 (*immodules_list, SIMPLE_ID) == 0)
641         return SIMPLE_ID;
642       else
643        {
644          GtkIMModule *module;
645          module = g_hash_table_lookup (contexts_hash, *immodules_list);
646          if (module)
647            return module->contexts[0]->context_id;
648        }
649       immodules_list++;
650     }
651
652   return NULL;
653 }
654
655 /**
656  * _gtk_im_module_get_default_context_id:
657  * @client_window: a window
658  * 
659  * Return the context_id of the best IM context type 
660  * for the given window.
661  * 
662  * Return value: the context ID (will never be %NULL)
663  */
664 const gchar *
665 _gtk_im_module_get_default_context_id (GdkWindow *client_window)
666 {
667   GSList *tmp_list;
668   const gchar *context_id = NULL;
669   gint best_goodness = 0;
670   gint i;
671   gchar *tmp_locale, *tmp, **immodules;
672   const gchar *envvar;
673   GdkScreen *screen;
674   GtkSettings *settings;
675       
676   if (!contexts_hash)
677     gtk_im_module_initialize ();
678
679   envvar = g_getenv("GTK_IM_MODULE");
680   if (envvar)
681     {
682         immodules = g_strsplit(envvar, ":", 0);
683         context_id = lookup_immodule(immodules);
684         g_strfreev(immodules);
685
686         if (context_id)
687           return context_id;
688     }
689
690   /* Check if the certain immodule is set in XSETTINGS.
691    */
692   if (GDK_IS_WINDOW (client_window))
693     {
694       screen = gdk_window_get_screen (client_window);
695       settings = gtk_settings_get_for_screen (screen);
696       g_object_get (G_OBJECT (settings), "gtk-im-module", &tmp, NULL);
697       if (tmp)
698         {
699           immodules = g_strsplit(tmp, ":", 0);
700           context_id = lookup_immodule(immodules);
701           g_strfreev(immodules);
702           g_free (tmp);
703
704           if (context_id)
705             return context_id;
706         }
707     }
708
709   /* Strip the locale code down to the essentials
710    */
711   tmp_locale = _gtk_get_lc_ctype ();
712   tmp = strchr (tmp_locale, '.');
713   if (tmp)
714     *tmp = '\0';
715   tmp = strchr (tmp_locale, '@');
716   if (tmp)
717     *tmp = '\0';
718   
719   tmp_list = modules_list;
720   while (tmp_list)
721     {
722       GtkIMModule *module = tmp_list->data;
723      
724       for (i = 0; i < module->n_contexts; i++)
725         {
726           const gchar *p = module->contexts[i]->default_locales;
727           while (p)
728             {
729               const gchar *q = strchr (p, ':');
730               gint goodness = match_locale (tmp_locale, p, q ? q - p : strlen (p));
731
732               if (goodness > best_goodness)
733                 {
734                   context_id = module->contexts[i]->context_id;
735                   best_goodness = goodness;
736                 }
737
738               p = q ? q + 1 : NULL;
739             }
740         }
741       
742       tmp_list = tmp_list->next;
743     }
744
745   g_free (tmp_locale);
746   
747   return context_id ? context_id : SIMPLE_ID;
748 }