Sophie

Sophie

distrib > Mageia > 9 > armv7hl > media > core-release-src > by-pkgid > 1c85592b61d40f1a362d33ca395a997b > files > 6

scim-1.4.18-7.mga9.src.rpm

From d35bf5d331d885e94914fea6eab9c56f20666c8d Mon Sep 17 00:00:00 2001
From: Derek Dai <daiderek@gmail.com>
Date: Sat, 20 Nov 2021 15:47:34 +0800
Subject: [PATCH 6/7] add GTK4 IM module

---
 configure.ac                                  |  76 +-
 extras/immodules/Makefile.am                  |   2 +-
 extras/immodules/client-gtk/gtk4/Makefile.am  |  52 +
 .../client-gtk/gtk4/im-scim-bridge-gtk.c      |  61 ++
 .../gtk4/scim-bridge-client-imcontext-gtk.c   | 997 ++++++++++++++++++
 .../gtk4/scim-bridge-client-imcontext-gtk.h   | 111 ++
 ...scim-bridge-client-key-event-utility-gtk.c | 132 +++
 ...scim-bridge-client-key-event-utility-gtk.h |  54 +
 extras/immodules/configure.ac                 |  33 +
 9 files changed, 1513 insertions(+), 5 deletions(-)
 create mode 100644 extras/immodules/client-gtk/gtk4/Makefile.am
 create mode 100644 extras/immodules/client-gtk/gtk4/im-scim-bridge-gtk.c
 create mode 100644 extras/immodules/client-gtk/gtk4/scim-bridge-client-imcontext-gtk.c
 create mode 100644 extras/immodules/client-gtk/gtk4/scim-bridge-client-imcontext-gtk.h
 create mode 100644 extras/immodules/client-gtk/gtk4/scim-bridge-client-key-event-utility-gtk.c
 create mode 100644 extras/immodules/client-gtk/gtk4/scim-bridge-client-key-event-utility-gtk.h

diff --git a/configure.ac b/configure.ac
index 752c371..2841926 100644
--- a/configure.ac
+++ b/configure.ac
@@ -242,6 +242,10 @@ PKG_CHECK_MODULES(GTK3, [gtk+-3.0 >= 3.0.0 pango >= 1.1.0 gdk-pixbuf-2.0 >= 2.4.
 			[SCIM_HAS_GTK3=yes],
 			[SCIM_HAS_GTK3=no])
 
+PKG_CHECK_MODULES(GTK4, [gtk4 >= 4.0.0 pango >= 1.48.0 gdk-pixbuf-2.0 >= 2.42.0],
+			[SCIM_HAS_GTK4=yes],
+			[SCIM_HAS_GTK4=no])
+
 SCIM_HAS_GTK2_2=no
 if test "$SCIM_HAS_GTK2" = "yes"; then
   if $PKG_CONFIG --exists "gtk+-2.0 >= 2.2" ; then
@@ -261,6 +265,14 @@ if test "$SCIM_HAS_GTK3" = "yes"; then
   AC_SUBST(GTK3_IM_MODULEDIR)
 fi
 
+if test "$SCIM_HAS_GTK4" = "yes"; then
+  GTK4_VERSION=4.0.0
+  GTK4_BINARY_VERSION=`$PKG_CONFIG --variable=gtk_binary_version gtk4`
+  GTK4_LIBDIR=`$PKG_CONFIG --variable=libdir gtk4`
+  GTK4_IM_MODULEDIR=$GTK4_LIBDIR/gtk-4.0/$GTK4_BINARY_VERSION/immodules
+  AC_SUBST(GTK4_IM_MODULEDIR)
+fi
+
 # Check if we have gthread
 PKG_CHECK_MODULES(GTHREAD2,[gthread-2.0 >= 2.0.0],
 			[SCIM_HAS_GTHREAD2=yes],
@@ -344,6 +356,11 @@ AC_ARG_WITH([gtk3-im-module-dir],
 	    [Select GTK3 immodule dir])],
 	[GTK3_IM_MODULEDIR=$with_gtk3_im_module_dir])
 
+AC_ARG_WITH([gtk4-im-module-dir],
+	[AS_HELP_STRING([--with-gtk4-im-module-dir=dir], 
+	    [Select GTK4 immodule dir])],
+	[GTK4_IM_MODULEDIR=$with_gtk4_im_module_dir])
+
 AC_ARG_WITH([qt3-moc],
 	[AS_HELP_STRING([--with-qt3-moc=file],
 		[Select QT3 moc program (default to QT3_PREFIX/bin/moc)])],
@@ -483,6 +500,12 @@ AC_ARG_ENABLE([gtk3-immodule],
 	[],
 	[enable_gtk3_immodule=yes])
 
+AC_ARG_ENABLE([gtk4-immodule],
+	[AS_HELP_STRING([--disable-gtk4-immodule],
+		[Do not build GTK4 IMModule])],
+	[],
+	[enable_gtk4_immodule=yes])
+
 AC_ARG_ENABLE([qt3-immodule],
 	[AS_HELP_STRING([--disable-qt3-immodule],
 		[Do not build QT3 IMModule])],
@@ -625,6 +648,15 @@ else
   enable_gtk3_immodule=no
 fi
 
+if test "$enable_gtk4_immodule" = "yes" -a "$SCIM_HAS_GTK4" = "yes"; then
+  SCIM_BUILD_GTK4_IMMODULE=1
+  SCIM_BUILD_IMMODULE=1
+  enable_immodule=yes
+else
+  SCIM_BUILD_GTK4_IMMODULE=0
+  enable_gtk4_immodule=no
+fi
+
 if test "$enable_im_agent" = "yes"; then
   SCIM_BUILD_IM_AGENT=1
 else
@@ -657,6 +689,17 @@ else
   enable_gtk3_immodule=no
 fi
 
+if test "$enable_gtk4_immodule" = "yes" -a "$SCIM_HAS_GTK4" = "yes"; then
+  SCIM_BUILD_GTK4_IMMODULE=1
+  SCIM_BUILD_IMMODULE=1
+  enable_immodule=yes
+  SCIM_BUILD_IM_AGENT=1
+  enable_im_agent=yes
+else
+  SCIM_BUILD_GTK4_IMMODULE=0
+  enable_gtk4_immodule=no
+fi
+
 if test "$enable_qt3_immodule" = "yes" -a "$SCIM_HAS_QT3" = "yes"; then
   SCIM_BUILD_QT3_IMMODULE=1
   SCIM_BUILD_IMMODULE=1
@@ -692,19 +735,37 @@ else
   enable_clutter_immodule=no
 fi
 
-if test "$SCIM_HAS_GTK3" = "yes" -a "$SCIM_HAS_GTK2" = "yes"; then
+if test "$SCIM_HAS_GTK4" = "yes" -a "$SCIM_HAS_GTK3" = "yes" -a "$SCIM_HAS_GTK2" = "yes"; then
   if test "$WANT_GTK_VERSION" = "2"; then
 	USE_GTK_VERSION="2"
-  else
+  elif test "$WANT_GTK_VERSION" = "3"; then
 	USE_GTK_VERSION="3"
+  else
+	USE_GTK_VERSION="4"
   fi
+elif test "$SCIM_HAS_GTK4" = "yes"; then
+  USE_GTK_VERSION="4"
 elif test "$SCIM_HAS_GTK3" = "yes"; then
   USE_GTK_VERSION="3"
 elif test "$SCIM_HAS_GTK2" = "yes"; then
   USE_GTK_VERSION="2"
 fi
 
-if test "$USE_GTK_VERSION" = "3"; then
+if test "$USE_GTK_VERSION" = "4"; then
+  SCIM_HAS_GTK=yes
+  GTK_LIBDIR=$GTK4_LIBDIR
+  GTK_VERSION=$GTK4_VERSION
+  GTK_MAJOR_VERSION=4
+  GTK_BINARY_VERSION=$GTK4_BINARY_VERSION
+  GTK_CFLAGS=$GTK4_CFLAGS
+  GTK_LIBS=$GTK4_LIBS
+  AC_SUBST(GTK_LIBDIR)
+  AC_SUBST(GTK_VERSION)
+  AC_SUBST(GTK_MAJOR_VERSION)
+  AC_SUBST(GTK_BINARY_VERSION)
+  AC_SUBST(GTK_CFLAGS)
+  AC_SUBST(GTK_LIBS)
+elif test "$USE_GTK_VERSION" = "3"; then
   SCIM_HAS_GTK=yes
   GTK_LIBDIR=$GTK3_LIBDIR
   GTK_VERSION=$GTK3_VERSION
@@ -753,7 +814,7 @@ else
 fi
 
 enable_tray_icon=no
-if test \( "$SCIM_HAS_GTK3"="yes" -o "$SCIM_HAS_GTK2_2" = "yes" \) -a "$no_x" != "yes"; then
+if test \( "$SCIM_HAS_GTK4"="yes" -o "$SCIM_HAS_GTK3"="yes" -o "$SCIM_HAS_GTK2_2" = "yes" \) -a "$no_x" != "yes"; then
   enable_tray_icon=yes
   AC_DEFINE(ENABLE_TRAY_ICON,1,[Enable the TrayIcon code.])
 fi
@@ -818,6 +879,9 @@ AM_CONDITIONAL(SCIM_BUILD_GTK2_IMMODULE,
 AM_CONDITIONAL(SCIM_BUILD_GTK3_IMMODULE,
         [test "$enable_gtk3_immodule" = "yes"])
 
+AM_CONDITIONAL(SCIM_BUILD_GTK4_IMMODULE,
+        [test "$enable_gtk4_immodule" = "yes"])
+
 AM_CONDITIONAL(SCIM_BUILD_QT3_IMMODULE,
         [test "$enable_qt3_immodule" = "yes"])
 
@@ -868,6 +932,7 @@ AC_SUBST(SCIM_BUILD_X11_UTILS)
 AC_SUBST(SCIM_BUILD_IM_AGENT)
 AC_SUBST(SCIM_BUILD_GTK2_IMMODULE)
 AC_SUBST(SCIM_BUILD_GTK3_IMMODULE)
+AC_SUBST(SCIM_BUILD_GTK4_IMMODULE)
 AC_SUBST(SCIM_BUILD_QT3_IMMODULE)
 AC_SUBST(SCIM_BUILD_QT4_IMMODULE)
 AC_SUBST(SCIM_BUILD_CLUTTER_IMMODULE)
@@ -930,6 +995,7 @@ AC_CONFIG_FILES([Makefile
 		 extras/immodules/client-common/Makefile
 		 extras/immodules/client-gtk/gtk2/Makefile
 		 extras/immodules/client-gtk/gtk3/Makefile
+		 extras/immodules/client-gtk/gtk4/Makefile
 		 extras/immodules/client-clutter/Makefile
 		 extras/immodules/doc/Makefile
 		 tests/Makefile
@@ -976,6 +1042,7 @@ Module options:
 
   GTK2 IMModule dir        $GTK2_IM_MODULEDIR
   GTK3 IMModule dir        $GTK3_IM_MODULEDIR
+  GTK4 IMModule dir        $GTK4_IM_MODULEDIR
   QT3 IMModule dir         $QT3_IM_MODULEDIR
   QT4 IMModule dir         $QT4_IM_MODULEDIR
   Clutter IMModule dir     $CLUTTER_IM_MODULEDIR
@@ -989,6 +1056,7 @@ Module options:
   Scim IM Agent            $enable_im_agent
   GTK2 IMModule            $enable_gtk2_immodule
   GTK3 IMModule            $enable_gtk3_immodule
+  GTK4 IMModule            $enable_gtk4_immodule
   QT3 IMModule             $enable_qt3_immodule
   QT4 IMModule             $enable_qt4_immodule
   CLUTTER IMModule         $enable_clutter_immodule
diff --git a/extras/immodules/Makefile.am b/extras/immodules/Makefile.am
index 56b1fee..8fceaa8 100644
--- a/extras/immodules/Makefile.am
+++ b/extras/immodules/Makefile.am
@@ -37,4 +37,4 @@
 MAINTAINERCLEANFILES    = Makefile.in
 CLEANFILES      = *.bak
 
-SUBDIRS = common client-common client-gtk/gtk2 client-gtk/gtk3 client-clutter agent doc
+SUBDIRS = common client-common client-gtk/gtk2 client-gtk/gtk3 client-gtk/gtk4 client-clutter agent doc
diff --git a/extras/immodules/client-gtk/gtk4/Makefile.am b/extras/immodules/client-gtk/gtk4/Makefile.am
new file mode 100644
index 0000000..ac20b37
--- /dev/null
+++ b/extras/immodules/client-gtk/gtk4/Makefile.am
@@ -0,0 +1,52 @@
+## Makefile.am -- Process this file with automake to produce Makefile.in
+##
+## Copyright (C) 2006 Ryo Dairiki
+##
+##
+## This library is free software; you can redistribute it and/or
+## modify it under the terms of the GNU Lesser General Public
+## License as published by the Free Software Foundation and 
+## appearing in the file LICENSE.LGPL included in the package of this file.
+## You can also redistribute it and/or modify it under the terms of 
+## the GNU General Public License as published by the Free Software Foundation and 
+## appearing in the file LICENSE.GPL included in the package of this file.
+##
+## This library is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+if SCIM_BUILD_GTK4_IMMODULE
+
+AM_CPPFLAGS	= -I$(top_builddir) \
+	    	  -I$(top_srcdir) \
+			  -I$(top_srcdir)/extras/immodules/common \
+			  -I$(top_srcdir)/extras/immodules/client-common
+
+noinst_HEADERS = ../scim-bridge-client-gtk.h \
+				 ../scim-bridge-client-imcontext-gtk.h \
+				 ../scim-bridge-client-key-event-utility-gtk.h
+
+moduledir = @GTK4_IM_MODULEDIR@
+
+module_LTLIBRARIES = libim-scim.la
+
+libim_scim_la_SOURCES = im-scim-bridge-gtk.c \
+						../scim-bridge-client-gtk.c \
+						scim-bridge-client-imcontext-gtk.c \
+						scim-bridge-client-key-event-utility-gtk.c
+
+libim_scim_la_CXXFLAGS=@GTK4_CFLAGS@
+libim_scim_la_CFLAGS  =@GTK4_CFLAGS@
+
+libim_scim_la_LDFLAGS = -rpath $(moduledir) \
+		     -avoid-version -no-undefined \
+		     -module
+
+libim_scim_la_LIBADD  = $(top_builddir)/extras/immodules/common/libscimbridgecommon.la \
+		     $(top_builddir)/extras/immodules/client-common/libscimbridgeclientcommon.la \
+		     @GTK4_LIBS@ \
+		     @X_LIBS@
+
+endif
+
+MAINTAINERCLEANFILES 	= Makefile.in
diff --git a/extras/immodules/client-gtk/gtk4/im-scim-bridge-gtk.c b/extras/immodules/client-gtk/gtk4/im-scim-bridge-gtk.c
new file mode 100644
index 0000000..93df5a4
--- /dev/null
+++ b/extras/immodules/client-gtk/gtk4/im-scim-bridge-gtk.c
@@ -0,0 +1,61 @@
+/*
+ * SCIM Bridge
+ *
+ * Copyright (c) 2006 Ryo Dairiki <ryo-dairiki@users.sourceforge.net>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation and 
+ * appearing in the file LICENSE.LGPL included in the package of this file.
+ * You can also redistribute it and/or modify it under the terms of 
+ * the GNU General Public License as published by the Free Software Foundation and 
+ * appearing in the file LICENSE.GPL included in the package of this file.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <gtk/gtkimmodule.h>
+
+#include "scim-bridge.h"
+#include "scim-bridge-client.h"
+#include "../scim-bridge-client-gtk.h"
+#include "scim-bridge-client-imcontext-gtk.h"
+
+/* Implementations */
+void g_io_im_scim_load (GIOModule *io_module)
+{
+    static boolean initialized = FALSE;
+
+    if (initialized) {
+        return;
+    }
+
+    scim_bridge_client_imcontext_register_type (io_module);
+    GIOExtension *ext = g_io_extension_point_implement (
+        GTK_IM_MODULE_EXTENSION_POINT_NAME,
+        GTK_TYPE_SCIM_CLIENT_IMCONTEXT,
+        "scim",
+        100);
+    scim_bridge_client_gtk_initialize ();
+
+    atexit (scim_bridge_client_gtk_finalize);
+
+    g_type_module_use(G_TYPE_MODULE(io_module));
+
+    initialized = TRUE;
+}
+
+
+void g_io_im_scim_unload (GIOModule *io_module)
+{
+    g_type_module_unuse(G_TYPE_MODULE(io_module));
+    scim_bridge_client_gtk_finalize ();
+}
diff --git a/extras/immodules/client-gtk/gtk4/scim-bridge-client-imcontext-gtk.c b/extras/immodules/client-gtk/gtk4/scim-bridge-client-imcontext-gtk.c
new file mode 100644
index 0000000..79c670d
--- /dev/null
+++ b/extras/immodules/client-gtk/gtk4/scim-bridge-client-imcontext-gtk.c
@@ -0,0 +1,997 @@
+/*
+ * SCIM Bridge
+ *
+ * Copyright (c) 2006 Ryo Dairiki <ryo-dairiki@users.sourceforge.net>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation and 
+ * appearing in the file LICENSE.LGPL included in the package of this file.
+ * You can also redistribute it and/or modify it under the terms of 
+ * the GNU General Public License as published by the Free Software Foundation and 
+ * appearing in the file LICENSE.GPL included in the package of this file.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <gtk/gtk.h>
+
+#include <gdk/gdk.h>
+
+#ifdef GDK_WINDOWING_X11 
+#include <gdk/x11/gdkx.h>
+#include <X11/Xlib.h>
+#endif
+
+#include "scim-bridge-attribute.h"
+#include "scim-bridge-client.h"
+#include "scim-bridge-client-imcontext-gtk.h"
+#include "scim-bridge-client-key-event-utility-gtk.h"
+#include "scim-bridge-imcontext.h"
+#include "scim-bridge-messenger.h"
+#include "scim-bridge-output.h"
+#include "scim-bridge-string.h"
+
+/* Typedef */
+struct _ScimBridgeClientIMContext
+{
+    GtkIMContext parent;
+    GtkIMContext *slave;
+    boolean slave_preedit;
+
+    scim_bridge_imcontext_id_t id;
+
+    char *preedit_string;
+    size_t preedit_string_capacity;
+    
+    PangoAttrList *preedit_attributes;
+
+    unsigned int preedit_cursor_position;
+    boolean preedit_cursor_flicking;
+    
+    boolean preedit_shown;
+    boolean preedit_started;
+
+    char *commit_string;
+    size_t commit_string_capacity;
+    
+    boolean enabled;
+
+    GtkWidget *client_widget;
+
+    int cursor_x;
+    int cursor_y;
+    int window_x;
+    int window_y;
+};
+
+
+typedef struct {
+    guint16 red;
+    guint16 green;
+    guint16 blue;
+} Color;
+
+
+/* Private variables */
+static Color preedit_normal_background;
+static Color preedit_normal_foreground;
+static Color preedit_active_background;
+static Color preedit_active_foreground;
+
+static GType class_type = 0;
+static GObjectClass *root_klass = NULL;
+
+static ScimBridgeClientIMContext *focused_imcontext = NULL;
+static GtkWidget *focused_widget = NULL;
+
+/* Class functions */
+static void scim_bridge_client_imcontext_class_initialize (ScimBridgeClientIMContextClass *klass, gpointer *klass_data);
+
+static void scim_bridge_client_imcontext_initialize (ScimBridgeClientIMContext *context, ScimBridgeClientIMContextClass *klass);
+static void scim_bridge_client_imcontext_finalize (GObject *object);
+
+static gboolean scim_bridge_client_imcontext_filter_key_event (GtkIMContext *context, GdkEvent *event);
+static void scim_bridge_client_imcontext_reset (GtkIMContext *context);
+static void scim_bridge_client_imcontext_get_preedit_string (GtkIMContext *context, gchar **str, PangoAttrList **attrs, gint *cursor_pos);
+static void scim_bridge_client_imcontext_set_preedit_enabled (GtkIMContext *context, gboolean enabled);
+
+static void scim_bridge_client_imcontext_set_client_widget (GtkIMContext *context, GtkWidget *window);
+static void scim_bridge_client_imcontext_focus_in (GtkIMContext *context);
+static void scim_bridge_client_imcontext_focus_out (GtkIMContext *context);
+static void scim_bridge_client_imcontext_set_cursor_location (GtkIMContext *context, GdkRectangle *area);
+
+/* slave callbacks */
+static void gtk_im_slave_commit_cb (GtkIMContext *context, const char *str, ScimBridgeClientIMContext *imcontext);
+static void gtk_im_slave_preedit_changed_cb (GtkIMContext *context, ScimBridgeClientIMContext *imcontext);
+static void gtk_im_slave_preedit_start_cb (GtkIMContext *context, ScimBridgeClientIMContext *imcontext);
+static void gtk_im_slave_preedit_end_cb (GtkIMContext *context, ScimBridgeClientIMContext *imcontext);
+
+static retval_t filter_key_event (ScimBridgeClientIMContext *imcontext, GdkEvent *event, boolean *consumed)
+{
+    scim_bridge_pdebugln (5, "filter_key_event ()");
+    
+    if (focused_imcontext != imcontext)
+        scim_bridge_client_imcontext_focus_in (GTK_IM_CONTEXT (imcontext));
+    //focused_widget = gtk_get_event_widget (event);
+    focused_widget = imcontext->client_widget;
+    
+    if (scim_bridge_client_is_messenger_opened ()) {
+        ScimBridgeKeyEvent *bridge_key_event = scim_bridge_alloc_key_event ();
+        scim_bridge_key_event_gdk_to_bridge (bridge_key_event, imcontext->client_widget, event);
+
+        *consumed = FALSE;
+        const retval_t retval_error = scim_bridge_client_handle_key_event (imcontext, bridge_key_event, consumed);
+        scim_bridge_free_key_event (bridge_key_event);
+
+        if (retval_error) {
+            scim_bridge_perrorln ("An IOException at filter_key_event ()");
+        } else {
+            return RETVAL_SUCCEEDED;
+        }
+    }
+
+    return RETVAL_FAILED;
+}
+
+
+static retval_t set_cursor_location (ScimBridgeClientIMContext *imcontext, int window_x, int window_y, int cursor_x, int cursor_y)
+{
+    scim_bridge_pdebugln (5, "set_cursor_location ()");
+
+    if (imcontext->window_x == window_x && imcontext->window_y == window_y && imcontext->cursor_x == cursor_x && imcontext->cursor_y == cursor_y) {
+        return RETVAL_SUCCEEDED;
+    } else {
+        imcontext->cursor_x = cursor_x;
+        imcontext->cursor_y = cursor_y;
+        imcontext->window_x = window_x;
+        imcontext->window_y = window_y;
+
+        scim_bridge_pdebugln (3, "The cursor location is changed: x = %d + %d\ty = %d + %d", imcontext->window_x, imcontext->cursor_x, imcontext->window_y, imcontext->cursor_y);
+
+        if (scim_bridge_client_is_messenger_opened ()) {
+            if (scim_bridge_client_set_cursor_location (imcontext, imcontext->window_x + imcontext->cursor_x, imcontext->window_y + imcontext->cursor_y)) {
+                scim_bridge_perrorln ("An IOException occurred at set_cursor_location ()");
+                return RETVAL_FAILED;
+            } else {
+                return RETVAL_SUCCEEDED;
+            }
+        }
+    }
+
+    return RETVAL_FAILED;
+}
+
+
+static void widget_get_origin(GtkWidget *widget, int *x, int *y)
+{
+    double widget_x, widget_y;
+    GtkRoot *root = gtk_widget_get_root(widget);
+    gtk_widget_translate_coordinates(widget, GTK_WIDGET(root), 0, 0, &widget_x, &widget_y);
+
+    GdkSurface *surface = gtk_native_get_surface (GTK_NATIVE(root));
+    Window wiget_win = gdk_x11_surface_get_xid (surface);
+    XWindowAttributes attr;
+    Display *display = gdk_x11_display_get_xdisplay (gdk_surface_get_display (surface));
+    XGetWindowAttributes(display, wiget_win, &attr);
+
+    Window child_win = None;
+    XTranslateCoordinates(display, wiget_win, attr.root, widget_x, widget_y, x, y, &child_win);
+}
+
+
+static gboolean key_snooper (GtkWidget *widget, GdkEvent *event, gpointer data)
+{
+    scim_bridge_pdebugln (7, "key_snooper ()");
+
+    GdkEventType type = gdk_event_get_event_type(event);
+    if (focused_imcontext && scim_bridge_client_is_messenger_opened () &&
+        (type == GDK_KEY_PRESS || type == GDK_KEY_RELEASE)) { //&&
+        //!(event->send_event & SEND_EVENT_MASK)) {
+        if (focused_imcontext->client_widget) {
+            // TODO get cliet_wiget's origin
+            int new_widget_x = 0;
+            int new_widget_y = 0;
+            widget_get_origin (focused_imcontext->client_widget, &new_widget_x, &new_widget_y);
+
+            if (focused_imcontext->window_x != new_widget_x || focused_imcontext->window_y != new_widget_y) {
+                scim_bridge_pdebugln (1,
+                    "The cursor location is changed: x = %d + %d\ty = %d + %d",
+                    new_widget_x, focused_imcontext->cursor_x, new_widget_y, focused_imcontext->cursor_y);
+
+                if (set_cursor_location (focused_imcontext, new_widget_x, new_widget_y, focused_imcontext->cursor_x, focused_imcontext->cursor_y)) {
+                    scim_bridge_perrorln ("An IOException at key_snooper ()");
+                    return FALSE;
+                }
+            }
+        }
+
+        boolean consumed = FALSE;
+        if (filter_key_event (focused_imcontext, event, &consumed)) {
+            scim_bridge_perrorln ("An IOException at key_snooper ()");
+            return FALSE;
+        } else {
+            if (consumed) {
+                g_signal_emit_by_name (focused_imcontext, "preedit-changed");
+                return TRUE;
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+static boolean is_precise_cursor_enabled ()
+{
+    static boolean first_time = TRUE;
+    static boolean precise_cursor_enabled = FALSE;
+
+    if (first_time) {
+        char *env_precise_cursor_enabled = getenv ("SCIM_BRIDGE_PRECISE_CURSOR_ENABLED");
+        if (env_precise_cursor_enabled != NULL) scim_bridge_string_to_boolean (&precise_cursor_enabled, env_precise_cursor_enabled);
+        first_time = FALSE;
+    }
+
+    return precise_cursor_enabled;
+}
+
+/* Bindings */
+void scim_bridge_client_imcontext_set_id (ScimBridgeClientIMContext *imcontext, scim_bridge_imcontext_id_t new_id)
+{
+    imcontext->id = new_id;
+}
+
+
+scim_bridge_imcontext_id_t scim_bridge_client_imcontext_get_id (const ScimBridgeClientIMContext *imcontext)
+{
+    return imcontext->id;
+}
+
+
+void scim_bridge_client_imcontext_set_preedit_string (ScimBridgeClientIMContext *imcontext, const char *preedit_string)
+{
+    if (imcontext->preedit_string != NULL && preedit_string != NULL && !strcmp (imcontext->preedit_string, preedit_string))
+        return;
+
+    size_t preedit_string_length;
+    if (preedit_string != NULL) {
+        preedit_string_length = strlen (preedit_string);
+    } else {
+        preedit_string_length = 0;
+    }
+    if (imcontext->preedit_string_capacity <= preedit_string_length) {
+        imcontext->preedit_string_capacity = preedit_string_length;
+        free (imcontext->preedit_string);
+        imcontext->preedit_string = malloc (sizeof (char) * (imcontext->preedit_string_capacity + 1));
+    }
+    if (preedit_string_length > 0) {
+        strcpy (imcontext->preedit_string, preedit_string);
+    } else {
+        imcontext->preedit_string[0] = '\0';
+    }
+}
+
+
+void scim_bridge_client_imcontext_set_preedit_shown (ScimBridgeClientIMContext *imcontext, boolean preedit_shown)
+{
+    imcontext->preedit_shown = preedit_shown;
+    if (!preedit_shown) {
+        free (imcontext->preedit_string);
+        imcontext->preedit_string = malloc (sizeof (char));
+        imcontext->preedit_string[0] = '\0';
+        imcontext->preedit_string_capacity = 0;
+        imcontext->preedit_cursor_position = 0;
+        if (imcontext->preedit_attributes != NULL) {
+            pango_attr_list_unref (imcontext->preedit_attributes);
+            imcontext->preedit_attributes = NULL;
+        }
+    }
+}
+
+void scim_bridge_client_imcontext_set_preedit_cursor_position (ScimBridgeClientIMContext *imcontext, int cursor_position)
+{
+    imcontext->preedit_cursor_position = cursor_position;
+}
+
+
+void scim_bridge_client_imcontext_set_preedit_attributes (ScimBridgeClientIMContext *imcontext, ScimBridgeAttribute** const preedit_attributes, int attribute_count)
+{   
+    if (imcontext->preedit_attributes != NULL)
+        pango_attr_list_unref (imcontext->preedit_attributes);
+    
+    imcontext->preedit_attributes = pango_attr_list_new ();
+    
+    int preedit_string_length = 0;
+    int preedit_wstring_length = 0;
+    if (imcontext->preedit_string != NULL) {
+        preedit_string_length = strlen (imcontext->preedit_string);
+        preedit_wstring_length = g_utf8_strlen (imcontext->preedit_string, -1);
+    }
+    
+    boolean *has_attribute = alloca (sizeof (boolean) * preedit_string_length);
+    int i;
+    for (i = 0; i < preedit_string_length; ++i) {
+        has_attribute[i] = FALSE;
+    }
+
+    for (i = 0; i < attribute_count; ++i) {
+        const ScimBridgeAttribute *attr = preedit_attributes[i];
+        const int begin_pos = scim_bridge_attribute_get_begin (attr);
+        const int end_pos = scim_bridge_attribute_get_end (attr);
+
+        if (begin_pos <= end_pos && 0 <= begin_pos && end_pos <= preedit_wstring_length) {
+            const int start_index = g_utf8_offset_to_pointer (imcontext->preedit_string, begin_pos) - imcontext->preedit_string;
+            const int end_index = g_utf8_offset_to_pointer (imcontext->preedit_string, end_pos) - imcontext->preedit_string;
+
+            const scim_bridge_attribute_type_t attr_type = scim_bridge_attribute_get_type (attr);
+            const scim_bridge_attribute_value_t attr_value = scim_bridge_attribute_get_value (attr);
+
+            boolean valid_attribute = FALSE;
+            if (attr_type == ATTRIBUTE_DECORATE) {
+                if (attr_value == SCIM_BRIDGE_ATTRIBUTE_DECORATE_UNDERLINE) {
+                    valid_attribute = TRUE;
+
+                    PangoAttribute *pango_attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
+                    pango_attr->start_index = start_index;
+                    pango_attr->end_index = end_index;
+                    pango_attr_list_insert (imcontext->preedit_attributes, pango_attr);
+                } else if (attr_value == SCIM_BRIDGE_ATTRIBUTE_DECORATE_REVERSE) {
+                    valid_attribute = TRUE;
+
+                    PangoAttribute *pango_attr0 = pango_attr_foreground_new (preedit_normal_background.red, preedit_normal_background.green, preedit_normal_background.blue);
+                    pango_attr0->start_index = start_index;
+                    pango_attr0->end_index = end_index;
+                    pango_attr_list_insert (imcontext->preedit_attributes, pango_attr0);
+
+                    PangoAttribute *pango_attr1 = pango_attr_background_new (preedit_normal_foreground.red, preedit_normal_foreground.green, preedit_normal_foreground.blue);
+                    pango_attr1->start_index = start_index;
+                    pango_attr1->end_index = end_index;
+                    pango_attr_list_insert (imcontext->preedit_attributes, pango_attr1);
+                } else if (attr_value == SCIM_BRIDGE_ATTRIBUTE_DECORATE_HIGHLIGHT) {
+                    valid_attribute = TRUE;
+
+                    PangoAttribute *pango_attr0 = pango_attr_foreground_new (preedit_active_foreground.red, preedit_active_foreground.green, preedit_active_foreground.blue);
+                    pango_attr0->start_index = start_index;
+                    pango_attr0->end_index = end_index;
+                    pango_attr_list_insert (imcontext->preedit_attributes, pango_attr0);
+
+                    PangoAttribute *pango_attr1 = pango_attr_background_new (preedit_active_background.red, preedit_active_background.green, preedit_active_background.blue);
+                    pango_attr1->start_index = start_index;
+                    pango_attr1->end_index = end_index;
+                    pango_attr_list_insert (imcontext->preedit_attributes, pango_attr1);
+                } else {
+                    scim_bridge_perrorln ("Unknown preedit decoration!");
+                }
+            } else if (attr_type == ATTRIBUTE_FOREGROUND) {
+                valid_attribute = TRUE;
+
+                const unsigned int red = scim_bridge_attribute_get_red (attr) * 256;
+                const unsigned int green = scim_bridge_attribute_get_green (attr) * 256;
+                const unsigned int blue = scim_bridge_attribute_get_blue (attr) * 256;
+
+                PangoAttribute *pango_attr = pango_attr_foreground_new (red, green, blue);
+                pango_attr->start_index = start_index;
+                pango_attr->end_index = end_index;
+                pango_attr_list_insert (imcontext->preedit_attributes, pango_attr);
+            } else if (attr_type == ATTRIBUTE_BACKGROUND) {
+                valid_attribute = TRUE;
+
+                const unsigned int red = scim_bridge_attribute_get_red (attr) * 256;
+                const unsigned int green = scim_bridge_attribute_get_green (attr) * 256;
+                const unsigned int blue = scim_bridge_attribute_get_blue (attr) * 256;
+
+                PangoAttribute *pango_attr = pango_attr_background_new (red, green, blue);
+                pango_attr->start_index = start_index;
+                pango_attr->end_index = end_index;
+                pango_attr_list_insert (imcontext->preedit_attributes, pango_attr);
+            }
+
+            if (valid_attribute) {
+                int j;
+                for (j = start_index; j < end_index; ++j) {
+                    has_attribute[j] = TRUE;
+                }
+            }
+        }
+
+    }
+
+    // Add underlines for the all characters without attributes.
+    for (i = 0; i < preedit_string_length; ++i) {
+        if (has_attribute[i] == FALSE) {
+            PangoAttribute *pango_attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
+            pango_attr->start_index = i;
+            for (; i < preedit_string_length && has_attribute[i] == FALSE; ++i);
+            pango_attr->end_index = i;
+            pango_attr_list_insert (imcontext->preedit_attributes, pango_attr);
+        }
+    }
+}
+
+
+void scim_bridge_client_imcontext_update_preedit (ScimBridgeClientIMContext *imcontext)
+{
+    if (imcontext->preedit_shown && !imcontext->preedit_started) {
+        g_signal_emit_by_name ((ScimBridgeClientIMContext*) imcontext, "preedit-start");
+        imcontext->preedit_started = TRUE;
+    }
+    
+    if (is_precise_cursor_enabled ()) {
+        const size_t old_cursor_position = imcontext->preedit_cursor_position;
+        imcontext->preedit_cursor_position = 0;
+        if (imcontext->preedit_string != NULL)
+            imcontext->preedit_cursor_position = g_utf8_strlen (imcontext->preedit_string, -1);
+        
+        imcontext->preedit_cursor_flicking = TRUE;
+        g_signal_emit_by_name ((ScimBridgeClientIMContext*) imcontext, "preedit-changed");
+        
+        imcontext->preedit_cursor_position = old_cursor_position;
+        imcontext->preedit_cursor_flicking = FALSE;
+    }
+    
+    g_signal_emit_by_name ((ScimBridgeClientIMContext*) imcontext, "preedit-changed");
+    
+    if (!imcontext->preedit_shown && imcontext->preedit_started) {
+        g_signal_emit_by_name ((ScimBridgeClientIMContext*) imcontext, "preedit-end");
+        imcontext->preedit_started = FALSE;
+    }
+}
+
+
+void scim_bridge_client_imcontext_set_commit_string (ScimBridgeClientIMContext *imcontext, const char *commit_string)
+{
+    size_t commit_string_length;
+    if (commit_string != NULL) {
+        commit_string_length = strlen (commit_string);
+    } else {
+        commit_string_length = 0;
+    }
+    
+    if (imcontext->commit_string_capacity <= commit_string_length) {
+        imcontext->commit_string_capacity = commit_string_length;
+        free (imcontext->commit_string);
+        imcontext->commit_string = malloc (sizeof (char) * (imcontext->commit_string_capacity + 1));
+    }
+    if (commit_string_length > 0) {
+        strcpy (imcontext->commit_string, commit_string);
+    } else {
+        imcontext->commit_string[0] = '\0';
+    }
+}
+
+
+void scim_bridge_client_imcontext_commit (ScimBridgeClientIMContext *imcontext)
+{
+    g_signal_emit_by_name ((ScimBridgeClientIMContext*) imcontext, "commit", imcontext->commit_string);
+}
+
+
+void scim_bridge_client_imcontext_beep (ScimBridgeClientIMContext *imcontext)
+{
+    gtk_widget_error_bell(imcontext->client_widget);
+}
+
+
+boolean scim_bridge_client_imcontext_get_surrounding_text (ScimBridgeClientIMContext *imcontext, int before_max, int after_max, char **string, int *cursor_position)
+{
+    char *str;
+    int cur_pos_in_utf8, an_pos_in_utf8;
+
+    if (gtk_im_context_get_surrounding_with_selection (GTK_IM_CONTEXT (imcontext),
+                                                       &str,
+                                                       &cur_pos_in_utf8,
+                                                       &an_pos_in_utf8)) {
+        const size_t fetch_wstr_length = g_utf8_strlen (str, -1);
+        const size_t after_wstr_length = g_utf8_strlen (str + cur_pos_in_utf8, an_pos_in_utf8);
+        const size_t before_wstr_length = fetch_wstr_length - after_wstr_length;
+
+        size_t before_copy_wstr_length;
+        size_t after_copy_wstr_length;
+        if (after_wstr_length > after_max) {
+            after_copy_wstr_length = after_max;
+        } else {
+            after_copy_wstr_length = after_wstr_length;
+        }
+        if (before_wstr_length > before_max) {
+            before_copy_wstr_length = before_max;
+        } else {
+            before_copy_wstr_length = before_wstr_length;
+        }
+
+        const size_t begin_wstr_index = before_wstr_length - before_copy_wstr_length;
+        const size_t end_wstr_index = fetch_wstr_length - (after_wstr_length - after_copy_wstr_length);
+
+        char* begin_str_ptr = g_utf8_offset_to_pointer (str, begin_wstr_index);
+        char* end_str_ptr = g_utf8_offset_to_pointer (str, end_wstr_index);
+        size_t str_length = end_str_ptr - begin_str_ptr;
+
+        *string = malloc (sizeof (char) * (str_length + 1));
+        strncpy (*string, begin_str_ptr, str_length);
+        (*string)[str_length] = '\0';
+        *cursor_position = before_copy_wstr_length;
+
+        g_free (str);
+        return TRUE;
+    } else {
+        *string = NULL;
+
+        return FALSE;
+    }
+}
+
+
+boolean scim_bridge_client_imcontext_delete_surrounding_text (ScimBridgeClientIMContext *imcontext, int offset, int length)
+{
+    boolean retval = gtk_im_context_delete_surrounding (GTK_IM_CONTEXT (imcontext), offset, length);
+    return retval;
+}
+
+
+boolean scim_bridge_client_imcontext_replace_surrounding_text (ScimBridgeClientIMContext *imcontext, int cursor_position, const char *string)
+{
+    gtk_im_context_set_surrounding_with_selection (GTK_IM_CONTEXT (imcontext),
+                                                   string,
+                                                   -1,
+                                                   cursor_position,
+                                                   cursor_position);
+    return TRUE;
+}
+
+void scim_bridge_client_imcontext_forward_key_event (ScimBridgeClientIMContext *imcontext, const ScimBridgeKeyEvent *key_event)
+{ 
+    scim_bridge_pdebugln (7, "scim_bridge_client_imcontext_forward_key_event: NOT IMPLEMENTED YET");
+}
+
+
+void scim_bridge_client_imcontext_imengine_status_changed (ScimBridgeClientIMContext *imcontext, boolean enabled)
+{
+    if (imcontext->preedit_shown) {
+        if (imcontext->enabled) {
+            scim_bridge_client_imcontext_set_preedit_shown (imcontext, FALSE);
+            scim_bridge_client_imcontext_update_preedit (imcontext);
+        }
+    }
+    imcontext->enabled = enabled;
+}
+
+
+/* Class Implementations */
+void scim_bridge_client_imcontext_static_initialize ()
+{
+    GdkRGBA color;
+    g_warn_if_fail(gdk_rgba_parse (&color, "gray92"));
+    preedit_normal_background = (Color){color.red * 65535, color.green * 65535, color.blue * 65535};
+    g_warn_if_fail(gdk_rgba_parse (&color, "black"));
+    preedit_normal_foreground = (Color){color.red * 65535, color.green * 65535, color.blue * 65535};
+    g_warn_if_fail(gdk_rgba_parse (&color, "light blue"));
+    preedit_active_background = (Color){color.red * 65535, color.green * 65535, color.blue * 65535};
+    g_warn_if_fail(gdk_rgba_parse (&color, "black"));
+    preedit_active_foreground = (Color){color.red * 65535, color.green * 65535, color.blue * 65535};
+
+    focused_imcontext = NULL;
+}
+
+
+void scim_bridge_client_imcontext_static_finalize ()
+{
+    focused_imcontext = NULL;
+}
+
+
+void scim_bridge_client_imcontext_connection_opened ()
+{
+}
+
+
+void scim_bridge_client_imcontext_connection_closed ()
+{
+    if (focused_imcontext != NULL) scim_bridge_client_imcontext_set_preedit_shown (focused_imcontext, FALSE);
+}
+
+
+void scim_bridge_client_imcontext_class_initialize (ScimBridgeClientIMContextClass *klass, gpointer *klass_data)
+{
+    root_klass = (GObjectClass *) g_type_class_peek_parent (klass);
+
+    GtkIMContextClass *gtk_im_klass = GTK_IM_CONTEXT_CLASS (klass);
+    gtk_im_klass->set_client_widget = scim_bridge_client_imcontext_set_client_widget;
+    gtk_im_klass->filter_keypress = scim_bridge_client_imcontext_filter_key_event;
+    gtk_im_klass->reset = scim_bridge_client_imcontext_reset;
+    gtk_im_klass->get_preedit_string = scim_bridge_client_imcontext_get_preedit_string;
+    gtk_im_klass->focus_in  = scim_bridge_client_imcontext_focus_in;
+    gtk_im_klass->focus_out = scim_bridge_client_imcontext_focus_out;
+    gtk_im_klass->set_cursor_location = scim_bridge_client_imcontext_set_cursor_location;
+    gtk_im_klass->set_use_preedit = scim_bridge_client_imcontext_set_preedit_enabled;
+
+    GObjectClass *gobject_klass = G_OBJECT_CLASS (klass);
+    gobject_klass->finalize = scim_bridge_client_imcontext_finalize;
+}
+
+
+GType scim_bridge_client_imcontext_get_type ()
+{
+    return class_type;
+}
+
+
+void scim_bridge_client_imcontext_register_type (GIOModule *io_module)
+{
+    scim_bridge_pdebugln (2, "scim_bridge_client_imcontext_register_type ()");
+
+    static const GTypeInfo klass_info = {
+        sizeof (ScimBridgeClientIMContextClass),
+        /* no base class initializer */
+        NULL,
+        /* no base class finalizer */
+        NULL,
+        /* class initializer */
+        (GClassInitFunc) scim_bridge_client_imcontext_class_initialize,
+        /* no class finalizer */
+        NULL,
+        /* no class data */
+        NULL,
+        sizeof (ScimBridgeClientIMContext),
+        0,
+        /* object initizlier */
+        (GInstanceInitFunc) scim_bridge_client_imcontext_initialize,
+        0
+    };
+
+    class_type = g_type_module_register_type (G_TYPE_MODULE(io_module),
+                                              GTK_TYPE_IM_CONTEXT,
+                                              "ScimBridgeClientIMContext",
+                                              &klass_info,
+                                              0);
+}
+
+
+GtkIMContext *scim_bridge_client_imcontext_new ()
+{
+    scim_bridge_pdebugln (4, "scim_bridge_client_imcontext_new ()");
+
+    ScimBridgeClientIMContext *ic = SCIM_BRIDGE_CLIENT_IMCONTEXT (g_object_new (GTK_TYPE_SCIM_CLIENT_IMCONTEXT, NULL));
+    return GTK_IM_CONTEXT (ic);
+}
+
+
+void scim_bridge_client_imcontext_initialize (ScimBridgeClientIMContext *imcontext, ScimBridgeClientIMContextClass *klass)
+{
+    scim_bridge_pdebugln (5, "scim_bridge_client_imcontext_initialize  ()");
+
+    /* slave exists for using gtk+'s table based input method */
+    imcontext->slave_preedit = FALSE;
+    imcontext->slave = gtk_im_context_simple_new ();
+    g_signal_connect(G_OBJECT(imcontext->slave),
+                     "commit",
+                     G_CALLBACK(gtk_im_slave_commit_cb),
+                     imcontext);
+    
+    g_signal_connect(G_OBJECT(imcontext->slave),
+                     "preedit-changed",
+                     G_CALLBACK(gtk_im_slave_preedit_changed_cb),
+                     imcontext);
+    
+    g_signal_connect(G_OBJECT(imcontext->slave),
+                     "preedit-start",
+                     G_CALLBACK(gtk_im_slave_preedit_start_cb),
+                     imcontext);
+    
+    g_signal_connect(G_OBJECT(imcontext->slave),
+                     "preedit-end",
+                     G_CALLBACK(gtk_im_slave_preedit_end_cb),
+                     imcontext);
+
+    imcontext->preedit_shown = FALSE;
+    imcontext->preedit_started = FALSE;
+    
+    imcontext->preedit_cursor_position = 0;
+    imcontext->preedit_cursor_flicking = FALSE;
+
+    imcontext->preedit_string = malloc (sizeof (char));
+    imcontext->preedit_string[0] = '\0';
+    imcontext->preedit_string_capacity = 0;
+
+    imcontext->preedit_attributes = NULL;
+
+    imcontext->commit_string = malloc (sizeof (char));
+    imcontext->commit_string[0] = '\0';
+    imcontext->commit_string_capacity = 0;
+
+    imcontext->enabled = FALSE;
+
+    imcontext->client_widget = NULL;
+
+    imcontext->id = -1;
+
+    if (!scim_bridge_client_is_messenger_opened ()) {
+        scim_bridge_perrorln ("The messenger is now down");
+    } else if (scim_bridge_client_register_imcontext (imcontext)) {
+        scim_bridge_perrorln ("Failed to register the IMContext");
+    } else {
+        scim_bridge_pdebugln (1, "IMContext registered: id = %d", imcontext->id);
+    }
+}
+
+
+void scim_bridge_client_imcontext_finalize (GObject *object)
+{
+    scim_bridge_pdebugln (5, "scim_bridge_client_imcontext_finalize ()");
+
+    ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (object);
+
+    if (imcontext == focused_imcontext) scim_bridge_client_imcontext_focus_out (GTK_IM_CONTEXT (imcontext));
+
+    if (!scim_bridge_client_is_messenger_opened ()) {
+        scim_bridge_perrorln ("The messenger is now down");
+    } else if (scim_bridge_client_deregister_imcontext (imcontext)) {
+        scim_bridge_perrorln ("Failed to deregister an IMContext");
+    } else {
+        scim_bridge_pdebugln (3, "IMContext deregistered: id = %d", imcontext->id);
+    }
+
+    if (imcontext->client_widget) g_object_unref (imcontext->client_widget);
+
+    free (imcontext->preedit_string);
+    free (imcontext->commit_string);
+
+    if (imcontext->preedit_attributes != NULL)
+        pango_attr_list_unref (imcontext->preedit_attributes);
+    
+    imcontext->preedit_attributes = NULL;
+    
+    g_signal_handlers_disconnect_by_func(imcontext->slave,
+                                         (void *)gtk_im_slave_commit_cb,
+                                         (void *)imcontext);
+    g_signal_handlers_disconnect_by_func(imcontext->slave,
+                                         (void *)gtk_im_slave_preedit_changed_cb,
+                                         (void *)imcontext);
+    g_signal_handlers_disconnect_by_func(imcontext->slave,
+                                         (void *)gtk_im_slave_preedit_start_cb,
+                                         (void *)imcontext);
+    g_signal_handlers_disconnect_by_func(imcontext->slave,
+                                         (void *)gtk_im_slave_preedit_end_cb,
+                                         (void *)imcontext);
+    g_object_unref(imcontext->slave);
+
+    root_klass->finalize (object);
+}
+
+
+/* Class functions */
+gboolean scim_bridge_client_imcontext_filter_key_event (GtkIMContext *context, GdkEvent *event)
+{
+    scim_bridge_pdebugln (8, "scim_bridge_client_imcontext_filter_key_event ()");
+
+    ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context);
+
+    boolean ret = FALSE;
+    if (imcontext) {
+        ret = key_snooper(0, event, 0);
+
+        if (imcontext->slave) {
+            if (!ret) {
+                ret = gtk_im_context_filter_keypress (imcontext->slave, event);
+            } else if (imcontext->slave_preedit) {
+                imcontext->slave_preedit = FALSE;
+                gtk_im_context_reset (imcontext->slave);
+            }
+        }
+
+    }
+
+    return ret;
+}
+
+
+void scim_bridge_client_imcontext_reset (GtkIMContext *context)
+{
+    scim_bridge_pdebugln (8, "scim_bridge_client_imcontext_reset ()");
+
+    ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context);
+
+    if (imcontext != focused_imcontext) return;
+
+    if (scim_bridge_client_is_messenger_opened () && imcontext != NULL) {
+        if (scim_bridge_client_reset_imcontext (imcontext)) {
+            scim_bridge_perrorln ("An IOException occurred at scim_bridge_client_imcontext_reset ()");
+        }
+    }
+}
+
+
+void scim_bridge_client_imcontext_get_preedit_string (GtkIMContext *context, gchar **str, PangoAttrList **pango_attrs, gint *cursor_pos)
+{
+    scim_bridge_pdebugln (4, "scim_bridge_client_imcontext_get_preedit_string ()");
+
+    ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context);
+
+    if (imcontext->slave_preedit) {
+        gtk_im_context_get_preedit_string (imcontext->slave, str, pango_attrs, cursor_pos);
+        return;
+    }
+    
+    if (scim_bridge_client_is_messenger_opened () && imcontext != NULL && imcontext->preedit_shown) {
+        const size_t preedit_string_length = strlen (imcontext->preedit_string);
+        const size_t preedit_wstring_length = g_utf8_strlen (imcontext->preedit_string, -1);
+        
+        if (str) {
+            if (preedit_string_length > 0) {
+                *str = g_strdup (imcontext->preedit_string);
+            } else {
+                *str = g_strdup ("");
+            }
+        }
+        
+        if (cursor_pos) {
+            if (imcontext->preedit_cursor_position > preedit_wstring_length) {
+                *cursor_pos = preedit_wstring_length;
+            } else {
+                *cursor_pos = imcontext->preedit_cursor_position;
+            }
+        }
+        
+        if (pango_attrs) {
+            *pango_attrs = imcontext->preedit_attributes;
+            pango_attr_list_ref (imcontext->preedit_attributes);
+        }
+    } else {
+        if (str) *str = g_strdup ("");
+        if (cursor_pos) *cursor_pos = 0;
+        if (pango_attrs) *pango_attrs = pango_attr_list_new ();
+    }
+}
+
+
+void scim_bridge_client_imcontext_focus_in (GtkIMContext *context)
+{
+    scim_bridge_pdebugln (8, "scim_bridge_client_imcontext_focus_in ()");
+
+    ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context);
+    
+    if (focused_imcontext != NULL && focused_imcontext != imcontext) {
+        scim_bridge_client_imcontext_focus_out (GTK_IM_CONTEXT (focused_imcontext));
+    }
+    focused_imcontext = imcontext;
+
+    if (!scim_bridge_client_is_messenger_opened () && scim_bridge_client_is_reconnection_enabled ()) {
+        scim_bridge_pdebugln (7, "Trying to open the connection again...");
+        scim_bridge_client_open_messenger ();
+    }
+
+    if (scim_bridge_client_is_messenger_opened () && imcontext != NULL) {
+        if (scim_bridge_client_change_focus (imcontext, TRUE)) {
+            scim_bridge_perrorln ("An IOException occurred at scim_bridge_client_imcontext_focus_in ()");
+        }
+    }
+}
+
+
+void scim_bridge_client_imcontext_focus_out (GtkIMContext *context)
+{
+    scim_bridge_pdebugln (8, "scim_bridge_client_imcontext_focus_out ()");
+
+    ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context);
+    focused_widget = NULL;
+
+    focused_imcontext = imcontext;
+    if (imcontext->preedit_shown) {
+        if (imcontext->enabled) {
+            scim_bridge_client_imcontext_set_preedit_shown (imcontext, FALSE);
+            scim_bridge_client_imcontext_update_preedit (imcontext);
+        }
+    }
+    if (scim_bridge_client_is_messenger_opened () && imcontext != NULL) {
+        if (scim_bridge_client_change_focus (imcontext, FALSE)) {
+            scim_bridge_perrorln ("An IOException occurred at scim_bridge_client_imcontext_focus_out ()");
+        }
+    }
+
+    focused_imcontext = NULL;
+}
+
+
+void scim_bridge_client_imcontext_set_client_widget (GtkIMContext *context, GtkWidget *new_widget)
+{
+    scim_bridge_pdebugln (7, "scim_bridge_client_imcontext_set_client_widget ()");
+
+    ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context);
+    if (!imcontext) {
+        return;
+    }
+
+    if (imcontext->client_widget) {
+        g_object_unref(imcontext->client_widget);
+    }
+
+    imcontext->client_widget = new_widget;
+    if (!new_widget) {
+        return;
+    }
+
+    g_object_ref (new_widget);
+    widget_get_origin (imcontext->client_widget, &imcontext->window_x, &imcontext->window_y);
+}
+
+void scim_bridge_client_imcontext_set_cursor_location (GtkIMContext *context, GdkRectangle *area)
+{
+    scim_bridge_pdebugln (4, "scim_bridge_client_imcontext_set_cursor_location ()");
+
+    ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context);
+    if (imcontext->preedit_cursor_flicking)
+        return;
+
+    if (imcontext != NULL && imcontext->client_widget != NULL) {
+        const int new_cursor_x = area->x + area->width;
+        const int new_cursor_y = area->y + area->height + 8;
+
+        int widget_x, widget_y;
+        widget_get_origin(imcontext->client_widget, &widget_x, &widget_y);
+
+        if (set_cursor_location (imcontext, widget_x, widget_y, new_cursor_x, new_cursor_y)) {
+            scim_bridge_perrorln ("An IOException occurred at scim_bridge_client_imcontext_set_cursor_location ()");
+        }
+    }
+}
+
+
+void scim_bridge_client_imcontext_set_preedit_enabled (GtkIMContext *context, gboolean enabled)
+{
+    scim_bridge_pdebugln (8, "scim_bridge_client_imcontext_set_preedit_enabled ()");
+
+    ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context);
+
+    if (imcontext != NULL) {
+        if (scim_bridge_client_is_messenger_opened ()) {
+            if (scim_bridge_client_set_preedit_mode (imcontext, enabled ? PREEDIT_EMBEDDED:PREEDIT_ANY)) {
+                scim_bridge_perrorln ("An IOException occurred at scim_bridge_client_imcontext_set_preedit_enabled ()");
+            }
+        }
+    }
+}
+
+static void
+gtk_im_slave_commit_cb (
+    GtkIMContext *context,
+    const char *str,
+    ScimBridgeClientIMContext *imcontext
+) {
+    g_return_if_fail(str);
+    g_signal_emit_by_name(imcontext, "commit", str);
+}
+
+static void
+gtk_im_slave_preedit_changed_cb (
+    GtkIMContext *context,
+    ScimBridgeClientIMContext *imcontext
+) {
+    imcontext->slave_preedit = TRUE;
+    g_signal_emit_by_name(imcontext, "preedit-changed");
+}
+
+static void
+gtk_im_slave_preedit_start_cb (
+    GtkIMContext *context,
+    ScimBridgeClientIMContext *imcontext
+) {
+    imcontext->slave_preedit = TRUE;
+    g_signal_emit_by_name(imcontext, "preedit-start");
+}
+
+static void
+gtk_im_slave_preedit_end_cb (
+    GtkIMContext *context,
+    ScimBridgeClientIMContext *imcontext
+) {
+    imcontext->slave_preedit = FALSE;
+    g_signal_emit_by_name(imcontext, "preedit-end");
+}
\ No newline at end of file
diff --git a/extras/immodules/client-gtk/gtk4/scim-bridge-client-imcontext-gtk.h b/extras/immodules/client-gtk/gtk4/scim-bridge-client-imcontext-gtk.h
new file mode 100644
index 0000000..909871c
--- /dev/null
+++ b/extras/immodules/client-gtk/gtk4/scim-bridge-client-imcontext-gtk.h
@@ -0,0 +1,111 @@
+/*
+ * SCIM Bridge
+ *
+ * Copyright (c) 2006 Ryo Dairiki <ryo-dairiki@users.sourceforge.net>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation and 
+ * appearing in the file LICENSE.LGPL included in the package of this file.
+ * You can also redistribute it and/or modify it under the terms of 
+ * the GNU General Public License as published by the Free Software Foundation and 
+ * appearing in the file LICENSE.GPL included in the package of this file.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+/**
+ * @file
+ * @author Ryo Dairiki <ryo-dairiki@users.sourceforge.net>
+ * @brief This is the header for gtk imcontext of scim-bridge.
+ */
+
+
+#ifndef SCIMBRIDGECLIENTIMCONTEXTGTK_H_
+#define SCIMBRIDGECLIENTIMCONTEXTGTK_H_
+
+#include <gtk/gtk.h>
+#if GTK_CHECK_VERSION(3, 0, 0)
+#else
+#include <gtk/gtkimcontext.h>
+#endif
+
+#include <gdk/gdk.h>
+
+#include "scim-bridge.h"
+#include "scim-bridge-client-imcontext.h"
+
+#define GTK_TYPE_SCIM_CLIENT_IMCONTEXT	(scim_bridge_client_imcontext_get_type ())
+#if GTK_CHECK_VERSION(3, 0, 0)
+#define SCIM_BRIDGE_CLIENT_IMCONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SCIM_CLIENT_IMCONTEXT, ScimBridgeClientIMContext))
+#else
+#define SCIM_BRIDGE_CLIENT_IMCONTEXT(obj) (GTK_CHECK_CAST ((obj), GTK_TYPE_SCIM_CLIENT_IMCONTEXT, ScimBridgeClientIMContext))
+#endif
+#define SCIM_BRIDGE_CLIENT_IMCONTEXT_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GTK_TYPE_SCIM_CLIENT_IMCONTEXT, ScimBridgeClientIMContextClass))
+#if GTK_CHECK_VERSION(3, 0, 0)
+#define IS_SCIM_BRIDGE_CLIENT_IMCONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SCIM_CLIENT_IMCONTEXT))
+#else
+#define IS_SCIM_BRIDGE_CLIENT_IMCONTEXT(class) (GTK_CHECK_TYPE ((obj), GTK_TYPE_SCIM_CLIENT_IMCONTEXT))
+#endif
+#define IS_SCIM_BRIDGE_CLIENT_IMCONTEXT_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SCIM_CLIENT_IMCONTEXT))
+#if GTK_CHECK_VERSION(3, 0, 0)
+#define SCIM_BRIDGE_CLIENT_IMCONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SCIM_CLIENT_IMCONTEXT, ScimBridgeClientIMContextClass))
+#else
+#define SCIM_BRIDGE_CLIENT_IMCONTEXT_GET_CLASS(obj) (GTK_CHECK_GET_CLASS ((obj), GTK_TYPE_SCIM_CLIENT_IMCONTEXT, ScimBridgeClientIMContextClass))
+#endif
+
+struct _ScimBridgeClientIMContextClass
+{
+    GtkIMContextClass parent_class;
+};
+
+/**
+ * IMContext class for gtk client.
+ */
+typedef struct _ScimBridgeClientIMContextClass ScimBridgeClientIMContextClass;
+
+/**
+ * Initialize IMContext class itself.
+ */
+void scim_bridge_client_imcontext_static_initialize ();
+
+/**
+ * Finalize IMContext class itself.
+ */
+void scim_bridge_client_imcontext_static_finalize ();
+
+/**
+ * The connection with the agent is opened.
+ */
+void scim_bridge_client_imcontext_connection_opened ();
+
+/**
+ * The connection with the agent is closed.
+ */
+void scim_bridge_client_imcontext_connection_closed ();
+
+/**
+ * Allocate an IMContext.
+ *
+ * @return new IMContext.
+ */
+GtkIMContext *scim_bridge_client_imcontext_new ();
+
+/**
+ * Get the type value of IMContexts.
+ *
+ * @return The type value of IMContexts.
+ */
+GType scim_bridge_client_imcontext_get_type ();
+
+/**
+ * Register the type value for IMContexts.
+ *
+ * @param type_module The type module.
+ */
+void scim_bridge_client_imcontext_register_type (GIOModule *io_module);
+
+#endif                                            /*SCIMBRIDGECLIENTIMCONTEXTGTK_H_*/
diff --git a/extras/immodules/client-gtk/gtk4/scim-bridge-client-key-event-utility-gtk.c b/extras/immodules/client-gtk/gtk4/scim-bridge-client-key-event-utility-gtk.c
new file mode 100644
index 0000000..318f7e4
--- /dev/null
+++ b/extras/immodules/client-gtk/gtk4/scim-bridge-client-key-event-utility-gtk.c
@@ -0,0 +1,132 @@
+/*
+ * SCIM Bridge
+ *
+ * Copyright (c) 2006 Ryo Dairiki <ryo-dairiki@users.sourceforge.net>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation and 
+ * appearing in the file LICENSE.LGPL included in the package of this file.
+ * You can also redistribute it and/or modify it under the terms of 
+ * the GNU General Public License as published by the Free Software Foundation and 
+ * appearing in the file LICENSE.GPL included in the package of this file.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include <sys/time.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+
+#ifdef GDK_WINDOWING_X11 
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <X11/keysym.h>
+#include <gdk/x11/gdkx.h>
+#endif
+
+#include "scim-bridge-client-key-event-utility-gtk.h"
+#include "scim-bridge-key-event.h"
+
+/* Implementations */
+void scim_bridge_key_event_bridge_to_gdk (GdkEvent *gdk_key_event,
+                                          GtkWidget *client_widget,
+                                          const ScimBridgeKeyEvent *key_event)
+{
+//    gdk_key_event->state = 0;
+//    if (scim_bridge_key_event_is_shift_down (key_event)) gdk_key_event->state |= GDK_SHIFT_MASK;
+//    if (scim_bridge_key_event_is_caps_lock_down (key_event)) gdk_key_event->state |= GDK_LOCK_MASK;
+//    if (scim_bridge_key_event_is_control_down (key_event)) gdk_key_event->state |= GDK_CONTROL_MASK;
+//    if (scim_bridge_key_event_is_alt_down (key_event)) gdk_key_event->state |= GDK_MOD1_MASK;
+//    if (scim_bridge_key_event_is_num_lock_down (key_event)) gdk_key_event->state |= GDK_MOD2_MASK;
+//
+//    if (scim_bridge_key_event_is_pressed (key_event)) {
+//        gdk_key_event->type = GDK_KEY_PRESS;
+//    } else {
+//        gdk_key_event->type = GDK_KEY_RELEASE;
+//        gdk_key_event->state |= GDK_RELEASE_MASK;
+//    }
+//
+//    gdk_key_event->widget = client_widget;
+//
+//    struct timeval current_time;
+//    gettimeofday (&current_time, NULL);
+//
+//    gdk_key_event->time = current_time.tv_sec * 1000 + current_time.tv_usec / 1000;
+//    gdk_key_event->keyval = scim_bridge_key_event_get_code (key_event);
+//    gdk_key_event->length = 0;
+//    gdk_key_event->string = 0;
+//
+//    GdkKeymap *key_map = get_gdk_keymap (gdk_key_event->widget);
+//
+//    GdkKeymapKey *keys;
+//    gint n_keys;
+//
+//    if (gdk_keymap_get_entries_for_keyval (key_map, gdk_key_event->keyval, &keys, &n_keys)) {
+//        gdk_key_event->hardware_keycode = keys[0].keycode;
+//        gdk_key_event->group = keys [0].group;
+//    } else {
+//        gdk_key_event->hardware_keycode = 0;
+//        gdk_key_event->group = 0;
+//    }
+}
+
+
+void scim_bridge_key_event_gdk_to_bridge (ScimBridgeKeyEvent *bridge_key_event,
+                                          GtkWidget *widget,
+                                          const GdkEvent *key_event)
+{
+    // Use Key Symbole provided by gtk.
+    guint keyval = gdk_key_event_get_keyval((GdkEvent *) key_event);
+    GdkModifierType state = gdk_event_get_modifier_state((GdkEvent *) key_event);
+    scim_bridge_key_event_set_code (bridge_key_event, (scim_bridge_key_code_t) keyval);
+
+    scim_bridge_key_event_clear_modifiers (bridge_key_event);
+    if (state & GDK_SHIFT_MASK || keyval == GDK_KEY_Shift_L || keyval == GDK_KEY_Shift_R)
+        scim_bridge_key_event_set_shift_down (bridge_key_event, TRUE);
+    if (state & GDK_LOCK_MASK || keyval == GDK_KEY_Caps_Lock)
+        scim_bridge_key_event_set_caps_lock_down (bridge_key_event, TRUE);
+    if (state & GDK_CONTROL_MASK || keyval == GDK_KEY_Control_L || keyval == GDK_KEY_Control_R)
+        scim_bridge_key_event_set_control_down (bridge_key_event, TRUE);
+    if (state & GDK_ALT_MASK || keyval == GDK_KEY_Alt_L || keyval == GDK_KEY_Alt_R)
+        scim_bridge_key_event_set_alt_down (bridge_key_event, TRUE);
+    if (gdk_device_get_num_lock_state(gdk_event_get_device((GdkEvent *) key_event)))
+        scim_bridge_key_event_set_num_lock_down (bridge_key_event, TRUE);
+
+
+    if (gdk_event_get_event_type((GdkEvent *) key_event) != GDK_KEY_RELEASE) {
+        scim_bridge_key_event_set_pressed (bridge_key_event, TRUE);
+    } else {
+        scim_bridge_key_event_set_pressed (bridge_key_event, FALSE);
+    }
+    
+#ifdef GDK_WINDOWING_X11
+    GdkX11Display *display = NULL;
+
+    if (widget != NULL) {
+        display = GDK_X11_DISPLAY (gtk_widget_get_display(widget));
+    } else {
+        display = GDK_X11_DISPLAY (gdk_display_get_default ());
+    }
+    
+    if (scim_bridge_key_event_get_code (bridge_key_event) == SCIM_BRIDGE_KEY_CODE_backslash) {
+        boolean kana_ro = FALSE;
+        int keysym_size = 0;
+        KeySym *keysyms = XGetKeyboardMapping (
+            gdk_x11_display_get_xdisplay(display),
+            gdk_key_event_get_keycode((GdkEvent *) key_event),
+            1,
+            &keysym_size);
+        if (keysyms != NULL) {
+            kana_ro = (keysyms[0] == XK_backslash && keysyms[1] == XK_underscore);
+            XFree (keysyms);
+        }
+        scim_bridge_key_event_set_quirk_enabled (bridge_key_event, SCIM_BRIDGE_KEY_QUIRK_KANA_RO, kana_ro);
+    }
+#endif
+}
diff --git a/extras/immodules/client-gtk/gtk4/scim-bridge-client-key-event-utility-gtk.h b/extras/immodules/client-gtk/gtk4/scim-bridge-client-key-event-utility-gtk.h
new file mode 100644
index 0000000..d5fd211
--- /dev/null
+++ b/extras/immodules/client-gtk/gtk4/scim-bridge-client-key-event-utility-gtk.h
@@ -0,0 +1,54 @@
+/*
+ * SCIM Bridge
+ *
+ * Copyright (c) 2006 Ryo Dairiki <ryo-dairiki@users.sourceforge.net>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation and 
+ * appearing in the file LICENSE.LGPL included in the package of this file.
+ * You can also redistribute it and/or modify it under the terms of 
+ * the GNU General Public License as published by the Free Software Foundation and 
+ * appearing in the file LICENSE.GPL included in the package of this file.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+/**
+ * @file
+ * @author Ryo Dairiki <ryo-dairiki@users.sourceforge.net>
+ * @brief This is the header of the functions to translate key events between scim-bridge and gtk.
+ */
+
+
+#ifndef SCIMBRIDGECLIENTKEYEVENTUTILITYGTK_H_
+#define SCIMBRIDGECLIENTKEYEVENTUTILITYGTK_H_
+
+#include <gdk/gdk.h>
+
+#include "scim-bridge.h"
+#include "scim-bridge-key-event.h"
+
+/**
+ * Translate a key event from scim-bridge into gdk.
+ *
+ * @param gdk_key_event A key event of gdk.
+ * @param client_wiget The gdk window for the key event.
+ * @param key_event The key event from scim-bridge.
+ */
+void scim_bridge_key_event_bridge_to_gdk (GdkEvent *gdk_key_event, GtkWidget *client_wiget, const ScimBridgeKeyEvent *key_event);
+
+
+/**
+ * Translate a key event from gdk into scim-bridge.
+ *
+ * @param bridge_key_event A key event of scim-bridge.
+ * @param client_wiget The gdk window for the key event.
+ * @param key_event The key event from gdk.
+ */
+void scim_bridge_key_event_gdk_to_bridge (ScimBridgeKeyEvent *bridge_key_event, GtkWidget *client_wiget, const GdkEvent *key_event);
+
+#endif                                            /*SCIMBRIDGECLIENTKEYEVENTUTILITYGTK_H_*/
diff --git a/extras/immodules/configure.ac b/extras/immodules/configure.ac
index 5481f3e..4959dac 100644
--- a/extras/immodules/configure.ac
+++ b/extras/immodules/configure.ac
@@ -65,6 +65,10 @@ PKG_CHECK_MODULES(GTK3, [gtk+-3.0 >= 3.0.0 pango >= 1.24.0 gdk-pixbuf-2.0 >= 2.2
 			[SCIM_BRIDGE_HAS_GTK3=yes],
 			[SCIM_BRIDGE_HAS_GTK3=no])
 
+PKG_CHECK_MODULES(GTK4, [gtk4 >= 4.0.0 pango >= 1.48.0 gdk-pixbuf-2.0 >= 2.42.0],
+			[SCIM_BRIDGE_HAS_GTK4=yes],
+			[SCIM_BRIDGE_HAS_GTK4=no])
+
 SCIM_BRIDGE_HAS_GTK2_2=no
 if test "$SCIM_BRIDGE_HAS_GTK2" = "yes"; then
   if $PKG_CONFIG --exists "gtk+-2.0 >= 2.2" ; then
@@ -98,6 +102,17 @@ if test "$SCIM_BRIDGE_HAS_GTK3" = "yes"; then
   AC_SUBST(GTK3_BINARY_VERSION)
 fi
 
+if test "$SCIM_BRIDGE_HAS_GTK4" = "yes"; then
+  GTK4_BINARY_VERSION=`$PKG_CONFIG --variable=gtk_binary_version gtk4`
+  GTK4_LIBDIR=`$PKG_CONFIG --variable=libdir gtk4`
+  if test -z "$GTK4_LIBDIR)"; then
+    GTK4_LIBDIR="$libdir"
+  fi
+  AC_SUBST(GTK4_LIBDIR)
+  AC_SUBST(GTK4_VERSION)
+  AC_SUBST(GTK4_BINARY_VERSION)
+fi
+
 # Check if we should build scim-bridge-qt-immodule
 PKG_CHECK_MODULES(QT3, [qt-mt >= 3.3],
 			[SCIM_BRIDGE_HAS_QT3=yes],
@@ -162,6 +177,10 @@ AC_ARG_ENABLE(gtk3-immodule,
 	      [AS_HELP_STRING([--enable-gtk3-immodule], [build GTK3 IM module])], , 
 		  enable_gtk3_immodule=yes)
 
+AC_ARG_ENABLE(gtk4-immodule,
+	      [AS_HELP_STRING([--enable-gtk4-immodule], [build GTK4 IM module])], , 
+		  enable_gtk4_immodule=yes)
+
 AC_ARG_ENABLE(qt3-immodule,
 	      [AS_HELP_STRING([--enable-qt3-immodule], [build Qt3 IM module])], , 
 		  enable_qt3_immodule=yes)
@@ -224,6 +243,15 @@ else
   enable_gtk3_immodule=no
 fi
 
+if test "$enable_gtk4_immodule" = "yes" -a "$SCIM_BRIDGE_HAS_GTK4" = "yes"; then
+  SCIM_BRIDGE_BUILD_GTK4_IMMODULE=1
+  SCIM_BRIDGE_BUILD_IMMODULE=1
+  enable_immodule=yes
+else
+  SCIM_BRIDGE_BUILD_GTK4_IMMODULE=0
+  enable_gtk4_immodule=no
+fi
+
 if test "$enable_qt3_immodule" = "yes" -a "$SCIM_BRIDGE_HAS_QT3" = "yes"; then
   SCIM_BRIDGE_BUILD_QT3_IMMODULE=1
   SCIM_BRIDGE_BUILD_IMMODULE=1
@@ -269,6 +297,9 @@ AM_CONDITIONAL(SCIM_BRIDGE_BUILD_GTK2_IMMODULE,
 AM_CONDITIONAL(SCIM_BRIDGE_BUILD_GTK3_IMMODULE,
 		[test "$enable_gtk3_immodule" = "yes"])
 
+AM_CONDITIONAL(SCIM_BRIDGE_BUILD_GTK4_IMMODULE,
+		[test "$enable_gtk4_immodule" = "yes"])
+
 AM_CONDITIONAL(SCIM_BRIDGE_BUILD_QT3_IMMODULE,
 		[test "$enable_qt3_immodule" = "yes"])
 
@@ -285,6 +316,7 @@ AC_SUBST(SCIM_BRIDGE_BUILD_DOCUMENTS)
 AC_SUBST(SCIM_BRIDGE_BUILD_AGENT)
 AC_SUBST(SCIM_BRIDGE_BUILD_GTK2_IMMODULE)
 AC_SUBST(SCIM_BRIDGE_BUILD_GTK3_IMMODULE)
+AC_SUBST(SCIM_BRIDGE_BUILD_GTK4_IMMODULE)
 AC_SUBST(SCIM_BRIDGE_BUILD_QT3_IMMODULE)
 AC_SUBST(SCIM_BRIDGE_BUILD_QT4_IMMODULE)
 AC_SUBST(SCIM_BRIDGE_BUILD_CLUTTER_IMMODULE)
@@ -343,6 +375,7 @@ Module options:
   Build Agent              $enable_agent
   Build GTK2 IMModule      $enable_gtk2_immodule
   Build GTK3 IMModule      $enable_gtk3_immodule
+  Build GTK4 IMModule      $enable_gtk4_immodule
   Build QT3 IMModule       $enable_qt3_immodule
   Build QT4 IMModule       $enable_qt4_immodule
   Build CLUTTER IMModule   $enable_clutter_immodule
-- 
2.37.3