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