]> Pileus Git - ~andy/gtk/blob - modules/input/gtkimcontextxim.c
Create enum PrinterStateLevel
[~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, see <http://www.gnu.org/licenses/>.
16  */
17
18 #include "config.h"
19 #include "locale.h"
20 #include <string.h>
21 #include <stdlib.h>
22
23 #include "gtkimcontextxim.h"
24
25 #include "gtk/gtkintl.h"
26
27 typedef struct _StatusWindow StatusWindow;
28 typedef struct _GtkXIMInfo GtkXIMInfo;
29
30 struct _GtkIMContextXIM
31 {
32   GtkIMContext object;
33
34   GtkXIMInfo *im_info;
35
36   gchar *locale;
37   gchar *mb_charset;
38
39   GdkWindow *client_window;
40   GtkWidget *client_widget;
41
42   /* The status window for this input context; we claim the
43    * status window when we are focused and have created an XIC
44    */
45   StatusWindow *status_window;
46
47   gint preedit_size;
48   gint preedit_length;
49   gunichar *preedit_chars;
50   XIMFeedback *feedbacks;
51
52   gint preedit_cursor;
53   
54   XIMCallback preedit_start_callback;
55   XIMCallback preedit_done_callback;
56   XIMCallback preedit_draw_callback;
57   XIMCallback preedit_caret_callback;
58
59   XIMCallback status_start_callback;
60   XIMCallback status_done_callback;
61   XIMCallback status_draw_callback;
62
63   XIMCallback string_conversion_callback;
64
65   XIC ic;
66
67   guint filter_key_release : 1;
68   guint use_preedit : 1;
69   guint finalizing : 1;
70   guint in_toplevel : 1;
71   guint has_focus : 1;
72 };
73
74 struct _GtkXIMInfo
75 {
76   GdkScreen *screen;
77   XIM im;
78   char *locale;
79   XIMStyle preedit_style_setting;
80   XIMStyle status_style_setting;
81   XIMStyle style;
82   GtkSettings *settings;
83   gulong status_set;
84   gulong preedit_set;
85   gulong display_closed_cb;
86   XIMStyles *xim_styles;
87   GSList *ics;
88
89   guint reconnecting :1;
90   guint supports_string_conversion;
91 };
92
93 /* A context status window; these are kept in the status_windows list. */
94 struct _StatusWindow
95 {
96   GtkWidget *window;
97   
98   /* Toplevel window to which the status window corresponds */
99   GtkWidget *toplevel;
100
101   /* Currently focused GtkIMContextXIM for the toplevel, if any */
102   GtkIMContextXIM *context;
103 };
104
105 static void     gtk_im_context_xim_class_init         (GtkIMContextXIMClass  *class);
106 static void     gtk_im_context_xim_init               (GtkIMContextXIM       *im_context_xim);
107 static void     gtk_im_context_xim_finalize           (GObject               *obj);
108 static void     gtk_im_context_xim_set_client_window  (GtkIMContext          *context,
109                                                        GdkWindow             *client_window);
110 static gboolean gtk_im_context_xim_filter_keypress    (GtkIMContext          *context,
111                                                        GdkEventKey           *key);
112 static void     gtk_im_context_xim_reset              (GtkIMContext          *context);
113 static void     gtk_im_context_xim_focus_in           (GtkIMContext          *context);
114 static void     gtk_im_context_xim_focus_out          (GtkIMContext          *context);
115 static void     gtk_im_context_xim_set_cursor_location (GtkIMContext          *context,
116                                                        GdkRectangle             *area);
117 static void     gtk_im_context_xim_set_use_preedit    (GtkIMContext          *context,
118                                                        gboolean               use_preedit);
119 static void     gtk_im_context_xim_get_preedit_string (GtkIMContext          *context,
120                                                        gchar                **str,
121                                                        PangoAttrList        **attrs,
122                                                        gint                  *cursor_pos);
123
124 static void reinitialize_ic      (GtkIMContextXIM *context_xim);
125 static void set_ic_client_window (GtkIMContextXIM *context_xim,
126                                   GdkWindow       *client_window);
127
128 static void setup_styles (GtkXIMInfo *info);
129
130 static void update_client_widget   (GtkIMContextXIM *context_xim);
131 static void update_status_window   (GtkIMContextXIM *context_xim);
132
133 static StatusWindow *status_window_get      (GtkWidget    *toplevel);
134 static void          status_window_free     (StatusWindow *status_window);
135 static void          status_window_set_text (StatusWindow *status_window,
136                                              const gchar  *text);
137
138 static void xim_destroy_callback   (XIM      xim,
139                                     XPointer client_data,
140                                     XPointer call_data);
141
142 static XIC       gtk_im_context_xim_get_ic            (GtkIMContextXIM *context_xim);
143 static void           xim_info_display_closed (GdkDisplay *display,
144                                                gboolean    is_error,
145                                                GtkXIMInfo *info);
146
147 static GObjectClass *parent_class;
148
149 GType gtk_type_im_context_xim = 0;
150
151 static GSList *open_ims = NULL;
152
153 /* List of status windows for different toplevels */
154 static GSList *status_windows = NULL;
155
156 void
157 gtk_im_context_xim_register_type (GTypeModule *type_module)
158 {
159   const GTypeInfo im_context_xim_info =
160   {
161     sizeof (GtkIMContextXIMClass),
162     (GBaseInitFunc) NULL,
163     (GBaseFinalizeFunc) NULL,
164     (GClassInitFunc) gtk_im_context_xim_class_init,
165     NULL,           /* class_finalize */    
166     NULL,           /* class_data */
167     sizeof (GtkIMContextXIM),
168     0,
169     (GInstanceInitFunc) gtk_im_context_xim_init,
170   };
171
172   gtk_type_im_context_xim = 
173     g_type_module_register_type (type_module,
174                                  GTK_TYPE_IM_CONTEXT,
175                                  "GtkIMContextXIM",
176                                  &im_context_xim_info, 0);
177 }
178
179 #define PREEDIT_MASK (XIMPreeditCallbacks | XIMPreeditPosition | \
180                       XIMPreeditArea | XIMPreeditNothing | XIMPreeditNone)
181 #define STATUS_MASK (XIMStatusCallbacks | XIMStatusArea | \
182                       XIMStatusNothing | XIMStatusNone)
183 #define ALLOWED_MASK (XIMPreeditCallbacks | XIMPreeditNothing | XIMPreeditNone | \
184                       XIMStatusCallbacks | XIMStatusNothing | XIMStatusNone)
185
186 static XIMStyle 
187 choose_better_style (XIMStyle style1, XIMStyle style2) 
188 {
189   XIMStyle s1, s2, u; 
190   
191   if (style1 == 0) return style2;
192   if (style2 == 0) return style1;
193   if ((style1 & (PREEDIT_MASK | STATUS_MASK))
194         == (style2 & (PREEDIT_MASK | STATUS_MASK)))
195     return style1;
196
197   s1 = style1 & PREEDIT_MASK;
198   s2 = style2 & PREEDIT_MASK;
199   u = s1 | s2;
200   if (s1 != s2) {
201     if (u & XIMPreeditCallbacks)
202       return (s1 == XIMPreeditCallbacks) ? style1 : style2;
203     else if (u & XIMPreeditPosition)
204       return (s1 == XIMPreeditPosition) ? style1 :style2;
205     else if (u & XIMPreeditArea)
206       return (s1 == XIMPreeditArea) ? style1 : style2;
207     else if (u & XIMPreeditNothing)
208       return (s1 == XIMPreeditNothing) ? style1 : style2;
209     else if (u & XIMPreeditNone)
210       return (s1 == XIMPreeditNone) ? style1 : style2;
211   } else {
212     s1 = style1 & STATUS_MASK;
213     s2 = style2 & STATUS_MASK;
214     u = s1 | s2;
215     if (u & XIMStatusCallbacks)
216       return (s1 == XIMStatusCallbacks) ? style1 : style2;
217     else if (u & XIMStatusArea)
218       return (s1 == XIMStatusArea) ? style1 : style2;
219     else if (u & XIMStatusNothing)
220       return (s1 == XIMStatusNothing) ? style1 : style2;
221     else if (u & XIMStatusNone)
222       return (s1 == XIMStatusNone) ? style1 : style2;
223   }
224   return 0; /* Get rid of stupid warning */
225 }
226
227 static void
228 reinitialize_all_ics (GtkXIMInfo *info)
229 {
230   GSList *tmp_list;
231
232   for (tmp_list = info->ics; tmp_list; tmp_list = tmp_list->next)
233     reinitialize_ic (tmp_list->data);
234 }
235
236 static void
237 status_style_change (GtkXIMInfo *info)
238 {
239   GtkIMStatusStyle status_style;
240   
241   g_object_get (info->settings,
242                 "gtk-im-status-style", &status_style,
243                 NULL);
244   if (status_style == GTK_IM_STATUS_CALLBACK)
245     info->status_style_setting = XIMStatusCallbacks;
246   else if (status_style == GTK_IM_STATUS_NOTHING)
247     info->status_style_setting = XIMStatusNothing;
248   else if (status_style == GTK_IM_STATUS_NONE)
249     info->status_style_setting = XIMStatusNone;
250   else
251     return;
252
253   setup_styles (info);
254   
255   reinitialize_all_ics (info);
256 }
257
258 static void
259 preedit_style_change (GtkXIMInfo *info)
260 {
261   GtkIMPreeditStyle preedit_style;
262   g_object_get (info->settings,
263                 "gtk-im-preedit-style", &preedit_style,
264                 NULL);
265   if (preedit_style == GTK_IM_PREEDIT_CALLBACK)
266     info->preedit_style_setting = XIMPreeditCallbacks;
267   else if (preedit_style == GTK_IM_PREEDIT_NOTHING)
268     info->preedit_style_setting = XIMPreeditNothing;
269   else if (preedit_style == GTK_IM_PREEDIT_NONE)
270     info->preedit_style_setting = XIMPreeditNone;
271   else
272     return;
273
274   setup_styles (info);
275   
276   reinitialize_all_ics (info);
277 }
278
279 static void
280 setup_styles (GtkXIMInfo *info)
281 {
282   int i;
283   unsigned long settings_preference;
284   XIMStyles *xim_styles = info->xim_styles;
285
286   settings_preference = info->status_style_setting|info->preedit_style_setting;
287   info->style = 0;
288   if (xim_styles)
289     {
290       for (i = 0; i < xim_styles->count_styles; i++)
291         if ((xim_styles->supported_styles[i] & ALLOWED_MASK) == xim_styles->supported_styles[i])
292           {
293             if (settings_preference == xim_styles->supported_styles[i])
294               {
295                 info->style = settings_preference;
296                 break;
297               }
298             info->style = choose_better_style (info->style,
299                                                xim_styles->supported_styles[i]);
300           }
301     }
302   if (info->style == 0)
303     info->style = XIMPreeditNothing | XIMStatusNothing;
304 }
305
306 static void
307 setup_im (GtkXIMInfo *info)
308 {
309   XIMValuesList *ic_values = NULL;
310   XIMCallback im_destroy_callback;
311   GdkDisplay *display;
312
313   if (info->im == NULL)
314     return;
315
316   im_destroy_callback.client_data = (XPointer)info;
317   im_destroy_callback.callback = (XIMProc)xim_destroy_callback;
318   XSetIMValues (info->im,
319                 XNDestroyCallback, &im_destroy_callback,
320                 NULL);
321
322   XGetIMValues (info->im,
323                 XNQueryInputStyle, &info->xim_styles,
324                 XNQueryICValuesList, &ic_values,
325                 NULL);
326
327   info->settings = gtk_settings_get_for_screen (info->screen);
328   info->status_set = g_signal_connect_swapped (info->settings,
329                                                "notify::gtk-im-status-style",
330                                                G_CALLBACK (status_style_change),
331                                                info);
332   info->preedit_set = g_signal_connect_swapped (info->settings,
333                                                 "notify::gtk-im-preedit-style",
334                                                 G_CALLBACK (preedit_style_change),
335                                                 info);
336
337   info->supports_string_conversion = FALSE;
338   if (ic_values)
339     {
340       int i;
341       
342       for (i = 0; i < ic_values->count_values; i++)
343         if (strcmp (ic_values->supported_values[i],
344                     XNStringConversionCallback) == 0)
345           {
346             info->supports_string_conversion = TRUE;
347             break;
348           }
349
350 #if 0
351       for (i = 0; i < ic_values->count_values; i++)
352         g_print ("%s\n", ic_values->supported_values[i]);
353       for (i = 0; i < xim_styles->count_styles; i++)
354         g_print ("%#x\n", xim_styles->supported_styles[i]);
355 #endif
356       
357       XFree (ic_values);
358     }
359
360   status_style_change (info);
361   preedit_style_change (info);
362
363   display = gdk_screen_get_display (info->screen);
364   info->display_closed_cb = g_signal_connect (display, "closed",
365                                               G_CALLBACK (xim_info_display_closed), info);
366 }
367
368 static void
369 xim_info_display_closed (GdkDisplay *display,
370                          gboolean    is_error,
371                          GtkXIMInfo *info)
372 {
373   GSList *ics, *tmp_list;
374
375   open_ims = g_slist_remove (open_ims, info);
376
377   ics = info->ics;
378   info->ics = NULL;
379
380   for (tmp_list = ics; tmp_list; tmp_list = tmp_list->next)
381     set_ic_client_window (tmp_list->data, NULL);
382
383   g_slist_free (ics);
384
385   if (info->status_set)
386     g_signal_handler_disconnect (info->settings, info->status_set);
387   if (info->preedit_set)
388     g_signal_handler_disconnect (info->settings, info->preedit_set);
389   if (info->display_closed_cb)
390     g_signal_handler_disconnect (display, info->display_closed_cb);
391
392   if (info->xim_styles)
393     XFree (info->xim_styles);
394   g_free (info->locale);
395
396   if (info->im)
397     XCloseIM (info->im);
398
399   g_free (info);
400 }
401
402 static void
403 xim_instantiate_callback (Display *display, XPointer client_data,
404                           XPointer call_data)
405 {
406   GtkXIMInfo *info = (GtkXIMInfo*)client_data;
407   XIM im = NULL;
408
409   im = XOpenIM (display, NULL, NULL, NULL);
410
411   if (!im)
412     return;
413
414   info->im = im;
415   setup_im (info);
416
417   XUnregisterIMInstantiateCallback (display, NULL, NULL, NULL,
418                                     xim_instantiate_callback,
419                                     (XPointer)info);
420   info->reconnecting = FALSE;
421 }
422
423 /* initialize info->im */
424 static void
425 xim_info_try_im (GtkXIMInfo *info)
426 {
427   GdkScreen *screen = info->screen;
428   GdkDisplay *display = gdk_screen_get_display (screen);
429
430   g_assert (info->im == NULL);
431   if (info->reconnecting)
432     return;
433
434   if (XSupportsLocale ())
435     {
436       if (!XSetLocaleModifiers (""))
437         g_warning ("Unable to set locale modifiers with XSetLocaleModifiers()");
438       info->im = XOpenIM (GDK_DISPLAY_XDISPLAY (display), NULL, NULL, NULL);
439       if (!info->im)
440         {
441           XRegisterIMInstantiateCallback (GDK_DISPLAY_XDISPLAY(display),
442                                           NULL, NULL, NULL,
443                                           xim_instantiate_callback,
444                                           (XPointer)info);
445           info->reconnecting = TRUE;
446           return;
447         }
448       setup_im (info);
449     }
450 }
451
452 static void
453 xim_destroy_callback (XIM      xim,
454                       XPointer client_data,
455                       XPointer call_data)
456 {
457   GtkXIMInfo *info = (GtkXIMInfo*)client_data;
458
459   info->im = NULL;
460
461   g_signal_handler_disconnect (info->settings, info->status_set);
462   info->status_set = 0;
463   g_signal_handler_disconnect (info->settings, info->preedit_set);
464   info->preedit_set = 0;
465
466   reinitialize_all_ics (info);
467   xim_info_try_im (info);
468   return;
469
470
471 static GtkXIMInfo *
472 get_im (GdkWindow *client_window,
473         const char *locale)
474 {
475   GSList *tmp_list;
476   GtkXIMInfo *info;
477   GdkScreen *screen = gdk_window_get_screen (client_window);
478
479   info = NULL;
480   tmp_list = open_ims;
481   while (tmp_list)
482     {
483       GtkXIMInfo *tmp_info = tmp_list->data;
484       if (tmp_info->screen == screen &&
485           strcmp (tmp_info->locale, locale) == 0)
486         {
487           if (tmp_info->im)
488             {
489               return tmp_info;
490             }
491           else
492             {
493               tmp_info = tmp_info;
494               break;
495             }
496         }
497       tmp_list = tmp_list->next;
498     }
499
500   if (info == NULL)
501     {
502       info = g_new (GtkXIMInfo, 1);
503       open_ims = g_slist_prepend (open_ims, info);
504
505       info->screen = screen;
506       info->locale = g_strdup (locale);
507       info->xim_styles = NULL;
508       info->preedit_style_setting = 0;
509       info->status_style_setting = 0;
510       info->settings = NULL;
511       info->preedit_set = 0;
512       info->status_set = 0;
513       info->display_closed_cb = 0;
514       info->ics = NULL;
515       info->reconnecting = FALSE;
516       info->im = NULL;
517     }
518
519   xim_info_try_im (info);
520   return info;
521 }
522
523 static void
524 gtk_im_context_xim_class_init (GtkIMContextXIMClass *class)
525 {
526   GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class);
527   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
528
529   parent_class = g_type_class_peek_parent (class);
530
531   im_context_class->set_client_window = gtk_im_context_xim_set_client_window;
532   im_context_class->filter_keypress = gtk_im_context_xim_filter_keypress;
533   im_context_class->reset = gtk_im_context_xim_reset;
534   im_context_class->get_preedit_string = gtk_im_context_xim_get_preedit_string;
535   im_context_class->focus_in = gtk_im_context_xim_focus_in;
536   im_context_class->focus_out = gtk_im_context_xim_focus_out;
537   im_context_class->set_cursor_location = gtk_im_context_xim_set_cursor_location;
538   im_context_class->set_use_preedit = gtk_im_context_xim_set_use_preedit;
539   gobject_class->finalize = gtk_im_context_xim_finalize;
540 }
541
542 static void
543 gtk_im_context_xim_init (GtkIMContextXIM *im_context_xim)
544 {
545   im_context_xim->use_preedit = TRUE;
546   im_context_xim->filter_key_release = FALSE;
547   im_context_xim->finalizing = FALSE;
548   im_context_xim->has_focus = FALSE;
549   im_context_xim->in_toplevel = FALSE;
550 }
551
552 static void
553 gtk_im_context_xim_finalize (GObject *obj)
554 {
555   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (obj);
556
557   context_xim->finalizing = TRUE;
558
559   if (context_xim->im_info && !context_xim->im_info->ics->next) 
560     {
561       if (context_xim->im_info->reconnecting)
562         {
563           GdkDisplay *display;
564
565           display = gdk_screen_get_display (context_xim->im_info->screen);
566           XUnregisterIMInstantiateCallback (GDK_DISPLAY_XDISPLAY (display),
567                                             NULL, NULL, NULL,
568                                             xim_instantiate_callback,
569                                             (XPointer)context_xim->im_info);
570         }
571       else if (context_xim->im_info->im)
572         {
573           XIMCallback im_destroy_callback;
574
575           im_destroy_callback.client_data = NULL;
576           im_destroy_callback.callback = NULL;
577           XSetIMValues (context_xim->im_info->im,
578                         XNDestroyCallback, &im_destroy_callback,
579                         NULL);
580         }
581     }
582
583   set_ic_client_window (context_xim, NULL);
584
585   g_free (context_xim->locale);
586   g_free (context_xim->mb_charset);
587
588   G_OBJECT_CLASS (parent_class)->finalize (obj);
589 }
590
591 static void
592 reinitialize_ic (GtkIMContextXIM *context_xim)
593 {
594   if (context_xim->ic)
595     {
596       XDestroyIC (context_xim->ic);
597       context_xim->ic = NULL;
598       update_status_window (context_xim);
599
600       if (context_xim->preedit_length)
601         {
602           context_xim->preedit_length = 0;
603           if (!context_xim->finalizing)
604             g_signal_emit_by_name (context_xim, "preedit-changed");
605         }
606     }
607   /* 
608      reset filter_key_release flag, otherwise keystrokes will be doubled
609      until reconnecting to XIM.
610   */
611   context_xim->filter_key_release = FALSE;
612 }
613
614 static void
615 set_ic_client_window (GtkIMContextXIM *context_xim,
616                       GdkWindow       *client_window)
617 {
618   reinitialize_ic (context_xim);
619   if (context_xim->client_window)
620     {
621       context_xim->im_info->ics = g_slist_remove (context_xim->im_info->ics, context_xim);
622       context_xim->im_info = NULL;
623     }
624   
625   context_xim->client_window = client_window;
626
627   if (context_xim->client_window)
628     {
629       context_xim->im_info = get_im (context_xim->client_window, context_xim->locale);
630       context_xim->im_info->ics = g_slist_prepend (context_xim->im_info->ics, context_xim);
631     }
632   
633   update_client_widget (context_xim);
634 }
635
636 static void
637 gtk_im_context_xim_set_client_window (GtkIMContext          *context,
638                                       GdkWindow             *client_window)
639 {
640   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
641
642   set_ic_client_window (context_xim, client_window);
643 }
644
645 GtkIMContext *
646 gtk_im_context_xim_new (void)
647 {
648   GtkIMContextXIM *result;
649   const gchar *charset;
650
651   if (!GDK_IS_X11_DISPLAY(gdk_display_get_default()))
652     return NULL;
653   result = g_object_new (GTK_TYPE_IM_CONTEXT_XIM, NULL);
654
655   result->locale = g_strdup (setlocale (LC_CTYPE, NULL));
656   
657   g_get_charset (&charset);
658   result->mb_charset = g_strdup (charset);
659
660   return GTK_IM_CONTEXT (result);
661 }
662
663 static char *
664 mb_to_utf8 (GtkIMContextXIM *context_xim,
665             const char      *str)
666 {
667   GError *error = NULL;
668   gchar *result;
669
670   if (strcmp (context_xim->mb_charset, "UTF-8") == 0)
671     result = g_strdup (str);
672   else
673     {
674       result = g_convert (str, -1,
675                           "UTF-8", context_xim->mb_charset,
676                           NULL, NULL, &error);
677       if (!result)
678         {
679           g_warning ("Error converting text from IM to UTF-8: %s\n", error->message);
680           g_error_free (error);
681         }
682     }
683   
684   return result;
685 }
686
687 static gboolean
688 gtk_im_context_xim_filter_keypress (GtkIMContext *context,
689                                     GdkEventKey  *event)
690 {
691   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
692   XIC ic = gtk_im_context_xim_get_ic (context_xim);
693   gchar static_buffer[256];
694   gchar *buffer = static_buffer;
695   gint buffer_size = sizeof(static_buffer) - 1;
696   gint num_bytes = 0;
697   KeySym keysym;
698   Status status;
699   gboolean result = FALSE;
700   GdkWindow *root_window = gdk_screen_get_root_window (gdk_window_get_screen (event->window));
701
702   XKeyPressedEvent xevent;
703
704   if (event->type == GDK_KEY_RELEASE && !context_xim->filter_key_release)
705     return FALSE;
706
707   xevent.type = (event->type == GDK_KEY_PRESS) ? KeyPress : KeyRelease;
708   xevent.serial = 0;            /* hope it doesn't matter */
709   xevent.send_event = event->send_event;
710   xevent.display = GDK_WINDOW_XDISPLAY (event->window);
711   xevent.window = GDK_WINDOW_XID (event->window);
712   xevent.root = GDK_WINDOW_XID (root_window);
713   xevent.subwindow = xevent.window;
714   xevent.time = event->time;
715   xevent.x = xevent.x_root = 0;
716   xevent.y = xevent.y_root = 0;
717   xevent.state = event->state;
718   xevent.keycode = event->hardware_keycode;
719   xevent.same_screen = True;
720   
721   if (XFilterEvent ((XEvent *)&xevent, GDK_WINDOW_XID (context_xim->client_window)))
722     return TRUE;
723   
724   if (event->state &
725       (gtk_accelerator_get_default_mod_mask () & ~(GDK_SHIFT_MASK | GDK_CONTROL_MASK))) 
726     return FALSE;
727
728  again:
729   if (ic)
730     num_bytes = XmbLookupString (ic, &xevent, buffer, buffer_size, &keysym, &status);
731   else
732     {
733       num_bytes = XLookupString (&xevent, buffer, buffer_size, &keysym, NULL);
734       status = XLookupBoth;
735     }
736
737   if (status == XBufferOverflow)
738     {
739       buffer_size = num_bytes;
740       if (buffer != static_buffer) 
741         g_free (buffer);
742       buffer = g_malloc (num_bytes + 1);
743       goto again;
744     }
745
746   /* I don't know how we should properly handle XLookupKeysym or XLookupBoth
747    * here ... do input methods actually change the keysym? we can't really
748    * feed it back to accelerator processing at this point...
749    */
750   if (status == XLookupChars || status == XLookupBoth)
751     {
752       char *result_utf8;
753
754       buffer[num_bytes] = '\0';
755
756       result_utf8 = mb_to_utf8 (context_xim, buffer);
757       if (result_utf8)
758         {
759           if ((guchar)result_utf8[0] >= 0x20 &&
760               result_utf8[0] != 0x7f) /* Some IM have a nasty habit of converting
761                                        * control characters into strings
762                                        */
763             {
764               g_signal_emit_by_name (context, "commit", result_utf8);
765               result = TRUE;
766             }
767           
768           g_free (result_utf8);
769         }
770     }
771
772   if (buffer != static_buffer) 
773     g_free (buffer);
774
775   return result;
776 }
777
778 static void
779 gtk_im_context_xim_focus_in (GtkIMContext *context)
780 {
781   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
782
783   if (!context_xim->has_focus)
784     {
785       XIC ic = gtk_im_context_xim_get_ic (context_xim);
786
787       context_xim->has_focus = TRUE;
788       update_status_window (context_xim);
789       
790       if (ic)
791         XSetICFocus (ic);
792     }
793
794   return;
795 }
796
797 static void
798 gtk_im_context_xim_focus_out (GtkIMContext *context)
799 {
800   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
801
802   if (context_xim->has_focus)
803     {
804       XIC ic = gtk_im_context_xim_get_ic (context_xim);
805       
806       context_xim->has_focus = FALSE;
807       update_status_window (context_xim);
808   
809       if (ic)
810         XUnsetICFocus (ic);
811     }
812
813   return;
814 }
815
816 static void
817 gtk_im_context_xim_set_cursor_location (GtkIMContext *context,
818                                         GdkRectangle *area)
819 {
820   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
821   XIC ic = gtk_im_context_xim_get_ic (context_xim);
822
823   XVaNestedList preedit_attr;
824   XPoint          spot;
825
826   if (!ic)
827     return;
828
829   spot.x = area->x;
830   spot.y = area->y + area->height;
831
832   preedit_attr = XVaCreateNestedList (0,
833                                       XNSpotLocation, &spot,
834                                       NULL);
835   XSetICValues (ic,
836                 XNPreeditAttributes, preedit_attr,
837                 NULL);
838   XFree(preedit_attr);
839
840   return;
841 }
842
843 static void
844 gtk_im_context_xim_set_use_preedit (GtkIMContext *context,
845                                     gboolean      use_preedit)
846 {
847   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
848
849   use_preedit = use_preedit != FALSE;
850
851   if (context_xim->use_preedit != use_preedit)
852     {
853       context_xim->use_preedit = use_preedit;
854       reinitialize_ic (context_xim);
855     }
856
857   return;
858 }
859
860 static void
861 gtk_im_context_xim_reset (GtkIMContext *context)
862 {
863   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
864   XIC ic = gtk_im_context_xim_get_ic (context_xim);
865   gchar *result;
866
867   /* restore conversion state after resetting ic later */
868   XIMPreeditState preedit_state = XIMPreeditUnKnown;
869   XVaNestedList preedit_attr;
870   gboolean have_preedit_state = FALSE;
871
872   if (!ic)
873     return;
874   
875
876   if (context_xim->preedit_length == 0)
877     return;
878
879   preedit_attr = XVaCreateNestedList(0,
880                                      XNPreeditState, &preedit_state,
881                                      NULL);
882   if (!XGetICValues(ic,
883                     XNPreeditAttributes, preedit_attr,
884                     NULL))
885     have_preedit_state = TRUE;
886
887   XFree(preedit_attr);
888
889   result = XmbResetIC (ic);
890
891   preedit_attr = XVaCreateNestedList(0,
892                                      XNPreeditState, preedit_state,
893                                      NULL);
894   if (have_preedit_state)
895     XSetICValues(ic,
896                  XNPreeditAttributes, preedit_attr,
897                  NULL);
898
899   XFree(preedit_attr);
900
901   if (result)
902     {
903       char *result_utf8 = mb_to_utf8 (context_xim, result);
904       if (result_utf8)
905         {
906           g_signal_emit_by_name (context, "commit", result_utf8);
907           g_free (result_utf8);
908         }
909     }
910
911   if (context_xim->preedit_length)
912     {
913       context_xim->preedit_length = 0;
914       g_signal_emit_by_name (context, "preedit-changed");
915     }
916
917   XFree (result);
918 }
919
920 /* Mask of feedback bits that we render
921  */
922 #define FEEDBACK_MASK (XIMReverse | XIMUnderline)
923
924 static void
925 add_feedback_attr (PangoAttrList *attrs,
926                    const gchar   *str,
927                    XIMFeedback    feedback,
928                    gint           start_pos,
929                    gint           end_pos)
930 {
931   PangoAttribute *attr;
932   
933   gint start_index = g_utf8_offset_to_pointer (str, start_pos) - str;
934   gint end_index = g_utf8_offset_to_pointer (str, end_pos) - str;
935
936   if (feedback & XIMUnderline)
937     {
938       attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
939       attr->start_index = start_index;
940       attr->end_index = end_index;
941
942       pango_attr_list_change (attrs, attr);
943     }
944
945   if (feedback & XIMReverse)
946     {
947       attr = pango_attr_foreground_new (0xffff, 0xffff, 0xffff);
948       attr->start_index = start_index;
949       attr->end_index = end_index;
950
951       pango_attr_list_change (attrs, attr);
952
953       attr = pango_attr_background_new (0, 0, 0);
954       attr->start_index = start_index;
955       attr->end_index = end_index;
956
957       pango_attr_list_change (attrs, attr);
958     }
959
960   if (feedback & ~FEEDBACK_MASK)
961     g_warning ("Unrendered feedback style: %#lx", feedback & ~FEEDBACK_MASK);
962 }
963
964 static void     
965 gtk_im_context_xim_get_preedit_string (GtkIMContext   *context,
966                                        gchar         **str,
967                                        PangoAttrList **attrs,
968                                        gint           *cursor_pos)
969 {
970   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
971   gchar *utf8 = g_ucs4_to_utf8 (context_xim->preedit_chars, context_xim->preedit_length, NULL, NULL, NULL);
972
973   if (attrs)
974     {
975       int i;
976       XIMFeedback last_feedback = 0;
977       gint start = -1;
978       
979       *attrs = pango_attr_list_new ();
980
981       for (i = 0; i < context_xim->preedit_length; i++)
982         {
983           XIMFeedback new_feedback = context_xim->feedbacks[i] & FEEDBACK_MASK;
984           if (new_feedback != last_feedback)
985             {
986               if (start >= 0)
987                 add_feedback_attr (*attrs, utf8, last_feedback, start, i);
988               
989               last_feedback = new_feedback;
990               start = i;
991             }
992         }
993
994       if (start >= 0)
995         add_feedback_attr (*attrs, utf8, last_feedback, start, i);
996     }
997
998   if (str)
999     *str = utf8;
1000   else
1001     g_free (utf8);
1002
1003   if (cursor_pos)
1004     *cursor_pos = context_xim->preedit_cursor;
1005 }
1006
1007 static int
1008 preedit_start_callback (XIC      xic,
1009                         XPointer client_data,
1010                         XPointer call_data)
1011 {
1012   GtkIMContext *context = GTK_IM_CONTEXT (client_data);
1013   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
1014   
1015   if (!context_xim->finalizing)
1016     g_signal_emit_by_name (context, "preedit-start");
1017
1018   return -1;                    /* No length limit */
1019 }                    
1020
1021 static void
1022 preedit_done_callback (XIC      xic,
1023                      XPointer client_data,
1024                      XPointer call_data)
1025 {
1026   GtkIMContext *context = GTK_IM_CONTEXT (client_data);
1027   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
1028
1029   if (context_xim->preedit_length)
1030     {
1031       context_xim->preedit_length = 0;
1032       if (!context_xim->finalizing)
1033         g_signal_emit_by_name (context_xim, "preedit-changed");
1034     }
1035
1036   if (!context_xim->finalizing)
1037     g_signal_emit_by_name (context, "preedit-end");
1038 }                    
1039
1040 static gint
1041 xim_text_to_utf8 (GtkIMContextXIM *context, XIMText *xim_text, gchar **text)
1042 {
1043   gint text_length = 0;
1044   GError *error = NULL;
1045   gchar *result = NULL;
1046
1047   if (xim_text && xim_text->string.multi_byte)
1048     {
1049       if (xim_text->encoding_is_wchar)
1050         {
1051           g_warning ("Wide character return from Xlib not currently supported");
1052           *text = NULL;
1053           return 0;
1054         }
1055
1056       if (strcmp (context->mb_charset, "UTF-8") == 0)
1057         result = g_strdup (xim_text->string.multi_byte);
1058       else
1059         result = g_convert (xim_text->string.multi_byte,
1060                             -1,
1061                             "UTF-8",
1062                             context->mb_charset,
1063                             NULL, NULL, &error);
1064       
1065       if (result)
1066         {
1067           text_length = g_utf8_strlen (result, -1);
1068           
1069           if (text_length != xim_text->length)
1070             {
1071               g_warning ("Size mismatch when converting text from input method: supplied length = %d\n, result length = %d", xim_text->length, text_length);
1072             }
1073         }
1074       else
1075         {
1076           g_warning ("Error converting text from IM to UCS-4: %s", error->message);
1077           g_error_free (error);
1078
1079           *text = NULL;
1080           return 0;
1081         }
1082
1083       *text = result;
1084       return text_length;
1085     }
1086   else
1087     {
1088       *text = NULL;
1089       return 0;
1090     }
1091 }
1092
1093 static void
1094 preedit_draw_callback (XIC                           xic, 
1095                        XPointer                      client_data,
1096                        XIMPreeditDrawCallbackStruct *call_data)
1097 {
1098   GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
1099
1100   XIMText *new_xim_text = call_data->text;
1101   gint new_text_length;
1102   gunichar *new_text = NULL;
1103   gint i;
1104   gint diff;
1105   gint new_length;
1106   gchar *tmp;
1107   
1108   gint chg_first = CLAMP (call_data->chg_first, 0, context->preedit_length);
1109   gint chg_length = CLAMP (call_data->chg_length, 0, context->preedit_length - chg_first);
1110
1111   context->preedit_cursor = call_data->caret;
1112   
1113   if (chg_first != call_data->chg_first || chg_length != call_data->chg_length)
1114     g_warning ("Invalid change to preedit string, first=%d length=%d (orig length == %d)",
1115                call_data->chg_first, call_data->chg_length, context->preedit_length);
1116
1117   new_text_length = xim_text_to_utf8 (context, new_xim_text, &tmp);
1118   if (tmp)
1119     {
1120       new_text = g_utf8_to_ucs4_fast (tmp, -1, NULL);
1121       g_free (tmp);
1122     }
1123   
1124   diff = new_text_length - chg_length;
1125   new_length = context->preedit_length + diff;
1126
1127   if (new_length > context->preedit_size)
1128     {
1129       context->preedit_size = new_length;
1130       context->preedit_chars = g_renew (gunichar, context->preedit_chars, new_length);
1131       context->feedbacks = g_renew (XIMFeedback, context->feedbacks, new_length);
1132     }
1133
1134   if (diff < 0)
1135     {
1136       for (i = chg_first + chg_length ; i < context->preedit_length; i++)
1137         {
1138           context->preedit_chars[i + diff] = context->preedit_chars[i];
1139           context->feedbacks[i + diff] = context->feedbacks[i];
1140         }
1141     }
1142   else
1143     {
1144       for (i = context->preedit_length - 1; i >= chg_first + chg_length ; i--)
1145         {
1146           context->preedit_chars[i + diff] = context->preedit_chars[i];
1147           context->feedbacks[i + diff] = context->feedbacks[i];
1148         }
1149     }
1150
1151   for (i = 0; i < new_text_length; i++)
1152     {
1153       context->preedit_chars[chg_first + i] = new_text[i];
1154       context->feedbacks[chg_first + i] = new_xim_text->feedback[i];
1155     }
1156
1157   context->preedit_length += diff;
1158
1159   g_free (new_text);
1160
1161   if (!context->finalizing)
1162     g_signal_emit_by_name (context, "preedit-changed");
1163 }
1164     
1165
1166 static void
1167 preedit_caret_callback (XIC                            xic,
1168                         XPointer                       client_data,
1169                         XIMPreeditCaretCallbackStruct *call_data)
1170 {
1171   GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
1172   
1173   if (call_data->direction == XIMAbsolutePosition)
1174     {
1175       context->preedit_cursor = call_data->position;
1176       if (!context->finalizing)
1177         g_signal_emit_by_name (context, "preedit-changed");
1178     }
1179   else
1180     {
1181       g_warning ("Caret movement command: %d %d %d not supported",
1182                  call_data->position, call_data->direction, call_data->style);
1183     }
1184 }            
1185
1186 static void
1187 status_start_callback (XIC      xic,
1188                        XPointer client_data,
1189                        XPointer call_data)
1190 {
1191   return;
1192
1193
1194 static void
1195 status_done_callback (XIC      xic,
1196                       XPointer client_data,
1197                       XPointer call_data)
1198 {
1199   return;
1200 }
1201
1202 static void
1203 status_draw_callback (XIC      xic,
1204                       XPointer client_data,
1205                       XIMStatusDrawCallbackStruct *call_data)
1206 {
1207   GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
1208
1209   if (call_data->type == XIMTextType)
1210     {
1211       gchar *text;
1212       xim_text_to_utf8 (context, call_data->data.text, &text);
1213
1214       if (context->status_window)
1215         status_window_set_text (context->status_window, text ? text : "");
1216     }
1217   else                          /* bitmap */
1218     {
1219       g_print ("Status drawn with bitmap - id = %#lx\n", call_data->data.bitmap);
1220     }
1221 }
1222
1223 static void
1224 string_conversion_callback (XIC xic, XPointer client_data, XPointer call_data)
1225 {
1226   GtkIMContextXIM *context_xim;
1227   XIMStringConversionCallbackStruct *conv_data;
1228   gchar *surrounding;
1229   gint  cursor_index;
1230
1231   context_xim = (GtkIMContextXIM *)client_data;
1232   conv_data = (XIMStringConversionCallbackStruct *)call_data;
1233
1234   if (gtk_im_context_get_surrounding ((GtkIMContext *)context_xim,
1235                                       &surrounding, &cursor_index))
1236     {
1237       gchar *text = NULL;
1238       gsize text_len = 0;
1239       gint  subst_offset = 0, subst_nchars = 0;
1240       gint  i;
1241       gchar *p = surrounding + cursor_index, *q;
1242       gshort position = (gshort)conv_data->position;
1243
1244       if (position > 0)
1245         {
1246           for (i = position; i > 0 && *p; --i)
1247             p = g_utf8_next_char (p);
1248           if (i > 0)
1249             return;
1250         }
1251       /* According to X11R6.4 Xlib - C Library Reference Manual
1252        * section 13.5.7.3 String Conversion Callback,
1253        * XIMStringConversionPosition is starting position _relative_
1254        * to current client's cursor position. So it should be able
1255        * to be negative, or referring to a position before the cursor
1256        * would be impossible. But current X protocol defines this as
1257        * unsigned short. So, compiler may warn about the value range
1258        * here. We hope the X protocol is fixed soon.
1259        */
1260       else if (position < 0)
1261         {
1262           for (i = position; i < 0 && p > surrounding; ++i)
1263             p = g_utf8_prev_char (p);
1264           if (i < 0)
1265             return;
1266         }
1267
1268       switch (conv_data->direction)
1269         {
1270         case XIMForwardChar:
1271           for (i = conv_data->factor, q = p; i > 0 && *q; --i)
1272             q = g_utf8_next_char (q);
1273           if (i > 0)
1274             break;
1275           text = g_locale_from_utf8 (p, q - p, NULL, &text_len, NULL);
1276           subst_offset = position;
1277           subst_nchars = conv_data->factor;
1278           break;
1279
1280         case XIMBackwardChar:
1281           for (i = conv_data->factor, q = p; i > 0 && q > surrounding; --i)
1282             q = g_utf8_prev_char (q);
1283           if (i > 0)
1284             break;
1285           text = g_locale_from_utf8 (q, p - q, NULL, &text_len, NULL);
1286           subst_offset = position - conv_data->factor;
1287           subst_nchars = conv_data->factor;
1288           break;
1289
1290         case XIMForwardWord:
1291         case XIMBackwardWord:
1292         case XIMCaretUp:
1293         case XIMCaretDown:
1294         case XIMNextLine:
1295         case XIMPreviousLine:
1296         case XIMLineStart:
1297         case XIMLineEnd:
1298         case XIMAbsolutePosition:
1299         case XIMDontChange:
1300         default:
1301           break;
1302         }
1303       /* block out any failure happenning to "text", including conversion */
1304       if (text)
1305         {
1306           conv_data->text = (XIMStringConversionText *)
1307                               malloc (sizeof (XIMStringConversionText));
1308           if (conv_data->text)
1309             {
1310               conv_data->text->length = text_len;
1311               conv_data->text->feedback = NULL;
1312               conv_data->text->encoding_is_wchar = False;
1313               conv_data->text->string.mbs = (char *)malloc (text_len);
1314               if (conv_data->text->string.mbs)
1315                 memcpy (conv_data->text->string.mbs, text, text_len);
1316               else
1317                 {
1318                   free (conv_data->text);
1319                   conv_data->text = NULL;
1320                 }
1321             }
1322
1323           g_free (text);
1324         }
1325       if (conv_data->operation == XIMStringConversionSubstitution
1326           && subst_nchars > 0)
1327         {
1328           gtk_im_context_delete_surrounding ((GtkIMContext *)context_xim,
1329                                             subst_offset, subst_nchars);
1330         }
1331
1332       g_free (surrounding);
1333     }
1334 }
1335
1336
1337 static XVaNestedList
1338 set_preedit_callback (GtkIMContextXIM *context_xim)
1339 {
1340   context_xim->preedit_start_callback.client_data = (XPointer)context_xim;
1341   context_xim->preedit_start_callback.callback = (XIMProc)preedit_start_callback;
1342   context_xim->preedit_done_callback.client_data = (XPointer)context_xim;
1343   context_xim->preedit_done_callback.callback = (XIMProc)preedit_done_callback;
1344   context_xim->preedit_draw_callback.client_data = (XPointer)context_xim;
1345   context_xim->preedit_draw_callback.callback = (XIMProc)preedit_draw_callback;
1346   context_xim->preedit_caret_callback.client_data = (XPointer)context_xim;
1347   context_xim->preedit_caret_callback.callback = (XIMProc)preedit_caret_callback;
1348   return XVaCreateNestedList (0,
1349                               XNPreeditStartCallback, &context_xim->preedit_start_callback,
1350                               XNPreeditDoneCallback, &context_xim->preedit_done_callback,
1351                               XNPreeditDrawCallback, &context_xim->preedit_draw_callback,
1352                               XNPreeditCaretCallback, &context_xim->preedit_caret_callback,
1353                               NULL);
1354 }
1355
1356 static XVaNestedList
1357 set_status_callback (GtkIMContextXIM *context_xim)
1358 {
1359   context_xim->status_start_callback.client_data = (XPointer)context_xim;
1360   context_xim->status_start_callback.callback = (XIMProc)status_start_callback;
1361   context_xim->status_done_callback.client_data = (XPointer)context_xim;
1362   context_xim->status_done_callback.callback = (XIMProc)status_done_callback;
1363   context_xim->status_draw_callback.client_data = (XPointer)context_xim;
1364   context_xim->status_draw_callback.callback = (XIMProc)status_draw_callback;
1365           
1366   return XVaCreateNestedList (0,
1367                               XNStatusStartCallback, &context_xim->status_start_callback,
1368                               XNStatusDoneCallback, &context_xim->status_done_callback,
1369                               XNStatusDrawCallback, &context_xim->status_draw_callback,
1370                               NULL);
1371 }
1372
1373
1374 static void
1375 set_string_conversion_callback (GtkIMContextXIM *context_xim, XIC xic)
1376 {
1377   if (!context_xim->im_info->supports_string_conversion)
1378     return;
1379   
1380   context_xim->string_conversion_callback.client_data = (XPointer)context_xim;
1381   context_xim->string_conversion_callback.callback = (XIMProc)string_conversion_callback;
1382   
1383   XSetICValues (xic,
1384                 XNStringConversionCallback,
1385                 (XPointer)&context_xim->string_conversion_callback,
1386                 NULL);
1387 }
1388
1389 static XIC
1390 gtk_im_context_xim_get_ic (GtkIMContextXIM *context_xim)
1391 {
1392   if (context_xim->im_info == NULL || context_xim->im_info->im == NULL)
1393     return NULL;
1394
1395   if (!context_xim->ic)
1396     {
1397       const char *name1 = NULL;
1398       XVaNestedList list1 = NULL;
1399       const char *name2 = NULL;
1400       XVaNestedList list2 = NULL;
1401       XIMStyle im_style = 0;
1402       XIC xic = NULL;
1403
1404       if (context_xim->use_preedit &&
1405           (context_xim->im_info->style & PREEDIT_MASK) == XIMPreeditCallbacks)
1406         {
1407           im_style |= XIMPreeditCallbacks;
1408           name1 = XNPreeditAttributes;
1409           list1 = set_preedit_callback (context_xim);
1410         }
1411       else if ((context_xim->im_info->style & PREEDIT_MASK) == XIMPreeditNone)
1412         im_style |= XIMPreeditNone;
1413       else
1414         im_style |= XIMPreeditNothing;
1415
1416       if ((context_xim->im_info->style & STATUS_MASK) == XIMStatusCallbacks)
1417         {
1418           im_style |= XIMStatusCallbacks;
1419           if (name1 == NULL)
1420             {
1421               name1 = XNStatusAttributes;
1422               list1 = set_status_callback (context_xim);
1423             }
1424           else
1425             {
1426               name2 = XNStatusAttributes;
1427               list2 = set_status_callback (context_xim);
1428             }
1429         }
1430       else if ((context_xim->im_info->style & STATUS_MASK) == XIMStatusNone)
1431         im_style |= XIMStatusNone;
1432       else
1433         im_style |= XIMStatusNothing;
1434
1435       xic = XCreateIC (context_xim->im_info->im,
1436                        XNInputStyle, im_style,
1437                        XNClientWindow, GDK_WINDOW_XID (context_xim->client_window),
1438                        name1, list1,
1439                        name2, list2,
1440                        NULL);
1441       if (list1)
1442         XFree (list1);
1443       if (list2)
1444         XFree (list2);
1445
1446       if (xic)
1447         {
1448           /* Don't filter key released events with XFilterEvents unless
1449            * input methods ask for. This is a workaround for Solaris input
1450            * method bug in C and European locales. It doubles each key
1451            * stroke if both key pressed and released events are filtered.
1452            * (bugzilla #81759)
1453            */
1454           gulong mask = 0xaaaaaaaa;
1455           XGetICValues (xic,
1456                         XNFilterEvents, &mask,
1457                         NULL);
1458           context_xim->filter_key_release = (mask & KeyReleaseMask) != 0;
1459           set_string_conversion_callback (context_xim, xic);
1460         }
1461       
1462       context_xim->ic = xic;
1463
1464       update_status_window (context_xim);
1465       
1466       if (xic && context_xim->has_focus)
1467         XSetICFocus (xic);
1468     }
1469   return context_xim->ic;
1470 }
1471
1472 /*****************************************************************
1473  * Status Window handling
1474  *
1475  * A status window is a small window attached to the toplevel
1476  * that is used to display information to the user about the
1477  * current input operation.
1478  *
1479  * We claim the toplevel's status window for an input context if:
1480  *
1481  * A) The input context has a toplevel
1482  * B) The input context has the focus
1483  * C) The input context has an XIC associated with it
1484  *
1485  * Tracking A) and C) is pretty reliable since we
1486  * compute A) and create the XIC for C) ourselves.
1487  * For B) we basically have to depend on our callers
1488  * calling ::focus-in and ::focus-out at the right time.
1489  *
1490  * The toplevel is computed by walking up the GdkWindow
1491  * hierarchy from context->client_window until we find a
1492  * window that is owned by some widget, and then calling
1493  * gtk_widget_get_toplevel() on that widget. This should
1494  * handle both cases where we might have GdkWindows without widgets,
1495  * and cases where GtkWidgets have strange window hierarchies
1496  * (like a torn off GtkHandleBox.)
1497  *
1498  * The status window is visible if and only if there is text
1499  * for it; whenever a new GtkIMContextXIM claims the status
1500  * window, we blank out any existing text. We actually only
1501  * create a GtkWindow for the status window the first time
1502  * it is shown; this is an important optimization when we are
1503  * using XIM with something like a simple compose-key input
1504  * method that never needs a status window.
1505  *****************************************************************/
1506
1507 /* Called when we no longer need a status window
1508 */
1509 static void
1510 disclaim_status_window (GtkIMContextXIM *context_xim)
1511 {
1512   if (context_xim->status_window)
1513     {
1514       g_assert (context_xim->status_window->context == context_xim);
1515
1516       status_window_set_text (context_xim->status_window, "");
1517       
1518       context_xim->status_window->context = NULL;
1519       context_xim->status_window = NULL;
1520     }
1521 }
1522
1523 /* Called when we need a status window
1524  */
1525 static void
1526 claim_status_window (GtkIMContextXIM *context_xim)
1527 {
1528   if (!context_xim->status_window && context_xim->client_widget)
1529     {
1530       GtkWidget *toplevel = gtk_widget_get_toplevel (context_xim->client_widget);
1531       if (toplevel && gtk_widget_is_toplevel (toplevel))
1532         {
1533           StatusWindow *status_window = status_window_get (toplevel);
1534
1535           if (status_window->context)
1536             disclaim_status_window (status_window->context);
1537
1538           status_window->context = context_xim;
1539           context_xim->status_window = status_window;
1540         }
1541     }
1542 }
1543
1544 /* Basic call made whenever something changed that might cause
1545  * us to need, or not to need a status window.
1546  */
1547 static void
1548 update_status_window (GtkIMContextXIM *context_xim)
1549 {
1550   if (context_xim->ic && context_xim->in_toplevel && context_xim->has_focus)
1551     claim_status_window (context_xim);
1552   else
1553     disclaim_status_window (context_xim);
1554 }
1555
1556 /* Updates the in_toplevel flag for @context_xim
1557  */
1558 static void
1559 update_in_toplevel (GtkIMContextXIM *context_xim)
1560 {
1561   if (context_xim->client_widget)
1562     {
1563       GtkWidget *toplevel = gtk_widget_get_toplevel (context_xim->client_widget);
1564       
1565       context_xim->in_toplevel = (toplevel && gtk_widget_is_toplevel (toplevel));
1566     }
1567   else
1568     context_xim->in_toplevel = FALSE;
1569
1570   /* Some paranoia, in case we don't get a focus out */
1571   if (!context_xim->in_toplevel)
1572     context_xim->has_focus = FALSE;
1573   
1574   update_status_window (context_xim);
1575 }
1576
1577 /* Callback when @widget's toplevel changes. It will always
1578  * change from NULL to a window, or a window to NULL;
1579  * we use that intermediate NULL state to make sure
1580  * that we disclaim the toplevel status window for the old
1581  * window.
1582  */
1583 static void
1584 on_client_widget_hierarchy_changed (GtkWidget       *widget,
1585                                     GtkWidget       *old_toplevel,
1586                                     GtkIMContextXIM *context_xim)
1587 {
1588   update_in_toplevel (context_xim);
1589 }
1590
1591 /* Finds the GtkWidget that owns the window, or if none, the
1592  * widget owning the nearest parent that has a widget.
1593  */
1594 static GtkWidget *
1595 widget_for_window (GdkWindow *window)
1596 {
1597   while (window)
1598     {
1599       gpointer user_data;
1600       gdk_window_get_user_data (window, &user_data);
1601       if (user_data)
1602         return user_data;
1603
1604       window = gdk_window_get_parent (window);
1605     }
1606
1607   return NULL;
1608 }
1609
1610 /* Called when context_xim->client_window changes; takes care of
1611  * removing and/or setting up our watches for the toplevel
1612  */
1613 static void
1614 update_client_widget (GtkIMContextXIM *context_xim)
1615 {
1616   GtkWidget *new_client_widget = widget_for_window (context_xim->client_window);
1617
1618   if (new_client_widget != context_xim->client_widget)
1619     {
1620       if (context_xim->client_widget)
1621         {
1622           g_signal_handlers_disconnect_by_func (context_xim->client_widget,
1623                                                 G_CALLBACK (on_client_widget_hierarchy_changed),
1624                                                 context_xim);
1625         }
1626       context_xim->client_widget = new_client_widget;
1627       if (context_xim->client_widget)
1628         {
1629           g_signal_connect (context_xim->client_widget, "hierarchy-changed",
1630                             G_CALLBACK (on_client_widget_hierarchy_changed),
1631                             context_xim);
1632         }
1633
1634       update_in_toplevel (context_xim);
1635     }
1636 }
1637
1638 /* Called when the toplevel is destroyed; frees the status window
1639  */
1640 static void
1641 on_status_toplevel_destroy (GtkWidget    *toplevel,
1642                             StatusWindow *status_window)
1643 {
1644   status_window_free (status_window);
1645 }
1646
1647 /* Called when the screen for the toplevel changes; updates the
1648  * screen for the status window to match.
1649  */
1650 static void
1651 on_status_toplevel_notify_screen (GtkWindow    *toplevel,
1652                                   GParamSpec   *pspec,
1653                                   StatusWindow *status_window)
1654 {
1655   if (status_window->window)
1656     gtk_window_set_screen (GTK_WINDOW (status_window->window),
1657                            gtk_widget_get_screen (GTK_WIDGET (toplevel)));
1658 }
1659
1660 /* Called when the toplevel window is moved; updates the position of
1661  * the status window to follow it.
1662  */
1663 static gboolean
1664 on_status_toplevel_configure (GtkWidget         *toplevel,
1665                               GdkEventConfigure *event,
1666                               StatusWindow      *status_window)
1667 {
1668   GdkRectangle rect;
1669   GtkRequisition requisition;
1670   gint y;
1671   gint height;
1672
1673   if (status_window->window)
1674     {
1675       height = gdk_screen_get_height (gtk_widget_get_screen (toplevel));
1676
1677       gdk_window_get_frame_extents (gtk_widget_get_window (toplevel),
1678                                     &rect);
1679       gtk_widget_get_preferred_size ( (status_window->window),
1680                                  &requisition, NULL);
1681
1682       if (rect.y + rect.height + requisition.height < height)
1683         y = rect.y + rect.height;
1684       else
1685         y = height - requisition.height;
1686       
1687       gtk_window_move (GTK_WINDOW (status_window->window), rect.x, y);
1688     }
1689
1690   return FALSE;
1691 }
1692
1693 /* Frees a status window and removes its link from the status_windows list
1694  */
1695 static void
1696 status_window_free (StatusWindow *status_window)
1697 {
1698   status_windows = g_slist_remove (status_windows, status_window);
1699
1700   if (status_window->context)
1701     status_window->context->status_window = NULL;
1702  
1703   g_signal_handlers_disconnect_by_func (status_window->toplevel,
1704                                         G_CALLBACK (on_status_toplevel_destroy),
1705                                         status_window);
1706   g_signal_handlers_disconnect_by_func (status_window->toplevel,
1707                                         G_CALLBACK (on_status_toplevel_notify_screen),
1708                                         status_window);
1709   g_signal_handlers_disconnect_by_func (status_window->toplevel,
1710                                         G_CALLBACK (on_status_toplevel_configure),
1711                                         status_window);
1712
1713   if (status_window->window)
1714     gtk_widget_destroy (status_window->window);
1715   
1716   g_object_set_data (G_OBJECT (status_window->toplevel), "gtk-im-xim-status-window", NULL);
1717  
1718   g_free (status_window);
1719 }
1720
1721 /* Finds the status window object for a toplevel, creating it if necessary.
1722  */
1723 static StatusWindow *
1724 status_window_get (GtkWidget *toplevel)
1725 {
1726   StatusWindow *status_window;
1727
1728   status_window = g_object_get_data (G_OBJECT (toplevel), "gtk-im-xim-status-window");
1729   if (status_window)
1730     return status_window;
1731   
1732   status_window = g_new0 (StatusWindow, 1);
1733   status_window->toplevel = toplevel;
1734
1735   status_windows = g_slist_prepend (status_windows, status_window);
1736
1737   g_signal_connect (toplevel, "destroy",
1738                     G_CALLBACK (on_status_toplevel_destroy),
1739                     status_window);
1740   g_signal_connect (toplevel, "configure-event",
1741                     G_CALLBACK (on_status_toplevel_configure),
1742                     status_window);
1743   g_signal_connect (toplevel, "notify::screen",
1744                     G_CALLBACK (on_status_toplevel_notify_screen),
1745                     status_window);
1746   
1747   g_object_set_data (G_OBJECT (toplevel), "gtk-im-xim-status-window", status_window);
1748
1749   return status_window;
1750 }
1751
1752 /* Draw the background (normally white) and border for the status window
1753  */
1754 static gboolean
1755 on_status_window_draw (GtkWidget *widget,
1756                        cairo_t   *cr)
1757 {
1758   GtkStyleContext *style;
1759   GdkRGBA color;
1760
1761   style = gtk_widget_get_style_context (widget);
1762
1763   gtk_style_context_get_background_color (style, 0, &color);
1764   gdk_cairo_set_source_rgba (cr, &color);
1765   cairo_paint (cr);
1766
1767   gtk_style_context_get_color (style, 0, &color);
1768   gdk_cairo_set_source_rgba (cr, &color);
1769   cairo_paint (cr);
1770
1771   cairo_rectangle (cr, 
1772                    0, 0,
1773                    gtk_widget_get_allocated_width (widget) - 1,
1774                    gtk_widget_get_allocated_height (widget) - 1);
1775   cairo_fill (cr);
1776
1777   return FALSE;
1778 }
1779
1780 /* Creates the widgets for the status window; called when we
1781  * first need to show text for the status window.
1782  */
1783 static void
1784 status_window_make_window (StatusWindow *status_window)
1785 {
1786   GtkWidget *window;
1787   GtkWidget *status_label;
1788   
1789   status_window->window = gtk_window_new (GTK_WINDOW_POPUP);
1790   window = status_window->window;
1791
1792   gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
1793   gtk_widget_set_app_paintable (window, TRUE);
1794
1795   status_label = gtk_label_new ("");
1796   g_object_set (status_label, "margin", 1, NULL);
1797   gtk_widget_show (status_label);
1798   
1799   gtk_container_add (GTK_CONTAINER (window), status_label);
1800   
1801   g_signal_connect (window, "draw",
1802                     G_CALLBACK (on_status_window_draw), NULL);
1803   
1804   gtk_window_set_screen (GTK_WINDOW (status_window->window),
1805                          gtk_widget_get_screen (status_window->toplevel));
1806
1807   on_status_toplevel_configure (status_window->toplevel, NULL, status_window);
1808 }
1809
1810 /* Updates the text in the status window, hiding or
1811  * showing the window as necessary.
1812  */
1813 static void
1814 status_window_set_text (StatusWindow *status_window,
1815                         const gchar  *text)
1816 {
1817   if (text[0])
1818     {
1819       GtkWidget *label;
1820       
1821       if (!status_window->window)
1822         status_window_make_window (status_window);
1823       
1824       label = gtk_bin_get_child (GTK_BIN (status_window->window));
1825       gtk_label_set_text (GTK_LABEL (label), text);
1826   
1827       gtk_widget_show (status_window->window);
1828     }
1829   else
1830     {
1831       if (status_window->window)
1832         gtk_widget_hide (status_window->window);
1833     }
1834 }
1835
1836 /**
1837  * gtk_im_context_xim_shutdown:
1838  * 
1839  * Destroys all the status windows that are kept by the XIM contexts.  This
1840  * function should only be called by the XIM module exit routine.
1841  **/
1842 void
1843 gtk_im_context_xim_shutdown (void)
1844 {
1845   while (status_windows)
1846     status_window_free (status_windows->data);
1847
1848   while (open_ims)
1849     {
1850       GtkXIMInfo *info = open_ims->data;
1851       GdkDisplay *display = gdk_screen_get_display (info->screen);
1852
1853       xim_info_display_closed (display, FALSE, info);
1854       open_ims = g_slist_remove_link (open_ims, open_ims);
1855     }
1856 }