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