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