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