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