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