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