xref: /vim-8.2.3635/src/gui_xim.c (revision 952d9d82)
1 /* vi:set ts=8 sts=4 sw=4 noet:
2  *
3  * VIM - Vi IMproved	by Bram Moolenaar
4  *
5  * Do ":help uganda"  in Vim to read copying and usage conditions.
6  * Do ":help credits" in Vim to see a list of people who contributed.
7  * See README.txt for an overview of the Vim source code.
8  */
9 
10 /*
11  * gui_xim.c: functions for the X Input Method
12  */
13 
14 #include "vim.h"
15 
16 #if defined(FEAT_GUI_GTK) && defined(FEAT_XIM)
17 # if GTK_CHECK_VERSION(3,0,0)
18 #  include <gdk/gdkkeysyms-compat.h>
19 # else
20 #  include <gdk/gdkkeysyms.h>
21 # endif
22 # ifdef MSWIN
23 #  include <gdk/gdkwin32.h>
24 # else
25 #  include <gdk/gdkx.h>
26 # endif
27 #endif
28 
29 /*
30  * XIM often causes trouble.  Define XIM_DEBUG to get a log of XIM callbacks
31  * in the "xim.log" file.
32  */
33 // #define XIM_DEBUG
34 #if defined(XIM_DEBUG) && defined(FEAT_GUI_GTK)
35 static void xim_log(char *s, ...) ATTRIBUTE_FORMAT_PRINTF(1, 2);
36 
37     static void
xim_log(char * s,...)38 xim_log(char *s, ...)
39 {
40     va_list arglist;
41     static FILE *fd = NULL;
42 
43     if (fd == (FILE *)-1)
44 	return;
45     if (fd == NULL)
46     {
47 	fd = mch_fopen("xim.log", "w");
48 	if (fd == NULL)
49 	{
50 	    emsg("Cannot open xim.log");
51 	    fd = (FILE *)-1;
52 	    return;
53 	}
54     }
55 
56     va_start(arglist, s);
57     vfprintf(fd, s, arglist);
58     va_end(arglist);
59 }
60 #endif
61 
62 #if defined(FEAT_GUI_MSWIN)
63 # define USE_IMACTIVATEFUNC (!gui.in_use && *p_imaf != NUL)
64 # define USE_IMSTATUSFUNC (!gui.in_use && *p_imsf != NUL)
65 #else
66 # define USE_IMACTIVATEFUNC (*p_imaf != NUL)
67 # define USE_IMSTATUSFUNC (*p_imsf != NUL)
68 #endif
69 
70 #if defined(FEAT_EVAL) && \
71     (defined(FEAT_XIM) || defined(IME_WITHOUT_XIM) || defined(VIMDLL))
72     static void
call_imactivatefunc(int active)73 call_imactivatefunc(int active)
74 {
75     typval_T argv[2];
76 
77     argv[0].v_type = VAR_NUMBER;
78     argv[0].vval.v_number = active ? 1 : 0;
79     argv[1].v_type = VAR_UNKNOWN;
80     (void)call_func_retnr(p_imaf, 1, argv);
81 }
82 
83     static int
call_imstatusfunc(void)84 call_imstatusfunc(void)
85 {
86     int is_active;
87 
88     // FIXME: Don't execute user function in unsafe situation.
89     if (exiting || is_autocmd_blocked())
90 	return FALSE;
91     // FIXME: :py print 'xxx' is shown duplicate result.
92     // Use silent to avoid it.
93     ++msg_silent;
94     is_active = call_func_retnr(p_imsf, 0, NULL);
95     --msg_silent;
96     return (is_active > 0);
97 }
98 #endif
99 
100 #if defined(FEAT_XIM) || defined(PROTO)
101 
102 # if defined(FEAT_GUI_GTK) || defined(PROTO)
103 static int xim_has_preediting INIT(= FALSE);  // IM current status
104 
105 /*
106  * Set preedit_start_col to the current cursor position.
107  */
108     static void
init_preedit_start_col(void)109 init_preedit_start_col(void)
110 {
111     if (State & CMDLINE)
112 	preedit_start_col = cmdline_getvcol_cursor();
113     else if (curwin != NULL && curwin->w_buffer != NULL)
114 	getvcol(curwin, &curwin->w_cursor, &preedit_start_col, NULL, NULL);
115     // Prevent that preediting marks the buffer as changed.
116     xim_changed_while_preediting = curbuf->b_changed;
117 }
118 
119 static int im_is_active	       = FALSE;	// IM is enabled for current mode
120 static int preedit_is_active   = FALSE;
121 static int im_preedit_cursor   = 0;	// cursor offset in characters
122 static int im_preedit_trailing = 0;	// number of characters after cursor
123 
124 static unsigned long im_commit_handler_id  = 0;
125 static unsigned int  im_activatekey_keyval = GDK_VoidSymbol;
126 static unsigned int  im_activatekey_state  = 0;
127 
128 static GtkWidget *preedit_window = NULL;
129 static GtkWidget *preedit_label = NULL;
130 
131 static void im_preedit_window_set_position(void);
132 
133     void
im_set_active(int active)134 im_set_active(int active)
135 {
136     int was_active;
137 
138     was_active = !!im_get_status();
139     im_is_active = (active && !p_imdisable);
140 
141     if (im_is_active != was_active)
142 	xim_reset();
143 }
144 
145     void
xim_set_focus(int focus)146 xim_set_focus(int focus)
147 {
148     if (xic != NULL)
149     {
150 	if (focus)
151 	    gtk_im_context_focus_in(xic);
152 	else
153 	    gtk_im_context_focus_out(xic);
154     }
155 }
156 
157     void
im_set_position(int row,int col)158 im_set_position(int row, int col)
159 {
160     if (xic != NULL)
161     {
162 	GdkRectangle area;
163 
164 	area.x = FILL_X(col);
165 	area.y = FILL_Y(row);
166 	area.width  = gui.char_width * (mb_lefthalve(row, col) ? 2 : 1);
167 	area.height = gui.char_height;
168 
169 	gtk_im_context_set_cursor_location(xic, &area);
170 
171 	if (p_imst == IM_OVER_THE_SPOT)
172 	    im_preedit_window_set_position();
173     }
174 }
175 
176 #  if 0 || defined(PROTO) // apparently only used in gui_x11.c
177     void
178 xim_set_preedit(void)
179 {
180     im_set_position(gui.row, gui.col);
181 }
182 #  endif
183 
184     static void
im_add_to_input(char_u * str,int len)185 im_add_to_input(char_u *str, int len)
186 {
187     // Convert from 'termencoding' (always "utf-8") to 'encoding'
188     if (input_conv.vc_type != CONV_NONE)
189     {
190 	str = string_convert(&input_conv, str, &len);
191 	g_return_if_fail(str != NULL);
192     }
193 
194     add_to_input_buf_csi(str, len);
195 
196     if (input_conv.vc_type != CONV_NONE)
197 	vim_free(str);
198 
199     if (p_mh) // blank out the pointer if necessary
200 	gui_mch_mousehide(TRUE);
201 }
202 
203      static void
im_preedit_window_set_position(void)204 im_preedit_window_set_position(void)
205 {
206     int x, y, width, height;
207     int screen_x, screen_y, screen_width, screen_height;
208 
209     if (preedit_window == NULL)
210 	return;
211 
212     gui_gtk_get_screen_geom_of_win(gui.drawarea, 0, 0,
213 			  &screen_x, &screen_y, &screen_width, &screen_height);
214     gdk_window_get_origin(gtk_widget_get_window(gui.drawarea), &x, &y);
215     gtk_window_get_size(GTK_WINDOW(preedit_window), &width, &height);
216     x = x + FILL_X(gui.col);
217     y = y + FILL_Y(gui.row);
218     if (x + width > screen_x + screen_width)
219 	x = screen_x + screen_width - width;
220     if (y + height > screen_y + screen_height)
221 	y = screen_y + screen_height - height;
222     gtk_window_move(GTK_WINDOW(preedit_window), x, y);
223 }
224 
225     static void
im_preedit_window_open()226 im_preedit_window_open()
227 {
228     char *preedit_string;
229 #if !GTK_CHECK_VERSION(3,16,0)
230     char buf[8];
231 #endif
232     PangoAttrList *attr_list;
233     PangoLayout *layout;
234 #if GTK_CHECK_VERSION(3,0,0)
235 # if !GTK_CHECK_VERSION(3,16,0)
236     GdkRGBA color;
237 # endif
238 #else
239     GdkColor color;
240 #endif
241     gint w, h;
242 
243     if (preedit_window == NULL)
244     {
245 	preedit_window = gtk_window_new(GTK_WINDOW_POPUP);
246 	gtk_window_set_transient_for(GTK_WINDOW(preedit_window),
247 						     GTK_WINDOW(gui.mainwin));
248 	preedit_label = gtk_label_new("");
249 	gtk_widget_set_name(preedit_label, "vim-gui-preedit-area");
250 	gtk_container_add(GTK_CONTAINER(preedit_window), preedit_label);
251     }
252 
253 #if GTK_CHECK_VERSION(3,16,0)
254     {
255 	GtkStyleContext * const context
256 				  = gtk_widget_get_style_context(gui.drawarea);
257 	GtkCssProvider * const provider = gtk_css_provider_new();
258 	gchar		   *css = NULL;
259 	const char * const fontname
260 			   = pango_font_description_get_family(gui.norm_font);
261 	gint	fontsize
262 		= pango_font_description_get_size(gui.norm_font) / PANGO_SCALE;
263 	gchar	*fontsize_propval = NULL;
264 
265 	if (!pango_font_description_get_size_is_absolute(gui.norm_font))
266 	{
267 	    // fontsize was given in points.  Convert it into that in pixels
268 	    // to use with CSS.
269 	    GdkScreen * const screen
270 		  = gdk_window_get_screen(gtk_widget_get_window(gui.mainwin));
271 	    const gdouble dpi = gdk_screen_get_resolution(screen);
272 	    fontsize = dpi * fontsize / 72;
273 	}
274 	if (fontsize > 0)
275 	    fontsize_propval = g_strdup_printf("%dpx", fontsize);
276 	else
277 	    fontsize_propval = g_strdup_printf("inherit");
278 
279 	css = g_strdup_printf(
280 		"widget#vim-gui-preedit-area {\n"
281 		"  font-family: %s,monospace;\n"
282 		"  font-size: %s;\n"
283 		"  color: #%.2lx%.2lx%.2lx;\n"
284 		"  background-color: #%.2lx%.2lx%.2lx;\n"
285 		"}\n",
286 		fontname != NULL ? fontname : "inherit",
287 		fontsize_propval,
288 		(gui.norm_pixel >> 16) & 0xff,
289 		(gui.norm_pixel >> 8) & 0xff,
290 		gui.norm_pixel & 0xff,
291 		(gui.back_pixel >> 16) & 0xff,
292 		(gui.back_pixel >> 8) & 0xff,
293 		gui.back_pixel & 0xff);
294 
295 	gtk_css_provider_load_from_data(provider, css, -1, NULL);
296 	gtk_style_context_add_provider(context,
297 				     GTK_STYLE_PROVIDER(provider), G_MAXUINT);
298 
299 	g_free(css);
300 	g_free(fontsize_propval);
301 	g_object_unref(provider);
302     }
303 #elif GTK_CHECK_VERSION(3,0,0)
304     gtk_widget_override_font(preedit_label, gui.norm_font);
305 
306     vim_snprintf(buf, sizeof(buf), "#%06X", gui.norm_pixel);
307     gdk_rgba_parse(&color, buf);
308     gtk_widget_override_color(preedit_label, GTK_STATE_FLAG_NORMAL, &color);
309 
310     vim_snprintf(buf, sizeof(buf), "#%06X", gui.back_pixel);
311     gdk_rgba_parse(&color, buf);
312     gtk_widget_override_background_color(preedit_label, GTK_STATE_FLAG_NORMAL,
313 								      &color);
314 #else
315     gtk_widget_modify_font(preedit_label, gui.norm_font);
316 
317     vim_snprintf(buf, sizeof(buf), "#%06X", (unsigned)gui.norm_pixel);
318     gdk_color_parse(buf, &color);
319     gtk_widget_modify_fg(preedit_label, GTK_STATE_NORMAL, &color);
320 
321     vim_snprintf(buf, sizeof(buf), "#%06X", (unsigned)gui.back_pixel);
322     gdk_color_parse(buf, &color);
323     gtk_widget_modify_bg(preedit_window, GTK_STATE_NORMAL, &color);
324 #endif
325 
326     gtk_im_context_get_preedit_string(xic, &preedit_string, &attr_list, NULL);
327 
328     if (preedit_string[0] != NUL)
329     {
330 	gtk_label_set_text(GTK_LABEL(preedit_label), preedit_string);
331 	gtk_label_set_attributes(GTK_LABEL(preedit_label), attr_list);
332 
333 	layout = gtk_label_get_layout(GTK_LABEL(preedit_label));
334 	pango_layout_get_pixel_size(layout, &w, &h);
335 	h = MAX(h, gui.char_height);
336 	gtk_window_resize(GTK_WINDOW(preedit_window), w, h);
337 
338 	gtk_widget_show_all(preedit_window);
339 
340 	im_preedit_window_set_position();
341     }
342 
343     g_free(preedit_string);
344     pango_attr_list_unref(attr_list);
345 }
346 
347     static void
im_preedit_window_close()348 im_preedit_window_close()
349 {
350     if (preedit_window != NULL)
351 	gtk_widget_hide(preedit_window);
352 }
353 
354     static void
im_show_preedit()355 im_show_preedit()
356 {
357     im_preedit_window_open();
358 
359     if (p_mh) // blank out the pointer if necessary
360 	gui_mch_mousehide(TRUE);
361 }
362 
363     static void
im_delete_preedit(void)364 im_delete_preedit(void)
365 {
366     char_u bskey[]  = {CSI, 'k', 'b'};
367     char_u delkey[] = {CSI, 'k', 'D'};
368 
369     if (p_imst == IM_OVER_THE_SPOT)
370     {
371 	im_preedit_window_close();
372 	return;
373     }
374 
375     if (State & NORMAL
376 #ifdef FEAT_TERMINAL
377 	    && !term_use_loop()
378 #endif
379        )
380     {
381 	im_preedit_cursor = 0;
382 	return;
383     }
384     for (; im_preedit_cursor > 0; --im_preedit_cursor)
385 	add_to_input_buf(bskey, (int)sizeof(bskey));
386 
387     for (; im_preedit_trailing > 0; --im_preedit_trailing)
388 	add_to_input_buf(delkey, (int)sizeof(delkey));
389 }
390 
391 /*
392  * Move the cursor left by "num_move_back" characters.
393  * Note that ins_left() checks im_is_preediting() to avoid breaking undo for
394  * these K_LEFT keys.
395  */
396     static void
im_correct_cursor(int num_move_back)397 im_correct_cursor(int num_move_back)
398 {
399     char_u backkey[] = {CSI, 'k', 'l'};
400 
401     if (State & NORMAL)
402 	return;
403 #  ifdef FEAT_RIGHTLEFT
404     if ((State & CMDLINE) == 0 && curwin != NULL && curwin->w_p_rl)
405 	backkey[2] = 'r';
406 #  endif
407     for (; num_move_back > 0; --num_move_back)
408 	add_to_input_buf(backkey, (int)sizeof(backkey));
409 }
410 
411 static int xim_expected_char = NUL;
412 static int xim_ignored_char = FALSE;
413 
414 /*
415  * Update the mode and cursor while in an IM callback.
416  */
417     static void
im_show_info(void)418 im_show_info(void)
419 {
420     int	    old_vgetc_busy;
421 
422     old_vgetc_busy = vgetc_busy;
423     vgetc_busy = TRUE;
424     showmode();
425     vgetc_busy = old_vgetc_busy;
426     if ((State & NORMAL) || (State & INSERT))
427 	setcursor();
428     out_flush();
429 }
430 
431 /*
432  * Callback invoked when the user finished preediting.
433  * Put the final string into the input buffer.
434  */
435     static void
im_commit_cb(GtkIMContext * context UNUSED,const gchar * str,gpointer data UNUSED)436 im_commit_cb(GtkIMContext *context UNUSED,
437 	     const gchar *str,
438 	     gpointer data UNUSED)
439 {
440     int		slen = (int)STRLEN(str);
441     int		add_to_input = TRUE;
442     int		clen;
443     int		len = slen;
444     int		commit_with_preedit = TRUE;
445     char_u	*im_str;
446 
447 #ifdef XIM_DEBUG
448     xim_log("im_commit_cb(): %s\n", str);
449 #endif
450 
451     if (p_imst == IM_ON_THE_SPOT)
452     {
453 	// The imhangul module doesn't reset the preedit string before
454 	// committing.  Call im_delete_preedit() to work around that.
455 	im_delete_preedit();
456 
457 	// Indicate that preediting has finished.
458 	if (preedit_start_col == MAXCOL)
459 	{
460 	    init_preedit_start_col();
461 	    commit_with_preedit = FALSE;
462 	}
463 
464 	// The thing which setting "preedit_start_col" to MAXCOL means that
465 	// "preedit_start_col" will be set forcedly when calling
466 	// preedit_changed_cb() next time.
467 	// "preedit_start_col" should not reset with MAXCOL on this part. Vim
468 	// is simulating the preediting by using add_to_input_str(). when
469 	// preedit begin immediately before committed, the typebuf is not
470 	// flushed to screen, then it can't get correct "preedit_start_col".
471 	// Thus, it should calculate the cells by adding cells of the committed
472 	// string.
473 	if (input_conv.vc_type != CONV_NONE)
474 	{
475 	    im_str = string_convert(&input_conv, (char_u *)str, &len);
476 	    g_return_if_fail(im_str != NULL);
477 	}
478 	else
479 	    im_str = (char_u *)str;
480 
481 	clen = mb_string2cells(im_str, len);
482 
483 	if (input_conv.vc_type != CONV_NONE)
484 	    vim_free(im_str);
485 	preedit_start_col += clen;
486     }
487 
488     // Is this a single character that matches a keypad key that's just
489     // been pressed?  If so, we don't want it to be entered as such - let
490     // us carry on processing the raw keycode so that it may be used in
491     // mappings as <kSomething>.
492     if (xim_expected_char != NUL)
493     {
494 	// We're currently processing a keypad or other special key
495 	if (slen == 1 && str[0] == xim_expected_char)
496 	{
497 	    // It's a match - don't do it here
498 	    xim_ignored_char = TRUE;
499 	    add_to_input = FALSE;
500 	}
501 	else
502 	{
503 	    // Not a match
504 	    xim_ignored_char = FALSE;
505 	}
506     }
507 
508     if (add_to_input)
509 	im_add_to_input((char_u *)str, slen);
510 
511     if (p_imst == IM_ON_THE_SPOT)
512     {
513 	// Inserting chars while "im_is_active" is set does not cause a
514 	// change of buffer.  When the chars are committed the buffer must be
515 	// marked as changed.
516 	if (!commit_with_preedit)
517 	    preedit_start_col = MAXCOL;
518 
519 	// This flag is used in changed() at next call.
520 	xim_changed_while_preediting = TRUE;
521     }
522 
523     if (gtk_main_level() > 0)
524 	gtk_main_quit();
525 }
526 
527 /*
528  * Callback invoked after start to the preedit.
529  */
530     static void
im_preedit_start_cb(GtkIMContext * context UNUSED,gpointer data UNUSED)531 im_preedit_start_cb(GtkIMContext *context UNUSED, gpointer data UNUSED)
532 {
533 #ifdef XIM_DEBUG
534     xim_log("im_preedit_start_cb()\n");
535 #endif
536 
537     im_is_active = TRUE;
538     preedit_is_active = TRUE;
539     gui_update_cursor(TRUE, FALSE);
540     im_show_info();
541 }
542 
543 /*
544  * Callback invoked after end to the preedit.
545  */
546     static void
im_preedit_end_cb(GtkIMContext * context UNUSED,gpointer data UNUSED)547 im_preedit_end_cb(GtkIMContext *context UNUSED, gpointer data UNUSED)
548 {
549 #ifdef XIM_DEBUG
550     xim_log("im_preedit_end_cb()\n");
551 #endif
552     im_delete_preedit();
553 
554     // Indicate that preediting has finished
555     if (p_imst == IM_ON_THE_SPOT)
556 	preedit_start_col = MAXCOL;
557     xim_has_preediting = FALSE;
558 
559 #if 0
560     // Removal of this line suggested by Takuhiro Nishioka.  Fixes that IM was
561     // switched off unintentionally.  We now use preedit_is_active (added by
562     // SungHyun Nam).
563     im_is_active = FALSE;
564 #endif
565     preedit_is_active = FALSE;
566     gui_update_cursor(TRUE, FALSE);
567     im_show_info();
568 }
569 
570 /*
571  * Callback invoked after changes to the preedit string.  If the preedit
572  * string was empty before, remember the preedit start column so we know
573  * where to apply feedback attributes.  Delete the previous preedit string
574  * if there was one, save the new preedit cursor offset, and put the new
575  * string into the input buffer.
576  *
577  * TODO: The pragmatic "put into input buffer" approach used here has
578  *       several fundamental problems:
579  *
580  * - The characters in the preedit string are subject to remapping.
581  *   That's broken, only the finally committed string should be remapped.
582  *
583  * - There is a race condition involved:  The retrieved value for the
584  *   current cursor position will be wrong if any unprocessed characters
585  *   are still queued in the input buffer.
586  *
587  * - Due to the lack of synchronization between the file buffer in memory
588  *   and any typed characters, it's practically impossible to implement the
589  *   "retrieve_surrounding" and "delete_surrounding" signals reliably.  IM
590  *   modules for languages such as Thai are likely to rely on this feature
591  *   for proper operation.
592  *
593  * Conclusions:  I think support for preediting needs to be moved to the
594  * core parts of Vim.  Ideally, until it has been committed, the preediting
595  * string should only be displayed and not affect the buffer content at all.
596  * The question how to deal with the synchronization issue still remains.
597  * Circumventing the input buffer is probably not desirable.  Anyway, I think
598  * implementing "retrieve_surrounding" is the only hard problem.
599  *
600  * One way to solve all of this in a clean manner would be to queue all key
601  * press/release events "as is" in the input buffer, and apply the IM filtering
602  * at the receiving end of the queue.  This, however, would have a rather large
603  * impact on the code base.  If there is an easy way to force processing of all
604  * remaining input from within the "retrieve_surrounding" signal handler, this
605  * might not be necessary.  Gotta ask on vim-dev for opinions.
606  */
607     static void
im_preedit_changed_cb(GtkIMContext * context,gpointer data UNUSED)608 im_preedit_changed_cb(GtkIMContext *context, gpointer data UNUSED)
609 {
610     char    *preedit_string = NULL;
611     int	    cursor_index    = 0;
612     int	    num_move_back   = 0;
613     char_u  *str;
614     char_u  *p;
615     int	    i;
616 
617     if (p_imst == IM_ON_THE_SPOT)
618 	gtk_im_context_get_preedit_string(context,
619 					  &preedit_string, NULL,
620 					  &cursor_index);
621     else
622 	gtk_im_context_get_preedit_string(context,
623 					  &preedit_string, NULL,
624 					  NULL);
625 
626 #ifdef XIM_DEBUG
627     xim_log("im_preedit_changed_cb(): %s\n", preedit_string);
628 #endif
629 
630     g_return_if_fail(preedit_string != NULL); // just in case
631 
632     if (p_imst == IM_OVER_THE_SPOT)
633     {
634 	if (preedit_string[0] == NUL)
635 	{
636 	    xim_has_preediting = FALSE;
637 	    im_delete_preedit();
638 	}
639 	else
640 	{
641 	    xim_has_preediting = TRUE;
642 	    im_show_preedit();
643 	}
644     }
645     else
646     {
647 	// If preedit_start_col is MAXCOL set it to the current cursor position.
648 	if (preedit_start_col == MAXCOL && preedit_string[0] != '\0')
649 	{
650 	    xim_has_preediting = TRUE;
651 
652 	    // Urgh, this breaks if the input buffer isn't empty now
653 	    init_preedit_start_col();
654 	}
655 	else if (cursor_index == 0 && preedit_string[0] == '\0')
656 	{
657 	    xim_has_preediting = FALSE;
658 
659 	    // If at the start position (after typing backspace)
660 	    // preedit_start_col must be reset.
661 	    preedit_start_col = MAXCOL;
662 	}
663 
664 	im_delete_preedit();
665 
666 	// Compute the end of the preediting area: "preedit_end_col".
667 	// According to the documentation of
668 	// gtk_im_context_get_preedit_string(), the cursor_pos output argument
669 	// returns the offset in bytes.  This is unfortunately not true -- real
670 	// life shows the offset is in characters, and the GTK+ source code
671 	// agrees with me.  Will file a bug later.
672 	if (preedit_start_col != MAXCOL)
673 	    preedit_end_col = preedit_start_col;
674 	str = (char_u *)preedit_string;
675 	for (p = str, i = 0; *p != NUL; p += utf_byte2len(*p), ++i)
676 	{
677 	    int is_composing;
678 
679 	    is_composing = ((*p & 0x80) != 0
680 					  && utf_iscomposing(utf_ptr2char(p)));
681 	    // These offsets are used as counters when generating <BS> and
682 	    // <Del> to delete the preedit string.  So don't count composing
683 	    // characters unless 'delcombine' is enabled.
684 	    if (!is_composing || p_deco)
685 	    {
686 		if (i < cursor_index)
687 		    ++im_preedit_cursor;
688 		else
689 		    ++im_preedit_trailing;
690 	    }
691 	    if (!is_composing && i >= cursor_index)
692 	    {
693 		// This is essentially the same as im_preedit_trailing, except
694 		// composing characters are not counted even if p_deco is set.
695 		++num_move_back;
696 	    }
697 	    if (preedit_start_col != MAXCOL)
698 		preedit_end_col += utf_ptr2cells(p);
699 	}
700 
701 	if (p > str)
702 	{
703 	    im_add_to_input(str, (int)(p - str));
704 	    im_correct_cursor(num_move_back);
705 	}
706     }
707 
708     g_free(preedit_string);
709 
710     if (gtk_main_level() > 0)
711 	gtk_main_quit();
712 }
713 
714 /*
715  * Translate the Pango attributes at iter to Vim highlighting attributes.
716  * Ignore attributes not supported by Vim highlighting.  This shouldn't have
717  * too much impact -- right now we handle even more attributes than necessary
718  * for the IM modules I tested with.
719  */
720     static int
translate_pango_attributes(PangoAttrIterator * iter)721 translate_pango_attributes(PangoAttrIterator *iter)
722 {
723     PangoAttribute  *attr;
724     int		    char_attr = HL_NORMAL;
725 
726     attr = pango_attr_iterator_get(iter, PANGO_ATTR_UNDERLINE);
727     if (attr != NULL && ((PangoAttrInt *)attr)->value
728 						 != (int)PANGO_UNDERLINE_NONE)
729 	char_attr |= HL_UNDERLINE;
730 
731     attr = pango_attr_iterator_get(iter, PANGO_ATTR_WEIGHT);
732     if (attr != NULL && ((PangoAttrInt *)attr)->value >= (int)PANGO_WEIGHT_BOLD)
733 	char_attr |= HL_BOLD;
734 
735     attr = pango_attr_iterator_get(iter, PANGO_ATTR_STYLE);
736     if (attr != NULL && ((PangoAttrInt *)attr)->value
737 						   != (int)PANGO_STYLE_NORMAL)
738 	char_attr |= HL_ITALIC;
739 
740     attr = pango_attr_iterator_get(iter, PANGO_ATTR_BACKGROUND);
741     if (attr != NULL)
742     {
743 	const PangoColor *color = &((PangoAttrColor *)attr)->color;
744 
745 	// Assume inverse if black background is requested
746 	if ((color->red | color->green | color->blue) == 0)
747 	    char_attr |= HL_INVERSE;
748     }
749 
750     return char_attr;
751 }
752 
753 /*
754  * Retrieve the highlighting attributes at column col in the preedit string.
755  * Return -1 if not in preediting mode or if col is out of range.
756  */
757     int
im_get_feedback_attr(int col)758 im_get_feedback_attr(int col)
759 {
760     char	    *preedit_string = NULL;
761     PangoAttrList   *attr_list	    = NULL;
762     int		    char_attr	    = -1;
763 
764     if (xic == NULL)
765 	return char_attr;
766 
767     gtk_im_context_get_preedit_string(xic, &preedit_string, &attr_list, NULL);
768 
769     if (preedit_string != NULL && attr_list != NULL)
770     {
771 	int idx;
772 
773 	// Get the byte index as used by PangoAttrIterator
774 	for (idx = 0; col > 0 && preedit_string[idx] != '\0'; --col)
775 	    idx += utfc_ptr2len((char_u *)preedit_string + idx);
776 
777 	if (preedit_string[idx] != '\0')
778 	{
779 	    PangoAttrIterator	*iter;
780 	    int			start, end;
781 
782 	    char_attr = HL_NORMAL;
783 	    iter = pango_attr_list_get_iterator(attr_list);
784 
785 	    // Extract all relevant attributes from the list.
786 	    do
787 	    {
788 		pango_attr_iterator_range(iter, &start, &end);
789 
790 		if (idx >= start && idx < end)
791 		    char_attr |= translate_pango_attributes(iter);
792 	    }
793 	    while (pango_attr_iterator_next(iter));
794 
795 	    pango_attr_iterator_destroy(iter);
796 	}
797     }
798 
799     if (attr_list != NULL)
800 	pango_attr_list_unref(attr_list);
801     g_free(preedit_string);
802 
803     return char_attr;
804 }
805 
806     void
xim_init(void)807 xim_init(void)
808 {
809 #ifdef XIM_DEBUG
810     xim_log("xim_init()\n");
811 #endif
812 
813     g_return_if_fail(gui.drawarea != NULL);
814     g_return_if_fail(gtk_widget_get_window(gui.drawarea) != NULL);
815 
816     xic = gtk_im_multicontext_new();
817     g_object_ref(xic);
818 
819     im_commit_handler_id = g_signal_connect(G_OBJECT(xic), "commit",
820 					    G_CALLBACK(&im_commit_cb), NULL);
821     g_signal_connect(G_OBJECT(xic), "preedit_changed",
822 		     G_CALLBACK(&im_preedit_changed_cb), NULL);
823     g_signal_connect(G_OBJECT(xic), "preedit_start",
824 		     G_CALLBACK(&im_preedit_start_cb), NULL);
825     g_signal_connect(G_OBJECT(xic), "preedit_end",
826 		     G_CALLBACK(&im_preedit_end_cb), NULL);
827 
828     gtk_im_context_set_client_window(xic, gtk_widget_get_window(gui.drawarea));
829 }
830 
831     void
im_shutdown(void)832 im_shutdown(void)
833 {
834 #ifdef XIM_DEBUG
835     xim_log("im_shutdown()\n");
836 #endif
837 
838     if (xic != NULL)
839     {
840 	gtk_im_context_focus_out(xic);
841 	g_object_unref(xic);
842 	xic = NULL;
843     }
844     im_is_active = FALSE;
845     im_commit_handler_id = 0;
846     if (p_imst == IM_ON_THE_SPOT)
847 	preedit_start_col = MAXCOL;
848     xim_has_preediting = FALSE;
849 }
850 
851 /*
852  * Convert the string argument to keyval and state for GdkEventKey.
853  * If str is valid return TRUE, otherwise FALSE.
854  *
855  * See 'imactivatekey' for documentation of the format.
856  */
857     static int
im_string_to_keyval(const char * str,unsigned int * keyval,unsigned int * state)858 im_string_to_keyval(const char *str, unsigned int *keyval, unsigned int *state)
859 {
860     const char	    *mods_end;
861     unsigned	    tmp_keyval;
862     unsigned	    tmp_state = 0;
863 
864     mods_end = strrchr(str, '-');
865     mods_end = (mods_end != NULL) ? mods_end + 1 : str;
866 
867     // Parse modifier keys
868     while (str < mods_end)
869 	switch (*str++)
870 	{
871 	    case '-':							break;
872 	    case 'S': case 's': tmp_state |= (unsigned)GDK_SHIFT_MASK;	break;
873 	    case 'L': case 'l': tmp_state |= (unsigned)GDK_LOCK_MASK;	break;
874 	    case 'C': case 'c': tmp_state |= (unsigned)GDK_CONTROL_MASK;break;
875 	    case '1':		tmp_state |= (unsigned)GDK_MOD1_MASK;	break;
876 	    case '2':		tmp_state |= (unsigned)GDK_MOD2_MASK;	break;
877 	    case '3':		tmp_state |= (unsigned)GDK_MOD3_MASK;	break;
878 	    case '4':		tmp_state |= (unsigned)GDK_MOD4_MASK;	break;
879 	    case '5':		tmp_state |= (unsigned)GDK_MOD5_MASK;	break;
880 	    default:
881 		return FALSE;
882 	}
883 
884     tmp_keyval = gdk_keyval_from_name(str);
885 
886     if (tmp_keyval == 0 || tmp_keyval == GDK_VoidSymbol)
887 	return FALSE;
888 
889     if (keyval != NULL)
890 	*keyval = tmp_keyval;
891     if (state != NULL)
892 	*state = tmp_state;
893 
894     return TRUE;
895 }
896 
897 /*
898  * Return TRUE if p_imak is valid, otherwise FALSE.  As a special case, an
899  * empty string is also regarded as valid.
900  *
901  * Note: The numerical key value of p_imak is cached if it was valid; thus
902  * boldly assuming im_xim_isvalid_imactivate() will always be called whenever
903  * 'imak' changes.  This is currently the case but not obvious -- should
904  * probably rename the function for clarity.
905  */
906     int
im_xim_isvalid_imactivate(void)907 im_xim_isvalid_imactivate(void)
908 {
909     if (p_imak[0] == NUL)
910     {
911 	im_activatekey_keyval = GDK_VoidSymbol;
912 	im_activatekey_state  = 0;
913 	return TRUE;
914     }
915 
916     return im_string_to_keyval((const char *)p_imak,
917 			       &im_activatekey_keyval,
918 			       &im_activatekey_state);
919 }
920 
921     static void
im_synthesize_keypress(unsigned int keyval,unsigned int state)922 im_synthesize_keypress(unsigned int keyval, unsigned int state)
923 {
924     GdkEventKey *event;
925 
926     event = (GdkEventKey *)gdk_event_new(GDK_KEY_PRESS);
927     g_object_ref(gtk_widget_get_window(gui.drawarea));
928 					// unreffed by gdk_event_free()
929     event->window = gtk_widget_get_window(gui.drawarea);
930     event->send_event = TRUE;
931     event->time = GDK_CURRENT_TIME;
932     event->state  = state;
933     event->keyval = keyval;
934     event->hardware_keycode = // needed for XIM
935 	XKeysymToKeycode(GDK_WINDOW_XDISPLAY(event->window), (KeySym)keyval);
936     event->length = 0;
937     event->string = NULL;
938 
939     gtk_im_context_filter_keypress(xic, event);
940 
941     // For consistency, also send the corresponding release event.
942     event->type = GDK_KEY_RELEASE;
943     event->send_event = FALSE;
944     gtk_im_context_filter_keypress(xic, event);
945 
946     gdk_event_free((GdkEvent *)event);
947 }
948 
949     void
xim_reset(void)950 xim_reset(void)
951 {
952 # ifdef FEAT_EVAL
953     if (USE_IMACTIVATEFUNC)
954 	call_imactivatefunc(im_is_active);
955     else
956 # endif
957     if (xic != NULL)
958     {
959 	gtk_im_context_reset(xic);
960 
961 	if (p_imdisable)
962 	    im_shutdown();
963 	else
964 	{
965 	    xim_set_focus(gui.in_focus);
966 
967 	    if (im_activatekey_keyval != GDK_VoidSymbol)
968 	    {
969 		if (im_is_active)
970 		{
971 		    g_signal_handler_block(xic, im_commit_handler_id);
972 		    im_synthesize_keypress(im_activatekey_keyval,
973 						    im_activatekey_state);
974 		    g_signal_handler_unblock(xic, im_commit_handler_id);
975 		}
976 	    }
977 	    else
978 	    {
979 		im_shutdown();
980 		xim_init();
981 		xim_set_focus(gui.in_focus);
982 	    }
983 	}
984     }
985 
986     if (p_imst == IM_ON_THE_SPOT)
987 	preedit_start_col = MAXCOL;
988     xim_has_preediting = FALSE;
989 }
990 
991     int
xim_queue_key_press_event(GdkEventKey * event,int down)992 xim_queue_key_press_event(GdkEventKey *event, int down)
993 {
994     if (down)
995     {
996 	// Workaround GTK2 XIM 'feature' that always converts keypad keys to
997 	// chars., even when not part of an IM sequence (ref. feature of
998 	// gdk/gdkkeyuni.c).
999 	// Flag any keypad keys that might represent a single char.
1000 	// If this (on its own - i.e., not part of an IM sequence) is
1001 	// committed while we're processing one of these keys, we can ignore
1002 	// that commit and go ahead & process it ourselves.  That way we can
1003 	// still distinguish keypad keys for use in mappings.
1004 	// Also add GDK_space to make <S-Space> work.
1005 	switch (event->keyval)
1006 	{
1007 	    case GDK_KP_Add:      xim_expected_char = '+';  break;
1008 	    case GDK_KP_Subtract: xim_expected_char = '-';  break;
1009 	    case GDK_KP_Divide:   xim_expected_char = '/';  break;
1010 	    case GDK_KP_Multiply: xim_expected_char = '*';  break;
1011 	    case GDK_KP_Decimal:  xim_expected_char = '.';  break;
1012 	    case GDK_KP_Equal:    xim_expected_char = '=';  break;
1013 	    case GDK_KP_0:	  xim_expected_char = '0';  break;
1014 	    case GDK_KP_1:	  xim_expected_char = '1';  break;
1015 	    case GDK_KP_2:	  xim_expected_char = '2';  break;
1016 	    case GDK_KP_3:	  xim_expected_char = '3';  break;
1017 	    case GDK_KP_4:	  xim_expected_char = '4';  break;
1018 	    case GDK_KP_5:	  xim_expected_char = '5';  break;
1019 	    case GDK_KP_6:	  xim_expected_char = '6';  break;
1020 	    case GDK_KP_7:	  xim_expected_char = '7';  break;
1021 	    case GDK_KP_8:	  xim_expected_char = '8';  break;
1022 	    case GDK_KP_9:	  xim_expected_char = '9';  break;
1023 	    case GDK_space:	  xim_expected_char = ' ';  break;
1024 	    default:		  xim_expected_char = NUL;
1025 	}
1026 	xim_ignored_char = FALSE;
1027     }
1028 
1029     // When typing fFtT, XIM may be activated. Thus it must pass
1030     // gtk_im_context_filter_keypress() in Normal mode.
1031     // And while doing :sh too.
1032     if (xic != NULL && !p_imdisable
1033 		    && (State & (INSERT | CMDLINE | NORMAL | EXTERNCMD)) != 0)
1034     {
1035 	// Filter 'imactivatekey' and map it to CTRL-^.  This way, Vim is
1036 	// always aware of the current status of IM, and can even emulate
1037 	// the activation key for modules that don't support one.
1038 	if (event->keyval == im_activatekey_keyval
1039 	     && (event->state & im_activatekey_state) == im_activatekey_state)
1040 	{
1041 	    unsigned int state_mask;
1042 
1043 	    // Require the state of the 3 most used modifiers to match exactly.
1044 	    // Otherwise e.g. <S-C-space> would be unusable for other purposes
1045 	    // if the IM activate key is <S-space>.
1046 	    state_mask  = im_activatekey_state;
1047 	    state_mask |= ((int)GDK_SHIFT_MASK | (int)GDK_CONTROL_MASK
1048 							| (int)GDK_MOD1_MASK);
1049 
1050 	    if ((event->state & state_mask) != im_activatekey_state)
1051 		return FALSE;
1052 
1053 	    // Don't send it a second time on GDK_KEY_RELEASE.
1054 	    if (event->type != GDK_KEY_PRESS)
1055 		return TRUE;
1056 
1057 	    if (map_to_exists_mode((char_u *)"", LANGMAP, FALSE))
1058 	    {
1059 		im_set_active(FALSE);
1060 
1061 		// ":lmap" mappings exists, toggle use of mappings.
1062 		State ^= LANGMAP;
1063 		if (State & LANGMAP)
1064 		{
1065 		    curbuf->b_p_iminsert = B_IMODE_NONE;
1066 		    State &= ~LANGMAP;
1067 		}
1068 		else
1069 		{
1070 		    curbuf->b_p_iminsert = B_IMODE_LMAP;
1071 		    State |= LANGMAP;
1072 		}
1073 		return TRUE;
1074 	    }
1075 
1076 	    return gtk_im_context_filter_keypress(xic, event);
1077 	}
1078 
1079 	// Don't filter events through the IM context if IM isn't active
1080 	// right now.  Unlike with GTK+ 1.2 we cannot rely on the IM module
1081 	// not doing anything before the activation key was sent.
1082 	if (im_activatekey_keyval == GDK_VoidSymbol || im_is_active)
1083 	{
1084 	    int imresult = gtk_im_context_filter_keypress(xic, event);
1085 
1086 	    if (p_imst == IM_ON_THE_SPOT)
1087 	    {
1088 		// Some XIM send following sequence:
1089 		// 1. preedited string.
1090 		// 2. committed string.
1091 		// 3. line changed key.
1092 		// 4. preedited string.
1093 		// 5. remove preedited string.
1094 		// if 3, Vim can't move back the above line for 5.
1095 		// thus, this part should not parse the key.
1096 		if (!imresult && preedit_start_col != MAXCOL
1097 					    && event->keyval == GDK_Return)
1098 		{
1099 		    im_synthesize_keypress(GDK_Return, 0U);
1100 		    return FALSE;
1101 		}
1102 	    }
1103 
1104 	    // If XIM tried to commit a keypad key as a single char.,
1105 	    // ignore it so we can use the keypad key 'raw', for mappings.
1106 	    if (xim_expected_char != NUL && xim_ignored_char)
1107 		// We had a keypad key, and XIM tried to thieve it
1108 		return FALSE;
1109 
1110 	    // This is supposed to fix a problem with iBus, that space
1111 	    // characters don't work in input mode.
1112 	    xim_expected_char = NUL;
1113 
1114 	    // Normal processing
1115 	    return imresult;
1116 	}
1117     }
1118 
1119     return FALSE;
1120 }
1121 
1122     int
im_get_status(void)1123 im_get_status(void)
1124 {
1125 #  ifdef FEAT_EVAL
1126     if (USE_IMSTATUSFUNC)
1127 	return call_imstatusfunc();
1128 #  endif
1129     return im_is_active;
1130 }
1131 
1132     int
preedit_get_status(void)1133 preedit_get_status(void)
1134 {
1135     return preedit_is_active;
1136 }
1137 
1138     int
im_is_preediting(void)1139 im_is_preediting(void)
1140 {
1141     return xim_has_preediting;
1142 }
1143 
1144 # else // !FEAT_GUI_GTK
1145 
1146 static int	xim_is_active = FALSE;  // XIM should be active in the current
1147 					// mode
1148 static int	xim_has_focus = FALSE;	// XIM is really being used for Vim
1149 #  ifdef FEAT_GUI_X11
1150 static XIMStyle	input_style;
1151 static int	status_area_enabled = TRUE;
1152 #  endif
1153 
1154 /*
1155  * Switch using XIM on/off.  This is used by the code that changes "State".
1156  * When 'imactivatefunc' is defined use that function instead.
1157  */
1158     void
im_set_active(int active_arg)1159 im_set_active(int active_arg)
1160 {
1161     int active = active_arg;
1162 
1163     // If 'imdisable' is set, XIM is never active.
1164     if (p_imdisable)
1165 	active = FALSE;
1166     else if (input_style & XIMPreeditPosition)
1167 	// There is a problem in switching XIM off when preediting is used,
1168 	// and it is not clear how this can be solved.  For now, keep XIM on
1169 	// all the time, like it was done in Vim 5.8.
1170 	active = TRUE;
1171 
1172 #  if defined(FEAT_EVAL)
1173     if (USE_IMACTIVATEFUNC)
1174     {
1175 	if (active != im_get_status())
1176 	{
1177 	    call_imactivatefunc(active);
1178 	    xim_has_focus = active;
1179 	}
1180 	return;
1181     }
1182 #  endif
1183 
1184     if (xic == NULL)
1185 	return;
1186 
1187     // Remember the active state, it is needed when Vim gets keyboard focus.
1188     xim_is_active = active;
1189     xim_set_preedit();
1190 }
1191 
1192 /*
1193  * Adjust using XIM for gaining or losing keyboard focus.  Also called when
1194  * "xim_is_active" changes.
1195  */
1196     void
xim_set_focus(int focus)1197 xim_set_focus(int focus)
1198 {
1199     if (xic == NULL)
1200 	return;
1201 
1202     // XIM only gets focus when the Vim window has keyboard focus and XIM has
1203     // been set active for the current mode.
1204     if (focus && xim_is_active)
1205     {
1206 	if (!xim_has_focus)
1207 	{
1208 	    xim_has_focus = TRUE;
1209 	    XSetICFocus(xic);
1210 	}
1211     }
1212     else
1213     {
1214 	if (xim_has_focus)
1215 	{
1216 	    xim_has_focus = FALSE;
1217 	    XUnsetICFocus(xic);
1218 	}
1219     }
1220 }
1221 
1222     void
im_set_position(int row UNUSED,int col UNUSED)1223 im_set_position(int row UNUSED, int col UNUSED)
1224 {
1225     xim_set_preedit();
1226 }
1227 
1228 /*
1229  * Set the XIM to the current cursor position.
1230  */
1231     void
xim_set_preedit(void)1232 xim_set_preedit(void)
1233 {
1234     XVaNestedList attr_list;
1235     XRectangle spot_area;
1236     XPoint over_spot;
1237     int line_space;
1238 
1239     if (xic == NULL)
1240 	return;
1241 
1242     xim_set_focus(TRUE);
1243 
1244     if (!xim_has_focus)
1245     {
1246 	// hide XIM cursor
1247 	over_spot.x = 0;
1248 	over_spot.y = -100; // arbitrary invisible position
1249 	attr_list = (XVaNestedList) XVaCreateNestedList(0,
1250 							XNSpotLocation,
1251 							&over_spot,
1252 							NULL);
1253 	XSetICValues(xic, XNPreeditAttributes, attr_list, NULL);
1254 	XFree(attr_list);
1255 	return;
1256     }
1257 
1258     if (input_style & XIMPreeditPosition)
1259     {
1260 	if (xim_fg_color == INVALCOLOR)
1261 	{
1262 	    xim_fg_color = gui.def_norm_pixel;
1263 	    xim_bg_color = gui.def_back_pixel;
1264 	}
1265 	over_spot.x = TEXT_X(gui.col);
1266 	over_spot.y = TEXT_Y(gui.row);
1267 	spot_area.x = 0;
1268 	spot_area.y = 0;
1269 	spot_area.height = gui.char_height * Rows;
1270 	spot_area.width  = gui.char_width * Columns;
1271 	line_space = gui.char_height;
1272 	attr_list = (XVaNestedList) XVaCreateNestedList(0,
1273 					XNSpotLocation, &over_spot,
1274 					XNForeground, (Pixel) xim_fg_color,
1275 					XNBackground, (Pixel) xim_bg_color,
1276 					XNArea, &spot_area,
1277 					XNLineSpace, line_space,
1278 					NULL);
1279 	if (XSetICValues(xic, XNPreeditAttributes, attr_list, NULL))
1280 	    emsg(_("E284: Cannot set IC values"));
1281 	XFree(attr_list);
1282     }
1283 }
1284 
1285 #  if defined(FEAT_GUI_X11)
1286 static char e_xim[] = N_("E285: Failed to create input context");
1287 #  endif
1288 
1289 #  if defined(FEAT_GUI_X11) || defined(PROTO)
1290 #   if defined(XtSpecificationRelease) && XtSpecificationRelease >= 6 && !defined(SUN_SYSTEM)
1291 #    define USE_X11R6_XIM
1292 #   endif
1293 
1294 static int xim_real_init(Window x11_window, Display *x11_display);
1295 
1296 
1297 #  ifdef USE_X11R6_XIM
1298     static void
xim_instantiate_cb(Display * display,XPointer client_data UNUSED,XPointer call_data UNUSED)1299 xim_instantiate_cb(
1300     Display	*display,
1301     XPointer	client_data UNUSED,
1302     XPointer	call_data UNUSED)
1303 {
1304     Window	x11_window;
1305     Display	*x11_display;
1306 
1307 #   ifdef XIM_DEBUG
1308     xim_log("xim_instantiate_cb()\n");
1309 #   endif
1310 
1311     gui_get_x11_windis(&x11_window, &x11_display);
1312     if (display != x11_display)
1313 	return;
1314 
1315     xim_real_init(x11_window, x11_display);
1316     gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH);
1317     if (xic != NULL)
1318 	XUnregisterIMInstantiateCallback(x11_display, NULL, NULL, NULL,
1319 					 xim_instantiate_cb, NULL);
1320 }
1321 
1322     static void
xim_destroy_cb(XIM im UNUSED,XPointer client_data UNUSED,XPointer call_data UNUSED)1323 xim_destroy_cb(
1324     XIM		im UNUSED,
1325     XPointer	client_data UNUSED,
1326     XPointer	call_data UNUSED)
1327 {
1328     Window	x11_window;
1329     Display	*x11_display;
1330 
1331 #   ifdef XIM_DEBUG
1332     xim_log("xim_destroy_cb()\n");
1333    #endif
1334     gui_get_x11_windis(&x11_window, &x11_display);
1335 
1336     xic = NULL;
1337     status_area_enabled = FALSE;
1338 
1339     gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH);
1340 
1341     XRegisterIMInstantiateCallback(x11_display, NULL, NULL, NULL,
1342 				   xim_instantiate_cb, NULL);
1343 }
1344 #  endif
1345 
1346     void
xim_init(void)1347 xim_init(void)
1348 {
1349     Window	x11_window;
1350     Display	*x11_display;
1351 
1352 #  ifdef XIM_DEBUG
1353     xim_log("xim_init()\n");
1354 #  endif
1355 
1356     gui_get_x11_windis(&x11_window, &x11_display);
1357 
1358     xic = NULL;
1359 
1360     if (xim_real_init(x11_window, x11_display))
1361 	return;
1362 
1363     gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH);
1364 
1365 #  ifdef USE_X11R6_XIM
1366     XRegisterIMInstantiateCallback(x11_display, NULL, NULL, NULL,
1367 				   xim_instantiate_cb, NULL);
1368 #  endif
1369 }
1370 
1371     static int
xim_real_init(Window x11_window,Display * x11_display)1372 xim_real_init(Window x11_window, Display *x11_display)
1373 {
1374     int		i;
1375     char	*p,
1376 		*s,
1377 		*ns,
1378 		*end,
1379 		tmp[1024];
1380 #  define IMLEN_MAX 40
1381     char	buf[IMLEN_MAX + 7];
1382     XIM		xim = NULL;
1383     XIMStyles	*xim_styles;
1384     XIMStyle	this_input_style = 0;
1385     Boolean	found;
1386     XPoint	over_spot;
1387     XVaNestedList preedit_list, status_list;
1388 
1389     input_style = 0;
1390     status_area_enabled = FALSE;
1391 
1392     if (xic != NULL)
1393 	return FALSE;
1394 
1395     if (gui.rsrc_input_method != NULL && *gui.rsrc_input_method != NUL)
1396     {
1397 	strcpy(tmp, gui.rsrc_input_method);
1398 	for (ns = s = tmp; ns != NULL && *s != NUL;)
1399 	{
1400 	    s = (char *)skipwhite((char_u *)s);
1401 	    if (*s == NUL)
1402 		break;
1403 	    if ((ns = end = strchr(s, ',')) == NULL)
1404 		end = s + strlen(s);
1405 	    while (isspace(((char_u *)end)[-1]))
1406 		end--;
1407 	    *end = NUL;
1408 
1409 	    if (strlen(s) <= IMLEN_MAX)
1410 	    {
1411 		strcpy(buf, "@im=");
1412 		strcat(buf, s);
1413 		if ((p = XSetLocaleModifiers(buf)) != NULL && *p != NUL
1414 			&& (xim = XOpenIM(x11_display, NULL, NULL, NULL))
1415 								      != NULL)
1416 		    break;
1417 	    }
1418 
1419 	    s = ns + 1;
1420 	}
1421     }
1422 
1423     if (xim == NULL && (p = XSetLocaleModifiers("")) != NULL && *p != NUL)
1424 	xim = XOpenIM(x11_display, NULL, NULL, NULL);
1425 
1426     // This is supposed to be useful to obtain characters through
1427     // XmbLookupString() without really using a XIM.
1428     if (xim == NULL && (p = XSetLocaleModifiers("@im=none")) != NULL
1429 								 && *p != NUL)
1430 	xim = XOpenIM(x11_display, NULL, NULL, NULL);
1431 
1432     if (xim == NULL)
1433     {
1434 	// Only give this message when verbose is set, because too many people
1435 	// got this message when they didn't want to use a XIM.
1436 	if (p_verbose > 0)
1437 	{
1438 	    verbose_enter();
1439 	    emsg(_("E286: Failed to open input method"));
1440 	    verbose_leave();
1441 	}
1442 	return FALSE;
1443     }
1444 
1445 #  ifdef USE_X11R6_XIM
1446     {
1447 	XIMCallback destroy_cb;
1448 
1449 	destroy_cb.callback = xim_destroy_cb;
1450 	destroy_cb.client_data = NULL;
1451 	if (XSetIMValues(xim, XNDestroyCallback, &destroy_cb, NULL))
1452 	    emsg(_("E287: Warning: Could not set destroy callback to IM"));
1453     }
1454 #  endif
1455 
1456     if (XGetIMValues(xim, XNQueryInputStyle, &xim_styles, NULL) || !xim_styles)
1457     {
1458 	emsg(_("E288: input method doesn't support any style"));
1459 	XCloseIM(xim);
1460 	return FALSE;
1461     }
1462 
1463     found = False;
1464     strcpy(tmp, gui.rsrc_preedit_type_name);
1465     for (s = tmp; s && !found; )
1466     {
1467 	while (*s && isspace((unsigned char)*s))
1468 	    s++;
1469 	if (!*s)
1470 	    break;
1471 	if ((ns = end = strchr(s, ',')) != 0)
1472 	    ns++;
1473 	else
1474 	    end = s + strlen(s);
1475 	while (isspace((unsigned char)*end))
1476 	    end--;
1477 	*end = '\0';
1478 
1479 	if (!strcmp(s, "OverTheSpot"))
1480 	    this_input_style = (XIMPreeditPosition | XIMStatusArea);
1481 	else if (!strcmp(s, "OffTheSpot"))
1482 	    this_input_style = (XIMPreeditArea | XIMStatusArea);
1483 	else if (!strcmp(s, "Root"))
1484 	    this_input_style = (XIMPreeditNothing | XIMStatusNothing);
1485 
1486 	for (i = 0; (unsigned short)i < xim_styles->count_styles; i++)
1487 	{
1488 	    if (this_input_style == xim_styles->supported_styles[i])
1489 	    {
1490 		found = True;
1491 		break;
1492 	    }
1493 	}
1494 	if (!found)
1495 	    for (i = 0; (unsigned short)i < xim_styles->count_styles; i++)
1496 	    {
1497 		if ((xim_styles->supported_styles[i] & this_input_style)
1498 			== (this_input_style & ~XIMStatusArea))
1499 		{
1500 		    this_input_style &= ~XIMStatusArea;
1501 		    found = True;
1502 		    break;
1503 		}
1504 	    }
1505 
1506 	s = ns;
1507     }
1508     XFree(xim_styles);
1509 
1510     if (!found)
1511     {
1512 	// Only give this message when verbose is set, because too many people
1513 	// got this message when they didn't want to use a XIM.
1514 	if (p_verbose > 0)
1515 	{
1516 	    verbose_enter();
1517 	    emsg(_("E289: input method doesn't support my preedit type"));
1518 	    verbose_leave();
1519 	}
1520 	XCloseIM(xim);
1521 	return FALSE;
1522     }
1523 
1524     over_spot.x = TEXT_X(gui.col);
1525     over_spot.y = TEXT_Y(gui.row);
1526     input_style = this_input_style;
1527 
1528     // A crash was reported when trying to pass gui.norm_font as XNFontSet,
1529     // thus that has been removed.  Hopefully the default works...
1530 #  ifdef FEAT_XFONTSET
1531     if (gui.fontset != NOFONTSET)
1532     {
1533 	preedit_list = XVaCreateNestedList(0,
1534 				XNSpotLocation, &over_spot,
1535 				XNForeground, (Pixel)gui.def_norm_pixel,
1536 				XNBackground, (Pixel)gui.def_back_pixel,
1537 				XNFontSet, (XFontSet)gui.fontset,
1538 				NULL);
1539 	status_list = XVaCreateNestedList(0,
1540 				XNForeground, (Pixel)gui.def_norm_pixel,
1541 				XNBackground, (Pixel)gui.def_back_pixel,
1542 				XNFontSet, (XFontSet)gui.fontset,
1543 				NULL);
1544     }
1545     else
1546 #  endif
1547     {
1548 	preedit_list = XVaCreateNestedList(0,
1549 				XNSpotLocation, &over_spot,
1550 				XNForeground, (Pixel)gui.def_norm_pixel,
1551 				XNBackground, (Pixel)gui.def_back_pixel,
1552 				NULL);
1553 	status_list = XVaCreateNestedList(0,
1554 				XNForeground, (Pixel)gui.def_norm_pixel,
1555 				XNBackground, (Pixel)gui.def_back_pixel,
1556 				NULL);
1557     }
1558 
1559     xic = XCreateIC(xim,
1560 		    XNInputStyle, input_style,
1561 		    XNClientWindow, x11_window,
1562 		    XNFocusWindow, gui.wid,
1563 		    XNPreeditAttributes, preedit_list,
1564 		    XNStatusAttributes, status_list,
1565 		    NULL);
1566     XFree(status_list);
1567     XFree(preedit_list);
1568     if (xic != NULL)
1569     {
1570 	if (input_style & XIMStatusArea)
1571 	{
1572 	    xim_set_status_area();
1573 	    status_area_enabled = TRUE;
1574 	}
1575 	else
1576 	    gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH);
1577     }
1578     else
1579     {
1580 	if (!is_not_a_term())
1581 	    emsg(_(e_xim));
1582 	XCloseIM(xim);
1583 	return FALSE;
1584     }
1585 
1586     return TRUE;
1587 }
1588 
1589 #  endif // FEAT_GUI_X11
1590 
1591 /*
1592  * Get IM status.  When IM is on, return TRUE.  Else return FALSE.
1593  * FIXME: This doesn't work correctly: Having focus doesn't always mean XIM is
1594  * active, when not having focus XIM may still be active (e.g., when using a
1595  * tear-off menu item).
1596  */
1597     int
im_get_status(void)1598 im_get_status(void)
1599 {
1600 #  ifdef FEAT_EVAL
1601     if (USE_IMSTATUSFUNC)
1602 	return call_imstatusfunc();
1603 #  endif
1604     return xim_has_focus;
1605 }
1606 
1607 # endif // !FEAT_GUI_GTK
1608 
1609 # if !defined(FEAT_GUI_GTK) || defined(PROTO)
1610 /*
1611  * Set up the status area.
1612  *
1613  * This should use a separate Widget, but that seems not possible, because
1614  * preedit_area and status_area should be set to the same window as for the
1615  * text input.  Unfortunately this means the status area pollutes the text
1616  * window...
1617  */
1618     void
xim_set_status_area(void)1619 xim_set_status_area(void)
1620 {
1621     XVaNestedList preedit_list = 0, status_list = 0, list = 0;
1622     XRectangle pre_area, status_area;
1623 
1624     if (xic == NULL)
1625 	return;
1626 
1627     if (input_style & XIMStatusArea)
1628     {
1629 	if (input_style & XIMPreeditArea)
1630 	{
1631 	    XRectangle *needed_rect;
1632 
1633 	    // to get status_area width
1634 	    status_list = XVaCreateNestedList(0, XNAreaNeeded,
1635 					      &needed_rect, NULL);
1636 	    XGetICValues(xic, XNStatusAttributes, status_list, NULL);
1637 	    XFree(status_list);
1638 
1639 	    status_area.width = needed_rect->width;
1640 	}
1641 	else
1642 	    status_area.width = gui.char_width * Columns;
1643 
1644 	status_area.x = 0;
1645 	status_area.y = gui.char_height * Rows + gui.border_offset;
1646 	if (gui.which_scrollbars[SBAR_BOTTOM])
1647 	    status_area.y += gui.scrollbar_height;
1648 #ifdef FEAT_MENU
1649 	if (gui.menu_is_active)
1650 	    status_area.y += gui.menu_height;
1651 #endif
1652 	status_area.height = gui.char_height;
1653 	status_list = XVaCreateNestedList(0, XNArea, &status_area, NULL);
1654     }
1655     else
1656     {
1657 	status_area.x = 0;
1658 	status_area.y = gui.char_height * Rows + gui.border_offset;
1659 	if (gui.which_scrollbars[SBAR_BOTTOM])
1660 	    status_area.y += gui.scrollbar_height;
1661 #ifdef FEAT_MENU
1662 	if (gui.menu_is_active)
1663 	    status_area.y += gui.menu_height;
1664 #endif
1665 	status_area.width = 0;
1666 	status_area.height = gui.char_height;
1667     }
1668 
1669     if (input_style & XIMPreeditArea)   // off-the-spot
1670     {
1671 	pre_area.x = status_area.x + status_area.width;
1672 	pre_area.y = gui.char_height * Rows + gui.border_offset;
1673 	pre_area.width = gui.char_width * Columns - pre_area.x;
1674 	if (gui.which_scrollbars[SBAR_BOTTOM])
1675 	    pre_area.y += gui.scrollbar_height;
1676 #ifdef FEAT_MENU
1677 	if (gui.menu_is_active)
1678 	    pre_area.y += gui.menu_height;
1679 #endif
1680 	pre_area.height = gui.char_height;
1681 	preedit_list = XVaCreateNestedList(0, XNArea, &pre_area, NULL);
1682     }
1683     else if (input_style & XIMPreeditPosition)   // over-the-spot
1684     {
1685 	pre_area.x = 0;
1686 	pre_area.y = 0;
1687 	pre_area.height = gui.char_height * Rows;
1688 	pre_area.width = gui.char_width * Columns;
1689 	preedit_list = XVaCreateNestedList(0, XNArea, &pre_area, NULL);
1690     }
1691 
1692     if (preedit_list && status_list)
1693 	list = XVaCreateNestedList(0, XNPreeditAttributes, preedit_list,
1694 				   XNStatusAttributes, status_list, NULL);
1695     else if (preedit_list)
1696 	list = XVaCreateNestedList(0, XNPreeditAttributes, preedit_list,
1697 				   NULL);
1698     else if (status_list)
1699 	list = XVaCreateNestedList(0, XNStatusAttributes, status_list,
1700 				   NULL);
1701     else
1702 	list = NULL;
1703 
1704     if (list)
1705     {
1706 	XSetICValues(xic, XNVaNestedList, list, NULL);
1707 	XFree(list);
1708     }
1709     if (status_list)
1710 	XFree(status_list);
1711     if (preedit_list)
1712 	XFree(preedit_list);
1713 }
1714 
1715     int
xim_get_status_area_height(void)1716 xim_get_status_area_height(void)
1717 {
1718     if (status_area_enabled)
1719 	return gui.char_height;
1720     return 0;
1721 }
1722 # endif
1723 
1724 #else // !defined(FEAT_XIM)
1725 
1726 # if defined(IME_WITHOUT_XIM) || defined(VIMDLL) || defined(PROTO)
1727 static int im_was_set_active = FALSE;
1728 
1729     int
1730 #  ifdef VIMDLL
mbyte_im_get_status(void)1731 mbyte_im_get_status(void)
1732 #  else
1733 im_get_status(void)
1734 #  endif
1735 {
1736 #  if defined(FEAT_EVAL)
1737     if (USE_IMSTATUSFUNC)
1738 	return call_imstatusfunc();
1739 #  endif
1740     return im_was_set_active;
1741 }
1742 
1743     void
1744 #  ifdef VIMDLL
mbyte_im_set_active(int active_arg)1745 mbyte_im_set_active(int active_arg)
1746 #  else
1747 im_set_active(int active_arg)
1748 #  endif
1749 {
1750 #  if defined(FEAT_EVAL)
1751     int	    active = !p_imdisable && active_arg;
1752 
1753     if (USE_IMACTIVATEFUNC && active != im_get_status())
1754     {
1755 	call_imactivatefunc(active);
1756 	im_was_set_active = active;
1757     }
1758 #  endif
1759 }
1760 
1761 #  if defined(FEAT_GUI) && !defined(FEAT_GUI_HAIKU) && !defined(VIMDLL)
1762     void
im_set_position(int row UNUSED,int col UNUSED)1763 im_set_position(int row UNUSED, int col UNUSED)
1764 {
1765 }
1766 #  endif
1767 # endif
1768 
1769 #endif // FEAT_XIM
1770