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