1 /*
2 
3   Copyright (c) 2003-2013 uim Project https://github.com/uim/uim
4 
5   All rights reserved.
6 
7   Redistribution and use in source and binary forms, with or without
8   modification, are permitted provided that the following conditions
9   are met:
10 
11   1. Redistributions of source code must retain the above copyright
12      notice, this list of conditions and the following disclaimer.
13   2. Redistributions in binary form must reproduce the above copyright
14      notice, this list of conditions and the following disclaimer in the
15      documentation and/or other materials provided with the distribution.
16   3. Neither the name of authors nor the names of its contributors
17      may be used to endorse or promote products derived from this software
18      without specific prior written permission.
19 
20   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
21   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
24   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26   OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30   SUCH DAMAGE.
31 
32 */
33 
34 /*
35  * gtk+-immodule
36  */
37 #include <config.h>
38 
39 #include <gtk/gtk.h>
40 #include <gtk/gtkimmodule.h>
41 #include <gdk/gdkkeysyms.h>
42 #ifdef GDK_WINDOWING_X11
43 #include <gdk/gdkx.h>
44 #endif
45 #include <glib.h>
46 
47 #include <string.h>
48 #include <stdlib.h>
49 #include <unistd.h>
50 #include <locale.h>
51 
52 #include "uim/uim.h"
53 #include "uim/uim-util.h"
54 #include "uim/uim-helper.h"
55 #include "uim/uim-im-switcher.h"
56 #include "uim/gettext.h"
57 #include "uim/uim-scm.h"
58 #include "uim/counted-init.h"
59 
60 #include "gtk-im-uim.h"
61 #include "uim-cand-win-gtk.h"
62 #include "uim-cand-win-vertical-gtk.h"
63 #include "uim-cand-win-tbl-gtk.h"
64 #include "uim-cand-win-horizontal-gtk.h"
65 #include "caret-state-indicator.h"
66 #include "key-util-gtk.h"
67 #ifdef GDK_WINDOWING_X11
68 #include "compose.h"
69 #endif
70 #include "text-util.h"
71 
72 /* exported symbols */
73 GtkIMContext *im_module_create(const gchar *context_id);
74 void im_module_list(const GtkIMContextInfo ***contexts, int *n_contexts);
75 void im_module_exit(void);
76 void im_module_init(GTypeModule *type_module);
77 
78 #ifdef GDK_WINDOWING_X11
79 extern int compose_handle_key(GdkEventKey *key, IMUIMContext *uic);
80 #endif
81 
82 #define NR_CANDIDATES 20
83 #define DEFAULT_SEPARATOR_STR "|"
84 
85 struct preedit_segment {
86   int attr;
87   gchar *str;
88 };
89 
90 static int im_uim_fd = -1;
91 static unsigned int read_tag;
92 #if IM_UIM_USE_SNOOPER
93 static guint snooper_id;
94 static gboolean snooper_installed = FALSE;
95 #elif IM_UIM_USE_TOPLEVEL
96 static GtkWidget *cur_toplevel;
97 static GtkWidget *grab_widget;
98 static gulong cur_key_press_handler_id;
99 static gulong cur_key_release_handler_id;
100 static GList *cwin_list;
101 #endif
102 
103 static IMUIMContext context_list;
104 static IMUIMContext *focused_context = NULL;
105 static gboolean disable_focused_context = FALSE;
106 
107 static GObjectClass *parent_class;
108 
109 typedef struct _IMContextUIMClass
110 {
111   GtkIMContextClass parent_class;
112 } IMContextUIMClass;
113 
114 
115 static void cand_select_cb(void *ptr, int index);
116 static void im_uim_class_init(GtkIMContextClass *class);
117 static void im_uim_class_finalize(GtkIMContextClass *class);
118 static void im_uim_init(IMUIMContext *uic);
119 static void switch_app_global_im_cb(void *ptr, const char *name);
120 static void switch_system_global_im_cb(void *ptr, const char *name);
121 
122 #if IM_UIM_USE_SNOOPER
123 static gboolean key_snoop(GtkWidget *grab_widget, GdkEventKey *key, gpointer data);
124 #elif IM_UIM_USE_TOPLEVEL
125 static gboolean handle_key_on_toplevel(GtkWidget *widget, GdkEventKey *event, gpointer data);
126 #endif
127 #if IM_UIM_USE_DELAY
128 static void cand_delay_timer_remove(UIMCandWinGtk *cwin);
129 #endif
130 #if IM_UIM_USE_NEW_PAGE_HANDLING
131 static GSList *get_page_candidates(IMUIMContext *uic, guint page, guint nr, guint display_limit);
132 static void free_candidates(GSList *candidates);
133 #endif
134 static void send_im_list(void);
135 static UIMCandWinGtk *im_uim_create_cand_win_gtk(void);
136 
137 static const GTypeInfo class_info = {
138   sizeof(IMContextUIMClass),
139   (GBaseInitFunc) NULL,
140   (GBaseFinalizeFunc) NULL,
141   (GClassInitFunc) im_uim_class_init,
142   (GClassFinalizeFunc)im_uim_class_finalize,
143   NULL, /* for class data */
144   sizeof(IMUIMContext), /* size of instance */
145   0,
146   (GInstanceInitFunc) im_uim_init, /* constructor */
147 };
148 
149 static GType type_im_uim = 0;
150 
151 #define IM_UIM_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),type_im_uim,IMUIMContext))
152 
153 static const GtkIMContextInfo im_uim_info = {
154   "uim", /* id */
155   "uim", /* human-readable name*/
156   "uim", /* domain for gettext*/
157   LOCALEDIR,
158   "ja:ko:zh:*"
159 };
160 
161 static const GtkIMContextInfo *im_uim_info_list = {
162   &im_uim_info
163 };
164 
165 
166 
167 /* gtk's string handling */
168 
169 void
im_uim_commit_string(void * ptr,const char * str)170 im_uim_commit_string(void *ptr, const char *str)
171 {
172   IMUIMContext *uic = (IMUIMContext *)ptr;
173   uim_bool show_state;
174   gint x, y;
175 
176   g_return_if_fail(str);
177   g_signal_emit_by_name(uic, "commit", str);
178 
179   show_state = uim_scm_symbol_value_bool("bridge-show-input-state?");
180   if (show_state && uic->win) {
181     gdk_window_get_origin(uic->win, &x, &y);
182     caret_state_indicator_update(uic->caret_state_indicator, x, y, NULL);
183   }
184 }
185 
186 static void
commit_cb(GtkIMContext * ic,const gchar * str,IMUIMContext * is)187 commit_cb(GtkIMContext *ic, const gchar *str, IMUIMContext *is)
188 {
189   g_return_if_fail(str);
190   g_signal_emit_by_name(is, "commit", str);
191 }
192 
193 static gboolean
get_user_defined_color(PangoColor * color,const gchar * uim_symbol)194 get_user_defined_color(PangoColor *color, const gchar *uim_symbol)
195 {
196   gboolean parsed = FALSE;
197   char *literal = uim_scm_symbol_value_str(uim_symbol);
198 
199   if (literal != NULL && literal[0] != '\0')
200     parsed = pango_color_parse(color, literal);
201 
202   free(literal);
203 
204   return parsed;
205 }
206 
207 static gchar *
get_preedit_segment(struct preedit_segment * ps,PangoAttrList * attrs,gchar * str)208 get_preedit_segment(struct preedit_segment *ps, PangoAttrList *attrs,
209 		    gchar *str)
210 {
211   PangoAttribute *attr;
212   const gchar *segment_str = ps->str;
213   gint len;
214 
215   if ((ps->attr & UPreeditAttr_Separator) && !strcmp(segment_str, ""))
216     segment_str = DEFAULT_SEPARATOR_STR;
217 
218   if (attrs) {
219     PangoColor color;
220     int begin, end;
221 
222     begin = strlen(str);
223     end = begin + strlen(segment_str);
224 
225     if (ps->attr & UPreeditAttr_UnderLine) {
226       attr = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
227       attr->start_index = begin;
228       attr->end_index = end;
229       pango_attr_list_change(attrs, attr);
230     }
231 
232     if (ps->attr & UPreeditAttr_Separator) {
233       const gchar *separator_fg_symbol, *separator_bg_symbol;
234 
235       if (ps->attr & UPreeditAttr_Reverse) {
236 	separator_fg_symbol = "reversed-separator-foreground";
237 	separator_bg_symbol = "reversed-separator-background";
238       } else {
239 	separator_fg_symbol = "separator-foreground";
240 	separator_bg_symbol = "separator-background";
241       }
242 
243       if (get_user_defined_color(&color, separator_fg_symbol)) {
244 	attr = pango_attr_foreground_new(color.red, color.green, color.blue);
245 	attr->start_index = begin;
246 	attr->end_index = end;
247 	pango_attr_list_change(attrs, attr);
248       }
249 
250       if (get_user_defined_color(&color, separator_bg_symbol)) {
251 	attr = pango_attr_background_new(color.red, color.green, color.blue);
252 	attr->start_index = begin;
253 	attr->end_index = end;
254 	pango_attr_list_change(attrs, attr);
255       }
256     } else if (ps->attr & UPreeditAttr_Reverse) {
257       if (get_user_defined_color(&color, "reversed-preedit-foreground")
258 	  || pango_color_parse(&color, "#fff")) {
259 	attr = pango_attr_foreground_new(color.red, color.green, color.blue);
260 	attr->start_index = begin;
261 	attr->end_index = end;
262 	pango_attr_list_change(attrs, attr);
263       }
264 
265       if (get_user_defined_color(&color, "reversed-preedit-background")
266 	  || pango_color_parse(&color, "#000")) {
267 	attr = pango_attr_background_new(color.red, color.green, color.blue);
268 	attr->start_index = begin;
269 	attr->end_index = end;
270 	pango_attr_list_change(attrs, attr);
271       }
272     }
273   }
274 
275   len = strlen(str) + strlen(segment_str) + 1;
276   str = (gchar *)g_realloc(str, len);
277   g_strlcat(str, segment_str, len);
278 
279   return str;
280 }
281 
282 /* only used when use_preedit == FALSE */
283 static void
show_preedit(GtkIMContext * ic,GtkWidget * preedit_label)284 show_preedit(GtkIMContext *ic, GtkWidget *preedit_label)
285 {
286   IMUIMContext *uic = IM_UIM_CONTEXT(ic);
287   GtkWidget *preedit_window;
288   gchar *str;
289   gint cursor_pos;
290   PangoAttrList *attrs;
291 
292   preedit_window = gtk_widget_get_parent(preedit_label);
293 
294   gtk_im_context_get_preedit_string(ic, &str, &attrs, &cursor_pos);
295 
296   if (strlen(str) > 0) {
297     gint x, y, w, h;
298     PangoLayout *layout;
299 
300     gtk_label_set_text(GTK_LABEL(preedit_label), str);
301     gtk_label_set_attributes(GTK_LABEL(preedit_label), attrs);
302 
303     gdk_window_get_origin(uic->win, &x, &y);
304 
305     gtk_window_move(GTK_WINDOW(preedit_window),
306 		    x + uic->preedit_pos.x,
307 		    y + uic->preedit_pos.y);
308 
309     layout = gtk_label_get_layout(GTK_LABEL(preedit_label));
310 
311     pango_layout_get_cursor_pos(layout, 0, NULL, NULL);
312 
313     pango_layout_get_pixel_size(layout, &w, &h);
314     gtk_window_resize(GTK_WINDOW(preedit_window), w, h);
315 
316     gtk_widget_show(preedit_window);
317   } else {
318     gtk_label_set_text(GTK_LABEL(preedit_label), "");
319     gtk_widget_hide(preedit_window);
320     gtk_window_resize(GTK_WINDOW(preedit_window), 1, 1);
321   }
322   g_free(str);
323   pango_attr_list_unref(attrs);
324 }
325 
326 
327 
328 /* widget utilities */
329 
330 #if IM_UIM_USE_TOPLEVEL
331 static void
remove_cur_toplevel()332 remove_cur_toplevel()
333 {
334 #if GTK_CHECK_VERSION(2, 18, 0)
335   if (cur_toplevel && gtk_widget_is_toplevel(cur_toplevel)) {
336 #else
337   if (cur_toplevel && GTK_WIDGET_TOPLEVEL(cur_toplevel)) {
338 #endif
339     if (cur_key_press_handler_id)
340       g_signal_handler_disconnect(cur_toplevel, cur_key_press_handler_id);
341     if (cur_key_release_handler_id)
342       g_signal_handler_disconnect(cur_toplevel, cur_key_release_handler_id);
343     cur_toplevel = NULL;
344   }
345 }
346 
347 static gboolean
348 cur_toplevel_deleted(GtkWidget *widget, gpointer data)
349 {
350   cur_toplevel = NULL;
351 
352   return FALSE;
353 }
354 
355 static void
356 update_cur_toplevel(IMUIMContext *uic)
357 {
358   /* Don't set our candwin's text widget as cur_toplevel */
359   if (uic->widget) {
360     UIMCandWinGtk *cwin;
361     GList *tmp_list;
362 
363     tmp_list = cwin_list;
364     while (tmp_list) {
365       cwin = tmp_list->data;
366       if (cwin->sub_window.text_view &&
367 		      cwin->sub_window.text_view == uic->widget)
368 	  return;
369       tmp_list = tmp_list->next;
370     }
371   }
372 
373   if (uic->widget) {
374     GtkWidget *toplevel = gtk_widget_get_toplevel(uic->widget);
375 #if GTK_CHECK_VERSION(2, 18, 0)
376     if (toplevel && gtk_widget_is_toplevel(toplevel)) {
377 #else
378     if (toplevel && GTK_WIDGET_TOPLEVEL(toplevel)) {
379 #endif
380       if (cur_toplevel != toplevel) {
381 	remove_cur_toplevel();
382 	cur_toplevel = toplevel;
383 	cur_key_press_handler_id = g_signal_connect(cur_toplevel,
384 			"key-press-event",
385 			G_CALLBACK(handle_key_on_toplevel), uic);
386 	cur_key_release_handler_id = g_signal_connect(cur_toplevel,
387 			"key-release-event",
388 			G_CALLBACK(handle_key_on_toplevel), uic);
389 	g_signal_connect(cur_toplevel,
390 			"delete_event",
391 			G_CALLBACK(cur_toplevel_deleted), NULL);
392       }
393     } else
394       remove_cur_toplevel();
395   } else
396     remove_cur_toplevel();
397 }
398 
399 static void
400 on_client_widget_hierarchy_changed(GtkWidget *widget, GtkWidget *old_toplevel, IMUIMContext *uic)
401 {
402   update_cur_toplevel(uic);
403 }
404 
405 static gboolean
406 on_client_widget_grab_notify(GtkWidget *widget, gboolean was_grabbed, IMUIMContext *uic)
407 {
408   if (was_grabbed)
409     grab_widget = NULL;
410   else {
411     grab_widget = gtk_grab_get_current();
412     if (!grab_widget) {
413       if (cur_toplevel && GTK_IS_WINDOW(cur_toplevel)) {
414         GtkWindowGroup *group;
415         GtkWindow *window;
416 
417         window = GTK_WINDOW(cur_toplevel);
418         group = gtk_window_get_group(window);
419 #if GTK_CHECK_VERSION(2, 22, 0)
420         grab_widget = gtk_window_group_get_current_grab(group);
421 #else
422         if (group && group->grabs)
423           grab_widget = GTK_WIDGET(group->grabs->data);
424 #endif
425       }
426     }
427   }
428 
429   return FALSE;
430 }
431 #endif /* IM_UIM_USE_TOPLEVEL */
432 
433 static GtkWidget *
434 widget_for_window(GdkWindow *window)
435 {
436   while (window) {
437     gpointer user_data;
438     gdk_window_get_user_data(window, &user_data);
439     if (user_data)
440       return user_data;
441 
442     window = gdk_window_get_parent(window);
443   }
444 
445   return NULL;
446 }
447 
448 static void
449 update_client_widget(IMUIMContext *uic)
450 {
451   GtkWidget *new_widget = widget_for_window(uic->win);
452 
453 #if IM_UIM_USE_TOPLEVEL
454   if (new_widget != uic->widget) {
455     if (uic->widget) {
456       g_signal_handlers_disconnect_by_func(uic->widget,
457 		      (gpointer)(uintptr_t)on_client_widget_hierarchy_changed, uic);
458       g_signal_handlers_disconnect_by_func(uic->widget,
459 		      (gpointer)(uintptr_t)on_client_widget_grab_notify, uic);
460     }
461     uic->widget = new_widget;
462     if (uic->widget) {
463       g_signal_connect(uic->widget, "hierarchy-changed",
464 		      G_CALLBACK(on_client_widget_hierarchy_changed), uic);
465       g_signal_connect(uic->widget, "grab-notify",
466 		      G_CALLBACK(on_client_widget_grab_notify), uic);
467     }
468 
469     update_cur_toplevel(uic);
470   }
471 #else /* IM_UIM_USE_TOPLEVEL */
472   uic->widget = new_widget;
473 #endif
474 }
475 
476 
477 
478 /* utility functions */
479 
480 static int
481 preedit_strlen(IMUIMContext *uic)
482 {
483   int i, len = 0;
484 
485   for (i = 0; i < uic->nr_psegs; i++)
486     len += strlen(uic->pseg[i].str);
487 
488   return len;
489 }
490 
491 static void
492 index_changed_cb(UIMCandWinGtk *cwin, IMUIMContext *uic)
493 {
494   gint index;
495 #if IM_UIM_USE_NEW_PAGE_HANDLING
496   guint new_page;
497 #endif
498 
499   g_return_if_fail(UIM_IS_CAND_WIN_GTK(cwin));
500 
501   index = uim_cand_win_gtk_get_index(cwin);
502   uim_set_candidate_index(uic->uc, index);
503 
504 #if IM_UIM_USE_NEW_PAGE_HANDLING
505   new_page = uim_cand_win_gtk_query_new_page_by_cand_select(uic->cwin, index);
506 
507   if (!uic->cwin->stores->pdata[new_page]) {
508     /* index_changed signal was triggered by prev/next page button on candwin
509      * (not from uim (cand_select_cb(), cand_shift_page_cb()))
510      */
511     guint nr = uic->cwin->nr_candidates;
512     guint display_limit = uic->cwin->display_limit;
513     GSList *list = get_page_candidates(uic, new_page, nr, display_limit);
514     uim_cand_win_gtk_set_page_candidates(uic->cwin, new_page, list);
515     free_candidates(list);
516   }
517 #endif /* IM_UIM_USE_NEW_PAGE_HANDLING */
518 }
519 
520 static void
521 layout_candwin(IMUIMContext *uic)
522 {
523 #if GTK_CHECK_VERSION(2, 90, 0)
524   gint x, y, width, height;
525 #else
526   gint x, y, width, height, depth;
527 #endif
528 
529   g_return_if_fail(uic);
530 
531   if (uic->win && uic->cwin) {
532 #if GTK_CHECK_VERSION(2, 90, 0)
533     gdk_window_get_geometry(uic->win, &x, &y, &width, &height);
534 #else
535     gdk_window_get_geometry(uic->win, &x, &y, &width, &height, &depth);
536 #endif
537     gdk_window_get_origin(uic->win, &x, &y);
538     {
539       GtkWindow *window = NULL;
540       GdkWindow *gdk_window = uic->win;
541       while (gdk_window) {
542         gpointer user_data;
543         gdk_window_get_user_data(gdk_window, &user_data);
544         if (user_data && GTK_IS_WINDOW(user_data)) {
545           window = user_data;
546           break;
547         }
548         gdk_window = gdk_window_get_parent(gdk_window);
549       }
550       if (window) {
551         gtk_window_set_transient_for(GTK_WINDOW(uic->cwin), window);
552       }
553     }
554     uim_cand_win_gtk_layout(uic->cwin, x, y, width, height);
555   }
556 }
557 
558 static GdkFilterReturn
559 toplevel_window_candidate_cb(GdkXEvent *xevent, GdkEvent *ev, gpointer data)
560 {
561   IMUIMContext *uic = data;
562 
563   if (!uic)
564     return GDK_FILTER_CONTINUE;
565 
566   if (uic->cwin_is_active)
567     layout_candwin(uic);
568 
569   return GDK_FILTER_CONTINUE;
570 }
571 
572 #if IM_UIM_USE_TOPLEVEL
573 static inline gboolean
574 event_key_equal(GdkEventKey *event1, GdkEventKey *event2)
575 {
576   return (event1->type == event2->type &&
577 	  event1->window == event2->window &&
578 	  event1->send_event == event2->send_event &&
579 	  event1->time == event2->time &&
580 	  event1->state == event2->state &&
581 	  event1->keyval == event2->keyval &&
582 	  event1->length == event2->length &&
583 	  event1->string == event2->string &&
584 	  event1->hardware_keycode == event2->hardware_keycode &&
585 	  event1->group == event2->group);
586 }
587 
588 static void
589 init_event_key_rec(GdkEventKey *event)
590 {
591   event->type = -1;
592   event->window = NULL;
593   event->send_event = 0;
594   event->time = 0;
595   event->state = 0;
596   event->keyval = 0;
597   event->length = 0;
598   event->string = NULL;
599   event->hardware_keycode = 0;
600   event->group = 0;
601 }
602 
603 static inline void
604 store_event_key(GdkEventKey *dest, GdkEventKey *source)
605 {
606   memcpy(dest, source, sizeof(GdkEventKey));
607 }
608 #endif
609 
610 static GString *
611 get_caret_state_label_from_prop_list(const char *str)
612 {
613   gchar **lines;
614   GString *label;
615   int i;
616 
617   label = g_string_new("");
618   lines = g_strsplit(str, "\n", 0);
619   for (i = 0; lines[i] && strcmp("", lines[i]); i++) {
620     gchar **cols;
621 
622     cols = g_strsplit(lines[i], "\t", 0);
623     if (cols && cols[0]) {
624       if (!strcmp("branch", cols[0])) {
625 	gchar *iconic_label = cols[2];
626 
627 	if (strcmp(label->str, ""))
628 	  g_string_append(label, "\t");
629 	g_string_append(label, iconic_label);
630       }
631     }
632     g_strfreev(cols);
633   }
634   g_strfreev(lines);
635 
636   return label;
637 }
638 
639 
640 
641 /* callback functions for libuim */
642 
643 static void
644 clear_cb(void *ptr)
645 {
646   IMUIMContext *uic = (IMUIMContext *)ptr;
647   int i;
648 
649   for (i = 0; i < uic->nr_psegs; i++)
650     g_free(uic->pseg[i].str);
651   free(uic->pseg);
652 
653   uic->pseg = NULL;
654   uic->nr_psegs = 0;
655 }
656 
657 static void
658 pushback_cb(void *ptr, int attr, const char *str)
659 {
660   IMUIMContext *uic = (IMUIMContext *)ptr;
661   g_return_if_fail(str);
662 
663   if (!strcmp(str, "")
664       && !(attr & (UPreeditAttr_Cursor | UPreeditAttr_Separator)))
665     return;
666 
667   uic->pseg = realloc(uic->pseg,
668 		      sizeof(struct preedit_segment) * (uic->nr_psegs + 1));
669   uic->pseg[uic->nr_psegs].str = g_strdup(str);
670   uic->pseg[uic->nr_psegs].attr = attr;
671   uic->nr_psegs++;
672 }
673 
674 static void
675 update_cb(void *ptr)
676 {
677   IMUIMContext *uic = (IMUIMContext *)ptr;
678   int preedit_len;
679 
680   g_return_if_fail(uic);
681 
682   preedit_len = preedit_strlen(uic);
683 
684   if (uic->prev_preedit_len == 0 && preedit_len)
685     g_signal_emit_by_name(uic, "preedit_start");
686 
687   if (uic->prev_preedit_len || preedit_len)
688     g_signal_emit_by_name(uic, "preedit_changed");
689 
690   if (uic->prev_preedit_len && preedit_len == 0)
691     g_signal_emit_by_name(uic, "preedit_end");
692 
693   uic->prev_preedit_len = preedit_len;
694 }
695 
696 static void
697 update_prop_list_cb(void *ptr, const char *str)
698 {
699   IMUIMContext *uic = (IMUIMContext *)ptr;
700   GString *prop_list;
701   uim_bool show_state;
702   char *show_state_with;
703   uim_bool show_state_mode;
704   uim_bool show_state_mode_on;
705 
706   if (uic != focused_context || disable_focused_context)
707     return;
708 
709   prop_list = g_string_new("");
710   g_string_printf(prop_list, "prop_list_update\ncharset=UTF-8\n%s", str);
711 
712   uim_helper_send_message(im_uim_fd, prop_list->str);
713   g_string_free(prop_list, TRUE);
714 
715   show_state = uim_scm_symbol_value_bool("bridge-show-input-state?");
716   show_state_with = uim_scm_c_symbol(uim_scm_symbol_value("bridge-show-with?"));
717   show_state_mode = (strcmp(show_state_with, "mode") == 0);
718   show_state_mode_on = uim_scm_symbol_value_bool("bridge-show-input-state-mode-on?");
719 
720   if (uic->win) {
721     if (show_state && !(show_state_mode && !show_state_mode_on)) {
722       gint timeout;
723       gint x, y;
724       GString *label;
725 
726       gdk_window_get_origin(uic->win, &x, &y);
727       label = get_caret_state_label_from_prop_list(str);
728       caret_state_indicator_update(uic->caret_state_indicator, x, y, label->str);
729       g_string_free(label, TRUE);
730       if (strcmp(show_state_with, "time") == 0)
731 	timeout = uim_scm_symbol_value_int("bridge-show-input-state-time-length");
732       else
733 	timeout = 0;
734 
735       if (timeout != 0)
736 	caret_state_indicator_set_timeout(uic->caret_state_indicator,
737 					timeout * 1000);
738       gtk_widget_show_all(uic->caret_state_indicator);
739     } else if (show_state_mode && !show_state_mode_on) {
740       gtk_widget_hide(uic->caret_state_indicator);
741     }
742   }
743   free(show_state_with);
744 }
745 
746 #if IM_UIM_USE_NEW_PAGE_HANDLING
747 static GSList *
748 get_page_candidates(IMUIMContext *uic,
749 		    guint page,
750 		    guint nr,
751 		    guint display_limit)
752 {
753   gint i, page_nr, start;
754   GSList *list = NULL;
755 
756   start = page * display_limit;
757   if (display_limit && (nr - start) > display_limit)
758     page_nr = display_limit;
759   else
760     page_nr = nr - start;
761 
762   for (i = start; i < (start + page_nr); i++) {
763     uim_candidate cand = uim_get_candidate(uic->uc, i,
764 		    display_limit ? (int)(i % display_limit) : i);
765     list = g_slist_prepend(list, cand);
766   }
767   list = g_slist_reverse(list);
768 
769   return list;
770 }
771 
772 static void
773 free_candidates(GSList *candidates)
774 {
775   g_slist_foreach(candidates, (GFunc)uim_candidate_free, NULL);
776   g_slist_free(candidates);
777 }
778 #endif /* IM_UIM_USE_NEW_PAGE_HANDLING */
779 
780 static void
781 cand_activate_cb(void *ptr, int nr, int display_limit)
782 {
783   IMUIMContext *uic = (IMUIMContext *)ptr;
784   GSList *list = NULL;
785 #if !IM_UIM_USE_NEW_PAGE_HANDLING
786   uim_candidate cand;
787   gint i;
788 #endif
789 
790 #if IM_UIM_USE_DELAY
791   cand_delay_timer_remove(uic->cwin);
792 #endif
793 
794   uic->cwin_is_active = TRUE;
795 
796 #if !IM_UIM_USE_NEW_PAGE_HANDLING
797   for (i = 0; i < nr; i++) {
798     cand = uim_get_candidate(uic->uc, i, display_limit ? i % display_limit : i);
799     list = g_slist_prepend(list, cand);
800   }
801   list = g_slist_reverse(list);
802 
803   uim_cand_win_gtk_set_candidates(uic->cwin, display_limit, list);
804 
805   g_slist_foreach(list, (GFunc)uim_candidate_free, NULL);
806   g_slist_free(list);
807 #else
808   list = get_page_candidates(uic, 0, nr, display_limit);
809 
810   uim_cand_win_gtk_set_nr_candidates(uic->cwin, nr, display_limit);
811   uic->cwin->candidate_index = -1; /* Don't select any candidate at first */
812   uim_cand_win_gtk_set_page_candidates(uic->cwin, 0, list);
813   uim_cand_win_gtk_set_page(uic->cwin, 0);
814 
815   free_candidates(list);
816 #endif /* IM_UIM_USE_NEW_PAGE_HANDLING */
817 
818   layout_candwin(uic);
819   gtk_widget_show(GTK_WIDGET(uic->cwin));
820 
821   if (uic->win) {
822     GdkWindow *toplevel;
823 
824     toplevel = gdk_window_get_toplevel(uic->win);
825     gdk_window_add_filter(toplevel, toplevel_window_candidate_cb, uic);
826   }
827 }
828 
829 #if IM_UIM_USE_DELAY
830 static gint
831 cand_activate_timeout(gpointer data)
832 {
833   IMUIMContext *uic = (IMUIMContext *)data;
834   int nr = -1, display_limit = -1, selected_index = -1;
835 
836   g_object_set_data(G_OBJECT(uic->cwin), "timeout-tag", GUINT_TO_POINTER(0));
837   uim_delay_activating(uic->uc, &nr, &display_limit, &selected_index);
838   if (nr > 0) {
839     cand_activate_cb(uic, nr, display_limit);
840     if (selected_index >= 0) {
841       cand_select_cb(uic, selected_index);
842     }
843   }
844   return FALSE;
845 }
846 
847 static void
848 cand_delay_timer_remove(UIMCandWinGtk *cwin)
849 {
850   guint tag = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(cwin), "timeout-tag"));
851   if (tag > 0)
852     g_source_remove(tag);
853 }
854 
855 static void
856 cand_activate_with_delay_cb(void *ptr, int delay)
857 {
858   IMUIMContext *uic = (IMUIMContext *)ptr;
859   guint tag;
860 
861   cand_delay_timer_remove(uic->cwin);
862   if (delay > 0) {
863     /* g_timeout_add_seconds() needs GLib 2.14 */
864     tag = g_timeout_add(delay * 1000, cand_activate_timeout, (gpointer)uic);
865     g_object_set_data(G_OBJECT(uic->cwin), "timeout-tag", GUINT_TO_POINTER(tag));
866   } else {
867     cand_activate_timeout(ptr);
868   }
869 }
870 #endif /* IM_UIM_USE_DELAY */
871 
872 static void
873 cand_select_cb(void *ptr, int index)
874 {
875   IMUIMContext *uic = (IMUIMContext *)ptr;
876 #if IM_UIM_USE_NEW_PAGE_HANDLING
877   guint new_page;
878 #endif
879 
880   layout_candwin(uic);
881 #if IM_UIM_USE_NEW_PAGE_HANDLING
882   new_page = uim_cand_win_gtk_query_new_page_by_cand_select(uic->cwin, index);
883 
884   if (!uic->cwin->stores->pdata[new_page]) {
885     guint nr = uic->cwin->nr_candidates;
886     guint display_limit = uic->cwin->display_limit;
887     GSList *list = get_page_candidates(uic, new_page, nr, display_limit);
888     uim_cand_win_gtk_set_page_candidates(uic->cwin, new_page, list);
889     free_candidates(list);
890   }
891 #endif /* IM_UIM_USE_NEW_PAGE_HANDLING */
892   g_signal_handlers_block_by_func(uic->cwin, (gpointer)(uintptr_t)index_changed_cb, uic);
893   uim_cand_win_gtk_set_index(uic->cwin, index);
894   g_signal_handlers_unblock_by_func(uic->cwin, (gpointer)(uintptr_t)index_changed_cb, uic);
895 }
896 
897 static void
898 cand_shift_page_cb(void *ptr, int direction)
899 {
900   IMUIMContext *uic = (IMUIMContext *)ptr;
901 #if IM_UIM_USE_NEW_PAGE_HANDLING
902   guint new_page;
903 #endif
904 
905   layout_candwin(uic);
906 
907   g_signal_handlers_block_by_func(uic->cwin,
908 				  (gpointer)(uintptr_t)index_changed_cb, uic);
909 #if IM_UIM_USE_NEW_PAGE_HANDLING
910   new_page = uim_cand_win_gtk_query_new_page_by_shift_page(uic->cwin,
911 							 direction);
912   if (!uic->cwin->stores->pdata[new_page]) {
913     guint nr = uic->cwin->nr_candidates;
914     guint display_limit = uic->cwin->display_limit;
915     GSList *list = get_page_candidates(uic, new_page, nr, display_limit);
916     uim_cand_win_gtk_set_page_candidates(uic->cwin, new_page, list);
917     free_candidates(list);
918   }
919 #endif /* IM_UIM_USE_NEW_PAGE_HANDLING */
920   uim_cand_win_gtk_shift_page(uic->cwin, direction);
921   if (uic->cwin->candidate_index != -1)
922     uim_set_candidate_index(uic->uc, uic->cwin->candidate_index);
923   g_signal_handlers_unblock_by_func(uic->cwin,
924 				   (gpointer)(uintptr_t)index_changed_cb, uic);
925 }
926 
927 static void
928 cand_deactivate_cb(void *ptr)
929 {
930   IMUIMContext *uic = (IMUIMContext *)ptr;
931 
932   uic->cwin_is_active = FALSE;
933 
934   if (uic->cwin) {
935 #if IM_UIM_USE_DELAY
936     cand_delay_timer_remove(uic->cwin);
937 #endif
938     gtk_widget_hide(GTK_WIDGET(uic->cwin));
939     uim_cand_win_gtk_clear_candidates(uic->cwin);
940   }
941 
942   if (uic->win) {
943     GdkWindow *toplevel;
944 
945     toplevel = gdk_window_get_toplevel(uic->win);
946     gdk_window_remove_filter(toplevel, toplevel_window_candidate_cb, uic);
947   }
948 }
949 
950 static void
951 configuration_changed_cb(void *ptr)
952 {
953   IMUIMContext *uic = (IMUIMContext *)ptr;
954 
955   if (focused_context == uic && !disable_focused_context)
956     send_im_list();
957 }
958 
959 static void
960 switch_app_global_im_cb(void *ptr, const char *name)
961 {
962   IMUIMContext *uic, *cc;
963   GString *im_name_sym;
964 
965   uic = (IMUIMContext *)ptr;
966   im_name_sym = g_string_new(name);
967   g_string_prepend_c(im_name_sym, '\'');
968 
969   for (cc = context_list.next; cc != &context_list; cc = cc->next) {
970     if (cc != uic)
971       uim_switch_im(cc->uc, name);
972   }
973   uim_prop_update_custom(uic->uc,
974 			 "custom-preserved-default-im-name", im_name_sym->str);
975   g_string_free(im_name_sym, TRUE);
976 }
977 
978 static void
979 switch_system_global_im_cb(void *ptr, const char *name)
980 {
981   GString *msg;
982 
983   /* switch contexts of this process */
984   switch_app_global_im_cb(ptr, name);
985 
986   /* Switch contexts of other processes. Bridges should not expect
987    * that the helper-server reflect back the messaage to the
988    * originating process.  -- YamaKen 2006-03-01 */
989   msg = g_string_new("");
990   g_string_printf(msg, "im_change_whole_desktop\n%s\n", name);
991   uim_helper_send_message(im_uim_fd, msg->str);
992   g_string_free(msg, TRUE);
993 }
994 
995 static int
996 acquire_text_cb(void *ptr, enum UTextArea text_id, enum UTextOrigin origin,
997 		int former_req_len, int latter_req_len, char **former,
998 		char **latter)
999 {
1000   int err;
1001   IMUIMContext *uic = (IMUIMContext *)ptr;
1002 
1003   switch (text_id) {
1004   case UTextArea_Primary:
1005     err = im_uim_acquire_primary_text(uic, origin, former_req_len,
1006 				      latter_req_len, former, latter);
1007     break;
1008   case UTextArea_Selection:
1009     err = im_uim_acquire_selection_text(uic, origin, former_req_len,
1010 					latter_req_len, former, latter);
1011     break;
1012   case UTextArea_Clipboard:
1013     err = im_uim_acquire_clipboard_text(uic, origin, former_req_len,
1014 					latter_req_len, former, latter);
1015     break;
1016   case UTextArea_Unspecified:
1017   default:
1018     err = -1;
1019   }
1020 
1021   return err;
1022 }
1023 
1024 static int
1025 delete_text_cb(void *ptr, enum UTextArea text_id, enum UTextOrigin origin,
1026 		int former_req_len, int latter_req_len)
1027 {
1028   int err;
1029   IMUIMContext *uic = (IMUIMContext *)ptr;
1030 
1031   switch (text_id) {
1032   case UTextArea_Primary:
1033     err = im_uim_delete_primary_text(uic, origin, former_req_len,
1034 				     latter_req_len);
1035     break;
1036   case UTextArea_Selection:
1037     err = im_uim_delete_selection_text(uic, origin, former_req_len,
1038 				       latter_req_len);
1039     break;
1040   case UTextArea_Clipboard:
1041   case UTextArea_Unspecified:
1042   default:
1043     err = -1;
1044   }
1045 
1046   return err;
1047 }
1048 
1049 /* uim helper related */
1050 
1051 static void
1052 helper_disconnect_cb(void)
1053 {
1054   im_uim_fd = -1;
1055   g_source_remove(read_tag);
1056 }
1057 
1058 static void
1059 parse_helper_str_im_change(const char *str)
1060 {
1061   IMUIMContext *cc;
1062   gchar **lines = g_strsplit(str, "\n", -1);
1063   gchar *im_name = lines[1];
1064   GString *im_name_sym = g_string_new(im_name);
1065 
1066   g_string_prepend_c(im_name_sym, '\'');
1067 
1068   if (g_str_has_prefix(str, "im_change_this_text_area_only") == TRUE) {
1069     if (focused_context && disable_focused_context == FALSE) {
1070       uim_switch_im(focused_context->uc, im_name);
1071       uim_prop_list_update(focused_context->uc);
1072     }
1073   } else if (g_str_has_prefix(str, "im_change_whole_desktop") == TRUE) {
1074     for (cc = context_list.next; cc != &context_list; cc = cc->next) {
1075       uim_switch_im(cc->uc, im_name);
1076       uim_prop_update_custom(cc->uc, "custom-preserved-default-im-name",
1077 			     im_name_sym->str);
1078       if (focused_context && cc == focused_context)
1079 	uim_prop_list_update(cc->uc);
1080     }
1081   } else if (g_str_has_prefix(str, "im_change_this_application_only") == TRUE) {
1082     if (focused_context && disable_focused_context == FALSE) {
1083       for (cc = context_list.next; cc != &context_list; cc = cc->next) {
1084 	uim_switch_im(cc->uc, im_name);
1085 	uim_prop_update_custom(cc->uc, "custom-preserved-default-im-name",
1086 			       im_name_sym->str);
1087 	if (cc == focused_context)
1088 	  uim_prop_list_update(cc->uc);
1089       }
1090     }
1091   }
1092   g_strfreev(lines);
1093   g_string_free(im_name_sym, TRUE);
1094 }
1095 
1096 static void
1097 send_im_list(void)
1098 {
1099   int nr, i;
1100   GString *msg;
1101   const char *current_im_name;
1102 
1103   if (!focused_context)
1104     return;
1105 
1106   nr = uim_get_nr_im(focused_context->uc);
1107   current_im_name = uim_get_current_im_name(focused_context->uc);
1108 
1109   msg = g_string_new("im_list\ncharset=UTF-8\n");
1110   for (i = 0; i < nr; i++) {
1111     /*
1112      * Return value of uim_get_im_language() is an ISO 639-1
1113      * compatible language code such as "ja". Since it is unfriendly
1114      * for human reading, we convert it into friendly one by
1115      * uim_get_language_name_from_locale() here.
1116      */
1117     const char *name = uim_get_im_name(focused_context->uc, i);
1118     const char *langcode = uim_get_im_language(focused_context->uc, i);
1119     const char *lang = uim_get_language_name_from_locale(langcode);
1120     const char *short_desc = uim_get_im_short_desc(focused_context->uc, i);
1121 
1122     g_string_append(msg, name);
1123     g_string_append(msg, "\t");
1124     if (lang)
1125       g_string_append(msg, lang);
1126     g_string_append(msg, "\t");
1127     if (short_desc)
1128       g_string_append(msg, short_desc);
1129     g_string_append(msg, "\t");
1130     if (strcmp(name, current_im_name) == 0)
1131       g_string_append(msg, "selected");
1132     g_string_append(msg, "\n");
1133   }
1134   uim_helper_send_message(im_uim_fd, msg->str);
1135   g_string_free(msg, TRUE);
1136 }
1137 
1138 /* Copied from helper-common-gtk.c. Maybe we need common GTK+ utility file. */
1139 static gchar *
1140 get_charset(gchar *line)
1141 {
1142   gchar **splitted = NULL;
1143 
1144   splitted = g_strsplit(line, "=", 0);
1145 
1146   if (splitted && splitted[0] && splitted[1]
1147       && strcmp("charset", splitted[0]) == 0) {
1148     gchar *charset = g_strdup(splitted[1]);
1149     g_strfreev(splitted);
1150     return charset;
1151   } else {
1152     g_strfreev(splitted);
1153     return NULL;
1154   }
1155 }
1156 
1157 static void
1158 commit_string_from_other_process(const gchar *str)
1159 {
1160   gchar **lines = g_strsplit(str, "\n", 0);
1161   gchar *commit_string;
1162 
1163   if (!lines || !lines[0] || !lines[1] || !lines[2])
1164     return; /* Message is broken, do nothing. */
1165 
1166   /*
1167    * If second line exists, we assume the first line as a charset
1168    * specifier.  This (rotten) convention is influenced by old design
1169    * mistake (character encoding was forgotten!).
1170    */
1171   if (strcmp(lines[2], "") != 0) {
1172     gchar *encoding, *commit_string_utf8;
1173 
1174     encoding = get_charset(lines[1]);
1175     commit_string = lines[2];
1176     commit_string_utf8 = g_convert(commit_string, strlen(commit_string),
1177 				   "UTF-8", encoding,
1178 				   NULL, /* gsize *bytes_read */
1179 				   NULL, /* size *bytes_written */
1180 				   NULL); /* GError **error */
1181     g_signal_emit_by_name(focused_context, "commit", commit_string_utf8);
1182     g_free(encoding);
1183     g_free(commit_string_utf8);
1184   } else {
1185     /* Assuming character encoding as UTF-8. */
1186     commit_string = lines[1];
1187     g_signal_emit_by_name(focused_context, "commit", commit_string);
1188   }
1189 
1190   g_strfreev(lines);
1191 }
1192 
1193 static void
1194 update_candwin_pos_type()
1195 {
1196   IMUIMContext *cc;
1197 
1198   for (cc = context_list.next; cc != &context_list; cc = cc->next) {
1199     if (cc->cwin)
1200       uim_cand_win_gtk_get_window_pos_type(cc->cwin);
1201   }
1202 }
1203 
1204 static void
1205 update_candwin_style()
1206 {
1207   IMUIMContext *cc;
1208   char *candwinprog; /* deprecated */
1209 
1210   candwinprog = uim_scm_symbol_value_str("uim-candwin-prog");
1211   /* don't update window style if deprecated uim-candwin-prog is set */
1212   if (candwinprog) {
1213     free(candwinprog);
1214     return;
1215   }
1216 
1217   for (cc = context_list.next; cc != &context_list; cc = cc->next) {
1218     if (cc->cwin) {
1219       g_signal_handlers_disconnect_by_func(cc->cwin,
1220 		      (gpointer)(uintptr_t)index_changed_cb, cc);
1221 #if IM_UIM_USE_DELAY
1222       cand_delay_timer_remove(cc->cwin);
1223 #endif
1224       gtk_widget_destroy(GTK_WIDGET(cc->cwin));
1225 #if IM_UIM_USE_TOPLEVEL
1226       cwin_list = g_list_remove(cwin_list, cc->cwin);
1227 #endif
1228       cc->cwin = im_uim_create_cand_win_gtk();
1229 #if IM_UIM_USE_TOPLEVEL
1230       cwin_list = g_list_append(cwin_list, cc->cwin);
1231 #endif
1232       g_signal_connect(G_OBJECT(cc->cwin), "index-changed",
1233 		       G_CALLBACK(index_changed_cb), cc);
1234     }
1235   }
1236 }
1237 
1238 static void
1239 parse_helper_str(const char *str)
1240 {
1241   gchar **lines;
1242 
1243   if (g_str_has_prefix(str, "im_change") == TRUE) {
1244     parse_helper_str_im_change(str);
1245   } else if (g_str_has_prefix(str, "prop_update_custom") == TRUE) {
1246     IMUIMContext *cc;
1247 
1248     lines = g_strsplit(str, "\n", 0);
1249     if (lines && lines[0] && lines[1] && lines[2]) {
1250       for (cc = context_list.next; cc != &context_list; cc = cc->next) {
1251 	uim_prop_update_custom(cc->uc, lines[1], lines[2]);
1252 	if (!strcmp(lines[1], "candidate-window-position"))
1253 	  update_candwin_pos_type();
1254 	if (!strcmp(lines[1], "candidate-window-style"))
1255 	  update_candwin_style();
1256 	break;  /* all custom variables are global */
1257       }
1258       g_strfreev(lines);
1259     }
1260   } else if (g_str_has_prefix(str, "custom_reload_notify") == TRUE) {
1261     uim_prop_reload_configs();
1262     update_candwin_pos_type();
1263     update_candwin_style();
1264   } else if (focused_context && !disable_focused_context) {
1265     if (g_str_has_prefix(str, "prop_list_get") == TRUE) {
1266       uim_prop_list_update(focused_context->uc);
1267     } else if (g_str_has_prefix(str, "prop_activate") == TRUE) {
1268       lines = g_strsplit(str, "\n", 0);
1269       if (lines && lines[0]) {
1270 	uim_prop_activate(focused_context->uc, lines[1]);
1271 	g_strfreev(lines);
1272       }
1273     } else if (g_str_has_prefix(str, "im_list_get") == TRUE) {
1274       send_im_list();
1275     } else if (g_str_has_prefix(str, "commit_string")) {
1276       commit_string_from_other_process(str);
1277     } else if (g_str_has_prefix(str, "focus_in") == TRUE) {
1278       disable_focused_context = TRUE;
1279       /*
1280        * We don't set "focused_context = NULL" here, because some
1281        * window managers have some focus related bugs??
1282        */
1283     }
1284   }
1285 }
1286 
1287 static gboolean
1288 helper_read_cb(GIOChannel *channel, GIOCondition c, gpointer p)
1289 {
1290   char *msg;
1291   int fd = g_io_channel_unix_get_fd(channel);
1292 
1293   uim_helper_read_proc(fd);
1294   while ((msg = uim_helper_get_message())) {
1295     parse_helper_str(msg);
1296     free(msg);
1297   }
1298   return TRUE;
1299 }
1300 
1301 static void
1302 check_helper_connection(uim_context uc)
1303 {
1304   if (im_uim_fd < 0) {
1305     im_uim_fd = uim_helper_init_client_fd(helper_disconnect_cb);
1306     if (im_uim_fd >= 0) {
1307       GIOChannel *channel;
1308       uim_set_uim_fd(uc, im_uim_fd);
1309       channel = g_io_channel_unix_new(im_uim_fd);
1310       read_tag = g_io_add_watch(channel, G_IO_IN | G_IO_HUP | G_IO_ERR,
1311 				helper_read_cb, NULL);
1312       g_io_channel_unref(channel);
1313     }
1314   }
1315 }
1316 
1317 
1318 
1319 /* class functions */
1320 
1321 /*
1322  * filter key event handler
1323  *
1324  * uim uses key snooper or toplevel key event for IM.  So filter key
1325  * event is just for fallbacks.
1326  *
1327  */
1328 static gboolean
1329 im_uim_filter_keypress(GtkIMContext *ic, GdkEventKey *key)
1330 {
1331   IMUIMContext *uic = IM_UIM_CONTEXT(ic);
1332   int rv;
1333 
1334 #if IM_UIM_USE_SNOOPER
1335   if (!snooper_installed) {
1336 #elif IM_UIM_USE_TOPLEVEL
1337   if (!cur_toplevel || (cur_toplevel && grab_widget) ||
1338 		  !event_key_equal(&uic->event_rec, key)) {
1339 #else
1340   if (TRUE) {
1341 #endif
1342     int kv, mod;
1343 
1344     im_uim_convert_keyevent(key, &kv, &mod);
1345 
1346     if (key->type == GDK_KEY_RELEASE)
1347       rv = uim_release_key(uic->uc, kv, mod);
1348     else
1349       rv = uim_press_key(uic->uc, kv, mod);
1350 
1351     if (rv) {
1352 #ifdef GDK_WINDOWING_X11
1353       rv = compose_handle_key(key, uic);
1354       if (rv)
1355 #endif
1356         return gtk_im_context_filter_keypress(uic->slave, key);
1357     }
1358 
1359     return TRUE;
1360   }
1361 
1362 #ifdef GDK_WINDOWING_X11
1363   rv = compose_handle_key(key, uic);
1364   if (rv)
1365     return gtk_im_context_filter_keypress(uic->slave, key);
1366 
1367   return TRUE;
1368 #else
1369   return gtk_im_context_filter_keypress(uic->slave, key);
1370 #endif
1371 }
1372 
1373 static void
1374 im_uim_get_preedit_string(GtkIMContext *ic, gchar **str, PangoAttrList **attrs,
1375 			  gint *cursor_pos)
1376 {
1377   gchar *tmp;
1378   int i, pos = 0;
1379   IMUIMContext *uic = IM_UIM_CONTEXT(ic);
1380 
1381   if (attrs)
1382     *attrs = pango_attr_list_new();
1383 
1384   tmp = g_strdup("");
1385 
1386   for (i = 0; i < uic->nr_psegs; i++) {
1387     if (uic->pseg[i].attr & UPreeditAttr_Cursor)
1388       pos = g_utf8_strlen(tmp, -1);
1389 
1390     if (attrs)
1391       tmp = get_preedit_segment(&uic->pseg[i], *attrs, tmp);
1392     else
1393       tmp = get_preedit_segment(&uic->pseg[i], NULL, tmp);
1394   }
1395   if (cursor_pos)
1396     *cursor_pos = pos;
1397 
1398   if (str)
1399     *str = tmp;
1400   else
1401     g_free(tmp);
1402 }
1403 
1404 static void
1405 im_uim_set_cursor_location(GtkIMContext *ic, GdkRectangle *area)
1406 {
1407   IMUIMContext *uic = IM_UIM_CONTEXT(ic);
1408 
1409   uic->preedit_pos = *area;
1410   uim_cand_win_gtk_set_cursor_location(uic->cwin, area);
1411   caret_state_indicator_set_cursor_location(uic->caret_state_indicator, area);
1412 
1413   if (uic->cwin_is_active)
1414     layout_candwin(uic);
1415 }
1416 
1417 static void
1418 im_uim_focus_in(GtkIMContext *ic)
1419 {
1420   IMUIMContext *uic = IM_UIM_CONTEXT(ic);
1421   IMUIMContext *cc;
1422 
1423   focused_context = uic;
1424   disable_focused_context = FALSE;
1425 
1426 #if IM_UIM_USE_SNOOPER
1427   /* Using key snooper is not recommended */
1428   if (snooper_installed == FALSE) {
1429     snooper_id = gtk_key_snooper_install((GtkKeySnoopFunc)key_snoop, NULL);
1430     snooper_installed = TRUE;
1431   }
1432 #elif IM_UIM_USE_TOPLEVEL
1433   update_cur_toplevel(uic);
1434 #endif
1435 
1436   check_helper_connection(uic->uc);
1437   uim_helper_client_focus_in(uic->uc);
1438   uim_prop_list_update(uic->uc);
1439 
1440   for (cc = context_list.next; cc != &context_list; cc = cc->next) {
1441     if (cc != uic && cc->cwin)
1442       gtk_widget_hide(GTK_WIDGET(cc->cwin));
1443   }
1444 
1445   if (uic->cwin && uic->cwin_is_active)
1446     gtk_widget_show(GTK_WIDGET(uic->cwin));
1447 
1448   uim_focus_in_context(uic->uc);
1449 }
1450 
1451 static void
1452 im_uim_focus_out(GtkIMContext *ic)
1453 {
1454   IMUIMContext *uic = IM_UIM_CONTEXT(ic);
1455 
1456 #if IM_UIM_USE_SNOOPER
1457   if (snooper_installed == TRUE) {
1458     gtk_key_snooper_remove(snooper_id);
1459     snooper_installed = FALSE;
1460   }
1461 #elif IM_UIM_USE_TOPLEVEL
1462   remove_cur_toplevel();
1463 #endif
1464 
1465   uim_focus_out_context(uic->uc);
1466 
1467   check_helper_connection(uic->uc);
1468   uim_helper_client_focus_out(uic->uc);
1469 
1470   if (uic->cwin)
1471     gtk_widget_hide(GTK_WIDGET(uic->cwin));
1472 
1473   gtk_widget_hide(uic->caret_state_indicator);
1474 }
1475 
1476 #define WORKAROUND_BROKEN_RESET_IN_GTK	1
1477 static void
1478 im_uim_reset(GtkIMContext *ic)
1479 {
1480   IMUIMContext *uic = IM_UIM_CONTEXT(ic);
1481 #if !defined(WORKAROUND_BROKEN_RESET_IN_GTK)
1482   uim_reset_context(uic->uc);
1483   clear_cb(uic);
1484   update_cb(uic);
1485 #else
1486   if (uic == focused_context) {
1487     uim_focus_out_context(uic->uc);
1488     uim_focus_in_context(uic->uc);
1489   } else {
1490     uim_reset_context(uic->uc);
1491     clear_cb(uic);
1492     update_cb(uic);
1493   }
1494 #endif
1495 #ifdef GDK_WINDOWING_X11
1496   im_uim_compose_reset(uic->compose);
1497 #endif
1498 }
1499 
1500 static void
1501 im_uim_set_use_preedit(GtkIMContext *ic, gboolean use_preedit)
1502 {
1503   IMUIMContext *uic = IM_UIM_CONTEXT(ic);
1504   GtkWidget *preedit_label = NULL;
1505 
1506   if (use_preedit == FALSE) {
1507     if (!uic->preedit_window) {
1508       uic->preedit_window = gtk_window_new(GTK_WINDOW_POPUP);
1509       preedit_label = gtk_label_new("");
1510       gtk_container_add(GTK_CONTAINER(uic->preedit_window), preedit_label);
1511       gtk_widget_show(preedit_label);
1512     }
1513     uic->preedit_handler_id =
1514       g_signal_connect(G_OBJECT(ic), "preedit-changed",
1515 		       G_CALLBACK(show_preedit), preedit_label);
1516   } else {
1517     if (uic->preedit_handler_id) {
1518       g_signal_handler_disconnect(G_OBJECT(ic), uic->preedit_handler_id);
1519       uic->preedit_handler_id = 0;
1520     }
1521     if (uic->preedit_window) {
1522       gtk_widget_destroy(uic->preedit_window);
1523       uic->preedit_window = NULL;
1524     }
1525   }
1526 }
1527 
1528 static void
1529 im_uim_set_client_window(GtkIMContext *ic, GdkWindow *w)
1530 {
1531   IMUIMContext *uic = IM_UIM_CONTEXT(ic);
1532 
1533   if (w) {
1534     uic->win = w;
1535   } else {
1536     uic->win = NULL;
1537   }
1538   update_client_widget(uic);
1539 }
1540 
1541 static UIMCandWinGtk *
1542 im_uim_create_cand_win_gtk()
1543 {
1544   UIMCandWinGtk *cwin = NULL;
1545   char *candwinprog; /* deprecated */
1546   char *style;
1547 
1548   candwinprog = uim_scm_symbol_value_str("uim-candwin-prog");
1549   style= uim_scm_symbol_value_str("candidate-window-style");
1550 
1551   if (candwinprog) {
1552     if (!strncmp(candwinprog, "uim-candwin-tbl", 15))
1553       cwin = UIM_CAND_WIN_GTK(uim_cand_win_tbl_gtk_new());
1554     else if (!strncmp(candwinprog, "uim-candwin-horizontal", 22))
1555       cwin = UIM_CAND_WIN_GTK(uim_cand_win_horizontal_gtk_new());
1556   } else {
1557     if (style) {
1558       if (!strcmp(style, "table"))
1559         cwin = UIM_CAND_WIN_GTK(uim_cand_win_tbl_gtk_new());
1560       else if (!strcmp(style, "horizontal"))
1561         cwin = UIM_CAND_WIN_GTK(uim_cand_win_horizontal_gtk_new());
1562     }
1563   }
1564   free(candwinprog);
1565   free(style);
1566 
1567   if (!cwin)
1568     cwin = UIM_CAND_WIN_GTK(uim_cand_win_vertical_gtk_new());
1569 
1570   return cwin;
1571 }
1572 
1573 static void
1574 im_uim_init(IMUIMContext *uic)
1575 {
1576   uic->win = NULL;
1577   uic->widget = NULL;
1578 #if IM_UIM_USE_TOPLEVEL
1579   init_event_key_rec(&uic->event_rec);
1580 #endif
1581   uic->caret_state_indicator = NULL;
1582   uic->pseg = NULL;
1583   uic->nr_psegs = 0;
1584   uic->prev_preedit_len = 0;
1585 
1586   uic->cwin = im_uim_create_cand_win_gtk();
1587 #if IM_UIM_USE_TOPLEVEL
1588   cwin_list = g_list_append(cwin_list, uic->cwin);
1589 #endif
1590   uic->cwin_is_active = FALSE;
1591   uic->preedit_window = NULL;
1592   uic->preedit_handler_id = 0;
1593 
1594   g_signal_connect(G_OBJECT(uic->cwin), "index-changed",
1595 		   G_CALLBACK(index_changed_cb), uic);
1596 }
1597 
1598 static void
1599 im_uim_dispose(GObject *obj)
1600 {
1601   IMUIMContext *uic = IM_UIM_CONTEXT(obj);
1602 
1603   if (uic->win) {
1604     im_uim_set_client_window(GTK_IM_CONTEXT(uic), NULL);
1605   }
1606 
1607   if (uic->cwin) {
1608 #if IM_UIM_USE_DELAY
1609     cand_delay_timer_remove(uic->cwin);
1610 #endif
1611     gtk_widget_destroy(GTK_WIDGET(uic->cwin));
1612 #if IM_UIM_USE_TOPLEVEL
1613     cwin_list = g_list_remove(cwin_list, uic->cwin);
1614 #endif
1615     uic->cwin = NULL;
1616   }
1617 
1618   if (uic->caret_state_indicator) {
1619     guint tag = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(uic->caret_state_indicator), "timeout-tag"));
1620     if (tag > 0)
1621       g_source_remove(tag);
1622     gtk_widget_destroy(uic->caret_state_indicator);
1623     uic->caret_state_indicator = NULL;
1624   }
1625 
1626   if (uic->preedit_handler_id) {
1627     g_signal_handler_disconnect(obj, uic->preedit_handler_id);
1628     uic->preedit_handler_id = 0;
1629   }
1630   if (uic->preedit_window) {
1631     gtk_widget_destroy(uic->preedit_window);
1632     uic->preedit_window = NULL;
1633   }
1634 
1635   if (uic->slave) {
1636     g_signal_handlers_disconnect_by_func(uic->slave, (gpointer)(uintptr_t)commit_cb, uic);
1637     g_object_unref(uic->slave);
1638     uic->slave = NULL;
1639   }
1640 
1641   parent_class->dispose(obj);
1642 }
1643 
1644 static void
1645 im_uim_finalize(GObject *obj)
1646 {
1647   IMUIMContext *uic = IM_UIM_CONTEXT(obj);
1648 
1649   uic->next->prev = uic->prev;
1650   uic->prev->next = uic->next;
1651 
1652   uim_release_context(uic->uc);
1653 
1654   parent_class->finalize(obj);
1655 
1656   if (uic == focused_context) {
1657     focused_context = NULL;
1658     disable_focused_context = TRUE;
1659   }
1660 #ifdef GDK_WINDOWING_X11
1661   free(uic->compose);
1662 #endif
1663 }
1664 
1665 static void
1666 im_uim_class_init(GtkIMContextClass *class)
1667 {
1668   GObjectClass *object_class = G_OBJECT_CLASS(class);
1669 
1670   parent_class = g_type_class_peek_parent(class);
1671   class->set_client_window = im_uim_set_client_window;
1672   class->filter_keypress = im_uim_filter_keypress;
1673   class->get_preedit_string = im_uim_get_preedit_string;
1674   class->set_cursor_location = im_uim_set_cursor_location;
1675   class->focus_in = im_uim_focus_in;
1676   class->focus_out = im_uim_focus_out;
1677   class->reset = im_uim_reset;
1678   class->set_use_preedit = im_uim_set_use_preedit;
1679 
1680   object_class->dispose = im_uim_dispose;
1681   object_class->finalize = im_uim_finalize;
1682 }
1683 
1684 static void
1685 im_uim_class_finalize(GtkIMContextClass *class)
1686 {
1687 }
1688 
1689 
1690 GtkIMContext *
1691 im_module_create(const gchar *context_id)
1692 {
1693   GObject *obj;
1694   IMUIMContext *uic;
1695   const char *im_name;
1696 
1697   g_return_val_if_fail(context_id, NULL);
1698   g_return_val_if_fail(!strcmp(context_id, "uim"), NULL);
1699 
1700   obj = g_object_new(type_im_uim, NULL);
1701   uic = IM_UIM_CONTEXT(obj);
1702 
1703   if (!uic)
1704     return NULL;
1705 
1706   im_name = uim_get_default_im_name(setlocale(LC_CTYPE, NULL));
1707   uic->uc = uim_create_context(uic, "UTF-8",
1708 			       NULL, im_name,
1709 			       uim_iconv,
1710 			       im_uim_commit_string);
1711   if (uic->uc == NULL) {
1712     parent_class->finalize(obj);
1713     return NULL;
1714   }
1715 
1716   check_helper_connection(uic->uc);
1717 
1718   uim_set_preedit_cb(uic->uc, clear_cb, pushback_cb, update_cb);
1719   uim_set_prop_list_update_cb(uic->uc, update_prop_list_cb);
1720   uim_set_candidate_selector_cb(uic->uc, cand_activate_cb, cand_select_cb,
1721 				cand_shift_page_cb, cand_deactivate_cb);
1722   uim_set_configuration_changed_cb(uic->uc, configuration_changed_cb);
1723   uim_set_im_switch_request_cb(uic->uc,
1724 			       switch_app_global_im_cb,
1725 			       switch_system_global_im_cb);
1726   uim_set_text_acquisition_cb(uic->uc, acquire_text_cb, delete_text_cb);
1727 #if IM_UIM_USE_DELAY
1728   uim_set_delay_candidate_selector_cb(uic->uc, cand_activate_with_delay_cb);
1729 #endif
1730 
1731   uim_prop_list_update(uic->uc);
1732 
1733 #ifdef GDK_WINDOWING_X11
1734   uic->compose = im_uim_compose_new();
1735 #endif
1736 
1737   /* slave exists for using gtk+'s table based input method */
1738   uic->slave = g_object_new(GTK_TYPE_IM_CONTEXT_SIMPLE, NULL);
1739   g_signal_connect(G_OBJECT(uic->slave), "commit",
1740 		   G_CALLBACK(commit_cb), uic);
1741 
1742   uic->caret_state_indicator = caret_state_indicator_new();
1743 
1744   uic->next = context_list.next;
1745   uic->prev = (IMUIMContext *)&context_list;
1746   context_list.next->prev = uic;
1747   context_list.next = uic;
1748   return GTK_IM_CONTEXT(uic);
1749 }
1750 
1751 void
1752 im_module_list(const GtkIMContextInfo ***contexts,
1753 	       int *n_contexts)
1754 {
1755   *contexts = &im_uim_info_list;
1756   *n_contexts = 1;
1757 }
1758 
1759 #if IM_UIM_USE_SNOOPER
1760 /* snooper is not recommended! */
1761 static gboolean
1762 key_snoop(GtkWidget *grab_widget, GdkEventKey *key, gpointer data)
1763 {
1764   if (focused_context) {
1765     int rv, kv, mod;
1766 
1767     im_uim_convert_keyevent(key, &kv, &mod);
1768 
1769     if (key->type == GDK_KEY_RELEASE)
1770       rv = uim_release_key(focused_context->uc, kv, mod);
1771     else
1772       rv = uim_press_key(focused_context->uc, kv, mod);
1773 
1774     if (rv)
1775       return FALSE;
1776     return TRUE;
1777   }
1778 
1779   return FALSE;
1780 }
1781 #endif
1782 
1783 #if IM_UIM_USE_TOPLEVEL
1784 static gboolean
1785 handle_key_on_toplevel(GtkWidget *widget, GdkEventKey *event, gpointer data)
1786 {
1787   IMUIMContext *uic = data;
1788   /* GtkWindow *window = GTK_WINDOW(widget); */
1789 
1790   if (focused_context == uic) {
1791     int rv, kv, mod;
1792 
1793     store_event_key(&uic->event_rec, event);
1794     im_uim_convert_keyevent(event, &kv, &mod);
1795 
1796     if (event->type == GDK_KEY_RELEASE)
1797       rv = uim_release_key(focused_context->uc, kv, mod);
1798     else
1799       rv = uim_press_key(focused_context->uc, kv, mod);
1800 
1801     if (rv)
1802       return FALSE;
1803 
1804 #if !GTK_CHECK_VERSION(2, 90, 0)
1805     /* FIXME: Can't compile with GSEAL_ENABLE */
1806     if (GTK_IS_TEXT_VIEW(uic->widget))
1807       GTK_TEXT_VIEW(uic->widget)->need_im_reset = TRUE;
1808     else if (GTK_IS_ENTRY(uic->widget)) {
1809       /* FIXME: Can't compile with GSEAL_ENABLE */
1810       if (gtk_editable_get_editable(GTK_EDITABLE(uic->widget)))
1811         GTK_ENTRY(uic->widget)->need_im_reset = TRUE;
1812     }
1813 #endif
1814     return TRUE;
1815   }
1816 
1817   return FALSE;
1818 }
1819 #endif
1820 
1821 void
1822 im_module_init(GTypeModule *type_module)
1823 {
1824   if (uim_counted_init() == -1)
1825     return;
1826 
1827   context_list.next = (IMUIMContext *)&context_list;
1828   context_list.prev = (IMUIMContext *)&context_list;
1829   type_im_uim = g_type_module_register_type(type_module, GTK_TYPE_IM_CONTEXT,
1830 					    "GtkIMContextUIM", &class_info, 0);
1831   uim_cand_win_gtk_register_type(type_module);
1832 
1833 #if IM_UIM_USE_SNOOPER
1834   /* Using snooper is not recommended! */
1835   snooper_id = gtk_key_snooper_install((GtkKeySnoopFunc)key_snoop, NULL);
1836   snooper_installed = TRUE;
1837 #endif
1838 
1839   im_uim_init_modifier_keys();
1840 #ifdef GDK_WINDOWING_X11
1841   im_uim_create_compose_tree();
1842 #endif
1843 }
1844 
1845 void
1846 im_module_exit(void)
1847 {
1848   if (im_uim_fd != -1)
1849     uim_helper_close_client_fd(im_uim_fd);
1850 
1851 #if IM_UIM_USE_SNOOPER
1852   gtk_key_snooper_remove(snooper_id);
1853 #endif
1854 #ifdef GDK_WINDOWING_X11
1855   im_uim_release_compose_tree();
1856 #endif
1857   uim_counted_quit();
1858 }
1859