]> Pileus Git - ~andy/gtk/blob - modules/input/gtkimcontextxim.c
fix a memory leak, free input context list (#113259).
[~andy/gtk] / modules / input / gtkimcontextxim.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2000 Red Hat, Inc.
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
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include "locale.h"
21 #include <string.h>
22
23 #include "gtk/gtkintl.h"
24 #include "gtk/gtklabel.h"
25 #include "gtk/gtksignal.h"
26 #include "gtk/gtkwindow.h"
27 #include "gtkimcontextxim.h"
28
29 typedef struct _StatusWindow StatusWindow;
30
31 struct _GtkXIMInfo
32 {
33   GdkScreen *screen;
34   XIM im;
35   char *locale;
36   XIMStyle preedit_style_setting;
37   XIMStyle status_style_setting;
38   XIMStyle style;
39   GtkSettings *settings;
40   gulong status_set;
41   gulong preedit_set;
42   XIMStyles *xim_styles;
43   GSList *ics;
44 };
45
46 /* A context status window; these are kept in the status_windows list. */
47 struct _StatusWindow
48 {
49   GtkWidget *window;
50   
51   /* Toplevel window to which the status window corresponds */
52   GtkWidget *toplevel;
53   
54   /* Signal connection ids; we connect to the toplevel */
55   gulong destroy_handler_id;
56   gulong configure_handler_id;
57 };
58
59 static void     gtk_im_context_xim_class_init         (GtkIMContextXIMClass  *class);
60 static void     gtk_im_context_xim_init               (GtkIMContextXIM       *im_context_xim);
61 static void     gtk_im_context_xim_finalize           (GObject               *obj);
62 static void     gtk_im_context_xim_set_client_window  (GtkIMContext          *context,
63                                                        GdkWindow             *client_window);
64 static gboolean gtk_im_context_xim_filter_keypress    (GtkIMContext          *context,
65                                                        GdkEventKey           *key);
66 static void     gtk_im_context_xim_reset              (GtkIMContext          *context);
67 static void     gtk_im_context_xim_focus_in           (GtkIMContext          *context);
68 static void     gtk_im_context_xim_focus_out          (GtkIMContext          *context);
69 static void     gtk_im_context_xim_set_cursor_location (GtkIMContext          *context,
70                                                        GdkRectangle             *area);
71 static void     gtk_im_context_xim_set_use_preedit    (GtkIMContext          *context,
72                                                        gboolean               use_preedit);
73 static void     gtk_im_context_xim_get_preedit_string (GtkIMContext          *context,
74                                                        gchar                **str,
75                                                        PangoAttrList        **attrs,
76                                                        gint                  *cursor_pos);
77
78 static void reinitialize_ic      (GtkIMContextXIM *context_xim,
79                                   gboolean         send_signal);
80 static void set_ic_client_window (GtkIMContextXIM *context_xim,
81                                   GdkWindow       *client_window,
82                                   gboolean         send_signal);
83
84 static void setup_styles (GtkXIMInfo *info);
85
86 static void status_window_show     (GtkIMContextXIM *context_xim);
87 static void status_window_hide     (GtkIMContextXIM *context_xim);
88 static void status_window_set_text (GtkIMContextXIM *context_xim,
89                                     const gchar     *text);
90
91 static XIC       gtk_im_context_xim_get_ic            (GtkIMContextXIM *context_xim);
92 static GObjectClass *parent_class;
93
94 GType gtk_type_im_context_xim = 0;
95
96 GSList *open_ims = NULL;
97
98 /* List of status windows for different toplevels */
99 static GSList *status_windows = NULL;
100
101 void
102 gtk_im_context_xim_register_type (GTypeModule *type_module)
103 {
104   static const GTypeInfo im_context_xim_info =
105   {
106     sizeof (GtkIMContextXIMClass),
107     (GBaseInitFunc) NULL,
108     (GBaseFinalizeFunc) NULL,
109     (GClassInitFunc) gtk_im_context_xim_class_init,
110     NULL,           /* class_finalize */    
111     NULL,           /* class_data */
112     sizeof (GtkIMContextXIM),
113     0,
114     (GInstanceInitFunc) gtk_im_context_xim_init,
115   };
116
117   gtk_type_im_context_xim = 
118     g_type_module_register_type (type_module,
119                                  GTK_TYPE_IM_CONTEXT,
120                                  "GtkIMContextXIM",
121                                  &im_context_xim_info, 0);
122 }
123
124 #define PREEDIT_MASK (XIMPreeditCallbacks | XIMPreeditPosition | \
125                       XIMPreeditArea | XIMPreeditNothing | XIMPreeditNone)
126 #define STATUS_MASK (XIMStatusCallbacks | XIMStatusArea | \
127                       XIMStatusNothing | XIMStatusNone)
128 #define ALLOWED_MASK (XIMPreeditCallbacks | XIMPreeditNothing | XIMPreeditNone | \
129                       XIMStatusCallbacks | XIMStatusNothing | XIMStatusNone)
130
131 static XIMStyle 
132 choose_better_style (XIMStyle style1, XIMStyle style2) 
133 {
134   XIMStyle s1, s2, u; 
135   
136   if (style1 == 0) return style2;
137   if (style2 == 0) return style1;
138   if ((style1 & (PREEDIT_MASK | STATUS_MASK))
139         == (style2 & (PREEDIT_MASK | STATUS_MASK)))
140     return style1;
141
142   s1 = style1 & PREEDIT_MASK;
143   s2 = style2 & PREEDIT_MASK;
144   u = s1 | s2;
145   if (s1 != s2) {
146     if (u & XIMPreeditCallbacks)
147       return (s1 == XIMPreeditCallbacks) ? style1 : style2;
148     else if (u & XIMPreeditPosition)
149       return (s1 == XIMPreeditPosition) ? style1 :style2;
150     else if (u & XIMPreeditArea)
151       return (s1 == XIMPreeditArea) ? style1 : style2;
152     else if (u & XIMPreeditNothing)
153       return (s1 == XIMPreeditNothing) ? style1 : style2;
154     else if (u & XIMPreeditNone)
155       return (s1 == XIMPreeditNone) ? style1 : style2;
156   } else {
157     s1 = style1 & STATUS_MASK;
158     s2 = style2 & STATUS_MASK;
159     u = s1 | s2;
160     if (u & XIMStatusCallbacks)
161       return (s1 == XIMStatusCallbacks) ? style1 : style2;
162     else if (u & XIMStatusArea)
163       return (s1 == XIMStatusArea) ? style1 : style2;
164     else if (u & XIMStatusNothing)
165       return (s1 == XIMStatusNothing) ? style1 : style2;
166     else if (u & XIMStatusNone)
167       return (s1 == XIMStatusNone) ? style1 : style2;
168   }
169   return 0; /* Get rid of stupid warning */
170 }
171
172 static void
173 reinitialize_all_ics (GtkXIMInfo *info)
174 {
175   GSList *tmp_list;
176
177   for (tmp_list = info->ics; tmp_list; tmp_list = tmp_list->next)
178     reinitialize_ic (tmp_list->data, TRUE);
179 }
180
181 static void
182 status_style_change (GtkXIMInfo *info)
183 {
184   GtkIMStatusStyle status_style;
185   
186   g_object_get (info->settings,
187                 "gtk-im-status-style", &status_style,
188                 NULL);
189   if (status_style == GTK_IM_STATUS_CALLBACK)
190     info->status_style_setting = XIMStatusCallbacks;
191   else if (status_style == GTK_IM_STATUS_NOTHING)
192     info->status_style_setting = XIMStatusNothing;
193   else
194     return;
195
196   setup_styles (info);
197   
198   reinitialize_all_ics (info);
199 }
200
201 static void
202 preedit_style_change (GtkXIMInfo *info)
203 {
204   GtkIMPreeditStyle preedit_style;
205   g_object_get (info->settings,
206                 "gtk-im-preedit-style", &preedit_style,
207                 NULL);
208   if (preedit_style == GTK_IM_PREEDIT_CALLBACK)
209     info->preedit_style_setting = XIMPreeditCallbacks;
210   else if (preedit_style == GTK_IM_PREEDIT_NOTHING)
211     info->preedit_style_setting = XIMPreeditNothing;
212   else
213     return;
214
215   setup_styles (info);
216   
217   reinitialize_all_ics (info);
218 }
219
220 static void
221 setup_styles (GtkXIMInfo *info)
222 {
223   int i;
224   unsigned long settings_preference;
225   XIMStyles *xim_styles = info->xim_styles;
226
227   settings_preference = info->status_style_setting|info->preedit_style_setting;
228   info->style = 0;
229   if (xim_styles)
230     {
231       for (i = 0; i < xim_styles->count_styles; i++)
232         if ((xim_styles->supported_styles[i] & ALLOWED_MASK) == xim_styles->supported_styles[i])
233           {
234             if (settings_preference == xim_styles->supported_styles[i])
235               {
236                 info->style = settings_preference;
237                 break;
238               }
239             info->style = choose_better_style (info->style,
240                                                xim_styles->supported_styles[i]);
241           }
242     }
243   if (info->style == 0)
244     info->style = XIMPreeditNothing | XIMStatusNothing;
245 }
246
247 static void
248 setup_im (GtkXIMInfo *info)
249 {
250   XIMValuesList *ic_values = NULL;
251
252   if (info->im == NULL)
253     return;
254
255   XGetIMValues (info->im,
256                 XNQueryInputStyle, &info->xim_styles,
257                 XNQueryICValuesList, &ic_values,
258                 NULL);
259
260   info->settings = gtk_settings_get_for_screen (info->screen);
261
262   if (!g_object_class_find_property (G_OBJECT_GET_CLASS (info->settings),
263                                      "gtk-im-preedit-style"))
264     gtk_settings_install_property (g_param_spec_enum ("gtk-im-preedit-style",
265                                                       _("IM Preedit style"),
266                                                       _("How to draw the input method preedit string"),
267                                                       GTK_TYPE_IM_PREEDIT_STYLE,
268                                                       GTK_IM_PREEDIT_CALLBACK,
269                                                       G_PARAM_READWRITE));
270
271   if (!g_object_class_find_property (G_OBJECT_GET_CLASS (info->settings),
272                                      "gtk-im-status-style"))
273     gtk_settings_install_property (g_param_spec_enum ("gtk-im-status-style",
274                                                       _("IM Status style"),
275                                                       _("How to draw the input method statusbar"),
276                                                       GTK_TYPE_IM_STATUS_STYLE,
277                                                       GTK_IM_STATUS_CALLBACK,
278                                                       G_PARAM_READWRITE));
279
280   info->status_set = g_signal_connect_swapped (info->settings,
281                                                "notify::gtk-im-status-style",
282                                                G_CALLBACK (status_style_change),
283                                                info);
284   info->preedit_set = g_signal_connect_swapped (info->settings,
285                                                 "notify::gtk-im-preedit-style",
286                                                 G_CALLBACK (preedit_style_change),
287                                                 info);
288
289   status_style_change (info);
290   preedit_style_change (info);
291
292 #if 0
293   if (ic_values)
294     {
295       for (i = 0; i < ic_values->count_values; i++)
296         g_print ("%s\n", ic_values->supported_values[i]);
297       for (i = 0; i < xim_styles->count_styles; i++)
298         g_print ("%#x\n", xim_styles->supported_styles[i]);
299     }
300 #endif
301
302   if (ic_values)
303     XFree (ic_values);
304 }
305
306 static void
307 xim_info_display_closed (GdkDisplay *display,
308                          gboolean    is_error,
309                          GtkXIMInfo *info)
310 {
311   GSList *ics, *tmp_list;
312
313   open_ims = g_slist_remove (open_ims, info);
314
315   ics = info->ics;
316   info->ics = NULL;
317
318   for (tmp_list = ics; tmp_list; tmp_list = tmp_list->next)
319     set_ic_client_window (tmp_list->data, NULL, TRUE);
320
321   g_slist_free (ics);
322   
323   g_signal_handler_disconnect (info->settings, info->status_set);
324   g_signal_handler_disconnect (info->settings, info->preedit_set);
325   
326   XFree (info->xim_styles->supported_styles);
327   XFree (info->xim_styles);
328   g_free (info->locale);
329
330   if (info->im)
331     XCloseIM (info->im);
332
333   g_free (info);
334 }
335
336 static GtkXIMInfo *
337 get_im (GdkWindow *client_window,
338         const char *locale)
339 {
340   GSList *tmp_list;
341   GtkXIMInfo *info;
342   XIM im = NULL;
343   GdkScreen *screen = gdk_drawable_get_screen (client_window);
344   GdkDisplay *display = gdk_screen_get_display (screen);
345
346   tmp_list = open_ims;
347   while (tmp_list)
348     {
349       info = tmp_list->data;
350       if (info->screen == screen &&
351           strcmp (info->locale, locale) == 0)
352         return info;
353
354       tmp_list = tmp_list->next;
355     }
356
357   info = NULL;
358
359   if (XSupportsLocale ())
360     {
361       if (!XSetLocaleModifiers (""))
362         g_warning ("Unable to set locale modifiers with XSetLocaleModifiers()");
363       
364       im = XOpenIM (GDK_DISPLAY_XDISPLAY (display), NULL, NULL, NULL);
365       
366       if (!im)
367         g_warning ("Unable to open XIM input method, falling back to XLookupString()");
368
369       info = g_new (GtkXIMInfo, 1);
370       open_ims = g_slist_prepend (open_ims, info);
371
372       info->screen = screen;
373       info->locale = g_strdup (locale);
374       info->im = im;
375       info->xim_styles = NULL;
376       info->preedit_style_setting = 0;
377       info->status_style_setting = 0;
378       info->settings = NULL;
379       info->preedit_set = 0;
380       info->status_set = 0;
381       info->ics = NULL;
382
383       setup_im (info);
384
385       g_signal_connect (display, "closed",
386                         G_CALLBACK (xim_info_display_closed), info);
387     }
388
389   return info;
390 }
391
392 static void
393 gtk_im_context_xim_class_init (GtkIMContextXIMClass *class)
394 {
395   GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class);
396   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
397
398   parent_class = g_type_class_peek_parent (class);
399
400   im_context_class->set_client_window = gtk_im_context_xim_set_client_window;
401   im_context_class->filter_keypress = gtk_im_context_xim_filter_keypress;
402   im_context_class->reset = gtk_im_context_xim_reset;
403   im_context_class->get_preedit_string = gtk_im_context_xim_get_preedit_string;
404   im_context_class->focus_in = gtk_im_context_xim_focus_in;
405   im_context_class->focus_out = gtk_im_context_xim_focus_out;
406   im_context_class->set_cursor_location = gtk_im_context_xim_set_cursor_location;
407   im_context_class->set_use_preedit = gtk_im_context_xim_set_use_preedit;
408   gobject_class->finalize = gtk_im_context_xim_finalize;
409 }
410
411 static void
412 gtk_im_context_xim_init (GtkIMContextXIM *im_context_xim)
413 {
414   im_context_xim->use_preedit = TRUE;
415   im_context_xim->filter_key_release = FALSE;
416   im_context_xim->status_visible = FALSE;
417 }
418
419 static void
420 gtk_im_context_xim_finalize (GObject *obj)
421 {
422   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (obj);
423
424   set_ic_client_window (context_xim, NULL, FALSE);
425
426   g_free (context_xim->locale);
427   g_free (context_xim->mb_charset);
428 }
429
430 static void
431 reinitialize_ic (GtkIMContextXIM *context_xim,
432                  gboolean         send_signal)
433 {
434   if (context_xim->ic)
435     {
436       XDestroyIC (context_xim->ic);
437       status_window_hide (context_xim);
438       context_xim->ic = NULL;
439
440       if (context_xim->preedit_length)
441         {
442           context_xim->preedit_length = 0;
443           if (send_signal)
444             g_signal_emit_by_name (context_xim, "preedit_changed");
445         }
446     }
447 }
448
449 static void
450 set_ic_client_window (GtkIMContextXIM *context_xim,
451                       GdkWindow       *client_window,
452                       gboolean         send_signal)
453 {
454   reinitialize_ic (context_xim, send_signal);
455   if (context_xim->client_window)
456     {
457       context_xim->im_info->ics = g_slist_remove (context_xim->im_info->ics, context_xim);
458       context_xim->im_info = NULL;
459     }
460   
461   context_xim->client_window = client_window;
462
463   if (context_xim->client_window)
464     {
465       context_xim->im_info = get_im (context_xim->client_window, context_xim->locale);
466       context_xim->im_info->ics = g_slist_prepend (context_xim->im_info->ics, context_xim);
467     }
468 }
469
470 static void
471 gtk_im_context_xim_set_client_window (GtkIMContext          *context,
472                                       GdkWindow             *client_window)
473 {
474   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
475
476   set_ic_client_window (context_xim, client_window, TRUE);
477 }
478
479 GtkIMContext *
480 gtk_im_context_xim_new (void)
481 {
482   GtkIMContextXIM *result;
483   const gchar *charset;
484
485   result = GTK_IM_CONTEXT_XIM (g_object_new (GTK_TYPE_IM_CONTEXT_XIM, NULL));
486
487   result->locale = g_strdup (setlocale (LC_CTYPE, NULL));
488   
489   g_get_charset (&charset);
490   result->mb_charset = g_strdup (charset);
491
492   return GTK_IM_CONTEXT (result);
493 }
494
495 static char *
496 mb_to_utf8 (GtkIMContextXIM *context_xim,
497             const char      *str)
498 {
499   GError *error = NULL;
500   gchar *result;
501
502   if (strcmp (context_xim->mb_charset, "UTF-8") == 0)
503     result = g_strdup (str);
504   else
505     {
506       result = g_convert (str, -1,
507                           "UTF-8", context_xim->mb_charset,
508                           NULL, NULL, &error);
509       if (!result)
510         {
511           g_warning ("Error converting text from IM to UTF-8: %s\n", error->message);
512           g_error_free (error);
513         }
514     }
515   
516   return result;
517 }
518
519 static gboolean
520 gtk_im_context_xim_filter_keypress (GtkIMContext *context,
521                                     GdkEventKey  *event)
522 {
523   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
524   XIC ic = gtk_im_context_xim_get_ic (context_xim);
525   gchar static_buffer[256];
526   gchar *buffer = static_buffer;
527   gint buffer_size = sizeof(static_buffer) - 1;
528   gint num_bytes = 0;
529   KeySym keysym;
530   Status status;
531   gboolean result = FALSE;
532   GdkWindow *root_window = gdk_screen_get_root_window (gdk_drawable_get_screen (event->window));
533
534   XKeyPressedEvent xevent;
535
536   if (event->type == GDK_KEY_RELEASE && !context_xim->filter_key_release)
537     return FALSE;
538
539   xevent.type = (event->type == GDK_KEY_PRESS) ? KeyPress : KeyRelease;
540   xevent.serial = 0;            /* hope it doesn't matter */
541   xevent.send_event = event->send_event;
542   xevent.display = GDK_DRAWABLE_XDISPLAY (event->window);
543   xevent.window = GDK_DRAWABLE_XID (event->window);
544   xevent.root = GDK_DRAWABLE_XID (root_window);
545   xevent.subwindow = xevent.window;
546   xevent.time = event->time;
547   xevent.x = xevent.x_root = 0;
548   xevent.y = xevent.y_root = 0;
549   xevent.state = event->state;
550   xevent.keycode = event->hardware_keycode;
551   xevent.same_screen = True;
552   
553   if (XFilterEvent ((XEvent *)&xevent, GDK_DRAWABLE_XID (context_xim->client_window)))
554     return TRUE;
555   
556  again:
557   if (ic)
558     num_bytes = XmbLookupString (ic, &xevent, buffer, buffer_size, &keysym, &status);
559   else
560     {
561       num_bytes = XLookupString (&xevent, buffer, buffer_size, &keysym, NULL);
562       status = XLookupBoth;
563     }
564
565   if (status == XBufferOverflow)
566     {
567       buffer_size = num_bytes;
568       if (buffer != static_buffer) 
569         g_free (buffer);
570       buffer = g_malloc (num_bytes + 1);
571       goto again;
572     }
573
574   /* I don't know how we should properly handle XLookupKeysym or XLookupBoth
575    * here ... do input methods actually change the keysym? we can't really
576    * feed it back to accelerator processing at this point...
577    */
578   if (status == XLookupChars || status == XLookupBoth)
579     {
580       char *result_utf8;
581
582       buffer[num_bytes] = '\0';
583
584       result_utf8 = mb_to_utf8 (context_xim, buffer);
585       if (result_utf8)
586         {
587           if ((guchar)result_utf8[0] >= 0x20 &&
588               result_utf8[0] != 0x7f) /* Some IM have a nasty habit of converting
589                                        * control characters into strings
590                                        */
591             {
592               g_signal_emit_by_name (context, "commit", result_utf8);
593               result = TRUE;
594             }
595           
596           g_free (result_utf8);
597         }
598     }
599
600   if (buffer != static_buffer) 
601     g_free (buffer);
602
603   return result;
604 }
605
606 static void
607 gtk_im_context_xim_focus_in (GtkIMContext *context)
608 {
609   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
610   XIC ic = gtk_im_context_xim_get_ic (context_xim);
611
612   if (!ic)
613     return;
614
615   XSetICFocus (ic);
616
617   status_window_show (context_xim);
618
619   return;
620 }
621
622 static void
623 gtk_im_context_xim_focus_out (GtkIMContext *context)
624 {
625   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
626   XIC ic = gtk_im_context_xim_get_ic (context_xim);
627
628   if (!ic)
629     return;
630
631   XUnsetICFocus (ic);
632
633   status_window_hide (context_xim);
634
635   return;
636 }
637
638 static void
639 gtk_im_context_xim_set_cursor_location (GtkIMContext *context,
640                                         GdkRectangle *area)
641 {
642   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
643   XIC ic = gtk_im_context_xim_get_ic (context_xim);
644
645   XVaNestedList preedit_attr;
646   XPoint          spot;
647
648   if (!ic)
649     return;
650
651   spot.x = area->x;
652   spot.y = area->y;
653
654   preedit_attr = XVaCreateNestedList (0,
655                                       XNSpotLocation, &spot,
656                                       0);
657   XSetICValues (ic,
658                 XNPreeditAttributes, preedit_attr,
659                 NULL);
660   XFree(preedit_attr);
661
662   return;
663 }
664
665 static void
666 gtk_im_context_xim_set_use_preedit (GtkIMContext *context,
667                                     gboolean      use_preedit)
668 {
669   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
670
671   use_preedit = use_preedit != FALSE;
672
673   if (context_xim->use_preedit != use_preedit)
674     {
675       context_xim->use_preedit = use_preedit;
676       reinitialize_ic (context_xim, TRUE);
677     }
678
679   return;
680 }
681
682 static void
683 gtk_im_context_xim_reset (GtkIMContext *context)
684 {
685   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
686   XIC ic = gtk_im_context_xim_get_ic (context_xim);
687   gchar *result;
688
689   /* restore conversion state after resetting ic later */
690   XIMPreeditState preedit_state = XIMPreeditUnKnown;
691   XVaNestedList preedit_attr;
692   gboolean have_preedit_state = FALSE;
693
694   if (!ic)
695     return;
696   
697
698   if (context_xim->preedit_length == 0)
699     return;
700
701   preedit_attr = XVaCreateNestedList(0,
702                                      XNPreeditState, &preedit_state,
703                                      0);
704   if (!XGetICValues(ic,
705                     XNPreeditAttributes, preedit_attr,
706                     NULL))
707     have_preedit_state = TRUE;
708
709   XFree(preedit_attr);
710
711   result = XmbResetIC (ic);
712
713   preedit_attr = XVaCreateNestedList(0,
714                                      XNPreeditState, preedit_state,
715                                      0);
716   if (have_preedit_state)
717     XSetICValues(ic,
718                  XNPreeditAttributes, preedit_attr,
719                  NULL);
720
721   XFree(preedit_attr);
722
723   if (result)
724     {
725       char *result_utf8 = mb_to_utf8 (context_xim, result);
726       if (result_utf8)
727         {
728           g_signal_emit_by_name (context, "commit", result_utf8);
729           g_free (result_utf8);
730         }
731     }
732
733   if (context_xim->preedit_length)
734     {
735       context_xim->preedit_length = 0;
736       g_signal_emit_by_name (context, "preedit_changed");
737     }
738
739   XFree (result);
740 }
741
742 /* Mask of feedback bits that we render
743  */
744 #define FEEDBACK_MASK (XIMReverse | XIMUnderline)
745
746 static void
747 add_feedback_attr (PangoAttrList *attrs,
748                    const gchar   *str,
749                    XIMFeedback    feedback,
750                    gint           start_pos,
751                    gint           end_pos)
752 {
753   PangoAttribute *attr;
754   
755   gint start_index = g_utf8_offset_to_pointer (str, start_pos) - str;
756   gint end_index = g_utf8_offset_to_pointer (str, end_pos) - str;
757
758   if (feedback & XIMUnderline)
759     {
760       attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
761       attr->start_index = start_index;
762       attr->end_index = end_index;
763
764       pango_attr_list_change (attrs, attr);
765     }
766
767   if (feedback & XIMReverse)
768     {
769       attr = pango_attr_foreground_new (0xffff, 0xffff, 0xffff);
770       attr->start_index = start_index;
771       attr->end_index = end_index;
772
773       pango_attr_list_change (attrs, attr);
774
775       attr = pango_attr_background_new (0, 0, 0);
776       attr->start_index = start_index;
777       attr->end_index = end_index;
778
779       pango_attr_list_change (attrs, attr);
780     }
781
782   if (feedback & ~FEEDBACK_MASK)
783     g_warning ("Unrendered feedback style: %#lx", feedback & ~FEEDBACK_MASK);
784 }
785
786 static void     
787 gtk_im_context_xim_get_preedit_string (GtkIMContext   *context,
788                                        gchar         **str,
789                                        PangoAttrList **attrs,
790                                        gint           *cursor_pos)
791 {
792   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
793   gchar *utf8 = g_ucs4_to_utf8 (context_xim->preedit_chars, context_xim->preedit_length, NULL, NULL, NULL);
794
795   if (attrs)
796     {
797       int i;
798       XIMFeedback last_feedback = 0;
799       gint start = -1;
800       
801       *attrs = pango_attr_list_new ();
802
803       for (i = 0; i < context_xim->preedit_length; i++)
804         {
805           XIMFeedback new_feedback = context_xim->feedbacks[i] & FEEDBACK_MASK;
806           if (new_feedback != last_feedback)
807             {
808               if (start >= 0)
809                 add_feedback_attr (*attrs, utf8, last_feedback, start, i);
810               
811               last_feedback = new_feedback;
812               start = i;
813             }
814         }
815
816       if (start >= 0)
817         add_feedback_attr (*attrs, utf8, last_feedback, start, i);
818     }
819
820   if (str)
821     *str = utf8;
822   else
823     g_free (utf8);
824
825   if (cursor_pos)
826     *cursor_pos = context_xim->preedit_cursor;
827 }
828
829 static void
830 preedit_start_callback (XIC      xic,
831                         XPointer client_data,
832                         XPointer call_data)
833 {
834   GtkIMContext *context = GTK_IM_CONTEXT (client_data);
835   
836   g_signal_emit_by_name (context, "preedit_start");
837 }                    
838
839 static void
840 preedit_done_callback (XIC      xic,
841                      XPointer client_data,
842                      XPointer call_data)
843 {
844   GtkIMContext *context = GTK_IM_CONTEXT (client_data);
845   
846   g_signal_emit_by_name (context, "preedit_end");  
847 }                    
848
849 static gint
850 xim_text_to_utf8 (GtkIMContextXIM *context, XIMText *xim_text, gchar **text)
851 {
852   gint text_length = 0;
853   GError *error = NULL;
854   gchar *result = NULL;
855
856   if (xim_text && xim_text->string.multi_byte)
857     {
858       if (xim_text->encoding_is_wchar)
859         {
860           g_warning ("Wide character return from Xlib not currently supported");
861           *text = NULL;
862           return 0;
863         }
864
865       if (strcmp (context->mb_charset, "UTF-8") == 0)
866         result = g_strdup (xim_text->string.multi_byte);
867       else
868         result = g_convert (xim_text->string.multi_byte,
869                             -1,
870                             "UTF-8",
871                             context->mb_charset,
872                             NULL, NULL, &error);
873       
874       if (result)
875         {
876           text_length = g_utf8_strlen (result, -1);
877           
878           if (text_length != xim_text->length)
879             {
880               g_warning ("Size mismatch when converting text from input method: supplied length = %d\n, result length = %d", xim_text->length, text_length);
881             }
882         }
883       else
884         {
885           g_warning ("Error converting text from IM to UCS-4: %s", error->message);
886           g_error_free (error);
887
888           *text = NULL;
889           return 0;
890         }
891
892       *text = result;
893       return text_length;
894     }
895   else
896     {
897       *text = NULL;
898       return 0;
899     }
900 }
901
902 static void
903 preedit_draw_callback (XIC                           xic, 
904                        XPointer                      client_data,
905                        XIMPreeditDrawCallbackStruct *call_data)
906 {
907   GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
908
909   XIMText *new_xim_text = call_data->text;
910   gint new_text_length;
911   gunichar *new_text = NULL;
912   gint i;
913   gint diff;
914   gint new_length;
915   gchar *tmp;
916   
917   gint chg_first = CLAMP (call_data->chg_first, 0, context->preedit_length);
918   gint chg_length = CLAMP (call_data->chg_length, 0, context->preedit_length - chg_first);
919
920   context->preedit_cursor = call_data->caret;
921   
922   if (chg_first != call_data->chg_first || chg_length != call_data->chg_length)
923     g_warning ("Invalid change to preedit string, first=%d length=%d (orig length == %d)",
924                call_data->chg_first, call_data->chg_length, context->preedit_length);
925
926   new_text_length = xim_text_to_utf8 (context, new_xim_text, &tmp);
927   if (tmp)
928     {
929       new_text = g_utf8_to_ucs4_fast (tmp, -1, NULL);
930       g_free (tmp);
931     }
932   
933   diff = new_text_length - chg_length;
934   new_length = context->preedit_length + diff;
935
936   if (new_length > context->preedit_size)
937     {
938       context->preedit_size = new_length;
939       context->preedit_chars = g_renew (gunichar, context->preedit_chars, new_length);
940       context->feedbacks = g_renew (XIMFeedback, context->feedbacks, new_length);
941     }
942
943   if (diff < 0)
944     {
945       for (i = chg_first + chg_length ; i < context->preedit_length; i++)
946         {
947           context->preedit_chars[i + diff] = context->preedit_chars[i];
948           context->feedbacks[i + diff] = context->feedbacks[i];
949         }
950     }
951   else
952     {
953       for (i = context->preedit_length - 1; i >= chg_first + chg_length ; i--)
954         {
955           context->preedit_chars[i + diff] = context->preedit_chars[i];
956           context->feedbacks[i + diff] = context->feedbacks[i];
957         }
958     }
959
960   for (i = 0; i < new_text_length; i++)
961     {
962       context->preedit_chars[chg_first + i] = new_text[i];
963       context->feedbacks[chg_first + i] = new_xim_text->feedback[i];
964     }
965
966   context->preedit_length += diff;
967
968   if (new_text)
969     g_free (new_text);
970
971   g_signal_emit_by_name (context, "preedit_changed");
972 }
973     
974
975 static void
976 preedit_caret_callback (XIC                            xic,
977                         XPointer                       client_data,
978                         XIMPreeditCaretCallbackStruct *call_data)
979 {
980   GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
981   
982   if (call_data->direction == XIMAbsolutePosition)
983     {
984       context->preedit_cursor = call_data->position;
985       g_signal_emit_by_name (context, "preedit_changed");
986     }
987   else
988     {
989       g_warning ("Caret movement command: %d %d %d not supported",
990                  call_data->position, call_data->direction, call_data->style);
991     }
992 }            
993
994 static void
995 status_start_callback (XIC      xic,
996                        XPointer client_data,
997                        XPointer call_data)
998 {
999   return;
1000
1001
1002 static void
1003 status_done_callback (XIC      xic,
1004                       XPointer client_data,
1005                       XPointer call_data)
1006 {
1007   return;
1008 }
1009
1010 static void
1011 status_draw_callback (XIC      xic,
1012                       XPointer client_data,
1013                       XIMStatusDrawCallbackStruct *call_data)
1014 {
1015   GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
1016
1017   if (context->status_visible == FALSE)
1018     return;
1019
1020   if (call_data->type == XIMTextType)
1021     {
1022       gchar *text;
1023       xim_text_to_utf8 (context, call_data->data.text, &text);
1024
1025       if (text)
1026         status_window_set_text (context, text);
1027       else
1028         status_window_set_text (context, "");
1029     }
1030   else                          /* bitmap */
1031     {
1032       g_print ("Status drawn with bitmap - id = %#lx\n", call_data->data.bitmap);
1033     }
1034 }
1035
1036 static XVaNestedList
1037 set_preedit_callback (GtkIMContextXIM *context_xim)
1038 {
1039   context_xim->preedit_start_callback.client_data = (XPointer)context_xim;
1040   context_xim->preedit_start_callback.callback = (XIMProc)preedit_start_callback;
1041   context_xim->preedit_done_callback.client_data = (XPointer)context_xim;
1042   context_xim->preedit_done_callback.callback = (XIMProc)preedit_done_callback;
1043   context_xim->preedit_draw_callback.client_data = (XPointer)context_xim;
1044   context_xim->preedit_draw_callback.callback = (XIMProc)preedit_draw_callback;
1045   context_xim->preedit_caret_callback.client_data = (XPointer)context_xim;
1046   context_xim->preedit_caret_callback.callback = (XIMProc)preedit_caret_callback;
1047   return XVaCreateNestedList (0,
1048                               XNPreeditStartCallback, &context_xim->preedit_start_callback,
1049                               XNPreeditDoneCallback, &context_xim->preedit_done_callback,
1050                               XNPreeditDrawCallback, &context_xim->preedit_draw_callback,
1051                               XNPreeditCaretCallback, &context_xim->preedit_caret_callback,
1052                               NULL);
1053 }
1054
1055 static XVaNestedList
1056 set_status_callback (GtkIMContextXIM *context_xim)
1057 {
1058   context_xim->status_start_callback.client_data = (XPointer)context_xim;
1059   context_xim->status_start_callback.callback = (XIMProc)status_start_callback;
1060   context_xim->status_done_callback.client_data = (XPointer)context_xim;
1061   context_xim->status_done_callback.callback = (XIMProc)status_done_callback;
1062   context_xim->status_draw_callback.client_data = (XPointer)context_xim;
1063   context_xim->status_draw_callback.callback = (XIMProc)status_draw_callback;
1064           
1065   return XVaCreateNestedList (0,
1066                               XNStatusStartCallback, &context_xim->status_start_callback,
1067                               XNStatusDoneCallback, &context_xim->status_done_callback,
1068                               XNStatusDrawCallback, &context_xim->status_draw_callback,
1069                               NULL);
1070 }
1071
1072 static XIC
1073 get_ic_real (GtkIMContextXIM *context_xim)
1074 {
1075   XIC xic = 0;
1076   const char *name1 = NULL;
1077   XVaNestedList list1 = NULL;
1078   const char *name2 = NULL;
1079   XVaNestedList list2 = NULL;
1080   XIMStyle im_style = 0;
1081
1082   if (context_xim->im_info->im == NULL)
1083     return (XIC)0;
1084
1085   if (context_xim->use_preedit &&
1086       (context_xim->im_info->style & PREEDIT_MASK) == XIMPreeditCallbacks)
1087     {
1088       im_style |= XIMPreeditCallbacks;
1089       name1 = XNPreeditAttributes;
1090       list1 = set_preedit_callback (context_xim);
1091     }
1092   else if ((context_xim->im_info->style & PREEDIT_MASK) == XIMPreeditNone)
1093     im_style |= XIMPreeditNone;
1094   else
1095     im_style |= XIMPreeditNothing;
1096
1097   if ((context_xim->im_info->style & STATUS_MASK) == XIMStatusCallbacks)
1098     {
1099       im_style |= XIMStatusCallbacks;
1100       if (name1 == NULL)
1101         {
1102           name1 = XNStatusAttributes;
1103           list1 = set_status_callback (context_xim);
1104         }
1105       else
1106         {
1107           name2 = XNStatusAttributes;
1108           list2 = set_status_callback (context_xim);
1109         }
1110     }
1111   else if ((context_xim->im_info->style & STATUS_MASK) == XIMStatusNone)
1112     im_style |= XIMStatusNone;
1113   else
1114     im_style |= XIMStatusNothing;
1115
1116   xic = XCreateIC (context_xim->im_info->im,
1117                    XNInputStyle, im_style,
1118                    XNClientWindow, GDK_DRAWABLE_XID (context_xim->client_window),
1119                    name1, list1,
1120                    name2, list2,
1121                    NULL);
1122   if (list1)
1123     XFree (list1);
1124   if (list2)
1125     XFree (list2);
1126
1127   if (xic)
1128     {
1129       /* Don't filter key released events with XFilterEvents unless
1130        * input methods ask for. This is a workaround for Solaris input
1131        * method bug in C and European locales. It doubles each key
1132        * stroke if both key pressed and released events are filtered.
1133        * (bugzilla #81759)
1134        */
1135       guint32 mask = 0;
1136       XGetICValues (xic,
1137                     XNFilterEvents, &mask,
1138                     NULL);
1139       context_xim->filter_key_release = (mask & KeyReleaseMask);
1140     }
1141
1142   return xic;
1143 }
1144
1145 static XIC
1146 gtk_im_context_xim_get_ic (GtkIMContextXIM *context_xim)
1147 {
1148   if (!context_xim->ic && context_xim->im_info)
1149     context_xim->ic = get_ic_real (context_xim);
1150   
1151   return context_xim->ic;
1152 }
1153
1154 /**************************
1155  *                        *
1156  * Status Window handling *
1157  *                        *
1158  **************************/
1159
1160 static gboolean
1161 status_window_expose_event (GtkWidget      *widget,
1162                             GdkEventExpose *event)
1163 {
1164   gdk_draw_rectangle (widget->window,
1165                       widget->style->base_gc [GTK_STATE_NORMAL],
1166                       TRUE,
1167                       0, 0,
1168                       widget->allocation.width, widget->allocation.height);
1169   gdk_draw_rectangle (widget->window,
1170                       widget->style->text_gc [GTK_STATE_NORMAL],
1171                       FALSE,
1172                       0, 0,
1173                       widget->allocation.width - 1, widget->allocation.height - 1);
1174
1175   return FALSE;
1176 }
1177
1178 static void
1179 status_window_style_set (GtkWidget *toplevel,
1180                          GtkStyle  *previous_style,
1181                          GtkWidget *label)
1182 {
1183   gint i;
1184   
1185   for (i = 0; i < 5; i++)
1186     gtk_widget_modify_fg (label, i, &toplevel->style->text[i]);
1187 }
1188
1189 /* Frees a status window and removes its link from the status_windows list */
1190 static void
1191 status_window_free (StatusWindow *status_window)
1192 {
1193   status_windows = g_slist_remove (status_windows, status_window);
1194  
1195   g_signal_handler_disconnect (status_window->toplevel, status_window->destroy_handler_id);
1196   g_signal_handler_disconnect (status_window->toplevel, status_window->configure_handler_id);
1197   gtk_widget_destroy (status_window->window);
1198   g_object_set_data (G_OBJECT (status_window->toplevel), "gtk-im-xim-status-window", NULL);
1199  
1200   g_free (status_window);
1201 }
1202
1203 static gboolean
1204 status_window_configure (GtkWidget         *toplevel,
1205                          GdkEventConfigure *event,
1206                          StatusWindow      *status_window)
1207 {
1208   GdkRectangle rect;
1209   GtkRequisition requisition;
1210   gint y;
1211   gint height = gdk_screen_get_height (gtk_widget_get_screen (toplevel));
1212   
1213   gdk_window_get_frame_extents (toplevel->window, &rect);
1214   gtk_widget_size_request (status_window->window, &requisition);
1215
1216   if (rect.y + rect.height + requisition.height < height)
1217     y = rect.y + rect.height;
1218   else
1219     y = height - requisition.height;
1220   
1221   gtk_window_move (GTK_WINDOW (status_window->window), rect.x, y);
1222
1223   return FALSE;
1224 }
1225
1226 static GtkWidget *
1227 status_window_get (GtkIMContextXIM *context_xim,
1228                    gboolean         create)
1229 {
1230   GdkWindow *toplevel_gdk;
1231   GtkWidget *toplevel;
1232   GtkWidget *window;
1233   StatusWindow *status_window;
1234   GtkWidget *status_label;
1235   GdkScreen *screen;
1236   GdkWindow *root_window;
1237   
1238   if (!context_xim->client_window)
1239     return NULL;
1240
1241   toplevel_gdk = context_xim->client_window;
1242   screen = gdk_drawable_get_screen (toplevel_gdk);
1243   root_window = gdk_screen_get_root_window (screen);
1244   
1245   while (TRUE)
1246     {
1247       GdkWindow *parent = gdk_window_get_parent (toplevel_gdk);
1248       if (parent == root_window)
1249         break;
1250       else
1251         toplevel_gdk = parent;
1252     }
1253
1254   gdk_window_get_user_data (toplevel_gdk, (gpointer *)&toplevel);
1255   if (!toplevel)
1256     return NULL;
1257
1258   status_window = g_object_get_data (G_OBJECT (toplevel), "gtk-im-xim-status-window");
1259   if (status_window)
1260     return status_window->window;
1261   else if (!create)
1262     return NULL;
1263
1264   status_window = g_new (StatusWindow, 1);
1265   status_window->window = gtk_window_new (GTK_WINDOW_POPUP);
1266   status_window->toplevel = toplevel;
1267
1268   status_windows = g_slist_prepend (status_windows, status_window);
1269
1270   window = status_window->window;
1271
1272   gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
1273   gtk_widget_set_app_paintable (window, TRUE);
1274
1275   status_label = gtk_label_new ("");
1276   gtk_misc_set_padding (GTK_MISC (status_label), 1, 1);
1277   gtk_widget_show (status_label);
1278   
1279   gtk_container_add (GTK_CONTAINER (window), status_label);
1280
1281   status_window->destroy_handler_id = g_signal_connect_swapped (toplevel, "destroy",
1282                                                                 G_CALLBACK (status_window_free),
1283                                                                 status_window);
1284   status_window->configure_handler_id = g_signal_connect (toplevel, "configure_event",
1285                                                           G_CALLBACK (status_window_configure),
1286                                                           status_window);
1287
1288   status_window_configure (toplevel, NULL, status_window);
1289
1290   g_signal_connect (window, "style_set",
1291                     G_CALLBACK (status_window_style_set), status_label);
1292   g_signal_connect (window, "expose_event",
1293                     G_CALLBACK (status_window_expose_event), NULL);
1294
1295   g_object_set_data (G_OBJECT (toplevel), "gtk-im-xim-status-window", status_window);
1296
1297   return window;
1298 }
1299
1300 static gboolean
1301 status_window_has_text (GtkWidget *status_window)
1302 {
1303   GtkWidget *label = GTK_BIN (status_window)->child;
1304   const gchar *text = gtk_label_get_text (GTK_LABEL (label));
1305
1306   return text[0] != '\0';
1307 }
1308
1309 static void
1310 status_window_show (GtkIMContextXIM *context_xim)
1311 {
1312   context_xim->status_visible = TRUE;
1313 }
1314
1315 static void
1316 status_window_hide (GtkIMContextXIM *context_xim)
1317 {
1318   GtkWidget *status_window = status_window_get (context_xim, FALSE);
1319
1320   context_xim->status_visible = FALSE;
1321
1322   if (status_window)
1323     status_window_set_text (context_xim, "");
1324 }
1325
1326 static void
1327 status_window_set_text (GtkIMContextXIM *context_xim,
1328                         const gchar     *text)
1329 {
1330   GtkWidget *status_window = status_window_get (context_xim, TRUE);
1331
1332   if (status_window)
1333     {
1334       GtkWidget *label = GTK_BIN (status_window)->child;
1335       gtk_label_set_text (GTK_LABEL (label), text);
1336       
1337       if (context_xim->status_visible && status_window_has_text (status_window))
1338         gtk_widget_show (status_window);
1339       else
1340         gtk_widget_hide (status_window);
1341     }
1342 }
1343
1344 /**
1345  * gtk_im_context_xim_shutdown:
1346  * 
1347  * Destroys all the status windows that are kept by the XIM contexts.  This
1348  * function should only be called by the XIM module exit routine.
1349  **/
1350 void
1351 gtk_im_context_xim_shutdown (void)
1352 {
1353   while (status_windows)
1354     status_window_free (status_windows->data);
1355 }