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