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