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