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