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