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