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
gtk_im_module_load(GTypeModule * module)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
gtk_im_module_unload(GTypeModule * module)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
gtk_im_module_finalize(GObject * object)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
G_DEFINE_TYPE(GtkIMModule,gtk_im_module,G_TYPE_TYPE_MODULE)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
gtk_im_module_init(GtkIMModule * object)193 gtk_im_module_init (GtkIMModule* object)
194 {
195 }
196
197 static void
free_info(GtkIMContextInfo * info)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
add_module(GtkIMModule * module,GSList * infos)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
correct_libdir_prefix(gchar ** path)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 immodules.cache 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
correct_localedir_prefix(gchar ** path)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 *
add_builtin_module(const gchar * module_name,const GtkIMContextInfo ** contexts,int n_contexts)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
gtk_im_module_initialize(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
compare_gtkimcontextinfo_name(const GtkIMContextInfo ** a,const GtkIMContextInfo ** b)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
_gtk_im_module_list(const GtkIMContextInfo *** contexts,guint * n_contexts)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 *
_gtk_im_module_create(const gchar * context_id)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
match_locale(const gchar * locale,const gchar * against,gint against_len)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 static const gchar *
lookup_immodule(gchar ** immodules_list)654 lookup_immodule (gchar **immodules_list)
655 {
656 while (immodules_list && *immodules_list)
657 {
658 if (g_strcmp0 (*immodules_list, SIMPLE_ID) == 0)
659 return SIMPLE_ID;
660 else
661 {
662 gboolean found;
663 gchar *context_id;
664 found = g_hash_table_lookup_extended (contexts_hash, *immodules_list,
665 &context_id, NULL);
666 if (found)
667 return context_id;
668 }
669 immodules_list++;
670 }
671
672 return NULL;
673 }
674
675 /**
676 * _gtk_im_module_get_default_context_id:
677 * @client_window: a window
678 *
679 * Return the context_id of the best IM context type
680 * for the given window.
681 *
682 * Return value: the context ID (will never be %NULL)
683 */
684 const gchar *
_gtk_im_module_get_default_context_id(GdkWindow * client_window)685 _gtk_im_module_get_default_context_id (GdkWindow *client_window)
686 {
687 GSList *tmp_list;
688 const gchar *context_id = NULL;
689 gint best_goodness = 0;
690 gint i;
691 gchar *tmp_locale, *tmp, **immodules;
692 const gchar *envvar;
693 GdkScreen *screen;
694 GtkSettings *settings;
695
696 if (!contexts_hash)
697 gtk_im_module_initialize ();
698
699 envvar = g_getenv("GTK_IM_MODULE");
700 if (envvar)
701 {
702 immodules = g_strsplit(envvar, ":", 0);
703 context_id = lookup_immodule(immodules);
704 g_strfreev(immodules);
705
706 if (context_id)
707 return context_id;
708 }
709
710 /* Check if the certain immodule is set in XSETTINGS.
711 */
712 if (GDK_IS_DRAWABLE (client_window))
713 {
714 screen = gdk_window_get_screen (client_window);
715 settings = gtk_settings_get_for_screen (screen);
716 g_object_get (G_OBJECT (settings), "gtk-im-module", &tmp, NULL);
717 if (tmp)
718 {
719 immodules = g_strsplit(tmp, ":", 0);
720 context_id = lookup_immodule(immodules);
721 g_strfreev(immodules);
722 g_free (tmp);
723
724 if (context_id)
725 return context_id;
726 }
727 }
728
729 /* Strip the locale code down to the essentials
730 */
731 tmp_locale = _gtk_get_lc_ctype ();
732 tmp = strchr (tmp_locale, '.');
733 if (tmp)
734 *tmp = '\0';
735 tmp = strchr (tmp_locale, '@');
736 if (tmp)
737 *tmp = '\0';
738
739 tmp_list = modules_list;
740 while (tmp_list)
741 {
742 GtkIMModule *module = tmp_list->data;
743
744 for (i = 0; i < module->n_contexts; i++)
745 {
746 const gchar *p = module->contexts[i]->default_locales;
747 while (p)
748 {
749 const gchar *q = strchr (p, ':');
750 gint goodness = match_locale (tmp_locale, p, q ? q - p : strlen (p));
751
752 if (goodness > best_goodness)
753 {
754 context_id = module->contexts[i]->context_id;
755 best_goodness = goodness;
756 }
757
758 p = q ? q + 1 : NULL;
759 }
760 }
761
762 tmp_list = tmp_list->next;
763 }
764
765 g_free (tmp_locale);
766
767 return context_id ? context_id : SIMPLE_ID;
768 }
769