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 "config.h"
21 #include "locale.h"
22 #include <string.h>
23 #include <stdlib.h>
24 
25 #include "gtkimcontextxim.h"
26 
27 #include "gtk/gtkintl.h"
28 
29 typedef struct _StatusWindow StatusWindow;
30 typedef struct _GtkXIMInfo GtkXIMInfo;
31 
32 struct _GtkIMContextXIM
33 {
34   GtkIMContext object;
35 
36   GtkXIMInfo *im_info;
37 
38   gchar *locale;
39   gchar *mb_charset;
40 
41   GdkWindow *client_window;
42   GtkWidget *client_widget;
43 
44   /* The status window for this input context; we claim the
45    * status window when we are focused and have created an XIC
46    */
47   StatusWindow *status_window;
48 
49   gint preedit_size;
50   gint preedit_length;
51   gunichar *preedit_chars;
52   XIMFeedback *feedbacks;
53 
54   gint preedit_cursor;
55 
56   XIMCallback preedit_start_callback;
57   XIMCallback preedit_done_callback;
58   XIMCallback preedit_draw_callback;
59   XIMCallback preedit_caret_callback;
60 
61   XIMCallback status_start_callback;
62   XIMCallback status_done_callback;
63   XIMCallback status_draw_callback;
64 
65   XIMCallback string_conversion_callback;
66 
67   XIC ic;
68 
69   guint filter_key_release : 1;
70   guint use_preedit : 1;
71   guint finalizing : 1;
72   guint in_toplevel : 1;
73   guint has_focus : 1;
74 };
75 
76 struct _GtkXIMInfo
77 {
78   GdkScreen *screen;
79   XIM im;
80   char *locale;
81   XIMStyle preedit_style_setting;
82   XIMStyle status_style_setting;
83   XIMStyle style;
84   GtkSettings *settings;
85   gulong status_set;
86   gulong preedit_set;
87   gulong display_closed_cb;
88   XIMStyles *xim_styles;
89   GSList *ics;
90 
91   guint reconnecting :1;
92   guint supports_string_conversion;
93 };
94 
95 /* A context status window; these are kept in the status_windows list. */
96 struct _StatusWindow
97 {
98   GtkWidget *window;
99 
100   /* Toplevel window to which the status window corresponds */
101   GtkWidget *toplevel;
102 
103   /* Currently focused GtkIMContextXIM for the toplevel, if any */
104   GtkIMContextXIM *context;
105 };
106 
107 static void     gtk_im_context_xim_class_init         (GtkIMContextXIMClass  *class);
108 static void     gtk_im_context_xim_init               (GtkIMContextXIM       *im_context_xim);
109 static void     gtk_im_context_xim_finalize           (GObject               *obj);
110 static void     gtk_im_context_xim_set_client_window  (GtkIMContext          *context,
111 						       GdkWindow             *client_window);
112 static gboolean gtk_im_context_xim_filter_keypress    (GtkIMContext          *context,
113 						       GdkEventKey           *key);
114 static void     gtk_im_context_xim_reset              (GtkIMContext          *context);
115 static void     gtk_im_context_xim_focus_in           (GtkIMContext          *context);
116 static void     gtk_im_context_xim_focus_out          (GtkIMContext          *context);
117 static void     gtk_im_context_xim_set_cursor_location (GtkIMContext          *context,
118 						       GdkRectangle		*area);
119 static void     gtk_im_context_xim_set_use_preedit    (GtkIMContext          *context,
120 						       gboolean		      use_preedit);
121 static void     gtk_im_context_xim_get_preedit_string (GtkIMContext          *context,
122 						       gchar                **str,
123 						       PangoAttrList        **attrs,
124 						       gint                  *cursor_pos);
125 
126 static void reinitialize_ic      (GtkIMContextXIM *context_xim);
127 static void set_ic_client_window (GtkIMContextXIM *context_xim,
128 				  GdkWindow       *client_window);
129 
130 static void setup_styles (GtkXIMInfo *info);
131 
132 static void update_client_widget   (GtkIMContextXIM *context_xim);
133 static void update_status_window   (GtkIMContextXIM *context_xim);
134 
135 static StatusWindow *status_window_get      (GtkWidget    *toplevel);
136 static void          status_window_free     (StatusWindow *status_window);
137 static void          status_window_set_text (StatusWindow *status_window,
138 					     const gchar  *text);
139 
140 static void xim_destroy_callback   (XIM      xim,
141 				    XPointer client_data,
142 				    XPointer call_data);
143 
144 static XIC       gtk_im_context_xim_get_ic            (GtkIMContextXIM *context_xim);
145 static void           xim_info_display_closed (GdkDisplay *display,
146 			                       gboolean    is_error,
147 			                       GtkXIMInfo *info);
148 
149 static GObjectClass *parent_class;
150 
151 GType gtk_type_im_context_xim = 0;
152 
153 static GSList *open_ims = NULL;
154 
155 /* List of status windows for different toplevels */
156 static GSList *status_windows = NULL;
157 
158 void
gtk_im_context_xim_register_type(GTypeModule * type_module)159 gtk_im_context_xim_register_type (GTypeModule *type_module)
160 {
161   const GTypeInfo im_context_xim_info =
162   {
163     sizeof (GtkIMContextXIMClass),
164     (GBaseInitFunc) NULL,
165     (GBaseFinalizeFunc) NULL,
166     (GClassInitFunc) gtk_im_context_xim_class_init,
167     NULL,           /* class_finalize */
168     NULL,           /* class_data */
169     sizeof (GtkIMContextXIM),
170     0,
171     (GInstanceInitFunc) gtk_im_context_xim_init,
172   };
173 
174   gtk_type_im_context_xim =
175     g_type_module_register_type (type_module,
176 				 GTK_TYPE_IM_CONTEXT,
177 				 "GtkIMContextXIM",
178 				 &im_context_xim_info, 0);
179 }
180 
181 #define PREEDIT_MASK (XIMPreeditCallbacks | XIMPreeditPosition | \
182 		      XIMPreeditArea | XIMPreeditNothing | XIMPreeditNone)
183 #define STATUS_MASK (XIMStatusCallbacks | XIMStatusArea | \
184 		      XIMStatusNothing | XIMStatusNone)
185 #define ALLOWED_MASK (XIMPreeditCallbacks | XIMPreeditNothing | XIMPreeditNone | \
186 		      XIMStatusCallbacks | XIMStatusNothing | XIMStatusNone)
187 
188 static XIMStyle
choose_better_style(XIMStyle style1,XIMStyle style2)189 choose_better_style (XIMStyle style1, XIMStyle style2)
190 {
191   XIMStyle s1, s2, u;
192 
193   if (style1 == 0) return style2;
194   if (style2 == 0) return style1;
195   if ((style1 & (PREEDIT_MASK | STATUS_MASK))
196     	== (style2 & (PREEDIT_MASK | STATUS_MASK)))
197     return style1;
198 
199   s1 = style1 & PREEDIT_MASK;
200   s2 = style2 & PREEDIT_MASK;
201   u = s1 | s2;
202   if (s1 != s2) {
203     if (u & XIMPreeditCallbacks)
204       return (s1 == XIMPreeditCallbacks) ? style1 : style2;
205     else if (u & XIMPreeditPosition)
206       return (s1 == XIMPreeditPosition) ? style1 :style2;
207     else if (u & XIMPreeditArea)
208       return (s1 == XIMPreeditArea) ? style1 : style2;
209     else if (u & XIMPreeditNothing)
210       return (s1 == XIMPreeditNothing) ? style1 : style2;
211     else if (u & XIMPreeditNone)
212       return (s1 == XIMPreeditNone) ? style1 : style2;
213   } else {
214     s1 = style1 & STATUS_MASK;
215     s2 = style2 & STATUS_MASK;
216     u = s1 | s2;
217     if (u & XIMStatusCallbacks)
218       return (s1 == XIMStatusCallbacks) ? style1 : style2;
219     else if (u & XIMStatusArea)
220       return (s1 == XIMStatusArea) ? style1 : style2;
221     else if (u & XIMStatusNothing)
222       return (s1 == XIMStatusNothing) ? style1 : style2;
223     else if (u & XIMStatusNone)
224       return (s1 == XIMStatusNone) ? style1 : style2;
225   }
226   return 0; /* Get rid of stupid warning */
227 }
228 
229 static void
reinitialize_all_ics(GtkXIMInfo * info)230 reinitialize_all_ics (GtkXIMInfo *info)
231 {
232   GSList *tmp_list;
233 
234   for (tmp_list = info->ics; tmp_list; tmp_list = tmp_list->next)
235     reinitialize_ic (tmp_list->data);
236 }
237 
238 static void
status_style_change(GtkXIMInfo * info)239 status_style_change (GtkXIMInfo *info)
240 {
241   GtkIMStatusStyle status_style;
242 
243   g_object_get (info->settings,
244 		"gtk-im-status-style", &status_style,
245 		NULL);
246   if (status_style == GTK_IM_STATUS_CALLBACK)
247     info->status_style_setting = XIMStatusCallbacks;
248   else if (status_style == GTK_IM_STATUS_NOTHING)
249     info->status_style_setting = XIMStatusNothing;
250   else if (status_style == GTK_IM_STATUS_NONE)
251     info->status_style_setting = XIMStatusNone;
252   else
253     return;
254 
255   setup_styles (info);
256 
257   reinitialize_all_ics (info);
258 }
259 
260 static void
preedit_style_change(GtkXIMInfo * info)261 preedit_style_change (GtkXIMInfo *info)
262 {
263   GtkIMPreeditStyle preedit_style;
264   g_object_get (info->settings,
265 		"gtk-im-preedit-style", &preedit_style,
266 		NULL);
267   if (preedit_style == GTK_IM_PREEDIT_CALLBACK)
268     info->preedit_style_setting = XIMPreeditCallbacks;
269   else if (preedit_style == GTK_IM_PREEDIT_NOTHING)
270     info->preedit_style_setting = XIMPreeditNothing;
271   else if (preedit_style == GTK_IM_PREEDIT_NONE)
272     info->preedit_style_setting = XIMPreeditNone;
273   else
274     return;
275 
276   setup_styles (info);
277 
278   reinitialize_all_ics (info);
279 }
280 
281 static void
setup_styles(GtkXIMInfo * info)282 setup_styles (GtkXIMInfo *info)
283 {
284   int i;
285   unsigned long settings_preference;
286   XIMStyles *xim_styles = info->xim_styles;
287 
288   settings_preference = info->status_style_setting|info->preedit_style_setting;
289   info->style = 0;
290   if (xim_styles)
291     {
292       for (i = 0; i < xim_styles->count_styles; i++)
293 	if ((xim_styles->supported_styles[i] & ALLOWED_MASK) == xim_styles->supported_styles[i])
294 	  {
295 	    if (settings_preference == xim_styles->supported_styles[i])
296 	      {
297 		info->style = settings_preference;
298 		break;
299 	      }
300 	    info->style = choose_better_style (info->style,
301 					       xim_styles->supported_styles[i]);
302 	  }
303     }
304   if (info->style == 0)
305     info->style = XIMPreeditNothing | XIMStatusNothing;
306 }
307 
308 static void
setup_im(GtkXIMInfo * info)309 setup_im (GtkXIMInfo *info)
310 {
311   XIMValuesList *ic_values = NULL;
312   XIMCallback im_destroy_callback;
313   GdkDisplay *display;
314 
315   if (info->im == NULL)
316     return;
317 
318   im_destroy_callback.client_data = (XPointer)info;
319   im_destroy_callback.callback = (XIMProc)xim_destroy_callback;
320   XSetIMValues (info->im,
321 		XNDestroyCallback, &im_destroy_callback,
322 		NULL);
323 
324   XGetIMValues (info->im,
325 		XNQueryInputStyle, &info->xim_styles,
326 		XNQueryICValuesList, &ic_values,
327 		NULL);
328 
329   info->settings = gtk_settings_get_for_screen (info->screen);
330   info->status_set = g_signal_connect_swapped (info->settings,
331 					       "notify::gtk-im-status-style",
332 					       G_CALLBACK (status_style_change),
333 					       info);
334   info->preedit_set = g_signal_connect_swapped (info->settings,
335 						"notify::gtk-im-preedit-style",
336 						G_CALLBACK (preedit_style_change),
337 						info);
338 
339   info->supports_string_conversion = FALSE;
340   if (ic_values)
341     {
342       int i;
343 
344       for (i = 0; i < ic_values->count_values; i++)
345 	if (strcmp (ic_values->supported_values[i],
346 		    XNStringConversionCallback) == 0)
347 	  {
348 	    info->supports_string_conversion = TRUE;
349 	    break;
350 	  }
351 
352 #if 0
353       for (i = 0; i < ic_values->count_values; i++)
354 	g_print ("%s\n", ic_values->supported_values[i]);
355       for (i = 0; i < xim_styles->count_styles; i++)
356 	g_print ("%#x\n", xim_styles->supported_styles[i]);
357 #endif
358 
359       XFree (ic_values);
360     }
361 
362   status_style_change (info);
363   preedit_style_change (info);
364 
365   display = gdk_screen_get_display (info->screen);
366   info->display_closed_cb = g_signal_connect (display, "closed",
367 	                                      G_CALLBACK (xim_info_display_closed), info);
368 }
369 
370 static void
xim_info_display_closed(GdkDisplay * display,gboolean is_error,GtkXIMInfo * info)371 xim_info_display_closed (GdkDisplay *display,
372 			 gboolean    is_error,
373 			 GtkXIMInfo *info)
374 {
375   GSList *ics, *tmp_list;
376 
377   open_ims = g_slist_remove (open_ims, info);
378 
379   ics = info->ics;
380   info->ics = NULL;
381 
382   for (tmp_list = ics; tmp_list; tmp_list = tmp_list->next)
383     set_ic_client_window (tmp_list->data, NULL);
384 
385   g_slist_free (ics);
386 
387   if (info->status_set)
388     g_signal_handler_disconnect (info->settings, info->status_set);
389   if (info->preedit_set)
390     g_signal_handler_disconnect (info->settings, info->preedit_set);
391   if (info->display_closed_cb)
392     g_signal_handler_disconnect (display, info->display_closed_cb);
393 
394   if (info->xim_styles)
395     XFree (info->xim_styles);
396   g_free (info->locale);
397 
398   if (info->im)
399     XCloseIM (info->im);
400 
401   g_free (info);
402 }
403 
404 static void
xim_instantiate_callback(Display * display,XPointer client_data,XPointer call_data)405 xim_instantiate_callback (Display *display, XPointer client_data,
406 			  XPointer call_data)
407 {
408   GtkXIMInfo *info = (GtkXIMInfo*)client_data;
409   XIM im = NULL;
410 
411   im = XOpenIM (display, NULL, NULL, NULL);
412 
413   if (!im)
414     return;
415 
416   info->im = im;
417   setup_im (info);
418 
419   XUnregisterIMInstantiateCallback (display, NULL, NULL, NULL,
420 				    xim_instantiate_callback,
421 				    (XPointer)info);
422   info->reconnecting = FALSE;
423 }
424 
425 /* initialize info->im */
426 static void
xim_info_try_im(GtkXIMInfo * info)427 xim_info_try_im (GtkXIMInfo *info)
428 {
429   GdkScreen *screen = info->screen;
430   GdkDisplay *display = gdk_screen_get_display (screen);
431 
432   g_assert (info->im == NULL);
433   if (info->reconnecting)
434     return;
435 
436   if (XSupportsLocale ())
437     {
438       if (!XSetLocaleModifiers (""))
439 	g_warning ("Unable to set locale modifiers with XSetLocaleModifiers()");
440       info->im = XOpenIM (GDK_DISPLAY_XDISPLAY (display), NULL, NULL, NULL);
441       if (!info->im)
442 	{
443 	  XRegisterIMInstantiateCallback (GDK_DISPLAY_XDISPLAY(display),
444 					  NULL, NULL, NULL,
445 					  xim_instantiate_callback,
446 					  (XPointer)info);
447 	  info->reconnecting = TRUE;
448 	  return;
449 	}
450       setup_im (info);
451     }
452 }
453 
454 static void
xim_destroy_callback(XIM xim,XPointer client_data,XPointer call_data)455 xim_destroy_callback (XIM      xim,
456 		      XPointer client_data,
457 		      XPointer call_data)
458 {
459   GtkXIMInfo *info = (GtkXIMInfo*)client_data;
460 
461   info->im = NULL;
462 
463   g_signal_handler_disconnect (info->settings, info->status_set);
464   info->status_set = 0;
465   g_signal_handler_disconnect (info->settings, info->preedit_set);
466   info->preedit_set = 0;
467 
468   reinitialize_all_ics (info);
469   xim_info_try_im (info);
470   return;
471 }
472 
473 static GtkXIMInfo *
get_im(GdkWindow * client_window,const char * locale)474 get_im (GdkWindow *client_window,
475 	const char *locale)
476 {
477   GSList *tmp_list;
478   GtkXIMInfo *info;
479   GdkScreen *screen = gdk_window_get_screen (client_window);
480 
481   info = NULL;
482   tmp_list = open_ims;
483   while (tmp_list)
484     {
485       GtkXIMInfo *tmp_info = tmp_list->data;
486       if (tmp_info->screen == screen &&
487 	  strcmp (tmp_info->locale, locale) == 0)
488 	{
489 	  if (tmp_info->im)
490 	    {
491 	      return tmp_info;
492 	    }
493 	  else
494 	    {
495 	      tmp_info = tmp_info;
496 	      break;
497 	    }
498 	}
499       tmp_list = tmp_list->next;
500     }
501 
502   if (info == NULL)
503     {
504       info = g_new (GtkXIMInfo, 1);
505       open_ims = g_slist_prepend (open_ims, info);
506 
507       info->screen = screen;
508       info->locale = g_strdup (locale);
509       info->xim_styles = NULL;
510       info->preedit_style_setting = 0;
511       info->status_style_setting = 0;
512       info->settings = NULL;
513       info->preedit_set = 0;
514       info->status_set = 0;
515       info->display_closed_cb = 0;
516       info->ics = NULL;
517       info->reconnecting = FALSE;
518       info->im = NULL;
519     }
520 
521   xim_info_try_im (info);
522   return info;
523 }
524 
525 static void
gtk_im_context_xim_class_init(GtkIMContextXIMClass * class)526 gtk_im_context_xim_class_init (GtkIMContextXIMClass *class)
527 {
528   GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class);
529   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
530 
531   parent_class = g_type_class_peek_parent (class);
532 
533   im_context_class->set_client_window = gtk_im_context_xim_set_client_window;
534   im_context_class->filter_keypress = gtk_im_context_xim_filter_keypress;
535   im_context_class->reset = gtk_im_context_xim_reset;
536   im_context_class->get_preedit_string = gtk_im_context_xim_get_preedit_string;
537   im_context_class->focus_in = gtk_im_context_xim_focus_in;
538   im_context_class->focus_out = gtk_im_context_xim_focus_out;
539   im_context_class->set_cursor_location = gtk_im_context_xim_set_cursor_location;
540   im_context_class->set_use_preedit = gtk_im_context_xim_set_use_preedit;
541   gobject_class->finalize = gtk_im_context_xim_finalize;
542 }
543 
544 static void
gtk_im_context_xim_init(GtkIMContextXIM * im_context_xim)545 gtk_im_context_xim_init (GtkIMContextXIM *im_context_xim)
546 {
547   im_context_xim->use_preedit = TRUE;
548   im_context_xim->filter_key_release = FALSE;
549   im_context_xim->finalizing = FALSE;
550   im_context_xim->has_focus = FALSE;
551   im_context_xim->in_toplevel = FALSE;
552 }
553 
554 static void
gtk_im_context_xim_finalize(GObject * obj)555 gtk_im_context_xim_finalize (GObject *obj)
556 {
557   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (obj);
558 
559   context_xim->finalizing = TRUE;
560 
561   if (context_xim->im_info && !context_xim->im_info->ics->next)
562     {
563       if (context_xim->im_info->reconnecting)
564 	{
565 	  GdkDisplay *display;
566 
567 	  display = gdk_screen_get_display (context_xim->im_info->screen);
568 	  XUnregisterIMInstantiateCallback (GDK_DISPLAY_XDISPLAY (display),
569 					    NULL, NULL, NULL,
570 					    xim_instantiate_callback,
571 					    (XPointer)context_xim->im_info);
572 	}
573       else if (context_xim->im_info->im)
574 	{
575 	  XIMCallback im_destroy_callback;
576 
577 	  im_destroy_callback.client_data = NULL;
578 	  im_destroy_callback.callback = NULL;
579 	  XSetIMValues (context_xim->im_info->im,
580 			XNDestroyCallback, &im_destroy_callback,
581 			NULL);
582 	}
583     }
584 
585   set_ic_client_window (context_xim, NULL);
586 
587   g_free (context_xim->locale);
588   g_free (context_xim->mb_charset);
589 
590   G_OBJECT_CLASS (parent_class)->finalize (obj);
591 }
592 
593 static void
reinitialize_ic(GtkIMContextXIM * context_xim)594 reinitialize_ic (GtkIMContextXIM *context_xim)
595 {
596   if (context_xim->ic)
597     {
598       XDestroyIC (context_xim->ic);
599       context_xim->ic = NULL;
600       update_status_window (context_xim);
601 
602       if (context_xim->preedit_length)
603 	{
604 	  context_xim->preedit_length = 0;
605 	  if (!context_xim->finalizing)
606 	    g_signal_emit_by_name (context_xim, "preedit-changed");
607 	}
608     }
609   /*
610      reset filter_key_release flag, otherwise keystrokes will be doubled
611      until reconnecting to XIM.
612   */
613   context_xim->filter_key_release = FALSE;
614 }
615 
616 static void
set_ic_client_window(GtkIMContextXIM * context_xim,GdkWindow * client_window)617 set_ic_client_window (GtkIMContextXIM *context_xim,
618 		      GdkWindow       *client_window)
619 {
620   reinitialize_ic (context_xim);
621   if (context_xim->client_window)
622     {
623       context_xim->im_info->ics = g_slist_remove (context_xim->im_info->ics, context_xim);
624       context_xim->im_info = NULL;
625     }
626 
627   context_xim->client_window = client_window;
628 
629   if (context_xim->client_window)
630     {
631       context_xim->im_info = get_im (context_xim->client_window, context_xim->locale);
632       context_xim->im_info->ics = g_slist_prepend (context_xim->im_info->ics, context_xim);
633     }
634 
635   update_client_widget (context_xim);
636 }
637 
638 static void
gtk_im_context_xim_set_client_window(GtkIMContext * context,GdkWindow * client_window)639 gtk_im_context_xim_set_client_window (GtkIMContext          *context,
640 				      GdkWindow             *client_window)
641 {
642   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
643 
644   set_ic_client_window (context_xim, client_window);
645 }
646 
647 GtkIMContext *
gtk_im_context_xim_new(void)648 gtk_im_context_xim_new (void)
649 {
650   GtkIMContextXIM *result;
651   const gchar *charset;
652 
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 *
mb_to_utf8(GtkIMContextXIM * context_xim,const char * str)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
gtk_im_context_xim_filter_keypress(GtkIMContext * context,GdkEventKey * event)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_DRAWABLE_XDISPLAY (event->window);
711   xevent.window = GDK_DRAWABLE_XID (event->window);
712   xevent.root = GDK_DRAWABLE_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_DRAWABLE_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
gtk_im_context_xim_focus_in(GtkIMContext * context)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
gtk_im_context_xim_focus_out(GtkIMContext * context)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
gtk_im_context_xim_set_cursor_location(GtkIMContext * context,GdkRectangle * area)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
gtk_im_context_xim_set_use_preedit(GtkIMContext * context,gboolean use_preedit)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
gtk_im_context_xim_reset(GtkIMContext * context)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
add_feedback_attr(PangoAttrList * attrs,const gchar * str,XIMFeedback feedback,gint start_pos,gint end_pos)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
gtk_im_context_xim_get_preedit_string(GtkIMContext * context,gchar ** str,PangoAttrList ** attrs,gint * cursor_pos)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
preedit_start_callback(XIC xic,XPointer client_data,XPointer call_data)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
preedit_done_callback(XIC xic,XPointer client_data,XPointer call_data)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
xim_text_to_utf8(GtkIMContextXIM * context,XIMText * xim_text,gchar ** text)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
preedit_draw_callback(XIC xic,XPointer client_data,XIMPreeditDrawCallbackStruct * call_data)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
preedit_caret_callback(XIC xic,XPointer client_data,XIMPreeditCaretCallbackStruct * call_data)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
status_start_callback(XIC xic,XPointer client_data,XPointer call_data)1187 status_start_callback (XIC      xic,
1188 		       XPointer client_data,
1189 		       XPointer call_data)
1190 {
1191   return;
1192 }
1193 
1194 static void
status_done_callback(XIC xic,XPointer client_data,XPointer call_data)1195 status_done_callback (XIC      xic,
1196 		      XPointer client_data,
1197 		      XPointer call_data)
1198 {
1199   return;
1200 }
1201 
1202 static void
status_draw_callback(XIC xic,XPointer client_data,XIMStatusDrawCallbackStruct * call_data)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
string_conversion_callback(XIC xic,XPointer client_data,XPointer call_data)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
set_preedit_callback(GtkIMContextXIM * context_xim)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
set_status_callback(GtkIMContextXIM * context_xim)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
set_string_conversion_callback(GtkIMContextXIM * context_xim,XIC xic)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
gtk_im_context_xim_get_ic(GtkIMContextXIM * context_xim)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_DRAWABLE_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
disclaim_status_window(GtkIMContextXIM * context_xim)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
claim_status_window(GtkIMContextXIM * context_xim)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
update_status_window(GtkIMContextXIM * context_xim)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
update_in_toplevel(GtkIMContextXIM * context_xim)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
on_client_widget_hierarchy_changed(GtkWidget * widget,GtkWidget * old_toplevel,GtkIMContextXIM * context_xim)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 *
widget_for_window(GdkWindow * window)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
update_client_widget(GtkIMContextXIM * context_xim)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
on_status_toplevel_destroy(GtkWidget * toplevel,StatusWindow * status_window)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
on_status_toplevel_notify_screen(GtkWindow * toplevel,GParamSpec * pspec,StatusWindow * status_window)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
on_status_toplevel_configure(GtkWidget * toplevel,GdkEventConfigure * event,StatusWindow * status_window)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 (toplevel->window, &rect);
1678       gtk_widget_size_request (status_window->window, &requisition);
1679 
1680       if (rect.y + rect.height + requisition.height < height)
1681 	y = rect.y + rect.height;
1682       else
1683 	y = height - requisition.height;
1684 
1685       gtk_window_move (GTK_WINDOW (status_window->window), rect.x, y);
1686     }
1687 
1688   return FALSE;
1689 }
1690 
1691 /* Frees a status window and removes its link from the status_windows list
1692  */
1693 static void
status_window_free(StatusWindow * status_window)1694 status_window_free (StatusWindow *status_window)
1695 {
1696   status_windows = g_slist_remove (status_windows, status_window);
1697 
1698   if (status_window->context)
1699     status_window->context->status_window = NULL;
1700 
1701   g_signal_handlers_disconnect_by_func (status_window->toplevel,
1702 					G_CALLBACK (on_status_toplevel_destroy),
1703 					status_window);
1704   g_signal_handlers_disconnect_by_func (status_window->toplevel,
1705 					G_CALLBACK (on_status_toplevel_notify_screen),
1706 					status_window);
1707   g_signal_handlers_disconnect_by_func (status_window->toplevel,
1708 					G_CALLBACK (on_status_toplevel_configure),
1709 					status_window);
1710 
1711   if (status_window->window)
1712     gtk_widget_destroy (status_window->window);
1713 
1714   g_object_set_data (G_OBJECT (status_window->toplevel), "gtk-im-xim-status-window", NULL);
1715 
1716   g_free (status_window);
1717 }
1718 
1719 /* Finds the status window object for a toplevel, creating it if necessary.
1720  */
1721 static StatusWindow *
status_window_get(GtkWidget * toplevel)1722 status_window_get (GtkWidget *toplevel)
1723 {
1724   StatusWindow *status_window;
1725 
1726   status_window = g_object_get_data (G_OBJECT (toplevel), "gtk-im-xim-status-window");
1727   if (status_window)
1728     return status_window;
1729 
1730   status_window = g_new0 (StatusWindow, 1);
1731   status_window->toplevel = toplevel;
1732 
1733   status_windows = g_slist_prepend (status_windows, status_window);
1734 
1735   g_signal_connect (toplevel, "destroy",
1736 		    G_CALLBACK (on_status_toplevel_destroy),
1737 		    status_window);
1738   g_signal_connect (toplevel, "configure-event",
1739 		    G_CALLBACK (on_status_toplevel_configure),
1740 		    status_window);
1741   g_signal_connect (toplevel, "notify::screen",
1742 		    G_CALLBACK (on_status_toplevel_notify_screen),
1743 		    status_window);
1744 
1745   g_object_set_data (G_OBJECT (toplevel), "gtk-im-xim-status-window", status_window);
1746 
1747   return status_window;
1748 }
1749 
1750 /* Creates the widgets for the status window; called when we
1751  * first need to show text for the status window.
1752  */
1753 static void
status_window_make_window(StatusWindow * status_window)1754 status_window_make_window (StatusWindow *status_window)
1755 {
1756   GtkWidget *window;
1757   GtkWidget *status_label;
1758 
1759   status_window->window = gtk_window_new (GTK_WINDOW_POPUP);
1760   window = status_window->window;
1761 
1762   gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
1763 
1764   status_label = gtk_label_new ("");
1765   gtk_misc_set_padding (GTK_MISC (status_label), 1, 1);
1766   gtk_widget_show (status_label);
1767 
1768   gtk_container_add (GTK_CONTAINER (window), status_label);
1769 
1770   gtk_window_set_screen (GTK_WINDOW (status_window->window),
1771 			 gtk_widget_get_screen (status_window->toplevel));
1772 
1773   on_status_toplevel_configure (status_window->toplevel, NULL, status_window);
1774 }
1775 
1776 /* Updates the text in the status window, hiding or
1777  * showing the window as necessary.
1778  */
1779 static void
status_window_set_text(StatusWindow * status_window,const gchar * text)1780 status_window_set_text (StatusWindow *status_window,
1781 			const gchar  *text)
1782 {
1783   if (text[0])
1784     {
1785       GtkWidget *label;
1786 
1787       if (!status_window->window)
1788 	status_window_make_window (status_window);
1789 
1790       label = GTK_BIN (status_window->window)->child;
1791       gtk_label_set_text (GTK_LABEL (label), text);
1792 
1793       gtk_widget_show (status_window->window);
1794     }
1795   else
1796     {
1797       if (status_window->window)
1798 	gtk_widget_hide (status_window->window);
1799     }
1800 }
1801 
1802 /**
1803  * gtk_im_context_xim_shutdown:
1804  *
1805  * Destroys all the status windows that are kept by the XIM contexts.  This
1806  * function should only be called by the XIM module exit routine.
1807  **/
1808 void
gtk_im_context_xim_shutdown(void)1809 gtk_im_context_xim_shutdown (void)
1810 {
1811   while (status_windows)
1812     status_window_free (status_windows->data);
1813 
1814   while (open_ims)
1815     {
1816       GtkXIMInfo *info = open_ims->data;
1817       GdkDisplay *display = gdk_screen_get_display (info->screen);
1818 
1819       xim_info_display_closed (display, FALSE, info);
1820       open_ims = g_slist_remove_link (open_ims, open_ims);
1821     }
1822 }
1823